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 });