github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/integration/components/variable-form-test.js (about) 1 import { module, test } from 'qunit'; 2 import { setupRenderingTest } from 'ember-qunit'; 3 import { hbs } from 'ember-cli-htmlbars'; 4 import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; 5 import { click, typeIn, find, findAll, render } from '@ember/test-helpers'; 6 import { setupMirage } from 'ember-cli-mirage/test-support'; 7 import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror'; 8 import { codeFillable, code } from 'nomad-ui/tests/pages/helpers/codemirror'; 9 import percySnapshot from '@percy/ember'; 10 import { 11 selectChoose, 12 clickTrigger, 13 } from 'ember-power-select/test-support/helpers'; 14 import faker from 'nomad-ui/mirage/faker'; 15 16 module('Integration | Component | variable-form', function (hooks) { 17 setupRenderingTest(hooks); 18 setupMirage(hooks); 19 setupCodeMirror(hooks); 20 21 test('passes an accessibility audit', async function (assert) { 22 assert.expect(1); 23 this.set( 24 'mockedModel', 25 server.create('variable', { 26 keyValues: [{ key: '', value: '' }], 27 }) 28 ); 29 await render(hbs`<VariableForm @model={{this.mockedModel}} />`); 30 await componentA11yAudit(this.element, assert); 31 }); 32 33 test('shows a single row by default and modifies on "Add More" and "Delete"', async function (assert) { 34 this.set( 35 'mockedModel', 36 server.create('variable', { 37 keyValues: [{ key: '', value: '' }], 38 }) 39 ); 40 assert.expect(7); 41 42 await render(hbs`<VariableForm @model={{this.mockedModel}} />`); 43 assert.equal( 44 findAll('div.key-value').length, 45 1, 46 'A single KV row exists by default' 47 ); 48 49 assert 50 .dom('[data-test-add-kv]') 51 .isDisabled( 52 'The "Add More" button is disabled until key and value are filled' 53 ); 54 55 await typeIn('.key-value label:nth-child(1) input', 'foo'); 56 57 assert 58 .dom('[data-test-add-kv]') 59 .isDisabled( 60 'The "Add More" button is still disabled with only key filled' 61 ); 62 63 await typeIn('.key-value label:nth-child(2) input', 'bar'); 64 65 assert 66 .dom('[data-test-add-kv]') 67 .isNotDisabled( 68 'The "Add More" button is no longer disabled after key and value are filled' 69 ); 70 71 await click('[data-test-add-kv]'); 72 73 assert.equal( 74 findAll('div.key-value').length, 75 2, 76 'A second KV row exists after adding a new one' 77 ); 78 79 await typeIn('.key-value:last-of-type label:nth-child(1) input', 'foo'); 80 await typeIn('.key-value:last-of-type label:nth-child(2) input', 'bar'); 81 82 await click('[data-test-add-kv]'); 83 84 assert.equal( 85 findAll('div.key-value').length, 86 3, 87 'A third KV row exists after adding a new one' 88 ); 89 90 await click('.key-value button.delete-row'); 91 92 assert.equal( 93 findAll('div.key-value').length, 94 2, 95 'Back down to two rows after hitting delete' 96 ); 97 }); 98 99 module('editing and creating new key/value pairs', function () { 100 test('it should allow each key/value row to toggle password visibility', async function (assert) { 101 faker.seed(1); 102 this.set( 103 'mockedModel', 104 server.create('variable', { 105 keyValues: [{ key: 'foo', value: 'bar' }], 106 }) 107 ); 108 109 assert.expect(6); 110 111 await render(hbs`<VariableForm @model={{this.mockedModel}} />`); 112 await click('[data-test-add-kv]'); // add a second variable 113 114 findAll('input.value-input').forEach((input, iter) => { 115 assert.equal( 116 input.getAttribute('type'), 117 'password', 118 `Value ${iter + 1} is hidden by default` 119 ); 120 }); 121 122 await click('.key-value button.show-hide-values'); 123 const [firstRow, secondRow] = findAll('input.value-input'); 124 125 assert.equal( 126 firstRow.getAttribute('type'), 127 'text', 128 'Only the row that is clicked on toggles visibility' 129 ); 130 assert.equal( 131 secondRow.getAttribute('type'), 132 'password', 133 'Rows that are not clicked remain obscured' 134 ); 135 136 await click('.key-value button.show-hide-values'); 137 assert.equal( 138 firstRow.getAttribute('type'), 139 'password', 140 'Only the row that is clicked on toggles visibility' 141 ); 142 assert.equal( 143 secondRow.getAttribute('type'), 144 'password', 145 'Rows that are not clicked remain obscured' 146 ); 147 await percySnapshot(assert); 148 }); 149 }); 150 151 test('Existing variable shows properties by default', async function (assert) { 152 assert.expect(13); 153 const keyValues = [ 154 { key: 'my-completely-normal-key', value: 'never' }, 155 { key: 'another key, but with spaces', value: 'gonna' }, 156 { key: 'once/more/with/slashes', value: 'give' }, 157 { key: 'and_some_underscores', value: 'you' }, 158 { key: 'and\\now/for-something_completely@different', value: 'up' }, 159 ]; 160 161 this.set( 162 'mockedModel', 163 server.create('variable', { 164 path: 'my/path/to', 165 keyValues, 166 }) 167 ); 168 await render(hbs`<VariableForm @model={{this.mockedModel}} />`); 169 assert.equal( 170 findAll('div.key-value').length, 171 5, 172 'Shows 5 existing key values' 173 ); 174 assert.equal( 175 findAll('button.delete-row').length, 176 5, 177 'Shows "delete" for all five rows' 178 ); 179 assert.equal( 180 findAll('[data-test-add-kv]').length, 181 1, 182 'Shows "add more" only on the last row' 183 ); 184 185 findAll('div.key-value').forEach((row, idx) => { 186 assert.equal( 187 row.querySelector(`label:nth-child(1) input`).value, 188 keyValues[idx].key, 189 `Key ${idx + 1} is correct` 190 ); 191 192 assert.equal( 193 row.querySelector(`label:nth-child(2) input`).value, 194 keyValues[idx].value, 195 keyValues[idx].value 196 ); 197 }); 198 }); 199 200 test('Prevent editing path input on existing variables', async function (assert) { 201 assert.expect(3); 202 203 const variable = await this.server.create('variable', { 204 name: 'foo', 205 namespace: 'bar', 206 path: '/baz/bat', 207 keyValues: [{ key: '', value: '' }], 208 }); 209 variable.isNew = false; 210 this.set('variable', variable); 211 await render(hbs`<VariableForm @model={{this.variable}} />`); 212 assert.dom('input.path-input').hasValue('/baz/bat', 'Path is set'); 213 assert 214 .dom('input.path-input') 215 .isDisabled('Existing variable is in disabled state'); 216 217 variable.isNew = true; 218 variable.path = ''; 219 this.set('variable', variable); 220 await render(hbs`<VariableForm @model={{this.variable}} />`); 221 assert 222 .dom('input.path-input') 223 .isNotDisabled('New variable is not in disabled state'); 224 }); 225 226 module('Validation', function () { 227 test('warns when you try to create a path that already exists', async function (assert) { 228 this.server.createList('namespace', 3); 229 230 this.set( 231 'mockedModel', 232 server.create('variable', { 233 path: '', 234 keyValues: [{ key: '', value: '' }], 235 }) 236 ); 237 238 server.create('variable', { 239 path: 'baz/bat', 240 }); 241 server.create('variable', { 242 path: 'baz/bat/qux', 243 namespace: server.db.namespaces[2].id, 244 }); 245 246 this.set('existingVariables', server.db.variables.toArray()); 247 248 await render( 249 hbs`<VariableForm @model={{this.mockedModel}} @existingVariables={{this.existingVariables}} />` 250 ); 251 252 await typeIn('.path-input', 'foo/bar'); 253 assert.dom('.duplicate-path-error').doesNotExist(); 254 assert.dom('.path-input').doesNotHaveClass('error'); 255 256 document.querySelector('.path-input').value = ''; // clear current input 257 await typeIn('.path-input', 'baz/bat'); 258 assert.dom('.duplicate-path-error').exists(); 259 assert.dom('.path-input').hasClass('error'); 260 261 await clickTrigger('[data-test-variable-namespace-filter]'); 262 await selectChoose( 263 '[data-test-variable-namespace-filter]', 264 server.db.namespaces[2].id 265 ); 266 assert.dom('.duplicate-path-error').doesNotExist(); 267 assert.dom('.path-input').doesNotHaveClass('error'); 268 269 document.querySelector('.path-input').value = ''; // clear current input 270 await typeIn('.path-input', 'baz/bat/qux'); 271 assert.dom('.duplicate-path-error').exists(); 272 assert.dom('.path-input').hasClass('error'); 273 }); 274 275 test('warns you when you set a key with . in it', async function (assert) { 276 this.set( 277 'mockedModel', 278 server.create('variable', { 279 keyValues: [{ key: '', value: '' }], 280 }) 281 ); 282 283 await render(hbs`<VariableForm @model={{this.mockedModel}} />`); 284 285 await typeIn('.key-value label:nth-child(1) input', 'superSecret'); 286 assert.dom('.key-value-error').doesNotExist(); 287 288 find('.key-value label:nth-child(1) input').value = ''; 289 290 await typeIn('.key-value label:nth-child(1) input', 'super.secret'); 291 assert.dom('.key-value-error').exists(); 292 }); 293 294 test('warns you when you create a duplicate key', async function (assert) { 295 this.set( 296 'mockedModel', 297 server.create('variable', { 298 keyValues: [{ key: 'myKey', value: 'myVal' }], 299 }) 300 ); 301 302 await render(hbs`<VariableForm @model={{this.mockedModel}} />`); 303 304 await click('[data-test-add-kv]'); 305 306 const secondKey = document.querySelectorAll('[data-test-var-key]')[1]; 307 await typeIn(secondKey, 'myWonderfulKey'); 308 assert.dom('.key-value-error').doesNotExist(); 309 310 secondKey.value = ''; 311 312 await typeIn(secondKey, 'myKey'); 313 assert.dom('.key-value-error').exists(); 314 }); 315 }); 316 317 module('Views', function () { 318 test('Allows you to swap between JSON and Key/Value Views', async function (assert) { 319 this.set( 320 'mockedModel', 321 server.create('variable', { 322 path: '', 323 keyValues: [{ key: '', value: '' }], 324 }) 325 ); 326 327 this.set( 328 'existingVariables', 329 server.createList('variable', 1, { 330 path: 'baz/bat', 331 }) 332 ); 333 334 this.set('view', 'table'); 335 336 await render( 337 hbs`<VariableForm @model={{this.mockedModel}} @existingVariables={{this.existingVariables}} @view={{this.view}} />` 338 ); 339 assert.dom('.key-value').exists(); 340 assert.dom('.CodeMirror').doesNotExist(); 341 342 this.set('view', 'json'); 343 assert.dom('.key-value').doesNotExist(); 344 assert.dom('.CodeMirror').exists(); 345 }); 346 347 test('Persists Key/Values table data to JSON', async function (assert) { 348 faker.seed(1); 349 assert.expect(2); 350 const keyValues = [ 351 { key: 'foo', value: '123' }, 352 { key: 'bar', value: '456' }, 353 ]; 354 this.set( 355 'mockedModel', 356 server.create('variable', { 357 path: '', 358 keyValues, 359 }) 360 ); 361 362 this.set('view', 'json'); 363 364 await render( 365 hbs`<VariableForm @model={{this.mockedModel}} @view={{this.view}} />` 366 ); 367 368 await percySnapshot(assert); 369 370 const keyValuesAsJSON = keyValues.reduce((acc, { key, value }) => { 371 acc[key] = value; 372 return acc; 373 }, {}); 374 375 assert.equal( 376 code('.editor-wrapper').get(), 377 JSON.stringify(keyValuesAsJSON, null, 2), 378 'JSON editor contains the key values, stringified, by default' 379 ); 380 381 this.set('view', 'table'); 382 383 await click('[data-test-add-kv]'); 384 385 await typeIn('.key-value:last-of-type label:nth-child(1) input', 'howdy'); 386 await typeIn( 387 '.key-value:last-of-type label:nth-child(2) input', 388 'partner' 389 ); 390 391 this.set('view', 'json'); 392 393 assert.ok( 394 code('[data-test-json-editor]').get().includes('"howdy": "partner"'), 395 'JSON editor contains the new key value' 396 ); 397 }); 398 399 test('Persists JSON data to Key/Values table', async function (assert) { 400 const keyValues = [{ key: '', value: '' }]; 401 this.set( 402 'mockedModel', 403 server.create('variable', { 404 path: '', 405 keyValues, 406 }) 407 ); 408 409 this.set('view', 'json'); 410 411 await render( 412 hbs`<VariableForm @model={{this.mockedModel}} @view={{this.view}} />` 413 ); 414 415 codeFillable('[data-test-json-editor]').get()( 416 JSON.stringify({ golden: 'gate' }, null, 2) 417 ); 418 this.set('view', 'table'); 419 assert.equal( 420 find(`.key-value:last-of-type label:nth-child(1) input`).value, 421 'golden', 422 'Key persists from JSON to Table' 423 ); 424 425 assert.equal( 426 find(`.key-value:last-of-type label:nth-child(2) input`).value, 427 'gate', 428 'Value persists from JSON to Table' 429 ); 430 }); 431 }); 432 });