github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/integration/components/multi-select-dropdown-test.js (about) 1 import { findAll, find, click, focus, render, triggerKeyEvent } from '@ember/test-helpers'; 2 import { module, test } from 'qunit'; 3 import { setupRenderingTest } from 'ember-qunit'; 4 import sinon from 'sinon'; 5 import hbs from 'htmlbars-inline-precompile'; 6 import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; 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 <MultiSelectDropdown 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 await componentA11yAudit(this.element, assert); 53 }); 54 55 test('component opens the options dropdown when clicked', async function(assert) { 56 const props = commonProperties(); 57 this.setProperties(props); 58 await render(commonTemplate); 59 60 await click('[data-test-dropdown-trigger]'); 61 62 await assert.ok(find('[data-test-dropdown-options]'), 'Options are shown now'); 63 await componentA11yAudit(this.element, assert); 64 65 await click('[data-test-dropdown-trigger]'); 66 67 assert.notOk(find('[data-test-dropdown-options]'), 'Options are hidden after clicking again'); 68 }); 69 70 test('all options are shown in the options dropdown, each with a checkbox input', async function(assert) { 71 const props = commonProperties(); 72 this.setProperties(props); 73 await render(commonTemplate); 74 75 await click('[data-test-dropdown-trigger]'); 76 77 assert.equal( 78 findAll('[data-test-dropdown-option]').length, 79 props.options.length, 80 'All options are shown' 81 ); 82 findAll('[data-test-dropdown-option]').forEach((optionEl, index) => { 83 const label = props.options[index].label; 84 assert.equal(optionEl.textContent.trim(), label, `Correct label for ${label}`); 85 assert.ok(optionEl.querySelector('input[type="checkbox"]'), 'Option contains a checkbox'); 86 }); 87 }); 88 89 test('onSelect gets called when an option is clicked', async function(assert) { 90 const props = commonProperties(); 91 this.setProperties(props); 92 await render(commonTemplate); 93 94 await click('[data-test-dropdown-trigger]'); 95 await click('[data-test-dropdown-option] label'); 96 97 assert.ok(props.onSelect.called, 'onSelect was called'); 98 const newSelection = props.onSelect.getCall(0).args[0]; 99 assert.deepEqual( 100 newSelection, 101 [props.options[0].key], 102 'onSelect was called with the first option key' 103 ); 104 }); 105 106 test('the component trigger shows the selection count when there is a selection', async function(assert) { 107 const props = commonProperties(); 108 props.selection = [props.options[0].key, props.options[1].key]; 109 this.setProperties(props); 110 await render(commonTemplate); 111 112 assert.ok( 113 find('[data-test-dropdown-trigger] [data-test-dropdown-count]'), 114 'The count is shown' 115 ); 116 assert.equal( 117 find('[data-test-dropdown-trigger] [data-test-dropdown-count]').textContent, 118 props.selection.length, 119 'The count is accurate' 120 ); 121 122 await componentA11yAudit(this.element, assert); 123 124 await this.set('selection', []); 125 126 assert.notOk( 127 find('[data-test-dropdown-trigger] [data-test-dropdown-count]'), 128 'The count is no longer shown when the selection is empty' 129 ); 130 }); 131 132 test('pressing DOWN when the trigger has focus opens the options list', async function(assert) { 133 const props = commonProperties(); 134 this.setProperties(props); 135 await render(commonTemplate); 136 137 await focus('[data-test-dropdown-trigger]'); 138 assert.notOk(find('[data-test-dropdown-options]'), 'Options are not shown on focus'); 139 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); 140 assert.ok(find('[data-test-dropdown-options]'), 'Options are now shown'); 141 assert.equal( 142 document.activeElement, 143 find('[data-test-dropdown-trigger]'), 144 'The dropdown trigger maintains focus' 145 ); 146 }); 147 148 test('pressing DOWN when the trigger has focus and the options list is open focuses the first option', async function(assert) { 149 const props = commonProperties(); 150 this.setProperties(props); 151 await render(commonTemplate); 152 153 await focus('[data-test-dropdown-trigger]'); 154 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); 155 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); 156 assert.equal( 157 document.activeElement, 158 find('[data-test-dropdown-option]'), 159 'The first option now has focus' 160 ); 161 }); 162 163 test('pressing TAB when the trigger has focus and the options list is open focuses the first option', async function(assert) { 164 const props = commonProperties(); 165 this.setProperties(props); 166 await render(commonTemplate); 167 168 await focus('[data-test-dropdown-trigger]'); 169 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); 170 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', TAB); 171 assert.equal( 172 document.activeElement, 173 find('[data-test-dropdown-option]'), 174 'The first option now has focus' 175 ); 176 }); 177 178 test('pressing UP when the first list option is focused does nothing', async function(assert) { 179 const props = commonProperties(); 180 this.setProperties(props); 181 await render(commonTemplate); 182 183 await click('[data-test-dropdown-trigger]'); 184 185 await focus('[data-test-dropdown-option]'); 186 await triggerKeyEvent('[data-test-dropdown-option]', 'keydown', ARROW_UP); 187 assert.equal( 188 document.activeElement, 189 find('[data-test-dropdown-option]'), 190 'The first option maintains focus' 191 ); 192 }); 193 194 test('pressing DOWN when the a list option is focused moves focus to the next list option', async function(assert) { 195 const props = commonProperties(); 196 this.setProperties(props); 197 await render(commonTemplate); 198 199 await click('[data-test-dropdown-trigger]'); 200 201 await focus('[data-test-dropdown-option]'); 202 await triggerKeyEvent('[data-test-dropdown-option]', 'keydown', ARROW_DOWN); 203 assert.equal( 204 document.activeElement, 205 findAll('[data-test-dropdown-option]')[1], 206 'The second option has focus' 207 ); 208 }); 209 210 test('pressing DOWN when the last list option has focus does nothing', async function(assert) { 211 const props = commonProperties(); 212 this.setProperties(props); 213 await render(commonTemplate); 214 215 await click('[data-test-dropdown-trigger]'); 216 217 await focus('[data-test-dropdown-option]'); 218 const optionEls = findAll('[data-test-dropdown-option]'); 219 const lastIndex = optionEls.length - 1; 220 221 for (const [index, option] of optionEls.entries()) { 222 await triggerKeyEvent(option, 'keydown', ARROW_DOWN); 223 224 if (index < lastIndex) { 225 assert.equal(document.activeElement, optionEls[index + 1], `Option ${index + 1} has focus`); 226 } 227 } 228 229 await triggerKeyEvent(optionEls[lastIndex], 'keydown', ARROW_DOWN); 230 assert.equal( 231 document.activeElement, 232 optionEls[lastIndex], 233 `Option ${lastIndex} still has focus` 234 ); 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 await click('[data-test-dropdown-trigger]'); 243 244 await focus('[data-test-dropdown-option]'); 245 await triggerKeyEvent('[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 zero tabindex and are therefore sequentially navigable', async function(assert) { 257 const props = commonProperties(); 258 this.setProperties(props); 259 await render(commonTemplate); 260 261 await click('[data-test-dropdown-trigger]'); 262 263 findAll('[data-test-dropdown-option]').forEach(option => { 264 assert.equal(parseInt(option.getAttribute('tabindex'), 10), 0, 'tabindex is zero'); 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 await 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 await focus('[data-test-dropdown-trigger]'); 289 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); 290 await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); 291 await triggerKeyEvent('[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 await 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 await componentA11yAudit(this.element, assert); 312 }); 313 });