diff --git a/addons/rose/addon/components/rose/form.js b/addons/rose/addon/components/rose/form.js deleted file mode 100644 index 0805f0348..000000000 --- a/addons/rose/addon/components/rose/form.js +++ /dev/null @@ -1,27 +0,0 @@ -import Component from '@ember/component'; -import layout from '../../templates/components/rose/form'; -import { action } from '@ember/object'; - -/** - * A component for building accessible forms. Yields contextual fields - * and actions. - */ -export default Component.extend({ - layout, - tagName: '', - - // =actions - - /** - * Calls the passed `onSubmit` function and disabled the default form - * submit behavior. - * @param {Event} e - */ - @action - handleSubmit(e) { - e.preventDefault(); - e.stopPropagation(); - this.onSubmit(); - return false; - }, -}); diff --git a/addons/rose/addon/components/rose/form/actions/edit-toggle/index.hbs b/addons/rose/addon/components/rose/form/actions/edit-toggle/index.hbs new file mode 100644 index 000000000..f052b495c --- /dev/null +++ b/addons/rose/addon/components/rose/form/actions/edit-toggle/index.hbs @@ -0,0 +1,8 @@ +
+ + {{@enableEditText}} + +
diff --git a/addons/rose/addon/templates/components/rose/form/actions.hbs b/addons/rose/addon/components/rose/form/actions/index.hbs similarity index 81% rename from addons/rose/addon/templates/components/rose/form/actions.hbs rename to addons/rose/addon/components/rose/form/actions/index.hbs index 47557fb7e..184f36893 100644 --- a/addons/rose/addon/templates/components/rose/form/actions.hbs +++ b/addons/rose/addon/components/rose/form/actions/index.hbs @@ -1,6 +1,6 @@
@@ -9,7 +9,7 @@ {{#if this.showCancel}} diff --git a/addons/rose/addon/components/rose/form/actions.js b/addons/rose/addon/components/rose/form/actions/index.js similarity index 60% rename from addons/rose/addon/components/rose/form/actions.js rename to addons/rose/addon/components/rose/form/actions/index.js index 4bfcbea85..f8d5bb76e 100644 --- a/addons/rose/addon/components/rose/form/actions.js +++ b/addons/rose/addon/components/rose/form/actions/index.js @@ -1,5 +1,5 @@ -import Component from '@ember/component'; -import layout from '../../../templates/components/rose/form/actions'; +import Component from '@glimmer/component'; +import { computed } from '@ember/object'; /** * A set of two form action buttons for submit and cancel. @@ -13,12 +13,13 @@ import layout from '../../../templates/components/rose/form/actions'; * @showCancel={{true}} * @cancel=(fn @cancel) /> */ -export default Component.extend({ - layout, - tagName: '', +export default class RoseFormActionsComponent extends Component { /** * @type {boolean} */ - showCancel: true, -}); + @computed('args.showCancel') + get showCancel() { + return this.args.showCancel ?? true; + } +} diff --git a/addons/rose/addon/components/rose/form/actions.stories.mdx b/addons/rose/addon/components/rose/form/actions/index.stories.mdx similarity index 100% rename from addons/rose/addon/components/rose/form/actions.stories.mdx rename to addons/rose/addon/components/rose/form/actions/index.stories.mdx diff --git a/addons/rose/addon/components/rose/form/index.hbs b/addons/rose/addon/components/rose/form/index.hbs new file mode 100644 index 000000000..045ad70c4 --- /dev/null +++ b/addons/rose/addon/components/rose/form/index.hbs @@ -0,0 +1,30 @@ +
+ {{!-- + Only show the edit toggle button if the toggle is enabled in the first place + AND the form is not already in editable mode. + --}} + {{yield (hash + radioGroup=(component 'rose/form/radio/group' disabled=this.disabled) + checkbox=(component 'rose/form/checkbox' disabled=this.disabled) + input=(component 'rose/form/input' disabled=this.disabled) + select=(component 'rose/form/select' disabled=this.disabled) + textarea=(component 'rose/form/textarea' disabled=this.disabled) + actions=(component + (if this.showEditToggleButton + 'rose/form/actions/edit-toggle' + 'rose/form/actions' + ) + submitDisabled=this.disabled + cancelDisabled=(if @showEditToggle false this.disabled) + cancel=this.handleCancel + enableEdit=this.enableEdit + ) + )}} +
diff --git a/addons/rose/addon/components/rose/form/index.js b/addons/rose/addon/components/rose/form/index.js new file mode 100644 index 000000000..249cd9fbd --- /dev/null +++ b/addons/rose/addon/components/rose/form/index.js @@ -0,0 +1,79 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { computed, action } from '@ember/object'; + +export default class RoseFormComponent extends Component { + + // =attributes + + /** + * This field indicates whether edit mode is toggled + * and only applies if `@showEditToggle` is true. + * @type {boolean} + */ + @tracked isEditable = false; + + /** + * By default, this field is unconditionally equal to the value of the passed + * `@disabled` argument. If desired the form may be "locked" from editing + * until the user intentionally enables edit mode (`@showEditToggle` is true), + * in which case the value is dependent on `@disabled` and `this.isEditable`. + * @type {boolean} + */ + @computed('args.disabled', 'args.showEditToggle', 'isEditable') + get disabled() { + if (this.args.showEditToggle) return this.args.disabled || !this.isEditable; + return this.args.disabled; + } + + /** + * True if showEditToggle is true and edit mode is enabled. + * @type {boolean} + */ + @computed('args.showEditToggle', 'isEditable') + get showEditToggleButton() { + return this.args.showEditToggle && !this.isEditable; + } + + // =actions + + /** + * Calls the passed `onSubmit` function while disabling the default form + * submit behavior. + * @param {Event} e + */ + @action + handleSubmit(e) { + e.preventDefault(); + e.stopPropagation(); + this.args.onSubmit(); + return false; + } + + /** + * Calls the passed `cancel` function. + * @param {Event} e + */ + @action + handleCancel() { + this.disableEdit(); + this.args.cancel(); + } + + /** + * Enables edit mode, which only applies when `@showEditToggle` is true. + */ + @action + enableEdit() { + this.isEditable = true; + } + + /** + * Disables edit mode, which only applies when `@showEditToggle` is true. + */ + @action + disableEdit() { + this.isEditable = false; + } + +} diff --git a/addons/rose/addon/components/rose/form.stories.mdx b/addons/rose/addon/components/rose/form/index.stories.mdx similarity index 88% rename from addons/rose/addon/components/rose/form.stories.mdx rename to addons/rose/addon/components/rose/form/index.stories.mdx index f57f85da2..f3498eaa2 100644 --- a/addons/rose/addon/components/rose/form.stories.mdx +++ b/addons/rose/addon/components/rose/form/index.stories.mdx @@ -16,6 +16,7 @@ and actions. - + `, context: { + showEditToggle: boolean('showEditToggle', false), disabled: boolean('disabled', false), submit: action('submit'), cancel: action('cancel'), @@ -77,6 +83,7 @@ and actions. @onSubmit={{this.submit}} @cancel={{this.cancel}} @disabled={{false}} + @showEditToggle={{true}} as |form|> - + ``` diff --git a/addons/rose/addon/templates/components/rose/form.hbs b/addons/rose/addon/templates/components/rose/form.hbs deleted file mode 100644 index f37f36d09..000000000 --- a/addons/rose/addon/templates/components/rose/form.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
- {{yield (hash - radioGroup=(component 'rose/form/radio/group' disabled=@disabled) - checkbox=(component 'rose/form/checkbox' disabled=@disabled) - input=(component 'rose/form/input' disabled=@disabled) - select=(component 'rose/form/select' disabled=@disabled) - textarea=(component 'rose/form/textarea' disabled=@disabled) - actions=(component 'rose/form/actions' disabled=@disabled cancel=@cancel) - )}} -
diff --git a/addons/rose/app/components/rose/form/actions/edit-toggle.js b/addons/rose/app/components/rose/form/actions/edit-toggle.js new file mode 100644 index 000000000..e0790458d --- /dev/null +++ b/addons/rose/app/components/rose/form/actions/edit-toggle.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/form/actions/edit-toggle'; \ No newline at end of file diff --git a/addons/rose/tests/integration/components/rose/form-test.js b/addons/rose/tests/integration/components/rose/form-test.js index f8d682fdd..7c33aa290 100644 --- a/addons/rose/tests/integration/components/rose/form-test.js +++ b/addons/rose/tests/integration/components/rose/form-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, find, click } from '@ember/test-helpers'; +import { render, find, findAll, click } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | rose/form', function (hooks) { @@ -64,4 +64,42 @@ module('Integration | Component | rose/form', function (hooks) { assert.ok(find('[name="checkbox-field"]')); assert.ok(find('[name="radio-group-field"]')); }); + + test('it supports an editability toggle when @showEditToggle is true', async function (assert) { + assert.expect(12); + this.cancel = () => { + assert.ok(true, 'cancel function may still be passed even for @showEditToggle'); + }; + this.onSubmit = () => {}; + await render(hbs` + + + + + `); + // Before enabling edit mode, fields are disabled and the edit mode button is displayed + assert.ok(find('.rose-form')); + assert.equal(findAll('input[disabled]').length, 1); + assert.equal(findAll('button').length, 1); + assert.equal(find('button').textContent.trim(), 'Edit'); + // After entering edit mode, fields are enabled and save/cancel buttons are displayed + await click('button'); + assert.equal(findAll('input[disabled]').length, 0); + assert.equal(findAll('button').length, 2); + assert.equal(find('[type="submit"]').textContent.trim(), 'Save'); + assert.equal(find('button:not([type="submit"])').textContent.trim(), 'Cancel'); + // After canceling, fields are disabled again and the edit mode button is displayed + await click('button:not([type="submit"])'); + assert.equal(findAll('input[disabled]').length, 1); + assert.equal(findAll('button').length, 1); + assert.equal(find('button').textContent.trim(), 'Edit'); + }); }); diff --git a/addons/rose/tests/integration/components/rose/form/actions-test.js b/addons/rose/tests/integration/components/rose/form/actions-test.js index fa018c7c6..0463d1247 100644 --- a/addons/rose/tests/integration/components/rose/form/actions-test.js +++ b/addons/rose/tests/integration/components/rose/form/actions-test.js @@ -14,10 +14,10 @@ module('Integration | Component | rose/form/actions', function (hooks) { assert.ok(find('button:not([type="submit"])')); }); - test('it can disabled the buttons', async function (assert) { + test('it can disable the buttons', async function (assert) { assert.expect(2); this.cancel = () => {}; - await render(hbs`{{rose/form/actions cancel=this.cancel disabled=true}}`); + await render(hbs`{{rose/form/actions cancel=this.cancel submitDisabled=true cancelDisabled=true}}`); assert.ok(find('[type="submit"]:disabled')); assert.ok(find('button:not([type="submit"]):disabled')); }); diff --git a/addons/rose/tests/integration/components/rose/form/actions/edit-toggle-test.js b/addons/rose/tests/integration/components/rose/form/actions/edit-toggle-test.js new file mode 100644 index 000000000..436a14b03 --- /dev/null +++ b/addons/rose/tests/integration/components/rose/form/actions/edit-toggle-test.js @@ -0,0 +1,22 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find, click } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | rose/form/actions/edit-toggle', function (hooks) { + setupRenderingTest(hooks); + + test('it renders a regular button', async function (assert) { + assert.expect(1); + this.enableEdit = () => {}; + await render(hbs`{{rose/form/actions/edit-toggle enableEdit=this.enableEdit}}`); + assert.ok(find('button')); + }); + + test('it executes a function on button click', async function (assert) { + assert.expect(1); + this.enableEdit = () => assert.ok(true, 'edit was clicked'); + await render(hbs`{{rose/form/actions/edit-toggle enableEdit=this.enableEdit}}`); + await click('button'); + }); +}); diff --git a/ui/core/tests/acceptance/groups-test.js b/ui/core/tests/acceptance/groups-test.js index 6f04e2492..c3d2e5add 100644 --- a/ui/core/tests/acceptance/groups-test.js +++ b/ui/core/tests/acceptance/groups-test.js @@ -15,49 +15,53 @@ module('Acceptance | groups', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - let orgScope; - let groupsURL; - let groupURL; - let newGroupURL; + const instances = { + scopes: { + global: null, + org: null, + }, + group: null, + }; + const urls = { + orgScope: null, + groups: null, + group: null, + newGroup: null, + }; hooks.beforeEach(function () { - orgScope = this.server.create( - 'scope', - { - type: 'org', - }, - 'withChildren' - ); - - const group = this.server.create('group', { - scope: orgScope, - }); - - groupsURL = `/scopes/${orgScope.id}/groups`; - groupURL = `${groupsURL}/${group.id}`; - newGroupURL = `${groupsURL}/new`; - authenticateSession({}); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.group = this.server.create('group', { + scope: instances.scopes.org, + }); + urls.groups = `/scopes/${instances.scopes.org.id}/groups`; + urls.group = `${urls.groups}/${instances.group.id}`; + urls.newGroup = `${urls.groups}/new`; }); test('visiting groups', async function (assert) { assert.expect(1); - await visit(groupsURL); + await visit(urls.groups); await a11yAudit(); - assert.equal(currentURL(), groupsURL); + assert.equal(currentURL(), urls.groups); }); test('visiting a group', async function (assert) { assert.expect(1); - await visit(newGroupURL); + await visit(urls.newGroup); await a11yAudit(); - assert.equal(currentURL(), newGroupURL); + assert.equal(currentURL(), urls.newGroup); }); test('can create new group', async function (assert) { assert.expect(1); const groupsCount = this.server.db.groups.length; - await visit(newGroupURL); + await visit(urls.newGroup); await fillIn('[name="name"]', 'group name'); await click('[type="submit"]'); assert.equal(this.server.db.groups.length, groupsCount + 1); @@ -66,10 +70,10 @@ module('Acceptance | groups', function (hooks) { test('can cancel new group creation', async function (assert) { assert.expect(2); const groupsCount = this.server.db.groups.length; - await visit(newGroupURL); + await visit(urls.newGroup); await fillIn('[name="name"]', 'group name'); await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), groupsURL); + assert.equal(currentURL(), urls.groups); assert.equal(this.server.db.groups.length, groupsCount); }); @@ -94,7 +98,7 @@ module('Acceptance | groups', function (hooks) { } ); }); - await visit(newGroupURL); + await visit(urls.newGroup); await fillIn('[name="name"]', 'group name'); await click('[type="submit"]'); assert.ok( @@ -111,16 +115,16 @@ module('Acceptance | groups', function (hooks) { test('can save changes to an existing group', async function (assert) { assert.expect(2); - await visit(groupURL); + await visit(urls.group); await fillIn('[name="name"]', 'Updated admin group'); await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), groupURL); + assert.equal(currentURL(), urls.group); assert.equal(this.server.db.groups[0].name, 'Updated admin group'); }); test('can cancel changes to an existing group', async function (assert) { assert.expect(1); - await visit(groupURL); + await visit(urls.group); await fillIn('[name="name"]', 'Updated admin group'); await click('.rose-form-actions [type="button"]'); assert.notEqual(find('[name="name"]').value, 'Updated admin group'); @@ -129,7 +133,7 @@ module('Acceptance | groups', function (hooks) { test('can delete a group', async function (assert) { assert.expect(1); const groupsCount = this.server.db.groups.length; - await visit(groupURL); + await visit(urls.group); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.equal(this.server.db.groups.length, groupsCount - 1); }); @@ -155,7 +159,7 @@ module('Acceptance | groups', function (hooks) { } ); }); - await visit(groupURL); + await visit(urls.group); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); assert.ok( @@ -183,7 +187,7 @@ module('Acceptance | groups', function (hooks) { } ); }); - await visit(groupURL); + await visit(urls.group); await fillIn('[name="name"]', 'Role name'); await click('[type="submit"]'); assert.ok( @@ -206,7 +210,7 @@ module('Acceptance | groups', function (hooks) { } ); }); - await visit(groupURL); + await visit(urls.group); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.ok( find('[role="alert"]').textContent.trim(), @@ -214,20 +218,4 @@ module('Acceptance | groups', function (hooks) { 'Displays primary error message.' ); }); - - test('can view group members', async function (assert) { - assert.expect(0); - }); - - test('can delete members', async function (assert) { - assert.expect(0); - }); - - test('select and save members to add', async function (assert) { - assert.expect(0); - }); - - test('select and cancel members to add', async function (assert) { - assert.expect(0); - }); }); diff --git a/ui/core/tests/acceptance/groups/members-test.js b/ui/core/tests/acceptance/groups/members-test.js new file mode 100644 index 000000000..69edf67df --- /dev/null +++ b/ui/core/tests/acceptance/groups/members-test.js @@ -0,0 +1,141 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; +import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | groups | members', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + group: null, + }; + const urls = { + orgScope: null, + groups: null, + group: null, + members: null, + addMembers: null, + }; + let membersCount; + + hooks.beforeEach(function () { + authenticateSession({}); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.group = this.server.create('group', { + scope: instances.scopes.org, + }, 'withMembers'); + membersCount = instances.group.memberIds.length; + urls.groups = `/scopes/${instances.scopes.org.id}/groups`; + urls.group = `${urls.groups}/${instances.group.id}`; + urls.members = `${urls.group}/members`; + urls.addMembers = `${urls.group}/add-members`; + }); + + test('visiting group members', async function (assert) { + assert.expect(2); + await visit(urls.members); + await a11yAudit(); + assert.equal(currentURL(), urls.members); + assert.equal(findAll('tbody tr').length, membersCount); + }); + + test('can remove a member', async function (assert) { + assert.expect(2); + await visit(urls.members); + assert.equal(findAll('tbody tr').length, membersCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, membersCount - 1); + }); + + test('shows error message on member remove', async function (assert) { + assert.expect(2); + this.server.post('/groups/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.members); + assert.equal(findAll('tbody tr').length, membersCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]')); + }); + + test('visiting member selection', async function(assert) { + assert.expect(1); + await visit(urls.addMembers); + await a11yAudit(); + assert.equal(currentURL(), urls.addMembers); + }); + + test('select and save members to add', async function (assert) { + assert.expect(3); + instances.group.update({ memberIds: [] }); + await visit(urls.members); + assert.equal(findAll('tbody tr').length, 0); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addMembers); + await click('tbody label'); + await click('form [type="submit"]'); + await visit(urls.members); + assert.equal(findAll('tbody tr').length, 1); + }); + + test('select and cancel members to add', async function (assert) { + assert.expect(4); + await visit(urls.members); + assert.equal(findAll('tbody tr').length, membersCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, membersCount - 1); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addMembers); + await click('tbody label'); + await click('form [type="button"]'); + await visit(urls.members); + assert.equal(findAll('tbody tr').length, membersCount - 1); + }); + + test('shows error message on member add', async function (assert) { + assert.expect(1); + this.server.post('/groups/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + instances.group.update({ memberIds: [] }); + await visit(urls.addMembers); + await click('tbody label'); + await click('form [type="submit"]'); + assert.ok(find('[role="alert"]')); + }); +}); diff --git a/ui/core/tests/acceptance/host-catalogs-test.js b/ui/core/tests/acceptance/host-catalogs-test.js index 677fada21..a9438517e 100644 --- a/ui/core/tests/acceptance/host-catalogs-test.js +++ b/ui/core/tests/acceptance/host-catalogs-test.js @@ -34,6 +34,7 @@ module('Acceptance | host catalogs', function (hooks) { hooks.beforeEach(function () { // Setup Mirage mock resources for this test + authenticateSession(); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -58,7 +59,6 @@ module('Acceptance | host catalogs', function (hooks) { test('visiting host catalogs', async function (assert) { assert.expect(1); - authenticateSession(); await visit(urls.hostCatalogs); await a11yAudit(); assert.equal(currentURL(), urls.hostCatalogs); @@ -66,7 +66,6 @@ module('Acceptance | host catalogs', function (hooks) { test('can navigate to host catalog form', async function (assert) { assert.expect(1); - authenticateSession({}); await visit(urls.hostCatalogs); await click('main tbody .rose-table-header-cell:nth-child(1) a'); await a11yAudit(); @@ -76,7 +75,6 @@ module('Acceptance | host catalogs', function (hooks) { test('can delete host catalog', async function (assert) { assert.expect(2); const hostCatalogsCount = this.server.db.hostCatalogs.length; - authenticateSession({}); await visit(urls.hostCatalog); assert.equal(currentURL(), urls.hostCatalog); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); @@ -85,7 +83,6 @@ module('Acceptance | host catalogs', function (hooks) { test('can update host catalog and save changes', async function (assert) { assert.expect(1); - authenticateSession({}); await visit(urls.hostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form [type="submit"]:not(:disabled)'); @@ -94,7 +91,6 @@ module('Acceptance | host catalogs', function (hooks) { test('can update host catalog and cancel changes', async function (assert) { assert.expect(1); - authenticateSession({}); await visit(urls.hostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form button:not([type="submit"])'); @@ -104,7 +100,6 @@ module('Acceptance | host catalogs', function (hooks) { test('can create host catalog and save changes', async function (assert) { assert.expect(1); const hostCatalogsCount = this.server.db.hostCatalogs.length; - authenticateSession({}); await visit(urls.newHostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form [type="submit"]:not(:disabled)'); @@ -114,7 +109,6 @@ module('Acceptance | host catalogs', function (hooks) { test('can create host catalog and cancel changes', async function (assert) { assert.expect(1); const hostCatalogsCount = this.server.db.hostCatalogs.length; - authenticateSession({}); await visit(urls.newHostCatalog); await fillIn('[name="name"]', 'Test Name'); await click('form button:not([type="submit"])'); @@ -134,7 +128,6 @@ module('Acceptance | host catalogs', function (hooks) { } ); }); - authenticateSession({}); await visit(urls.hostCatalog); await fillIn('[name="name"]', 'random string'); await click('form [type="submit"]'); @@ -159,7 +152,6 @@ module('Acceptance | host catalogs', function (hooks) { } ); }); - authenticateSession({}); await visit(urls.hostCatalog); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); await a11yAudit(); @@ -191,7 +183,6 @@ module('Acceptance | host catalogs', function (hooks) { } ); }); - authenticateSession({}); await visit(urls.newHostCatalog); await fillIn('[name="name"]', 'random string'); await click('form [type="submit"]'); @@ -229,7 +220,6 @@ module('Acceptance | host catalogs', function (hooks) { } ); }); - authenticateSession({}); await visit(urls.hostCatalog); await fillIn('[name="name"]', 'random string'); await click('form [type="submit"]'); diff --git a/ui/core/tests/acceptance/host-catalogs/host-sets-test.js b/ui/core/tests/acceptance/host-catalogs/host-sets-test.js new file mode 100644 index 000000000..2c90b8ada --- /dev/null +++ b/ui/core/tests/acceptance/host-catalogs/host-sets-test.js @@ -0,0 +1,102 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, fillIn, click, findAll } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | host catalogs | host sets', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + hostCatalog: null, + hostSets: null, + }; + const urls = { + orgScope: null, + projects: null, + project: null, + hostCatalogs: null, + hostCatalog: null, + hostSets: null, + newHostSet: null + }; + let hostSetsCount; + + hooks.beforeEach(function () { + // Setup Mirage mock resources for this test + authenticateSession(); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.scopes.project = this.server.create('scope', { + type: 'project', + scope: { id: instances.scopes.org.id, type: 'org' }, + }); + instances.hostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + }); + instances.hostSets = this.server.createList('host-set', 3, { + scope: instances.scopes.project, + hostCatalog: instances.hostCatalog + }); + hostSetsCount = instances.hostCatalog.hostSetIds.length; + // Generate route URLs for resources + urls.orgScope = `/scopes/${instances.scopes.org.id}`; + urls.projects = `${urls.orgScope}/projects`; + urls.project = `${urls.projects}/${instances.hostCatalog.scope.id}`; + urls.hostCatalogs = `${urls.project}/host-catalogs`; + urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; + urls.hostSets = `${urls.hostCatalog}/host-sets`; + urls.newHostSet = `${urls.hostSets}/new`; + }); + + test('visiting host sets', async function (assert) { + assert.expect(3); + await visit(urls.hostSets); + await a11yAudit(); + assert.equal(currentURL(), urls.hostSets); + assert.ok(hostSetsCount); + assert.equal(findAll('tbody tr').length, hostSetsCount); + }); + + test('can navigate to a host set form', async function (assert) { + assert.expect(1); + await visit(urls.hostSets); + await click('main tbody .rose-table-header-cell:nth-child(1) a'); + await a11yAudit(); + assert.notEqual(currentURL(), urls.hostSets); + }); + + test('can create host set and save changes', async function (assert) { + assert.expect(1); + await visit(urls.newHostSet); + await fillIn('[name="name"]', 'Test Name'); + await fillIn('[name="description"]', 'description'); + await click('form [type="submit"]:not(:disabled)'); + await visit(urls.hostSets); + assert.equal(findAll('tbody tr').length, hostSetsCount + 1); + }); + + test('can create host set and cancel changes', async function (assert) { + assert.expect(2); + await visit(urls.newHostSet); + await fillIn('[name="name"]', 'Test Name'); + await click('form button:not([type="submit"])'); + assert.equal(currentURL(), urls.hostSets); + assert.equal(findAll('tbody tr').length, hostSetsCount); + }); +}); diff --git a/ui/core/tests/acceptance/host-catalogs/hosts-test.js b/ui/core/tests/acceptance/host-catalogs/hosts-test.js new file mode 100644 index 000000000..5b4720a24 --- /dev/null +++ b/ui/core/tests/acceptance/host-catalogs/hosts-test.js @@ -0,0 +1,102 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, fillIn, click, findAll } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | host catalogs | hosts', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + hostCatalog: null, + hosts: null, + }; + const urls = { + orgScope: null, + projects: null, + project: null, + hostCatalogs: null, + hostCatalog: null, + hosts: null, + newHost: null + }; + let hostsCount; + + hooks.beforeEach(function () { + // Setup Mirage mock resources for this test + authenticateSession(); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.scopes.project = this.server.create('scope', { + type: 'project', + scope: { id: instances.scopes.org.id, type: 'org' }, + }); + instances.hostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + }); + instances.hosts = this.server.createList('host', 3, { + scope: instances.scopes.project, + hostCatalog: instances.hostCatalog + }); + hostsCount = instances.hosts.length; + // Generate route URLs for resources + urls.orgScope = `/scopes/${instances.scopes.org.id}`; + urls.projects = `${urls.orgScope}/projects`; + urls.project = `${urls.projects}/${instances.hostCatalog.scope.id}`; + urls.hostCatalogs = `${urls.project}/host-catalogs`; + urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; + urls.hosts = `${urls.hostCatalog}/hosts`; + urls.newHost = `${urls.hosts}/new`; + }); + + test('visiting hosts', async function (assert) { + assert.expect(3); + await visit(urls.hosts); + await a11yAudit(); + assert.equal(currentURL(), urls.hosts); + assert.ok(hostsCount); + assert.equal(findAll('tbody tr').length, hostsCount); + }); + + test('can navigate to a host form', async function (assert) { + assert.expect(1); + await visit(urls.hosts); + await click('main tbody .rose-table-header-cell:nth-child(1) a'); + await a11yAudit(); + assert.notEqual(currentURL(), urls.hosts); + }); + + test('can create host and save changes', async function (assert) { + assert.expect(1); + await visit(urls.newHost); + await fillIn('[name="name"]', 'Test Name'); + await fillIn('[name="description"]', 'description'); + await click('form [type="submit"]:not(:disabled)'); + await visit(urls.hosts); + assert.equal(findAll('tbody tr').length, hostsCount + 1); + }); + + test('can create host and cancel changes', async function (assert) { + assert.expect(2); + await visit(urls.newHost); + await fillIn('[name="name"]', 'Test Name'); + await click('form button:not([type="submit"])'); + assert.equal(currentURL(), urls.hosts); + assert.equal(findAll('tbody tr').length, hostsCount); + }); +});