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