From d8e95a7342d85e11eaaf3685c6b1e84ece5f1e92 Mon Sep 17 00:00:00 2001 From: Lucas Di Pentima Date: Wed, 25 Nov 2020 12:00:59 -0300 Subject: [PATCH] 17098: Fixes unit test for refactored menu kind function. Added 'redux-mock-store' package that will allow us to unit test dispatched actions. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- package.json | 2 + .../context-menu/context-menu-actions.test.ts | 269 ++++++++---------- .../context-menu/context-menu-actions.ts | 18 +- src/store/resources/resources.ts | 2 + yarn.lock | 27 ++ 5 files changed, 159 insertions(+), 159 deletions(-) diff --git a/package.json b/package.json index 346d4910..c972ff02 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "@types/react-router-dom": "4.3.1", "@types/react-router-redux": "5.0.16", "@types/redux-devtools": "3.0.44", + "@types/redux-mock-store": "1.0.2", "@types/sinon": "7.5", "@types/uuid": "3.4.4", "axios-mock-adapter": "1.17.0", @@ -107,6 +108,7 @@ "node-sass": "4.9.4", "node-sass-chokidar": "1.3.4", "redux-devtools": "3.4.1", + "redux-mock-store": "1.5.4", "typescript": "3.1.1", "wait-on": "4.0.2", "yamljs": "0.3.0" diff --git a/src/store/context-menu/context-menu-actions.test.ts b/src/store/context-menu/context-menu-actions.test.ts index c3e78679..2778568e 100644 --- a/src/store/context-menu/context-menu-actions.test.ts +++ b/src/store/context-menu/context-menu-actions.test.ts @@ -2,159 +2,128 @@ // // SPDX-License-Identifier: AGPL-3.0 -import * as resource from '~/models/resource'; import { ContextMenuKind } from '~/views-components/context-menu/context-menu'; -import { resourceKindToContextMenuKind } from './context-menu-actions'; +import { resourceUuidToContextMenuKind } from './context-menu-actions'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; describe('context-menu-actions', () => { - describe('resourceKindToContextMenuKind', () => { - const uuid = '123'; - - describe('ResourceKind.PROJECT', () => { - beforeEach(() => { - // setup - jest.spyOn(resource, 'extractUuidKind') - .mockImplementation(() => resource.ResourceKind.PROJECT); - }); - - it('should return ContextMenuKind.PROJECT_ADMIN', () => { - // given - const isAdmin = true; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin); - - // then - expect(result).toEqual(ContextMenuKind.PROJECT_ADMIN); - }); - - it('should return ContextMenuKind.PROJECT', () => { - // given - const isAdmin = false; - const isEditable = true; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable); - - // then - expect(result).toEqual(ContextMenuKind.PROJECT); - }); - - it('should return ContextMenuKind.READONLY_PROJECT', () => { - // given - const isAdmin = false; - const isEditable = false; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable); - - // then - expect(result).toEqual(ContextMenuKind.READONLY_PROJECT); - }); - }); - - describe('ResourceKind.COLLECTION', () => { - beforeEach(() => { - // setup - jest.spyOn(resource, 'extractUuidKind') - .mockImplementation(() => resource.ResourceKind.COLLECTION); - }); - - it('should return ContextMenuKind.COLLECTION_ADMIN', () => { - // given - const isAdmin = true; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin); - - // then - expect(result).toEqual(ContextMenuKind.COLLECTION_ADMIN); - }); - - it('should return ContextMenuKind.COLLECTION', () => { - // given - const isAdmin = false; - const isEditable = true; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable); - - // then - expect(result).toEqual(ContextMenuKind.COLLECTION); - }); - - it('should return ContextMenuKind.READONLY_COLLECTION', () => { - // given - const isAdmin = false; - const isEditable = false; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable); - - // then - expect(result).toEqual(ContextMenuKind.READONLY_COLLECTION); - }); - }); - - describe('ResourceKind.PROCESS', () => { - beforeEach(() => { - // setup - jest.spyOn(resource, 'extractUuidKind') - .mockImplementation(() => resource.ResourceKind.PROCESS); - }); - - it('should return ContextMenuKind.PROCESS_ADMIN', () => { - // given - const isAdmin = true; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin); - - // then - expect(result).toEqual(ContextMenuKind.PROCESS_ADMIN); - }); - - it('should return ContextMenuKind.PROCESS_RESOURCE', () => { - // given - const isAdmin = false; - - // when - const result = resourceKindToContextMenuKind(uuid, isAdmin); - - // then - expect(result).toEqual(ContextMenuKind.PROCESS_RESOURCE); - }); - }); - - describe('ResourceKind.USER', () => { - beforeEach(() => { - // setup - jest.spyOn(resource, 'extractUuidKind') - .mockImplementation(() => resource.ResourceKind.USER); - }); - - it('should return ContextMenuKind.ROOT_PROJECT', () => { - // when - const result = resourceKindToContextMenuKind(uuid); - - // then - expect(result).toEqual(ContextMenuKind.ROOT_PROJECT); - }); - }); - - describe('ResourceKind.LINK', () => { - beforeEach(() => { - // setup - jest.spyOn(resource, 'extractUuidKind') - .mockImplementation(() => resource.ResourceKind.LINK); - }); - - it('should return ContextMenuKind.LINK', () => { - // when - const result = resourceKindToContextMenuKind(uuid); - - // then - expect(result).toEqual(ContextMenuKind.LINK); + describe('resourceUuidToContextMenuKind', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const userUuid = 'zzzzz-tpzed-bbbbbbbbbbbbbbb'; + const otherUserUuid = 'zzzzz-tpzed-bbbbbbbbbbbbbbc'; + const headCollectionUuid = 'zzzzz-4zz18-aaaaaaaaaaaaaaa'; + const oldCollectionUuid = 'zzzzz-4zz18-aaaaaaaaaaaaaab'; + const projectUuid = 'zzzzz-j7d0g-ccccccccccccccc'; + const linkUuid = 'zzzzz-o0j2j-0123456789abcde'; + const containerRequestUuid = 'zzzzz-xvhdp-0123456789abcde'; + + it('should return the correct menu kind', () => { + const cases = [ + // resourceUuid, isAdminUser, isEditable, isTrashed, expected + [headCollectionUuid, false, true, true, ContextMenuKind.TRASHED_COLLECTION], + [headCollectionUuid, false, true, false, ContextMenuKind.COLLECTION], + [headCollectionUuid, false, false, true, ContextMenuKind.READONLY_COLLECTION], + [headCollectionUuid, false, false, false, ContextMenuKind.READONLY_COLLECTION], + [headCollectionUuid, true, true, true, ContextMenuKind.TRASHED_COLLECTION], + [headCollectionUuid, true, true, false, ContextMenuKind.COLLECTION_ADMIN], + [headCollectionUuid, true, false, true, ContextMenuKind.TRASHED_COLLECTION], + [headCollectionUuid, true, false, false, ContextMenuKind.COLLECTION_ADMIN], + + [oldCollectionUuid, false, true, true, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, false, true, false, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, false, false, true, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, false, false, false, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, true, true, true, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, true, true, false, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, true, false, true, ContextMenuKind.OLD_VERSION_COLLECTION], + [oldCollectionUuid, true, false, false, ContextMenuKind.OLD_VERSION_COLLECTION], + + // FIXME: WB2 doesn't currently have context menu for trashed projects + // [projectUuid, false, true, true, ContextMenuKind.TRASHED_PROJECT], + [projectUuid, false, true, false, ContextMenuKind.PROJECT], + [projectUuid, false, false, true, ContextMenuKind.READONLY_PROJECT], + [projectUuid, false, false, false, ContextMenuKind.READONLY_PROJECT], + // [projectUuid, true, true, true, ContextMenuKind.TRASHED_PROJECT], + [projectUuid, true, true, false, ContextMenuKind.PROJECT_ADMIN], + // [projectUuid, true, false, true, ContextMenuKind.TRASHED_PROJECT], + [projectUuid, true, false, false, ContextMenuKind.PROJECT_ADMIN], + + [linkUuid, false, true, true, ContextMenuKind.LINK], + [linkUuid, false, true, false, ContextMenuKind.LINK], + [linkUuid, false, false, true, ContextMenuKind.LINK], + [linkUuid, false, false, false, ContextMenuKind.LINK], + [linkUuid, true, true, true, ContextMenuKind.LINK], + [linkUuid, true, true, false, ContextMenuKind.LINK], + [linkUuid, true, false, true, ContextMenuKind.LINK], + [linkUuid, true, false, false, ContextMenuKind.LINK], + + [userUuid, false, true, true, ContextMenuKind.ROOT_PROJECT], + [userUuid, false, true, false, ContextMenuKind.ROOT_PROJECT], + [userUuid, false, false, true, ContextMenuKind.ROOT_PROJECT], + [userUuid, false, false, false, ContextMenuKind.ROOT_PROJECT], + [userUuid, true, true, true, ContextMenuKind.ROOT_PROJECT], + [userUuid, true, true, false, ContextMenuKind.ROOT_PROJECT], + [userUuid, true, false, true, ContextMenuKind.ROOT_PROJECT], + [userUuid, true, false, false, ContextMenuKind.ROOT_PROJECT], + + [containerRequestUuid, false, true, true, ContextMenuKind.PROCESS_RESOURCE], + [containerRequestUuid, false, true, false, ContextMenuKind.PROCESS_RESOURCE], + [containerRequestUuid, false, false, true, ContextMenuKind.PROCESS_RESOURCE], + [containerRequestUuid, false, false, false, ContextMenuKind.PROCESS_RESOURCE], + [containerRequestUuid, true, true, true, ContextMenuKind.PROCESS_ADMIN], + [containerRequestUuid, true, true, false, ContextMenuKind.PROCESS_ADMIN], + [containerRequestUuid, true, false, true, ContextMenuKind.PROCESS_ADMIN], + [containerRequestUuid, true, false, false, ContextMenuKind.PROCESS_ADMIN], + ] + + cases.forEach(([resourceUuid, isAdminUser, isEditable, isTrashed, expected]) => { + const initialState = { + resources: { + [headCollectionUuid]: { + uuid: headCollectionUuid, + ownerUuid: projectUuid, + currentVersionUuid: headCollectionUuid, + isTrashed: isTrashed, + }, + [oldCollectionUuid]: { + uuid: oldCollectionUuid, + currentVersionUuid: headCollectionUuid, + isTrashed: isTrashed, + + }, + [projectUuid]: { + uuid: projectUuid, + ownerUuid: isEditable ? userUuid : otherUserUuid, + writableBy: isEditable ? [userUuid] : [otherUserUuid], + }, + [linkUuid]: { + uuid: linkUuid, + }, + [userUuid]: { + uuid: userUuid, + }, + [containerRequestUuid]: { + uuid: containerRequestUuid, + ownerUuid: projectUuid, + }, + }, + auth: { + user: { + uuid: userUuid, + isAdmin: isAdminUser, + }, + }, + }; + const store = mockStore(initialState); + + const menuKind = store.dispatch(resourceUuidToContextMenuKind(resourceUuid as string)) + try { + expect(menuKind).toBe(expected); + } catch (err) { + throw new Error(`menuKind for resource ${JSON.stringify(initialState.resources[resourceUuid as string])} expected to be ${expected} but got ${menuKind}.`); + } }); }); }); diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 9a586890..22553885 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -203,13 +203,13 @@ export const openProcessContextMenu = (event: React.MouseEvent, pro export const resourceUuidToContextMenuKind = (uuid: string) => (dispatch: Dispatch, getState: () => RootState) => { - const { isAdmin, uuid: userUuid } = getState().auth.user!; + const { isAdmin: isAdminUser, uuid: userUuid } = getState().auth.user!; const kind = extractUuidKind(uuid); const resource = getResourceWithEditableStatus(uuid, userUuid)(getState().resources); - const isEditable = (resource || {} as EditableResource).isEditable; + const isEditable = isAdminUser || (resource || {} as EditableResource).isEditable; switch (kind) { case ResourceKind.PROJECT: - return !isAdmin + return !isAdminUser ? isEditable ? ContextMenuKind.PROJECT : ContextMenuKind.READONLY_PROJECT @@ -219,17 +219,17 @@ export const resourceUuidToContextMenuKind = (uuid: string) => if (c === undefined) { return; } const isOldVersion = c.uuid !== c.currentVersionUuid; const isTrashed = c.isTrashed; - return (isTrashed && isEditable) - ? ContextMenuKind.TRASHED_COLLECTION - : isOldVersion - ? ContextMenuKind.OLD_VERSION_COLLECTION - : isAdmin + return isOldVersion + ? ContextMenuKind.OLD_VERSION_COLLECTION + : (isTrashed && isEditable) + ? ContextMenuKind.TRASHED_COLLECTION + : isAdminUser ? ContextMenuKind.COLLECTION_ADMIN : isEditable ? ContextMenuKind.COLLECTION : ContextMenuKind.READONLY_COLLECTION; case ResourceKind.PROCESS: - return !isAdmin + return !isAdminUser ? ContextMenuKind.PROCESS_RESOURCE : ContextMenuKind.PROCESS_ADMIN; case ResourceKind.USER: diff --git a/src/store/resources/resources.ts b/src/store/resources/resources.ts index eb3c5509..696a1362 100644 --- a/src/store/resources/resources.ts +++ b/src/store/resources/resources.ts @@ -31,6 +31,8 @@ const getResourceWritableBy = (state: ResourcesState, id: string, userUuid: stri export const getResourceWithEditableStatus = (id: string, userUuid?: string) => (state: ResourcesState): T | undefined => { + if (state[id] === undefined) { return; } + const resource = JSON.parse(JSON.stringify(state[id] as T)); if (resource) { diff --git a/yarn.lock b/yarn.lock index 842d6cf8..d11e22b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -465,6 +465,13 @@ "@types/react" "*" redux "^3.6.0 || ^4.0.0" +"@types/redux-mock-store@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz#c27d5deadfb29d8514bdb0fc2cadae6feea1922d" + integrity sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw== + dependencies: + redux "^4.0.5" + "@types/shell-quote@1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@types/shell-quote/-/shell-quote-1.6.0.tgz#537b2949a2ebdcb0d353e448fee45b081021963f" @@ -6839,6 +6846,11 @@ lodash.isfunction@^3.0.8: resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -9317,6 +9329,13 @@ redux-form@7.4.2: prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" +redux-mock-store@1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.4.tgz#90d02495fd918ddbaa96b83aef626287c9ab5872" + integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA== + dependencies: + lodash.isplainobject "^4.0.6" + redux-thunk@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" @@ -9348,6 +9367,14 @@ redux@^3.6.0: loose-envify "^1.1.0" symbol-observable "^1.0.3" +redux@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" -- 2.30.2