17098: Fixes unit test for refactored menu kind function.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 25 Nov 2020 15:00:59 +0000 (12:00 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 25 Nov 2020 15:00:59 +0000 (12:00 -0300)
Added 'redux-mock-store' package that will allow us to unit test dispatched
actions.

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

package.json
src/store/context-menu/context-menu-actions.test.ts
src/store/context-menu/context-menu-actions.ts
src/store/resources/resources.ts
yarn.lock

index 346d4910c19bb51899bf6e71f5626ae5e05aeb5b..c972ff02377218afa2de4afb58cf0a7cafb32676 100644 (file)
@@ -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",
     "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"
index c3e78679278f69783ff5c7a055da5def03217729..2778568e7681d1073a7f627e70bf9444b8aaee21 100644 (file)
 //
 // 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<any>(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}.`);
+                }
             });
         });
     });
index 9a5868904da5bab8b430df7a451416014043cd6d..225538859a743a690e2da15143fe600d8e786fe6 100644 (file)
@@ -203,13 +203,13 @@ export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, 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<GroupResource & EditableResource>(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:
index eb3c5509f2f1c3f99c2afc028c4ae18e7e308b6c..696a136280c1a72fef39a8a204e5fd9557439508 100644 (file)
@@ -31,6 +31,8 @@ const getResourceWritableBy = (state: ResourcesState, id: string, userUuid: stri
 
 export const getResourceWithEditableStatus = <T extends EditableResource & GroupResource>(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) {
index 842d6cf837166f6369177b2ee8b64ae9002c0993..d11e22b6599eb2d312cb4e122ef368c7f38a53a4 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
     "@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"