From 1238a187dfeb90e4d859201a72fc020d3f66f8c7 Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:16:09 -0700 Subject: [PATCH] UI: address test flakiness, especially kmip role edit form (#28262) * absolute hail mary * what about this? * that was not right * nope * refactor problematic test * remove all of the runloop stuff, just chasing flaky tests * chasing authPage * move away from page objects for runCmd * replace existing runCmd function * add line * test if removing chrome version helps this time? * rerun tests * rerun tests * Revert "test if removing chrome version helps this time?" This reverts commit 0b189c4f6978d6c55c283e3fe9fddd03d28c4377. * remove await * add trace log * change test:oss command * remove log tracing --- ui/app/models/kmip/role.js | 2 +- ui/package.json | 2 +- ui/tests/acceptance/enterprise-kmip-test.js | 91 ++++++- .../acceptance/pki/pki-configuration-test.js | 19 +- .../acceptance/settings/auth/enable-test.js | 6 +- ui/tests/helpers/commands.js | 42 +++- .../components/edit-form-kmip-role-test.js | 238 ------------------ .../integration/components/edit-form-test.js | 80 ++---- 8 files changed, 162 insertions(+), 318 deletions(-) delete mode 100644 ui/tests/integration/components/edit-form-kmip-role-test.js diff --git a/ui/app/models/kmip/role.js b/ui/app/models/kmip/role.js index a3a294c315e4..8083acd7b331 100644 --- a/ui/app/models/kmip/role.js +++ b/ui/app/models/kmip/role.js @@ -10,7 +10,7 @@ import apiPath from 'vault/utils/api-path'; import lazyCapabilities from 'vault/macros/lazy-capabilities'; import { removeManyFromArray } from 'vault/helpers/remove-from-array'; -export const COMPUTEDS = { +const COMPUTEDS = { operationFields: computed('newFields', function () { return this.newFields.filter((key) => key.startsWith('operation')); }), diff --git a/ui/package.json b/ui/package.json index c34b5f8a629e..b2c2dce2289d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -31,7 +31,7 @@ "start:chroot": "ember server --proxy=http://127.0.0.1:8300 --port=4300", "test": "concurrently --kill-others-on-fail -P -c \"auto\" -n lint:js,lint:hbs,vault \"yarn:lint:js:quiet\" \"yarn:lint:hbs:quiet\" \"node scripts/start-vault.js {@}\" --", "test:enos": "concurrently --kill-others-on-fail -P -c \"auto\" -n lint:js,lint:hbs,enos \"yarn:lint:js:quiet\" \"yarn:lint:hbs:quiet\" \"node scripts/enos-test-ember.js {@}\" --", - "test:oss": "yarn run test -f='!enterprise' --split=8 --preserve-test-name --parallel", + "test:oss": "yarn run test -f='!enterprise'", "test:ent": "node scripts/start-vault.js -f='enterprise'", "test:quick": "node scripts/start-vault.js --split=8 --preserve-test-name --parallel", "test:quick-oss": "node scripts/start-vault.js -f='!enterprise' --split=8 --preserve-test-name --parallel", diff --git a/ui/tests/acceptance/enterprise-kmip-test.js b/ui/tests/acceptance/enterprise-kmip-test.js index 3eca7565719a..a2f743227640 100644 --- a/ui/tests/acceptance/enterprise-kmip-test.js +++ b/ui/tests/acceptance/enterprise-kmip-test.js @@ -3,7 +3,16 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { currentURL, currentRouteName, settled, fillIn, waitUntil, find, click } from '@ember/test-helpers'; +import { + currentURL, + currentRouteName, + settled, + fillIn, + visit, + waitUntil, + find, + click, +} from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -14,7 +23,7 @@ import credentialsPage from 'vault/tests/pages/secrets/backend/kmip/credentials' import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import { allEngines } from 'vault/helpers/mountable-secret-engines'; -import { runCmd } from 'vault/tests/helpers/commands'; +import { mountEngineCmd, runCmd } from 'vault/tests/helpers/commands'; import { v4 as uuidv4 } from 'uuid'; // port has a lower limit of 1024 @@ -76,6 +85,7 @@ const generateCreds = async (backend) => { } return { backend, scope, role, serial }; }; + module('Acceptance | Enterprise | KMIP secrets', function (hooks) { setupApplicationTest(hooks); @@ -347,4 +357,81 @@ module('Acceptance | Enterprise | KMIP secrets', function (hooks) { assert.strictEqual(credentialsPage.listItemLinks.length, 0, 'renders no credentials'); assert.ok(credentialsPage.isEmpty, 'renders empty'); }); + + // the kmip/role model relies on openApi so testing the form via an acceptance test + module('kmip role edit form', function (hooks) { + hooks.beforeEach(async function () { + this.store = this.owner.lookup('service:store'); + this.scope = 'my-scope'; + this.name = 'my-role'; + await authPage.login(); + await runCmd(mountEngineCmd('kmip', this.backend), false); + await runCmd([`write ${this.backend}/scope/${this.scope} -force`]); + await rolesPage.visit({ backend: this.backend, scope: this.scope }); + this.setModel = async () => { + await click('[data-test-edit-form-submit]'); + await visit(`/vault/secrets/${this.backend}/kmip/scopes/${this.scope}/roles/${this.name}`); + this.model = this.store.peekRecord('kmip/role', this.name); + }; + }); + + // "operationNone" is the attr name for the 'Allow this role to perform KMIP operations' toggle + // operationNone = false => the toggle is ON and KMIP operations are allowed + // operationNone = true => the toggle is OFF and KMIP operations are not allowed + test('it submits when operationNone is toggled on', async function (assert) { + assert.expect(3); + + await click('[data-test-role-create]'); + await fillIn(GENERAL.inputByAttr('name'), this.name); + assert.dom(GENERAL.inputByAttr('operationAll')).isChecked('operationAll is checked by default'); + await this.setModel(); + assert.true(this.model.operationAll, 'operationAll is true'); + assert.strictEqual(this.model.operationNone, undefined, 'operationNone is unset'); + }); + + test('it submits when operationNone is toggled off', async function (assert) { + assert.expect(4); + + await click('[data-test-role-create]'); + await fillIn(GENERAL.inputByAttr('name'), this.name); + await click(GENERAL.inputByAttr('operationNone')); + assert + .dom(GENERAL.inputByAttr('operationNone')) + .isNotChecked('Allow this role to perform KMIP operations is toggled off'); + assert + .dom(GENERAL.inputByAttr('operationAll')) + .doesNotExist('clicking the toggle hides KMIP operation checkboxes'); + await this.setModel(); + assert.strictEqual(this.model.operationAll, undefined, 'operationAll is unset'); + assert.true(this.model.operationNone, 'operationNone is true'); + }); + + test('it submits when operationAll is unchecked', async function (assert) { + assert.expect(2); + + await click('[data-test-role-create]'); + await fillIn(GENERAL.inputByAttr('name'), this.name); + await click(GENERAL.inputByAttr('operationAll')); + await this.setModel(); + assert.strictEqual(this.model.operationAll, undefined, 'operationAll is unset'); + assert.true(this.model.operationNone, 'operationNone is true'); + }); + + test('it submits individually selected operations', async function (assert) { + assert.expect(6); + + await click('[data-test-role-create]'); + await fillIn(GENERAL.inputByAttr('name'), this.name); + await click(GENERAL.inputByAttr('operationAll')); + await click(GENERAL.inputByAttr('operationGet')); + await click(GENERAL.inputByAttr('operationGetAttributes')); + assert.dom(GENERAL.inputByAttr('operationAll')).isNotChecked(); + assert.dom(GENERAL.inputByAttr('operationCreate')).isNotChecked(); // unchecking operationAll deselects the other checkboxes + await this.setModel(); + assert.strictEqual(this.model.operationAll, undefined, 'operationAll is unset'); + assert.strictEqual(this.model.operationNone, undefined, 'operationNone is unset'); + assert.true(this.model.operationGet, 'operationGet is true'); + assert.true(this.model.operationGetAttributes, 'operationGetAttributes is true'); + }); + }); }); diff --git a/ui/tests/acceptance/pki/pki-configuration-test.js b/ui/tests/acceptance/pki/pki-configuration-test.js index 2928f3dff761..e3df2392fdf1 100644 --- a/ui/tests/acceptance/pki/pki-configuration-test.js +++ b/ui/tests/acceptance/pki/pki-configuration-test.js @@ -10,8 +10,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support'; import { click, currentURL, fillIn, visit, settled, find, waitFor, waitUntil } from '@ember/test-helpers'; import { v4 as uuidv4 } from 'uuid'; -import authPage from 'vault/tests/pages/auth'; -import logout from 'vault/tests/pages/logout'; +import { login, logout } from 'vault/tests/helpers/auth/auth-helpers'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; import { runCmd } from 'vault/tests/helpers/commands'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; @@ -29,17 +28,17 @@ module('Acceptance | pki configuration test', function (hooks) { hooks.beforeEach(async function () { this.pemBundle = issuerPemBundle; - await authPage.login(); + await login(); // Setup PKI engine const mountPath = `pki-workflow-${uuidv4()}`; await enablePage.enable('pki', mountPath); this.mountPath = mountPath; - await logout.visit(); + await logout(); }); hooks.afterEach(async function () { - await logout.visit(); - await authPage.login(); + await logout(); + await login(); // Cleanup engine await runCmd([`delete sys/mounts/${this.mountPath}`]); }); @@ -48,7 +47,7 @@ module('Acceptance | pki configuration test', function (hooks) { setupMirage(hooks); test('it shows the delete all issuers modal', async function (assert) { - await authPage.login(this.pkiAdminToken); + await login(this.pkiAdminToken); await visit(`/vault/secrets/${this.mountPath}/pki/configuration`); await click(PKI_CONFIGURE_CREATE.configureButton); assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`); @@ -78,7 +77,7 @@ module('Acceptance | pki configuration test', function (hooks) { }); test('it shows the correct empty state message if certificates exists after delete all issuers', async function (assert) { - await authPage.login(this.pkiAdminToken); + await login(this.pkiAdminToken); await visit(`/vault/secrets/${this.mountPath}/pki/configuration`); await click(PKI_CONFIGURE_CREATE.configureButton); assert.strictEqual( @@ -157,7 +156,7 @@ module('Acceptance | pki configuration test', function (hooks) { }); test('it shows the correct empty state message if roles and certificates exists after delete all issuers', async function (assert) { - await authPage.login(this.pkiAdminToken); + await login(this.pkiAdminToken); // Configure PKI await visit(`/vault/secrets/${this.mountPath}/pki/configuration`); await click(PKI_CONFIGURE_CREATE.configureButton); @@ -231,7 +230,7 @@ module('Acceptance | pki configuration test', function (hooks) { // test coverage for ed25519 certs not displaying because the verify() function errors test('it generates and displays a root issuer of key type = ed25519', async function (assert) { assert.expect(4); - await authPage.login(this.pkiAdminToken); + await login(this.pkiAdminToken); await visit(`/vault/secrets/${this.mountPath}/pki/overview`); await click(GENERAL.secretTab('Issuers')); await click(PKI_ISSUER_LIST.generateIssuerDropdown); diff --git a/ui/tests/acceptance/settings/auth/enable-test.js b/ui/tests/acceptance/settings/auth/enable-test.js index 0c40bab40a67..3f553c2cedbc 100644 --- a/ui/tests/acceptance/settings/auth/enable-test.js +++ b/ui/tests/acceptance/settings/auth/enable-test.js @@ -11,14 +11,14 @@ import { v4 as uuidv4 } from 'uuid'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import page from 'vault/tests/pages/settings/auth/enable'; import listPage from 'vault/tests/pages/access/methods'; -import authPage from 'vault/tests/pages/auth'; +import { login } from 'vault/tests/helpers/auth/auth-helpers'; module('Acceptance | settings/auth/enable', function (hooks) { setupApplicationTest(hooks); hooks.beforeEach(function () { this.uid = uuidv4(); - return authPage.login(); + return login(); }); test('it mounts and redirects', async function (assert) { @@ -29,7 +29,7 @@ module('Acceptance | settings/auth/enable', function (hooks) { assert.strictEqual(currentRouteName(), 'vault.cluster.settings.auth.enable'); await page.enable(type, path); await settled(); - await assert.strictEqual( + assert.strictEqual( page.flash.latestMessage, `Successfully mounted the ${type} auth method at ${path}.`, 'success flash shows' diff --git a/ui/tests/helpers/commands.js b/ui/tests/helpers/commands.js index 4f3da8e84ffc..77af014a4b59 100644 --- a/ui/tests/helpers/commands.js +++ b/ui/tests/helpers/commands.js @@ -3,8 +3,13 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import consoleClass from 'vault/tests/pages/components/console/ui-panel'; -import { create } from 'ember-cli-page-object'; +import { click, fillIn, findAll, triggerKeyEvent } from '@ember/test-helpers'; + +const REPL = { + toggle: '[data-test-console-toggle]', + consoleInput: '[data-test-component="console/command-input"] input', + logOutputItems: '[data-test-component="console/output-log"] > div', +}; /** * Helper functions to run common commands in the consoleComponent during tests. @@ -31,30 +36,47 @@ import { create } from 'ember-cli-page-object'; * } */ -const cc = create(consoleClass); - /** * runCmd is used to run commands and throw an error if the output includes "Error" * @param {string || string[]} commands array of commands that should run * @param {boolean} throwErrors * @returns the last log output. Throws an error if it includes an error */ -export async function runCmd(commands, throwErrors = true) { +export const runCmd = async (commands, throwErrors = true) => { if (!commands) { throw new Error('runCmd requires commands array passed in'); } if (!Array.isArray(commands)) { commands = [commands]; } - await cc.toggle(); - await cc.runCommands(commands, false); - const lastOutput = cc.lastLogOutput; - await cc.toggle(); + await click(REPL.toggle); + await enterCommands(commands); + const lastOutput = await lastLogOutput(); + await click(REPL.toggle); if (throwErrors && lastOutput.includes('Error')) { throw new Error(`Error occurred while running commands: "${commands.join('; ')}" - ${lastOutput}`); } return lastOutput; -} +}; + +export const enterCommands = async (commands) => { + const toExecute = Array.isArray(commands) ? commands : [commands]; + for (const command of toExecute) { + await fillIn(REPL.consoleInput, command); + await triggerKeyEvent(REPL.consoleInput, 'keyup', 'Enter'); + } +}; + +export const lastLogOutput = async () => { + const items = findAll(REPL.logOutputItems); + const count = items.length; + if (count === 0) { + // If no logOutput items are found, we can assume the response is empty + return ''; + } + const outputItemText = items[count - 1].innerText; + return outputItemText; +}; // Common commands export function mountEngineCmd(type, customName = '') { diff --git a/ui/tests/integration/components/edit-form-kmip-role-test.js b/ui/tests/integration/components/edit-form-kmip-role-test.js deleted file mode 100644 index 8790571f1554..000000000000 --- a/ui/tests/integration/components/edit-form-kmip-role-test.js +++ /dev/null @@ -1,238 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { later, run, _cancelTimers as cancelTimers } from '@ember/runloop'; -import { resolve } from 'rsvp'; -import EmberObject, { computed } from '@ember/object'; -import Service from '@ember/service'; -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { click, render, settled } from '@ember/test-helpers'; -import hbs from 'htmlbars-inline-precompile'; -import sinon from 'sinon'; -import { setupEngine } from 'ember-engines/test-support'; -import { COMPUTEDS } from 'vault/models/kmip/role'; - -const flash = Service.extend({ - success: sinon.stub(), -}); -const namespace = Service.extend({}); - -const fieldToCheckbox = (field) => ({ name: field, type: 'boolean' }); - -const createModel = (options) => { - const model = EmberObject.extend(COMPUTEDS, { - /* eslint-disable ember/avoid-leaking-state-in-ember-objects */ - newFields: [ - 'role', - 'operationActivate', - 'operationAddAttribute', - 'operationAll', - 'operationCreate', - 'operationDestroy', - 'operationDiscoverVersion', - 'operationGet', - 'operationGetAttributes', - 'operationLocate', - 'operationNone', - 'operationRekey', - 'operationRevoke', - 'tlsClientKeyBits', - 'tlsClientKeyType', - 'tlsClientTtl', - ], - fields: computed('operationFields', function () { - return this.operationFields.map(fieldToCheckbox); - }), - destroyRecord() { - return resolve(); - }, - save() { - return resolve(); - }, - rollbackAttributes() {}, - }); - return model.create({ - ...options, - }); -}; - -module('Integration | Component | edit form kmip role', function (hooks) { - setupRenderingTest(hooks); - setupEngine(hooks, 'kmip'); - - hooks.beforeEach(function () { - this.context = { owner: this.engine }; // this.engine set by setupEngine - run(() => { - this.engine.unregister('service:flash-messages'); - this.engine.register('service:flash-messages', flash); - this.engine.register('service:namespace', namespace); - }); - }); - - test('it renders: new model', async function (assert) { - assert.expect(3); - const model = createModel({ isNew: true }); - this.set('model', model); - this.onSave = ({ model }) => { - assert.false(model.operationNone, 'callback fires with operationNone as false'); - assert.true(model.operationAll, 'callback fires with operationAll as true'); - }; - await render(hbs``, this.context); - - assert.dom('[data-test-input="operationAll"]').isChecked('sets operationAll'); - await click('[data-test-edit-form-submit]'); - }); - - test('it renders: operationAll', async function (assert) { - assert.expect(3); - const model = createModel({ operationAll: true }); - this.set('model', model); - this.onSave = ({ model }) => { - assert.false(model.operationNone, 'callback fires with operationNone as false'); - assert.true(model.operationAll, 'callback fires with operationAll as true'); - }; - await render(hbs``, this.context); - assert.dom('[data-test-input="operationAll"]').isChecked('sets operationAll'); - await click('[data-test-edit-form-submit]'); - }); - - test('it renders: operationNone', async function (assert) { - assert.expect(2); - const model = createModel({ operationNone: true, operationAll: undefined }); - this.set('model', model); - - this.onSave = ({ model }) => { - assert.true(model.operationNone, 'callback fires with operationNone as true'); - }; - await render(hbs``, this.context); - assert.dom('[data-test-input="operationNone"]').isNotChecked('sets operationNone'); - await click('[data-test-edit-form-submit]'); - }); - - test('it renders: choose operations', async function (assert) { - assert.expect(3); - const model = createModel({ operationGet: true }); - this.set('model', model); - this.onSave = ({ model }) => { - assert.false(model.operationNone, 'callback fires with operationNone as false'); - }; - await render(hbs``, this.context); - - assert.dom('[data-test-input="operationNone"]').isChecked('sets operationNone'); - assert.dom('[data-test-input="operationAll"]').isNotChecked('sets operationAll'); - await click('[data-test-edit-form-submit]'); - }); - - test('it saves operationNone=true when unchecking operationAll box', async function (assert) { - assert.expect(15); - const model = createModel({ isNew: true }); - this.set('model', model); - this.onSave = ({ model }) => { - assert.true(model.operationNone, 'callback fires with operationNone as true'); - assert.false(model.operationAll, 'callback fires with operationAll as false'); - }; - - await render(hbs``, this.context); - await click('[data-test-input="operationAll"]'); - for (const field of model.fields) { - const { name } = field; - if (name === 'operationNone') continue; - assert.dom(`[data-test-input="${name}"]`).isNotChecked(`${name} is unchecked`); - } - - assert.dom('[data-test-input="operationAll"]').isNotChecked('sets operationAll'); - assert - .dom('[data-test-input="operationNone"]') - .isChecked('operationNone toggle is true which means allow operations'); - await click('[data-test-edit-form-submit]'); - }); - - const savingTests = [ - [ - 'setting operationAll', - { operationNone: true, operationGet: true }, - 'operationNone', - { - operationAll: true, - operationNone: false, - operationGet: true, - }, - { - operationGet: null, - operationNone: false, - }, - 5, - ], - [ - 'setting operationNone', - { operationAll: true, operationCreate: true }, - 'operationNone', - { - operationAll: false, - operationNone: true, - operationCreate: true, - }, - { - operationNone: true, - operationCreate: null, - operationAll: false, - }, - 6, - ], - - [ - 'setting choose, and selecting an additional item', - { operationAll: true, operationGet: true, operationCreate: true }, - 'operationAll,operationDestroy', - { - operationAll: false, - operationCreate: true, - operationGet: true, - }, - { - operationGet: true, - operationCreate: true, - operationDestroy: true, - operationAll: false, - }, - 7, - ], - ]; - for (const testCase of savingTests) { - const [name, initialState, displayClicks, stateBeforeSave, stateAfterSave, assertionCount] = testCase; - test(name, async function (assert) { - assert.expect(assertionCount); - const model = createModel(initialState); - this.set('model', model); - const clickTargets = displayClicks.split(','); - await render(hbs``, this.context); - - for (const clickTarget of clickTargets) { - await click(`label[for=${clickTarget}]`); - } - for (const beforeStateKey of Object.keys(stateBeforeSave)) { - assert.strictEqual( - model.get(beforeStateKey), - stateBeforeSave[beforeStateKey], - `sets ${beforeStateKey}` - ); - } - - await click('[data-test-edit-form-submit]'); - - later(() => cancelTimers(), 50); - await settled(); - - for (const afterStateKey of Object.keys(stateAfterSave)) { - assert.strictEqual( - model.get(afterStateKey), - stateAfterSave[afterStateKey], - `sets ${afterStateKey} on save` - ); - } - }); - } -}); diff --git a/ui/tests/integration/components/edit-form-test.js b/ui/tests/integration/components/edit-form-test.js index 1c8a1a0596ee..4b06fbb39c22 100644 --- a/ui/tests/integration/components/edit-form-test.js +++ b/ui/tests/integration/components/edit-form-test.js @@ -3,77 +3,51 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { later, run, _cancelTimers as cancelTimers } from '@ember/runloop'; -import { resolve } from 'rsvp'; import EmberObject from '@ember/object'; -import Service from '@ember/service'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, settled } from '@ember/test-helpers'; +import { click, render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; -import { create } from 'ember-cli-page-object'; -import editForm from 'vault/tests/pages/components/edit-form'; - -const component = create(editForm); - -const flash = Service.extend({ - success: sinon.stub(), -}); - -const createModel = (canDelete = true) => { - return EmberObject.create({ - fields: [ - { name: 'one', type: 'string' }, - { name: 'two', type: 'boolean' }, - ], - canDelete, - destroyRecord() { - return resolve(); - }, - save() { - return resolve(); - }, - rollbackAttributes() {}, - }); -}; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; module('Integration | Component | edit form', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { - run(() => { - this.owner.unregister('service:flash-messages'); - this.owner.register('service:flash-messages', flash); + this.model = EmberObject.create({ + fields: [ + { name: 'one', type: 'string' }, + { name: 'two', type: 'boolean' }, + ], + destroyRecord() {}, + save() {}, + rollbackAttributes() {}, }); + this.onSave = sinon.spy(); + this.renderComponent = () => + render(hbs` + + `); }); test('it renders', async function (assert) { - this.set('model', createModel()); - await render(hbs`{{edit-form model=this.model}}`); - - assert.ok(component.fields.length, 2); + await this.renderComponent(); + assert.dom(GENERAL.fieldByAttr('one')).exists(); + assert.dom(GENERAL.fieldByAttr('two')).exists(); }); test('it calls flash message fns on save', async function (assert) { assert.expect(4); - const onSave = () => { - return resolve(); - }; - this.set('model', createModel()); - this.set('onSave', onSave); - const saveSpy = sinon.spy(this, 'onSave'); - - await render(hbs`{{edit-form model=this.model onSave=this.onSave}}`); - - component.submit(); - later(() => cancelTimers(), 50); - await settled(); - - assert.true(saveSpy.calledOnce, 'calls passed onSave'); - assert.strictEqual(saveSpy.getCall(0).args[0].saveType, 'save'); - assert.deepEqual(saveSpy.getCall(0).args[0].model, this.model, 'passes model to onSave'); const flash = this.owner.lookup('service:flash-messages'); - assert.strictEqual(flash.success.callCount, 1, 'calls flash message success'); + this.flashSuccessSpy = sinon.spy(flash, 'success'); + await this.renderComponent(); + await click('[data-test-edit-form-submit]'); + const { saveType, model } = this.onSave.lastCall.args[0]; + const [flashMessage] = this.flashSuccessSpy.lastCall.args; + assert.strictEqual(flashMessage, 'Saved!'); + assert.strictEqual(saveType, 'save'); + assert.strictEqual(saveType, 'save'); + assert.deepEqual(model, this.model, 'passes model to onSave'); }); });