Merge branch 'main' into toolbar

This commit is contained in:
Dharini Jeeva authored and GitHub committed 2021-10-26 09:15:07 -07:00
commit 95bea7a83b
73 files changed
+1060 -740

No files matched your search

-3
View File
@@ -12,8 +12,5 @@ insert_final_newline = true
indent_style = space
indent_size = 2
[*.hbs]
insert_final_newline = false
[*.{diff,md}]
trim_trailing_whitespace = false
+5
View File
@@ -58,5 +58,10 @@ module.exports = {
plugins: ['node'],
extends: ['plugin:node/recommended'],
},
{
// Test files:
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
},
],
};
@@ -1,20 +1,46 @@
{{!
Ember's LinkTo component won't accept nullish @query or @model, so we have to
overcompensate by duplicating it four times, one for every combination.
This is really unpleasant.
}}
{{#if @model}}
<LinkTo
...attributes
@route={{@route}}
@query={{@query}}
@model={{@model}}
class="rose-dropdown-link {{if @style (concat "rose-dropdown-link-" @style)}}"
>
{{yield}}
</LinkTo>
{{#if @query}}
<LinkTo
...attributes
@route={{@route}}
@query={{@query}}
@model={{@model}}
class="rose-dropdown-link {{if @style (concat "rose-dropdown-link-" @style)}}"
>
{{yield}}
</LinkTo>
{{else}}
<LinkTo
...attributes
@route={{@route}}
@model={{@model}}
class="rose-dropdown-link {{if @style (concat "rose-dropdown-link-" @style)}}"
>
{{yield}}
</LinkTo>
{{/if}}
{{else}}
<LinkTo
...attributes
@route={{@route}}
@query={{@query}}
class="rose-dropdown-link {{if @style (concat "rose-dropdown-link-" @style)}}"
>
{{yield}}
</LinkTo>
{{#if @query}}
<LinkTo
...attributes
@route={{@route}}
@query={{@query}}
class="rose-dropdown-link {{if @style (concat "rose-dropdown-link-" @style)}}"
>
{{yield}}
</LinkTo>
{{else}}
<LinkTo
...attributes
@route={{@route}}
class="rose-dropdown-link {{if @style (concat "rose-dropdown-link-" @style)}}"
>
{{yield}}
</LinkTo>
{{/if}}
{{/if}}
@@ -0,0 +1,12 @@
{{#each @items as |item|}}
{{yield (hash
item=item
checkbox=(component 'rose/form/checkbox'
name=@name
disabled=@disabled
inline=@inline
checked=(includes item @selectedItems)
onChange=(fn this.toggleItem item)
)
)}}
{{/each}}
@@ -0,0 +1,37 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
/**
* A checkbox group represents an array of arbitrary items, which differs from
* single checkboxes which represent boolean values. When checkboxes within a
* checkbox are toggled, their associated item is toggled within the group's
* array value.
*
* The term "item" is used to disambiguate it from "value", which has an
* existing meaning in the context of form fields. However, items
* are just arbitrary values.
*/
export default class RoseFormCheckboxGroupComponent extends Component {
// =actions
/**
* Returns a new copy of the @selectedItems array where the given item is:
* - removed from the array, if it was already present
* - or added to the array, if it was not already present
*/
@action
toggleItem(item) {
const selectedItems = this.args.selectedItems || [];
const currentItems = [...selectedItems];
if (currentItems.includes(item)) {
const i = currentItems.indexOf(item);
currentItems.splice(i, 1);
} else {
currentItems.push(item);
}
if (this.args.onChange) this.args.onChange(currentItems);
}
}
@@ -0,0 +1,54 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
import { text, boolean, select, object } from '@storybook/addon-knobs';
<Meta title="Rose|Form/Checkbox/Group" component="Form/Checkbox/Group" />
# Form checkboxfield group
Renders a form checkboxfield group.
```htmlbars
<Rose::Form::Checkbox::Group
@name='checkbox-group'
@items={{this.items}}
@selectedItems={{this.selectedItems}}
@onChange={{fn this.onChange}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
```
<Preview>
<Story name="Basic" height="10rem">{{
template: hbs`
<Rose::Form::Checkbox::Group
@name={{name}}
@inline={{inline}}
@disabled={{disabled}}
@items={{items}}
@selectedItems={{selectedItems}}
@onChange={{onChange}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
`,
context: {
inline: boolean('inline', true),
disabled: boolean('disabled', false),
items: object('items', [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
{ id: 3, name: 'Baz' },
]),
selectedItems: object('selectedItems', []),
onChange: action('changed'),
}
}}</Story>
</Preview>
@@ -6,6 +6,7 @@
id={{this.id}}
name={{@name}}
disabled={{@disabled}}
onChange={{@onChange}}
@type="checkbox"
@checked={{if @checked true false}}
aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" />
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/form/checkbox/group';
+9 -5
View File
@@ -38,6 +38,7 @@
"emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774"
},
"devDependencies": {
"ember-get-config": "^0.3.0",
"@babel/core": "^7.15.5",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.4.2",
@@ -50,6 +51,7 @@
"@storybook/addons": "^6.3.9",
"@storybook/api": "^6.3.12",
"@storybook/cli": "^6.3.10",
"@storybook/components": "^6.3.12",
"@storybook/ember": "^6.3.8",
"@storybook/ember-cli-storybook": "^0.4.0",
"babel-eslint": "^10.1.0",
@@ -57,13 +59,13 @@
"broccoli-asset-rev": "^3.0.0",
"doctoc": "^2.0.1",
"ember-auto-import": "^1.12.0",
"ember-cli": "~3.28.0",
"ember-cli": "~3.28.3",
"ember-cli-dependency-checker": "^3.2.0",
"ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sass": "^10.0.1",
"ember-cli-sri": "^2.1.1",
"ember-cli-terser": "^4.0.2",
"ember-cli-update": "^0.54.6",
"ember-composable-helpers": "^4.5.0",
"ember-disable-prototype-extensions": "^1.1.3",
"ember-export-application-global": "^2.0.1",
"ember-inline-svg": "^1.0.0",
@@ -73,7 +75,7 @@
"ember-page-title": "^6.2.2",
"ember-qunit": "^5.1.4",
"ember-resolver": "^8.0.3",
"ember-source": "~3.24.0",
"ember-source": "~3.28.4",
"ember-source-channel-url": "^3.0.0",
"ember-template-lint": "^3.7.0",
"ember-try": "^1.4.0",
@@ -82,6 +84,7 @@
"eslint-plugin-ember": "^10.5.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-qunit": "^6.2.0",
"loader.js": "^4.7.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.4.0",
@@ -90,10 +93,11 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"sass": "^1.41.0",
"sass-lint": "^1.13.1"
"sass-lint": "^1.13.1",
"webpack": "^5.59.1"
},
"engines": {
"node": ">=10 <=15"
"node": ">=12 <=17"
},
"ember": {
"edition": "octane"
@@ -1,12 +1,28 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
const possibleItems = [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' },
];
export default class IndexController extends Controller {
color = 'green';
// =attributes
@tracked items = [...possibleItems];
@tracked selectedItems = [possibleItems[2]];
// =actions
@action
actionHandler(/*value*/) {
// eslint-disable-next-line no-console
//console.log('action handled');
checkboxGroupChanged(selected) {
this.selectedItems = [...selected];
}
@action
clearSelected() {
this.selectedItems = [];
}
}
@@ -0,0 +1,17 @@
import { registerDeprecationHandler } from '@ember/debug';
import config from 'ember-get-config';
const isTesting = config.environment === 'test';
export function initialize(/* application */) {
if (isTesting) {
// Disable all deprecations for now in tests.
registerDeprecationHandler((/*message, options, next*/) => {
return;
});
}
}
export default {
initialize,
};
@@ -1,3 +1,28 @@
<h2>Checkbox Group</h2>
<Rose::Form::Checkbox::Group
@items={{this.items}}
@selectedItems={{this.selectedItems}}
@onChange={{this.checkboxGroupChanged}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
<p>
Items selected:
{{#each this.selectedItems as |selected|}}
{{selected.id}},
{{/each}}
</p>
<Rose::Button @style='primary' {{on 'click' this.clearSelected}}>
Clear Selected Items
</Rose::Button>
<Rose::Separator />
<h2>KeyValue Dropdown</h2>
<Rose::Dropdown
@text="Credential library details"
@@ -22,7 +22,8 @@ module('Integration | Component | rose/badge', function (hooks) {
});
test('it styles content using @style', async function (assert) {
let styles = [
assert.expect(14);
const styles = [
'outline',
'dark',
'informational',
@@ -23,7 +23,7 @@ module('Integration | Component | rose/button', function (hooks) {
test('it is disabled when @disabled={{true}}', async function (assert) {
await render(hbs`<Rose::Button @disabled={{true}} />`);
assert.equal(find('button').disabled, true);
assert.true(find('button').disabled);
});
test('it adds a style class', async function (assert) {
@@ -23,7 +23,7 @@ module('Integration | Component | rose/dropdown/button', function (hooks) {
test('it is disabled when @disabled={{true}}', async function (assert) {
await render(hbs`<Rose::Dropdown::Button @disabled={{true}} />`);
assert.equal(find('button').disabled, true);
assert.true(find('button').disabled);
});
test('it adds a style class', async function (assert) {
@@ -60,17 +60,17 @@ module('Integration | Component | rose/form/checkbox', function (hooks) {
test('it is not checked by default', async function (assert) {
await render(hbs`<Rose::Form::Checkbox />`);
assert.equal(find('input').checked, false);
assert.false(find('input').checked);
});
test('it is not disabled by default', async function (assert) {
await render(hbs`<Rose::Form::Checkbox />`);
assert.equal(find('input').disabled, false);
assert.false(find('input').disabled);
});
test('it is checked when @checked={{true}}', async function (assert) {
await render(hbs`<Rose::Form::Checkbox @checked={{true}} />`);
assert.equal(find('input').checked, true);
assert.true(find('input').checked);
});
test('it marks error when @error={{true}}', async function (assert) {
@@ -80,6 +80,6 @@ module('Integration | Component | rose/form/checkbox', function (hooks) {
test('it is disabled when @disabled={{true}}', async function (assert) {
await render(hbs`<Rose::Form::Checkbox @disabled={{true}} />`);
assert.equal(find('input').disabled, true);
assert.true(find('input').disabled);
});
});
@@ -0,0 +1,100 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, findAll, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/form/checkbox/group', function (hooks) {
setupRenderingTest(hooks);
let items;
let selectedItems;
hooks.beforeEach(function () {
items = [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
{ id: 3, name: 'Baz' },
];
selectedItems = [items[2]];
});
test('it renders checkboxes equal in length to an array of items', async function (assert) {
assert.expect(1);
this.items = items;
await render(hbs`
<Rose::Form::Checkbox::Group
@items={{this.items}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
`);
assert.equal(findAll('input').length, 3);
});
test('it checks checkboxes for items present in a selectedItems array', async function (assert) {
assert.expect(1);
this.items = items;
this.selectedItems = selectedItems;
await render(hbs`
<Rose::Form::Checkbox::Group
@items={{this.items}}
@selectedItems={{this.selectedItems}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
`);
assert.equal(findAll(':checked').length, 1);
});
test('it fires onChange when items are checked, passing an array of selected items', async function (assert) {
assert.expect(2);
this.items = items;
this.selectedItems = selectedItems;
this.onChange = (newlySelectedItems) => {
assert.equal(selectedItems.length, 1);
assert.equal(newlySelectedItems.length, 2);
};
await render(hbs`
<Rose::Form::Checkbox::Group
@items={{this.items}}
@selectedItems={{this.selectedItems}}
@onChange={{this.onChange}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
`);
await click('.rose-form-checkbox:first-child input');
});
test('it fires onChange when items are unchecked, passing an array of selected items', async function (assert) {
assert.expect(2);
this.items = items;
this.selectedItems = selectedItems;
this.onChange = (newlySelectedItems) => {
assert.equal(selectedItems.length, 1);
assert.equal(newlySelectedItems.length, 0);
};
await render(hbs`
<Rose::Form::Checkbox::Group
@items={{this.items}}
@selectedItems={{this.selectedItems}}
@onChange={{this.onChange}}
as |group|
>
<group.checkbox
@label={{group.item.name}}
value={{group.item.id}} />
</Rose::Form::Checkbox::Group>
`);
await click('.rose-form-checkbox:last-child input');
});
});
@@ -43,13 +43,13 @@ module('Integration | Component | rose/form/radio/group', function (hooks) {
</Rose::Form::Radio::Group>
`);
assert.equal(findAll('[type="radio"]')[0].checked, false);
assert.equal(findAll('[type="radio"]')[1].checked, true);
assert.false(findAll('[type="radio"]')[0].checked);
assert.true(findAll('[type="radio"]')[1].checked);
await click(findAll('[type="radio"]')[0]);
assert.equal(findAll('[type="radio"]')[0].checked, true);
assert.equal(findAll('[type="radio"]')[1].checked, false);
assert.true(findAll('[type="radio"]')[0].checked);
assert.false(findAll('[type="radio"]')[1].checked);
});
test('it reflects active radiofield value in @selectedValue and triggers a @changed function', async function (assert) {
@@ -100,19 +100,19 @@ module('Integration | Component | rose/form/radio/group', function (hooks) {
</Rose::Form::Radio::Group>
`);
assert.equal(findAll('[type="radio"]')[0].checked, false);
assert.equal(findAll('[type="radio"]')[1].checked, false);
assert.false(findAll('[type="radio"]')[0].checked);
assert.false(findAll('[type="radio"]')[1].checked);
this.set('selectedValue', 'flamingo');
assert.equal(findAll('[type="radio"]')[0].checked, false);
assert.equal(findAll('[type="radio"]')[1].checked, true);
assert.false(findAll('[type="radio"]')[0].checked);
assert.true(findAll('[type="radio"]')[1].checked);
this.set('selectedValue', 'pegion');
assert.equal(findAll('[type="radio"]')[0].checked, true);
assert.equal(findAll('[type="radio"]')[1].checked, false);
assert.true(findAll('[type="radio"]')[0].checked);
assert.false(findAll('[type="radio"]')[1].checked);
this.set('selectedValue', 'dog');
assert.equal(findAll('[type="radio"]')[0].checked, false);
assert.equal(findAll('[type="radio"]')[1].checked, false);
assert.false(findAll('[type="radio"]')[0].checked);
assert.false(findAll('[type="radio"]')[1].checked);
});
});
@@ -28,25 +28,25 @@ module('Integration | Component | rose/form/radio/radio', function (hooks) {
await render(
hbs`<Rose::Form::Radio::Radio @value="tree" @selectedValue="tree"/>`
);
assert.equal(await find('input').checked, true);
assert.true(await find('input').checked);
});
test('it is not checked when @value and @selectedValue values are different', async function (assert) {
await render(hbs`<Rose::Form::Radio::Radio @value="tree"/>`);
assert.equal(await find('input').checked, false);
assert.false(await find('input').checked);
await render(hbs`<Rose::Form::Radio::Radio @selectedValue="tree"/>`);
assert.equal(await find('input').checked, false);
assert.false(await find('input').checked);
await render(
hbs`<Rose::Form::Radio::Radio @value="root" @selectedValue="tree"/>`
);
assert.equal(await find('input').checked, false);
assert.false(await find('input').checked);
});
test('it is not disabled by default', async function (assert) {
await render(hbs`<Rose::Form::Radio::Radio />`);
assert.equal(await find('input').disabled, false);
assert.false(await find('input').disabled);
});
test('it is not in error state by default', async function (assert) {
@@ -56,7 +56,7 @@ module('Integration | Component | rose/form/radio/radio', function (hooks) {
test('it is disabled when @disabled={{true}}', async function (assert) {
await render(hbs`<Rose::Form::Radio::Radio @disabled={{true}}/>`);
assert.equal(await find('input').disabled, true);
assert.true(await find('input').disabled);
});
test('it is in error state when @error={{true}}', async function (assert) {
@@ -7,6 +7,7 @@ module('Integration | Modifier | on-click-outside', function (hooks) {
setupRenderingTest(hooks);
test('it fires event when an outside element is clicked', async function (assert) {
assert.expect(2);
this.set('clickedOutside', (element, event) => {
assert.ok(element);
assert.equal(event.target.id, 'outside');