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