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