diff --git a/addons/api/mirage/factories/role.js b/addons/api/mirage/factories/role.js index 7b1536a21..7813730e6 100644 --- a/addons/api/mirage/factories/role.js +++ b/addons/api/mirage/factories/role.js @@ -7,6 +7,11 @@ import factory from '../generated/factories/role'; import { trait } from 'miragejs'; import permissions from '../helpers/permissions'; import generateId from '../helpers/id'; +import { + TYPE_SCOPE_GLOBAL, + TYPE_SCOPE_ORG, + TYPE_SCOPE_PROJECT, +} from 'api/models/scope'; export default factory.extend({ authorized_actions: () => @@ -33,9 +38,19 @@ export default factory.extend({ * Adds grant scopes to the role. */ withScopes: trait({ - afterCreate(role) { + afterCreate(role, server) { const { scope } = role; - role.update({ grant_scope_ids: ['this', scope.id] }); + const newScope = + scope.id === 'global' + ? server.create('scope', { + scope: { id: 'global', type: TYPE_SCOPE_GLOBAL }, + type: 'org', + }) + : server.create('scope', { + scope: { id: scope.id, type: TYPE_SCOPE_ORG }, + type: TYPE_SCOPE_PROJECT, + }); + role.update({ grant_scope_ids: ['this', newScope.id] }); }, }), diff --git a/ui/admin/app/components/form/role/manage-scopes/index.hbs b/ui/admin/app/components/form/role/manage-scopes/index.hbs index 2842cd267..2151fe728 100644 --- a/ui/admin/app/components/form/role/manage-scopes/index.hbs +++ b/ui/admin/app/components/form/role/manage-scopes/index.hbs @@ -67,11 +67,10 @@ {{/if}} - {{! TODO: Add correct route string once route is created}} - {{! TODO: Add check-circle icon if custom scopes were added}} { + if (!uniqueParentScopeIds.includes(scope.scope.id)) { + uniqueParentScopeIds.push(scope.scope.id); + const item = scope.scope.name + ? { id: scope.scope.id, name: scope.scope.name } + : { id: scope.scope.id }; + availableParentScopes.push(item); + } + }); + return availableParentScopes; + } + + /** + * Returns scope type options. + * @type {object} + */ + get scopeTypeOptions() { + return [ + { + id: TYPE_SCOPE_ORG, + name: this.intl.t(`resources.scope.types.${TYPE_SCOPE_ORG}`), + }, + { + id: TYPE_SCOPE_PROJECT, + name: this.intl.t(`resources.scope.types.${TYPE_SCOPE_PROJECT}`), + }, + ]; + } + + /** + * Returns the filters object used for displaying filter tags. + * @type {object} + */ + get filters() { + return { + allFilters: { + parentScopes: this.availableParentScopes, + types: this.scopeTypeOptions, + }, + selectedFilters: { + parentScopes: this.parentScopes, + types: this.types, + }, + }; + } + + // =methods + isKeywordThis(id) { return id === GRANT_SCOPE_THIS; } @@ -20,4 +100,30 @@ export default class ScopesScopeRolesRoleScopesController extends Controller { isKeywordChildrenOrDescendants(id) { return id === GRANT_SCOPE_CHILDREN || id === GRANT_SCOPE_DESCENDANTS; } + + // =actions + + /** + * Handles input on each keystroke and the search queryParam + * @param {object} event + */ + @action + @debounce(250) + handleSearchInput(event) { + const { value } = event.target; + this.search = value; + this.page = 1; + } + + /** + * Sets a query param to the value of selectedItems + * and resets the page to 1. + * @param {string} paramKey + * @param {[string]} selectedItems + */ + @action + applyFilter(paramKey, selectedItems) { + this[paramKey] = [...selectedItems]; + this.page = 1; + } } diff --git a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes.js b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes.js index c5cb3cb01..b8800bb02 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes.js @@ -5,10 +5,4 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeRolesRoleManageScopesRoute extends Route { - // =methods - - model() { - return this.modelFor('scopes.scope.roles.role'); - } -} +export default class ScopesScopeRolesRoleManageScopesRoute extends Route {} diff --git a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/index.js b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/index.js index 1536b9229..20aa61f8a 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/index.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/index.js @@ -5,4 +5,17 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeRolesRoleManageScopesIndexRoute extends Route {} +export default class ScopesScopeRolesRoleManageScopesIndexRoute extends Route { + // =methods + + /** + * Sets showCheckIcon queryParam to default value when exiting this route. + * @param {Controller} controller + * @param {boolean} isExiting + */ + resetController(controller, isExiting) { + if (isExiting) { + controller.set('showCheckIcon', false); + } + } +} diff --git a/ui/admin/app/routes/scopes/scope/roles/role/scopes.js b/ui/admin/app/routes/scopes/scope/roles/role/scopes.js index ded02916a..64c0c82c1 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/scopes.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/scopes.js @@ -4,11 +4,96 @@ */ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; export default class ScopesScopeRolesRoleScopesRoute extends Route { + // =services + + @service store; + + // =attributes + + queryParams = { + search: { + refreshModel: true, + replace: true, + }, + parentScopes: { + refreshModel: true, + replace: true, + }, + types: { + refreshModel: true, + replace: true, + }, + page: { + refreshModel: true, + }, + pageSize: { + refreshModel: true, + }, + }; + // =methods - model() { - return this.modelFor('scopes.scope.roles.role'); + /** + * Loads grant scopes for current role. + * @return {Promise<{role: RoleModel, grantScopes: [ScopeModel], allGrantScopes: [ScopeModel], totalItems: number}> } + */ + async model({ search, parentScopes, types, page, pageSize }) { + const role = this.modelFor('scopes.scope.roles.role'); + const filters = { + scope_id: [], + type: [], + id: [], + }; + role.grant_scope_ids.forEach((id) => { + if (id.startsWith('p_') || id.startsWith('o_')) { + filters.id.push({ equals: id }); + } + }); + + let grantScopes = []; + if (!parentScopes.length && !types.length) { + role.grantScopeKeywords.forEach((keyword) => { + if (keyword.includes(search)) { + grantScopes.push({ id: keyword }); + } + }); + } + + let allGrantScopes = []; + let totalItems = grantScopes.length; + if (filters.id.length) { + parentScopes.forEach((id) => filters.scope_id.push({ equals: id })); + types.forEach((type) => filters.type.push({ equals: type })); + + allGrantScopes = await this.getAllGrantScopes(filters.id); + + const queriedScopes = await this.store.query('scope', { + scope_id: 'global', + query: { search, filters }, + page, + pageSize: pageSize - totalItems, + }); + grantScopes = + page === 1 ? [...grantScopes, ...queriedScopes] : queriedScopes; + totalItems += queriedScopes.meta?.totalItems; + } + return { role, grantScopes, allGrantScopes, totalItems }; + } + + /** + * Get all the grant scopes but only load them once when entering the route. + * @param {[object]} id + * @returns {Promise<[ScopeModel]>} + */ + async getAllGrantScopes(id) { + const options = { pushToStore: false, peekIndexedDb: true }; + return this.store.query( + 'scope', + { scope_id: 'global', query: { filters: { id } } }, + options, + ); } } diff --git a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs index c9870bad5..554b351e8 100644 --- a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs +++ b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs @@ -20,7 +20,7 @@ diff --git a/ui/admin/app/templates/scopes/scope/roles/role/scopes.hbs b/ui/admin/app/templates/scopes/scope/roles/role/scopes.hbs index 9477db3e6..4684c7bea 100644 --- a/ui/admin/app/templates/scopes/scope/roles/role/scopes.hbs +++ b/ui/admin/app/templates/scopes/scope/roles/role/scopes.hbs @@ -21,137 +21,190 @@

- {{#if @model.scope.isOrg}} + {{#if @model.role.scope.isOrg}} {{t 'resources.role.scope.description.org'}} - {{else if @model.scope.isProject}} + {{else if @model.role.scope.isProject}} {{t 'resources.role.scope.description.project'}} {{else}} {{t 'resources.role.scope.description.global'}} {{/if}}

- + - + - + - - {{#if @model.grant_scope_ids}} - - <:body as |B|> - - - {{#if (this.isKeywordThis B.data.id)}} - - {{#if @model.scope.name}} - {{@model.scope.name}} - {{else}} - {{@model.scope.id}} - {{/if}} - - - - - {{else if (this.isKeywordChildrenOrDescendants B.data.id)}} - - {{t - (concat 'resources.role.scope.keywords.' B.data.id '.title') - }} - - - {{t - (concat - 'resources.role.scope.keywords.' B.data.id '.sub-title' - ) - }} - - {{else}} - {{#if (can 'read model' B.data)}} - - {{#if B.data.isGlobal}} - {{t 'titles.global'}} + + {{#if @model.role.grant_scope_ids}} + + + {{#if @model.role.scope.isGlobal}} + + + + + + + {{/if}} + + + {{#if @model.role.scope.isGlobal}} + + {{/if}} + + {{#if @model.grantScopes}} + + <:body as |B|> + + + {{#if (this.isKeywordThis B.data.id)}} + + {{#if @model.role.scope.name}} + {{@model.role.scope.name}} {{else}} - {{B.data.displayName}} + {{@model.role.scope.id}} {{/if}} - + + + + + {{else if (this.isKeywordChildrenOrDescendants B.data.id)}} + + {{t + (concat + 'resources.role.scope.keywords.' B.data.id '.title' + ) + }} + + + {{t + (concat + 'resources.role.scope.keywords.' B.data.id '.sub-title' + ) + }} + {{else}} - {{B.data.displayName}} - {{/if}} - {{/if}} - - - - - - {{#if B.data.scope.isGlobal}} - - {{#if B.data.scope.name}} - {{t 'titles.global'}} + {{#if (can 'read model' B.data)}} + + {{#if B.data.isGlobal}} + {{t 'titles.global'}} + {{else}} + {{B.data.displayName}} + {{/if}} + {{else}} - {{B.data.scope.id}} + {{B.data.displayName}} {{/if}} - - {{else if B.data.scope.isOrg}} - - {{#if B.data.scope.name}} - {{B.data.scope.name}} - {{else}} - {{B.data.scope.id}} - {{/if}} - - {{else}} - {{#if B.data.scope.name}} - {{B.data.scope.name}} - {{else}} - {{B.data.scope.id}} {{/if}} - {{/if}} - - - {{#if B.data.type}} - + + - {{/if}} - - - - + + + {{#if B.data.scope.isGlobal}} + + {{~if + B.data.scope.name + (t 'titles.global') + B.data.scope.id + }} + + {{else if B.data.scope.isOrg}} + + {{~if B.data.scope.name B.data.scope.name B.data.scope.id}} + + {{/if}} + + + {{#if B.data.type}} + + {{/if}} + + + + + + {{else}} + + + + + {{/if}} {{else}} - {{#if (can 'setGrantScopes role' @model)}} + {{#if (can 'setGrantScopes role' @model.role)}} `tbody [data-test-${type}-scopes-table-row="${id}"] input`; + const GRANT_SCOPE_ROW_SELECTOR = (id) => + `tbody [data-test-grant-scope-row="${id}"]`; + const FILTER_DROPDOWN_SELECTOR = (name) => + `.search-filtering [name="${name}"] button`; + const FILTER_APPLY_BUTTON_SELECTOR = + '.search-filtering [data-test-dropdown-apply-button]'; const TABLE_ROW_SELECTOR = 'tbody tr'; const TABLE_SCOPE_SELECTOR = 'tbody tr:nth-child(2) a'; const TABLE_PARENT_SCOPE_SELECTOR = 'tbody tr:nth-child(2) td:nth-child(3) a'; @@ -45,6 +52,8 @@ module('Acceptance | roles | global-scope', function (hooks) { const NO_SCOPES_MSG_SELECTOR = '.role-grant-scopes div'; const NO_SCOPES_MSG_LINK_SELECTOR = '.role-grant-scopes div div:nth-child(3) a'; + const BUTTON_ICON_SELECTOR = + '.hds-button__icon [data-test-icon="check-circle"]'; const instances = { scopes: { @@ -109,10 +118,109 @@ module('Acceptance | roles | global-scope', function (hooks) { assert.strictEqual( currentURL(), - `/scopes/${instances.scopes.global.id}/scopes`, + `/scopes/${instances.role.grant_scope_ids[1]}/scopes`, ); }); + test('user can search for existing grant scope on a role', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + + await fillIn(SEARCH_INPUT_SELECTOR, instances.role.grant_scope_ids[0]); + await waitUntil( + () => + findAll(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .length === 0, + ); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .doesNotExist(); + }); + + test('user can search for grant scopes on a role and get no results', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + + await fillIn(SEARCH_INPUT_SELECTOR, 'fake scope that does not exist'); + await waitUntil(() => findAll(NO_RESULTS_MSG_SELECTOR).length === 1); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .doesNotExist(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .doesNotExist(); + assert.dom(NO_RESULTS_MSG_SELECTOR).includesText('No results found'); + }); + + test('user can filter for grant scopes on a role by parent scope', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + + await click(FILTER_DROPDOWN_SELECTOR('parent-scope')); + await click(`input[value="${instances.scopes.global.id}"]`); + await click(FILTER_APPLY_BUTTON_SELECTOR); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .doesNotExist(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + }); + + test('user can filter for grant scopes on a role by type', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + + await click(FILTER_DROPDOWN_SELECTOR('type')); + await click(`input[value="${TYPE_SCOPE_ORG}"]`); + await click(FILTER_APPLY_BUTTON_SELECTOR); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .doesNotExist(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + }); + test('user can naviage to parent scope from role grant scopes', async function (assert) { instances.role.update({ grant_scope_ids: ['this', instances.scopes.org.id], @@ -260,6 +368,7 @@ module('Acceptance | roles | global-scope', function (hooks) { await click(SAVE_BTN_SELECTOR); assert.strictEqual(currentURL(), urls.manageScopes); + assert.dom(BUTTON_ICON_SELECTOR).isVisible(); await click(SAVE_BTN_SELECTOR); @@ -403,6 +512,7 @@ module('Acceptance | roles | global-scope', function (hooks) { await click(SAVE_BTN_SELECTOR); assert.strictEqual(currentURL(), urls.manageScopes); + assert.dom(BUTTON_ICON_SELECTOR).isVisible(); await click(SAVE_BTN_SELECTOR); diff --git a/ui/admin/tests/acceptance/roles/org-scope-test.js b/ui/admin/tests/acceptance/roles/org-scope-test.js index ce992464b..98fc49bd6 100644 --- a/ui/admin/tests/acceptance/roles/org-scope-test.js +++ b/ui/admin/tests/acceptance/roles/org-scope-test.js @@ -32,6 +32,10 @@ module('Acceptance | roles | org-scope', function (hooks) { `.hds-form-toggle input[name="${name}"]`; const SCOPE_CHECKBOX_SELECTOR = (type, id) => `tbody [data-test-${type}-scopes-table-row="${id}"] input`; + const GRANT_SCOPE_ROW_SELECTOR = (id) => + `tbody [data-test-grant-scope-row="${id}"]`; + const FILTER_DROPDOWN_SELECTOR = (name) => + `.search-filtering [name="${name}"] button`; const TABLE_ROW_SELECTOR = 'tbody tr'; const TABLE_SCOPE_SELECTOR = 'tbody tr:nth-child(2) a'; const TABLE_PARENT_SCOPE_SELECTOR = 'tbody tr:nth-child(2) td:nth-child(3) a'; @@ -45,6 +49,8 @@ module('Acceptance | roles | org-scope', function (hooks) { const NO_SCOPES_MSG_SELECTOR = '.role-grant-scopes div'; const NO_SCOPES_MSG_LINK_SELECTOR = '.role-grant-scopes div div:nth-child(3) a'; + const BUTTON_ICON_SELECTOR = + '.hds-button__icon [data-test-icon="check-circle"]'; const instances = { scopes: { @@ -109,7 +115,7 @@ module('Acceptance | roles | org-scope', function (hooks) { assert.strictEqual( currentURL(), - `/scopes/${instances.scopes.org.id}/scopes`, + `/scopes/${instances.role.grant_scope_ids[1]}/edit`, ); }); @@ -121,10 +127,70 @@ module('Acceptance | roles | org-scope', function (hooks) { assert.strictEqual( currentURL(), - `/scopes/${instances.scopes.global.id}/scopes`, + `/scopes/${instances.scopes.org.id}/scopes`, ); }); + test('user can search for existing grant scope on a role', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + + await fillIn(SEARCH_INPUT_SELECTOR, instances.role.grant_scope_ids[0]); + await waitUntil( + () => + findAll(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .length === 0, + ); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .doesNotExist(); + }); + + test('user can search for grant scopes on a role and get no results', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .exists(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .exists(); + + await fillIn(SEARCH_INPUT_SELECTOR, 'fake scope that does not exist'); + await waitUntil(() => findAll(NO_RESULTS_MSG_SELECTOR).length === 1); + + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[0])) + .doesNotExist(); + assert + .dom(GRANT_SCOPE_ROW_SELECTOR(instances.role.grant_scope_ids[1])) + .doesNotExist(); + assert.dom(NO_RESULTS_MSG_SELECTOR).includesText('No results found'); + }); + + test('user cannot filter grant scopes on an org level role', async function (assert) { + await visit(urls.role); + + await click(`[href="${urls.roleScopes}"]`); + + assert.dom(FILTER_DROPDOWN_SELECTOR('parent-scope')).doesNotExist(); + assert.dom(FILTER_DROPDOWN_SELECTOR('type')).doesNotExist(); + }); + test('user sees no scopes message and action when role has no grant scopes', async function (assert) { instances.role.update({ grant_scope_ids: [] }); await visit(urls.role); @@ -258,6 +324,7 @@ module('Acceptance | roles | org-scope', function (hooks) { await click(SAVE_BTN_SELECTOR); assert.strictEqual(currentURL(), urls.manageScopes); + assert.dom(BUTTON_ICON_SELECTOR).isVisible(); await click(SAVE_BTN_SELECTOR); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js index 75e363f2f..b056556bc 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -import { visit, currentURL } from '@ember/test-helpers'; +import { visit, currentURL, waitUntil } from '@ember/test-helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -67,7 +67,8 @@ module( test('cancel action causes transition to expected route', async function (assert) { await visit(urls.manageScopes); - await controller.cancel(); + controller.cancel(); + await waitUntil(() => currentURL() === urls.scopes); assert.strictEqual(currentURL(), urls.scopes); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js index 98da37d20..f9a5f6333 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js @@ -4,18 +4,22 @@ */ import { module, test } from 'qunit'; -import { setupTest } from 'admin/tests/helpers'; +import { setupTest } from 'ember-qunit'; +import { visit, waitUntil } from '@ember/test-helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { GRANT_SCOPE_THIS, GRANT_SCOPE_CHILDREN, GRANT_SCOPE_DESCENDANTS, } from 'api/models/role'; +import { TYPE_SCOPE_ORG, TYPE_SCOPE_PROJECT } from 'api/models/scope'; module('Unit | Controller | scopes/scope/roles/role/scopes', function (hooks) { setupTest(hooks); setupMirage(hooks); + setupIndexedDb(hooks); let controller; @@ -60,4 +64,48 @@ module('Unit | Controller | scopes/scope/roles/role/scopes', function (hooks) { ); assert.notOk(controller.isKeywordChildrenOrDescendants(GRANT_SCOPE_THIS)); }); + + test('availableParentScopes returns expected object', async function (assert) { + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: instances.scopes.global.id }, + }); + instances.role.update({ grant_scope_ids: [instances.scopes.org.id] }); + await visit(urls.scopes); + + assert.deepEqual(controller.availableParentScopes, [ + { id: instances.scopes.global.id }, + ]); + }); + + test('scopeTypeOptions returns expected object', function (assert) { + assert.deepEqual(controller.scopeTypeOptions, [ + { id: TYPE_SCOPE_ORG, name: 'Org' }, + { id: TYPE_SCOPE_PROJECT, name: 'Project' }, + ]); + }); + + test('filters returns expected entries', async function (assert) { + await visit(urls.scopes); + + assert.ok(controller.filters.allFilters); + assert.ok(controller.filters.selectedFilters); + }); + + test('handleSearchInput action sets expected values correctly', async function (assert) { + const searchValue = 'test'; + controller.handleSearchInput({ target: { value: searchValue } }); + await waitUntil(() => controller.search === searchValue); + + assert.strictEqual(controller.page, 1); + assert.strictEqual(controller.search, searchValue); + }); + + test('applyFilter action sets expected values correctly', function (assert) { + const selectedItems = [TYPE_SCOPE_ORG]; + controller.applyFilter('types', selectedItems); + + assert.strictEqual(controller.page, 1); + assert.deepEqual(controller.types, selectedItems); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js index d7e43d7d7..33e273c2e 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js @@ -36,6 +36,7 @@ module('Unit | Controller | scopes/scope/targets/index', function (hooks) { const urls = { projectScope: null, targets: null, + target: null, alias: null, }; @@ -68,7 +69,8 @@ module('Unit | Controller | scopes/scope/targets/index', function (hooks) { urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.targets = `${urls.projectScope}/targets`; - urls.alias = `${urls.projectScope}/targets/${instances.target.id}/${instances.alias.id}`; + urls.target = `${urls.targets}/${instances.target.id}`; + urls.alias = `${urls.target}/${instances.alias.id}`; getTargetCount = () => this.server.schema.targets.all().models.length; getAliasCount = () => this.server.schema.aliases.all().models.length; @@ -218,7 +220,7 @@ module('Unit | Controller | scopes/scope/targets/index', function (hooks) { }); test('deleteAlias action destroys specified model', async function (assert) { - await visit(urls.targets); + await visit(urls.target); const alias = await store.findRecord('alias', instances.alias.id); const aliasCount = getAliasCount(); diff --git a/ui/admin/tests/unit/routes/scopes/scope/roles/role/scopes-test.js b/ui/admin/tests/unit/routes/scopes/scope/roles/role/scopes-test.js index 29ab21a3a..660ff0961 100644 --- a/ui/admin/tests/unit/routes/scopes/scope/roles/role/scopes-test.js +++ b/ui/admin/tests/unit/routes/scopes/scope/roles/role/scopes-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { setupTest } from 'admin/tests/helpers'; +import { setupTest } from 'ember-qunit'; module('Unit | Route | scopes/scope/roles/role/scopes', function (hooks) { setupTest(hooks);