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