diff --git a/.changeset/great-comics-own.md b/.changeset/great-comics-own.md new file mode 100644 index 00000000..d879bc2c --- /dev/null +++ b/.changeset/great-comics-own.md @@ -0,0 +1,5 @@ +--- +"mx-ui-components": minor +--- + +feat(mox): add light mode to mox/select + mox/typeahead components diff --git a/addon/components/mox/input.hbs b/addon/components/mox/input.hbs index 80cd491b..2065d493 100644 --- a/addon/components/mox/input.hbs +++ b/addon/components/mox/input.hbs @@ -1,20 +1,22 @@ {{#if @label}} {{@label}} {{/if}} - - +
+ + +
diff --git a/addon/components/mox/search-input.hbs b/addon/components/mox/search-input.hbs index 75fccbdc..3aa19f15 100644 --- a/addon/components/mox/search-input.hbs +++ b/addon/components/mox/search-input.hbs @@ -1,7 +1,7 @@
+ \ No newline at end of file diff --git a/addon/components/mox/select.js b/addon/components/mox/select.js index 18902160..c2dc4d5c 100644 --- a/addon/components/mox/select.js +++ b/addon/components/mox/select.js @@ -28,6 +28,26 @@ export default class MoxSelectComponent extends Component { return get(this.args.selectedOption, optionNameKey); } + get themeClasses() { + let lightMode = `bg-gray-50 text-gray-800 disabled:bg-gray-200 disabled:text-gray-600 disabled:border-gray-200`; + let darkMode = `dark:bg-gray-800 dark:text-white dark:disabled:bg-gray-700 dark:disabled:text-gray-500 dark:disabled:border-gray-700`; + return `${lightMode} ${darkMode}`; + } + + get validThemeClasses() { + let lightMode = `border-gray-300 focus:border-cyan-500 focus:ring-cyan-500`; + let darkMode = + 'dark:border-gray-500 dark:focus:border-cyan-500 dark:focus:ring-cyan-500'; + return `${lightMode} ${darkMode}`; + } + + get invalidThemeClasses() { + let lightMode = `border-red-600 active:border-red-700 focus:border-red-500 focus:ring-red-500`; + let darkMode = + 'dark:border-red-800 dark:active:border-red-900 dark:focus:border-red-900 dark:focus:ring-red-900'; + return `${lightMode} ${darkMode}`; + } + @action toggleOptions() { if (this.args.isDisabled) { diff --git a/addon/components/mox/select/option.hbs b/addon/components/mox/select/option.hbs index c43b65cc..2859dbba 100644 --- a/addon/components/mox/select/option.hbs +++ b/addon/components/mox/select/option.hbs @@ -1,13 +1,17 @@ -{{!-- template-lint-disable require-presentational-children --}} +{{! template-lint-disable require-presentational-children }}
  • - {{!-- template-lint-disable no-invalid-interactive --}} + {{! template-lint-disable no-invalid-interactive }}
    {{/if}}
    - {{!-- template-lint-enable no-invalid-interactive --}} + {{! template-lint-enable no-invalid-interactive }} {{#if (eq this.optionValue this.selectedOptionValue)}} {{/if}}
  • -{{!-- template-lint-enable require-presentational-children --}} +{{! template-lint-enable require-presentational-children }} \ No newline at end of file diff --git a/addon/components/mox/typeahead-select.hbs b/addon/components/mox/typeahead-select.hbs index 5b3cb99d..00205de5 100644 --- a/addon/components/mox/typeahead-select.hbs +++ b/addon/components/mox/typeahead-select.hbs @@ -3,7 +3,7 @@ {{@label}} {{/if}}
    - +
    @@ -52,7 +52,7 @@ id={{concat "typeaheadselectlistbox" @id}} tabindex="1" role="listbox" - class="max-h-56 rounded-md text-base overflow-auto focus:outline-none sm:text-sm border border-gray-500" + class="max-h-56 rounded-md text-base overflow-auto focus:outline-none sm:text-sm border border-gray-300 dark:border-gray-500" > {{!-- template-lint-enable no-positive-tabindex --}} {{#each this.matches as |match idx|}} @@ -60,13 +60,14 @@
  • {{@name}} - {{@category}} + {{@category}}
  • diff --git a/package.json b/package.json index ed03774f..7370599e 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "ember-concurrency": "^3.0.0", "ember-keyboard": "8.2.0", "ember-load-initializers": "^2.1.2", + "ember-modifier": "^4.1.0", "ember-page-title": "^7.0.0", "ember-qunit": "^6.2.0", "ember-resolver": "^10.1.0", diff --git a/stories/mox-select-light.stories.js b/stories/mox-select-light.stories.js new file mode 100644 index 00000000..640e198b --- /dev/null +++ b/stories/mox-select-light.stories.js @@ -0,0 +1,220 @@ +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@ember/object'; + +const connectorTypes = [ + { name: 'source', value: 'source' }, + { name: 'sink', value: 'sink' }, +]; + +const connectorTypesCustom = [ + { name: 'My environment', value: 'my-environment', type: 'Self-Hosted' }, + { name: 'Meroxa environment', value: 'meroxa-environment', type: 'Private' }, + { name: 'Common', value: 'common', type: 'Common' }, +]; + +const connectorTypesDisabled = [ + { name: 'My environment', value: 'my-environment', isDisabled: false }, + { name: 'Meroxa environment', value: 'meroxa-environment', isDisabled: true }, + { name: 'Common', value: 'common', isDisabled: false }, +]; + +const resourceTypes = [ + { name: 'Postgres', value: 'postgres' }, + { name: 'MongoDB', value: 'mongodb' }, +]; + +export default { + title: 'Mox Light/Mox::Select', + parameters: { + backgrounds: { + default: 'Mute', + values: [ + { + name: 'White', + value: '#ffffff', + }, + { + name: 'Mute', + value: '#F3F4F6', + }, + ], + }, + }, + argTypes: { + options: { control: 'text' }, + buttonType: { control: 'text' }, + }, +}; + +const Template = (args) => ({ + template: hbs` +
    + +
    +`, + context: args, +}); + +const CustomOptionsTemplate = (args) => ({ + template: hbs` +
    + + + {{#each this.connectorTypes as |connector|}} + + {{/each}} + +
    +`, + context: args, +}); + +const CustomOptionsBlockTemplate = (args) => ({ + template: hbs` +
    + + + {{#each this.resourceTypes as |resource|}} + + + + {{/each}} + +
    +`, + context: args, +}); + +const DisabledOptionsTemplate = (args) => ({ + template: hbs` +
    + + {{#each this.connectorTypes as |connector|}} + + {{/each}} + +
    +`, + context: args, +}); + +export const Default = Template.bind({}); +Default.args = { + connectorTypes: connectorTypes, + selectedConnectorType: connectorTypes[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: null, +}; + +export const Short = Template.bind({}); +Short.args = { + connectorTypes: connectorTypes, + selectedConnectorType: connectorTypes[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: 'w-20', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + connectorTypes: connectorTypes, + selectedConnectorType: connectorTypes[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: null, + isDisabled: true, +}; + +export const CustomOptions = CustomOptionsTemplate.bind({}); +CustomOptions.args = { + connectorTypes: connectorTypesCustom, + selectedConnectorType: connectorTypesCustom[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: 'w-48', + categoryKey: 'type', +}; + +export const IconOptions = CustomOptionsBlockTemplate.bind({}); +IconOptions.args = { + resourceTypes: resourceTypes, + selectedResourceType: resourceTypes[0], + isEditing: false, + setResourceType: action(function (value) { + this.set('selectedResourceType', value); + }), + wrapperClass: 'w-48', +}; + +export const DisabledOptions = DisabledOptionsTemplate.bind({}); +DisabledOptions.args = { + connectorTypes: connectorTypesDisabled, + selectedConnectorType: connectorTypesDisabled[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: 'w-48', +}; + +export const Errors = Template.bind({}); +Errors.args = { + connectorTypes: connectorTypes, + selectedConnectorType: connectorTypes[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: null, + isValid: false, + error: 'Invalid connector', + inputAction: () => {}, +}; diff --git a/stories/mox-select.stories.js b/stories/mox-select.stories.js index a27bafc6..d2b6479f 100644 --- a/stories/mox-select.stories.js +++ b/stories/mox-select.stories.js @@ -28,6 +28,16 @@ export default { parameters: { backgrounds: { default: 'Dark', + values: [ + { + name: 'Dark', + value: '#111827', + }, + { + name: 'Sky', + value: '#06B6D4', + }, + ], }, }, argTypes: { @@ -135,7 +145,7 @@ Default.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: null, + wrapperClass: 'dark', }; export const Short = Template.bind({}); @@ -146,7 +156,7 @@ Short.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: 'w-20', + wrapperClass: 'dark w-20', }; export const Disabled = Template.bind({}); @@ -157,7 +167,7 @@ Disabled.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: null, + wrapperClass: 'dark', isDisabled: true, }; @@ -169,7 +179,7 @@ CustomOptions.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: 'w-48', + wrapperClass: 'dark w-48', categoryKey: 'type', }; @@ -181,7 +191,7 @@ IconOptions.args = { setResourceType: action(function (value) { this.set('selectedResourceType', value); }), - wrapperClass: 'w-48', + wrapperClass: 'dark w-48', }; export const DisabledOptions = DisabledOptionsTemplate.bind({}); @@ -192,7 +202,7 @@ DisabledOptions.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: 'w-48', + wrapperClass: 'dark w-48', }; export const Errors = Template.bind({}); @@ -203,7 +213,7 @@ Errors.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: null, + wrapperClass: 'dark', isValid: false, error: 'Invalid connector', inputAction: () => {}, diff --git a/stories/mox-typeahead-select-light.stories.js b/stories/mox-typeahead-select-light.stories.js new file mode 100644 index 00000000..00829068 --- /dev/null +++ b/stories/mox-typeahead-select-light.stories.js @@ -0,0 +1,129 @@ +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@ember/object'; + +const options = [ + { name: 'Apple', value: 'apple' }, + { name: 'Banana', value: 'banana' }, + { name: 'Carrot', value: 'carrot' }, +]; + +export default { + title: 'Mox Light/Mox::TypeaheadSelect', + argTypes: { + options: { control: 'text' }, + buttonType: { control: 'text' }, + }, + parameters: { + backgrounds: { + default: 'Mute', + values: [ + { + name: 'White', + value: '#ffffff', + }, + { + name: 'Mute', + value: '#F3F4F6', + }, + ], + }, + }, +}; + +const Template = (args) => ({ + template: hbs` +
    + +
    +`, + context: args, +}); + +const LabelledTemplate = (args) => ({ + template: hbs` + Food +
    + +
    +`, + context: args, +}); + +export const Default = Template.bind({}); +Default.args = { + connectorTypes: options, + selectedConnectorType: options[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: null, + label: 'Fruit', + id: 'fruit-basket', +}; + +export const Short = Template.bind({}); +Short.args = { + connectorTypes: options, + selectedConnectorType: options[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: 'w-20', + label: 'Fruit', + id: 'fruit-basket', +}; + +export const ExternalLabel = LabelledTemplate.bind({}); +ExternalLabel.args = { + connectorTypes: options, + selectedConnectorType: options[0], + isEditing: false, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + wrapperClass: null, + id: 'fruit-basket', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + connectorTypes: options, + selectedConnectorType: options[0], + isEditing: true, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + label: 'Fruit', + id: 'fruit-basket', +}; + +export const Errors = Template.bind({}); +Errors.args = { + connectorTypes: options, + selectedConnectorType: options[0], + isEditing: true, + setConnectorType: action(function (value) { + this.set('selectedConnectorType', value); + }), + label: 'Fruit', + id: 'fruit-basket', + isValid: false, + error: 'No more fruit', +}; diff --git a/stories/mox-typeahead-select.stories.js b/stories/mox-typeahead-select.stories.js index 66dca69e..79927c6a 100644 --- a/stories/mox-typeahead-select.stories.js +++ b/stories/mox-typeahead-select.stories.js @@ -16,6 +16,16 @@ export default { parameters: { backgrounds: { default: 'Dark', + values: [ + { + name: 'Dark', + value: '#111827', + }, + { + name: 'Sky', + value: '#06B6D4', + }, + ], }, }, }; @@ -40,15 +50,17 @@ const Template = (args) => ({ const LabelledTemplate = (args) => ({ template: hbs` - Food -
    - +
    + Food +
    + +
    `, context: args, @@ -62,7 +74,7 @@ Default.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: null, + wrapperClass: 'dark', label: 'Fruit', id: 'fruit-basket', }; @@ -75,7 +87,7 @@ Short.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: 'w-20', + wrapperClass: 'dark w-20', label: 'Fruit', id: 'fruit-basket', }; @@ -88,7 +100,7 @@ ExternalLabel.args = { setConnectorType: action(function (value) { this.set('selectedConnectorType', value); }), - wrapperClass: null, + wrapperClass: 'dark', id: 'fruit-basket', }; @@ -102,6 +114,7 @@ Disabled.args = { }), label: 'Fruit', id: 'fruit-basket', + wrapperClass: 'dark', }; export const Errors = Template.bind({}); @@ -116,4 +129,5 @@ Errors.args = { id: 'fruit-basket', isValid: false, error: 'No more fruit', + wrapperClass: 'dark', }; diff --git a/tests/integration/components/mox/input-test.js b/tests/integration/components/mox/input-test.js index 289dd97c..87ef6a82 100644 --- a/tests/integration/components/mox/input-test.js +++ b/tests/integration/components/mox/input-test.js @@ -65,10 +65,14 @@ module('Integration | Component | mox/input', function (hooks) { `); let secondField = await find('[data-test-second-field]'); + let initialPosition = secondField.getBoundingClientRect().top; assert .dom('[data-test-first-field] [data-test-mox-input-error]') .doesNotExist(); - assert.strictEqual(secondField.offsetTop, 54); + assert.strictEqual( + secondField.getBoundingClientRect().top, + initialPosition + ); this.set('error', 'message1'); @@ -76,10 +80,20 @@ module('Integration | Component | mox/input', function (hooks) { .dom('[data-test-first-field] + [data-test-mox-input-error]') .includesText('message1'); assert.strictEqual( - secondField.offsetTop, - 54, + secondField.getBoundingClientRect().top, + initialPosition, 'the second field does not change its position relative to the top' ); + + this.set('error', null); + + assert + .dom('[data-test-first-field] [data-test-mox-input-error]') + .doesNotExist(); + assert.strictEqual( + secondField.getBoundingClientRect().top, + initialPosition + ); }); module('light mode', function () { diff --git a/tests/integration/components/mox/select-test.js b/tests/integration/components/mox/select-test.js index 07f67d1a..a1ab4615 100644 --- a/tests/integration/components/mox/select-test.js +++ b/tests/integration/components/mox/select-test.js @@ -51,114 +51,6 @@ module('Integration | Component | mox/select', function (hooks) { assert.dom('[data-test-select-button]').isDisabled(); }); - test('it highlights the field if it is invalid (dark mode)', async function (assert) { - this.set('onInput', () => {}); - - await render(hbs` -
    - -
    `); - - assert.dom('[data-test-select-button]').hasClass('border-red-800'); - assert.dom('[data-test-select-button]').hasStyle({ - borderColor: 'rgb(153, 27, 27)', - }); - }); - - test('it allows to validate and invalidate the field after rendering (dark mode)', async function (assert) { - this.set('onInput', () => {}); - this.set('isValid', null); - - await render(hbs` -
    - -
    `); - - assert.dom('[data-test-select-button]').doesNotHaveClass('border-red-800'); - assert.dom('[data-test-select-button]').hasStyle({ - borderColor: 'rgb(107, 114, 128)', - }); - - this.set('isValid', false); - - assert.dom('[data-test-select-button]').hasClass('border-red-800'); - assert.dom('[data-test-select-button]').hasStyle({ - borderColor: 'rgb(153, 27, 27)', - }); - - this.set('isValid', true); - - assert.dom('[data-test-select-button]').doesNotHaveClass('border-red-800'); - assert.dom('[data-test-select-button]').hasStyle({ - borderColor: 'rgb(107, 114, 128)', - }); - }); - - test('it may display a validation error alongside the field', async function (assert) { - this.set('onInput', () => {}); - - await render(hbs``); - - assert - .dom('[data-test-mox-select-error]') - .includesText(`Connector missing`); - }); - - test('it is accessible (dark mode)', async function (assert) { - await render(hbs` -
    - -
    `); - - await a11yAudit(); - assert.ok(true, 'no a11y detected'); - }); - - test('the invalid input state is accessible', async function (assert) { - this.set('onInput', () => {}); - - await render(hbs`
    - -
    `); - await a11yAudit(); - assert.ok(true, 'no accessibility errors'); - }); - module('when toggled', function (hooks) { hooks.beforeEach(async function () { await render(hbs` @@ -267,6 +159,56 @@ module('Integration | Component | mox/select', function (hooks) { .includesText('Self-Hosted'); }); + test('it is accessible with custom options (dark mode)', async function (assert) { + await render(hbs` +
    + + {{#each this.options as |option|}} + + {{/each}} + +
    `); + + await click('[data-test-select-button]'); + + await a11yAudit(); + assert.ok(true, 'it has no accessibility errors'); + }); + + test('it is accessible with custom options (light mode)', async function (assert) { + await render(hbs` +
    + + {{#each this.options as |option|}} + + {{/each}} + +
    `); + + await click('[data-test-select-button]'); + + await a11yAudit(); + assert.ok(true, 'it has no accessibility errors'); + }); + test('it allows selecting customized options', async function (assert) { await render(hbs` + - {{#each this.options as |option|}} - - {{/each}} - `); + {{#each this.options as |option|}} + + {{/each}} + +
    `); + + await click('[data-test-select-button]'); + assert.dom('[data-test-select-option]').exists({ count: 3 }); + const options = this.element.querySelectorAll( + '[data-test-select-option]' + ); + + assert.dom(options[0]).includesText('My environment'); + assert.dom(options[0]).hasClass('dark:text-white'); + + assert.dom(options[2]).includesText('Common'); + assert.dom(options[2]).hasClass('dark:text-white'); + + assert.dom(options[1]).includesText('Meroxa environment'); + assert.dom(options[1]).hasClass('dark:text-gray-500'); + assert.dom(options[1]).hasClass('dark:bg-gray-700'); + }); + + test('it renders disabled options (light mode)', async function (assert) { + await render(hbs` +
    + + {{#each this.options as |option|}} + + {{/each}} + +
    `); await click('[data-test-select-button]'); assert.dom('[data-test-select-option]').exists({ count: 3 }); @@ -338,14 +318,14 @@ module('Integration | Component | mox/select', function (hooks) { ); assert.dom(options[0]).includesText('My environment'); - assert.dom(options[0]).hasClass('text-white'); + assert.dom(options[0]).hasClass('text-gray-800'); assert.dom(options[2]).includesText('Common'); - assert.dom(options[2]).hasClass('text-white'); + assert.dom(options[2]).hasClass('text-gray-800'); assert.dom(options[1]).includesText('Meroxa environment'); - assert.dom(options[1]).hasClass('text-gray-500'); - assert.dom(options[1]).hasClass('bg-gray-700'); + assert.dom(options[1]).hasClass('text-gray-600'); + assert.dom(options[1]).hasClass('bg-gray-200'); }); test('it does not allow selecting disabled options', async function (assert) { @@ -396,4 +376,238 @@ module('Integration | Component | mox/select', function (hooks) { ); }); }); + + module('dark mode', function () { + test('it highlights the field if it is invalid (dark mode)', async function (assert) { + this.set('onInput', () => {}); + + await render(hbs` +
    + +
    `); + + assert.dom('[data-test-select-button]').hasClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(153, 27, 27)', + }); + }); + + test('it allows to validate and invalidate the field after rendering (dark mode)', async function (assert) { + this.set('onInput', () => {}); + this.set('isValid', null); + + await render(hbs` +
    + +
    `); + + assert + .dom('[data-test-select-button]') + .doesNotHaveClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(107, 114, 128)', + }); + + this.set('isValid', false); + + assert.dom('[data-test-select-button]').hasClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(153, 27, 27)', + }); + + this.set('isValid', true); + + assert + .dom('[data-test-select-button]') + .doesNotHaveClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(107, 114, 128)', + }); + }); + + test('it may display a validation error alongside the field', async function (assert) { + this.set('onInput', () => {}); + + await render(hbs` +
    + +
    `); + + assert + .dom('[data-test-mox-select-error]') + .includesText(`Connector missing`); + }); + + test('it is accessible (dark mode)', async function (assert) { + await render(hbs` +
    + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('the invalid input state is accessible', async function (assert) { + this.set('onInput', () => {}); + + await render(hbs`
    + +
    `); + await a11yAudit(); + assert.ok(true, 'no accessibility errors'); + }); + }); + + module('light mode', function () { + test('it highlights the field if it is invalid (light mode)', async function (assert) { + this.set('onInput', () => {}); + + await render(hbs` +
    + +
    `); + + assert.dom('[data-test-select-button]').hasClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(220, 38, 38)', + }); + }); + + test('it allows to validate and invalidate the field after rendering (light mode)', async function (assert) { + this.set('onInput', () => {}); + this.set('isValid', null); + + await render(hbs` +
    + +
    `); + + assert + .dom('[data-test-select-button]') + .doesNotHaveClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(209, 213, 219)', + }); + + this.set('isValid', false); + + assert.dom('[data-test-select-button]').hasClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(220, 38, 38)', + }); + + this.set('isValid', true); + + assert + .dom('[data-test-select-button]') + .doesNotHaveClass('dark:border-red-800'); + assert.dom('[data-test-select-button]').hasStyle({ + borderColor: 'rgb(209, 213, 219)', + }); + }); + + test('it may display a validation error alongside the field', async function (assert) { + this.set('onInput', () => {}); + + await render(hbs` +
    + +
    `); + + assert + .dom('[data-test-mox-select-error]') + .includesText(`Connector missing`); + }); + + test('it is accessible (dark mode)', async function (assert) { + await render(hbs` +
    + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('the invalid input state is accessible', async function (assert) { + this.set('onInput', () => {}); + + await render(hbs`
    + +
    `); + await a11yAudit(); + assert.ok(true, 'no accessibility errors'); + }); + }); }); diff --git a/tests/integration/components/mox/typeahead-select-test.js b/tests/integration/components/mox/typeahead-select-test.js index 47d7f52c..adfade03 100644 --- a/tests/integration/components/mox/typeahead-select-test.js +++ b/tests/integration/components/mox/typeahead-select-test.js @@ -114,70 +114,6 @@ module('Integration | Component | mox/typeahead-select', function (hooks) { .includesText(`Name can't be blank`); }); - test('it is accessible (with embedded label)', async function (assert) { - await render(hbs` - `); - - await a11yAudit(); - assert.ok(true, 'no a11y detected'); - }); - - test('it is accessible (with external label)', async function (assert) { - await render(hbs` - - `); - - await a11yAudit(); - assert.ok(true, 'no a11y detected'); - }); - - test('it is accessible (disabled)', async function (assert) { - await render(hbs` - - `); - - await a11yAudit(); - assert.ok(true, 'no a11y detected'); - }); - - test('it is accessible (validation error / dark mode)', async function (assert) { - await render(hbs` -
    - -
    `); - - await a11yAudit(); - assert.ok(true, 'no a11y detected'); - }); - test('it hides the option list when not focused', async function (assert) { await render(hbs` + +
    `); + + await click('[data-test-typeahead-select-input]'); + + const options = this.element.querySelectorAll( + '[data-test-select-option]' + ); + assert.dom(options[0]).hasClass('dark:bg-gray-700'); + + await triggerKeyEvent( + '[data-test-typeahead-select-input]', + 'keydown', + 'ArrowDown' + ); + assert.dom(options[0]).doesNotHaveClass('dark:bg-gray-700'); + assert.dom(options[1]).hasClass('dark:bg-gray-700'); + + await triggerKeyEvent( + '[data-test-typeahead-select-input]', + 'keydown', + 'ArrowUp' + ); + assert.dom(options[0]).hasClass('dark:bg-gray-700'); + assert.dom(options[1]).doesNotHaveClass('dark:bg-gray-700'); + }); + + test('it is accessible (with embedded label)', async function (assert) { + await render(hbs` +
    + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('it is accessible (with external label)', async function (assert) { + await render(hbs` +
    + My external label + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('it is accessible (disabled)', async function (assert) { + await render(hbs` +
    + + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('it is accessible (validation error / dark mode)', async function (assert) { + await render(hbs` +
    + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + }); + + module('light mode', function () { + test('it allows keyboard navigation', async function (assert) { + await render(hbs` +
    + +
    `); + + await click('[data-test-typeahead-select-input]'); + + const options = this.element.querySelectorAll( + '[data-test-select-option]' + ); + assert.dom(options[0]).hasClass('bg-gray-200'); + assert.dom(options[0]).doesNotHaveClass('bg-white'); + + await triggerKeyEvent( + '[data-test-typeahead-select-input]', + 'keydown', + 'ArrowDown' + ); + + assert.dom(options[0]).hasClass('bg-white'); + assert.dom(options[0]).doesNotHaveClass('bg-gray-200'); + assert.dom(options[1]).hasClass('bg-gray-200'); + assert.dom(options[1]).doesNotHaveClass('bg-white'); + + await triggerKeyEvent( + '[data-test-typeahead-select-input]', + 'keydown', + 'ArrowUp' + ); + assert.dom(options[0]).hasClass('bg-gray-200'); + assert.dom(options[1]).doesNotHaveClass('bg-gray-200'); + assert.dom(options[1]).hasClass('bg-white'); + }); + + test('it is accessible (with embedded label)', async function (assert) { + await render(hbs` +
    + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('it is accessible (with external label)', async function (assert) { + await render(hbs` +
    + My external label + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('it is accessible (disabled)', async function (assert) { + await render(hbs` +
    + + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + + test('it is accessible (validation error / light mode)', async function (assert) { + await render(hbs` +
    + +
    `); + + await a11yAudit(); + assert.ok(true, 'no a11y detected'); + }); + }); });