github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/ui/tests/integration/multi-select-dropdown-test.js (about)

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