From 41688ad80ca3f39bb1f6dbc3de00550345881566 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Sun, 20 Sep 2020 16:15:13 -0400 Subject: [PATCH 01/15] refactor: Refactor role acceptance test to config objects --- ui/core/tests/acceptance/roles-test.js | 74 ++++++++++++++------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js index df68d06eb..fb1eccf2b 100644 --- a/ui/core/tests/acceptance/roles-test.js +++ b/ui/core/tests/acceptance/roles-test.js @@ -15,49 +15,53 @@ module('Acceptance | roles', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - let orgScope; - let rolesURL; - let roleURL; - let newRoleURL; + const instances = { + scopes: { + global: null, + org: null, + }, + role: null, + }; + const urls = { + orgScope: null, + roles: null, + role: null, + newRole: null, + }; hooks.beforeEach(function () { - orgScope = this.server.create( - 'scope', - { - type: 'org', - }, - 'withChildren' - ); - - const role = this.server.create('role', { - scope: orgScope, - }); - - rolesURL = `/scopes/${orgScope.id}/roles`; - roleURL = `${rolesURL}/${role.id}`; - newRoleURL = `${rolesURL}/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.role = this.server.create('role', { + scope: instances.scopes.org, + }); + urls.roles = `/scopes/${instances.scopes.org.id}/roles`; + urls.role = `${urls.roles}/${instances.role.id}`; + urls.newRole = `${urls.roles}/new`; }); test('visiting roles', async function (assert) { assert.expect(1); - await visit(rolesURL); + await visit(urls.roles); await a11yAudit(); - assert.equal(currentURL(), rolesURL); + assert.equal(currentURL(), urls.roles); }); test('visiting a role', async function (assert) { assert.expect(1); - await visit(newRoleURL); + await visit(urls.newRole); await a11yAudit(); - assert.equal(currentURL(), newRoleURL); + assert.equal(currentURL(), urls.newRole); }); test('can create new role', async function (assert) { assert.expect(1); const rolesCount = this.server.db.roles.length; - await visit(newRoleURL); + await visit(urls.newRole); await fillIn('[name="name"]', 'role name'); await click('[type="submit"]'); assert.equal(this.server.db.roles.length, rolesCount + 1); @@ -66,10 +70,10 @@ module('Acceptance | roles', function (hooks) { test('can cancel new role creation', async function (assert) { assert.expect(2); const rolesCount = this.server.db.roles.length; - await visit(newRoleURL); + await visit(urls.newRole); await fillIn('[name="name"]', 'role name'); await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), rolesURL); + assert.equal(currentURL(), urls.roles); assert.equal(this.server.db.roles.length, rolesCount); }); @@ -94,7 +98,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit(newRoleURL); + await visit(urls.newRole); await fillIn('[name="name"]', 'role name'); await click('[type="submit"]'); assert.ok( @@ -111,16 +115,16 @@ module('Acceptance | roles', function (hooks) { test('can save changes to an existing role', async function (assert) { assert.expect(2); - await visit(roleURL); + await visit(urls.role); await fillIn('[name="name"]', 'Updated admin role'); await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), roleURL); + assert.equal(currentURL(), urls.role); assert.equal(this.server.db.roles[0].name, 'Updated admin role'); }); test('can cancel changes to an existing role', async function (assert) { assert.expect(1); - await visit(roleURL); + await visit(urls.role); await fillIn('[name="name"]', 'Updated admin role'); await click('.rose-form-actions [type="button"]'); assert.notEqual(find('[name="name"]').value, 'Updated admin role'); @@ -129,7 +133,7 @@ module('Acceptance | roles', function (hooks) { test('can delete a role', async function (assert) { assert.expect(1); const rolesCount = this.server.db.roles.length; - await visit(roleURL); + await visit(urls.role); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.equal(this.server.db.roles.length, rolesCount - 1); }); @@ -155,7 +159,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit(roleURL); + await visit(urls.role); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); assert.ok( @@ -183,7 +187,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit(roleURL); + await visit(urls.role); await fillIn('[name="name"]', 'Role name'); await click('[type="submit"]'); assert.ok( @@ -206,7 +210,7 @@ module('Acceptance | roles', function (hooks) { } ); }); - await visit(roleURL); + await visit(urls.role); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.ok( find('[role="alert"]').textContent.trim(), From a8efd3768d718902189ed9d325a5e594d97c28fa Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 21 Sep 2020 10:36:59 -0400 Subject: [PATCH 02/15] test: Add grants association test to role acceptance test --- ui/core/tests/acceptance/roles-test.js | 145 ++++++++++++++++++++----- 1 file changed, 118 insertions(+), 27 deletions(-) diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js index fb1eccf2b..734ab23fe 100644 --- a/ui/core/tests/acceptance/roles-test.js +++ b/ui/core/tests/acceptance/roles-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn, 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'; @@ -38,7 +38,7 @@ module('Acceptance | roles', function (hooks) { }); instances.role = this.server.create('role', { scope: instances.scopes.org, - }); + }, 'withPrincipals'); urls.roles = `/scopes/${instances.scopes.org.id}/roles`; urls.role = `${urls.roles}/${instances.role.id}`; urls.newRole = `${urls.roles}/new`; @@ -219,39 +219,130 @@ module('Acceptance | roles', function (hooks) { ); }); - test('principals may be viewed in principals tab', async function (assert) { - assert.expect(0); + module('principals', function(hooks) { + let principalsCount; + + hooks.beforeEach(function() { + principalsCount = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; + instances.addPrincipals = urls.addPrincipals = `${urls.role}/add-principals`; + urls.rolePrincipals = `${urls.role}/principals`; + this.server.create('user', { scope: instances.scopes.org }); + }); + + test('visiting role principals', async function (assert) { + assert.expect(1); + await visit(urls.rolePrincipals); + await a11yAudit(); + assert.equal(currentURL(), urls.rolePrincipals); + }); + + test('principal can be removed from a role', async function (assert) { + assert.expect(1); + await visit(urls.rolePrincipals); + await click('main tbody .rose-table-row:nth-child(1) .rose-table-cell:nth-child(4) .rose-dropdown-button-danger'); + const count = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; + assert.equal(count, principalsCount - 1); + }); + + test('select and save principals to add', async function (assert) { + assert.expect(1); + await visit(urls.addPrincipals); + await click('main tbody .rose-table-cell:nth-child(1) [type="checkbox"]'); + await click('form [type="submit"]:not(:disabled)'); + const count = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; + assert.equal(count, principalsCount + 1); + }); + + test('select and cancel principals to add', async function (assert) { + assert.expect(1); + await visit(urls.addPrincipals); + await click('main tbody .rose-table-cell:nth-child(1) [type="checkbox"]'); + await click('form button:not([type="submit"])'); + const count = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; + assert.equal(count, principalsCount); + }); }); - test('principal can be removed from a role', async function (assert) { - assert.expect(0); - }); + module('grants', function(hooks) { + let grantsCount; - test('grants can be updated and saved or canceled', async function (assert) { - assert.expect(0); - }); + hooks.beforeEach(function() { + grantsCount = this.server.db.roles[0].grant_strings.length; + urls.grants = `${urls.role}/grants`; + }); - test('grants can be removed and saved or canceled', async function (assert) { - assert.expect(0); - }); + test('visiting role grants', async function (assert) { + assert.expect(1); + await visit(urls.grants); + await a11yAudit(); + assert.equal(currentURL(), urls.grants); + }); - test('new grants can be added and saved or canceled', async function (assert) { - assert.expect(0); - }); + test('update a grant', async function(assert) { + assert.expect(1); + this.server.post('/roles/:id', (_, { requestBody }) => { + const attrs = JSON.parse(requestBody); + assert.equal(attrs.grant_strings[0], 'id=123,action=delete', "attributes don't match the expected ones"); + }); + await visit(urls.grants); + const grant = findAll('.rose-list-key-value:nth-child(1) [name="grant"]')[1]; + await fillIn(grant, 'id=123,action=delete'); + await click('form [type="submit"]:not(:disabled)'); + }); - test('viewing role principals', async function (assert) { - assert.expect(0); - }); + test('cancel a grant update', async function(assert) { + assert.expect(1); + await visit(urls.grants); + const grant = findAll('.rose-list-key-value:nth-child(1) [name="grant"]')[1]; + await fillIn(grant, 'id=123,action=delete'); + await click('form button:not([type="submit"])'); + const grantAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]')[1]; + assert.notEqual(grant.value, grantAfter.value); + }); - test('can delete a principal', async function (assert) { - assert.expect(0); - }); + test('create a grant', async function(assert) { + assert.expect(1); + this.server.post('/roles/:id', (_, { requestBody }) => { + const attrs = JSON.parse(requestBody); + assert.equal(attrs.grant_strings.length, grantsCount + 1, "grants don't match the expected ones"); + }); + await visit(urls.grants); + await fillIn('.rose-list-key-value:nth-child(1) [name="grant"]', 'id=123,action=delete'); + await click('.rose-list-key-value:nth-child(1) [type="submit"]:not(:disabled)'); + await click('form [type="submit"]:not(:disabled)'); + }); - test('select and save principals to add', async function (assert) { - assert.expect(0); - }); + test('cancel a grant creation', async function(assert) { + assert.expect(1); + await visit(urls.grants); + const grantsBefore = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; + await fillIn('.rose-list-key-value:nth-child(1) [name="grant"]', 'id=123,action=delete'); + await click('.rose-list-key-value:nth-child(1) [type="submit"]:not(:disabled)'); + await click('form button:not([type="submit"])'); + const grantsAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; + assert.equal(grantsBefore, grantsAfter); + }); - test('select and cancel principals to add', async function (assert) { - assert.expect(0); + test('delete a grant', async function(assert) { + assert.expect(1); + await visit(urls.grants); + const grant = findAll('.rose-list-key-value:nth-child(1) button:not([type="submit"])')[0]; + const grantsBefore = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; + await click(grant); + await click('form [type="submit"]:not(:disabled)'); + const grantsAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; + assert.equal(grantsBefore - 1, grantsAfter); + }); + + test('cancel a grant deletion', async function(assert) { + assert.expect(1); + await visit(urls.grants); + const grant = findAll('.rose-list-key-value:nth-child(1) button:not([type="submit"])')[0]; + const grantsBefore = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; + await click(grant); + await click('.rose-form-actions button:not([type="submit"])'); + const grantsAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; + assert.equal(grantsBefore, grantsAfter); + }); }); }); From f49df60639a8bea7706d598ca50893986ee58745 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 22 Sep 2020 09:28:38 -0400 Subject: [PATCH 03/15] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20placeholer?= =?UTF-8?q?=20auth-token=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/generated/models/auth-token.js | 8 ++++++++ addons/api/addon/models/auth-token.js | 3 +++ addons/api/app/models/auth-token.js | 1 + addons/api/tests/unit/models/auth-token-test.js | 13 +++++++++++++ 4 files changed, 25 insertions(+) create mode 100644 addons/api/addon/generated/models/auth-token.js create mode 100644 addons/api/addon/models/auth-token.js create mode 100644 addons/api/app/models/auth-token.js create mode 100644 addons/api/tests/unit/models/auth-token-test.js diff --git a/addons/api/addon/generated/models/auth-token.js b/addons/api/addon/generated/models/auth-token.js new file mode 100644 index 000000000..31ce7fd41 --- /dev/null +++ b/addons/api/addon/generated/models/auth-token.js @@ -0,0 +1,8 @@ +import BaseModel from '../../models/base'; + +/** + * NOTE: we don't yet use this model for display in the UI and thus no + * attributes are included at this time. This model is used only for + * validating that an auth token exists. + */ +export default class GeneratedAuthTokenModel extends BaseModel {} diff --git a/addons/api/addon/models/auth-token.js b/addons/api/addon/models/auth-token.js new file mode 100644 index 000000000..4ab39a0aa --- /dev/null +++ b/addons/api/addon/models/auth-token.js @@ -0,0 +1,3 @@ +import GeneratedAuthTokenModel from '../generated/models/auth-token'; + +export default class AuthTokenModel extends GeneratedAuthTokenModel {} diff --git a/addons/api/app/models/auth-token.js b/addons/api/app/models/auth-token.js new file mode 100644 index 000000000..8322bb3f1 --- /dev/null +++ b/addons/api/app/models/auth-token.js @@ -0,0 +1 @@ +export { default } from 'api/models/auth-token'; diff --git a/addons/api/tests/unit/models/auth-token-test.js b/addons/api/tests/unit/models/auth-token-test.js new file mode 100644 index 000000000..1f0826b86 --- /dev/null +++ b/addons/api/tests/unit/models/auth-token-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | auth token', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('auth-token', {}); + assert.ok(model); + }); +}); From 3a811f1865ae23dfd489a8be0b4a26a6a6a61840 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 22 Sep 2020 10:55:32 -0400 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20token=20vali?= =?UTF-8?q?dation=20step=20to=20authenticator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/auth/addon/authenticators/password.js | 26 ++++++++++-- .../dummy/app/authenticators/password.js | 4 ++ .../unit/authenticators/password-test.js | 41 +++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/addons/auth/addon/authenticators/password.js b/addons/auth/addon/authenticators/password.js index 67c8ce422..eb383d6c8 100644 --- a/addons/auth/addon/authenticators/password.js +++ b/addons/auth/addon/authenticators/password.js @@ -13,8 +13,14 @@ import fetch from 'fetch'; * Upon session invalidation, deauthentication is attempted at the URL generated * by `buildDeauthEndpointURL`, but is not guaranteed. * + * When a session is restored, a call is made to the endpoint specified by + * `buildTokenValidationEndpointURL`. If this endpoint responds with 401 or + * 404, then the session is invalid and the restoration is rejected. All other + * responses will resolve the session restoration successfully. + * * This authenticator should not be used directly because it does not specify - * an `buildAuthEndpointURL` or `buildDeauthEndpointURL` of its own. + * an `buildAuthEndpointURL`, `buildDeauthEndpointURL`, or + * `buildTokenValidationEndpointURL` of its own. * To use, generate an application authenticator in your app * `authenticators/application.js` and extend this class. * @@ -31,13 +37,25 @@ export default class PasswordAuthenticator extends BaseAuthenticator { // =methods /** - * Restores the session if data is present. Otherwise rejects. + * Restores the session if data is present and token validation succeeds + * (any response other than 401 or 404 === success). Otherwise rejects. * @override * @param {object} data * @return {Promise} */ - restore(data) { - return data ? resolve(data) : reject(); + async restore(data) { + if (!data) return reject(); + const tokenID = data.id; + const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID); + const response = await fetch(tokenValidationURL, { method: 'get' }); + // 401 and 404 responses mean the token is invalid, whereas other types of + // error responses do not tell us about the validity of the token. + switch (response.status) { + case 401: + case 404: + return reject(); + } + return resolve(data); } /** diff --git a/addons/auth/tests/dummy/app/authenticators/password.js b/addons/auth/tests/dummy/app/authenticators/password.js index 1953a8f5d..06d437263 100644 --- a/addons/auth/tests/dummy/app/authenticators/password.js +++ b/addons/auth/tests/dummy/app/authenticators/password.js @@ -1,6 +1,10 @@ import PasswordAuthenticator from 'auth/authenticators/password'; export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator { + buildTokenValidationEndpointURL(id) { + return `/validate/${id}`; + } + buildAuthEndpointURL() { return '/authenticate'; } diff --git a/addons/auth/tests/unit/authenticators/password-test.js b/addons/auth/tests/unit/authenticators/password-test.js index 873277469..040ce3758 100644 --- a/addons/auth/tests/unit/authenticators/password-test.js +++ b/addons/auth/tests/unit/authenticators/password-test.js @@ -96,4 +96,45 @@ module('Unit | Authenticator | password', function (hooks) { .then(() => assert.ok(true)) .catch(() => assert.notOk(true, 'uh oh, this should not happen')); }); + + test('it passes token validation on restoration if receiving 200', async function (assert) { + assert.expect(1); + const mockData = { id: 'token_123' }; + const authenticator = this.owner.lookup('authenticator:password'); + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + assert.ok(true, 'token validation was requested'); + return new Response(200); + }); + await authenticator.restore(mockData); + }); + + test('it fails token validation on restoration if receiving 401', async function (assert) { + assert.expect(2); + const mockData = { id: 'token_123' }; + const authenticator = this.owner.lookup('authenticator:password'); + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + assert.ok(true, 'token validation was requested'); + return new Response(401); + }); + try { + await authenticator.restore(mockData); + } catch (e) { + assert.ok(true, 'restoration failed'); + } + }); + + test('it fails token validation on restoration if receiving 404', async function (assert) { + assert.expect(2); + const mockData = { id: 'token_123' }; + const authenticator = this.owner.lookup('authenticator:password'); + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + assert.ok(true, 'token validation was requested'); + return new Response(404); + }); + try { + await authenticator.restore(mockData); + } catch (e) { + assert.ok(true, 'restoration failed'); + } + }); }); From 8e0e148558f6a717415554c75841b2eae1b91861 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 22 Sep 2020 11:03:30 -0400 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20token=20vali?= =?UTF-8?q?dation=20support=20to=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/core/app/authenticators/password.js | 11 +++++++++++ ui/core/mirage/config.js | 10 +++++++++- ui/core/tests/unit/authenticators/password-test.js | 7 ++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ui/core/app/authenticators/password.js b/ui/core/app/authenticators/password.js index 2d64dfc47..a0ceccdd9 100644 --- a/ui/core/app/authenticators/password.js +++ b/ui/core/app/authenticators/password.js @@ -43,6 +43,17 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator { return adapter.buildURL('scope', scopeID, options, 'findRecord'); } + /** + * Generates an auth token validation URL used to check tokens on restoration. + * @override + * @param {string} tokenID + * @return {string} + */ + buildTokenValidationEndpointURL(tokenID) { + const adapter = this.store.adapterFor('auth-token'); + return adapter.buildURL('auth-token', tokenID, {}, 'findRecord'); + } + /** * Intercepts the authenticate response, if any, and assigns the returned * token to all future requests via `addTokenToAuthorization`. diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 59cbf0180..3d332f121 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -1,7 +1,7 @@ import config from '../config/environment'; import { authHandler, deauthHandler } from './route-handlers/auth'; import { pickRandomStatusString } from './factories/session'; -//import { Response } from 'miragejs'; +import { Response } from 'miragejs'; export default function() { @@ -326,6 +326,14 @@ export default function() { return session.update(updatedAttrs); }); + // auth-tokens + + this.get('/auth-tokens/:id', function () { + // Set to 401 or 404 to simulate token invalidation, which will + // cause the session to fail restoration and logout the user. + return new Response(200); + }); + /* Uncomment the following line and the Response import above * Then change the response code to simulate error responses. * this.get('/scopes', () => new Response(505)); diff --git a/ui/core/tests/unit/authenticators/password-test.js b/ui/core/tests/unit/authenticators/password-test.js index 79996c8c0..0314aed17 100644 --- a/ui/core/tests/unit/authenticators/password-test.js +++ b/ui/core/tests/unit/authenticators/password-test.js @@ -1,6 +1,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { Response } from 'miragejs'; module('Unit | Authenticator | password', function (hooks) { setupTest(hooks); @@ -25,7 +26,11 @@ module('Unit | Authenticator | password', function (hooks) { test('it adds an authorization header to application adapter on restore', async function (assert) { assert.expect(1); - authenticator.restore({ token: 'token1234' }); + const mockData = { id: 'token1234', token: 'token1234' }; + this.server.get(authenticator.buildTokenValidationEndpointURL(mockData.id), () => { + return new Response(200); + }); + authenticator.restore(mockData); assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234'); }); From c014003d1c0b69149a38b84988103157827ce18c Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Tue, 22 Sep 2020 17:52:56 -0400 Subject: [PATCH 06/15] test: Refactor grants and principals tests in role test --- ui/core/tests/acceptance/roles-test.js | 283 ++++++++++++++++++------- 1 file changed, 212 insertions(+), 71 deletions(-) diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js index 734ab23fe..8759b0433 100644 --- a/ui/core/tests/acceptance/roles-test.js +++ b/ui/core/tests/acceptance/roles-test.js @@ -51,30 +51,12 @@ module('Acceptance | roles', function (hooks) { assert.equal(currentURL(), urls.roles); }); - test('visiting a role', async function (assert) { + test('can navigate to a role form', async function (assert) { assert.expect(1); - await visit(urls.newRole); + await visit(urls.roles); + await click('main tbody .rose-table-header-cell:nth-child(1) a'); await a11yAudit(); - assert.equal(currentURL(), urls.newRole); - }); - - test('can create new role', async function (assert) { - assert.expect(1); - const rolesCount = this.server.db.roles.length; - await visit(urls.newRole); - await fillIn('[name="name"]', 'role name'); - await click('[type="submit"]'); - assert.equal(this.server.db.roles.length, rolesCount + 1); - }); - - test('can cancel new role creation', async function (assert) { - assert.expect(2); - const rolesCount = this.server.db.roles.length; - await visit(urls.newRole); - await fillIn('[name="name"]', 'role name'); - await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), urls.roles); - assert.equal(this.server.db.roles.length, rolesCount); + assert.equal(currentURL(), urls.role); }); test('saving a new role with invalid fields displays error messages', async function (assert) { @@ -130,6 +112,25 @@ module('Acceptance | roles', function (hooks) { assert.notEqual(find('[name="name"]').value, 'Updated admin role'); }); + test('can create new role', async function (assert) { + assert.expect(1); + const rolesCount = this.server.db.roles.length; + await visit(urls.newRole); + await fillIn('[name="name"]', 'role name'); + await click('[type="submit"]'); + assert.equal(this.server.db.roles.length, rolesCount + 1); + }); + + test('can cancel new role creation', async function (assert) { + assert.expect(2); + const rolesCount = this.server.db.roles.length; + await visit(urls.newRole); + await fillIn('[name="name"]', 'role name'); + await click('.rose-form-actions [type="button"]'); + assert.equal(currentURL(), urls.roles); + assert.equal(this.server.db.roles.length, rolesCount); + }); + test('can delete a role', async function (assert) { assert.expect(1); const rolesCount = this.server.db.roles.length; @@ -138,6 +139,35 @@ module('Acceptance | roles', function (hooks) { assert.equal(this.server.db.roles.length, rolesCount - 1); }); + test('saving a new role with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/roles', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.newRole); + await fillIn('[name="name"]', 'new target'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok(find('[role="alert"]')); + assert.ok(find('.rose-form-error-message')); + }); + test('saving an existing role with invalid fields displays error messages', async function (assert) { assert.expect(2); this.server.patch('/roles/:id', () => { @@ -224,47 +254,100 @@ module('Acceptance | roles', function (hooks) { hooks.beforeEach(function() { principalsCount = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; - instances.addPrincipals = urls.addPrincipals = `${urls.role}/add-principals`; urls.rolePrincipals = `${urls.role}/principals`; - this.server.create('user', { scope: instances.scopes.org }); + urls.addPrincipals = `${urls.role}/add-principals`; }); test('visiting role principals', async function (assert) { - assert.expect(1); + assert.expect(2); await visit(urls.rolePrincipals); await a11yAudit(); assert.equal(currentURL(), urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); }); test('principal can be removed from a role', async function (assert) { - assert.expect(1); + assert.expect(2); await visit(urls.rolePrincipals); - await click('main tbody .rose-table-row:nth-child(1) .rose-table-cell:nth-child(4) .rose-dropdown-button-danger'); - const count = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; - assert.equal(count, principalsCount - 1); + assert.equal(findAll('tbody tr').length, principalsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, principalsCount - 1); + }); + + test('shows error message on principal remove', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]')); }); test('select and save principals to add', async function (assert) { - assert.expect(1); - await visit(urls.addPrincipals); - await click('main tbody .rose-table-cell:nth-child(1) [type="checkbox"]'); - await click('form [type="submit"]:not(:disabled)'); - const count = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; - assert.equal(count, principalsCount + 1); + assert.expect(3); + instances.role.update({ userIds: [], groupIds: [] }); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, 0); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addPrincipals); + await click('tbody label'); + await click('form [type="submit"]'); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, 1); }); test('select and cancel principals to add', async function (assert) { + assert.expect(4); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); + // Remove a principal to populate association view + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, principalsCount - 1); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addPrincipals); + await click('tbody label'); + await click('form [type="button"]'); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount - 1); + }); + + test('shows error message on principal add', async function (assert) { assert.expect(1); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + instances.role.update({ userIds: [], groupIds: [] }); await visit(urls.addPrincipals); - await click('main tbody .rose-table-cell:nth-child(1) [type="checkbox"]'); - await click('form button:not([type="submit"])'); - const count = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; - assert.equal(count, principalsCount); + await click('tbody label'); + await click('form [type="submit"]'); + assert.ok(find('[role="alert"]')); }); }); module('grants', function(hooks) { let grantsCount; + const newGrantForm = 'form:nth-child(1)', + grantsForm = 'form:nth-child(2)'; hooks.beforeEach(function() { grantsCount = this.server.db.roles[0].grant_strings.length; @@ -272,77 +355,135 @@ module('Acceptance | roles', function (hooks) { }); test('visiting role grants', async function (assert) { - assert.expect(1); + assert.expect(2); await visit(urls.grants); await a11yAudit(); assert.equal(currentURL(), urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); }); test('update a grant', async function(assert) { assert.expect(1); this.server.post('/roles/:id', (_, { requestBody }) => { const attrs = JSON.parse(requestBody); - assert.equal(attrs.grant_strings[0], 'id=123,action=delete', "attributes don't match the expected ones"); + assert.equal(attrs.grant_strings[0], 'id=123,action=delete', "A grant is updated"); }); await visit(urls.grants); - const grant = findAll('.rose-list-key-value:nth-child(1) [name="grant"]')[1]; - await fillIn(grant, 'id=123,action=delete'); - await click('form [type="submit"]:not(:disabled)'); + await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); }); test('cancel a grant update', async function(assert) { assert.expect(1); await visit(urls.grants); - const grant = findAll('.rose-list-key-value:nth-child(1) [name="grant"]')[1]; - await fillIn(grant, 'id=123,action=delete'); - await click('form button:not([type="submit"])'); - const grantAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]')[1]; - assert.notEqual(grant.value, grantAfter.value); + await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); + await click('.rose-form-actions button:not([type="submit"])'); + assert.notEqual(find(`${grantsForm} [name="grant"]`).value, 'id=123,action=delete'); + }); + + test('shows error message on grant update', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount); + await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.ok(find('[role="alert"]')); }); test('create a grant', async function(assert) { assert.expect(1); this.server.post('/roles/:id', (_, { requestBody }) => { const attrs = JSON.parse(requestBody); - assert.equal(attrs.grant_strings.length, grantsCount + 1, "grants don't match the expected ones"); + assert.equal(attrs.grant_strings.length, grantsCount + 1, "A grant is created"); }); await visit(urls.grants); - await fillIn('.rose-list-key-value:nth-child(1) [name="grant"]', 'id=123,action=delete'); - await click('.rose-list-key-value:nth-child(1) [type="submit"]:not(:disabled)'); - await click('form [type="submit"]:not(:disabled)'); + await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); + await click(`${newGrantForm} [type="submit"]:not(:disabled)`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); }); test('cancel a grant creation', async function(assert) { assert.expect(1); await visit(urls.grants); - const grantsBefore = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; - await fillIn('.rose-list-key-value:nth-child(1) [name="grant"]', 'id=123,action=delete'); - await click('.rose-list-key-value:nth-child(1) [type="submit"]:not(:disabled)'); - await click('form button:not([type="submit"])'); - const grantsAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; - assert.equal(grantsBefore, grantsAfter); + await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); + await click(`${newGrantForm} [type="submit"]:not(:disabled)`); + await click('.rose-form-actions button:not([type="submit"])'); + assert.notOk(find(`${newGrantForm} [name="grant"]`).value); + }); + + test('shows error message on grant create', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount); + await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); + await click(`${newGrantForm} [type="submit"]:not(:disabled)`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.ok(find('[role="alert"]')); }); test('delete a grant', async function(assert) { assert.expect(1); await visit(urls.grants); - const grant = findAll('.rose-list-key-value:nth-child(1) button:not([type="submit"])')[0]; - const grantsBefore = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; - await click(grant); - await click('form [type="submit"]:not(:disabled)'); - const grantsAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; - assert.equal(grantsBefore - 1, grantsAfter); + await click(`${grantsForm} button:not([type="submit"])`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount - 1); }); - test('cancel a grant deletion', async function(assert) { + test('cancel a grant remove', async function(assert) { assert.expect(1); await visit(urls.grants); - const grant = findAll('.rose-list-key-value:nth-child(1) button:not([type="submit"])')[0]; - const grantsBefore = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; - await click(grant); + await click(`${grantsForm} button`); await click('.rose-form-actions button:not([type="submit"])'); - const grantsAfter = findAll('.rose-list-key-value:nth-child(1) [name="grant"]').length; - assert.equal(grantsBefore, grantsAfter); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); + }); + + test('shows error message on grant remove', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount); + await click(`${grantsForm} button:not([type="submit"])`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.ok(find('[role="alert"]')); }); }); }); From 09937e43b7af30cbd2614cda3f0508d5190b6320 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 23 Sep 2020 12:16:31 -0400 Subject: [PATCH 07/15] refactor: Breakout role tests into role, principal, and grant --- ui/core/tests/acceptance/roles-test.js | 489 ------------------ ui/core/tests/acceptance/roles/grants-test.js | 182 +++++++ .../tests/acceptance/roles/principals-test.js | 134 +++++ ui/core/tests/acceptance/roles/roles-test.js | 251 +++++++++ 4 files changed, 567 insertions(+), 489 deletions(-) delete mode 100644 ui/core/tests/acceptance/roles-test.js create mode 100644 ui/core/tests/acceptance/roles/grants-test.js create mode 100644 ui/core/tests/acceptance/roles/principals-test.js create mode 100644 ui/core/tests/acceptance/roles/roles-test.js diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js deleted file mode 100644 index 8759b0433..000000000 --- a/ui/core/tests/acceptance/roles-test.js +++ /dev/null @@ -1,489 +0,0 @@ -import { module, test } from 'qunit'; -import { visit, currentURL, click, fillIn, 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 | roles', function (hooks) { - setupApplicationTest(hooks); - setupMirage(hooks); - - const instances = { - scopes: { - global: null, - org: null, - }, - role: null, - }; - const urls = { - orgScope: null, - roles: null, - role: null, - newRole: null, - }; - - 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.role = this.server.create('role', { - scope: instances.scopes.org, - }, 'withPrincipals'); - urls.roles = `/scopes/${instances.scopes.org.id}/roles`; - urls.role = `${urls.roles}/${instances.role.id}`; - urls.newRole = `${urls.roles}/new`; - }); - - test('visiting roles', async function (assert) { - assert.expect(1); - await visit(urls.roles); - await a11yAudit(); - assert.equal(currentURL(), urls.roles); - }); - - test('can navigate to a role form', async function (assert) { - assert.expect(1); - await visit(urls.roles); - await click('main tbody .rose-table-header-cell:nth-child(1) a'); - await a11yAudit(); - assert.equal(currentURL(), urls.role); - }); - - test('saving a new role with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.post('/roles', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: { - request_fields: [ - { - name: 'name', - description: 'Name is required.', - }, - ], - }, - } - ); - }); - await visit(urls.newRole); - await fillIn('[name="name"]', 'role name'); - await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'The request was invalid.', - 'Displays primary error message.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.', - 'Displays field-level errors.' - ); - }); - - test('can save changes to an existing role', async function (assert) { - assert.expect(2); - await visit(urls.role); - await fillIn('[name="name"]', 'Updated admin role'); - await click('.rose-form-actions [type="submit"]'); - assert.equal(currentURL(), urls.role); - assert.equal(this.server.db.roles[0].name, 'Updated admin role'); - }); - - test('can cancel changes to an existing role', async function (assert) { - assert.expect(1); - await visit(urls.role); - await fillIn('[name="name"]', 'Updated admin role'); - await click('.rose-form-actions [type="button"]'); - assert.notEqual(find('[name="name"]').value, 'Updated admin role'); - }); - - test('can create new role', async function (assert) { - assert.expect(1); - const rolesCount = this.server.db.roles.length; - await visit(urls.newRole); - await fillIn('[name="name"]', 'role name'); - await click('[type="submit"]'); - assert.equal(this.server.db.roles.length, rolesCount + 1); - }); - - test('can cancel new role creation', async function (assert) { - assert.expect(2); - const rolesCount = this.server.db.roles.length; - await visit(urls.newRole); - await fillIn('[name="name"]', 'role name'); - await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), urls.roles); - assert.equal(this.server.db.roles.length, rolesCount); - }); - - test('can delete a role', async function (assert) { - assert.expect(1); - const rolesCount = this.server.db.roles.length; - await visit(urls.role); - await click('.rose-layout-page-actions .rose-dropdown-button-danger'); - assert.equal(this.server.db.roles.length, rolesCount - 1); - }); - - test('saving a new role with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.post('/roles', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: { - request_fields: [ - { - name: 'name', - description: 'Name is required.', - }, - ], - }, - } - ); - }); - await visit(urls.newRole); - await fillIn('[name="name"]', 'new target'); - await click('form [type="submit"]'); - await a11yAudit(); - assert.ok(find('[role="alert"]')); - assert.ok(find('.rose-form-error-message')); - }); - - test('saving an existing role with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.patch('/roles/:id', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: { - request_fields: [ - { - name: 'name', - description: 'Name is required.', - }, - ], - }, - } - ); - }); - await visit(urls.role); - await fillIn('[name="name"]', 'random string'); - await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'The request was invalid.', - 'Displays primary error message.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.', - 'Displays field-level errors.' - ); - }); - - test('errors are displayed when save project fails', async function (assert) { - assert.expect(1); - this.server.patch('/roles/:id', () => { - return new Response( - 490, - {}, - { - status: 490, - code: 'error', - message: 'Oops.', - } - ); - }); - await visit(urls.role); - await fillIn('[name="name"]', 'Role name'); - await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Oops.', - 'Displays primary error message.' - ); - }); - - test('errors are displayed when delete project fails', async function (assert) { - assert.expect(1); - this.server.del('/roles/:id', () => { - return new Response( - 490, - {}, - { - status: 490, - code: 'error', - message: 'Oops.', - } - ); - }); - await visit(urls.role); - await click('.rose-layout-page-actions .rose-dropdown-button-danger'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Oops.', - 'Displays primary error message.' - ); - }); - - module('principals', function(hooks) { - let principalsCount; - - hooks.beforeEach(function() { - principalsCount = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; - urls.rolePrincipals = `${urls.role}/principals`; - urls.addPrincipals = `${urls.role}/add-principals`; - }); - - test('visiting role principals', async function (assert) { - assert.expect(2); - await visit(urls.rolePrincipals); - await a11yAudit(); - assert.equal(currentURL(), urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, principalsCount); - }); - - test('principal can be removed from a role', async function (assert) { - assert.expect(2); - await visit(urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, principalsCount); - await click('tbody tr .rose-dropdown-button-danger'); - assert.equal(findAll('tbody tr').length, principalsCount - 1); - }); - - test('shows error message on principal remove', async function (assert) { - assert.expect(2); - this.server.post('/roles/:idMethod', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: {}, - } - ); - }); - await visit(urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, principalsCount); - await click('tbody tr .rose-dropdown-button-danger'); - assert.ok(find('[role="alert"]')); - }); - - test('select and save principals to add', async function (assert) { - assert.expect(3); - instances.role.update({ userIds: [], groupIds: [] }); - await visit(urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, 0); - await click('.rose-layout-page-actions a') - assert.equal(currentURL(), urls.addPrincipals); - await click('tbody label'); - await click('form [type="submit"]'); - await visit(urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, 1); - }); - - test('select and cancel principals to add', async function (assert) { - assert.expect(4); - await visit(urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, principalsCount); - // Remove a principal to populate association view - await click('tbody tr .rose-dropdown-button-danger'); - assert.equal(findAll('tbody tr').length, principalsCount - 1); - await click('.rose-layout-page-actions a') - assert.equal(currentURL(), urls.addPrincipals); - await click('tbody label'); - await click('form [type="button"]'); - await visit(urls.rolePrincipals); - assert.equal(findAll('tbody tr').length, principalsCount - 1); - }); - - test('shows error message on principal add', async function (assert) { - assert.expect(1); - this.server.post('/roles/:idMethod', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: {}, - } - ); - }); - instances.role.update({ userIds: [], groupIds: [] }); - await visit(urls.addPrincipals); - await click('tbody label'); - await click('form [type="submit"]'); - assert.ok(find('[role="alert"]')); - }); - }); - - module('grants', function(hooks) { - let grantsCount; - const newGrantForm = 'form:nth-child(1)', - grantsForm = 'form:nth-child(2)'; - - hooks.beforeEach(function() { - grantsCount = this.server.db.roles[0].grant_strings.length; - urls.grants = `${urls.role}/grants`; - }); - - test('visiting role grants', async function (assert) { - assert.expect(2); - await visit(urls.grants); - await a11yAudit(); - assert.equal(currentURL(), urls.grants); - assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); - }); - - test('update a grant', async function(assert) { - assert.expect(1); - this.server.post('/roles/:id', (_, { requestBody }) => { - const attrs = JSON.parse(requestBody); - assert.equal(attrs.grant_strings[0], 'id=123,action=delete', "A grant is updated"); - }); - await visit(urls.grants); - await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); - await click('.rose-form-actions [type="submit"]:not(:disabled)'); - }); - - test('cancel a grant update', async function(assert) { - assert.expect(1); - await visit(urls.grants); - await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); - await click('.rose-form-actions button:not([type="submit"])'); - assert.notEqual(find(`${grantsForm} [name="grant"]`).value, 'id=123,action=delete'); - }); - - test('shows error message on grant update', async function (assert) { - assert.expect(2); - this.server.post('/roles/:idMethod', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: {}, - } - ); - }); - await visit(urls.grants); - assert.equal(findAll(`${grantsForm} [name="grant"]`).length, - grantsCount); - await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); - await click('.rose-form-actions [type="submit"]:not(:disabled)'); - assert.ok(find('[role="alert"]')); - }); - - test('create a grant', async function(assert) { - assert.expect(1); - this.server.post('/roles/:id', (_, { requestBody }) => { - const attrs = JSON.parse(requestBody); - assert.equal(attrs.grant_strings.length, grantsCount + 1, "A grant is created"); - }); - await visit(urls.grants); - await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); - await click(`${newGrantForm} [type="submit"]:not(:disabled)`); - await click('.rose-form-actions [type="submit"]:not(:disabled)'); - }); - - test('cancel a grant creation', async function(assert) { - assert.expect(1); - await visit(urls.grants); - await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); - await click(`${newGrantForm} [type="submit"]:not(:disabled)`); - await click('.rose-form-actions button:not([type="submit"])'); - assert.notOk(find(`${newGrantForm} [name="grant"]`).value); - }); - - test('shows error message on grant create', async function (assert) { - assert.expect(2); - this.server.post('/roles/:idMethod', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: {}, - } - ); - }); - await visit(urls.grants); - assert.equal(findAll(`${grantsForm} [name="grant"]`).length, - grantsCount); - await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); - await click(`${newGrantForm} [type="submit"]:not(:disabled)`); - await click('.rose-form-actions [type="submit"]:not(:disabled)'); - assert.ok(find('[role="alert"]')); - }); - - test('delete a grant', async function(assert) { - assert.expect(1); - await visit(urls.grants); - await click(`${grantsForm} button:not([type="submit"])`); - await click('.rose-form-actions [type="submit"]:not(:disabled)'); - assert.equal(findAll(`${grantsForm} [name="grant"]`).length, - grantsCount - 1); - }); - - test('cancel a grant remove', async function(assert) { - assert.expect(1); - await visit(urls.grants); - await click(`${grantsForm} button`); - await click('.rose-form-actions button:not([type="submit"])'); - assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); - }); - - test('shows error message on grant remove', async function (assert) { - assert.expect(2); - this.server.post('/roles/:idMethod', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: {}, - } - ); - }); - await visit(urls.grants); - assert.equal(findAll(`${grantsForm} [name="grant"]`).length, - grantsCount); - await click(`${grantsForm} button:not([type="submit"])`); - await click('.rose-form-actions [type="submit"]:not(:disabled)'); - assert.ok(find('[role="alert"]')); - }); - }); -}); diff --git a/ui/core/tests/acceptance/roles/grants-test.js b/ui/core/tests/acceptance/roles/grants-test.js new file mode 100644 index 000000000..e767928fb --- /dev/null +++ b/ui/core/tests/acceptance/roles/grants-test.js @@ -0,0 +1,182 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, click, fillIn, 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 | roles | grants', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + role: null, + }; + const urls = { + orgScope: null, + roles: null, + role: null, + newRole: null, + }; + const newGrantForm = 'form:nth-child(1)'; + const grantsForm = 'form:nth-child(2)'; + let grantsCount; + + 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.role = this.server.create('role', { + scope: instances.scopes.org, + }); + grantsCount = this.server.db.roles[0].grant_strings.length; + urls.roles = `/scopes/${instances.scopes.org.id}/roles`; + urls.role = `${urls.roles}/${instances.role.id}`; + urls.grants = `${urls.role}/grants`; + }); + + test('visiting role grants', async function (assert) { + assert.expect(2); + await visit(urls.grants); + await a11yAudit(); + assert.equal(currentURL(), urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); + }); + + test('update a grant', async function(assert) { + assert.expect(1); + this.server.post('/roles/:id', (_, { requestBody }) => { + const attrs = JSON.parse(requestBody); + assert.equal(attrs.grant_strings[0], 'id=123,action=delete', "A grant is updated"); + }); + await visit(urls.grants); + await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + }); + + test('cancel a grant update', async function(assert) { + assert.expect(1); + await visit(urls.grants); + await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); + await click('.rose-form-actions button:not([type="submit"])'); + assert.notEqual(find(`${grantsForm} [name="grant"]`).value, 'id=123,action=delete'); + }); + + test('shows error message on grant update', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount); + await fillIn(`${grantsForm} [name="grant"]`, 'id=123,action=delete'); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.ok(find('[role="alert"]')); + }); + + test('create a grant', async function(assert) { + assert.expect(1); + this.server.post('/roles/:id', (_, { requestBody }) => { + const attrs = JSON.parse(requestBody); + assert.equal(attrs.grant_strings.length, grantsCount + 1, "A grant is created"); + }); + await visit(urls.grants); + await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); + await click(`${newGrantForm} [type="submit"]:not(:disabled)`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + }); + + test('cancel a grant creation', async function(assert) { + assert.expect(1); + await visit(urls.grants); + await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); + await click(`${newGrantForm} [type="submit"]:not(:disabled)`); + await click('.rose-form-actions button:not([type="submit"])'); + assert.notOk(find(`${newGrantForm} [name="grant"]`).value); + }); + + test('shows error message on grant create', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount); + await fillIn(`${newGrantForm} [name="grant"]`, 'id=123,action=delete'); + await click(`${newGrantForm} [type="submit"]:not(:disabled)`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.ok(find('[role="alert"]')); + }); + + test('delete a grant', async function(assert) { + assert.expect(1); + await visit(urls.grants); + await click(`${grantsForm} button:not([type="submit"])`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount - 1); + }); + + test('cancel a grant remove', async function(assert) { + assert.expect(1); + await visit(urls.grants); + await click(`${grantsForm} button`); + await click('.rose-form-actions button:not([type="submit"])'); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); + }); + + test('shows error message on grant remove', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.grants); + assert.equal(findAll(`${grantsForm} [name="grant"]`).length, + grantsCount); + await click(`${grantsForm} button:not([type="submit"])`); + await click('.rose-form-actions [type="submit"]:not(:disabled)'); + assert.ok(find('[role="alert"]')); + }); +}); diff --git a/ui/core/tests/acceptance/roles/principals-test.js b/ui/core/tests/acceptance/roles/principals-test.js new file mode 100644 index 000000000..493a7a9f4 --- /dev/null +++ b/ui/core/tests/acceptance/roles/principals-test.js @@ -0,0 +1,134 @@ +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 | roles | principals', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + role: null, + }; + const urls = { + orgScope: null, + roles: null, + role: null, + newRole: null, + }; + let principalsCount; + + 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.role = this.server.create('role', { + scope: instances.scopes.org, + }, 'withPrincipals'); + principalsCount = this.server.db.roles[0].userIds.length + this.server.db.roles[0].groupIds.length; + urls.roles = `/scopes/${instances.scopes.org.id}/roles`; + urls.role = `${urls.roles}/${instances.role.id}`; + urls.rolePrincipals = `${urls.role}/principals`; + urls.addPrincipals = `${urls.role}/add-principals`; + }); + + test('visiting role principals', async function (assert) { + assert.expect(2); + await visit(urls.rolePrincipals); + await a11yAudit(); + assert.equal(currentURL(), urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); + }); + + test('principal can be removed from a role', async function (assert) { + assert.expect(2); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, principalsCount - 1); + }); + + test('shows error message on principal remove', async function (assert) { + assert.expect(2); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); + await click('tbody tr .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]')); + }); + + test('select and save principals to add', async function (assert) { + assert.expect(3); + instances.role.update({ userIds: [], groupIds: [] }); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, 0); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addPrincipals); + await click('tbody label'); + await click('form [type="submit"]'); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, 1); + }); + + test('select and cancel principals to add', async function (assert) { + assert.expect(4); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount); + // Remove a principal to populate association view + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, principalsCount - 1); + await click('.rose-layout-page-actions a') + assert.equal(currentURL(), urls.addPrincipals); + await click('tbody label'); + await click('form [type="button"]'); + await visit(urls.rolePrincipals); + assert.equal(findAll('tbody tr').length, principalsCount - 1); + }); + + test('shows error message on principal add', async function (assert) { + assert.expect(1); + this.server.post('/roles/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + instances.role.update({ userIds: [], groupIds: [] }); + await visit(urls.addPrincipals); + await click('tbody label'); + await click('form [type="submit"]'); + assert.ok(find('[role="alert"]')); + }); +}); diff --git a/ui/core/tests/acceptance/roles/roles-test.js b/ui/core/tests/acceptance/roles/roles-test.js new file mode 100644 index 000000000..a9212b06f --- /dev/null +++ b/ui/core/tests/acceptance/roles/roles-test.js @@ -0,0 +1,251 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, click, fillIn, find } 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 | roles', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + role: null, + }; + const urls = { + orgScope: null, + roles: null, + role: null, + newRole: null, + }; + + 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.role = this.server.create('role', { + scope: instances.scopes.org, + }, 'withPrincipals'); + urls.roles = `/scopes/${instances.scopes.org.id}/roles`; + urls.role = `${urls.roles}/${instances.role.id}`; + urls.newRole = `${urls.roles}/new`; + }); + + test('visiting roles', async function (assert) { + assert.expect(1); + await visit(urls.roles); + await a11yAudit(); + assert.equal(currentURL(), urls.roles); + }); + + test('can navigate to a role form', async function (assert) { + assert.expect(1); + await visit(urls.roles); + await click('main tbody .rose-table-header-cell:nth-child(1) a'); + await a11yAudit(); + assert.equal(currentURL(), urls.role); + }); + + test('saving a new role with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/roles', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.newRole); + await fillIn('[name="name"]', 'role name'); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.', + 'Displays primary error message.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.', + 'Displays field-level errors.' + ); + }); + + test('can save changes to an existing role', async function (assert) { + assert.expect(2); + await visit(urls.role); + await fillIn('[name="name"]', 'Updated admin role'); + await click('.rose-form-actions [type="submit"]'); + assert.equal(currentURL(), urls.role); + assert.equal(this.server.db.roles[0].name, 'Updated admin role'); + }); + + test('can cancel changes to an existing role', async function (assert) { + assert.expect(1); + await visit(urls.role); + await fillIn('[name="name"]', 'Updated admin role'); + await click('.rose-form-actions [type="button"]'); + assert.notEqual(find('[name="name"]').value, 'Updated admin role'); + }); + + test('can create new role', async function (assert) { + assert.expect(1); + const rolesCount = this.server.db.roles.length; + await visit(urls.newRole); + await fillIn('[name="name"]', 'role name'); + await click('[type="submit"]'); + assert.equal(this.server.db.roles.length, rolesCount + 1); + }); + + test('can cancel new role creation', async function (assert) { + assert.expect(2); + const rolesCount = this.server.db.roles.length; + await visit(urls.newRole); + await fillIn('[name="name"]', 'role name'); + await click('.rose-form-actions [type="button"]'); + assert.equal(currentURL(), urls.roles); + assert.equal(this.server.db.roles.length, rolesCount); + }); + + test('can delete a role', async function (assert) { + assert.expect(1); + const rolesCount = this.server.db.roles.length; + await visit(urls.role); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.equal(this.server.db.roles.length, rolesCount - 1); + }); + + test('saving a new role with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/roles', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.newRole); + await fillIn('[name="name"]', 'new target'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok(find('[role="alert"]')); + assert.ok(find('.rose-form-error-message')); + }); + + test('saving an existing role with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.patch('/roles/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.role); + await fillIn('[name="name"]', 'random string'); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'The request was invalid.', + 'Displays primary error message.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.', + 'Displays field-level errors.' + ); + }); + + test('errors are displayed when save project fails', async function (assert) { + assert.expect(1); + this.server.patch('/roles/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.role); + await fillIn('[name="name"]', 'Role name'); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Oops.', + 'Displays primary error message.' + ); + }); + + test('errors are displayed when delete project fails', async function (assert) { + assert.expect(1); + this.server.del('/roles/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.role); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Oops.', + 'Displays primary error message.' + ); + }); +}); From fbe04de148dfaf87895a9e3819e85bf049615931 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 23 Sep 2020 12:20:15 -0400 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20convert=20form?= =?UTF-8?q?=20to=20nested=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/rose/addon/components/rose/form.js | 27 ------------------- .../rose/form/index.hbs} | 0 .../rose/addon/components/rose/form/index.js | 21 +++++++++++++++ .../index.stories.mdx} | 0 4 files changed, 21 insertions(+), 27 deletions(-) delete mode 100644 addons/rose/addon/components/rose/form.js rename addons/rose/addon/{templates/components/rose/form.hbs => components/rose/form/index.hbs} (100%) create mode 100644 addons/rose/addon/components/rose/form/index.js rename addons/rose/addon/components/rose/{form.stories.mdx => form/index.stories.mdx} (100%) 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/templates/components/rose/form.hbs b/addons/rose/addon/components/rose/form/index.hbs similarity index 100% rename from addons/rose/addon/templates/components/rose/form.hbs rename to addons/rose/addon/components/rose/form/index.hbs 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..24bb8e0c2 --- /dev/null +++ b/addons/rose/addon/components/rose/form/index.js @@ -0,0 +1,21 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; + +export default class RoseFormComponent extends Component { + + // =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; + } + +} diff --git a/addons/rose/addon/components/rose/form.stories.mdx b/addons/rose/addon/components/rose/form/index.stories.mdx similarity index 100% rename from addons/rose/addon/components/rose/form.stories.mdx rename to addons/rose/addon/components/rose/form/index.stories.mdx From 296580e604bf845a0cddbcc3696ce6e0a2241b8b Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 23 Sep 2020 12:53:09 -0400 Subject: [PATCH 09/15] refactor: Move roles acceptance test to root folder --- ui/core/tests/acceptance/{roles => }/roles-test.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/core/tests/acceptance/{roles => }/roles-test.js (100%) diff --git a/ui/core/tests/acceptance/roles/roles-test.js b/ui/core/tests/acceptance/roles-test.js similarity index 100% rename from ui/core/tests/acceptance/roles/roles-test.js rename to ui/core/tests/acceptance/roles-test.js From c069247356e988d3f569f1506843229fd6d3652f Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 23 Sep 2020 13:27:50 -0400 Subject: [PATCH 10/15] test: Add group members acceptance test --- ui/core/tests/acceptance/groups-test.js | 90 +++++------ .../tests/acceptance/groups/members-test.js | 141 ++++++++++++++++++ 2 files changed, 180 insertions(+), 51 deletions(-) create mode 100644 ui/core/tests/acceptance/groups/members-test.js 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..63ace2b73 --- /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 delete 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"]')); + }); +}); From 8811b47cb28cd388d6e55172053067388d2205f9 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 23 Sep 2020 13:29:45 -0400 Subject: [PATCH 11/15] refactor: Remove double comments in member test --- ui/core/tests/acceptance/groups/members-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/core/tests/acceptance/groups/members-test.js b/ui/core/tests/acceptance/groups/members-test.js index 63ace2b73..ec5a577bc 100644 --- a/ui/core/tests/acceptance/groups/members-test.js +++ b/ui/core/tests/acceptance/groups/members-test.js @@ -6,9 +6,9 @@ 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, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, } from 'ember-simple-auth/test-support'; module('Acceptance | groups | members', function (hooks) { From f1514da252d08cdac69fa40c8337c58323bbe6a4 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 23 Sep 2020 16:41:29 -0400 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20support=20fo?= =?UTF-8?q?r=20showEditToggle=20to=20form=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rose/form/actions/edit-toggle/index.hbs | 8 +++ .../rose/addon/components/rose/form/index.hbs | 25 ++++++-- .../rose/addon/components/rose/form/index.js | 60 ++++++++++++++++++- .../components/rose/form/index.stories.mdx | 15 ++++- .../components/rose/form/actions.hbs | 4 +- .../rose/form/actions/edit-toggle.js | 1 + .../integration/components/rose/form-test.js | 40 ++++++++++++- .../components/rose/form/actions-test.js | 4 +- .../rose/form/actions/edit-toggle-test.js | 22 +++++++ 9 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 addons/rose/addon/components/rose/form/actions/edit-toggle/index.hbs create mode 100644 addons/rose/app/components/rose/form/actions/edit-toggle.js create mode 100644 addons/rose/tests/integration/components/rose/form/actions/edit-toggle-test.js 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/components/rose/form/index.hbs b/addons/rose/addon/components/rose/form/index.hbs index f37f36d09..045ad70c4 100644 --- a/addons/rose/addon/components/rose/form/index.hbs +++ b/addons/rose/addon/components/rose/form/index.hbs @@ -6,12 +6,25 @@ autocomplete="off" {{on "submit" this.handleSubmit}} > + {{!-- + 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=@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) + 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 index 24bb8e0c2..249cd9fbd 100644 --- a/addons/rose/addon/components/rose/form/index.js +++ b/addons/rose/addon/components/rose/form/index.js @@ -1,8 +1,40 @@ import Component from '@glimmer/component'; -import { action } from '@ember/object'; +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 /** @@ -18,4 +50,30 @@ export default class RoseFormComponent extends Component { 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/index.stories.mdx b/addons/rose/addon/components/rose/form/index.stories.mdx index f57f85da2..f3498eaa2 100644 --- a/addons/rose/addon/components/rose/form/index.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/actions.hbs b/addons/rose/addon/templates/components/rose/form/actions.hbs index 47557fb7e..184f36893 100644 --- a/addons/rose/addon/templates/components/rose/form/actions.hbs +++ b/addons/rose/addon/templates/components/rose/form/actions.hbs @@ -1,6 +1,6 @@
@@ -9,7 +9,7 @@ {{#if this.showCancel}} 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'); + }); +}); From 7901f561d85bd82406762f7259bd41ac895e571a Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 23 Sep 2020 16:59:13 -0400 Subject: [PATCH 13/15] test: Remove duplicated new role test --- ui/core/tests/acceptance/roles-test.js | 36 -------------------------- 1 file changed, 36 deletions(-) diff --git a/ui/core/tests/acceptance/roles-test.js b/ui/core/tests/acceptance/roles-test.js index a9212b06f..eee3ffa50 100644 --- a/ui/core/tests/acceptance/roles-test.js +++ b/ui/core/tests/acceptance/roles-test.js @@ -59,42 +59,6 @@ module('Acceptance | roles', function (hooks) { assert.equal(currentURL(), urls.role); }); - test('saving a new role with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.post('/roles', () => { - return new Response( - 400, - {}, - { - status: 400, - code: 'invalid_argument', - message: 'The request was invalid.', - details: { - request_fields: [ - { - name: 'name', - description: 'Name is required.', - }, - ], - }, - } - ); - }); - await visit(urls.newRole); - await fillIn('[name="name"]', 'role name'); - await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'The request was invalid.', - 'Displays primary error message.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.', - 'Displays field-level errors.' - ); - }); - test('can save changes to an existing role', async function (assert) { assert.expect(2); await visit(urls.role); From dbdd1f982aa100f1f0df39fc9ca0c681756cac0b Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 23 Sep 2020 17:13:49 -0400 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20convert=20form?= =?UTF-8?q?=20actions=20component=20to=20nested=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rose/form/actions/index.hbs} | 0 .../rose/form/{actions.js => actions/index.js} | 15 ++++++++------- .../index.stories.mdx} | 0 3 files changed, 8 insertions(+), 7 deletions(-) rename addons/rose/addon/{templates/components/rose/form/actions.hbs => components/rose/form/actions/index.hbs} (100%) rename addons/rose/addon/components/rose/form/{actions.js => actions/index.js} (60%) rename addons/rose/addon/components/rose/form/{actions.stories.mdx => actions/index.stories.mdx} (100%) diff --git a/addons/rose/addon/templates/components/rose/form/actions.hbs b/addons/rose/addon/components/rose/form/actions/index.hbs similarity index 100% rename from addons/rose/addon/templates/components/rose/form/actions.hbs rename to addons/rose/addon/components/rose/form/actions/index.hbs 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 From e50004772fea151ef82d5855e36838efb72f3d21 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 24 Sep 2020 11:53:19 -0400 Subject: [PATCH 15/15] refactor: Refactor 'delete' of a member to 'remove' --- ui/core/tests/acceptance/groups/members-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/tests/acceptance/groups/members-test.js b/ui/core/tests/acceptance/groups/members-test.js index ec5a577bc..69edf67df 100644 --- a/ui/core/tests/acceptance/groups/members-test.js +++ b/ui/core/tests/acceptance/groups/members-test.js @@ -56,7 +56,7 @@ module('Acceptance | groups | members', function (hooks) { assert.equal(findAll('tbody tr').length, membersCount); }); - test('can delete a member', async function (assert) { + test('can remove a member', async function (assert) { assert.expect(2); await visit(urls.members); assert.equal(findAll('tbody tr').length, membersCount);