github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/integration/components/multi-select-dropdown-test.js (about)

     1  import {
     2    findAll,
     3    find,
     4    click,
     5    focus,
     6    render,
     7    triggerKeyEvent,
     8  } from '@ember/test-helpers';
     9  import { module, test } from 'qunit';
    10  import { setupRenderingTest } from 'ember-qunit';
    11  import sinon from 'sinon';
    12  import hbs from 'htmlbars-inline-precompile';
    13  import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
    14  
    15  const TAB = 9;
    16  const ESC = 27;
    17  const SPACE = 32;
    18  const ARROW_UP = 38;
    19  const ARROW_DOWN = 40;
    20  
    21  module('Integration | Component | multi-select dropdown', function (hooks) {
    22    setupRenderingTest(hooks);
    23  
    24    const commonProperties = () => ({
    25      label: 'This is the dropdown label',
    26      selection: [],
    27      options: [
    28        { key: 'consul', label: 'Consul' },
    29        { key: 'nomad', label: 'Nomad' },
    30        { key: 'terraform', label: 'Terraform' },
    31        { key: 'packer', label: 'Packer' },
    32        { key: 'vagrant', label: 'Vagrant' },
    33        { key: 'vault', label: 'Vault' },
    34      ],
    35      onSelect: sinon.spy(),
    36    });
    37  
    38    const commonTemplate = hbs`
    39      <MultiSelectDropdown
    40        @label={{this.label}}
    41        @options={{this.options}}
    42        @selection={{this.selection}}
    43        @onSelect={{this.onSelect}} />
    44    `;
    45  
    46    test('component is initially closed', async function (assert) {
    47      assert.expect(4);
    48  
    49      const props = commonProperties();
    50      this.setProperties(props);
    51      await render(commonTemplate);
    52  
    53      assert.ok(find('.dropdown-trigger'), 'Trigger is shown');
    54      assert.equal(
    55        find('[data-test-dropdown-trigger]').textContent.trim(),
    56        props.label,
    57        'Trigger is appropriately labeled'
    58      );
    59      assert.notOk(
    60        find('[data-test-dropdown-options]'),
    61        'Options are not rendered'
    62      );
    63  
    64      await componentA11yAudit(this.element, assert);
    65    });
    66  
    67    test('component opens the options dropdown when clicked', async function (assert) {
    68      assert.expect(3);
    69  
    70      const props = commonProperties();
    71      this.setProperties(props);
    72      await render(commonTemplate);
    73  
    74      await click('[data-test-dropdown-trigger]');
    75  
    76      await assert.ok(
    77        find('[data-test-dropdown-options]'),
    78        'Options are shown now'
    79      );
    80      await componentA11yAudit(this.element, assert);
    81  
    82      await click('[data-test-dropdown-trigger]');
    83  
    84      assert.notOk(
    85        find('[data-test-dropdown-options]'),
    86        'Options are hidden after clicking again'
    87      );
    88    });
    89  
    90    test('all options are shown in the options dropdown, each with a checkbox input', async function (assert) {
    91      assert.expect(13);
    92  
    93      const props = commonProperties();
    94      this.setProperties(props);
    95      await render(commonTemplate);
    96  
    97      await click('[data-test-dropdown-trigger]');
    98  
    99      assert.equal(
   100        findAll('[data-test-dropdown-option]').length,
   101        props.options.length,
   102        'All options are shown'
   103      );
   104      findAll('[data-test-dropdown-option]').forEach((optionEl, index) => {
   105        const label = props.options[index].label;
   106        assert.equal(
   107          optionEl.textContent.trim(),
   108          label,
   109          `Correct label for ${label}`
   110        );
   111        assert.ok(
   112          optionEl.querySelector('input[type="checkbox"]'),
   113          'Option contains a checkbox'
   114        );
   115      });
   116    });
   117  
   118    test('onSelect gets called when an option is clicked', async function (assert) {
   119      const props = commonProperties();
   120      this.setProperties(props);
   121      await render(commonTemplate);
   122  
   123      await click('[data-test-dropdown-trigger]');
   124      await click('[data-test-dropdown-option] label');
   125  
   126      assert.ok(props.onSelect.called, 'onSelect was called');
   127      const newSelection = props.onSelect.getCall(0).args[0];
   128      assert.deepEqual(
   129        newSelection,
   130        [props.options[0].key],
   131        'onSelect was called with the first option key'
   132      );
   133    });
   134  
   135    test('the component trigger shows the selection count when there is a selection', async function (assert) {
   136      assert.expect(4);
   137  
   138      const props = commonProperties();
   139      props.selection = [props.options[0].key, props.options[1].key];
   140      this.setProperties(props);
   141      await render(commonTemplate);
   142  
   143      assert.ok(
   144        find('[data-test-dropdown-trigger] [data-test-dropdown-count]'),
   145        'The count is shown'
   146      );
   147      assert.equal(
   148        find('[data-test-dropdown-trigger] [data-test-dropdown-count]')
   149          .textContent,
   150        props.selection.length,
   151        'The count is accurate'
   152      );
   153  
   154      await componentA11yAudit(this.element, assert);
   155  
   156      await this.set('selection', []);
   157  
   158      assert.notOk(
   159        find('[data-test-dropdown-trigger] [data-test-dropdown-count]'),
   160        'The count is no longer shown when the selection is empty'
   161      );
   162    });
   163  
   164    test('pressing DOWN when the trigger has focus opens the options list', async function (assert) {
   165      const props = commonProperties();
   166      this.setProperties(props);
   167      await render(commonTemplate);
   168  
   169      await focus('[data-test-dropdown-trigger]');
   170      assert.notOk(
   171        find('[data-test-dropdown-options]'),
   172        'Options are not shown on focus'
   173      );
   174      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', ARROW_DOWN);
   175      assert.ok(find('[data-test-dropdown-options]'), 'Options are now shown');
   176      assert.equal(
   177        document.activeElement,
   178        find('[data-test-dropdown-trigger]'),
   179        'The dropdown trigger maintains focus'
   180      );
   181    });
   182  
   183    test('pressing DOWN when the trigger has focus and the options list is open focuses the first option', async function (assert) {
   184      const props = commonProperties();
   185      this.setProperties(props);
   186      await render(commonTemplate);
   187  
   188      await focus('[data-test-dropdown-trigger]');
   189      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', ARROW_DOWN);
   190      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', ARROW_DOWN);
   191      assert.equal(
   192        document.activeElement,
   193        find('[data-test-dropdown-option]'),
   194        'The first option now has focus'
   195      );
   196    });
   197  
   198    test('pressing TAB when the trigger has focus and the options list is open focuses the first option', async function (assert) {
   199      const props = commonProperties();
   200      this.setProperties(props);
   201      await render(commonTemplate);
   202  
   203      await focus('[data-test-dropdown-trigger]');
   204      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', ARROW_DOWN);
   205      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', TAB);
   206      assert.equal(
   207        document.activeElement,
   208        find('[data-test-dropdown-option]'),
   209        'The first option now has focus'
   210      );
   211    });
   212  
   213    test('pressing UP when the first list option is focused does nothing', async function (assert) {
   214      const props = commonProperties();
   215      this.setProperties(props);
   216      await render(commonTemplate);
   217  
   218      await click('[data-test-dropdown-trigger]');
   219  
   220      await focus('[data-test-dropdown-option]');
   221      await triggerKeyEvent('[data-test-dropdown-option]', 'keyup', ARROW_UP);
   222      assert.equal(
   223        document.activeElement,
   224        find('[data-test-dropdown-option]'),
   225        'The first option maintains focus'
   226      );
   227    });
   228  
   229    test('pressing DOWN when the a list option is focused moves focus to the next list option', async function (assert) {
   230      const props = commonProperties();
   231      this.setProperties(props);
   232      await render(commonTemplate);
   233  
   234      await click('[data-test-dropdown-trigger]');
   235  
   236      await focus('[data-test-dropdown-option]');
   237      await triggerKeyEvent('[data-test-dropdown-option]', 'keyup', ARROW_DOWN);
   238      assert.equal(
   239        document.activeElement,
   240        findAll('[data-test-dropdown-option]')[1],
   241        'The second option has focus'
   242      );
   243    });
   244  
   245    test('pressing DOWN when the last list option has focus does nothing', async function (assert) {
   246      assert.expect(6);
   247  
   248      const props = commonProperties();
   249      this.setProperties(props);
   250      await render(commonTemplate);
   251  
   252      await click('[data-test-dropdown-trigger]');
   253  
   254      await focus('[data-test-dropdown-option]');
   255      const optionEls = findAll('[data-test-dropdown-option]');
   256      const lastIndex = optionEls.length - 1;
   257  
   258      for (const [index, option] of optionEls.entries()) {
   259        await triggerKeyEvent(option, 'keyup', ARROW_DOWN);
   260  
   261        if (index < lastIndex) {
   262          /* eslint-disable-next-line qunit/no-conditional-assertions */
   263          assert.equal(
   264            document.activeElement,
   265            optionEls[index + 1],
   266            `Option ${index + 1} has focus`
   267          );
   268        }
   269      }
   270  
   271      await triggerKeyEvent(optionEls[lastIndex], 'keyup', ARROW_DOWN);
   272      assert.equal(
   273        document.activeElement,
   274        optionEls[lastIndex],
   275        `Option ${lastIndex} still has focus`
   276      );
   277    });
   278  
   279    test('onSelect gets called when pressing SPACE when a list option is focused', async function (assert) {
   280      const props = commonProperties();
   281      this.setProperties(props);
   282      await render(commonTemplate);
   283  
   284      await click('[data-test-dropdown-trigger]');
   285  
   286      await focus('[data-test-dropdown-option]');
   287      await triggerKeyEvent('[data-test-dropdown-option]', 'keyup', SPACE);
   288  
   289      assert.ok(props.onSelect.called, 'onSelect was called');
   290      const newSelection = props.onSelect.getCall(0).args[0];
   291      assert.deepEqual(
   292        newSelection,
   293        [props.options[0].key],
   294        'onSelect was called with the first option key'
   295      );
   296    });
   297  
   298    test('list options have a zero tabindex and are therefore sequentially navigable', async function (assert) {
   299      assert.expect(6);
   300  
   301      const props = commonProperties();
   302      this.setProperties(props);
   303      await render(commonTemplate);
   304  
   305      await click('[data-test-dropdown-trigger]');
   306  
   307      findAll('[data-test-dropdown-option]').forEach((option) => {
   308        assert.equal(
   309          parseInt(option.getAttribute('tabindex'), 10),
   310          0,
   311          'tabindex is zero'
   312        );
   313      });
   314    });
   315  
   316    test('the checkboxes inside list options have a negative tabindex and are therefore not sequentially navigable', async function (assert) {
   317      assert.expect(6);
   318  
   319      const props = commonProperties();
   320      this.setProperties(props);
   321      await render(commonTemplate);
   322  
   323      await click('[data-test-dropdown-trigger]');
   324  
   325      findAll('[data-test-dropdown-option]').forEach((option) => {
   326        assert.ok(
   327          parseInt(
   328            option
   329              .querySelector('input[type="checkbox"]')
   330              .getAttribute('tabindex'),
   331            10
   332          ) < 0,
   333          'tabindex is a negative value'
   334        );
   335      });
   336    });
   337  
   338    test('pressing ESC when the options list is open closes the list and returns focus to the dropdown trigger', async function (assert) {
   339      const props = commonProperties();
   340      this.setProperties(props);
   341      await render(commonTemplate);
   342  
   343      await focus('[data-test-dropdown-trigger]');
   344      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', ARROW_DOWN);
   345      await triggerKeyEvent('[data-test-dropdown-trigger]', 'keyup', ARROW_DOWN);
   346      await triggerKeyEvent('[data-test-dropdown-option]', 'keyup', ESC);
   347  
   348      assert.notOk(
   349        find('[data-test-dropdown-options]'),
   350        'The options list is hidden once more'
   351      );
   352      assert.equal(
   353        document.activeElement,
   354        find('[data-test-dropdown-trigger]'),
   355        'The trigger has focus'
   356      );
   357    });
   358  
   359    test('when there are no list options, an empty message is shown', async function (assert) {
   360      assert.expect(4);
   361  
   362      const props = commonProperties();
   363      props.options = [];
   364      this.setProperties(props);
   365      await render(commonTemplate);
   366  
   367      await click('[data-test-dropdown-trigger]');
   368      assert.ok(
   369        find('[data-test-dropdown-options]'),
   370        'The dropdown is still shown'
   371      );
   372      assert.ok(find('[data-test-dropdown-empty]'), 'The empty state is shown');
   373      assert.notOk(find('[data-test-dropdown-option]'), 'No options are shown');
   374      await componentA11yAudit(this.element, assert);
   375    });
   376  });