- {{! template-lint-disable no-positive-tabindex no-pointer-down-event-binding }}
+ {{! template-lint-disable no-positive-tabindex }}
{{#let (uniqueId) as |id|}}
diff --git a/addon/components/select-box/trigger.gjs b/addon/components/select-box/trigger.gjs
deleted file mode 100644
index dd70d2263..000000000
--- a/addon/components/select-box/trigger.gjs
+++ /dev/null
@@ -1,22 +0,0 @@
-import { on } from '@ember/modifier';
-import lifecycle from '@zestia/ember-select-box/modifiers/lifecycle';
-
-
- {{! template-lint-disable no-positive-tabindex require-aria-activedescendant-tabindex no-pointer-down-event-binding }}
-
- {{~yield~}}
-
-
diff --git a/addon/modifiers/lifecycle.js b/addon/modifiers/lifecycle.js
index 1e15e9f9a..4dac9fa44 100644
--- a/addon/modifiers/lifecycle.js
+++ b/addon/modifiers/lifecycle.js
@@ -4,17 +4,17 @@ import { registerDestructor } from '@ember/destroyable';
export default class LifecycleModifier extends Modifier {
didSetup;
- constructor(appInstance, args) {
+ constructor(appInstance, { named: { onDestroy } }) {
super(...arguments);
- registerDestructor(this, args.named.onDestroy);
+ onDestroy && registerDestructor(this, onDestroy);
}
- modify(element, positional, named) {
+ modify(element, positional, { onInsert }) {
if (this.didSetup) {
return;
}
- named.onInsert(element);
+ onInsert?.(element);
this.didSetup = true;
}
}
diff --git a/app/components/dropdown.js b/app/components/dropdown.js
new file mode 100644
index 000000000..d64ab88ec
--- /dev/null
+++ b/app/components/dropdown.js
@@ -0,0 +1 @@
+export { default } from '@zestia/ember-select-box/components/dropdown/index';
diff --git a/package-lock.json b/package-lock.json
index fb8653e49..87777d907 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@zestia/ember-select-box",
- "version": "16.4.0",
+ "version": "17.0.0-9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zestia/ember-select-box",
- "version": "16.4.0",
+ "version": "17.0.0-9",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index 316b71225..94bda86f8 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"build": "ember build --environment=production",
"deploy": "ember build --environment production && ember github-pages:commit --message \"Deploy gh-pages from commit $(git rev-parse HEAD)\" && git push origin gh-pages:gh-pages",
"lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
- "lint:css": "stylelint '**/*.{css,scss}'",
+ "lint:css": "stylelint '**/*.{css,scss}' --quiet-deprecation-warnings",
"lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
"lint:fix": "npm run lint:css:fix; npm run lint:hbs:fix; npm run lint:js:fix; npm run lint:prettier:fix",
"lint:hbs": "ember-template-lint .",
@@ -110,5 +110,5 @@
"test:ember": "ember test",
"test:ember-compatibility": "ember try:each"
},
- "version": "16.4.0"
+ "version": "17.0.0-9"
}
diff --git a/tests/acceptance/performance-test.js b/tests/acceptance/performance-test.js
index f6df30c3f..6de2d62fe 100644
--- a/tests/acceptance/performance-test.js
+++ b/tests/acceptance/performance-test.js
@@ -22,7 +22,7 @@ module('Acceptance | performance', function (hooks) {
this.startTimer();
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
this.stopTimer();
diff --git a/tests/dummy/app/components/custom-button.gjs b/tests/dummy/app/components/custom-button.gjs
deleted file mode 100644
index ca7dc33a9..000000000
--- a/tests/dummy/app/components/custom-button.gjs
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- {{yield}}
-
-
diff --git a/tests/dummy/app/components/example3.gjs b/tests/dummy/app/components/example3.gjs
index 549e664cd..69fc4f160 100644
--- a/tests/dummy/app/components/example3.gjs
+++ b/tests/dummy/app/components/example3.gjs
@@ -8,15 +8,19 @@ import SelectBox from '@zestia/ember-select-box/components/select-box';
...attributes
as |sb|
>
-
- {{yield sb.value to="trigger"}}
-
-
- {{#each @options as |value|}}
-
- {{yield value to="option"}}
-
- {{/each}}
-
+
+
+ {{yield sb.value to="trigger"}}
+
+
+
+ {{#each @options as |value|}}
+
+ {{yield value to="option"}}
+
+ {{/each}}
+
+
+
diff --git a/tests/dummy/app/components/example4.gjs b/tests/dummy/app/components/example4.gjs
index 21ef9c007..9e83b84d8 100644
--- a/tests/dummy/app/components/example4.gjs
+++ b/tests/dummy/app/components/example4.gjs
@@ -18,24 +18,28 @@ export default class extends Component {
...attributes
as |sb|
>
-
- {{yield sb.value to="trigger"}}
-
-
- {{#each @options as |value|}}
-
-
- {{yield value to="option"}}
-
- {{/each}}
-
+
+
+ {{yield sb.value to="trigger"}}
+
+
+
+ {{#each @options as |value|}}
+
+
+ {{yield value to="option"}}
+
+ {{/each}}
+
+
+
}
diff --git a/tests/dummy/app/components/example6.gjs b/tests/dummy/app/components/example6.gjs
index edfddce06..875a75cbc 100644
--- a/tests/dummy/app/components/example6.gjs
+++ b/tests/dummy/app/components/example6.gjs
@@ -1,29 +1,22 @@
import SelectBox from '@zestia/ember-select-box/components/select-box';
-import Component from '@glimmer/component';
-import { action } from '@ember/object';
+import { fn } from '@ember/helper';
-export default class extends Component {
- @action
- handleOpen(sb) {
- sb.search('');
- }
-
-
-
+
+
+
{{yield sb.value to="trigger"}}
- {{#if sb.isOpen}}
-
+ {{#if dd.isOpen}}
+
{{#each sb.options as |value|}}
@@ -36,8 +29,8 @@ export default class extends Component {
{{/each}}
-
+
{{/if}}
-
-
-}
+
+
+
diff --git a/tests/dummy/app/components/example7.gjs b/tests/dummy/app/components/example7.gjs
index 0f9415319..6b8634195 100644
--- a/tests/dummy/app/components/example7.gjs
+++ b/tests/dummy/app/components/example7.gjs
@@ -3,15 +3,11 @@ import { on } from '@ember/modifier';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
+import { fn } from '@ember/helper';
export default class extends Component {
@tracked inputValue = '';
- @action
- handleOpen(sb) {
- sb.search(sb.query);
- }
-
@action
handleSelect(sb) {
this.inputValue = '';
@@ -24,42 +20,45 @@ export default class extends Component {
@value={{@value}}
@onChange={{@onChange}}
@onSelect={{this.handleSelect}}
- @onOpen={{this.handleOpen}}
@onSearch={{@onSearch}}
...attributes
as |sb|
>
-
-
+
+
+
-
- {{if sb.isOpen "↑" "↓"}}
-
-
+
+ {{if dd.isOpen "↑" "↓"}}
+
+
- {{#if sb.isOpen}}
-
- {{#if sb.isBusy}}
-
- {{yield to="busy"}}
-
- {{else if sb.options}}
- {{#each sb.options as |value|}}
-
- {{yield value to="option"}}
-
- {{/each}}
- {{else}}
-
- {{yield sb.query to="noOptions"}}
-
- {{/if}}
-
- {{/if}}
+ {{#if dd.isOpen}}
+
+
+ {{#if sb.isBusy}}
+
+ {{yield to="busy"}}
+
+ {{else if sb.options}}
+ {{#each sb.options as |value|}}
+
+ {{yield value to="option"}}
+
+ {{/each}}
+ {{else}}
+
+ {{yield sb.query to="noOptions"}}
+
+ {{/if}}
+
+
+ {{/if}}
+
}
diff --git a/tests/dummy/app/controllers/custom-trigger.js b/tests/dummy/app/controllers/custom-trigger.js
deleted file mode 100644
index a66668632..000000000
--- a/tests/dummy/app/controllers/custom-trigger.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import Controller from '@ember/controller';
-import data from 'dummy/utils/data';
-import { tracked } from '@glimmer/tracking';
-import { action } from '@ember/object';
-
-export default class extends Controller {
- @tracked selected;
-
- data = data;
-
- @action
- handleSelect(item) {
- this.selected = item;
- }
-}
diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js
index d4735ab98..fc8eab4a0 100644
--- a/tests/dummy/app/router.js
+++ b/tests/dummy/app/router.js
@@ -16,6 +16,6 @@ Router.map(function () {
this.route('example5');
this.route('example6');
this.route('example7');
+ this.route('dropdown');
this.route('performance');
- this.route('custom-trigger');
});
diff --git a/tests/dummy/app/styles/app.scss b/tests/dummy/app/styles/app.scss
index 28c77d49f..5d9048d3a 100644
--- a/tests/dummy/app/styles/app.scss
+++ b/tests/dummy/app/styles/app.scss
@@ -7,4 +7,4 @@
@use 'example5';
@use 'example6';
@use 'example7';
-@use 'custom-trigger';
+@use 'dropdown';
diff --git a/tests/dummy/app/styles/custom-trigger.scss b/tests/dummy/app/styles/custom-trigger.scss
deleted file mode 100644
index 79d26f822..000000000
--- a/tests/dummy/app/styles/custom-trigger.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-@use 'example';
-
-.custom-trigger {
- &[data-open='false'] .select-box__options {
- @include example.screen-reader-only;
- }
-
- .select-box__trigger {
- display: contents;
- }
-
- .select-box__options {
- @include example.box;
- @include example.options;
-
- height: 240px;
- }
-
- .select-box__option {
- @include example.option;
- }
-
- .select-box__option[aria-current='true'] {
- @include example.option-current;
- }
-
- .select-box__option[aria-selected='true'] {
- @include example.option-selected;
- }
-}
diff --git a/tests/dummy/app/styles/dropdown.scss b/tests/dummy/app/styles/dropdown.scss
new file mode 100644
index 000000000..c947f66ff
--- /dev/null
+++ b/tests/dummy/app/styles/dropdown.scss
@@ -0,0 +1,29 @@
+@use 'example';
+
+.dropdown.example {
+ .dropdown__trigger {
+ appearance: none;
+ all: unset;
+ cursor: pointer;
+ padding: var(--space-m);
+ outline: none;
+ user-select: none;
+ display: inline-block;
+ border: 2px solid var(--light-grey);
+ }
+
+ .dropdown__trigger:focus {
+ @include example.box-focused;
+ }
+
+ .dropdown__content {
+ border: 2px solid var(--light-grey);
+ margin-top: var(--space-m);
+ padding: var(--space-xl);
+ width: 300px;
+ }
+
+ .dropdown__content:focus-within {
+ @include example.box-focused;
+ }
+}
diff --git a/tests/dummy/app/styles/example.scss b/tests/dummy/app/styles/example.scss
index 6dccdbe5f..666840b47 100644
--- a/tests/dummy/app/styles/example.scss
+++ b/tests/dummy/app/styles/example.scss
@@ -12,26 +12,37 @@
border: 2px solid var(--light-grey);
display: inline-flex;
flex-direction: column;
- margin: var(--space-sm);
- padding: var(--space-sm);
+ margin: var(--space-s);
+ padding: var(--space-s);
}
@mixin box-focused {
border-color: var(--dark-grey);
}
-@mixin option {
+@mixin trigger {
cursor: pointer;
- padding: var(--space-lg);
+ padding: var(--space-m);
+ outline: none;
+ user-select: none;
+ border: 2px solid transparent;
}
-@mixin options {
- outline: none;
+@mixin dropdown-content {
display: flex;
flex-direction: column;
+}
+
+@mixin options {
+ outline: none;
overflow: auto;
}
+@mixin option {
+ cursor: pointer;
+ padding: var(--space-l);
+}
+
@mixin option-current {
box-shadow:
inset 0 0 0 2px var(--white),
@@ -45,19 +56,11 @@
}
@mixin group-label {
- padding: var(--space-sm);
- margin: var(--space-sm);
+ padding: var(--space-s);
+ margin: var(--space-s);
font-weight: bold;
}
-@mixin trigger {
- cursor: pointer;
- padding: var(--space-md);
- outline: none;
- user-select: none;
- border: 2px solid transparent;
-}
-
@mixin trigger-focused {
border-color: var(--blue);
}
@@ -68,8 +71,8 @@
box-sizing: border-box;
min-width: 200px;
border: 2px solid var(--light-grey);
- padding: var(--space-sm);
- margin: var(--space-sm);
+ padding: var(--space-s);
+ margin: var(--space-s);
outline: none;
display: block;
}
diff --git a/tests/dummy/app/styles/example3.scss b/tests/dummy/app/styles/example3.scss
index 72f5b16b3..4de604683 100644
--- a/tests/dummy/app/styles/example3.scss
+++ b/tests/dummy/app/styles/example3.scss
@@ -7,28 +7,32 @@
@include example.box-focused;
}
- &[data-open='false'] .select-box__options {
+ .dropdown[data-open='false'] .dropdown__content {
@include example.screen-reader-only;
}
- .select-box__trigger {
+ .dropdown__trigger {
@include example.trigger;
width: 200px;
}
- .select-box__trigger:focus {
+ .dropdown__trigger:focus {
@include example.trigger-focused;
}
- .select-box__option {
- @include example.option;
+ .dropdown__content {
+ @include example.dropdown-content;
+
+ height: 240px;
}
.select-box__options {
@include example.options;
+ }
- height: 240px;
+ .select-box__option {
+ @include example.option;
}
.select-box__option[aria-current='true'] {
diff --git a/tests/dummy/app/styles/example4.scss b/tests/dummy/app/styles/example4.scss
index 8ef53f334..6300f504c 100644
--- a/tests/dummy/app/styles/example4.scss
+++ b/tests/dummy/app/styles/example4.scss
@@ -7,28 +7,32 @@
@include example.box-focused;
}
- &[data-open='false'] .select-box__options {
+ .dropdown[data-open='false'] .dropdown__content {
@include example.screen-reader-only;
}
- .select-box__trigger {
+ .dropdown__trigger {
@include example.trigger;
width: 200px;
}
- .select-box__trigger:focus {
+ .dropdown__trigger:focus {
@include example.trigger-focused;
}
- .select-box__option {
- @include example.option;
+ .dropdown__content {
+ @include example.dropdown-content;
+
+ height: 282px;
}
.select-box__options {
@include example.options;
+ }
- height: 282px;
+ .select-box__option {
+ @include example.option;
}
.select-box__option[aria-current='true'] {
diff --git a/tests/dummy/app/styles/example6.scss b/tests/dummy/app/styles/example6.scss
index c281a29a4..2c0938885 100644
--- a/tests/dummy/app/styles/example6.scss
+++ b/tests/dummy/app/styles/example6.scss
@@ -7,12 +7,16 @@
@include example.box-focused;
}
- .select-box__trigger {
+ .dropdown__trigger {
@include example.trigger;
width: 200px;
}
+ .dropdown__content {
+ @include example.dropdown-content;
+ }
+
.select-box__input {
@include example.input;
}
@@ -25,10 +29,6 @@
@include example.option;
}
- .select-box__options {
- @include example.options;
- }
-
.select-box__option[aria-current='true'] {
@include example.option-current;
}
@@ -37,8 +37,3 @@
@include example.option-selected;
}
}
-
-.example6__dropdown {
- display: flex;
- flex-direction: column;
-}
diff --git a/tests/dummy/app/styles/example7.scss b/tests/dummy/app/styles/example7.scss
index 11cbdbcd1..a537e89f6 100644
--- a/tests/dummy/app/styles/example7.scss
+++ b/tests/dummy/app/styles/example7.scss
@@ -7,13 +7,17 @@
@include example.box-focused;
}
- .select-box__trigger {
+ .dropdown__trigger {
@include example.trigger;
width: 20px;
text-align: center;
}
+ .dropdown__content {
+ @include example.dropdown-content;
+ }
+
.select-box__input {
@include example.input;
}
@@ -26,10 +30,6 @@
@include example.option;
}
- .select-box__options {
- @include example.options;
- }
-
.select-box__option[aria-current='true'] {
@include example.option-current;
}
diff --git a/tests/dummy/app/styles/variables.scss b/tests/dummy/app/styles/variables.scss
index c7c22a212..e69f8a9a5 100644
--- a/tests/dummy/app/styles/variables.scss
+++ b/tests/dummy/app/styles/variables.scss
@@ -6,7 +6,8 @@
--red: red;
--blue: blue;
--white: white;
- --space-lg: 6px;
- --space-md: 4px;
- --space-sm: 2px;
+ --space-xl: 10px;
+ --space-l: 6px;
+ --space-m: 4px;
+ --space-s: 2px;
}
diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs
index 8478ddb20..755034e93 100644
--- a/tests/dummy/app/templates/application.hbs
+++ b/tests/dummy/app/templates/application.hbs
@@ -30,6 +30,10 @@
Combobox with input outside
+ |
+
+ Dropdown
+
diff --git a/tests/dummy/app/templates/custom-trigger.hbs b/tests/dummy/app/templates/custom-trigger.hbs
deleted file mode 100644
index f022f634a..000000000
--- a/tests/dummy/app/templates/custom-trigger.hbs
+++ /dev/null
@@ -1,26 +0,0 @@
-
- Selected:
- {{this.selected.name}}
-
-
-
-
-
- {{if sb.value sb.value.name "None"}}
-
-
- {{#if sb.isOpen}}
-
- {{#each this.data.pies as |value|}}
-
- {{value.name}}
-
- {{/each}}
-
- {{/if}}
-
\ No newline at end of file
diff --git a/tests/dummy/app/templates/dropdown.hbs b/tests/dummy/app/templates/dropdown.hbs
new file mode 100644
index 000000000..0412edc11
--- /dev/null
+++ b/tests/dummy/app/templates/dropdown.hbs
@@ -0,0 +1,20 @@
+
+ This addon utilises a dropdown component to create comboboxes that can open
+ and close like a native single select.
+
+
+
+
+ Click here
+
+ {{#if dd.isOpen}}
+
+
+ Non interactive element
+
+
+ Interactive element
+
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/tests/dummy/app/templates/performance.hbs b/tests/dummy/app/templates/performance.hbs
index 32af83eb9..d9fb4c219 100644
--- a/tests/dummy/app/templates/performance.hbs
+++ b/tests/dummy/app/templates/performance.hbs
@@ -9,16 +9,20 @@
@onChange={{this.handleSelect}}
as |sb|
>
-
- {{if sb.value sb.value.name "None"}}
-
- {{#if sb.isOpen}}
-
- {{#each this.data as |value|}}
-
- {{value.name}}
-
- {{/each}}
-
- {{/if}}
+
+
+ {{if sb.value sb.value.name "None"}}
+
+ {{#if dd.isOpen}}
+
+
+ {{#each this.data as |value|}}
+
+ {{value.name}}
+
+ {{/each}}
+
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/tests/integration/components/dropdown/content/render-test.gjs b/tests/integration/components/dropdown/content/render-test.gjs
new file mode 100644
index 000000000..be4c49c81
--- /dev/null
+++ b/tests/integration/components/dropdown/content/render-test.gjs
@@ -0,0 +1,72 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown/content', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__content').hasTagName('div');
+ });
+
+ test('it renders', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+ Hello World
+
+
+ );
+
+ assert.dom('.dropdown__content').hasText('Hello World');
+ });
+
+ test('splattributes', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__content').hasClass('foo');
+ });
+
+ test('tabindex', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__content').doesNotHaveAttribute('tabindex');
+ });
+
+ test('tabindex (closure component)', async function (assert) {
+ assert.expect(1);
+
+ // So we can disable keyboard-focusable-scrollers
+
+ await render(
+
+ {{component dd.Content tabindex="-1"}}
+
+ );
+
+ assert.dom('.dropdown__content').hasAttribute('tabindex', '-1');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/api-test.gjs b/tests/integration/components/dropdown/index/api-test.gjs
new file mode 100644
index 000000000..5d0b152c9
--- /dev/null
+++ b/tests/integration/components/dropdown/index/api-test.gjs
@@ -0,0 +1,110 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { find, render, rerender, click } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown (api)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('api', async function (assert) {
+ assert.expect(8);
+
+ let api;
+ let api2;
+
+ const handleReady = (dd) => (api = dd);
+ const capture = (dd) => (api2 = dd);
+
+ await render(
+
+
+
+ {{capture dd}}
+
+
+ );
+
+ assert.strictEqual(api, api2);
+
+ // Components
+ assert.strictEqual(typeof api.Trigger, 'object');
+ assert.strictEqual(typeof api.Content, 'object');
+
+ // Properties
+ assert.deepEqual(api.element, find('.dropdown'));
+ assert.strictEqual(api.isOpen, false);
+
+ // Actions
+ assert.strictEqual(typeof api.open, 'function');
+ assert.strictEqual(typeof api.close, 'function');
+ assert.strictEqual(typeof api.toggle, 'function');
+ });
+
+ test('api writing', async function (assert) {
+ assert.expect(1);
+
+ let api;
+
+ const handleReady = (sb) => (api = sb);
+
+ await render(
+
+
+ {{dd.isOpen}}
+
+
+ );
+
+ assert.throws(() => {
+ api.isOpen = true;
+ }, 'read only api');
+ });
+
+ test('isOpen', async function (assert) {
+ assert.expect(2);
+
+ let api;
+
+ const handleReady = (dd) => (api = dd);
+
+ await render(
+
+
+
+ );
+
+ assert.false(api.isOpen);
+
+ await click('.dropdown__trigger');
+
+ assert.true(api.isOpen);
+ });
+
+ test('toggle', async function (assert) {
+ assert.expect(3);
+
+ let api;
+
+ const handleReady = (dd) => (api = dd);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+
+ api.toggle();
+
+ await rerender();
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ api.toggle();
+
+ await rerender();
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/click-trigger-test.gjs b/tests/integration/components/dropdown/index/click-trigger-test.gjs
new file mode 100644
index 000000000..0d4789df7
--- /dev/null
+++ b/tests/integration/components/dropdown/index/click-trigger-test.gjs
@@ -0,0 +1,52 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, click } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown (clicking trigger)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('clicking trigger', async function (assert) {
+ assert.expect(7);
+
+ // Whether or not the Content renders is up to the developer.
+ // This allows it to be hidden with CSS instead if preferred.
+
+ await render(
+
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.dropdown__trigger').hasAttribute('aria-expanded', 'false');
+ assert.dom('.dropdown__content').exists();
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.dropdown__trigger').hasAttribute('aria-expanded', 'true');
+ assert.dom('.dropdown__content').exists();
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ });
+
+ test('right clicking trigger', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+
+ await click('.dropdown__trigger', { button: 2 });
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/closing-test.gjs b/tests/integration/components/dropdown/index/closing-test.gjs
new file mode 100644
index 000000000..e18c6e585
--- /dev/null
+++ b/tests/integration/components/dropdown/index/closing-test.gjs
@@ -0,0 +1,317 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import {
+ render,
+ click,
+ rerender,
+ focus,
+ blur,
+ triggerEvent,
+ triggerKeyEvent
+} from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+import { on } from '@ember/modifier';
+
+module('dropdown (closing)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ let handleClose;
+
+ hooks.beforeEach(function (assert) {
+ handleClose = (reason) => assert.step(`close ${reason?.description}`);
+ });
+
+ test('pressing escape', async function (assert) {
+ assert.expect(3);
+
+ await render(
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ await triggerKeyEvent('.dropdown', 'keydown', 'Escape');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close ESCAPE']);
+ });
+
+ test('clicking outside', async function (assert) {
+ assert.expect(5);
+
+ await render(
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.dropdown__trigger').isNotFocused();
+ assert.verifySteps(['close FOCUS_LEAVE'], 'focusout fires before mouseup');
+ });
+
+ test('clicking dropdown container', async function (assert) {
+ assert.expect(5);
+
+ // This tests that the dropdown container is treated
+ // as empty space, and so click it is the same as
+ // clicking outside the dropdown content element.
+
+ await render(
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.dropdown');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.dropdown__trigger').isNotFocused();
+ assert.verifySteps(['close FOCUS_LEAVE']);
+ });
+
+ test('clicking a non interactive element inside the dropdown content', async function (assert) {
+ assert.expect(7);
+
+ // Selecting text won't cause the dropdown to close.
+
+ await render(
+
+
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.dropdown__trigger').isNotFocused();
+ assert.verifySteps([]);
+
+ await click('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close CLICK_OUTSIDE']);
+ });
+
+ test('clicking outside a manually opened dropdown', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close CLICK_OUTSIDE']);
+ });
+
+ test('mousing up without first having moused down', async function (assert) {
+ assert.expect(3);
+
+ // This can happen when rendering a dropdown with an initial open state
+ // that was the result of a click to reveal it.
+
+ await render(
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await triggerEvent('.outside', 'mouseup');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.verifySteps([]);
+ });
+
+ test('mousing down on the trigger but mousing up outside', async function (assert) {
+ assert.expect(5);
+
+ // aka click-abort
+
+ await render(
+
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+
+ await triggerEvent('.dropdown__trigger', 'mousedown');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await triggerEvent('.outside', 'mouseup');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close CLICK_OUTSIDE']);
+ });
+
+ test('focus leaving the dropdown trigger', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await blur('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close FOCUS_LEAVE']);
+ });
+
+ test('focus leaving the dropdown trigger when manually opened', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+
+ );
+
+ await focus('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close FOCUS_LEAVE']);
+ });
+
+ test('focus leaving an interactive element inside the dropdown', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+ await focus('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close FOCUS_LEAVE']);
+ });
+
+ test('focus leaving an interactive element inside the content', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+ await focus('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close FOCUS_LEAVE']);
+ });
+
+ test('closing with exposed api', async function (assert) {
+ assert.expect(5);
+
+ let api;
+
+ const handleReady = (dd) => (api = dd);
+
+ await render(
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ api.close();
+
+ await rerender();
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.dropdown__trigger').isFocused();
+ assert.verifySteps(['close undefined']);
+ });
+
+ test('closing with yielded api', async function (assert) {
+ assert.expect(5);
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ // Intentionally twice
+ await click('.close');
+ await click('.close');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.dropdown__trigger').isNotFocused();
+ assert.verifySteps(['close undefined']);
+ });
+});
diff --git a/tests/integration/components/dropdown/index/edge-case-test.gjs b/tests/integration/components/dropdown/index/edge-case-test.gjs
new file mode 100644
index 000000000..821cf4868
--- /dev/null
+++ b/tests/integration/components/dropdown/index/edge-case-test.gjs
@@ -0,0 +1,40 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, focus, triggerKeyEvent } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+import { tracked } from '@glimmer/tracking';
+import { on } from '@ember/modifier';
+
+module('dropdown (edge cases)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('destroying a dropdown with a focus inside (and no trigger)', async function (assert) {
+ assert.expect(1);
+
+ // https://github.com/emberjs/ember.js/issues/18043
+
+ const state = new (class {
+ @tracked show = true;
+ })();
+
+ const destroy = (value) => {
+ state.show = false;
+ };
+
+ await render(
+ {{#if state.show}}
+
+
+
+
+ {{/if}}
+ );
+
+ await focus('input');
+ await triggerKeyEvent('.close', 'keydown', 'Enter');
+
+ assert
+ .dom('.select-box')
+ .doesNotExist('does not cause infinite revalidation bug');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/focus-test.gjs b/tests/integration/components/dropdown/index/focus-test.gjs
new file mode 100644
index 000000000..6d4383892
--- /dev/null
+++ b/tests/integration/components/dropdown/index/focus-test.gjs
@@ -0,0 +1,218 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, focus, click } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+import { on } from '@ember/modifier';
+import { tracked } from '@glimmer/tracking';
+import autoFocus from '@zestia/ember-auto-focus/modifiers/auto-focus';
+
+module('dropdown (focus)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ let handleClose;
+
+ hooks.beforeEach(function (assert) {
+ handleClose = (reason) => assert.step(`close ${reason.description}`);
+ });
+
+ test('clicking an interactive element inside the dropdown container', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.inside').isFocused();
+ assert.verifySteps([]);
+ });
+
+ test('clicking an interactive element inside the dropdown content', async function (assert) {
+ assert.expect(5);
+
+ let event;
+
+ const handleMouseDown = (_event) => (event = _event);
+
+ await render(
+ {{! template-lint-disable no-pointer-down-event-binding }}
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.dropdown__trigger').isNotFocused();
+ assert.false(event.defaultPrevented);
+ assert.verifySteps([]);
+ });
+
+ test('focus moving inside the dropdown', async function (assert) {
+ assert.expect(3);
+
+ await render(
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.inside').isFocused();
+ });
+
+ test('focus moving inside the content', async function (assert) {
+ assert.expect(3);
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.inside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.inside').isFocused();
+ });
+
+ test('keyboard-focusable-scrollers', async function (assert) {
+ assert.expect(2);
+
+ // we use do not employ any methods to prevent
+ // keyboard-focusable-scrollers from taking affect
+ // this allows focus to move to the dropdown content
+ // when it is overflowing.
+
+ let event;
+
+ const handleMouseDown = (_event) => (event = _event);
+
+ await render(
+ {{! template-lint-disable no-pointer-down-event-binding }}
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+ await click('.dropdown__content');
+
+ assert.dom('.dropdown__content').doesNotHaveAttribute('tabindex');
+ assert.false(event.defaultPrevented);
+ });
+
+ test('closing does not steal focus', async function (assert) {
+ assert.expect(1);
+
+ const state = new (class {
+ @tracked showInput;
+ })();
+
+ const handleClick = () => {
+ state.showInput = true;
+ };
+
+ await render(
+
+
+
+
+
+
+
+ {{#if state.showInput}}
+
+ {{/if}}
+ );
+
+ await click('.dropdown__trigger');
+ await click('.inside');
+
+ assert.dom('.outside').isFocused();
+ });
+
+ test('auto focusing an element inside the dropdown content', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+ {{#if dd.isOpen}}
+
+
+
+ {{/if}}
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.inside').isFocused();
+ });
+
+ test('focus-visible', async function (assert) {
+ assert.expect(1);
+
+ await render(
+ {{! template-lint-disable no-forbidden-elements }}
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown__trigger').hasStyle(
+ { outline: 'rgb(255, 0, 0) solid 2px' },
+ `the dropdown's focus management does not accidentally cause
+ focus-visible styles to apply. (here, the trigger was clicked,
+ whereas the styles should only apply if the user had navigated
+ to the element using the keyboard`
+ );
+ });
+});
diff --git a/tests/integration/components/dropdown/index/in-element-test.gjs b/tests/integration/components/dropdown/index/in-element-test.gjs
new file mode 100644
index 000000000..c5ab7bb60
--- /dev/null
+++ b/tests/integration/components/dropdown/index/in-element-test.gjs
@@ -0,0 +1,49 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { click, render, find } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown (in-element)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ const destination = () => find('.destination');
+
+ test('a common scenario of rendering a dropdown in an external element works', async function (assert) {
+ assert.expect(6);
+
+ await render(
+
+
+ Trigger
+
+ {{#if dd.isOpen}}
+ {{#in-element (destination) insertBefore=null}}
+
+ Hello World
+
+
+ {{/in-element}}
+ {{/if}}
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.dropdown .dropdown__content').doesNotExist();
+ assert.dom('.destination .dropdown__content').exists();
+
+ await click('.test');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await click('.outside');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.outside').isFocused();
+ });
+});
diff --git a/tests/integration/components/dropdown/index/key-enter-test.gjs b/tests/integration/components/dropdown/index/key-enter-test.gjs
new file mode 100644
index 000000000..986208952
--- /dev/null
+++ b/tests/integration/components/dropdown/index/key-enter-test.gjs
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, triggerKeyEvent } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown (enter)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('enter on trigger', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+
+ );
+
+ await triggerKeyEvent('.dropdown__trigger', 'keydown', 'Enter');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await triggerKeyEvent('.dropdown__trigger', 'keydown', 'Enter');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/key-escape-test.gjs b/tests/integration/components/dropdown/index/key-escape-test.gjs
new file mode 100644
index 000000000..ea68b21b3
--- /dev/null
+++ b/tests/integration/components/dropdown/index/key-escape-test.gjs
@@ -0,0 +1,128 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, click, focus, triggerKeyEvent } from '@ember/test-helpers';
+import { on } from '@ember/modifier';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown (escape)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ let handleClose;
+
+ hooks.beforeEach(function (assert) {
+ handleClose = () => assert.step('close');
+ });
+
+ test('escape on trigger', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await triggerKeyEvent('.dropdown__trigger', 'keydown', 'Escape');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close']);
+ });
+
+ test('escape on dropdown', async function (assert) {
+ assert.expect(4);
+
+ // This ensures our listeners are on the dropdown itself,
+ // and not just on the trigger.
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.inside');
+ await triggerKeyEvent('.dropdown', 'keydown', 'Escape');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close']);
+ });
+
+ test('escape inside something else escapable (closed)', async function (assert) {
+ assert.expect(1);
+
+ let event;
+
+ const handleKeyDownParent = (_event) => (event = _event);
+
+ await render(
+ {{! template-lint-disable no-invalid-interactive }}
+
+
+
+
+
+
+
+ );
+
+ await triggerKeyEvent('.dropdown', 'keydown', 'Escape');
+
+ assert.true(
+ event instanceof Event,
+ 'event not stopped, escape allowed to bubble up'
+ );
+ });
+
+ test('escape inside something else escapable (open)', async function (assert) {
+ assert.expect(5);
+
+ let event;
+
+ const handleKeyDownParent = (_event) => (event = _event);
+
+ await render(
+ {{! template-lint-disable no-invalid-interactive }}
+
+
+
+
+
+
+
+
+
+ );
+
+ await click('.parent .dropdown__trigger');
+ await click('.child .dropdown__trigger');
+ await triggerKeyEvent('.child', 'keydown', 'Escape');
+
+ assert.dom('.parent').hasAttribute('data-open', 'true');
+ assert.dom('.child').hasAttribute('data-open', 'false');
+
+ assert.notOk(
+ event,
+ `event propagation is stopped, since escape has caused the dropdown
+ to close. we don't want escape to also close the parent element
+ that the select box is contained within`
+ );
+
+ assert.verifySteps(['close']);
+ });
+});
diff --git a/tests/integration/components/dropdown/index/key-space-test.gjs b/tests/integration/components/dropdown/index/key-space-test.gjs
new file mode 100644
index 000000000..edbd4ac20
--- /dev/null
+++ b/tests/integration/components/dropdown/index/key-space-test.gjs
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, triggerKeyEvent } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('space on trigger (space)', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+
+ );
+
+ await triggerKeyEvent('.dropdown__trigger', 'keydown', ' ');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+
+ await triggerKeyEvent('.dropdown__trigger', 'keydown', ' ');
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/opening-test.gjs b/tests/integration/components/dropdown/index/opening-test.gjs
new file mode 100644
index 000000000..defdb36bd
--- /dev/null
+++ b/tests/integration/components/dropdown/index/opening-test.gjs
@@ -0,0 +1,72 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, rerender } from '@ember/test-helpers';
+import { tracked } from '@glimmer/tracking';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown (opening)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('open', async function (assert) {
+ assert.expect(2);
+
+ let api;
+
+ const handleReady = (dd) => (api = dd);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+
+ api.open();
+
+ await rerender();
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ });
+
+ test('can set initial open state', async function (assert) {
+ assert.expect(3);
+
+ await render(
+
+
+
+
+ );
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.dropdown__trigger').hasAttribute('aria-expanded', 'true');
+ assert.dom('.dropdown__trigger').isNotFocused();
+ });
+
+ test('cannot open dropdown manually with argument', async function (assert) {
+ assert.expect(2);
+
+ // We can easily support this using localCopy.
+ // But, changing `@open` will not pass through the expected
+ // code path, like it would with a user interaction, and so
+ // onOpen will not fire if we do that.
+
+ const state = new (class {
+ @tracked isOpen;
+ })();
+
+ await render(
+
+
+
+ );
+
+ state.isOpen = true;
+
+ await rerender();
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.dropdown__trigger').hasAttribute('aria-expanded', 'false');
+ });
+});
diff --git a/tests/integration/components/dropdown/index/render-test.gjs b/tests/integration/components/dropdown/index/render-test.gjs
new file mode 100644
index 000000000..a6e2fff7a
--- /dev/null
+++ b/tests/integration/components/dropdown/index/render-test.gjs
@@ -0,0 +1,40 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, find } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ assert.expect(1);
+
+ await render(
);
+
+ assert.dom('.dropdown').hasTagName('div');
+ });
+
+ test('open', async function (assert) {
+ assert.expect(1);
+
+ await render(
);
+
+ assert.dom('.dropdown').hasAttribute('data-open', 'false');
+ });
+
+ test('splattributes', async function (assert) {
+ assert.expect(1);
+
+ await render(
);
+
+ assert.dom('.dropdown').hasClass('foo');
+ });
+
+ test('whitespace', async function (assert) {
+ assert.expect(1);
+
+ await render(
);
+
+ assert.strictEqual(find('.dropdown').innerHTML, '');
+ });
+});
diff --git a/tests/integration/components/dropdown/trigger/render-test.gjs b/tests/integration/components/dropdown/trigger/render-test.gjs
new file mode 100644
index 000000000..443f0acaa
--- /dev/null
+++ b/tests/integration/components/dropdown/trigger/render-test.gjs
@@ -0,0 +1,152 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, find } from '@ember/test-helpers';
+import Dropdown from '@zestia/ember-select-box/components/dropdown';
+
+module('dropdown/trigger', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__trigger').hasTagName('div');
+ });
+
+ test('splattributes', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__trigger').hasClass('foo');
+ });
+
+ test('role', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__trigger').doesNotHaveAttribute('role');
+ });
+
+ test('role (closure component)', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+ {{component dd.Trigger role="button"}}
+
+ );
+
+ assert.dom('.dropdown__trigger').hasAttribute('role', 'button');
+ });
+
+ test('tabindex', async function (assert) {
+ assert.expect(1);
+
+ // Requires tabindex so that Safari will populate relatedTarget
+ // and handle focus properly.
+
+ await render(
+
+
+
+ );
+
+ assert.dom('.dropdown__trigger').hasAttribute('tabindex', '0');
+ });
+
+ test('tabindex (closure component)', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+ {{component dd.Trigger tabindex="-1"}}
+
+ );
+
+ assert.dom('.dropdown__trigger').hasAttribute('tabindex', '-1');
+ });
+
+ test('class (closure component)', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+ {{component dd.Trigger class="foo"}}
+
+ );
+
+ assert.dom('.dropdown__trigger').hasClass('foo');
+ });
+
+ test('whitespace', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+ );
+
+ assert.strictEqual(find('.dropdown__trigger').innerHTML, '');
+ });
+
+ test('aria defaults', async function (assert) {
+ assert.expect(6);
+
+ await render(
+
+
+
+ );
+
+ assert
+ .dom('.dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false')
+ .hasAttribute('aria-haspopup', 'true')
+ .doesNotHaveAttribute('aria-busy')
+ .doesNotHaveAttribute('aria-controls')
+ .doesNotHaveAttribute('aria-disabled')
+ .doesNotHaveAttribute('aria-activedescendant');
+ });
+
+ test('aria (closure component)', async function (assert) {
+ assert.expect(6);
+
+ await render(
+
+ {{component
+ dd.Trigger
+ aria-expanded="true"
+ aria-busy="true"
+ aria-controls="foo"
+ aria-disabled="true"
+ aria-activedescendant="bar"
+ }}
+
+ );
+
+ assert
+ .dom('.dropdown__trigger')
+ .hasAttribute('aria-haspopup', 'true')
+ .hasAttribute('aria-expanded', 'true')
+ .hasAttribute('aria-busy', 'true')
+ .hasAttribute('aria-controls', 'foo')
+ .hasAttribute('aria-disabled', 'true')
+ .hasAttribute('aria-activedescendant', 'bar');
+ });
+});
diff --git a/tests/integration/components/select-box/click-trigger-test.gjs b/tests/integration/components/select-box/click-trigger-test.gjs
deleted file mode 100644
index 635865799..000000000
--- a/tests/integration/components/select-box/click-trigger-test.gjs
+++ /dev/null
@@ -1,59 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render, triggerEvent } from '@ember/test-helpers';
-import { on } from '@ember/modifier';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-
-module('select-box (clicking trigger)', function (hooks) {
- setupRenderingTest(hooks);
-
- test('clicking trigger toggles select box on mousedown', async function (assert) {
- assert.expect(7);
-
- // Mouse down makes the select box feel more responsive
- // than on click, which would open on mouseup.
- // It's also what would happen if you clicked on a
- // native select box.
-
- let event;
-
- const handleMouseDownTrigger = (_event) => (event = _event);
-
- await render(
-
- {{! template-lint-disable no-pointer-down-event-binding }}
-
-
- );
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
-
- await triggerEvent('.select-box__trigger', 'mousedown');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
-
- await triggerEvent('.select-box__trigger', 'mousedown');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
-
- assert.true(event.defaultPrevented);
- });
-
- test('right clicking trigger does not open select box', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
- );
-
- await triggerEvent('.select-box__trigger', 'mousedown', { button: 2 });
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- });
-});
diff --git a/tests/integration/components/select-box/closing-test.gjs b/tests/integration/components/select-box/closing-test.gjs
deleted file mode 100644
index 66df39c2f..000000000
--- a/tests/integration/components/select-box/closing-test.gjs
+++ /dev/null
@@ -1,301 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import {
- render,
- click,
- blur,
- focus,
- triggerEvent,
- triggerKeyEvent
-} from '@ember/test-helpers';
-import { tracked } from '@glimmer/tracking';
-import { on } from '@ember/modifier';
-import autoFocus from '@zestia/ember-auto-focus/modifiers/auto-focus';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-
-module('select-box (closing)', function (hooks) {
- setupRenderingTest(hooks);
-
- let api;
- let handleClose;
-
- const handleReady = (sb) => (api = sb);
-
- hooks.beforeEach(function (assert) {
- handleClose = () => assert.step('close');
- });
-
- test('closing with api', async function (assert) {
- assert.expect(6);
-
- await render(
-
-
-
-
-
- );
-
- assert.true(api.isOpen);
-
- await click('button');
- await click('button');
-
- assert.verifySteps(['close']);
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- });
-
- test('closes when trigger loses focus', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
- );
-
- await focus('.select-box__trigger');
- await blur('.select-box__trigger');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- });
-
- test('closes when input loses focus', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
- );
-
- await focus('.select-box__input');
- await blur('.select-box__input');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- });
-
- test('closing listbox', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
- );
-
- await click('button');
-
- assert.verifySteps([], 'listboxes cannot be closed');
- });
-
- test('closing does not steal focus', async function (assert) {
- assert.expect(2);
-
- const state = new (class {
- @tracked value;
- })();
-
- const handleChange = (value) => (state.value = value);
-
- await render(
-
-
-
-
-
-
-
- {{#if state.value}}
-
- {{/if}}
- );
-
- await click('.select-box__trigger');
- await click('.select-box__option');
-
- assert.dom('.outside').hasValue('foo').isFocused();
- });
-
- test('closing due to loss of focus', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
- await blur('.select-box__trigger');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.verifySteps(['close']);
- });
-
- test('closing due to mousing up outside', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
-
-
-
- );
-
- await triggerEvent('.select-box__trigger', 'mousedown');
- await triggerEvent('.outside', 'mouseup');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.verifySteps(['close']);
- });
-
- test('mousing up outside must have moused down first', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
-
-
- );
-
- await click('.outside');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.verifySteps([]);
- });
-
- test('closing due to touching outside', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
- );
-
- await triggerEvent('.select-box', 'mousedown');
- await triggerEvent('.outside', 'touchstart');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.verifySteps(['close']);
- });
-
- test('closing due to pressing escape', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Escape');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.verifySteps(['close']);
- });
-
- test('close only fires once', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
- await blur('.select-box__trigger');
- await triggerEvent('.outside', 'mouseup');
-
- assert.verifySteps(
- ['close'],
- `a select box may close on focus leave and mousing up outside,
- if focus is lost _because_ of the mousing up outside, then
- the onClose action only fires once, not twice`
- );
- });
-
- test('programmatically closing', async function (assert) {
- assert.expect(3);
-
- const handleSelect = () => false;
-
- await render(
-
-
-
-
- );
-
- await click('.select-box__trigger');
-
- assert.verifySteps(
- [],
- `the return value of onSelect controls whether or not the
- select box will close after making the selection`
- );
-
- await click('button');
-
- assert.verifySteps(['close']);
- });
-
- test('clicking to programmatically close', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
- {{#if sb.isOpen}}
-
-
-
- {{/if}}
-
- );
-
- await click('.select-box__trigger');
- await click('button');
-
- assert.ok(true, 'does not cause infinite revalidation bug');
- });
-});
diff --git a/tests/integration/components/select-box/conditional-modifier-test.gjs b/tests/integration/components/select-box/conditional-modifier-test.gjs
deleted file mode 100644
index d7d88f409..000000000
--- a/tests/integration/components/select-box/conditional-modifier-test.gjs
+++ /dev/null
@@ -1,42 +0,0 @@
-import { module, skip } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render, click } from '@ember/test-helpers';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-import { modifier } from 'ember-modifier';
-import { tracked } from '@glimmer/tracking';
-
-module('select-box', function (hooks) {
- setupRenderingTest(hooks);
-
- // Regression test for issue
- // https://github.com/ember-modifier/ember-modifier/issues/851
-
- const position = modifier(
- (dropdown, [container]) => (dropdown.dataset.positioned = 'true'),
- { eager: false }
- );
-
- skip('it does not blow up', async function (assert) {
- assert.expect(0);
-
- const state = new (class {
- @tracked value;
- })();
-
- const handleChange = (value) => {
- state.value = value;
- };
-
- await render(
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger'); // Open
- await click('.select-box__option'); // Close
- });
-});
diff --git a/tests/integration/components/group/render-test.gjs b/tests/integration/components/select-box/group/render-test.gjs
similarity index 100%
rename from tests/integration/components/group/render-test.gjs
rename to tests/integration/components/select-box/group/render-test.gjs
diff --git a/tests/integration/components/select-box/in-element-test.gjs b/tests/integration/components/select-box/in-element-test.gjs
deleted file mode 100644
index 429ef5eed..000000000
--- a/tests/integration/components/select-box/in-element-test.gjs
+++ /dev/null
@@ -1,52 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import { click, render, find, triggerEvent } from '@ember/test-helpers';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-
-module('select-box (in-element)', function (hooks) {
- setupRenderingTest(hooks);
-
- const destination = () => find('.destination');
-
- test('a common scenario of rendering a dropdown in an external element works', async function (assert) {
- assert.expect(6);
-
- await render(
-
-
- {{sb.value}}
-
- {{#if sb.isOpen}}
- {{#in-element (destination) insertBefore=null}}
-
-
-
-
- {{/in-element}}
- {{/if}}
-
-
-
-
-
- );
-
- await triggerEvent('.select-box__trigger', 'mousedown');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box .select-box__options').doesNotExist();
- assert.dom('.destination .select-box__options').exists();
-
- // Implied that the mouse will leave the select box,
- // because we used in-element so the options are _outside_.
- await triggerEvent('.select-box', 'mouseleave');
- await triggerEvent('.select-box__option:nth-child(2)', 'mouseup');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasText('bar');
-
- await click('.outside');
-
- assert.dom('.outside').isFocused();
- });
-});
diff --git a/tests/integration/components/select-box/api-test.gjs b/tests/integration/components/select-box/index/api-test.gjs
similarity index 65%
rename from tests/integration/components/select-box/api-test.gjs
rename to tests/integration/components/select-box/index/api-test.gjs
index 476332cda..272881f94 100644
--- a/tests/integration/components/select-box/api-test.gjs
+++ b/tests/integration/components/select-box/index/api-test.gjs
@@ -1,6 +1,13 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
-import { find, render, fillIn, settled, click } from '@ember/test-helpers';
+import {
+ find,
+ render,
+ rerender,
+ fillIn,
+ settled,
+ click
+} from '@ember/test-helpers';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
import { fn } from '@ember/helper';
@@ -10,7 +17,7 @@ module('select-box (api)', function (hooks) {
setupRenderingTest(hooks);
test('api', async function (assert) {
- assert.expect(18);
+ assert.expect(17);
let api;
let api2;
@@ -20,10 +27,14 @@ module('select-box (api)', function (hooks) {
await render(
-
-
- {{capture sb}}
-
+
+
+
+
+ {{capture sb}}
+
+
+
);
@@ -34,26 +45,65 @@ module('select-box (api)', function (hooks) {
assert.strictEqual(typeof api.Input, 'object');
assert.strictEqual(typeof api.Option, 'object');
assert.strictEqual(typeof api.Options, 'object');
+ assert.strictEqual(typeof api.Dropdown, 'object');
assert.strictEqual(typeof api.Trigger, 'object');
+ assert.strictEqual(typeof api.Content, 'object');
// Properties
assert.deepEqual(api.element, find('.select-box'));
assert.strictEqual(api.isBusy, null);
- assert.false(api.isOpen);
assert.strictEqual(api.options, undefined);
assert.strictEqual(api.query, null);
assert.strictEqual(api.value, undefined);
+ assert.strictEqual(typeof api.dropdown, 'object');
// Actions
- assert.strictEqual(typeof api.close, 'function');
- assert.strictEqual(typeof api.open, 'function');
assert.strictEqual(typeof api.search, 'function');
- assert.strictEqual(typeof api.toggle, 'function');
assert.strictEqual(typeof api.update, 'function');
assert.strictEqual(typeof api.select, 'function');
});
- test('provides access to two useful elements', async function (assert) {
+ test('api without dropdown', async function (assert) {
+ assert.expect(2);
+
+ let api;
+
+ const handleReady = (sb) => (api = sb);
+
+ await render(
);
+
+ assert.strictEqual(typeof api.Dropdown, 'object');
+ assert.strictEqual(
+ api.Trigger,
+ undefined,
+ `can't render a select box trigger without first
+ having rendered a dropdown.`
+ );
+ });
+
+ test('api writing', async function (assert) {
+ assert.expect(1);
+
+ let api;
+
+ const handleReady = (sb) => (api = sb);
+
+ await render(
+
+
+
+ {{sb.value}}
+
+
+
+ );
+
+ assert.throws(() => {
+ api.value = 'foo';
+ }, 'read only api');
+ });
+
+ test('provides access to the main element', async function (assert) {
assert.expect(1);
let api;
@@ -69,7 +119,7 @@ module('select-box (api)', function (hooks) {
assert.strictEqual(api.element, find('.select-box'));
});
- test('isBusy (searchable)', async function (assert) {
+ test('isBusy (aka searchable)', async function (assert) {
assert.expect(3);
let api;
@@ -81,9 +131,8 @@ module('select-box (api)', function (hooks) {
await render(
-
-
-
+
+
);
@@ -125,15 +174,17 @@ module('select-box (api)', function (hooks) {
await render(
-
+
+
+
);
- assert.false(api.isOpen);
+ assert.false(api.dropdown.isOpen);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
- assert.true(api.isOpen);
+ assert.true(api.dropdown.isOpen);
});
test('value', async function (assert) {
@@ -184,66 +235,6 @@ module('select-box (api)', function (hooks) {
assert.verifySteps(['foo']);
});
- test('open', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
- );
-
- await click('button');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
- });
-
- test('close', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
- );
-
- await click('button');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- });
-
- test('toggle', async function (assert) {
- assert.expect(6);
-
- await render(
-
-
-
-
-
- );
-
- await click('button');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
-
- await click('button');
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- });
-
test('select', async function (assert) {
assert.expect(8);
@@ -303,61 +294,69 @@ module('select-box (api)', function (hooks) {
test('select (not focused)', async function (assert) {
assert.expect(4);
- const state = new (class {
- @tracked api;
- })();
+ let api;
- const handleReady = (sb) => (state.api = sb);
+ const handleReady = (sb) => (api = sb);
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
);
- await click('button');
+ api.select('2');
+
+ await rerender();
assert
.dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-selected', 'true', 'precondition');
+ .hasAttribute('aria-selected', 'true');
assert.dom('.select-box__input').isNotFocused();
- assert.dom('.select-box__trigger').isNotFocused();
+ assert.dom('.select-box .dropdown__trigger').isNotFocused();
assert.dom('.select-box__options').isNotFocused();
});
test('select (closes)', async function (assert) {
- assert.expect(1);
+ assert.expect(2);
- const state = new (class {
- @tracked api;
- })();
+ let api;
- const handleReady = (sb) => (state.api = sb);
+ const handleReady = (sb) => (api = sb);
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
);
- await click('button');
+ await click('.dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+
+ api.select('2');
+
+ await rerender();
assert
- .dom('.select-box')
+ .dom('.select-box .dropdown')
.hasAttribute(
'data-open',
'false',
@@ -368,25 +367,21 @@ module('select-box (api)', function (hooks) {
test('select (disabled option)', async function (assert) {
assert.expect(2);
- const state = new (class {
- @tracked api;
- })();
+ let api;
- const handleReady = (sb) => (state.api = sb);
+ const handleReady = (sb) => (api = sb);
await render(
-
-
-
-
);
- await click('button');
+ api.select('foo');
+
+ await rerender();
assert
.dom('.select-box__option:nth-child(1)')
@@ -426,7 +421,6 @@ module('select-box (api)', function (hooks) {
@onBuildSelection={{handleBuildSelection}}
as |sb|
>
-
@@ -434,8 +428,10 @@ module('select-box (api)', function (hooks) {
);
- await click('button');
- await click('button');
+ api.update('2');
+ api.update('2');
+
+ await rerender();
assert.strictEqual(state.value, '1', 'does not mutate value');
assert.strictEqual(api.value, '2', 'api is correct');
@@ -469,31 +465,4 @@ module('select-box (api)', function (hooks) {
await click('button');
});
-
- test('open boolean with a trigger defaults to closed', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
-
- );
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__options').hasAttribute('data-open', 'false');
- });
-
- test('open boolean without a trigger is undefined', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
- assert.dom('.select-box__options').doesNotHaveAttribute('data-open');
- });
});
diff --git a/tests/integration/components/select-box/change-options-test.gjs b/tests/integration/components/select-box/index/change-options-test.gjs
similarity index 98%
rename from tests/integration/components/select-box/change-options-test.gjs
rename to tests/integration/components/select-box/index/change-options-test.gjs
index 0411a1c0e..3ef0ed538 100644
--- a/tests/integration/components/select-box/change-options-test.gjs
+++ b/tests/integration/components/select-box/index/change-options-test.gjs
@@ -99,18 +99,21 @@ module('select-box (changing options)', function (hooks) {
assert.expect(7);
const options = [
+ // a
'a1',
'a2',
'a3',
'a4',
'a5',
'a6',
+ // b
'b1',
'b2',
'b3',
'b4',
'b5',
'b6',
+ // c
'c1',
'c2',
'c3',
@@ -150,12 +153,12 @@ module('select-box (changing options)', function (hooks) {
assert.dom('.select-box__option').exists({ count: 18 });
assert.dom('.select-box__option[aria-current="true"]').hasText('b3');
- // 16px each times 6 preceeding options
+ // 16px each, times 6 preceding options
assert.strictEqual(find('.select-box__options').scrollTop, 96);
await fillIn('.select-box__input', 'b');
- // 16px each times 2 preceeding options
+ // 16px each, times 2 preceding options
assert.strictEqual(
find('.select-box__options').scrollTop,
32,
diff --git a/tests/integration/components/select-box/click-input-test.gjs b/tests/integration/components/select-box/index/click-input-test.gjs
similarity index 69%
rename from tests/integration/components/select-box/click-input-test.gjs
rename to tests/integration/components/select-box/index/click-input-test.gjs
index 47d362279..c8ec7f19c 100644
--- a/tests/integration/components/select-box/click-input-test.gjs
+++ b/tests/integration/components/select-box/index/click-input-test.gjs
@@ -11,15 +11,19 @@ module('select-box (clicking input)', function (hooks) {
await render(
-
-
+
+
+
+
);
await click('.select-box__input');
- assert.dom('.select-box').hasAttribute('data-open', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
});
});
diff --git a/tests/integration/components/select-box/click-option-test.gjs b/tests/integration/components/select-box/index/click-option-test.gjs
similarity index 62%
rename from tests/integration/components/select-box/click-option-test.gjs
rename to tests/integration/components/select-box/index/click-option-test.gjs
index 1c551e60d..094e7a43a 100644
--- a/tests/integration/components/select-box/click-option-test.gjs
+++ b/tests/integration/components/select-box/index/click-option-test.gjs
@@ -1,8 +1,7 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render, click, triggerEvent } from '@ember/test-helpers';
+import { render, click } from '@ember/test-helpers';
import { on } from '@ember/modifier';
-import { tracked } from '@glimmer/tracking';
import SelectBox from '@zestia/ember-select-box/components/select-box';
module('select-box (clicking option)', function (hooks) {
@@ -45,7 +44,7 @@ module('select-box (clicking option)', function (hooks) {
});
test('clicking on a child', async function (assert) {
- assert.expect(4);
+ assert.expect(5);
let event;
@@ -54,38 +53,48 @@ module('select-box (clicking option)', function (hooks) {
await render(
{{! template-lint-disable no-pointer-down-event-binding }}
-
-
-
- Link
-
-
+
+
+ {{sb.value}}
+
+
+
+
+ Link
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
- assert.dom('.select-box').hasAttribute('data-open', 'true', 'precondition');
+ assert
+ .dom('.select-box .dropdown')
+ .hasAttribute('data-open', 'true', 'precondition');
- await triggerEvent('.select-box__option', 'mousedown');
await click('a');
assert.true(
event.defaultPrevented,
- 'focus will not leave the trigger or input'
+ `focus will not leave the trigger or input. but,
+ the link will still be visited. prevent default on the link click, if
+ you wish to have options that can be cmd clicked to open in a new tab`
);
- assert.dom('.select-box').hasAttribute(
+ assert.dom('.select-box .dropdown').hasAttribute(
'data-open',
'false',
- `
- the select box closes because an option was selected. even though the target
- was a child of the option. this allows for greater flexibility building UI's
- that require custom markup
- `
+ `the select box closes because an option was selected. even though the target
+ was a child of the option.`
);
- assert.dom('.select-box__trigger').isFocused('does not lose focus');
+ assert.dom('.select-box .dropdown__trigger').hasText('#');
+
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .isFocused('does not lose focus');
});
test('clicking an option closes single select boxes', async function (assert) {
@@ -93,21 +102,27 @@ module('select-box (clicking option)', function (hooks) {
await render(
-
-
-
-
+
+
+
+
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
- assert.dom('.select-box').hasAttribute('data-open', 'true', 'precondition');
+ assert
+ .dom('.select-box .dropdown')
+ .hasAttribute('data-open', 'true', 'precondition');
await click('.select-box__option');
assert
- .dom('.select-box')
+ .dom('.select-box .dropdown')
.hasAttribute(
'data-open',
'false',
@@ -120,20 +135,26 @@ module('select-box (clicking option)', function (hooks) {
await render(
-
-
-
-
+
+
+
+
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
- assert.dom('.select-box').hasAttribute('data-open', 'true', 'precondition');
+ assert
+ .dom('.select-box .dropdown')
+ .hasAttribute('data-open', 'true', 'precondition');
await click('.select-box__option');
- assert.dom('.select-box').hasAttribute(
+ assert.dom('.select-box .dropdown').hasAttribute(
'data-open',
'true',
`assume that more options are to be selected. do not assume its ok to close
@@ -161,38 +182,6 @@ module('select-box (clicking option)', function (hooks) {
assert.dom('.select-box__option[aria-selected="true"]').doesNotExist();
});
- test('clicking a child that goes away (and so is the select box)', async function (assert) {
- assert.expect(1);
-
- const state = new (class {
- value;
- @tracked hideSelectBox;
- })();
-
- const handleChange = (value) => {
- state.value = value;
- state.hideSelectBox = true;
- };
-
- await render(
- {{#unless state.hideSelectBox}}
-
-
-
- {{#unless state.value}}
-
- {{/unless}}
-
-
- {{/unless}}
- );
-
- await click('.select-box__trigger');
- await click('.select-box__option');
-
- assert.ok(true, 'does not cause infinite revalidation bug');
- });
-
test('right clicking an option does not select it', async function (assert) {
assert.expect(1);
diff --git a/tests/integration/components/select-box/index/click-trigger-test.gjs b/tests/integration/components/select-box/index/click-trigger-test.gjs
new file mode 100644
index 000000000..d46a375a5
--- /dev/null
+++ b/tests/integration/components/select-box/index/click-trigger-test.gjs
@@ -0,0 +1,77 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, click, triggerEvent } from '@ember/test-helpers';
+import { on } from '@ember/modifier';
+import SelectBox from '@zestia/ember-select-box/components/select-box';
+
+module('select-box (clicking trigger)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('clicking trigger toggles select box on mousedown', async function (assert) {
+ assert.expect(7);
+
+ // Mouse down makes the select box feel more responsive
+ // than on click, which would open on mouse up.
+ // It's also what would happen if you clicked on a
+ // native select box.
+
+ let event;
+
+ const handleMouseDownTrigger = (_event) => (event = _event);
+
+ await render(
+
+ {{! template-lint-disable no-pointer-down-event-binding }}
+
+
+
+
+ );
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+
+ await triggerEvent('.select-box .dropdown__trigger', 'mousedown');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
+
+ await triggerEvent('.select-box .dropdown__trigger', 'mousedown');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+
+ assert.true(
+ event.defaultPrevented,
+ `whilst mousing down on the trigger would ordinarily focus it,
+ here we prevent that, so that we can manually control where
+ focus moves to next. e.g. either remaining on the trigger,
+ or advancing the a comobox's input`
+ );
+ });
+
+ test('right clicking trigger does not open select box', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger', { button: 2 });
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+ });
+});
diff --git a/tests/integration/components/select-box/index/closing-test.gjs b/tests/integration/components/select-box/index/closing-test.gjs
new file mode 100644
index 000000000..b96479b14
--- /dev/null
+++ b/tests/integration/components/select-box/index/closing-test.gjs
@@ -0,0 +1,349 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import {
+ render,
+ click,
+ blur,
+ focus,
+ triggerEvent,
+ triggerKeyEvent
+} from '@ember/test-helpers';
+import { tracked } from '@glimmer/tracking';
+import { on } from '@ember/modifier';
+import autoFocus from '@zestia/ember-auto-focus/modifiers/auto-focus';
+import SelectBox from '@zestia/ember-select-box/components/select-box';
+
+module('select-box (closing)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ let handleClose;
+
+ hooks.beforeEach(function (assert) {
+ handleClose = (reason) => assert.step(`close ${reason?.description}`);
+ });
+
+ test('closing with api', async function (assert) {
+ assert.expect(8);
+
+ await render(
+
+
+
+
+
+
+
+
+
+ );
+
+ await click('.dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
+
+ // Intentionally twice
+ await click('.close');
+ await click('.close');
+
+ assert.verifySteps(['close undefined']);
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
+ });
+
+ test('closes when trigger loses focus', async function (assert) {
+ assert.expect(6);
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
+
+ await focus('.select-box .dropdown__trigger');
+ await blur('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
+ });
+
+ test('closes when input loses focus', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box__input').isFocused();
+
+ await blur('.select-box__input');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
+ });
+
+ test('closing does not steal focus', async function (assert) {
+ assert.expect(2);
+
+ const state = new (class {
+ @tracked value;
+ })();
+
+ const handleChange = (value) => (state.value = value);
+
+ await render(
+
+
+
+
+
+
+
+
+
+
+
+ {{#if state.value}}
+
+ {{/if}}
+ );
+
+ await click('.select-box .dropdown__trigger');
+ await click('.select-box__option');
+
+ assert.dom('.outside').hasValue('foo').isFocused();
+ });
+
+ test('closing due to mousing up outside', async function (assert) {
+ assert.expect(3);
+
+ // This is a 'click abort'. When the user mouses down on the
+ // select box, drags their cursor over an option, but then
+ // decides no, and drags their cursor outside the select box
+ // and releases.
+
+ await render(
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ await triggerEvent('.select-box .dropdown__trigger', 'mousedown');
+ await triggerEvent('.outside', 'mouseup');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close CLICK_OUTSIDE']);
+ });
+
+ test('mousing up outside will close a manually opened dropdown', async function (assert) {
+ assert.expect(4);
+
+ await render(
+
+
+
+
+
+
+
+ );
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+
+ await click('.outside');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close CLICK_OUTSIDE']);
+ });
+
+ test('closing due to pressing escape', async function (assert) {
+ assert.expect(3);
+
+ await render(
+
+
+
+
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'Escape'
+ );
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.verifySteps(['close ESCAPE']);
+ });
+
+ test('programmatically closing', async function (assert) {
+ assert.expect(3);
+
+ const handleSelect = () => false;
+
+ await render(
+
+
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.verifySteps(
+ [],
+ `the return value of onSelect controls whether or not the
+ select box will close after making the selection`
+ );
+
+ await click('button');
+
+ assert.verifySteps(['close undefined']);
+ });
+
+ test('clicking to programmatically close', async function (assert) {
+ assert.expect(3);
+
+ await render(
+
+
+
+ {{#if dd.isOpen}}
+
+
+
+
+
+
+ {{/if}}
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+
+ await click('.close');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+
+ assert.ok(true, 'does not cause infinite revalidation bug');
+ });
+
+ test('closing forgets previous active option', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+ await triggerEvent('.select-box__option:nth-child(2)', 'mouseenter');
+
+ assert
+ .dom('.select-box__option:nth-child(2)')
+ .hasAttribute('aria-current', 'true');
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
+ });
+
+ test('closing due to clicking an option', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+
+
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+ await click('.select-box__option');
+
+ assert.verifySteps(['close SELECTED']);
+ });
+});
diff --git a/tests/integration/components/select-box/disabling-test.gjs b/tests/integration/components/select-box/index/disabling-test.gjs
similarity index 72%
rename from tests/integration/components/select-box/disabling-test.gjs
rename to tests/integration/components/select-box/index/disabling-test.gjs
index 87665a8c5..93ee4fb31 100644
--- a/tests/integration/components/select-box/disabling-test.gjs
+++ b/tests/integration/components/select-box/index/disabling-test.gjs
@@ -32,18 +32,24 @@ module('select-box (disabling)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
assert.dom('.select-box').hasAttribute('data-disabled', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-disabled', 'true');
- assert.dom('.select-box__trigger').hasAttribute('tabindex', '-1');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-disabled', 'true');
+ assert.dom('.select-box .dropdown__trigger').hasAttribute('tabindex', '-1');
assert
.dom('.select-box__option[aria-disabled="true"]')
.exists({ count: 3 });
@@ -54,7 +60,6 @@ module('select-box (disabling)', function (hooks) {
await render(
-
@@ -80,22 +85,26 @@ module('select-box (disabling)', function (hooks) {
await render(
-
- {{#if sb.isOpen}}
-
-
-
-
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+
+
+
+
+
+ {{/if}}
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
await click('.select-box__option:nth-child(2)');
assert
- .dom('.select-box')
+ .dom('.select-box .dropdown')
.hasAttribute(
'data-open',
'true',
diff --git a/tests/integration/components/select-box/index/edge-case-test.gjs b/tests/integration/components/select-box/index/edge-case-test.gjs
new file mode 100644
index 000000000..9cf93558c
--- /dev/null
+++ b/tests/integration/components/select-box/index/edge-case-test.gjs
@@ -0,0 +1,83 @@
+import { module, skip, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render, click } from '@ember/test-helpers';
+import SelectBox from '@zestia/ember-select-box/components/select-box';
+import { modifier } from 'ember-modifier';
+import { tracked } from '@glimmer/tracking';
+
+module('select-box (edge cases)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ // Regression test for issue
+ // https://github.com/ember-modifier/ember-modifier/issues/851
+
+ const position = modifier(
+ (dropdown, [container]) => (dropdown.dataset.positioned = 'true')
+ );
+
+ skip('it does not blow up', async function (assert) {
+ assert.expect(0);
+
+ const state = new (class {
+ @tracked value;
+ })();
+
+ const handleChange = (value) => {
+ state.value = value;
+ };
+
+ await render(
+
+
+
+
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger'); // Open
+ await click('.select-box__option'); // Close
+ });
+
+ test('destroying a select box after clicking an option', async function (assert) {
+ assert.expect(1);
+
+ const state = new (class {
+ value;
+ @tracked show = true;
+ })();
+
+ const handleChange = (value) => {
+ state.value = value;
+ state.show = false;
+ };
+
+ await render(
+ {{#if state.show}}
+
+
+
+
+
+ {{#unless state.value}}
+
+ {{/unless}}
+
+
+
+
+ {{/if}}
+ );
+
+ await click('.select-box .dropdown__trigger');
+ await click('.select-box__option');
+
+ assert
+ .dom('.select-box')
+ .doesNotExist('does not cause infinite revalidation bug');
+ });
+});
diff --git a/tests/integration/components/select-box/focus-test.gjs b/tests/integration/components/select-box/index/focus-test.gjs
similarity index 62%
rename from tests/integration/components/select-box/focus-test.gjs
rename to tests/integration/components/select-box/index/focus-test.gjs
index 5c1504a88..161ebd232 100644
--- a/tests/integration/components/select-box/focus-test.gjs
+++ b/tests/integration/components/select-box/index/focus-test.gjs
@@ -1,4 +1,4 @@
-import { module, test, todo } from 'qunit';
+import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import {
render,
@@ -27,7 +27,7 @@ module('select-box (focus)', function (hooks) {
);
- await triggerEvent('.select-box__option', 'mousedown');
+ await click('.select-box__option');
assert
.dom('.select-box__options')
@@ -48,7 +48,7 @@ module('select-box (focus)', function (hooks) {
assert.dom('.select-box__input').isNotFocused('does not steal focus');
- await triggerEvent('.select-box__option', 'mousedown');
+ await click('.select-box__option');
assert
.dom('.select-box__input')
@@ -60,19 +60,25 @@ module('select-box (focus)', function (hooks) {
await render(
-
-
-
-
+
+
+
+
+
+
+
+
);
- assert.dom('.select-box__trigger').isNotFocused('does not steal focus');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .isNotFocused('does not steal focus');
- await triggerEvent('.select-box__option', 'mousedown');
+ await click('.select-box__option');
assert
- .dom('.select-box__trigger')
+ .dom('.select-box .dropdown__trigger')
.isFocused('the trigger is the main interactive element');
});
@@ -81,11 +87,15 @@ module('select-box (focus)', function (hooks) {
await render(
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
@@ -151,19 +161,23 @@ module('select-box (focus)', function (hooks) {
await render(
-
-
-
-
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
+ await focus('.select-box .dropdown__trigger');
await triggerEvent('.select-box__option', 'mouseenter');
assert.dom('.select-box__option').hasAttribute('aria-current', 'true');
- await blur('.select-box__trigger');
+ await blur('.select-box .dropdown__trigger');
assert.dom('.select-box__option').hasAttribute('aria-current', 'true');
});
@@ -195,15 +209,19 @@ module('select-box (focus)', function (hooks) {
await render(
-
-
+
+
+
+
);
- await focus('.select-box__trigger');
+ await focus('.select-box .dropdown__trigger');
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
});
@@ -212,51 +230,62 @@ module('select-box (focus)', function (hooks) {
await render(
-
- {{#if sb.isOpen}}
-
-
-
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+
+
+
+
+ {{/if}}
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
assert.dom('.select-box__input').isFocused('focus advances to input');
await click('.select-box__option');
assert.dom('.select-box__option').doesNotExist('precondition (closed)');
- assert.dom('.select-box__trigger').isFocused('focus restored to trigger');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .isFocused('focus restored to trigger');
});
- test('a focused input going due to focus leaving', async function (assert) {
- assert.expect(3);
+ test('a focused input going away', async function (assert) {
+ assert.expect(4);
await render(
-
- {{#if sb.isOpen}}
-
-
-
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+
+
+
+
+ {{/if}}
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
assert.dom('.select-box__input').isFocused('focus advances to input');
- await click('.outside');
+ await focus('.outside');
assert.dom('.select-box__option').doesNotExist('precondition (closed)');
- assert.dom('.select-box__trigger').isNotFocused(`
+ assert.dom('.outside').isFocused();
+ assert.dom('.select-box .dropdown__trigger').isNotFocused(`
if focus is leaving the select box, this will cause it to close,
closing should never steal focus. focus should be allowed to leave.
`);
@@ -267,18 +296,22 @@ module('select-box (focus)', function (hooks) {
await render(
-
- {{#if sb.isOpen}}
-
-
-
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+
+
+
+
+ {{/if}}
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Enter');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'Enter');
assert.dom('.select-box__input').isFocused('focus advances to input');
@@ -286,32 +319,9 @@ module('select-box (focus)', function (hooks) {
await triggerKeyEvent('.select-box__input', 'keydown', 'Enter');
assert.dom('.select-box__input').doesNotExist('precondition (closed)');
- assert.dom('.select-box__trigger').isFocused('focus restored to trigger');
- });
-
- test('a focused input going away with no trigger', async function (assert) {
- assert.expect(1);
-
- await render(
-
- {{#if sb.isOpen}}
-
-
-
-
- {{/if}}
-
- );
-
- await focus('.select-box__input');
- await click('.select-box__option');
-
- assert.dom(document.body).isFocused(`
- no attempt is made to retain focus on the trigger,
- because there isn't one.
- this is an unlikely scenario since a trigger should be
- used to open the combobox in the first place
- `);
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .isFocused('focus restored to trigger');
});
test('focus leaving combobox (trigger)', async function (assert) {
@@ -319,24 +329,30 @@ module('select-box (focus)', function (hooks) {
await render(
-
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
- assert.dom('.select-box').hasAttribute('data-open', 'true', 'precondition');
+ assert
+ .dom('.select-box .dropdown')
+ .hasAttribute('data-open', 'true', 'precondition');
focus('.outside');
- await waitFor('.select-box__trigger');
+ await waitFor('.select-box .dropdown__trigger');
- assert.dom('.select-box__trigger').isNotFocused('focus is not stolen');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .isNotFocused('focus is not stolen');
await settled();
- assert.dom('.select-box').hasAttribute('data-open', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
assert.dom('.outside').isFocused();
});
@@ -377,12 +393,14 @@ module('select-box (focus)', function (hooks) {
await render(
-
-
+
+
+
+
);
- assert.dom('.select-box__trigger').hasAttribute(
+ assert.dom('.select-box .dropdown__trigger').hasAttribute(
'tabindex',
'-1',
`because the input is the focused element for this combobox,
@@ -392,49 +410,36 @@ module('select-box (focus)', function (hooks) {
});
test('focus moving to somewhere else inside the combo box (none interactive element)', async function (assert) {
- assert.expect(3);
-
- let event;
-
- const handleMouseDown = (_event) => (event = _event);
+ assert.expect(2);
await render(
- {{! template-lint-disable no-pointer-down-event-binding }}
-
-
-
-
-
+
+
+
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
await click('.inside');
assert
- .dom('.select-box')
+ .dom('.select-box .dropdown')
.hasAttribute(
'data-open',
'true',
'remains open if mouse lands on a non interactive element inside the select box'
);
- assert.dom('.select-box__trigger').isFocused(`
+ assert.dom('.select-box .dropdown__trigger').isFocused(`
focus must be maintained on the interactive element, regardless
of where is clicked inside the select box, otherwise the
select box will not be receptive to user actions, which is the
same as how any other native component would behave
`);
-
- // Note
- // we use tabindex="-1" on the listbox to prevent
- // keyboard-focusable-scrollers from stealing focus, but
- // this has a down side of actually allowing the listbox
- // to be focusable on click, so we must prevent that.
- // https://issues.chromium.org/issues/376718258
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1930662
-
- assert.true(event.defaultPrevented);
});
test('focus moving to somewhere else inside the combo box', async function (assert) {
@@ -447,27 +452,28 @@ module('select-box (focus)', function (hooks) {
await render(
{{! template-lint-disable no-pointer-down-event-binding }}
-
-
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
await click('.inside');
- assert.dom('.select-box').hasAttribute(
+ assert.dom('.select-box .dropdown').hasAttribute(
'data-open',
'true',
- `focus is free to move about inside an open select box
- it will not be receptive to user actions, but the interactive
- elements inside it will, and that's ok. unusual, but at least
- allows flexibility for the developer if designing a highly
- custom select box`
+ `focus is free to move about inside an open select box dropdown
+ by using the keyboard so as to not hurt a11y. but when using a
+ mouse we must maintain focus on the primary interactive element,
+ otherwise the select box will not be receptive to user input`
);
- assert.dom('.inside').isFocused();
+ assert.dom('.select-box .dropdown__trigger').isFocused();
- assert.false(event.defaultPrevented);
+ assert.true(event.defaultPrevented);
});
test('focus moving to somewhere else inside the combo box then leaving', async function (assert) {
@@ -475,20 +481,22 @@ module('select-box (focus)', function (hooks) {
await render(
-
-
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
await click('.inside');
- assert.dom('.select-box').hasAttribute('data-open', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
await focus('.outside');
- assert.dom('.select-box').hasAttribute('data-open', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
});
test('focusable options', async function (assert) {
@@ -548,29 +556,33 @@ module('select-box (focus)', function (hooks) {
);
});
- todo('focus-visible', async function (assert) {
+ test('focus-visible', async function (assert) {
assert.expect(1);
await render(
{{! template-lint-disable no-forbidden-elements }}
-
- {{sb.value}}
-
-
- foo
- bar
- baz
-
+
+
+ {{sb.value}}
+
+
+
+ foo
+ bar
+ baz
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
- assert.dom('.select-box__trigger').doesNotHaveStyle(
+ assert.dom('.select-box .dropdown__trigger').hasStyle(
{ outline: 'rgb(255, 0, 0) solid 2px' },
`the select box's focus management does not accidentally cause
focus-visible styles to apply. (here, the trigger was clicked,
@@ -578,4 +590,73 @@ module('select-box (focus)', function (hooks) {
to the element using the keyboard`
);
});
+
+ test('focus out does not forget active option', async function (assert) {
+ assert.expect(2);
+
+ await render(
+
+
+ a
+ b
+ c
+
+
+ );
+
+ await focus('.select-box__options');
+ await triggerEvent('.select-box__option:nth-child(2)', 'mouseenter');
+ await triggerEvent('.select-box__options', 'mouseleave');
+
+ assert
+ .dom('.select-box__option:nth-child(2)')
+ .hasAttribute('aria-current', 'true');
+
+ await blur('.select-box__options');
+
+ assert
+ .dom('.select-box__option:nth-child(2)')
+ .hasAttribute('aria-current', 'true');
+ });
+
+ test('keyboard-focusable-scrollers fix', async function (assert) {
+ assert.expect(3);
+
+ // we use tabindex="-1" on the dropdown content to prevent
+ // keyboard-focusable-scrollers from stealing focus, but
+ // this has a down side of actually allowing the listbox
+ // to be focusable on click, so we must prevent that.
+ //
+ // https://issues.chromium.org/issues/376718258
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1930662
+ //
+ // here, when the primary interactive element is focused,
+ // and the user presses tab, we always want focus to move
+ // to the next interactive element. skipping the dropdown
+ // content (if it was overflowing), and also skipping over
+ // the listbox (if it was overflowing).
+
+ let event;
+
+ const handleMouseDown = (_event) => (event = _event);
+
+ await render(
+ {{! template-lint-disable no-pointer-down-event-binding }}
+
+
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+ await click('.select-box__options');
+
+ assert.dom('.dropdown__content').hasAttribute('tabindex', '-1');
+ assert.dom('.select-box__options').hasAttribute('tabindex', '-1');
+ assert.true(event.defaultPrevented);
+ });
});
diff --git a/tests/integration/components/select-box/index/in-element-test.gjs b/tests/integration/components/select-box/index/in-element-test.gjs
new file mode 100644
index 000000000..55038477d
--- /dev/null
+++ b/tests/integration/components/select-box/index/in-element-test.gjs
@@ -0,0 +1,53 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { click, render, find } from '@ember/test-helpers';
+import SelectBox from '@zestia/ember-select-box/components/select-box';
+
+module('select-box (in-element)', function (hooks) {
+ setupRenderingTest(hooks);
+
+ const destination = () => find('.destination');
+
+ test('a common scenario of rendering a dropdown in an external element works', async function (assert) {
+ assert.expect(6);
+
+ await render(
+
+
+
+ {{sb.value}}
+
+ {{#if dd.isOpen}}
+ {{#in-element (destination) insertBefore=null}}
+
+
+
+
+
+
+ {{/in-element}}
+ {{/if}}
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert.dom('.select-box .select-box__options').doesNotExist();
+ assert.dom('.destination .select-box__options').exists();
+
+ await click('.select-box__option:nth-child(2)');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.select-box .dropdown__trigger').hasText('bar');
+
+ await click('.outside');
+
+ assert.dom('.outside').isFocused();
+ });
+});
diff --git a/tests/integration/components/select-box/jump-to-option-test.gjs b/tests/integration/components/select-box/index/jump-to-option-test.gjs
similarity index 80%
rename from tests/integration/components/select-box/jump-to-option-test.gjs
rename to tests/integration/components/select-box/index/jump-to-option-test.gjs
index f6990bb51..f84faf559 100644
--- a/tests/integration/components/select-box/jump-to-option-test.gjs
+++ b/tests/integration/components/select-box/index/jump-to-option-test.gjs
@@ -47,17 +47,21 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a
- b1
- b2
-
+
+
+
+
+ a
+ b1
+ b2
+
+
+
);
- await click('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'B');
+ await click('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'B');
assert
.dom('.select-box__option:nth-child(2)')
@@ -71,17 +75,21 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a
- a1
- B
-
+
+
+
+
+ a
+ a1
+ B
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'A');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'A');
assert
.dom('.select-box__option:nth-child(1)')
@@ -95,16 +103,20 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a
- b
-
+
+
+
+
+ a
+ b
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'B');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'B');
assert
.dom('.select-box__option:nth-child(2)')
@@ -118,11 +130,15 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a
- b
-
+
+
+
+
+ a
+ b
+
+
+
);
@@ -253,19 +269,23 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a
- b
- c
-
+
+
+
+
+ a
+ b
+ c
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', ' ');
- await triggerKeyEvent('.select-box__trigger', 'keydown', ' ');
- await triggerKeyEvent('.select-box__trigger', 'keydown', ' ');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', ' ');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', ' ');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', ' ');
assert
.dom('.select-box__option:nth-child(1)')
@@ -367,12 +387,16 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a1
- a2
- a3
-
+
+
+
+
+ a1
+ a2
+ a3
+
+
+
);
@@ -380,7 +404,7 @@ module('select-box (jump to option)', function (hooks) {
.dom('.select-box__option:nth-child(2)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'A');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'A');
assert
.dom('.select-box__option:nth-child(3)')
@@ -392,12 +416,16 @@ module('select-box (jump to option)', function (hooks) {
await render(
-
-
- a1
- a2
- a3
-
+
+
+
+
+ a1
+ a2
+ a3
+
+
+
);
@@ -405,19 +433,19 @@ module('select-box (jump to option)', function (hooks) {
.dom('.select-box__option:nth-child(2)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'A');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'A');
assert
.dom('.select-box__option:nth-child(3)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'A');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'A');
assert
.dom('.select-box__option:nth-child(1)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'A');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'A');
assert
.dom('.select-box__option:nth-child(2)')
diff --git a/tests/integration/components/select-box/key-arrow-down-test.gjs b/tests/integration/components/select-box/index/key-arrow-down-test.gjs
similarity index 66%
rename from tests/integration/components/select-box/key-arrow-down-test.gjs
rename to tests/integration/components/select-box/index/key-arrow-down-test.gjs
index c3a8f12ca..d53143a81 100644
--- a/tests/integration/components/select-box/key-arrow-down-test.gjs
+++ b/tests/integration/components/select-box/index/key-arrow-down-test.gjs
@@ -60,53 +60,77 @@ module('select-box (down arrow key)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await focus('.select-box__trigger');
+ await focus('.select-box .dropdown__trigger');
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option[aria-current="true"]')
.doesNotExist('the first down auto opens rather than navigating options');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option:nth-child(1)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option:nth-child(2)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option:nth-child(3)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option:nth-child(3)')
.hasAttribute('aria-current', 'true');
assert
- .dom('.select-box__trigger')
+ .dom('.select-box .dropdown__trigger')
.hasAttribute(
'aria-activedescendant',
find('.select-box__option:nth-child(3)').getAttribute('id')
@@ -181,7 +205,7 @@ module('select-box (down arrow key)', function (hooks) {
await focus('.select-box__options');
await triggerKeyEvent('.select-box__options', 'keydown', 'ArrowDown');
- assert.true(event.defaultPrevented);
+ assert.true(event.defaultPrevented, 'prevents window scrolling');
});
test('down scrolls the active option into view', async function (assert) {
@@ -190,53 +214,42 @@ module('select-box (down arrow key)', function (hooks) {
await render(
{{! template-lint-disable no-forbidden-elements }}
-
- {{#if sb.isOpen}}
-
- a
- b
- c
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+ a
+ b
+ c
+
+
+ {{/if}}
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
const startTop = find('.select-box__option:nth-child(2)').offsetTop;
const expectedTop = find('.select-box__option:nth-child(3)').offsetTop;
assert.strictEqual(startTop, 16);
assert.strictEqual(expectedTop, 32);
- assert.strictEqual(find('.select-box__options').scrollTop, startTop);
-
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
-
- assert.strictEqual(find('.select-box__options').scrollTop, expectedTop);
- });
+ assert.strictEqual(find('.dropdown__content').scrollTop, startTop);
- test('down on options will not open listbox', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
-
-
-
- );
-
- await focus('.select-box__options');
- await triggerKeyEvent('.select-box__options', 'keydown', 'ArrowDown');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
+ assert.strictEqual(find('.dropdown__content').scrollTop, expectedTop);
});
test('down on trigger will open combobox', async function (assert) {
@@ -244,79 +257,94 @@ module('select-box (down arrow key)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
- .dom('.select-box')
- .hasAttribute('data-open', 'true', 'opens the select box');
+ .dom('.select-box .dropdown')
+ .hasAttribute('data-open', 'true', 'opens the select box dropdown');
assert
- .dom('.select-box__trigger')
+ .dom('.select-box .dropdown__trigger')
.hasAttribute('aria-expanded', 'true', 'opens the combobox box');
- assert
- .dom('.select-box__option:nth-child(1)')
- .hasAttribute('aria-current', 'true', 'activates the first option');
+ assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
});
test('down on trigger will open combobox (with selected value)', async function (assert) {
assert.expect(1);
await render(
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option:nth-child(2)')
.hasAttribute('aria-current', 'true', 'the selected option is active');
});
- test('down on input will not open combobox (behaviour undefined)', async function (assert) {
- assert.expect(3);
+ test('down on input will not open combobox', async function (assert) {
+ assert.expect(2);
+
+ // We can assume a Trigger is for opening/closing.
+ // But if there's a dropdown and no trigger, then its
+ // probably a custom select box, and we make no assumptions
+ // The developer might want to open after a search has finished
+ // for example.
await render(
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
await focus('.select-box__input');
await triggerKeyEvent('.select-box__input', 'keydown', 'ArrowDown');
- assert.dom('.select-box').doesNotHaveAttribute('data-open', 'false');
-
- assert
- .dom('.select-box__input')
- .doesNotHaveAttribute('aria-expanded', 'false');
-
- assert
- .dom('.select-box__option:nth-child(1)')
- .hasAttribute('aria-current', 'true', 'still navigates options');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
});
test('up on options of a listbox after making a selection', async function (assert) {
@@ -387,19 +415,29 @@ module('select-box (down arrow key)', function (hooks) {
test('does not scroll the active option into view when closed', async function (assert) {
assert.expect(1);
+ // check this
+
await render(
-
-
- a
- b
- c
-
+
+
+
+
+ a
+ b
+ c
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert.strictEqual(find('.select-box__options').scrollTop, 0);
});
diff --git a/tests/integration/components/select-box/key-arrow-up-test.gjs b/tests/integration/components/select-box/index/key-arrow-up-test.gjs
similarity index 69%
rename from tests/integration/components/select-box/key-arrow-up-test.gjs
rename to tests/integration/components/select-box/index/key-arrow-up-test.gjs
index 84815d073..abb89b089 100644
--- a/tests/integration/components/select-box/key-arrow-up-test.gjs
+++ b/tests/integration/components/select-box/index/key-arrow-up-test.gjs
@@ -65,22 +65,30 @@ module('select-box (up arrow key)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await focus('.select-box__trigger');
+ await focus('.select-box .dropdown__trigger');
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
assert
.dom('.select-box__option[aria-current="true"]')
@@ -88,26 +96,38 @@ module('select-box (up arrow key)', function (hooks) {
await triggerEvent('.select-box__option:nth-child(3)', 'mouseenter');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
assert
.dom('.select-box__option:nth-child(2)')
.hasAttribute('aria-current', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
assert
.dom('.select-box__option:nth-child(1)')
.hasAttribute('aria-current', 'true');
assert
- .dom('.select-box__trigger')
+ .dom('.select-box .dropdown__trigger')
.hasAttribute(
'aria-activedescendant',
find('.select-box__option:nth-child(1)').getAttribute('id')
);
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
assert
.dom('.select-box__option:nth-child(1)')
@@ -178,7 +198,7 @@ module('select-box (up arrow key)', function (hooks) {
await focus('.select-box__options');
await triggerKeyEvent('.select-box__options', 'keydown', 'ArrowUp');
- assert.true(event.defaultPrevented);
+ assert.true(event.defaultPrevented, 'prevents window scrolling');
});
test('up scrolls the active option into view', async function (assert) {
@@ -187,53 +207,42 @@ module('select-box (up arrow key)', function (hooks) {
await render(
{{! template-lint-disable no-forbidden-elements }}
-
- {{#if sb.isOpen}}
-
- a
- b
- c
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+ a
+ b
+ c
+
+
+ {{/if}}
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
const startTop = find('.select-box__option:nth-child(2)').offsetTop;
const expectedTop = find('.select-box__option:nth-child(1)').offsetTop;
assert.strictEqual(startTop, 16);
assert.strictEqual(expectedTop, 0);
- assert.strictEqual(find('.select-box__options').scrollTop, startTop);
-
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
-
- assert.strictEqual(find('.select-box__options').scrollTop, expectedTop);
- });
-
- test('up on options will not open listbox', async function (assert) {
- assert.expect(1);
+ assert.strictEqual(find('.dropdown__content').scrollTop, startTop);
- await render(
-
-
-
-
-
-
-
- );
-
- await focus('.select-box__options');
- await triggerKeyEvent('.select-box__options', 'keydown', 'ArrowUp');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
+ assert.strictEqual(find('.dropdown__content').scrollTop, expectedTop);
});
test('up on trigger will open combobox', async function (assert) {
@@ -241,24 +250,32 @@ module('select-box (up arrow key)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
assert
- .dom('.select-box')
+ .dom('.select-box .dropdown')
.hasAttribute('data-open', 'true', 'opens the select box');
assert
- .dom('.select-box__trigger')
+ .dom('.select-box .dropdown__trigger')
.hasAttribute('aria-expanded', 'true', 'opens the combobox box');
assert
@@ -271,17 +288,25 @@ module('select-box (up arrow key)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowDown');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowDown'
+ );
assert
.dom('.select-box__option:nth-child(2)')
@@ -291,25 +316,30 @@ module('select-box (up arrow key)', function (hooks) {
test('up on input will not open combobox (behaviour undefined)', async function (assert) {
assert.expect(2);
+ // We can assume a Trigger is for opening/closing.
+ // But if there's a dropdown and no trigger, then its
+ // probably a custom select box, and we make no assumptions
+ // The developer might want to open after a search has finished
+ // for example.
+
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
);
await focus('.select-box__input');
await triggerKeyEvent('.select-box__input', 'keydown', 'ArrowUp');
- assert.dom('.select-box').doesNotHaveAttribute('data-open', 'false');
-
- assert
- .dom('.select-box__input')
- .doesNotHaveAttribute('aria-expanded', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
});
test('down on options of a listbox after making a selection', async function (assert) {
@@ -380,19 +410,29 @@ module('select-box (up arrow key)', function (hooks) {
test('does not scroll the active option into view when closed', async function (assert) {
assert.expect(1);
+ // check this
+
await render(
-
-
- a
- b
- c
-
+
+
+
+
+ a
+ b
+ c
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'ArrowUp');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'ArrowUp'
+ );
assert.strictEqual(find('.select-box__options').scrollTop, 0);
});
diff --git a/tests/integration/components/select-box/key-enter-test.gjs b/tests/integration/components/select-box/index/key-enter-test.gjs
similarity index 62%
rename from tests/integration/components/select-box/key-enter-test.gjs
rename to tests/integration/components/select-box/index/key-enter-test.gjs
index 99d4e2f3c..e8a0cbc39 100644
--- a/tests/integration/components/select-box/key-enter-test.gjs
+++ b/tests/integration/components/select-box/index/key-enter-test.gjs
@@ -27,43 +27,51 @@ module('select-box (enter)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Enter');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'Enter');
assert.verifySteps([], 'change event is not fired');
assert.true(event.defaultPrevented);
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
assert
.dom('.select-box__option:nth-child(1)')
.hasAttribute('aria-current', 'false')
.hasAttribute('aria-selected', 'false');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Enter');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'Enter');
assert.verifySteps([], 'change event is not fired');
assert.true(event.defaultPrevented);
assert
- .dom('.select-box')
+ .dom('.select-box .dropdown')
.hasAttribute(
'data-open',
'true',
'does not close (no option was active)'
);
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
});
test('enter on trigger of combobox (multiple)', async function (assert) {
@@ -71,21 +79,25 @@ module('select-box (enter)', function (hooks) {
await render(
-
-
-
-
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Enter');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'Enter');
- assert.dom('.select-box').hasAttribute('data-open', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Enter');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'Enter');
- assert.dom('.select-box').hasAttribute('data-open', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
});
test('enter in input of combobox', async function (assert) {
@@ -93,12 +105,16 @@ module('select-box (enter)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
@@ -114,8 +130,8 @@ module('select-box (enter)', function (hooks) {
assert.verifySteps([]);
assert.true(event.defaultPrevented);
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
- assert.dom('.select-box__input').doesNotHaveAttribute('aria-expanded');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
assert.dom('.select-box__option[aria-selected="true"]').doesNotExist();
@@ -123,8 +139,8 @@ module('select-box (enter)', function (hooks) {
assert.verifySteps([]);
assert.true(event.defaultPrevented);
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
- assert.dom('.select-box__input').doesNotHaveAttribute('aria-expanded');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
assert.dom('.select-box__option[aria-selected="true"]').doesNotExist();
});
@@ -134,13 +150,17 @@ module('select-box (enter)', function (hooks) {
await render(
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
@@ -152,7 +172,7 @@ module('select-box (enter)', function (hooks) {
assert.verifySteps([]);
assert.true(event.defaultPrevented);
- assert.dom('.select-box').hasAttribute('data-open', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
assert
@@ -164,7 +184,7 @@ module('select-box (enter)', function (hooks) {
assert.verifySteps([]);
assert.true(event.defaultPrevented);
- assert.dom('.select-box').hasAttribute('data-open', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
assert
@@ -178,12 +198,16 @@ module('select-box (enter)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
@@ -199,8 +223,37 @@ module('select-box (enter)', function (hooks) {
because instead, it will select the active option`
);
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
- assert.dom('.select-box__input').doesNotHaveAttribute('aria-expanded');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
+ });
+
+ test('enter in a combobox (but without options)', async function (assert) {
+ assert.expect(2);
+
+ // Just because there are no options (no listbox), yet, pressing enter
+ // should still not submit the form
+
+ await render(
+
+
+
+
+ {{#if dd.isOpen}}
+
+
+
+
+
+ {{/if}}
+
+
+ );
+
+ await focus('.select-box__input');
+ await triggerKeyEvent('.select-box__input', 'keydown', 'Enter');
+
+ assert.verifySteps([]);
+ assert.true(event.defaultPrevented);
});
test('enter in listbox', async function (assert) {
@@ -277,14 +330,18 @@ module('select-box (enter)', function (hooks) {
await render(
-
-
-
-
+
+
+
+
+
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
await focus('.select-box__option');
await triggerKeyEvent('.select-box__option', 'keydown', 'Enter');
diff --git a/tests/integration/components/select-box/key-escape-test.gjs b/tests/integration/components/select-box/index/key-escape-test.gjs
similarity index 50%
rename from tests/integration/components/select-box/key-escape-test.gjs
rename to tests/integration/components/select-box/index/key-escape-test.gjs
index 47f97e6c6..4f0a213e5 100644
--- a/tests/integration/components/select-box/key-escape-test.gjs
+++ b/tests/integration/components/select-box/index/key-escape-test.gjs
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render, click, triggerKeyEvent } from '@ember/test-helpers';
+import { render, click, focus, triggerKeyEvent } from '@ember/test-helpers';
import { on } from '@ember/modifier';
import SelectBox from '@zestia/ember-select-box/components/select-box';
@@ -17,31 +17,43 @@ module('select-box (escape)', function (hooks) {
assert.expect(12);
await render(
-
-
-
- foo
-
+
+
+
+
+
+ foo
+
+
+
);
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
assert
.dom('.select-box__option')
.hasAttribute('aria-current', 'false')
.hasAttribute('aria-selected', 'false');
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Escape');
+ await triggerKeyEvent(
+ '.select-box .dropdown__trigger',
+ 'keydown',
+ 'Escape'
+ );
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__trigger').isFocused();
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'false');
+ assert.dom('.select-box .dropdown__trigger').isFocused();
assert
.dom('.select-box__option')
@@ -55,11 +67,15 @@ module('select-box (escape)', function (hooks) {
assert.expect(12);
await render(
-
-
-
- foo
-
+
+
+
+
+
+ foo
+
+
+
);
@@ -72,12 +88,12 @@ module('select-box (escape)', function (hooks) {
.hasAttribute('aria-current', 'false')
.hasAttribute('aria-selected', 'false');
- assert.dom('.select-box').hasAttribute('data-open', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
await triggerKeyEvent('.select-box__input', 'keydown', 'Escape');
- assert.dom('.select-box').hasAttribute('data-open', 'false');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
assert.dom('.select-box__input').isFocused();
@@ -89,7 +105,34 @@ module('select-box (escape)', function (hooks) {
assert.verifySteps(['close']);
});
- test('escape inside something else escapable (e.g. a dropdown) - escape parent', async function (assert) {
+ test('escape on an interactive element', async function (assert) {
+ assert.expect(4);
+
+ // This ensures our listeners are on the dropdown itself,
+ // and not just on the trigger.
+
+ await render(
+
+
+
+
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+
+ await focus('.inside');
+ await triggerKeyEvent('.select-box .dropdown', 'keydown', 'Escape');
+
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'false');
+
+ assert.verifySteps(['close']);
+ });
+
+ test('escape inside something else escapable (closed)', async function (assert) {
assert.expect(1);
let event;
@@ -99,14 +142,15 @@ module('select-box (escape)', function (hooks) {
await render(
{{! template-lint-disable no-invalid-interactive }}
-
-
-
+
+
+
+
);
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Escape');
+ await triggerKeyEvent('.select-box .dropdown', 'keydown', 'Escape');
assert.true(
event instanceof Event,
@@ -114,7 +158,7 @@ module('select-box (escape)', function (hooks) {
);
});
- test('escape inside something else escapable (e.g. a dropdown) - escape child', async function (assert) {
+ test('escape inside something else escapable (open)', async function (assert) {
assert.expect(3);
let event;
@@ -124,20 +168,21 @@ module('select-box (escape)', function (hooks) {
await render(
{{! template-lint-disable no-invalid-interactive }}
-
-
-
+
+
+
+
);
- await click('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Escape');
+ await click('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown', 'keydown', 'Escape');
assert.notOk(
event,
`event propagation is stopped, since escape has caused the select box
- to close. we don't want escape to also close the parent element (dropdown)
+ to close. we don't want escape to also close the parent element
that the select box is contained within`
);
diff --git a/tests/integration/components/select-box/key-options-test.gjs b/tests/integration/components/select-box/index/key-options-test.gjs
similarity index 87%
rename from tests/integration/components/select-box/key-options-test.gjs
rename to tests/integration/components/select-box/index/key-options-test.gjs
index 0247c1b23..4b952e6c4 100644
--- a/tests/integration/components/select-box/key-options-test.gjs
+++ b/tests/integration/components/select-box/index/key-options-test.gjs
@@ -51,12 +51,16 @@ module('select-box (keydown on options)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
diff --git a/tests/integration/components/select-box/key-space-test.gjs b/tests/integration/components/select-box/index/key-space-test.gjs
similarity index 60%
rename from tests/integration/components/select-box/key-space-test.gjs
rename to tests/integration/components/select-box/index/key-space-test.gjs
index 5fae871b0..97230d3a1 100644
--- a/tests/integration/components/select-box/key-space-test.gjs
+++ b/tests/integration/components/select-box/index/key-space-test.gjs
@@ -26,43 +26,47 @@ module('select-box (space)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', ' ');
+ await focus('.select-box .dropdown__trigger');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', ' ');
assert.verifySteps([], 'change event is not fired');
assert.true(event.defaultPrevented);
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
assert
.dom('.select-box__option:nth-child(1)')
.hasAttribute('aria-current', 'false')
.hasAttribute('aria-selected', 'false');
- await triggerKeyEvent('.select-box__trigger', 'keydown', ' ');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', ' ');
assert.verifySteps([], 'change event is not fired');
assert.true(event.defaultPrevented);
- assert
- .dom('.select-box')
- .hasAttribute(
- 'data-open',
- 'true',
- 'does not close (no option was active)'
- );
+ // Does not close (no option was active)
+ // This is counter to a normal dropdown, which toggles.
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
+ assert.dom('.select-box .dropdown').hasAttribute('data-open', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-expanded', 'true');
});
test('space in input of combobox', async function (assert) {
@@ -70,12 +74,16 @@ module('select-box (space)', function (hooks) {
await render(
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
@@ -85,11 +93,13 @@ module('select-box (space)', function (hooks) {
assert.verifySteps([], 'change event is not fired');
assert.false(event.defaultPrevented, 'can type spaces still');
- assert.dom('.select-box').doesNotHaveAttribute(
- 'data-open',
- `does not open, because the space character is typed into the input
- also there is no trigger present to imply its openable`
- );
+ assert
+ .dom('.select-box .dropdown')
+ .hasAttribute(
+ 'data-open',
+ 'false',
+ 'does not open, because the space character is typed into the input'
+ );
assert
.dom('.select-box__option:nth-child(1)')
@@ -134,49 +144,56 @@ module('select-box (space)', function (hooks) {
});
test('space on trigger of combobox whilst typing', async function (assert) {
- assert.expect(10);
+ assert.expect(11);
await render(
{{! template-lint-disable no-whitespace-for-layout }}
-
-
- a
- a1
- a 2
-
+
+
+
+
+ a
+ a1
+ a 2
+
+
+
);
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await focus('.select-box__trigger');
+ await focus('.select-box .dropdown__trigger');
assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'A');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', 'A');
assert.false(event.defaultPrevented);
- await triggerKeyEvent('.select-box__trigger', 'keydown', ' ');
+ assert.verifySteps(['A'], 'change event fires');
+
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', ' ');
assert.true(
event.defaultPrevented,
'will not scroll if space is pressed whilst typing to jump to an option'
);
- await triggerKeyEvent('.select-box__trigger', 'keydown', '2');
+ await triggerKeyEvent('.select-box .dropdown__trigger', 'keydown', '2');
assert.false(event.defaultPrevented);
- assert.verifySteps(['A', 'A 2'], 'change event fires');
+ assert.verifySteps(['A 2'], 'change event fires');
- assert.dom('.select-box').hasAttribute(
+ assert.dom('.select-box .dropdown').hasAttribute(
'data-open',
'false',
`space usually toggles a select box open/closed. but in this case
- we are in the middle of jumping to an option. (this is how native
- select boxes behave).`
+ we are in the middle of jumping to an option and so that character
+ should form part of the 'search' for the option
+ (this is how native select boxes behave).`
);
assert
diff --git a/tests/integration/components/select-box/mouse-option-test.gjs b/tests/integration/components/select-box/index/mouse-option-test.gjs
similarity index 84%
rename from tests/integration/components/select-box/mouse-option-test.gjs
rename to tests/integration/components/select-box/index/mouse-option-test.gjs
index 6243a9e1b..2a4a073f1 100644
--- a/tests/integration/components/select-box/mouse-option-test.gjs
+++ b/tests/integration/components/select-box/index/mouse-option-test.gjs
@@ -99,36 +99,11 @@ module('select-box (mouseenter option)', function (hooks) {
.doesNotHaveAttribute('aria-current');
});
- test("mousing into an option activates it even if the select box doesn't have focus", async function (assert) {
- assert.expect(4);
-
- const handleActivateOption = () => assert.step('activate');
-
- await render(
-
-
-
-
-
-
- );
-
- await triggerEvent('.select-box__option:nth-child(2)', 'mouseenter');
-
- assert
- .dom('.select-box__option:nth-child(1)')
- .hasAttribute('aria-current', 'false');
-
- assert
- .dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-current', 'true');
-
- assert.verifySteps(['activate']);
- });
-
test('mousing over an option does not scroll to it', async function (assert) {
assert.expect(1);
+ // This would create annoying shift as you move the mouse.
+
await render(
{{! template-lint-disable no-forbidden-elements }}
+
+
+
+
+ {{#if dd.isOpen}}
+
+
+ One
+ Two
+ Three
+
+
+ {{/if}}
+
+
+ );
+
+ await click('.select-box .dropdown__trigger');
+
+ const expectedTop = find('.select-box__option:nth-child(3)').offsetTop;
+
+ assert.strictEqual(expectedTop, 32);
+ assert.strictEqual(find('.dropdown__content').scrollTop, expectedTop);
+ });
+
+ todo('dropdown trigger not available', async function (assert) {
+ assert.expect(1);
+
+ await render(
+
+
+
+
+
+ );
+
+ assert.dom('.dropdown__trigger').doesNotExist(`
+ For valid aria, the select box implementation of the dropdown
+ trigger should be used (sb.Trigger), not (dd.Trigger)
+ Sadly, there's not that easy to prevent accidental use of this.
+ `);
+ });
+});
diff --git a/tests/integration/components/select-box/render-test.gjs b/tests/integration/components/select-box/index/render-test.gjs
similarity index 58%
rename from tests/integration/components/select-box/render-test.gjs
rename to tests/integration/components/select-box/index/render-test.gjs
index 32c6b6952..1b56b63b7 100644
--- a/tests/integration/components/select-box/render-test.gjs
+++ b/tests/integration/components/select-box/index/render-test.gjs
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render } from '@ember/test-helpers';
+import { render, find } from '@ember/test-helpers';
import SelectBox from '@zestia/ember-select-box/components/select-box';
module('select-box', function (hooks) {
@@ -30,27 +30,11 @@ module('select-box', function (hooks) {
assert.dom('.select-box').hasClass('foo');
});
- test('expanded (listbox)', async function (assert) {
+ test('whitespace', async function (assert) {
assert.expect(1);
- await render(
-
-
-
- );
-
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
- });
-
- test('expanded (combobox)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
+ await render( );
- assert.dom('.select-box').hasAttribute('data-open', 'false');
+ assert.strictEqual(find('.select-box').innerHTML, '');
});
});
diff --git a/tests/integration/components/select-box/searching-test.gjs b/tests/integration/components/select-box/index/searching-test.gjs
similarity index 89%
rename from tests/integration/components/select-box/searching-test.gjs
rename to tests/integration/components/select-box/index/searching-test.gjs
index 21bef217b..7682655bf 100644
--- a/tests/integration/components/select-box/searching-test.gjs
+++ b/tests/integration/components/select-box/index/searching-test.gjs
@@ -19,19 +19,25 @@ module('select-box (searching)', function (hooks) {
await render(
-
-
+
+
+
+
);
assert.dom('.select-box').hasAttribute('data-busy', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-busy', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-busy', 'false');
assert.dom('.select-box__input').hasAttribute('aria-busy', 'false');
await fillIn('.select-box__input', 'x');
assert.dom('.select-box').hasAttribute('data-busy', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-busy', 'true');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-busy', 'true');
assert.dom('.select-box__input').hasAttribute('aria-busy', 'true');
deferred.resolve();
@@ -39,7 +45,9 @@ module('select-box (searching)', function (hooks) {
await settled();
assert.dom('.select-box').hasAttribute('data-busy', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-busy', 'false');
+ assert
+ .dom('.select-box .dropdown__trigger')
+ .hasAttribute('aria-busy', 'false');
assert.dom('.select-box__input').hasAttribute('aria-busy', 'false');
});
@@ -104,7 +112,6 @@ module('select-box (searching)', function (hooks) {
const handleSearch = async (query, sb) => {
await deferred.promise;
- sb.open();
};
await render(
@@ -331,18 +338,22 @@ module('select-box (searching)', function (hooks) {
const handleSearch = (q) => deferred.promise;
- const handleClickInput = (sb) => sb.search('f').then(sb.open);
+ const handleClickInput = (sb) => sb.search('f').then(sb.dropdown.open);
await render(
-
- {{#if sb.isOpen}}
-
- {{#each sb.options}}
-
- {{/each}}
-
- {{/if}}
+
+
+ {{#if dd.isOpen}}
+
+
+ {{#each sb.options}}
+
+ {{/each}}
+
+
+ {{/if}}
+
);
@@ -364,15 +375,19 @@ module('select-box (searching)', function (hooks) {
-
- {{#each sb.options as |value|}}
-
- {{/each}}
-
+
+
+
+ {{#each sb.options as |value|}}
+
+ {{/each}}
+
+
+
);
- await click('.select-box__trigger');
+ await click('.select-box .dropdown__trigger');
assert.dom('.select-box__input').hasValue('');
assert.dom('.select-box__options').exists({ count: 1 });
diff --git a/tests/integration/components/select-box/single-test.gjs b/tests/integration/components/select-box/index/single-test.gjs
similarity index 80%
rename from tests/integration/components/select-box/single-test.gjs
rename to tests/integration/components/select-box/index/single-test.gjs
index ec780e8ac..ba876d0a0 100644
--- a/tests/integration/components/select-box/single-test.gjs
+++ b/tests/integration/components/select-box/index/single-test.gjs
@@ -97,8 +97,8 @@ module('select-box (single)', function (hooks) {
find('.select-box__options').scrollTop,
0,
`does not trigger scroll into view on the active option
- on initial render, we don't want to accidently cause
- pages to jump around`
+ on initial render, we don't want to accidentally cause
+ the page to jump around`
);
assert
@@ -113,7 +113,7 @@ module('select-box (single)', function (hooks) {
assert.strictEqual(
find('.select-box__options').scrollTop,
0,
- `changing the value programatically does not scroll the active
+ `changing the value programmatically does not scroll the active
option into view. scrolling into view should only happen as
a result of the user interacting with the select box`
);
@@ -141,14 +141,18 @@ module('select-box (single)', function (hooks) {
await render(
-
- {{sb.value}}
-
-
- 1
- 2
- 3
-
+
+
+ {{sb.value}}
+
+
+
+ 1
+ 2
+ 3
+
+
+
);
@@ -156,7 +160,7 @@ module('select-box (single)', function (hooks) {
.dom('.select-box__option:nth-child(1)')
.hasAttribute('aria-selected', 'true');
- assert.dom('.select-box__trigger').hasText('1');
+ assert.dom('.select-box .dropdown__trigger').hasText('1');
await click('.select-box__option:nth-child(2)');
@@ -164,21 +168,21 @@ module('select-box (single)', function (hooks) {
.dom('.select-box__option:nth-child(2)')
.hasAttribute('aria-selected', 'true');
- assert.dom('.select-box__trigger').hasText('2');
+ assert.dom('.select-box .dropdown__trigger').hasText('2');
state.value = 3;
await settled();
- assert.dom('.select-box__trigger').hasText('3');
+ assert.dom('.select-box .dropdown__trigger').hasText('3');
assert
.dom('.select-box__option:nth-child(3)')
.hasAttribute('aria-selected', 'true');
});
- test('onActivate doesnt fire initially', async function (assert) {
- assert.expect(1);
+ test("onActivate doesn't fire initially", async function (assert) {
+ assert.expect(2);
const handleActivate = () => assert.step('activate');
@@ -190,6 +194,8 @@ module('select-box (single)', function (hooks) {
);
+ assert.dom('.select-box__option').hasAttribute('aria-current', 'true');
+
assert.verifySteps([]);
});
@@ -243,15 +249,17 @@ module('select-box (single)', function (hooks) {
await render(
-
- {{state.value}},
- {{sb.value}}
-
-
- One
- Two
- Three
-
+
+
+ {{state.value}},
+ {{sb.value}}
+
+
+ One
+ Two
+ Three
+
+
);
@@ -259,7 +267,7 @@ module('select-box (single)', function (hooks) {
assert.strictEqual(state.value, 2);
- assert.dom('.select-box__trigger').hasText('2, 3');
+ assert.dom('.select-box .dropdown__trigger').hasText('2, 3');
assert
.dom('.select-box__option[aria-selected="true"]')
@@ -289,15 +297,17 @@ module('select-box (single)', function (hooks) {
@onBuildSelection={{buildSelection}}
as |sb|
>
-
- {{state.value}},
- {{sb.value}}
-
-
- One
- Two
- Three
-
+
+
+ {{state.value}},
+ {{sb.value}}
+
+
+ One
+ Two
+ Three
+
+
);
@@ -305,7 +315,7 @@ module('select-box (single)', function (hooks) {
assert.strictEqual(state.value, 3);
- assert.dom('.select-box__trigger').hasText('3, 2');
+ assert.dom('.select-box .dropdown__trigger').hasText('3, 2');
assert.dom('.select-box__option[aria-selected="true"]').hasText(
'Two',
diff --git a/tests/integration/components/select-box/value-test.gjs b/tests/integration/components/select-box/index/value-test.gjs
similarity index 96%
rename from tests/integration/components/select-box/value-test.gjs
rename to tests/integration/components/select-box/index/value-test.gjs
index df1780288..3315472eb 100644
--- a/tests/integration/components/select-box/value-test.gjs
+++ b/tests/integration/components/select-box/index/value-test.gjs
@@ -44,15 +44,19 @@ module('select-box (value)', function (hooks) {
await render(
-
- {{sb.value}}
-
-
+
+
+ {{sb.value}}
+
+
+
+
+
);
assert
- .dom('.select-box__trigger')
+ .dom('.select-box .dropdown__trigger')
.hasText(
'foo',
"not a valid option, but that's still the value of the select box"
diff --git a/tests/integration/components/input/render-test.gjs b/tests/integration/components/select-box/input/render-test.gjs
similarity index 76%
rename from tests/integration/components/input/render-test.gjs
rename to tests/integration/components/select-box/input/render-test.gjs
index c409a6429..c2ecc5c3f 100644
--- a/tests/integration/components/input/render-test.gjs
+++ b/tests/integration/components/select-box/input/render-test.gjs
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render, setupOnerror, resetOnerror } from '@ember/test-helpers';
+import { render } from '@ember/test-helpers';
import SelectBox from '@zestia/ember-select-box/components/select-box';
module('select-box/input', function (hooks) {
@@ -47,8 +47,10 @@ module('select-box/input', function (hooks) {
await render(
-
-
+
+
+
+
);
@@ -75,24 +77,4 @@ module('select-box/input', function (hooks) {
.doesNotHaveAttribute('type', 'text')
.doesNotHaveAttribute('type', 'search');
});
-
- test('multiple inputs', async function (assert) {
- assert.expect(1);
-
- setupOnerror((error) => {
- assert.strictEqual(
- error.message,
- 'Assertion Failed: can only have 1 input'
- );
- });
-
- await render(
-
-
-
-
- );
-
- resetOnerror();
- });
});
diff --git a/tests/integration/components/select-box/mouse-up-test.gjs b/tests/integration/components/select-box/mouse-up-test.gjs
deleted file mode 100644
index c70effee3..000000000
--- a/tests/integration/components/select-box/mouse-up-test.gjs
+++ /dev/null
@@ -1,59 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import { render, focus, triggerEvent } from '@ember/test-helpers';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-
-module('select-box (mouseenter option)', function (hooks) {
- setupRenderingTest(hooks);
-
- test('mousing down on trigger but mousing up on an option', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- one
- two
-
-
- );
-
- await triggerEvent('.select-box__trigger', 'mousedown');
- await triggerEvent('.select-box__option:nth-child(2)', 'mouseup');
-
- assert
- .dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-selected', 'true');
- });
-
- test('mousing down on trigger but mousing up outside the select box', async function (assert) {
- assert.expect(4);
-
- const handleSelect = () => assert.step('select');
-
- await render(
-
-
-
- one
- two
-
-
-
-
- );
-
- await focus('.select-box__trigger');
- await triggerEvent('.select-box__trigger', 'mousedown');
- await triggerEvent('.select-box__option:nth-child(2)', 'mouseenter');
-
- await triggerEvent('.select-box', 'mouseleave');
- await triggerEvent('.outside', 'mouseup');
-
- assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- assert.dom('.select-box__trigger').isFocused();
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.verifySteps([]);
- });
-});
diff --git a/tests/integration/components/select-box/opening-test.gjs b/tests/integration/components/select-box/opening-test.gjs
deleted file mode 100644
index 8d25e2688..000000000
--- a/tests/integration/components/select-box/opening-test.gjs
+++ /dev/null
@@ -1,429 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import {
- render,
- rerender,
- click,
- focus,
- find,
- triggerEvent,
- triggerKeyEvent
-} from '@ember/test-helpers';
-import { on } from '@ember/modifier';
-import { array } from '@ember/helper';
-import { tracked } from '@glimmer/tracking';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-
-module('select-box (opening)', function (hooks) {
- setupRenderingTest(hooks);
-
- test('opening with api', async function (assert) {
- assert.expect(7);
-
- let api;
-
- const handleReady = (sb) => (api = sb);
- const handleOpen = () => assert.step('open');
-
- await render(
-
-
-
-
-
- );
-
- assert.false(api.isOpen);
-
- await click('button');
- await click('button');
-
- assert.true(api.isOpen);
-
- assert.verifySteps(['open']);
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
- });
-
- test('can set initial open state', async function (assert) {
- assert.expect(6);
-
- await render(
-
-
-
-
-
-
-
- );
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'true');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
- assert.dom('.select-box__trigger').isNotFocused();
- assert.dom('.select-box__input').isNotFocused();
- assert.dom('.select-box__option').hasAttribute('aria-current', 'true');
- });
-
- test('initial open state of combobox without a trigger (custom behaviour!)', async function (assert) {
- assert.expect(5);
-
- // The addon can't know if the combobox is one where the options are already visible,
- // or one where they will be shown by interacting with the input element. If the developer
- // had used a Trigger element, we make the assumption that it will control the expanded state.
-
- await render(
-
-
-
-
- );
-
- assert
- .dom('.select-box')
- .doesNotHaveAttribute(
- 'data-open',
- 'technically invalid, ought to have expanded attr'
- );
-
- // ...therefore, because the developer has opted out of having a Trigger button,
- // they must manually set its initial expanded state and configure a way to
- // control that expanded state, in order to be valid aria. Example:
-
- await render(
-
-
-
-
- );
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
-
- await focus('.select-box__input');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
- });
-
- test('cannot manually open listbox', async function (assert) {
- assert.expect(2);
-
- const handleOpen = () => assert.step('open');
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box').doesNotHaveAttribute('data-open');
- assert.verifySteps([]);
- });
-
- test('cannot open combobox manually with argument', async function (assert) {
- assert.expect(3);
-
- const state = new (class {
- @tracked isOpen;
- })();
-
- await render(
-
-
-
-
- );
-
- state.isOpen = true;
-
- await rerender();
-
- assert.dom('.select-box').hasAttribute('data-open', 'false');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'false');
- });
-
- test('activates first option (undefined === undefined)', async function (assert) {
- assert.expect(3);
-
- await render(
-
-
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
-
- assert
- .dom('.select-box__option:nth-child(1)')
- .hasAttribute('aria-current', 'true');
-
- assert
- .dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-current', 'false');
-
- assert
- .dom('.select-box__option:nth-child(3)')
- .hasAttribute('aria-current', 'false');
- });
-
- test('activates option for value (single)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
-
- assert
- .dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-current', 'true');
- });
-
- test('activating option (multiple)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
-
- assert
- .dom('.select-box__option[aria-current="true"]')
- .doesNotExist(
- 'does not attempt to activate any of the options for the given value'
- );
- });
-
- test('activates option for value after they are rendered', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
- {{#if sb.isOpen}}
-
-
-
-
-
- {{/if}}
-
- );
-
- await click('.select-box__trigger');
-
- assert
- .dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-current', 'true');
- });
-
- test('opening via the trigger does not lose focus', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- await click('.select-box__trigger');
-
- assert.dom('.select-box__trigger').isFocused();
- });
-
- test('opening via the trigger advances focus to the input (mouse)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
- );
-
- await click('.select-box__trigger');
-
- assert.dom('.select-box__input').isFocused();
- });
-
- test('opening via the trigger advances focus to the input (keyboard)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
- );
-
- await focus('.select-box__trigger');
- await triggerKeyEvent('.select-box__trigger', 'keydown', 'Enter');
-
- assert.dom('.select-box__input').isFocused();
- });
-
- test('opening via the api advances focus to the input', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
- );
-
- await triggerEvent('.select-box__trigger', 'mouseenter');
-
- assert.dom('.select-box__input').isFocused();
- });
-
- test('opening listbox', async function (assert) {
- assert.expect(1);
-
- const handleOpen = () => assert.step('open');
-
- await render(
-
-
-
-
- );
-
- await click('button');
-
- assert.verifySteps([], 'listboxes cannot be opened');
- });
-
- test('opening combo box with arg (trigger)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box__trigger').isNotFocused('does not steal focus');
- });
-
- test('opening combo box with arg (input)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box__input').isNotFocused('does not steal focus');
- });
-
- test('opening combobox with input only', async function (assert) {
- assert.expect(4);
-
- await render(
-
-
-
-
-
-
-
- );
-
- assert.dom('.select-box[aria-current="true"]').doesNotExist();
-
- await click('.select-box__input');
-
- assert
- .dom('.select-box__option:nth-child(1)')
- .hasAttribute('aria-current', 'true');
-
- assert.dom('.select-box').hasAttribute('data-open', 'true');
- assert.dom('.select-box__input').hasAttribute('aria-expanded', 'true');
- });
-
- test('opening with a selected value scrolls it into view', async function (assert) {
- assert.expect(2);
-
- await render(
- {{! template-lint-disable no-forbidden-elements }}
-
-
-
-
- {{#if sb.isOpen}}
-
- One
- Two
- Three
-
- {{/if}}
-
- );
-
- await click('.select-box__trigger');
-
- const expectedTop = find('.select-box__option:nth-child(3)').offsetTop;
-
- assert.strictEqual(expectedTop, 32);
- assert.strictEqual(find('.select-box__options').scrollTop, expectedTop);
- });
-
- test('opening forgets previous active option', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
-
-
-
-
-
- );
-
- await click('.select-box__trigger');
-
- await triggerEvent('.select-box__option:nth-child(2)', 'mouseenter');
-
- assert
- .dom('.select-box__option:nth-child(2)')
- .hasAttribute('aria-current', 'true');
-
- await click('.select-box__trigger');
-
- assert.dom('.select-box__option[aria-current="true"]').doesNotExist();
- });
-});
diff --git a/tests/integration/components/option/api-test.gjs b/tests/integration/components/select-box/option/api-test.gjs
similarity index 100%
rename from tests/integration/components/option/api-test.gjs
rename to tests/integration/components/select-box/option/api-test.gjs
diff --git a/tests/integration/components/option/render-test.gjs b/tests/integration/components/select-box/option/render-test.gjs
similarity index 100%
rename from tests/integration/components/option/render-test.gjs
rename to tests/integration/components/select-box/option/render-test.gjs
diff --git a/tests/integration/components/options/render-test.gjs b/tests/integration/components/select-box/options/render-test.gjs
similarity index 75%
rename from tests/integration/components/options/render-test.gjs
rename to tests/integration/components/select-box/options/render-test.gjs
index 366f7a2a7..d698d8c9e 100644
--- a/tests/integration/components/options/render-test.gjs
+++ b/tests/integration/components/select-box/options/render-test.gjs
@@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
-import { find, render, setupOnerror, resetOnerror } from '@ember/test-helpers';
+import { find, render } from '@ember/test-helpers';
import SelectBox from '@zestia/ember-select-box/components/select-box';
module('select-box/options', function (hooks) {
@@ -75,9 +75,7 @@ module('select-box/options', function (hooks) {
'-1',
`the main interactive element is the input (combobox)
focus should not move to the listbox, which is
- aria controlled by the input.
- this prevents keyboard-focusable-scrollers from stealing focus,
- since options often overflows`
+ aria controlled virtually by the input.`
);
});
@@ -86,8 +84,12 @@ module('select-box/options', function (hooks) {
await render(
-
-
+
+
+
+
+
+
);
@@ -95,8 +97,8 @@ module('select-box/options', function (hooks) {
'tabindex',
'-1',
`the main interactive element is the trigger (combobox)
- focus should not move to the listbox, which is
- aria controlled by the trigger`
+ focus should not move to the listbox (options element), which is
+ aria controlled virtually by the trigger`
);
});
@@ -155,39 +157,4 @@ module('select-box/options', function (hooks) {
.dom('.select-box__options')
.hasAttribute('aria-multiselectable', 'true');
});
-
- test('multiple listboxes', async function (assert) {
- assert.expect(1);
-
- setupOnerror((error) => {
- assert.strictEqual(
- error.message,
- 'Assertion Failed: can only have 1 listbox'
- );
- });
-
- await render(
-
-
-
-
- );
-
- resetOnerror();
- });
-
- test('no listbox', async function (assert) {
- assert.expect(1);
-
- setupOnerror((error) => {
- assert.strictEqual(
- error.message,
- 'Assertion Failed: must have an interactive element'
- );
- });
-
- await render( );
-
- resetOnerror();
- });
});
diff --git a/tests/integration/components/trigger/render-test.gjs b/tests/integration/components/trigger/render-test.gjs
deleted file mode 100644
index 3d656903c..000000000
--- a/tests/integration/components/trigger/render-test.gjs
+++ /dev/null
@@ -1,171 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'dummy/tests/helpers';
-import {
- render,
- find,
- setupOnerror,
- resetOnerror,
- triggerEvent
-} from '@ember/test-helpers';
-import SelectBox from '@zestia/ember-select-box/components/select-box';
-
-module('select-box/trigger', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it renders', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box__trigger').hasTagName('div');
- });
-
- test('splattributes', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box__trigger').hasClass('foo');
- });
-
- test('aria defaults', async function (assert) {
- assert.expect(6);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box__trigger').doesNotHaveAttribute('aria-busy');
- assert.dom('.select-box__trigger').doesNotHaveAttribute('aria-controls');
- assert.dom('.select-box__trigger').doesNotHaveAttribute('aria-disabled');
- assert.dom('.select-box__trigger').hasAttribute('aria-expanded', 'false');
- assert
- .dom('.select-box__trigger')
- .doesNotHaveAttribute('aria-activedescendant');
-
- assert
- .dom('.select-box__trigger')
- .doesNotHaveAttribute(
- 'aria-haspopup',
- 'listbox',
- 'spec says this is implicit due to role of combobox'
- );
- });
-
- test('aria controls', async function (assert) {
- assert.expect(2);
-
- await render(
-
-
-
-
- );
-
- assert.ok(
- find('.select-box__trigger')
- .getAttribute('aria-controls')
- .match(/[\w\d]+/)
- );
-
- assert
- .dom('.select-box__options')
- .hasAttribute(
- 'id',
- find('.select-box__trigger').getAttribute('aria-controls')
- );
- });
-
- test('role', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- assert.dom('.select-box__trigger').hasAttribute('role', 'combobox');
- });
-
- test('role (with input)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
- );
-
- assert
- .dom('.select-box__trigger')
- .hasAttribute(
- 'role',
- 'button',
- 'the trigger is not the primary interactive element'
- );
- });
-
- test('whitespace', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
- );
-
- assert.strictEqual(find('.select-box__trigger').innerHTML, '');
- });
-
- test('multiple triggers', async function (assert) {
- assert.expect(1);
-
- setupOnerror((error) => {
- assert.strictEqual(
- error.message,
- 'Assertion Failed: can only have 1 trigger'
- );
- });
-
- await render(
-
-
-
-
- );
-
- resetOnerror();
- });
-
- test('active descendant (with input)', async function (assert) {
- assert.expect(1);
-
- await render(
-
-
-
-
-
-
-
- );
-
- await triggerEvent('.select-box__option', 'mouseenter');
-
- assert
- .dom('.select-box__trigger')
- .doesNotHaveAttribute('aria-activedescendant');
- });
-});