github.com/hernad/nomad@v1.6.112/ui/tests/integration/components/multi-select-dropdown-test.js (about)

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