From 0234f9b526d07d48f660d664271f65832f97a26b Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Mon, 16 Nov 2020 13:55:49 -0500 Subject: [PATCH 1/6] Start move to ImmutableCollectionOperations TODO: - Update tests --- .../package-lock.json | 5 + .../package.json | 1 + .../src/index.tsx | 3 +- .../src/store/alert/reducers.ts | 8 +- .../src/store/alert/types.ts | 3 +- .../src/store/contract/reducers.ts | 15 +- .../src/store/contract/selectors.ts | 12 +- .../src/store/contract/types.ts | 1 + .../src/store/employee/reducers.ts | 17 +- .../src/store/employee/selectors.ts | 10 +- .../src/store/employee/types.ts | 1 + .../src/store/mockStore.ts | 15 +- .../src/store/roster/operations.ts | 4 +- .../src/store/rotation/reducers.ts | 19 +- .../src/store/rotation/types.ts | 1 + .../src/store/skill/reducers.ts | 15 +- .../src/store/skill/selectors.ts | 13 +- .../src/store/skill/types.ts | 1 + .../src/store/spot/reducers.ts | 17 +- .../src/store/spot/selectors.ts | 10 +- .../src/store/spot/types.ts | 1 + .../src/store/store.ts | 2 +- .../src/store/tenant/reducers.ts | 10 +- .../src/store/tenant/types.ts | 3 +- .../src/ui/Alerts.tsx | 5 +- .../src/ui/components/DataTable.tsx | 48 +-- .../src/ui/header/Toolbar.tsx | 7 +- .../src/ui/pages/admin/AdminPage.tsx | 18 +- .../availability/AvailabilityRosterPage.tsx | 23 +- .../availability/EditAvailabilityModal.tsx | 5 +- .../src/ui/pages/employee/EmployeesPage.tsx | 11 +- .../ui/pages/rotation/EditTimeBucketModal.tsx | 2 +- .../src/ui/pages/rotation/EmployeeStub.tsx | 2 +- .../src/ui/pages/rotation/RotationPage.tsx | 10 +- .../ui/pages/shift/CurrentShiftRosterPage.tsx | 25 +- .../src/ui/pages/shift/EditShiftModal.tsx | 15 +- .../ui/pages/shift/ExportScheduleModal.tsx | 13 +- .../ui/pages/shift/ProvisionShiftsModal.tsx | 9 +- .../src/ui/pages/shift/ShiftRosterPage.tsx | 25 +- .../src/ui/pages/spot/SpotsPage.tsx | 5 +- .../ImmutableCollectionOperations.test.ts | 306 ------------------ .../src/util/ImmutableCollectionOperations.ts | 84 +---- 42 files changed, 229 insertions(+), 571 deletions(-) diff --git a/optaweb-employee-rostering-frontend/package-lock.json b/optaweb-employee-rostering-frontend/package-lock.json index 19ce50b92..45045344c 100644 --- a/optaweb-employee-rostering-frontend/package-lock.json +++ b/optaweb-employee-rostering-frontend/package-lock.json @@ -8733,6 +8733,11 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" }, + "immutable": { + "version": "4.0.0-rc.12", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", + "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==" + }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", diff --git a/optaweb-employee-rostering-frontend/package.json b/optaweb-employee-rostering-frontend/package.json index 43e30f0bb..2d74fc891 100644 --- a/optaweb-employee-rostering-frontend/package.json +++ b/optaweb-employee-rostering-frontend/package.json @@ -12,6 +12,7 @@ "i18next": "^19.1.0", "i18next-browser-languagedetector": "^4.0.1", "i18next-xhr-backend": "^3.2.2", + "immutable": "^4.0.0-rc.12", "moment": "^2.24.0", "node-sass": "^4.14.1", "react": "^16.13.1", diff --git a/optaweb-employee-rostering-frontend/src/index.tsx b/optaweb-employee-rostering-frontend/src/index.tsx index d55e72af0..8b2a80d43 100644 --- a/optaweb-employee-rostering-frontend/src/index.tsx +++ b/optaweb-employee-rostering-frontend/src/index.tsx @@ -27,6 +27,7 @@ import App from './ui/App'; // import i18n (needs to be bundled) import i18n from './i18n'; +import { List } from 'immutable'; const path = window.location.pathname; let windowTenantId = 0; @@ -39,7 +40,7 @@ const store = configureStore({ }, { tenantData: { currentTenantId: windowTenantId, - tenantList: [], + tenantList: List(), timezoneList: [], }, }); diff --git a/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts b/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts index ffd8f58e3..7fa9cec47 100644 --- a/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { withElement, withoutElementWithId } from 'util/ImmutableCollectionOperations'; import { ActionType, AlertList, AlertAction } from './types'; +import { List } from 'immutable'; export const initialState: AlertList = { - alertList: [], + alertList: List(), idGeneratorIndex: 0, }; @@ -27,10 +27,10 @@ const alertReducer = (state = initialState, action: AlertAction): AlertList => { case ActionType.ADD_ALERT: { const alertWithId = { ...action.alertInfo, id: state.idGeneratorIndex }; const nextIndex = state.idGeneratorIndex + 1; - return { ...state, idGeneratorIndex: nextIndex, alertList: withElement(state.alertList, alertWithId) }; + return { ...state, idGeneratorIndex: nextIndex, alertList: state.alertList.push(alertWithId) }; } case ActionType.REMOVE_ALERT: { - return { ...state, alertList: withoutElementWithId(state.alertList, action.id) }; + return { ...state, alertList: state.alertList.filterNot(alert => alert.id === action.id) }; } default: return state; diff --git a/optaweb-employee-rostering-frontend/src/store/alert/types.ts b/optaweb-employee-rostering-frontend/src/store/alert/types.ts index 3604e2aa0..d2c5ea9f2 100644 --- a/optaweb-employee-rostering-frontend/src/store/alert/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/alert/types.ts @@ -16,6 +16,7 @@ import { Action } from 'redux'; import { BasicObject } from 'types'; +import { List } from 'immutable'; export enum ActionType { ADD_ALERT = 'ADD_ALERT', @@ -47,6 +48,6 @@ export interface AlertInfo { } export interface AlertList { - readonly alertList: AlertInfo[]; + readonly alertList: List; readonly idGeneratorIndex: number; } diff --git a/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts b/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts index 8cbd316ea..e17dcc2bf 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts @@ -15,16 +15,16 @@ */ import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, + createIdMapFromList } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Contract } from 'domain/Contract'; import { ActionType, ContractList, ContractAction } from './types'; +import { Map } from 'immutable'; export const initialState: ContractList = { isLoading: true, - contractMapById: new Map>(), + contractMapById: Map>(), }; const contractReducer = (state = initialState, action: ContractAction): ContractList => { @@ -32,14 +32,11 @@ const contractReducer = (state = initialState, action: ContractAction): Contract case ActionType.SET_CONTRACT_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_CONTRACT: { - return { ...state, contractMapById: mapWithElement(state.contractMapById, action.contract) }; + case ActionType.ADD_CONTRACT, ActionType.UPDATE_CONTRACT: { + return { ...state, contractMapById: state.contractMapById.set(action.contract.id as number, action.contract) }; } case ActionType.REMOVE_CONTRACT: { - return { ...state, contractMapById: mapWithoutElement(state.contractMapById, action.contract) }; - } - case ActionType.UPDATE_CONTRACT: { - return { ...state, contractMapById: mapWithUpdatedElement(state.contractMapById, action.contract) }; + return { ...state, contractMapById: state.contractMapById.remove(action.contract.id as number) }; } case ActionType.REFRESH_CONTRACT_LIST: { return { ...state, contractMapById: createIdMapFromList(action.contractList) }; diff --git a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts index 50132bafa..99e395296 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts @@ -15,6 +15,7 @@ */ import { Contract } from 'domain/Contract'; import { AppState } from '../types'; +import { Map, List } from 'immutable'; export const getContractById = (state: AppState, id: number): Contract => { if (state.contractList.isLoading) { @@ -24,20 +25,19 @@ export const getContractById = (state: AppState, id: number): Contract => { }; let oldContractMapById: Map | null = null; -let contractListForOldContractMapById: Contract[] | null = null; +let contractListForOldContractMapById: List | null = null; -export const getContractList = (state: AppState): Contract[] => { +export const getContractList = (state: AppState): List => { if (state.contractList.isLoading) { - return []; + return List(); } if (oldContractMapById === state.contractList.contractMapById && contractListForOldContractMapById !== null) { return contractListForOldContractMapById; } - - const out: Contract[] = []; - state.contractList.contractMapById.forEach((value, key) => out.push(getContractById(state, key))); + const out = state.contractList.contractMapById.keySeq().map(id => getContractById(state, id)).toList(); oldContractMapById = state.contractList.contractMapById; contractListForOldContractMapById = out; + return out; }; diff --git a/optaweb-employee-rostering-frontend/src/store/contract/types.ts b/optaweb-employee-rostering-frontend/src/store/contract/types.ts index a1b3f9c34..98c14950e 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/types.ts @@ -17,6 +17,7 @@ import { Action } from 'redux'; import { Contract } from 'domain/Contract'; import DomainObjectView from 'domain/DomainObjectView'; +import { Map } from 'immutable'; export enum ActionType { ADD_CONTRACT = 'ADD_CONTRACT', diff --git a/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts b/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts index da4e8376b..f766b4772 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts @@ -15,16 +15,16 @@ */ import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, + createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Employee } from 'domain/Employee'; import { ActionType, EmployeeList, EmployeeAction } from './types'; +import { Map } from 'immutable'; export const initialState: EmployeeList = { isLoading: true, - employeeMapById: new Map>(), + employeeMapById: Map>(), }; const employeeReducer = (state = initialState, action: EmployeeAction): EmployeeList => { @@ -32,14 +32,13 @@ const employeeReducer = (state = initialState, action: EmployeeAction): Employee case ActionType.SET_EMPLOYEE_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_EMPLOYEE: { - return { ...state, employeeMapById: mapWithElement(state.employeeMapById, action.employee) }; + case ActionType.ADD_EMPLOYEE, ActionType.UPDATE_EMPLOYEE: { + return { ...state, employeeMapById: state.employeeMapById.set(action.employee.id as number, + mapDomainObjectToView(action.employee)) + }; } case ActionType.REMOVE_EMPLOYEE: { - return { ...state, employeeMapById: mapWithoutElement(state.employeeMapById, action.employee) }; - } - case ActionType.UPDATE_EMPLOYEE: { - return { ...state, employeeMapById: mapWithUpdatedElement(state.employeeMapById, action.employee) }; + return { ...state, employeeMapById: state.employeeMapById.remove(action.employee.id as number) }; } case ActionType.REFRESH_EMPLOYEE_LIST: { return { ...state, employeeMapById: createIdMapFromList(action.employeeList) }; diff --git a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts index cc4af583e..9d7c1b9b0 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts @@ -18,6 +18,7 @@ import { skillSelectors } from 'store/skill'; import { Employee } from 'domain/Employee'; import DomainObjectView from 'domain/DomainObjectView'; import { AppState } from '../types'; +import { Map, List } from 'immutable'; export const getEmployeeById = (state: AppState, id: number): Employee => { if (state.employeeList.isLoading || state.skillList.isLoading || state.contractList.isLoading) { @@ -32,18 +33,17 @@ export const getEmployeeById = (state: AppState, id: number): Employee => { }; let oldEmployeeMapById: Map> | null = null; -let employeeListForOldEmployeeMapById: Employee[] | null = null; +let employeeListForOldEmployeeMapById: List | null = null; -export const getEmployeeList = (state: AppState): Employee[] => { +export const getEmployeeList = (state: AppState): List => { if (state.employeeList.isLoading || state.skillList.isLoading || state.contractList.isLoading) { - return []; + return List(); } if (oldEmployeeMapById === state.employeeList.employeeMapById && employeeListForOldEmployeeMapById !== null) { return employeeListForOldEmployeeMapById; } - const out: Employee[] = []; - state.employeeList.employeeMapById.forEach((value, key) => out.push(getEmployeeById(state, key))); + const out = state.employeeList.employeeMapById.keySeq().map(id => getEmployeeById(state, id)).toList(); oldEmployeeMapById = state.employeeList.employeeMapById; employeeListForOldEmployeeMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/employee/types.ts b/optaweb-employee-rostering-frontend/src/store/employee/types.ts index 196d71fe7..195104662 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/types.ts @@ -17,6 +17,7 @@ import { Action } from 'redux'; import { Employee } from 'domain/Employee'; import DomainObjectView from 'domain/DomainObjectView'; +import { Map } from 'immutable'; export enum ActionType { ADD_EMPLOYEE = 'ADD_EMPLOYEE', diff --git a/optaweb-employee-rostering-frontend/src/store/mockStore.ts b/optaweb-employee-rostering-frontend/src/store/mockStore.ts index f843137f0..c12a81d8f 100644 --- a/optaweb-employee-rostering-frontend/src/store/mockStore.ts +++ b/optaweb-employee-rostering-frontend/src/store/mockStore.ts @@ -22,6 +22,7 @@ import RestServiceClient from './rest/RestServiceClient'; import { TenantAction } from './tenant/types'; import { SkillAction } from './skill/types'; import { AppState } from './types'; +import { Map, List } from 'immutable'; jest.mock('./rest/RestServiceClient'); @@ -38,28 +39,28 @@ export const mockStore = (state: Partial) => { const out = { store: mockStoreCreator({ tenantData: { currentTenantId: 0, - tenantList: [], + tenantList: List(), timezoneList: ['America/Toronto'], }, employeeList: { isLoading: true, - employeeMapById: new Map(), + employeeMapById: Map(), }, contractList: { isLoading: true, - contractMapById: new Map(), + contractMapById: Map(), }, spotList: { isLoading: true, - spotMapById: new Map(), + spotMapById: Map(), }, skillList: { isLoading: true, - skillMapById: new Map(), + skillMapById: Map(), }, timeBucketList: { isLoading: true, - timeBucketMapById: new Map(), + timeBucketMapById: Map(), }, rosterState: { isLoading: true, @@ -77,7 +78,7 @@ export const mockStore = (state: Partial) => { solverStatus: 'NOT_SOLVING', }, alerts: { - alertList: [], + alertList: List(), idGeneratorIndex: 0, }, isConnected: true, diff --git a/optaweb-employee-rostering-frontend/src/store/roster/operations.ts b/optaweb-employee-rostering-frontend/src/store/roster/operations.ts index 3d5b17b91..51915a420 100644 --- a/optaweb-employee-rostering-frontend/src/store/roster/operations.ts +++ b/optaweb-employee-rostering-frontend/src/store/roster/operations.ts @@ -176,7 +176,7 @@ ThunkCommandFactory = () => (dispatch, state) => { const startDate = moment(rosterState.firstDraftDate).startOf('week').toDate(); const endDate = moment(rosterState.firstDraftDate).endOf('week').toDate(); const spotList = spotSelectors.getSpotList(state()); - const shownSpots = (spotList.length > 0) ? [spotList[0]] : []; + const shownSpots = (spotList.size > 0) ? [spotList.get(0) as Spot] : []; if (shownSpots.length > 0) { dispatch(getShiftRosterFor({ @@ -199,7 +199,7 @@ ThunkCommandFactory = () => (dispatch, state const startDate = moment(rosterState.firstDraftDate).startOf('week').toDate(); const endDate = moment(rosterState.firstDraftDate).endOf('week').toDate(); const employeeList = employeeSelectors.getEmployeeList(state()); - const shownEmployees = (employeeList.length > 0) ? [employeeList[0]] : []; + const shownEmployees = (employeeList.size > 0) ? [employeeList.get(0) as Employee] : []; if (shownEmployees.length > 0) { dispatch(getAvailabilityRosterFor({ diff --git a/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts b/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts index d08a55d42..57f405ca4 100644 --- a/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts @@ -15,16 +15,16 @@ */ import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, + createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { TimeBucket } from 'domain/TimeBucket'; import { ActionType, TimeBucketList, TimeBucketAction } from './types'; +import { Map } from 'immutable'; export const initialState: TimeBucketList = { isLoading: true, - timeBucketMapById: new Map>(), + timeBucketMapById: Map>(), }; const timeBucketReducer = (state = initialState, action: TimeBucketAction): TimeBucketList => { @@ -32,16 +32,13 @@ const timeBucketReducer = (state = initialState, action: TimeBucketAction): Time case ActionType.SET_TIME_BUCKET_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_TIME_BUCKET: { - return { ...state, timeBucketMapById: mapWithElement(state.timeBucketMapById, action.timeBucket) }; + case ActionType.ADD_TIME_BUCKET, ActionType.UPDATE_TIME_BUCKET: { + return { ...state, timeBucketMapById: state.timeBucketMapById.set(action.timeBucket.id as number, + mapDomainObjectToView(action.timeBucket)) + }; } case ActionType.REMOVE_TIME_BUCKET: { - return { ...state, timeBucketMapById: mapWithoutElement(state.timeBucketMapById, action.timeBucket) }; - } - case ActionType.UPDATE_TIME_BUCKET: { - return { ...state, - timeBucketMapById: mapWithUpdatedElement(state.timeBucketMapById, - action.timeBucket) }; + return { ...state, timeBucketMapById: state.timeBucketMapById.remove(action.timeBucket.id as number) }; } case ActionType.REFRESH_TIME_BUCKET_LIST: { return { ...state, timeBucketMapById: createIdMapFromList(action.timeBucketList) }; diff --git a/optaweb-employee-rostering-frontend/src/store/rotation/types.ts b/optaweb-employee-rostering-frontend/src/store/rotation/types.ts index e34864c81..0c209aa67 100644 --- a/optaweb-employee-rostering-frontend/src/store/rotation/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/rotation/types.ts @@ -17,6 +17,7 @@ import { Action } from 'redux'; import DomainObjectView from 'domain/DomainObjectView'; import { TimeBucket } from 'domain/TimeBucket'; +import { Map } from 'immutable'; export enum ActionType { ADD_TIME_BUCKET = 'ADD__TIME_BUCKET', diff --git a/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts b/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts index 862d804c1..766685a0c 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts @@ -15,16 +15,16 @@ */ import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, + createIdMapFromList } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Skill } from 'domain/Skill'; import { ActionType, SkillList, SkillAction } from './types'; +import { Map } from 'immutable'; export const initialState: SkillList = { isLoading: true, - skillMapById: new Map>(), + skillMapById: Map>(), }; const skillReducer = (state = initialState, action: SkillAction): SkillList => { @@ -32,14 +32,11 @@ const skillReducer = (state = initialState, action: SkillAction): SkillList => { case ActionType.SET_SKILL_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_SKILL: { - return { ...state, skillMapById: mapWithElement(state.skillMapById, action.skill) }; + case ActionType.ADD_SKILL, ActionType.UPDATE_SKILL: { + return { ...state, skillMapById: state.skillMapById.set(action.skill.id as number, action.skill) }; } case ActionType.REMOVE_SKILL: { - return { ...state, skillMapById: mapWithoutElement(state.skillMapById, action.skill) }; - } - case ActionType.UPDATE_SKILL: { - return { ...state, skillMapById: mapWithUpdatedElement(state.skillMapById, action.skill) }; + return { ...state, skillMapById: state.skillMapById.remove(action.skill.id as number) }; } case ActionType.REFRESH_SKILL_LIST: { return { ...state, skillMapById: createIdMapFromList(action.skillList) }; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts index 6577d4db3..c5c66bedc 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts @@ -15,6 +15,8 @@ */ import { Skill } from 'domain/Skill'; import { AppState } from '../types'; +import { List, Map } from 'immutable'; +import DomainObjectView from 'domain/DomainObjectView'; export const getSkillById = (state: AppState, id: number): Skill => { if (state.skillList.isLoading) { @@ -24,19 +26,18 @@ export const getSkillById = (state: AppState, id: number): Skill => { }; -let oldSkillMapById: Map | null = null; -let skillListForOldSkillMapById: Skill[] | null = null; +let oldSkillMapById: Map>| null = null; +let skillListForOldSkillMapById: List | null = null; -export const getSkillList = (state: AppState): Skill[] => { +export const getSkillList = (state: AppState): List => { if (state.skillList.isLoading) { - return []; + return List(); } if (oldSkillMapById === state.skillList.skillMapById && skillListForOldSkillMapById !== null) { return skillListForOldSkillMapById; } - const out: Skill[] = []; - state.skillList.skillMapById.forEach((value, key) => out.push(getSkillById(state, key))); + const out = state.skillList.skillMapById.keySeq().map((key) => getSkillById(state, key)).toList(); oldSkillMapById = state.skillList.skillMapById; skillListForOldSkillMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/types.ts b/optaweb-employee-rostering-frontend/src/store/skill/types.ts index 4c05d6842..f8c4922c6 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/types.ts @@ -17,6 +17,7 @@ import { Action } from 'redux'; import { Skill } from 'domain/Skill'; import DomainObjectView from 'domain/DomainObjectView'; +import { Map } from 'immutable'; export enum ActionType { ADD_SKILL = 'ADD_SKILL', diff --git a/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts b/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts index c48963f82..31c432219 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts @@ -15,16 +15,16 @@ */ import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, + createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Spot } from 'domain/Spot'; import { ActionType, SpotList, SpotAction } from './types'; +import { Map } from 'immutable'; export const initialState: SpotList = { isLoading: true, - spotMapById: new Map>(), + spotMapById: Map>(), }; const spotReducer = (state = initialState, action: SpotAction): SpotList => { @@ -32,14 +32,13 @@ const spotReducer = (state = initialState, action: SpotAction): SpotList => { case ActionType.SET_SPOT_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_SPOT: { - return { ...state, spotMapById: mapWithElement(state.spotMapById, action.spot) }; + case ActionType.ADD_SPOT, ActionType.UPDATE_SPOT: { + return { ...state, spotMapById: state.spotMapById.set(action.spot.id as number, + mapDomainObjectToView(action.spot)) + }; } case ActionType.REMOVE_SPOT: { - return { ...state, spotMapById: mapWithoutElement(state.spotMapById, action.spot) }; - } - case ActionType.UPDATE_SPOT: { - return { ...state, spotMapById: mapWithUpdatedElement(state.spotMapById, action.spot) }; + return { ...state, spotMapById: state.spotMapById.remove(action.spot.id as number) }; } case ActionType.REFRESH_SPOT_LIST: { return { ...state, spotMapById: createIdMapFromList(action.spotList) }; diff --git a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts index 1e0d73226..552a0577f 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts @@ -17,6 +17,7 @@ import { skillSelectors } from 'store/skill'; import { Spot } from 'domain/Spot'; import DomainObjectView from 'domain/DomainObjectView'; import { AppState } from '../types'; +import { Map, List } from 'immutable'; export const getSpotById = (state: AppState, id: number): Spot => { if (state.spotList.isLoading || state.skillList.isLoading) { @@ -30,18 +31,17 @@ export const getSpotById = (state: AppState, id: number): Spot => { }; let oldSpotMapById: Map> | null = null; -let spotListForOldSpotMapById: Spot[] | null = null; +let spotListForOldSpotMapById: List | null = null; -export const getSpotList = (state: AppState): Spot[] => { +export const getSpotList = (state: AppState): List => { if (state.spotList.isLoading || state.skillList.isLoading) { - return []; + return List(); } if (oldSpotMapById === state.spotList.spotMapById && spotListForOldSpotMapById !== null) { return spotListForOldSpotMapById; } - const out: Spot[] = []; - state.spotList.spotMapById.forEach((value, key) => out.push(getSpotById(state, key))); + const out = state.spotList.spotMapById.keySeq().map((key) => getSpotById(state, key)).toList(); oldSpotMapById = state.spotList.spotMapById; spotListForOldSpotMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/spot/types.ts b/optaweb-employee-rostering-frontend/src/store/spot/types.ts index 77704c7bb..d1b0f8391 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/types.ts @@ -17,6 +17,7 @@ import { Action } from 'redux'; import { Spot } from 'domain/Spot'; import DomainObjectView from 'domain/DomainObjectView'; +import { Map } from 'immutable'; export enum ActionType { ADD_SPOT = 'ADD_SPOT', diff --git a/optaweb-employee-rostering-frontend/src/store/store.ts b/optaweb-employee-rostering-frontend/src/store/store.ts index 8be792751..a0baecfa5 100644 --- a/optaweb-employee-rostering-frontend/src/store/store.ts +++ b/optaweb-employee-rostering-frontend/src/store/store.ts @@ -45,7 +45,7 @@ export function configureStore( ): Store { const restServiceClient = new RestServiceClient(restBaseURL, axios); - const middlewares = [thunk.withExtraArgument(restServiceClient), createLogger()]; + const middlewares = [thunk.withExtraArgument(restServiceClient), /* createLogger() */]; const middlewareEnhancer = applyMiddleware(...middlewares); const enhancers = [middlewareEnhancer]; diff --git a/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts b/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts index ee2f5ae32..85980189b 100644 --- a/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { withElement, withoutElement } from 'util/ImmutableCollectionOperations'; import { ActionType, TenantData, TenantAction, ConnectAction, ConnectionActionType } from './types'; +import { List } from 'immutable'; const initialState: TenantData = { currentTenantId: 0, - tenantList: [], + tenantList: List(), timezoneList: [], }; @@ -31,13 +31,13 @@ const tenantReducer = (state = initialState, action: TenantAction): TenantData = return { ...state, currentTenantId: action.tenantId }; } case ActionType.REFRESH_TENANT_LIST: { - return { ...state, currentTenantId: action.tenantId, tenantList: action.tenantList }; + return { ...state, currentTenantId: action.tenantId, tenantList: List(action.tenantList) }; } case ActionType.ADD_TENANT: { - return { ...state, tenantList: withElement(state.tenantList, action.tenant) }; + return { ...state, tenantList: state.tenantList.push(action.tenant) }; } case ActionType.REMOVE_TENANT: { - return { ...state, tenantList: withoutElement(state.tenantList, action.tenant) }; + return { ...state, tenantList: state.tenantList.filterNot(tenant => tenant.id === action.tenant.id) }; } case ActionType.REFRESH_SUPPORTED_TIMEZONES: { return { ...state, timezoneList: action.timezoneList }; diff --git a/optaweb-employee-rostering-frontend/src/store/tenant/types.ts b/optaweb-employee-rostering-frontend/src/store/tenant/types.ts index 490a2499d..744f78bf5 100644 --- a/optaweb-employee-rostering-frontend/src/store/tenant/types.ts +++ b/optaweb-employee-rostering-frontend/src/store/tenant/types.ts @@ -16,6 +16,7 @@ import { Action } from 'redux'; import { Tenant } from 'domain/Tenant'; +import { List } from 'immutable'; export enum ActionType { CHANGE_TENANT = 'CHANGE_TENANT', @@ -63,6 +64,6 @@ RemoveTenantAction | RefreshSupportedTimezoneListAction; export interface TenantData { readonly currentTenantId: number; - readonly tenantList: Tenant[]; + readonly tenantList: List; readonly timezoneList: string[]; } diff --git a/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx b/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx index 506744f31..4b805a311 100644 --- a/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx @@ -25,9 +25,10 @@ import moment from 'moment'; import { useInterval } from 'util/FunctionalComponentUtils'; import { BasicObject, ServerSideExceptionInfo } from 'types'; import { ServerSideExceptionDialog } from './components/ServerSideExceptionDialog'; +import { List } from 'immutable'; interface StateProps { - alerts: AlertInfo[]; + alerts: List; } interface DispatchProps { @@ -59,7 +60,7 @@ const Alerts: React.FC = (props) => { const [hoveredOverAlerts, hoveredOverAlertsSetter] = React.useState([] as number[]); const shouldUpdateNextSecond = props.alerts.filter(alert => hoveredOverAlerts.find( id => id === alert.id, - ) === undefined).length > 0; + ) === undefined).size > 0; const additionClassNames = (alert: AlertInfo) => { const secondsFromEvent = moment.duration(moment().diff(moment(alert.createdAt))).asSeconds(); diff --git a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx index 30ae075df..bfc63117b 100644 --- a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx @@ -28,17 +28,18 @@ import { import { Button, ButtonVariant, Pagination, Level, LevelItem } from '@patternfly/react-core'; import { SaveIcon, CloseIcon, EditIcon, TrashIcon } from '@patternfly/react-icons'; import { Predicate, ReadonlyPartial, Sorter } from 'types'; -import { toggleElement, Stream } from 'util/ImmutableCollectionOperations'; +import { toggleElement, conditionally } from 'util/ImmutableCollectionOperations'; import { WithTranslation } from 'react-i18next'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { RouteComponentProps } from 'react-router'; import FilterComponent from './FilterComponent'; import { EditableComponent } from './EditableComponent'; +import { List } from 'immutable'; export interface DataTableProps extends WithTranslation, RouteComponentProps { title: string; columnTitles: string[]; - tableData: T[]; + tableData: List; } interface DataTableState { @@ -277,7 +278,7 @@ export abstract class DataTable> extends React.Co page: '1', itemsPerPage: '10', filter: null, - sortBy: null, + sortBy: this.getSorters().filter(x => x !== null).length > 0? `${this.getSorters().findIndex(x => x !== null)}` : null, asc: 'true', }); const [page, perPage] = [parseInt(urlProps.page as string, 10), parseInt(urlProps.itemsPerPage as string, 10)]; @@ -285,37 +286,42 @@ export abstract class DataTable> extends React.Co const sortDirection: 'asc'|'desc' = urlProps.asc === 'true' ? 'asc' : 'desc'; const sortBy = urlProps.sortBy ? { index: parseInt(urlProps.sortBy, 10), direction: sortDirection } : {}; - const additionalRows: IRow[] = (this.state.newRowData !== null) - ? [ + const additionalRows: List = (this.state.newRowData !== null) + ? List([ { cells: this.editDataRow(this.state.newRowData, setProperty) .map(c => ({ title: c })) .concat([{ title: this.getAddButtons(this.state.newRowData) }]), }, - ] : []; + ]) : List(); const sorters = this.getSorters(); - - const filteredRows = new Stream(this.props.tableData) + + const filteredRows = conditionally(this.props.tableData.valueSeq(), // eslint-disable-next-line consistent-return - .conditionally((s) => { + (s) => { if (urlProps.sortBy !== null) { - return s.sort(sorters[parseInt(urlProps.sortBy, 10)] as Sorter, - urlProps.asc === 'true'); + return s.sort(sorters[parseInt(urlProps.sortBy, 10)] as Sorter); + } + }).then( + // eslint-disable-next-line consistent-return + (s) => { + if (urlProps.asc !== 'true') { + return s.reverse(); } - }) + }).then( // eslint-disable-next-line consistent-return - .conditionally((s) => { + (s) => { if (urlProps.filter !== null) { return s.filter(this.getFilter()(urlProps.filter)); } - }); - - const rowsThatMatchFilter = filteredRows.collect(c => c.length); + }).result; + const rowsThatMatchFilterCount = filteredRows.count(); + const rows = additionalRows.concat(filteredRows - .page(page, perPage) - .map(this.convertDataToTableRow) - .collect(c => c)); + .skip((page - 1) * perPage) + .take(perPage) + .map(this.convertDataToTableRow)); const columns: ICell[] = this.props.columnTitles.map((title, index) => ({ title, @@ -346,7 +352,7 @@ export abstract class DataTable> extends React.Co > extends React.Co /> - +
diff --git a/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.tsx b/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.tsx index 277eec403..4ac27a324 100644 --- a/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.tsx @@ -31,10 +31,11 @@ import { connect } from 'react-redux'; import { tenantOperations } from 'store/tenant'; import { AppState } from 'store/types'; import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { List } from 'immutable'; interface StateProps { currentTenantId: number; - tenantList: Tenant[]; + tenantList: List; } const mapStateToProps = ({ tenantData }: AppState): StateProps => ({ @@ -116,7 +117,7 @@ export class ToolbarComponent extends React.Component { ); const { tenantList, currentTenantId } = this.props; const { isTenantSelectOpen } = this.state; - if (tenantList.length === 0) { + if (tenantList.size === 0) { return ( @@ -146,7 +147,7 @@ export class ToolbarComponent extends React.Component { {tenant.name} - ))} + )).toArray()} /> diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.tsx index 41f54f4fc..a6338d7e7 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.tsx @@ -19,13 +19,13 @@ import { AppState } from 'store/types'; import { Text, Level, LevelItem, Pagination, Button } from '@patternfly/react-core'; import { connect } from 'react-redux'; import { DataTableUrlProps } from 'ui/components/DataTable'; -import { Stream } from 'util/ImmutableCollectionOperations'; import { stringFilter } from 'util/CommonFilters'; import { Tenant } from 'domain/Tenant'; +import { List } from 'immutable'; import { tenantOperations } from 'store/tenant'; import * as adminOperations from 'store/admin/operations'; import FilterComponent from 'ui/components/FilterComponent'; -import { Table, IRow, TableHeader, TableBody } from '@patternfly/react-table'; +import { Table, TableHeader, TableBody } from '@patternfly/react-table'; import { TrashIcon } from '@patternfly/react-icons'; import { useTranslation } from 'react-i18next'; import { ConfirmDialog } from 'ui/components/ConfirmDialog'; @@ -35,7 +35,7 @@ import NewTenantFormModal from './NewTenantFormModal'; interface StateProps { tenantId: number; - tenantList: Tenant[]; + tenantList: List; } const mapStateToProps = (state: AppState): StateProps => ({ @@ -76,14 +76,14 @@ export const AdminPage: React.FC = (props) => { const page = parseInt(urlProps.page as string, 10); const itemsPerPage = parseInt(urlProps.itemsPerPage as string, 10); const filter = stringFilter((tenant: Tenant) => tenant.name)(filterText); - const filteredRows = new Stream(tenantList) + const filteredRows = tenantList .filter(filter); - const numOfFilteredRows = filteredRows.collect(c => c.length); + const numOfFilteredRows = filteredRows.size; const rowsInPage = filteredRows - .page(page, itemsPerPage) - .collect(c => c); + .skip(page * itemsPerPage) + .take(itemsPerPage); return ( <> @@ -149,7 +149,7 @@ export const AdminPage: React.FC = (props) => { caption={t('tenants')} cells={[t('name'), '']} rows={ - rowsInPage.map(tenant => ( + rowsInPage.map(tenant => ( { cells: [ ({tenant.name}), @@ -176,7 +176,7 @@ export const AdminPage: React.FC = (props) => { ), ], - })) + })).toArray() } > diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx index 6ba17b461..b94a318c2 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx @@ -44,13 +44,14 @@ import AvailabilityEvent, { AvailabilityPopoverHeader, AvailabilityPopoverBody } import EditAvailabilityModal from './EditAvailabilityModal'; import ShiftEvent, { ShiftPopupHeader, ShiftPopupBody } from '../shift/ShiftEvent'; import EditShiftModal from '../shift/EditShiftModal'; +import { List } from 'immutable'; interface StateProps { tenantId: number; isSolving: boolean; isLoading: boolean; - allEmployeeList: Employee[]; - shownEmployeeList: Employee[]; + allEmployeeList: List; + shownEmployeeList: List; employeeIdToShiftListMap: Map; employeeIdToAvailabilityListMap: Map; totalNumOfSpots: number; @@ -72,8 +73,8 @@ const mapStateToProps = (state: AppState): StateProps => ({ isSolving: state.solverState.solverStatus !== 'NOT_SOLVING', isLoading: rosterSelectors.isAvailabilityRosterLoading(state), allEmployeeList: employeeSelectors.getEmployeeList(state), - shownEmployeeList: lastShownEmployeeList = rosterSelectors.isAvailabilityRosterLoading(state) - ? lastShownEmployeeList : rosterSelectors.getEmployeeListInAvailabilityRoster(state), + shownEmployeeList: List(lastShownEmployeeList = rosterSelectors.isAvailabilityRosterLoading(state) + ? lastShownEmployeeList : rosterSelectors.getEmployeeListInAvailabilityRoster(state)), employeeIdToShiftListMap: lastEmployeeIdToShiftListMap = rosterSelectors .getEmployeeListInAvailabilityRoster(state) .reduce((prev, curr) => prev.set(curr.id as number, @@ -85,7 +86,7 @@ const mapStateToProps = (state: AppState): StateProps => ({ rosterSelectors.getAvailabilityListForEmployee(state, curr)), rosterSelectors.isAvailabilityRosterLoading(state) ? lastEmployeeIdToAvailabilityListMap : new Map()), - totalNumOfSpots: spotSelectors.getSpotList(state).length, + totalNumOfSpots: spotSelectors.getSpotList(state).size, rosterState: state.rosterState.rosterState, score: state.availabilityRoster.availabilityRosterView ? state.availabilityRoster.availabilityRosterView.score : null, indictmentSummary: state.availabilityRoster.availabilityRosterView @@ -175,7 +176,7 @@ export class AvailabilityRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const employee = this.props.allEmployeeList - .find(e => e.name === urlProps.employee) || this.props.allEmployeeList[0]; + .find(e => e.name === urlProps.employee) || this.props.allEmployeeList.get(0); if (employee) { this.props.getAvailabilityRosterFor({ fromDate: startDate, @@ -289,11 +290,11 @@ export class AvailabilityRosterPage extends React.Component { employee: null, week: null, }); - const changedTenant = this.props.shownEmployeeList.length === 0 + const changedTenant = this.props.shownEmployeeList.size === 0 || (urlProps.employee !== null - && this.props.tenantId !== this.props.shownEmployeeList[0].tenantId); + && this.props.tenantId !== (this.props.shownEmployeeList.get(0) as Employee).tenantId); - if (this.props.shownEmployeeList.length === 0 || changedTenant || this.props.rosterState === null) { + if (this.props.shownEmployeeList.size === 0 || changedTenant || this.props.rosterState === null) { return ( @@ -318,7 +319,7 @@ export class AvailabilityRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const shownEmployee = this.props.allEmployeeList.find(e => e.name === urlProps.employee) - || this.props.shownEmployeeList[0]; + || this.props.shownEmployeeList.get(0) as Employee; const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -380,7 +381,7 @@ export class AvailabilityRosterPage extends React.Component { aria-label="Select Employee" emptyText={t('selectEmployee')} optionToStringMap={employee => employee.name} - options={this.props.allEmployeeList} + options={this.props.allEmployeeList.toArray()} value={shownEmployee} onChange={(e) => { this.onUpdateAvailabilityRoster({ diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx index f49907850..5c253f67d 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx @@ -27,12 +27,13 @@ import { employeeSelectors } from 'store/employee'; import 'react-datepicker/dist/react-datepicker.css'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; import { withTranslation, WithTranslation } from 'react-i18next'; +import { List } from 'immutable'; interface Props { tenantId: number; availability?: EmployeeAvailability; isOpen: boolean; - employeeList: Employee[]; + employeeList: List; onSave: (availability: EmployeeAvailability) => void; onDelete: (availability: EmployeeAvailability) => void; onClose: () => void; @@ -156,7 +157,7 @@ export class EditAvailabilityModal extends React.Component employee.name} onChange={employee => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, employee }, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx index a337c09a3..d4a132fd5 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx @@ -49,11 +49,12 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withTranslation, WithTranslation, Trans } from 'react-i18next'; import moment from 'moment'; import { ColorPicker, StatefulColorPicker, defaultColorList } from 'ui/components/ColorPicker'; +import { List } from 'immutable'; interface StateProps extends DataTableProps { tenantId: number; - skillList: Skill[]; - contractList: Contract[]; + skillList: List; + contractList: List; } const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ @@ -161,13 +162,13 @@ export class EmployeesPage extends DataTable { emptyText={this.props.t('selectAContract')} optionToStringMap={c => c.name} value={data.contract} - options={this.props.contractList} + options={this.props.contractList.toArray()} onChange={contract => setProperty('contract', contract)} />, skill.name} value={data.skillProficiencySet ? data.skillProficiencySet : []} onChange={selected => setProperty('skillProficiencySet', selected)} @@ -248,7 +249,7 @@ export class EmployeesPage extends DataTable { /> ); - if (this.props.contractList.length === 0) { + if (this.props.contractList.size === 0) { return ( {importElement} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx index 737a83b44..328a3af31 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx @@ -85,7 +85,7 @@ export const TimeBucketEditor: React.FC = (props) => { skill.name} emptyText="Select additional skills..." autoSize={false} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx index 672b0bb6a..234df9829 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx @@ -164,7 +164,7 @@ export const EditEmployeeStubListModal: React.FC > ((typeof o === 'string') ? t('unassigned') : o.name)} autoSize={false} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx index 621783066..f385d48d5 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx @@ -52,7 +52,7 @@ export const RotationPage: React.FC<{}> = () => { const [selectedStub, setSelectedStub] = useState('NO_SHIFT'); const [isEditingTimeBuckets, setIsEditingTimeBuckets] = useState(false); - const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.length > 0) ? spotList[0].name : undefined); + const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.size > 0) ? (spotList.get(0) as Spot).name : undefined); const shownSpot = spotList.find(s => s.name === shownSpotName); const shownTimeBuckets = shownSpot ? timeBucketList.filter(tb => tb.spot.id === shownSpot.id) : []; const oldShownTimeBuckets = useRef(shownTimeBuckets.map(tb => tb.id).join(',')); @@ -70,8 +70,8 @@ export const RotationPage: React.FC<{}> = () => { const [stubList, setStubList] = useState(getEmployeesInTimeBuckets()); React.useEffect(() => { - if (shownSpot === undefined && spotList.length > 0) { - setShownSpotName(spotList[0].name); + if (shownSpot === undefined && spotList.size > 0) { + setShownSpotName((spotList.get(0) as Spot).name); } }, [spotList, shownSpot, setShownSpotName]); @@ -86,7 +86,7 @@ export const RotationPage: React.FC<{}> = () => { }, [oldShownTimeBuckets, shownSpotName, spotList, timeBucketList, getEmployeesInTimeBuckets]); - if (rosterState === null || isLoading || spotList.length <= 0 || shownSpotName === null) { + if (rosterState === null || isLoading || spotList.size <= 0 || shownSpotName === null) { return ( @@ -114,7 +114,7 @@ export const RotationPage: React.FC<{}> = () => { aria-label="Select Spot" emptyText={t('selectSpot')} optionToStringMap={spot => spot.name} - options={spotList} + options={spotList.toArray()} value={shownSpot} onChange={(s) => { setShownSpotName(s ? s.name : null); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx index 10f6aa6cc..7ef474f4e 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx @@ -41,13 +41,14 @@ import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; +import { List } from 'immutable'; interface StateProps { tenantId: number; isSolving: boolean; isLoading: boolean; - allSpotList: Spot[]; - shownSpotList: Spot[]; + allSpotList: List; + shownSpotList: List; spotIdToShiftListMap: Map; totalNumOfSpots: number; rosterState: RosterState | null; @@ -67,14 +68,14 @@ const mapStateToProps = (state: AppState): StateProps => ({ isLoading: rosterSelectors.isShiftRosterLoading(state), allSpotList: spotSelectors.getSpotList(state), // The use of "x = isLoading? x : getUpdatedData()" is a way to use old value if data is still loading - shownSpotList: lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList - : rosterSelectors.getSpotListInShiftRoster(state), + shownSpotList: List(lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList + : rosterSelectors.getSpotListInShiftRoster(state)), spotIdToShiftListMap: lastSpotIdToShiftListMap = rosterSelectors.getSpotListInShiftRoster(state) .reduce((prev, curr) => prev.set(curr.id as number, rosterSelectors.getShiftListForSpot(state, curr)), // reducing an empty array returns the starting value rosterSelectors.isShiftRosterLoading(state) ? lastSpotIdToShiftListMap : new Map()), - totalNumOfSpots: spotSelectors.getSpotList(state).length, + totalNumOfSpots: spotSelectors.getSpotList(state).size, rosterState: state.rosterState.rosterState, score: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.score : null, indictmentSummary: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.indictmentSummary : null, @@ -130,7 +131,7 @@ export class ShiftRosterPage extends React.Component { onUpdateShiftRoster(urlProps: ShiftRosterUrlProps) { if (this.props.rosterState) { - const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.allSpotList[0]; + const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.allSpotList.get(0) as Spot; const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); @@ -234,10 +235,10 @@ export class ShiftRosterPage extends React.Component { spot: null, week: null, }); - const changedTenant = this.props.shownSpotList.length === 0 - || this.props.tenantId !== this.props.shownSpotList[0].tenantId; + const changedTenant = this.props.shownSpotList.size === 0 + || this.props.tenantId !== (this.props.shownSpotList.get(0) as Spot).tenantId; - if (this.props.shownSpotList.length === 0 || this.state.firstLoad + if (this.props.shownSpotList.size === 0 || this.state.firstLoad || changedTenant || this.props.rosterState === null) { return ( @@ -262,7 +263,7 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); - const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.shownSpotList[0]; + const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.shownSpotList.get(0) as Spot; const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -308,7 +309,7 @@ export class ShiftRosterPage extends React.Component { aria-label="Select Spot" emptyText={t('selectSpot')} optionToStringMap={spot => spot.name} - options={this.props.allSpotList} + options={this.props.allSpotList.toArray()} value={shownSpot} onChange={(s) => { this.onUpdateShiftRoster({ @@ -363,7 +364,7 @@ export class ShiftRosterPage extends React.Component { defaultToDate={endDate} /> - key={this.props.shownSpotList[0].id} + key={(this.props.shownSpotList.get(0) as Spot).id} startDate={startDate} endDate={endDate} events={this.props.spotIdToShiftListMap.get(shownSpot.id as number) || []} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx index 0690a682b..98dec720c 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx @@ -34,14 +34,15 @@ import { Skill } from 'domain/Skill'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; import moment from 'moment'; import { useValidators } from 'util/ValidationUtils'; +import { List } from 'immutable'; interface Props { tenantId: number; shift?: Shift; isOpen: boolean; - skillList: Skill[]; - employeeList: Employee[]; - spotList: Spot[]; + skillList: List; + employeeList: List; + spotList: List; onSave: (shift: Shift) => void; onDelete: (shift: Shift) => void; onClose: () => void; @@ -212,7 +213,7 @@ export class EditShiftModal extends React.Component spot.name} onChange={spot => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, spot }, @@ -225,7 +226,7 @@ export class EditShiftModal extends React.Component skill.name} onChange={requiredSkillSet => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, requiredSkillSet }, @@ -239,7 +240,7 @@ export class EditShiftModal extends React.Component (employee ? employee.name : t('unassigned'))} onChange={employee => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, employee: (employee !== undefined) ? employee : null }, @@ -264,7 +265,7 @@ export class EditShiftModal extends React.Component (employee ? employee.name : t('none'))} onChange={employee => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, rotationEmployee: (employee !== undefined) ? employee : null }, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx index aebef2d08..aa12061cc 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx @@ -23,10 +23,11 @@ import { AppState } from 'store/types'; import { spotSelectors } from 'store/spot'; import { connect } from 'react-redux'; import moment from 'moment'; +import { List } from 'immutable'; interface StateProps { tenantId: number; - spotList: Spot[]; + spotList: List; } interface OwnProps { @@ -46,7 +47,7 @@ export const ExportScheduleModal: React.FC = (props) => { const { t } = useTranslation('ExportScheduleModal'); const [fromDate, setFromDate] = React.useState(props.defaultFromDate); const [toDate, setToDate] = React.useState(props.defaultToDate); - const [exportedSpots, setExportedSpots] = React.useState(props.spotList); + const [exportedSpots, setExportedSpots] = React.useState>(props.spotList); // Work around since useEffect use shallowEquality, and the same date created at different times are not equal const defaultFromDateTime = props.defaultFromDate.getTime(); @@ -60,7 +61,7 @@ export const ExportScheduleModal: React.FC = (props) => { } }, [props.isOpen, defaultFromDateTime, defaultToDateTime, props.spotList]); - const spotSet = (exportedSpots.length > 0) ? exportedSpots.map(s => `${s.id}`).join(',') : null; + const spotSet = (exportedSpots.size > 0) ? exportedSpots.map(s => `${s.id}`).join(',') : null; let exportUrl = '_blank'; if (spotSet && toDate && fromDate) { @@ -121,10 +122,10 @@ export const ExportScheduleModal: React.FC = (props) => { spot.name} - onChange={setExportedSpots} + onChange={newExportedSpots => setExportedSpots(List(newExportedSpots))} /> diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx index d0890d55f..0887be36f 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx @@ -28,6 +28,7 @@ import { spotSelectors } from 'store/spot'; import { Spot } from 'domain/Spot'; import { TimeBucket } from 'domain/TimeBucket'; import moment from 'moment'; +import { List } from 'immutable'; export interface SpotTimeBucketSelectProps { spot: Spot; @@ -131,7 +132,7 @@ export const ProvisionShiftsModal: React.FC = (props) const [fromDate, setFromDate] = React.useState(props.defaultFromDate); const [toDate, setToDate] = React.useState(props.defaultToDate); const [rotationOffset, setRotationOffset] = React.useState(0); - const [provisionedSpots, setProvisionedSpots] = React.useState([]); + const [provisionedSpots, setProvisionedSpots] = React.useState>(List()); const [provisionedTimeBuckets, setProvisionedTimeBuckets] = React.useState([]); const timeBucketList = useSelector(timeBucketSelectors.getTimeBucketList); @@ -217,11 +218,11 @@ export const ProvisionShiftsModal: React.FC = (props) spot.name} onChange={(newSpotList) => { - setProvisionedSpots(newSpotList); + setProvisionedSpots(List(newSpotList)); let newTimeBucketList = provisionedTimeBuckets; newSpotList.filter(spot => !provisionedSpots.includes(spot)).forEach((spot) => { // New spot added diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx index 81f29638a..e1ee31f5f 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx @@ -42,13 +42,14 @@ import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './S import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; import { ProvisionShiftsModal } from './ProvisionShiftsModal'; +import { List } from 'immutable'; interface StateProps { tenantId: number; isSolving: boolean; isLoading: boolean; - allSpotList: Spot[]; - shownSpotList: Spot[]; + allSpotList: List; + shownSpotList: List; spotIdToShiftListMap: Map; totalNumOfSpots: number; rosterState: RosterState | null; @@ -68,14 +69,14 @@ const mapStateToProps = (state: AppState): StateProps => ({ isLoading: rosterSelectors.isShiftRosterLoading(state), allSpotList: spotSelectors.getSpotList(state), // The use of "x = isLoading? x : getUpdatedData()" is a way to use old value if data is still loading - shownSpotList: lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList - : rosterSelectors.getSpotListInShiftRoster(state), + shownSpotList: List(lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList + : rosterSelectors.getSpotListInShiftRoster(state)), spotIdToShiftListMap: lastSpotIdToShiftListMap = rosterSelectors.getSpotListInShiftRoster(state) .reduce((prev, curr) => prev.set(curr.id as number, rosterSelectors.getShiftListForSpot(state, curr)), // reducing an empty array returns the starting value rosterSelectors.isShiftRosterLoading(state) ? lastSpotIdToShiftListMap : new Map()), - totalNumOfSpots: spotSelectors.getSpotList(state).length, + totalNumOfSpots: spotSelectors.getSpotList(state).size, rosterState: state.rosterState.rosterState, score: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.score : null, indictmentSummary: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.indictmentSummary : null, @@ -133,7 +134,7 @@ export class ShiftRosterPage extends React.Component { onUpdateShiftRoster(urlProps: ShiftRosterUrlProps) { if (this.props.rosterState) { - const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.allSpotList[0]; + const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.allSpotList.get(0) as Spot; const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); @@ -237,10 +238,10 @@ export class ShiftRosterPage extends React.Component { spot: null, week: null, }); - const changedTenant = this.props.shownSpotList.length === 0 - || this.props.tenantId !== this.props.shownSpotList[0].tenantId; + const changedTenant = this.props.shownSpotList.size === 0 + || this.props.tenantId !== (this.props.shownSpotList.get(0) as Spot).tenantId; - if (this.props.shownSpotList.length === 0 || this.state.firstLoad + if (this.props.shownSpotList.size === 0 || this.state.firstLoad || changedTenant || this.props.rosterState === null) { return ( @@ -265,7 +266,7 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); - const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.shownSpotList[0]; + const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.shownSpotList.get(0) as Spot; const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -320,7 +321,7 @@ export class ShiftRosterPage extends React.Component { aria-label="Select Spot" emptyText={t('selectSpot')} optionToStringMap={spot => spot.name} - options={this.props.allSpotList} + options={this.props.allSpotList.toArray()} value={shownSpot} onChange={(s) => { this.onUpdateShiftRoster({ @@ -380,7 +381,7 @@ export class ShiftRosterPage extends React.Component { defaultToDate={endDate} /> - key={this.props.shownSpotList[0].id} + key={(this.props.shownSpotList.get(0) as Spot).id} startDate={startDate} endDate={endDate} events={this.props.spotIdToShiftListMap.get(shownSpot.id as number) || []} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx index d580e6c63..e9201198b 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx @@ -30,10 +30,11 @@ import { stringFilter } from 'util/CommonFilters'; import { withTranslation, WithTranslation } from 'react-i18next'; import { withRouter } from 'react-router'; import { ArrowIcon } from '@patternfly/react-icons'; +import { List } from 'immutable'; interface StateProps extends DataTableProps { tenantId: number; - skillList: Skill[]; + skillList: List; } const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ @@ -111,7 +112,7 @@ export class SpotsPage extends DataTable { skill.name} value={data.requiredSkillSet ? data.requiredSkillSet : []} onChange={selected => setProperty('requiredSkillSet', selected)} diff --git a/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.test.ts b/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.test.ts index 7a94d5755..f6a087d76 100644 --- a/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.test.ts +++ b/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.test.ts @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { DomainObject } from 'domain/DomainObject'; -import DomainObjectView from 'domain/DomainObjectView'; import * as immutableCollectionOperations from './ImmutableCollectionOperations'; describe('Immutable Collection Operations', () => { @@ -35,92 +32,6 @@ describe('Immutable Collection Operations', () => { expect(original).toEqual(originalCopy); }); - it('should not modify the collection on without element with id', () => { - const object1: DomainObject = { - tenantId: 0, - id: 0, - version: 0, - }; - const object2: DomainObject = { - tenantId: 0, - id: 1, - version: 0, - }; - const collection: DomainObject[] = [object1, object2]; - const copy: DomainObject[] = JSON.parse(JSON.stringify(collection)); - const without1 = immutableCollectionOperations.withoutElementWithId(collection, 0); - expect(collection).toEqual(copy); - expect(without1).toEqual([object2]); - - const without2 = immutableCollectionOperations.withoutElementWithId(collection, 1); - expect(collection).toEqual(copy); - expect(without2).toEqual([object1]); - }); - - it('should not modify the collection on without element', () => { - const object1: DomainObject = { - tenantId: 0, - id: 0, - version: 0, - }; - const object2: DomainObject = { - tenantId: 0, - id: 1, - version: 0, - }; - const collection: DomainObject[] = [object1, object2]; - const copy: DomainObject[] = JSON.parse(JSON.stringify(collection)); - const without1 = immutableCollectionOperations.withoutElement(collection, object1); - expect(collection).toEqual(copy); - expect(without1).toEqual([object2]); - - const without2 = immutableCollectionOperations.withoutElement(collection, object2); - expect(collection).toEqual(copy); - expect(without2).toEqual([object1]); - }); - - it('should not modify the collection on with element', () => { - const object1: DomainObject = { - tenantId: 0, - id: 0, - version: 0, - }; - const addedObject: DomainObject = { - tenantId: 0, - id: 1, - version: 0, - }; - const collection: DomainObject[] = [object1]; - const copy: DomainObject[] = JSON.parse(JSON.stringify(collection)); - const withAdded = immutableCollectionOperations.withElement(collection, addedObject); - expect(collection).toEqual(copy); - expect(withAdded).toEqual([object1, addedObject]); - }); - - it('should not modify the collection on with updated element', () => { - const object1: DomainObject = { - tenantId: 0, - id: 0, - version: 0, - }; - const object2: DomainObject = { - tenantId: 0, - id: 1, - version: 0, - }; - const updatedObject1: DomainObject = { ...object1, version: 1 }; - const updatedObject2: DomainObject = { ...object2, version: 1 }; - const collection: DomainObject[] = [object1, object2]; - const copy: DomainObject[] = JSON.parse(JSON.stringify(collection)); - const withUpdated1 = immutableCollectionOperations.withUpdatedElement(collection, updatedObject1); - expect(collection).toEqual(copy); - expect(withUpdated1).toEqual([object2, updatedObject1]); - - const withUpdated2 = immutableCollectionOperations.withUpdatedElement(collection, updatedObject2); - expect(collection).toEqual(copy); - expect(withUpdated2).toEqual([object1, updatedObject2]); - }); - it('should not modify the collection when element is not present in the collection in toggleElement', () => { const obj1 = 0; const obj2 = 1; @@ -184,221 +95,4 @@ describe('Immutable Collection Operations', () => { expect(view.domainObjMemList).toEqual([6, 7]); expect(view.otherMem).toEqual('Test'); }); - - interface MockDomainObject extends DomainObject { - domainObj: DomainObject; - } - - it('should return a new map with the entry added in mapWithElement', () => { - const map = new Map>([ - [0, { - tenantId: 0, - id: 0, - version: 0, - domainObj: 3, - }], - ]); - - const obj = { - tenantId: 0, - id: 1, - version: 0, - domainObj: { - tenantId: 0, - id: 2, - version: 0, - }, - }; - - const copy = new Map(map); - const mapWithObj = immutableCollectionOperations.mapWithElement(map, obj); - - expect(map).toEqual(copy); - expect(mapWithObj).toEqual(new Map([ - [0, map.get(0)], - [1, { - tenantId: 0, - id: 1, - version: 0, - domainObj: 2, - }], - ])); - }); - - it('should return a new map with the entry removed in mapWithoutElement', () => { - const map = new Map>([ - [0, { - tenantId: 0, - id: 0, - version: 0, - domainObj: 3, - }], - [1, { - tenantId: 0, - id: 1, - version: 0, - domainObj: 2, - }], - ]); - - const obj = { - tenantId: 0, - id: 1, - version: 0, - domainObj: { - tenantId: 0, - id: 2, - version: 0, - }, - }; - - const copy = new Map(map); - const mapWithoutObj = immutableCollectionOperations.mapWithoutElement(map, obj); - - expect(map).toEqual(copy); - expect(mapWithoutObj).toEqual(new Map([ - [0, map.get(0)], - ])); - }); - - it('should return a new map with the entry update in mapWithUpdatedElement', () => { - const map = new Map>([ - [0, { - tenantId: 0, - id: 0, - version: 0, - domainObj: 3, - }], - [1, { - tenantId: 0, - id: 1, - version: 0, - domainObj: 2, - }], - ]); - - const obj = { - tenantId: 0, - id: 1, - version: 1, - domainObj: { - tenantId: 0, - id: 5, - version: 0, - }, - }; - - const copy = new Map(map); - const mapWithUpdatedObj = immutableCollectionOperations.mapWithUpdatedElement(map, obj); - - expect(map).toEqual(copy); - expect(mapWithUpdatedObj).toEqual(new Map([ - [0, map.get(0)], - [1, { - tenantId: 0, - id: 1, - version: 1, - domainObj: 5, - }], - ])); - }); -}); - -describe('Stream operations', () => { - it('should map the collection element in map', () => { - const orig = [1, 2, 3]; - const result = new immutableCollectionOperations.Stream(orig).map(n => n + 1).collect(r => r); - expect(result).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(result).toEqual([2, 3, 4]); - }); - - it('should filter the collection element in filter', () => { - const orig = [1, 2, 3]; - const result = new immutableCollectionOperations.Stream(orig).filter(n => n === 1).collect(r => r); - expect(result).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(result).toEqual([1]); - }); - - it('should paged the collection element in page', () => { - const orig = [1, 2, 3]; - const result = new immutableCollectionOperations.Stream(orig).page(1, 2).collect(r => r); - expect(result).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(result).toEqual([1, 2]); - }); - - it('should sort the collection element in sort', () => { - const orig = [1, 2, 3]; - const result = new immutableCollectionOperations.Stream(orig).sort((a, b) => b - a).collect(r => r); - expect(result).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(result).toEqual([3, 2, 1]); - - const revResult = new immutableCollectionOperations.Stream(orig).sort((a, b) => b - a, false).collect(r => r); - expect(revResult).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(revResult).toEqual([1, 2, 3]); - }); - - it('should replace the stream in conditionally', () => { - const orig = [1, 2, 3]; - const replaceResult = new immutableCollectionOperations.Stream(orig) - .conditionally(s => s.filter(x => x === 1)).collect(r => r); - expect(replaceResult).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(replaceResult).toEqual([1]); - - const noReplaceResult = new immutableCollectionOperations.Stream(orig) - .conditionally(() => undefined).collect(r => r); - expect(noReplaceResult).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(noReplaceResult).toEqual([1, 2, 3]); - }); - - it('should collect a result in collect', () => { - const orig = [1, 2, 3]; - const result = new immutableCollectionOperations.Stream(orig).collect(r => r); - expect(result).not.toBe(orig); - expect(orig).toEqual([1, 2, 3]); - expect(result).toEqual([1, 2, 3]); - - const length = new immutableCollectionOperations.Stream(orig).collect(r => r.length); - expect(length).toEqual(3); - }); - - it('streams themselves should be immutable', () => { - const stream = new immutableCollectionOperations.Stream([1, 2, 3]); - const filterStream = stream.filter(n => n === 1); - expect(stream).not.toBe(filterStream); - expect(stream.collect(c => c)).toEqual([1, 2, 3]); - expect(filterStream.collect(c => c)).toEqual([1]); - - const mapStream = stream.map(n => n + 1); - expect(stream).not.toBe(mapStream); - expect(stream.collect(c => c)).toEqual([1, 2, 3]); - expect(mapStream.collect(c => c)).toEqual([2, 3, 4]); - - const pageStream = stream.page(1, 2); - expect(stream).not.toBe(pageStream); - expect(stream.collect(c => c)).toEqual([1, 2, 3]); - expect(pageStream.collect(c => c)).toEqual([1, 2]); - - const sortStream = stream.sort((a, b) => b - a); - expect(stream).not.toBe(sortStream); - expect(stream.collect(c => c)).toEqual([1, 2, 3]); - expect(sortStream.collect(c => c)).toEqual([3, 2, 1]); - - const replaceStream = stream.conditionally(s => s.map(x => x + 1)); - expect(stream).not.toBe(replaceStream); - expect(stream.collect(c => c)).toEqual([1, 2, 3]); - expect(replaceStream.collect(c => c)).toEqual([2, 3, 4]); - - const noReplaceStream = stream.conditionally(() => undefined); - // In the no replace case, the collection doesn't change, so it safe - // for the stream to be the same - expect(stream.collect(c => c)).toEqual([1, 2, 3]); - expect(noReplaceStream.collect(c => c)).toEqual([1, 2, 3]); - }); }); diff --git a/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts b/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts index 5b7cd8c90..f8ac69c40 100644 --- a/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts +++ b/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts @@ -16,7 +16,7 @@ import { DomainObject } from 'domain/DomainObject'; import DomainObjectView from 'domain/DomainObjectView'; -import { Sorter } from 'types'; +import { Map, Seq } from 'immutable'; export function toggleElement(collection: T[], element: T): T[] { if (collection.indexOf(element) !== -1) { @@ -26,22 +26,6 @@ export function toggleElement(collection: T[], element: T): T[] { return [...collection, element]; } -export function withoutElementWithId(collection: T[], removedElementId: number): T[] { - return collection.filter(element => element.id !== removedElementId); -} - -export function withoutElement(collection: T[], removedElement: T): T[] { - return withoutElementWithId(collection, removedElement.id as number); -} - -export function withElement(collection: T[], addedElement: T): T[] { - return [...collection, addedElement]; -} - -export function withUpdatedElement(collection: T[], updatedElement: T): T[] { - return withElement(withoutElement(collection, updatedElement), updatedElement); -} - interface ObjectWithKeys { [key: string]: any; } @@ -71,62 +55,20 @@ export function mapDomainObjectToView(obj: T|DomainObjec } export function createIdMapFromList(collection: T[]): Map> { - const map = new Map>(); - collection.forEach(ele => map.set(ele.id as number, mapDomainObjectToView(ele))); + let map = Map>(); + collection.forEach(ele => (map = map.set(ele.id as number, mapDomainObjectToView(ele)))); return map; } -export function mapWithoutElement(map: Map>, - removedElement: T|DomainObjectView): Map> { - const copy = new Map>(map); - copy.delete(removedElement.id as number); - return copy; -} - -export function mapWithElement(map: Map>, - addedElement: T|DomainObjectView): Map> { - const copy = new Map>(map); - copy.set(addedElement.id as number, mapDomainObjectToView(addedElement)); - return copy; -} - -export function mapWithUpdatedElement(map: Map>, - updatedElement: T|DomainObjectView): Map> { - return mapWithElement(map, updatedElement); -} - -// An immutable version of a collection with a lot of helpful methods -export class Stream { - private collection: T[]; - - constructor(collection: T[]) { - this.collection = collection; - } - - map(fn: (ele: T, index: number) => X): Stream { - return new Stream(this.collection.map(fn)); - } - - filter(predicate: (ele: T, index: number) => boolean): Stream { - return new Stream(this.collection.filter(predicate)); - } - - // Note: page starts at 1 - page(page: number, perPage: number): Stream { - return this.filter((v, i) => (page - 1) * perPage <= i && i < page * perPage); - } - - sort(sorter: Sorter, asc = true): Stream { - const comparator: Sorter = asc ? sorter : (a, b) => sorter(b, a); - return new Stream([...this.collection].sort(comparator)); - } - - conditionally(streamMap: (stream: Stream) => Stream|undefined): Stream { - const out = streamMap(this); - return (out !== undefined) ? out : this; - } - - collect(collector: (collection: T[]) => X): X { - return collector([...this.collection]); +export interface FluentValue { + result: V, + then: (map: M) => FluentValue +}; +export function conditionally(seq: Seq, mapper: (seq: Seq) => Seq | undefined): +FluentValue,(seq: Seq) => Seq | undefined> { + const out = mapper(seq) || seq; + return { + result: out, + then: (newMapper) => conditionally(out, newMapper), } } From fdd6b79120770d456e33949eeffa0721c24bc8cd Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 17 Nov 2020 15:29:21 -0500 Subject: [PATCH 2/6] Migrated tests to use immutable.js --- .../src/index.tsx | 2 +- .../src/store/admin/admin.test.ts | 3 +- .../src/store/alert/alert.test.ts | 14 ++-- .../src/store/alert/reducers.ts | 2 +- .../src/store/contract/contract.test.ts | 32 ++++---- .../src/store/contract/reducers.ts | 8 +- .../src/store/contract/selectors.ts | 4 +- .../src/store/employee/employee.test.ts | 46 ++++++------ .../src/store/employee/reducers.ts | 14 ++-- .../src/store/employee/selectors.ts | 2 +- .../src/store/mockStore.ts | 2 +- .../src/store/roster/roster.test.ts | 35 ++++----- .../src/store/rotation/reducers.ts | 14 ++-- .../src/store/rotation/rotation.test.ts | 41 +++++----- .../src/store/shift/shift.test.ts | 11 +-- .../src/store/skill/reducers.ts | 8 +- .../src/store/skill/selectors.ts | 4 +- .../src/store/skill/skill.test.ts | 30 ++++---- .../src/store/spot/reducers.ts | 14 ++-- .../src/store/spot/selectors.ts | 4 +- .../src/store/spot/spot.test.ts | 36 +++++---- .../src/store/store.ts | 2 +- .../src/store/tenant/reducers.ts | 2 +- .../src/store/tenant/tenant.test.ts | 36 ++++----- .../src/ui/Alerts.test.tsx | 19 ++--- .../src/ui/Alerts.tsx | 2 +- .../src/ui/components/DataTable.test.tsx | 21 +++--- .../src/ui/components/DataTable.tsx | 17 +++-- .../__snapshots__/DataTable.test.tsx.snap | 63 +++++++++++++--- .../src/ui/header/Toolbar.test.tsx | 7 +- .../src/ui/pages/admin/AdminPage.test.tsx | 5 +- .../src/ui/pages/admin/AdminPage.tsx | 2 +- .../AvailabilityRosterPage.test.tsx | 19 ++--- .../availability/AvailabilityRosterPage.tsx | 2 +- .../EditAvailabilityModal.test.tsx | 3 +- .../ui/pages/contract/ContractsPage.test.tsx | 41 +++++----- .../__snapshots__/ContractsPage.test.tsx.snap | 14 +++- .../ui/pages/employee/EmployeePage.test.tsx | 74 +++++++++++-------- .../__snapshots__/EmployeePage.test.tsx.snap | 7 +- .../rotation/EditTimeBucketModal.test.tsx | 5 +- .../ui/pages/rotation/EmployeeStub.test.tsx | 5 +- .../ui/pages/rotation/RotationPage.test.tsx | 3 +- .../src/ui/pages/rotation/RotationPage.tsx | 3 +- .../shift/CurrentShiftRosterPage.test.tsx | 15 ++-- .../ui/pages/shift/CurrentShiftRosterPage.tsx | 5 +- .../ui/pages/shift/EditShiftModal.test.tsx | 7 +- .../pages/shift/ExportScheduleModal.test.tsx | 7 +- .../pages/shift/ProvisionShiftsModal.test.tsx | 3 +- .../ui/pages/shift/ShiftRosterPage.test.tsx | 15 ++-- .../src/ui/pages/shift/ShiftRosterPage.tsx | 5 +- .../src/ui/pages/skill/SkillsPage.test.tsx | 15 ++-- .../__snapshots__/SkillsPage.test.tsx.snap | 14 +++- .../src/ui/pages/spot/SpotsPage.test.tsx | 35 +++++---- .../__snapshots__/SpotsPage.test.tsx.snap | 14 +++- .../src/util/ImmutableCollectionOperations.ts | 18 ++--- 55 files changed, 471 insertions(+), 360 deletions(-) diff --git a/optaweb-employee-rostering-frontend/src/index.tsx b/optaweb-employee-rostering-frontend/src/index.tsx index 8b2a80d43..8e8bb82ec 100644 --- a/optaweb-employee-rostering-frontend/src/index.tsx +++ b/optaweb-employee-rostering-frontend/src/index.tsx @@ -23,11 +23,11 @@ import { SpinnerIcon } from '@patternfly/react-icons'; import './index.css'; import { I18nextProvider } from 'react-i18next'; import { configureStore } from 'store'; +import { List } from 'immutable'; import App from './ui/App'; // import i18n (needs to be bundled) import i18n from './i18n'; -import { List } from 'immutable'; const path = window.location.pathname; let windowTenantId = 0; diff --git a/optaweb-employee-rostering-frontend/src/store/admin/admin.test.ts b/optaweb-employee-rostering-frontend/src/store/admin/admin.test.ts index 06f8e02a2..1ab22cf1a 100644 --- a/optaweb-employee-rostering-frontend/src/store/admin/admin.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/admin/admin.test.ts @@ -18,6 +18,7 @@ import * as tenantOperations from 'store/tenant/operations'; import { onPost } from 'store/rest/RestTestUtils'; import { alert } from 'store/alert'; import { doNothing } from 'types'; +import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as adminOperations from './operations'; @@ -51,7 +52,7 @@ describe('Contract operations', () => { const state: Partial = { tenantData: { currentTenantId: 0, - tenantList: [], + tenantList: List(), timezoneList: ['America/Toronto'], }, }; diff --git a/optaweb-employee-rostering-frontend/src/store/alert/alert.test.ts b/optaweb-employee-rostering-frontend/src/store/alert/alert.test.ts index 265c788c0..4aa534d38 100644 --- a/optaweb-employee-rostering-frontend/src/store/alert/alert.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/alert/alert.test.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { withElement, withoutElementWithId } from 'util/ImmutableCollectionOperations'; import { ServerSideExceptionInfo, BasicObject } from 'types'; +import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -23,15 +23,15 @@ import { AlertInfo, AlertComponent } from './types'; const state: Partial = { alerts: { - alertList: [{ + alertList: List([{ id: 0, createdAt: new Date(), i18nKey: 'alert1', - variant: 'info', + variant: 'info' as 'info', params: {}, components: [], componentProps: [], - }], + }]), idGeneratorIndex: 1, }, }; @@ -162,12 +162,14 @@ describe('Alert reducers', () => { it('add an alert', () => { expect( reducer(state.alerts, actions.addAlert(addedAlert)), - ).toEqual({ idGeneratorIndex: 2, alertList: withElement(storeState.alerts.alertList, { ...addedAlert, id: 1 }) }); + ).toEqual({ idGeneratorIndex: 2, alertList: storeState.alerts.alertList.push({ ...addedAlert, id: 1 }) }); }); it('remove an alert', () => { expect( reducer(state.alerts, actions.removeAlert(removedAlertId)), - ).toEqual({ ...state.alerts, alertList: withoutElementWithId(storeState.alerts.alertList, removedAlertId) }); + ).toEqual({ ...state.alerts, + alertList: storeState.alerts.alertList + .filterNot(a => a.id === removedAlertId) }); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts b/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts index 7fa9cec47..76bd301f6 100644 --- a/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/alert/reducers.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { ActionType, AlertList, AlertAction } from './types'; import { List } from 'immutable'; +import { ActionType, AlertList, AlertAction } from './types'; export const initialState: AlertList = { alertList: List(), diff --git a/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts b/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts index 0f78e0593..6f03ba776 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts @@ -15,12 +15,10 @@ */ import { alert } from 'store/alert'; -import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete } from 'store/rest/RestTestUtils'; import { Contract } from 'domain/Contract'; +import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -29,8 +27,8 @@ import reducer, { contractSelectors, contractOperations } from './index'; const state: Partial = { contractList: { isLoading: false, - contractMapById: new Map([ - [0, { + contractMapById: createIdMapFromList([ + { tenantId: 0, id: 0, version: 0, @@ -39,8 +37,8 @@ const state: Partial = { maximumMinutesPerWeek: null, maximumMinutesPerMonth: null, maximumMinutesPerYear: null, - }], - [1, { + }, + { tenantId: 0, id: 1, version: 0, @@ -49,8 +47,8 @@ const state: Partial = { maximumMinutesPerWeek: 100, maximumMinutesPerMonth: null, maximumMinutesPerYear: null, - }], - [2, { + }, + { tenantId: 0, id: 2, version: 0, @@ -59,7 +57,7 @@ const state: Partial = { maximumMinutesPerWeek: null, maximumMinutesPerMonth: null, maximumMinutesPerYear: 100, - }], + }, ]), }, }; @@ -220,19 +218,19 @@ describe('Contract reducers', () => { expect( reducer(state.contractList, actions.addContract(addedContract)), ).toEqual({ ...state.contractList, - contractMapById: mapWithElement(storeState.contractList.contractMapById, addedContract) }); + contractMapById: storeState.contractList.contractMapById.set(addedContract.id as number, addedContract) }); }); it('remove contract', () => { expect( reducer(state.contractList, actions.removeContract(deletedContract)), ).toEqual({ ...state.contractList, - contractMapById: mapWithoutElement(storeState.contractList.contractMapById, deletedContract) }); + contractMapById: storeState.contractList.contractMapById.delete(deletedContract.id as number) }); }); it('update contract', () => { expect( reducer(state.contractList, actions.updateContract(updatedContract)), ).toEqual({ ...state.contractList, - contractMapById: mapWithUpdatedElement(storeState.contractList.contractMapById, updatedContract) }); + contractMapById: storeState.contractList.contractMapById.set(updatedContract.id as number, updatedContract) }); }); it('refresh contract list', () => { expect( @@ -272,12 +270,12 @@ describe('Contract selectors', () => { ...storeState, contractList: { ...storeState.contractList, isLoading: true }, }); - expect(contractList).toEqual([]); + expect(contractList).toEqual(List()); }); it('should return a list of all contracts', () => { const contractList = contractSelectors.getContractList(storeState); - expect(contractList).toEqual(expect.arrayContaining([ + expect(contractList.toArray()).toEqual(expect.arrayContaining([ { tenantId: 0, id: 0, @@ -309,6 +307,6 @@ describe('Contract selectors', () => { maximumMinutesPerYear: 100, }, ])); - expect(contractList.length).toEqual(3); + expect(contractList.size).toEqual(3); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts b/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts index e17dcc2bf..30203fe1e 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { - createIdMapFromList -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Contract } from 'domain/Contract'; -import { ActionType, ContractList, ContractAction } from './types'; import { Map } from 'immutable'; +import { ActionType, ContractList, ContractAction } from './types'; export const initialState: ContractList = { isLoading: true, @@ -32,7 +30,7 @@ const contractReducer = (state = initialState, action: ContractAction): Contract case ActionType.SET_CONTRACT_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_CONTRACT, ActionType.UPDATE_CONTRACT: { + case ActionType.ADD_CONTRACT: case ActionType.UPDATE_CONTRACT: { return { ...state, contractMapById: state.contractMapById.set(action.contract.id as number, action.contract) }; } case ActionType.REMOVE_CONTRACT: { diff --git a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts index 99e395296..48db5da78 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import { Contract } from 'domain/Contract'; -import { AppState } from '../types'; import { Map, List } from 'immutable'; +import { AppState } from '../types'; export const getContractById = (state: AppState, id: number): Contract => { if (state.contractList.isLoading) { @@ -38,6 +38,6 @@ export const getContractList = (state: AppState): List => { oldContractMapById = state.contractList.contractMapById; contractListForOldContractMapById = out; - + return out; }; diff --git a/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts b/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts index c50729c60..114e0d4fe 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts @@ -15,14 +15,12 @@ */ import { alert } from 'store/alert'; -import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete, onUploadFile } from 'store/rest/RestTestUtils'; import { Employee } from 'domain/Employee'; import * as skillActions from 'store/skill/actions'; import * as contractActions from 'store/contract/actions'; +import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -31,8 +29,8 @@ import reducer, { employeeSelectors, employeeOperations } from './index'; const state: Partial = { employeeList: { isLoading: false, - employeeMapById: new Map([ - [1, { + employeeMapById: createIdMapFromList([ + { tenantId: 0, id: 1, version: 0, @@ -41,8 +39,8 @@ const state: Partial = { contract: 1, shortId: 'e1', color: '#FFFFFF', - }], - [2, { + }, + { tenantId: 0, id: 2, version: 0, @@ -51,13 +49,13 @@ const state: Partial = { contract: 1, shortId: 'e2', color: '#FFFFFF', - }], + }, ]), }, contractList: { isLoading: false, - contractMapById: new Map([ - [1, { + contractMapById: createIdMapFromList([ + { tenantId: 0, id: 1, version: 0, @@ -66,18 +64,18 @@ const state: Partial = { maximumMinutesPerWeek: null, maximumMinutesPerMonth: 10, maximumMinutesPerYear: null, - }], + }, ]), }, skillList: { isLoading: false, - skillMapById: new Map([ - [3, { + skillMapById: createIdMapFromList([ + { tenantId: 0, id: 3, version: 0, name: 'Skill 3', - }], + }, ]), }, }; @@ -276,19 +274,21 @@ describe('Employee reducers', () => { expect( reducer(state.employeeList, actions.addEmployee(addedEmployee)), ).toEqual({ ...state.employeeList, - employeeMapById: mapWithElement(storeState.employeeList.employeeMapById, addedEmployee) }); + employeeMapById: storeState.employeeList.employeeMapById + .set(addedEmployee.id as number, mapDomainObjectToView(addedEmployee)) }); }); it('remove employee', () => { expect( reducer(state.employeeList, actions.removeEmployee(deletedEmployee)), ).toEqual({ ...state.employeeList, - employeeMapById: mapWithoutElement(storeState.employeeList.employeeMapById, deletedEmployee) }); + employeeMapById: storeState.employeeList.employeeMapById.delete(deletedEmployee.id as number) }); }); it('update employee', () => { expect( reducer(state.employeeList, actions.updateEmployee(updatedEmployee)), ).toEqual({ ...state.employeeList, - employeeMapById: mapWithUpdatedElement(storeState.employeeList.employeeMapById, updatedEmployee) }); + employeeMapById: storeState.employeeList.employeeMapById + .set(updatedEmployee.id as number, mapDomainObjectToView(updatedEmployee)) }); }); it('refresh employee list', () => { expect( @@ -351,22 +351,22 @@ describe('Employee selectors', () => { ...storeState, skillList: { ...storeState.skillList, isLoading: true }, }); - expect(employeeList).toEqual([]); + expect(employeeList).toEqual(List()); employeeList = employeeSelectors.getEmployeeList({ ...storeState, contractList: { ...storeState.contractList, isLoading: true }, }); - expect(employeeList).toEqual([]); + expect(employeeList).toEqual(List()); employeeList = employeeSelectors.getEmployeeList({ ...storeState, employeeList: { ...storeState.employeeList, isLoading: true }, }); - expect(employeeList).toEqual([]); + expect(employeeList).toEqual(List()); }); it('should return a list of all employee', () => { const employeeList = employeeSelectors.getEmployeeList(storeState); - expect(employeeList).toEqual(expect.arrayContaining([ + expect(employeeList.toArray()).toEqual(expect.arrayContaining([ { tenantId: 0, id: 1, @@ -413,6 +413,6 @@ describe('Employee selectors', () => { color: '#FFFFFF', }, ])); - expect(employeeList.length).toEqual(2); + expect(employeeList.size).toEqual(2); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts b/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts index f766b4772..83141cee0 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { - createIdMapFromList, mapDomainObjectToView -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Employee } from 'domain/Employee'; -import { ActionType, EmployeeList, EmployeeAction } from './types'; import { Map } from 'immutable'; +import { ActionType, EmployeeList, EmployeeAction } from './types'; export const initialState: EmployeeList = { isLoading: true, @@ -32,10 +30,10 @@ const employeeReducer = (state = initialState, action: EmployeeAction): Employee case ActionType.SET_EMPLOYEE_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_EMPLOYEE, ActionType.UPDATE_EMPLOYEE: { - return { ...state, employeeMapById: state.employeeMapById.set(action.employee.id as number, - mapDomainObjectToView(action.employee)) - }; + case ActionType.ADD_EMPLOYEE: case ActionType.UPDATE_EMPLOYEE: { + return { ...state, + employeeMapById: state.employeeMapById.set(action.employee.id as number, + mapDomainObjectToView(action.employee)) }; } case ActionType.REMOVE_EMPLOYEE: { return { ...state, employeeMapById: state.employeeMapById.remove(action.employee.id as number) }; diff --git a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts index 9d7c1b9b0..85e353961 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts @@ -17,8 +17,8 @@ import { contractSelectors } from 'store/contract'; import { skillSelectors } from 'store/skill'; import { Employee } from 'domain/Employee'; import DomainObjectView from 'domain/DomainObjectView'; -import { AppState } from '../types'; import { Map, List } from 'immutable'; +import { AppState } from '../types'; export const getEmployeeById = (state: AppState, id: number): Employee => { if (state.employeeList.isLoading || state.skillList.isLoading || state.contractList.isLoading) { diff --git a/optaweb-employee-rostering-frontend/src/store/mockStore.ts b/optaweb-employee-rostering-frontend/src/store/mockStore.ts index c12a81d8f..9a57e2b12 100644 --- a/optaweb-employee-rostering-frontend/src/store/mockStore.ts +++ b/optaweb-employee-rostering-frontend/src/store/mockStore.ts @@ -18,11 +18,11 @@ import * as Redux from 'react-redux'; import createMockStore, { MockStoreCreator } from 'redux-mock-store'; import thunk, { ThunkDispatch } from 'redux-thunk'; import { resetRestClientMock } from 'store/rest/RestTestUtils'; +import { Map, List } from 'immutable'; import RestServiceClient from './rest/RestServiceClient'; import { TenantAction } from './tenant/types'; import { SkillAction } from './skill/types'; import { AppState } from './types'; -import { Map, List } from 'immutable'; jest.mock('./rest/RestServiceClient'); diff --git a/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts b/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts index 5613e730b..d8374da32 100644 --- a/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts @@ -24,11 +24,12 @@ import { Spot } from 'domain/Spot'; import { ShiftRosterView } from 'domain/ShiftRosterView'; import { AvailabilityRosterView } from 'domain/AvailabilityRosterView'; import { Employee } from 'domain/Employee'; -import DomainObjectView from 'domain/DomainObjectView'; import { RosterState } from 'domain/RosterState'; import { serializeLocalDate } from 'store/rest/DataSerialization'; import { flushPromises } from 'setupTests'; import { doNothing } from 'types'; +import { Map, List } from 'immutable'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { availabilityRosterReducer } from './reducers'; import { rosterStateReducer, shiftRosterViewReducer, rosterSelectors, rosterOperations, solverReducer } from './index'; import * as actions from './actions'; @@ -196,13 +197,13 @@ const mockAvailabilityRoster: AvailabilityRosterView = { const state: Partial = { tenantData: { currentTenantId: 0, - tenantList: [], + tenantList: List(), timezoneList: ['America/Toronto'], }, employeeList: { isLoading: false, - employeeMapById: new Map([[ - 20, { + employeeMapById: createIdMapFromList([ + { tenantId: 0, id: 20, version: 0, @@ -212,12 +213,12 @@ const state: Partial = { shortId: 'e', color: '#FFFFFF', }, - ]]), + ]), }, contractList: { isLoading: false, - contractMapById: new Map([[ - 30, { + contractMapById: createIdMapFromList([ + { tenantId: 0, id: 30, version: 0, @@ -227,19 +228,19 @@ const state: Partial = { maximumMinutesPerMonth: null, maximumMinutesPerYear: null, }, - ]]), + ]), }, spotList: { isLoading: false, - spotMapById: new Map([[ - 10, { + spotMapById: createIdMapFromList([ + { tenantId: 0, id: 10, version: 0, name: 'Spot', requiredSkillSet: [], }, - ]]), + ]), }, rosterState: { isLoading: false, @@ -255,7 +256,7 @@ const state: Partial = { }, skillList: { isLoading: false, - skillMapById: new Map(), + skillMapById: Map(), }, }; @@ -288,7 +289,7 @@ describe('Roster operations', () => { }); it('should not dispatch actions or call client if tenantId is negative', async () => { - const { store, client } = mockStore({ tenantData: { currentTenantId: -1, tenantList: [], timezoneList: [] } }); + const { store, client } = mockStore({ tenantData: { currentTenantId: -1, tenantList: List(), timezoneList: [] } }); const pagination = { pageNumber: 0, itemsPerPage: 10, @@ -668,7 +669,7 @@ describe('Roster operations', () => { it('should dispatch actions and call client on getInitialShiftRoster', async () => { const { store, client } = mockStore(state); const tenantId = store.getState().tenantData.currentTenantId; - const spotList: Spot[] = [spotSelectors.getSpotList(store.getState())[0]]; + const spotList: Spot[] = [spotSelectors.getSpotList(store.getState()).get(0) as Spot]; const fromDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) .startOf('week').toDate(); const toDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) @@ -695,7 +696,7 @@ describe('Roster operations', () => { const { store, client } = mockStore({ ...state, spotList: { - spotMapById: new Map>(), + spotMapById: Map(), isLoading: false, }, }); @@ -821,7 +822,7 @@ describe('Roster operations', () => { it('should dispatch actions and call client on getInitialAvailabilityRoster', async () => { const { store, client } = mockStore(state); const tenantId = store.getState().tenantData.currentTenantId; - const employeeList: Employee[] = [employeeSelectors.getEmployeeList(store.getState())[0]]; + const employeeList: Employee[] = [employeeSelectors.getEmployeeList(store.getState()).get(0) as Employee]; const fromDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) .startOf('week').toDate(); const toDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) @@ -857,7 +858,7 @@ describe('Roster operations', () => { const { store, client } = mockStore({ ...state, employeeList: { - employeeMapById: new Map>(), + employeeMapById: Map(), isLoading: false, }, }); diff --git a/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts b/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts index 57f405ca4..1fe4cf2c1 100644 --- a/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { - createIdMapFromList, mapDomainObjectToView -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { TimeBucket } from 'domain/TimeBucket'; -import { ActionType, TimeBucketList, TimeBucketAction } from './types'; import { Map } from 'immutable'; +import { ActionType, TimeBucketList, TimeBucketAction } from './types'; export const initialState: TimeBucketList = { isLoading: true, @@ -32,10 +30,10 @@ const timeBucketReducer = (state = initialState, action: TimeBucketAction): Time case ActionType.SET_TIME_BUCKET_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_TIME_BUCKET, ActionType.UPDATE_TIME_BUCKET: { - return { ...state, timeBucketMapById: state.timeBucketMapById.set(action.timeBucket.id as number, - mapDomainObjectToView(action.timeBucket)) - }; + case ActionType.ADD_TIME_BUCKET: case ActionType.UPDATE_TIME_BUCKET: { + return { ...state, + timeBucketMapById: state.timeBucketMapById.set(action.timeBucket.id as number, + mapDomainObjectToView(action.timeBucket)) }; } case ActionType.REMOVE_TIME_BUCKET: { return { ...state, timeBucketMapById: state.timeBucketMapById.remove(action.timeBucket.id as number) }; diff --git a/optaweb-employee-rostering-frontend/src/store/rotation/rotation.test.ts b/optaweb-employee-rostering-frontend/src/store/rotation/rotation.test.ts index 9591a6d26..a989cb412 100644 --- a/optaweb-employee-rostering-frontend/src/store/rotation/rotation.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/rotation/rotation.test.ts @@ -16,8 +16,7 @@ import { alert } from 'store/alert'; import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, + createIdMapFromList, mapDomainObjectToView, } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete, onPut } from 'store/rest/RestTestUtils'; @@ -28,6 +27,7 @@ import { TimeBucketView, } from 'store/rotation/TimeBucketView'; import moment from 'moment'; +import { Map } from 'immutable'; import reducer, { timeBucketSelectors, timeBucketOperations } from './index'; import * as actions from './actions'; import { AppState } from '../types'; @@ -36,12 +36,12 @@ import { mockStore } from '../mockStore'; const state: Partial = { skillList: { isLoading: false, - skillMapById: new Map(), + skillMapById: Map(), }, employeeList: { isLoading: false, - employeeMapById: new Map([ - [3, { + employeeMapById: createIdMapFromList([ + { tenantId: 0, id: 3, version: 0, @@ -50,13 +50,13 @@ const state: Partial = { skillProficiencySet: [], shortId: 'e', color: '#FFFFFF', - }], + }, ]), }, contractList: { isLoading: false, - contractMapById: new Map([ - [10, { + contractMapById: createIdMapFromList([ + { tenantId: 0, id: 10, version: 0, @@ -65,25 +65,25 @@ const state: Partial = { maximumMinutesPerWeek: null, maximumMinutesPerMonth: null, maximumMinutesPerYear: null, - }], + }, ]), }, spotList: { isLoading: false, - spotMapById: new Map([ - [1, { + spotMapById: createIdMapFromList([ + { tenantId: 0, id: 1, version: 0, name: 'Spot', requiredSkillSet: [], - }], + }, ]), }, timeBucketList: { isLoading: false, - timeBucketMapById: new Map([ - [2, { + timeBucketMapById: createIdMapFromList([ + { tenantId: 0, id: 2, version: 0, @@ -93,8 +93,8 @@ const state: Partial = { seatList: [{ dayInRotation: 0, employee: 3 }], startTime: moment('09:00', 'HH:mm').toDate(), endTime: moment('17:00', 'HH:mm').toDate(), - }], - [4, { + }, + { tenantId: 0, id: 4, version: 0, @@ -104,7 +104,7 @@ const state: Partial = { seatList: [{ dayInRotation: 0, employee: 3 }], startTime: moment('17:00', 'HH:mm').toDate(), endTime: moment('09:00', 'HH:mm').toDate(), - }], + }, ]), }, }; @@ -330,21 +330,22 @@ describe('Rotation reducers', () => { reducer(storeState.timeBucketList, actions.addTimeBucket(mapDomainObjectToView(addedTimeBucket))), ).toEqual({ ...storeState.timeBucketList, timeBucketMapById: - mapWithElement(storeState.timeBucketList.timeBucketMapById, mapDomainObjectToView(addedTimeBucket)) }); + storeState.timeBucketList.timeBucketMapById + .set(addedTimeBucket.id as number, mapDomainObjectToView(addedTimeBucket)) }); }); it('remove shift template', () => { expect( reducer(storeState.timeBucketList, actions.removeTimeBucket(mapDomainObjectToView(deletedTimeBucket))), ).toEqual({ ...storeState.timeBucketList, timeBucketMapById: - mapWithoutElement(storeState.timeBucketList.timeBucketMapById, mapDomainObjectToView(deletedTimeBucket)) }); + storeState.timeBucketList.timeBucketMapById.delete(deletedTimeBucket.id as number) }); }); it('update shift template', () => { expect( reducer(storeState.timeBucketList, actions.updateTimeBucket(mapDomainObjectToView(updatedTimeBucket))), ).toEqual({ ...storeState.timeBucketList, timeBucketMapById: - mapWithUpdatedElement(storeState.timeBucketList.timeBucketMapById, + storeState.timeBucketList.timeBucketMapById.set(updatedTimeBucket.id as number, mapDomainObjectToView(updatedTimeBucket)) }); }); it('refresh shift template list', () => { diff --git a/optaweb-employee-rostering-frontend/src/store/shift/shift.test.ts b/optaweb-employee-rostering-frontend/src/store/shift/shift.test.ts index 25abec693..bcde24162 100644 --- a/optaweb-employee-rostering-frontend/src/store/shift/shift.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/shift/shift.test.ts @@ -21,6 +21,7 @@ import { Shift } from 'domain/Shift'; import moment from 'moment'; import { serializeLocalDateTime } from 'store/rest/DataSerialization'; import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { shiftAdapter, KindaShiftView, kindaShiftViewAdapter } from './KindaShiftView'; import { shiftOperations } from './index'; import { AppState } from '../types'; @@ -330,19 +331,19 @@ describe('shift adapters', () => { const state: Partial = { skillList: { isLoading: false, - skillMapById: new Map([ - [1234, { + skillMapById: createIdMapFromList([ + { tenantId: 0, id: 1234, version: 0, name: 'Skill 2', - }], - [2312, { + }, + { tenantId: 0, id: 2312, version: 1, name: 'Skill 3', - }], + }, ]), }, }; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts b/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts index 766685a0c..ee1ec468e 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { - createIdMapFromList -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Skill } from 'domain/Skill'; -import { ActionType, SkillList, SkillAction } from './types'; import { Map } from 'immutable'; +import { ActionType, SkillList, SkillAction } from './types'; export const initialState: SkillList = { isLoading: true, @@ -32,7 +30,7 @@ const skillReducer = (state = initialState, action: SkillAction): SkillList => { case ActionType.SET_SKILL_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_SKILL, ActionType.UPDATE_SKILL: { + case ActionType.ADD_SKILL: case ActionType.UPDATE_SKILL: { return { ...state, skillMapById: state.skillMapById.set(action.skill.id as number, action.skill) }; } case ActionType.REMOVE_SKILL: { diff --git a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts index c5c66bedc..103d64cf5 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts @@ -14,9 +14,9 @@ * limitations under the License. */ import { Skill } from 'domain/Skill'; -import { AppState } from '../types'; import { List, Map } from 'immutable'; import DomainObjectView from 'domain/DomainObjectView'; +import { AppState } from '../types'; export const getSkillById = (state: AppState, id: number): Skill => { if (state.skillList.isLoading) { @@ -37,7 +37,7 @@ export const getSkillList = (state: AppState): List => { return skillListForOldSkillMapById; } - const out = state.skillList.skillMapById.keySeq().map((key) => getSkillById(state, key)).toList(); + const out = state.skillList.skillMapById.keySeq().map(key => getSkillById(state, key)).toList(); oldSkillMapById = state.skillList.skillMapById; skillListForOldSkillMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts b/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts index 483b6efaf..c310d6f33 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts @@ -15,12 +15,10 @@ */ import { alert } from 'store/alert'; -import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete } from 'store/rest/RestTestUtils'; import { Skill } from 'domain/Skill'; +import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -29,19 +27,19 @@ import reducer, { skillSelectors, skillOperations } from './index'; const state: Partial = { skillList: { isLoading: false, - skillMapById: new Map([ - [1234, { + skillMapById: createIdMapFromList([ + { tenantId: 0, id: 1234, version: 0, name: 'Skill 2', - }], - [2312, { + }, + { tenantId: 0, id: 2312, version: 1, name: 'Skill 3', - }], + }, ]), }, }; @@ -152,14 +150,16 @@ describe('Skill reducers', () => { it('add skill', () => { expect( reducer(storeState.skillList, actions.addSkill(addedSkill)), - ).toEqual({ ...storeState.skillList, skillMapById: mapWithElement(storeState.skillList.skillMapById, addedSkill) }); + ).toEqual({ ...storeState.skillList, + skillMapById: storeState.skillList.skillMapById + .set(addedSkill.id as number, addedSkill) }); }); it('remove skill', () => { expect( reducer(storeState.skillList, actions.removeSkill(deletedSkill)), ).toEqual({ ...storeState.skillList, - skillMapById: mapWithoutElement(storeState.skillList.skillMapById, deletedSkill), + skillMapById: storeState.skillList.skillMapById.delete(deletedSkill.id as number), }); }); it('update skill', () => { @@ -167,7 +167,7 @@ describe('Skill reducers', () => { reducer(storeState.skillList, actions.updateSkill(updatedSkill)), ).toEqual({ ...storeState.skillList, - skillMapById: mapWithUpdatedElement(storeState.skillList.skillMapById, updatedSkill), + skillMapById: storeState.skillList.skillMapById.set(updatedSkill.id as number, updatedSkill), }); }); it('refresh skill list', () => { @@ -202,12 +202,12 @@ describe('Skill selectors', () => { ...storeState, skillList: { ...storeState.skillList, isLoading: true }, }); - expect(skillList).toEqual([]); + expect(skillList).toEqual(List()); }); it('should return a list of all skills', () => { const skillList = skillSelectors.getSkillList(storeState); - expect(skillList).toEqual(expect.arrayContaining([ + expect(skillList.toArray()).toEqual(expect.arrayContaining([ { tenantId: 0, id: 1234, @@ -221,6 +221,6 @@ describe('Skill selectors', () => { name: 'Skill 3', }, ])); - expect(skillList.length).toEqual(2); + expect(skillList.size).toEqual(2); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts b/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts index 31c432219..86f2a214d 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { - createIdMapFromList, mapDomainObjectToView -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import DomainObjectView from 'domain/DomainObjectView'; import { Spot } from 'domain/Spot'; -import { ActionType, SpotList, SpotAction } from './types'; import { Map } from 'immutable'; +import { ActionType, SpotList, SpotAction } from './types'; export const initialState: SpotList = { isLoading: true, @@ -32,10 +30,10 @@ const spotReducer = (state = initialState, action: SpotAction): SpotList => { case ActionType.SET_SPOT_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_SPOT, ActionType.UPDATE_SPOT: { - return { ...state, spotMapById: state.spotMapById.set(action.spot.id as number, - mapDomainObjectToView(action.spot)) - }; + case ActionType.ADD_SPOT: case ActionType.UPDATE_SPOT: { + return { ...state, + spotMapById: state.spotMapById.set(action.spot.id as number, + mapDomainObjectToView(action.spot)) }; } case ActionType.REMOVE_SPOT: { return { ...state, spotMapById: state.spotMapById.remove(action.spot.id as number) }; diff --git a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts index 552a0577f..11b660aeb 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts @@ -16,8 +16,8 @@ import { skillSelectors } from 'store/skill'; import { Spot } from 'domain/Spot'; import DomainObjectView from 'domain/DomainObjectView'; -import { AppState } from '../types'; import { Map, List } from 'immutable'; +import { AppState } from '../types'; export const getSpotById = (state: AppState, id: number): Spot => { if (state.spotList.isLoading || state.skillList.isLoading) { @@ -41,7 +41,7 @@ export const getSpotList = (state: AppState): List => { return spotListForOldSpotMapById; } - const out = state.spotList.spotMapById.keySeq().map((key) => getSpotById(state, key)).toList(); + const out = state.spotList.spotMapById.keySeq().map(key => getSpotById(state, key)).toList(); oldSpotMapById = state.spotList.spotMapById; spotListForOldSpotMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts b/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts index a8f5767f7..b4369c6c7 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts @@ -15,12 +15,10 @@ */ import { alert } from 'store/alert'; -import { - createIdMapFromList, mapWithElement, mapWithoutElement, - mapWithUpdatedElement, -} from 'util/ImmutableCollectionOperations'; +import { createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete } from 'store/rest/RestTestUtils'; import { Spot } from 'domain/Spot'; +import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -29,32 +27,32 @@ import reducer, { spotSelectors, spotOperations } from './index'; const state: Partial = { spotList: { isLoading: false, - spotMapById: new Map([ - [1234, { + spotMapById: createIdMapFromList([ + { tenantId: 0, id: 1234, version: 1, name: 'Spot 2', requiredSkillSet: [1], - }], - [2312, { + }, + { tenantId: 0, id: 2312, version: 0, name: 'Spot 3', requiredSkillSet: [], - }], + }, ]), }, skillList: { isLoading: false, - skillMapById: new Map([ - [1, { + skillMapById: createIdMapFromList([ + { tenantId: 0, id: 1, version: 0, name: 'Skill 1', - }], + }, ]), }, }; @@ -205,19 +203,19 @@ describe('Spot reducers', () => { expect( reducer(storeState.spotList, actions.addSpot(addedSpot)), ).toEqual({ ...storeState.spotList, - spotMapById: mapWithElement(storeState.spotList.spotMapById, addedSpot) }); + spotMapById: storeState.spotList.spotMapById.set(addedSpot.id as number, mapDomainObjectToView(addedSpot)) }); }); it('remove spot', () => { expect( reducer(storeState.spotList, actions.removeSpot(deletedSpot)), ).toEqual({ ...storeState.spotList, - spotMapById: mapWithoutElement(storeState.spotList.spotMapById, deletedSpot) }); + spotMapById: storeState.spotList.spotMapById.delete(deletedSpot.id as number) }); }); it('update spot', () => { expect( reducer(storeState.spotList, actions.updateSpot(updatedSpot)), ).toEqual({ ...storeState.spotList, - spotMapById: mapWithUpdatedElement(storeState.spotList.spotMapById, updatedSpot) }); + spotMapById: storeState.spotList.spotMapById.set(updatedSpot.id as number, mapDomainObjectToView(updatedSpot)) }); }); it('refresh spot list', () => { expect( @@ -265,17 +263,17 @@ describe('Spot selectors', () => { ...storeState, skillList: { ...storeState.skillList, isLoading: true }, }); - expect(spotList).toEqual([]); + expect(spotList).toEqual(List()); spotList = spotSelectors.getSpotList({ ...storeState, spotList: { ...storeState.spotList, isLoading: true }, }); - expect(spotList).toEqual([]); + expect(spotList).toEqual(List()); }); it('should return a list of all spots', () => { const spotList = spotSelectors.getSpotList(storeState); - expect(spotList).toEqual(expect.arrayContaining([ + expect(spotList.toArray()).toEqual(expect.arrayContaining([ { tenantId: 0, id: 1234, @@ -298,6 +296,6 @@ describe('Spot selectors', () => { requiredSkillSet: [], }, ])); - expect(spotList.length).toEqual(2); + expect(spotList.size).toEqual(2); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/store.ts b/optaweb-employee-rostering-frontend/src/store/store.ts index a0baecfa5..8be792751 100644 --- a/optaweb-employee-rostering-frontend/src/store/store.ts +++ b/optaweb-employee-rostering-frontend/src/store/store.ts @@ -45,7 +45,7 @@ export function configureStore( ): Store { const restServiceClient = new RestServiceClient(restBaseURL, axios); - const middlewares = [thunk.withExtraArgument(restServiceClient), /* createLogger() */]; + const middlewares = [thunk.withExtraArgument(restServiceClient), createLogger()]; const middlewareEnhancer = applyMiddleware(...middlewares); const enhancers = [middlewareEnhancer]; diff --git a/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts b/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts index 85980189b..2757f6951 100644 --- a/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/tenant/reducers.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { ActionType, TenantData, TenantAction, ConnectAction, ConnectionActionType } from './types'; import { List } from 'immutable'; +import { ActionType, TenantData, TenantAction, ConnectAction, ConnectionActionType } from './types'; const initialState: TenantData = { currentTenantId: 0, diff --git a/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts b/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts index 384607a20..b7d53142b 100644 --- a/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts @@ -21,10 +21,11 @@ import { Tenant } from 'domain/Tenant'; import moment from 'moment'; import { timeBucketOperations } from 'store/rotation'; import { RosterState } from 'domain/RosterState'; -import * as immutableOperations from 'util/ImmutableCollectionOperations'; import { flushPromises } from 'setupTests'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { doNothing } from 'types'; +import { Map, List } from 'immutable'; +import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -43,7 +44,7 @@ import reducer, { tenantOperations } from './index'; const state: Partial = { tenantData: { currentTenantId: 0, - tenantList: [ + tenantList: List([ { id: 0, version: 0, @@ -54,7 +55,7 @@ const state: Partial = { version: 0, name: 'Tenant 1', }, - ], + ]), timezoneList: ['America/Toronto'], }, isConnected: true, @@ -250,7 +251,7 @@ describe('Tenant operations', () => { const newState: AppState = { tenantData: { currentTenantId: 0, - tenantList: [ + tenantList: List([ { id: 0, version: 0, @@ -261,13 +262,13 @@ describe('Tenant operations', () => { version: 0, name: 'Tenant 1', }, - ], + ]), timezoneList: ['America/Toronto'], }, employeeList: { isLoading: false, - employeeMapById: new Map([ - [10, { + employeeMapById: createIdMapFromList([ + { tenantId: 0, id: 10, version: 0, @@ -276,24 +277,24 @@ describe('Tenant operations', () => { skillProficiencySet: [], shortId: 'A', color: '#FFFFFF', - }], + }, ]), }, contractList: { isLoading: false, - contractMapById: new Map(), + contractMapById: Map(), }, spotList: { isLoading: false, - spotMapById: new Map(), + spotMapById: Map(), }, skillList: { isLoading: false, - skillMapById: new Map(), + skillMapById: Map(), }, timeBucketList: { isLoading: false, - timeBucketMapById: new Map(), + timeBucketMapById: Map(), }, rosterState: { isLoading: false, @@ -321,7 +322,7 @@ describe('Tenant operations', () => { solverStatus: 'NOT_SOLVING', }, alerts: { - alertList: [], + alertList: List(), idGeneratorIndex: 0, }, isConnected: true, @@ -468,7 +469,7 @@ describe('Tenant reducers', () => { it('refresh tenant list', () => { expect( reducer(storeState.tenantData, actions.refreshTenantList({ currentTenantId: 1, tenantList: newTenantList })), - ).toEqual({ ...storeState.tenantData, currentTenantId: 1, tenantList: newTenantList }); + ).toEqual({ ...storeState.tenantData, currentTenantId: 1, tenantList: List(newTenantList) }); }); it('change tenant', () => { expect( @@ -479,15 +480,14 @@ describe('Tenant reducers', () => { expect( reducer(storeState.tenantData, actions.addTenant(newTenantList[2])), ).toEqual({ ...storeState.tenantData, - tenantList: immutableOperations.withElement(storeState.tenantData.tenantList, newTenantList[2]) }); + tenantList: storeState.tenantData.tenantList.set(2, newTenantList[2]) }); }); it('removeTenant', () => { expect( - reducer(storeState.tenantData, actions.removeTenant(storeState.tenantData.tenantList[0])), + reducer(storeState.tenantData, actions.removeTenant(storeState.tenantData.tenantList.get(0) as Tenant)), ).toEqual({ ...storeState.tenantData, - tenantList: immutableOperations.withoutElement(storeState.tenantData.tenantList, - storeState.tenantData.tenantList[0]), + tenantList: storeState.tenantData.tenantList.delete(0), }); }); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx b/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx index 4cb0631f0..9807f0436 100644 --- a/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx @@ -21,6 +21,7 @@ import { mockTranslate } from 'setupTests'; import { AlertComponent } from 'store/alert/types'; import moment from 'moment'; import { ServerSideExceptionInfo, BasicObject } from 'types'; +import { List } from 'immutable'; import { Alerts, Props, mapToComponent } from './Alerts'; describe('Alerts', () => { @@ -74,18 +75,18 @@ describe('Alerts', () => { }); const noAlerts: Props = { - alerts: [], + alerts: List(), removeAlert: jest.fn(), }; const date = new Date(); // setupTests set the date to a mock date for us const someAlerts: Props = { - alerts: [ + alerts: List([ { id: 0, createdAt: date, i18nKey: 'infoMessage', - variant: 'info', + variant: 'info' as 'info', params: {}, components: [], componentProps: [], @@ -94,7 +95,7 @@ const someAlerts: Props = { id: 1, createdAt: moment(date).add(4, 'seconds').toDate(), i18nKey: 'successMessage', - variant: 'success', + variant: 'success' as 'success', params: {}, components: [], componentProps: [], @@ -103,22 +104,22 @@ const someAlerts: Props = { id: 2, createdAt: moment(date).add(11, 'seconds').toDate(), i18nKey: 'dangerMessage', - variant: 'danger', + variant: 'danger' as 'danger', params: {}, components: [], componentProps: [], }, - ], + ]), removeAlert: jest.fn(), }; const someAlertsWithComponents: Props = { - alerts: [ + alerts: List([ { id: 0, createdAt: date, i18nKey: 'exception', - variant: 'danger', + variant: 'danger' as 'danger', params: {}, components: [AlertComponent.SERVER_SIDE_EXCEPTION_DIALOG], componentProps: [{ @@ -137,6 +138,6 @@ const someAlertsWithComponents: Props = { }, }], }, - ], + ]), removeAlert: jest.fn(), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx b/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx index 4b805a311..943cd0949 100644 --- a/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx @@ -24,8 +24,8 @@ import * as alertOperations from 'store/alert/operations'; import moment from 'moment'; import { useInterval } from 'util/FunctionalComponentUtils'; import { BasicObject, ServerSideExceptionInfo } from 'types'; -import { ServerSideExceptionDialog } from './components/ServerSideExceptionDialog'; import { List } from 'immutable'; +import { ServerSideExceptionDialog } from './components/ServerSideExceptionDialog'; interface StateProps { alerts: List; diff --git a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx index 7896d9034..844b3ae1e 100644 --- a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx @@ -19,6 +19,7 @@ import * as React from 'react'; import { stringSorter } from 'util/CommonSorters'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; import { DataTable, DataTableProps, DataTableUrlProps } from './DataTable'; interface MockData {name: string; number: number} @@ -61,11 +62,11 @@ describe('DataTable component', () => { const dataTable = new MockDataTable(twoRows); dataTable.render(); expect(dataTable.displayDataRow).toBeCalledTimes(2); - expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData[0]); - expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData[1]); + expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData.get(0)); + expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData.get(1)); expect(dataTable.editDataRow).toBeCalledTimes(2); - expect(dataTable.editDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData[0], expect.any(Function)); - expect(dataTable.editDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData[1], expect.any(Function)); + expect(dataTable.editDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData.get(0), expect.any(Function)); + expect(dataTable.editDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData.get(1), expect.any(Function)); }); it('should render viewer initially', () => { @@ -103,15 +104,15 @@ describe('DataTable component', () => { expect((dataTable.instance() as MockDataTable).getInitialStateForNewRow).toBeCalled(); expect((dataTable.instance() as MockDataTable).displayDataRow).toBeCalledTimes(2); - expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData[0]); - expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData[1]); + expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData.get(0)); + expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData.get(1)); expect((dataTable.instance() as MockDataTable).editDataRow).toBeCalledTimes(3); expect((dataTable.instance() as MockDataTable).editDataRow) .toHaveBeenNthCalledWith(1, {}, expect.any(Function)); expect((dataTable.instance() as MockDataTable).editDataRow) - .toHaveBeenNthCalledWith(2, twoRows.tableData[0], expect.any(Function)); + .toHaveBeenNthCalledWith(2, twoRows.tableData.get(0), expect.any(Function)); expect((dataTable.instance() as MockDataTable).editDataRow) - .toHaveBeenNthCalledWith(3, twoRows.tableData[1], expect.any(Function)); + .toHaveBeenNthCalledWith(3, twoRows.tableData.get(1), expect.any(Function)); expect(toJson(shallow(
{dataTable.instance().render()}
))).toMatchSnapshot(); }); @@ -318,7 +319,7 @@ const noRows: DataTableProps = { tReady: true, title: 'Data Table', columnTitles: ['Column 1', 'Column 2'], - tableData: [], + tableData: List(), ...getRouterProps('/table', {}), }; @@ -327,6 +328,6 @@ const twoRows: DataTableProps = { tReady: true, title: 'Data Table', columnTitles: ['Column 1', 'Column 2'], - tableData: [{ name: 'Some Data', number: 1 }, { name: 'More Data', number: 2 }], + tableData: List([{ name: 'Some Data', number: 1 }, { name: 'More Data', number: 2 }]), ...getRouterProps('/table', {}), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx index bfc63117b..f86c459e4 100644 --- a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx @@ -32,9 +32,9 @@ import { toggleElement, conditionally } from 'util/ImmutableCollectionOperations import { WithTranslation } from 'react-i18next'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { RouteComponentProps } from 'react-router'; +import { List } from 'immutable'; import FilterComponent from './FilterComponent'; import { EditableComponent } from './EditableComponent'; -import { List } from 'immutable'; export interface DataTableProps extends WithTranslation, RouteComponentProps { title: string; @@ -278,7 +278,8 @@ export abstract class DataTable> extends React.Co page: '1', itemsPerPage: '10', filter: null, - sortBy: this.getSorters().filter(x => x !== null).length > 0? `${this.getSorters().findIndex(x => x !== null)}` : null, + sortBy: this.getSorters().filter(x => x !== null).length > 0 + ? `${this.getSorters().findIndex(x => x !== null)}` : null, asc: 'true', }); const [page, perPage] = [parseInt(urlProps.page as string, 10), parseInt(urlProps.itemsPerPage as string, 10)]; @@ -295,7 +296,7 @@ export abstract class DataTable> extends React.Co }, ]) : List(); const sorters = this.getSorters(); - + const filteredRows = conditionally(this.props.tableData.valueSeq(), // eslint-disable-next-line consistent-return (s) => { @@ -306,18 +307,20 @@ export abstract class DataTable> extends React.Co // eslint-disable-next-line consistent-return (s) => { if (urlProps.asc !== 'true') { - return s.reverse(); + return s.reverse(); } - }).then( + }, + ).then( // eslint-disable-next-line consistent-return (s) => { if (urlProps.filter !== null) { return s.filter(this.getFilter()(urlProps.filter)); } - }).result; + }, + ).result; const rowsThatMatchFilterCount = filteredRows.count(); - + const rows = additionalRows.concat(filteredRows .skip((page - 1) * perPage) .take(perPage) diff --git a/optaweb-employee-rostering-frontend/src/ui/components/__snapshots__/DataTable.test.tsx.snap b/optaweb-employee-rostering-frontend/src/ui/components/__snapshots__/DataTable.test.tsx.snap index 032429f90..8aa4bb301 100644 --- a/optaweb-employee-rostering-frontend/src/ui/components/__snapshots__/DataTable.test.tsx.snap +++ b/optaweb-employee-rostering-frontend/src/ui/components/__snapshots__/DataTable.test.tsx.snap @@ -299,7 +299,12 @@ exports[`DataTable component should only render data on page 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -381,7 +386,12 @@ exports[`DataTable component should only render data on page 2`] = ` } onSort={[Function]} rows={Array []} - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -688,7 +698,12 @@ exports[`DataTable component should only render rows that match filter 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -995,7 +1010,12 @@ exports[`DataTable component should only render rows that match filter 2`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -1302,7 +1322,12 @@ exports[`DataTable component should only render rows that match filter 3`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -1609,7 +1634,12 @@ exports[`DataTable component should render correctly with a few rows 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -1691,7 +1721,12 @@ exports[`DataTable component should render correctly with no rows 1`] = ` } onSort={[Function]} rows={Array []} - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -2038,7 +2073,12 @@ exports[`DataTable component should render editor for new row 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > @@ -2969,7 +3009,12 @@ exports[`DataTable component should render viewer initially 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 1, + } + } > diff --git a/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.test.tsx b/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.test.tsx index 71cd14111..2b20ec244 100644 --- a/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/header/Toolbar.test.tsx @@ -17,6 +17,7 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; import { ToolbarComponent, Props } from './Toolbar'; describe('Toolbar Component', () => { @@ -61,7 +62,7 @@ describe('Toolbar Component', () => { }); const noTenants: Props = { - tenantList: [], + tenantList: List(), currentTenantId: 0, refreshTenantList: jest.fn(), changeTenant: jest.fn(), @@ -69,7 +70,7 @@ const noTenants: Props = { }; const twoTenants: Props = { - tenantList: [ + tenantList: List([ { id: 1, version: 0, @@ -80,7 +81,7 @@ const twoTenants: Props = { version: 0, name: 'Tenant 2', }, - ], + ]), currentTenantId: 2, refreshTenantList: jest.fn(), changeTenant: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.test.tsx index d3ea6b981..0dd7bbd31 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/admin/AdminPage.test.tsx @@ -21,6 +21,7 @@ import { Tenant } from 'domain/Tenant'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { DataTableUrlProps } from 'ui/components/DataTable'; import { Button } from '@patternfly/react-core'; +import { List } from 'immutable'; import { AdminPage, Props } from './AdminPage'; describe('Admin Page', () => { @@ -90,7 +91,7 @@ describe('Admin Page', () => { )[1].cells[1]).find(Button).simulate('click'); }); expect(twoTenants.removeTenant).toBeCalled(); - expect(twoTenants.removeTenant).toBeCalledWith(twoTenants.tenantList[1]); + expect(twoTenants.removeTenant).toBeCalledWith(twoTenants.tenantList.get(1)); }); it('should not call remove tenant when the delete tenant button is clicked for the current tenant', () => { @@ -123,7 +124,7 @@ function generateProps(numberOfTenants: number, urlProps: Partial = (props) => { const numOfFilteredRows = filteredRows.size; const rowsInPage = filteredRows - .skip(page * itemsPerPage) + .skip((page - 1) * itemsPerPage) .take(itemsPerPage); return ( diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx index bcdf780b5..78879a958 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx @@ -27,6 +27,7 @@ import { useTranslation, Trans } from 'react-i18next'; import Actions from 'ui/components/Actions'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import Schedule from 'ui/components/calendar/Schedule'; +import { List } from 'immutable'; import { AvailabilityRosterPage, Props, ShiftOrAvailability, isShift, isAvailability, isAllDayAvailability, isDay, AvailabilityRosterUrlProps, @@ -48,8 +49,8 @@ describe('Availability Roster Page', () => { const availabilityRosterPage = shallow(); @@ -59,8 +60,8 @@ describe('Availability Roster Page', () => { it('should render correctly when there are no employees', () => { const availabilityRosterPage = shallow(); @@ -97,7 +98,7 @@ describe('Availability Roster Page', () => { expect(baseProps.getAvailabilityRosterFor).toBeCalledWith({ fromDate: newDateStart, toDate: newDateEnd, - employeeList: baseProps.shownEmployeeList, + employeeList: baseProps.shownEmployeeList.toArray(), }); }); @@ -118,8 +119,8 @@ describe('Availability Roster Page', () => { it('should go to the Employees page if the user click on the link', () => { const availabilityRosterPage = shallow(); @@ -639,8 +640,8 @@ const baseProps: Props = { isLoading: false, totalNumOfSpots: 1, rosterState, - allEmployeeList: [employee, newEmployee], - shownEmployeeList: [employee], + allEmployeeList: List([employee, newEmployee]), + shownEmployeeList: List([employee]), employeeIdToShiftListMap: new Map([ [4, [shift]], ]), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx index b94a318c2..8496faa99 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx @@ -40,11 +40,11 @@ import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { UrlProps, setPropsInUrl, getPropsFromUrl } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; +import { List } from 'immutable'; import AvailabilityEvent, { AvailabilityPopoverHeader, AvailabilityPopoverBody } from './AvailabilityEvent'; import EditAvailabilityModal from './EditAvailabilityModal'; import ShiftEvent, { ShiftPopupHeader, ShiftPopupBody } from '../shift/ShiftEvent'; import EditShiftModal from '../shift/EditShiftModal'; -import { List } from 'immutable'; interface StateProps { tenantId: number; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx index 994dd1ebc..97dba1abe 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx @@ -20,6 +20,7 @@ import { Employee } from 'domain/Employee'; import moment from 'moment'; import { EmployeeAvailability } from 'domain/EmployeeAvailability'; import { useTranslation } from 'react-i18next'; +import { List } from 'immutable'; import { EditAvailabilityModal } from './EditAvailabilityModal'; describe('Edit Availability Modal', () => { @@ -323,5 +324,5 @@ const baseProps = { tReady: true, tenantId: 1, isOpen: true, - employeeList: [employee], + employeeList: List([employee]), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx index a9840db97..107042499 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx @@ -21,6 +21,7 @@ import { Sorter } from 'types'; import { Contract } from 'domain/Contract'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; import { ContractsPage, Props } from './ContractsPage'; describe('Contracts page', () => { @@ -36,14 +37,14 @@ describe('Contracts page', () => { it('should render the viewer correctly', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData[1]; + const contract = twoContracts.tableData.get(1) as Contract; const viewer = shallow(contractsPage.renderViewer(contract)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData[1]; + const contract = twoContracts.tableData.get(1) as Contract; const editor = shallow(contractsPage.renderEditor(contract)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -104,7 +105,7 @@ describe('Contracts page', () => { it('should call addContract on addData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData[1]; + const contract = twoContracts.tableData.get(1) as Contract; contractsPage.addData(contract); expect(twoContracts.addContract).toBeCalled(); expect(twoContracts.addContract).toBeCalledWith(contract); @@ -112,7 +113,7 @@ describe('Contracts page', () => { it('should call updateContract on updateData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData[1]; + const contract = twoContracts.tableData.get(1) as Contract; contractsPage.updateData(contract); expect(twoContracts.updateContract).toBeCalled(); expect(twoContracts.updateContract).toBeCalledWith(contract); @@ -120,7 +121,7 @@ describe('Contracts page', () => { it('should call removeContract on removeData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData[1]; + const contract = twoContracts.tableData.get(1) as Contract; contractsPage.removeData(contract); expect(twoContracts.removeContract).toBeCalled(); expect(twoContracts.removeContract).toBeCalledWith(contract); @@ -130,55 +131,55 @@ describe('Contracts page', () => { const contractPage = new ContractsPage(twoContracts); const filter = contractPage.getFilter(); - expect(twoContracts.tableData.filter(filter('1'))).toEqual([twoContracts.tableData[0]]); - expect(twoContracts.tableData.filter(filter('2'))).toEqual([twoContracts.tableData[1]]); + expect(twoContracts.tableData.filter(filter('1'))).toEqual(List([twoContracts.tableData.get(0) as Contract])); + expect(twoContracts.tableData.filter(filter('2'))).toEqual(List([twoContracts.tableData.get(1) as Contract])); }); it('should return a sorter that sort by name', () => { const contractPage = new ContractsPage(twoContracts); const sorter = contractPage.getSorters()[0] as Sorter; - const list = [twoContracts.tableData[1], twoContracts.tableData[0]]; - expect(list.sort(sorter)).toEqual(twoContracts.tableData); + const list = [twoContracts.tableData.get(1) as Contract, twoContracts.tableData.get(0) as Contract]; + expect(list.sort(sorter)).toEqual(twoContracts.tableData.toArray()); }); it('should treat incomplete data as incomplete', () => { const contractsPage = new ContractsPage(twoContracts); - const noName = { ...twoContracts.tableData[1], name: undefined }; + const noName = { ...twoContracts.tableData.get(1) as Contract, name: undefined }; const result1 = contractsPage.isDataComplete(noName); expect(result1).toEqual(false); - const noMaxHoursPerDay = { ...twoContracts.tableData[1], maximumMinutesPerDay: undefined }; + const noMaxHoursPerDay = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerDay: undefined }; const result2 = contractsPage.isDataComplete(noMaxHoursPerDay); expect(result2).toEqual(false); - const noMaxHoursPerWeek = { ...twoContracts.tableData[1], maximumMinutesPerWeek: undefined }; + const noMaxHoursPerWeek = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerWeek: undefined }; const result3 = contractsPage.isDataComplete(noMaxHoursPerWeek); expect(result3).toEqual(false); - const noMaxHoursPerMonth = { ...twoContracts.tableData[1], maximumMinutesPerMonth: undefined }; + const noMaxHoursPerMonth = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerMonth: undefined }; const result4 = contractsPage.isDataComplete(noMaxHoursPerMonth); expect(result4).toEqual(false); - const noMaxHoursPerYear = { ...twoContracts.tableData[1], maximumMinutesPerYear: undefined }; + const noMaxHoursPerYear = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerYear: undefined }; const result5 = contractsPage.isDataComplete(noMaxHoursPerYear); expect(result5).toEqual(false); - const completed = { ...twoContracts.tableData[1] }; + const completed = { ...twoContracts.tableData.get(1) as Contract }; const result6 = contractsPage.isDataComplete(completed); expect(result6).toEqual(true); }); it('should treat empty name as invalid', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = { ...twoContracts.tableData[1], name: '' }; + const contract = { ...twoContracts.tableData.get(1) as Contract, name: '' }; const result = contractsPage.isValid(contract); expect(result).toEqual(false); }); it('should treat non-empty name as valid', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = { ...twoContracts.tableData[1], name: 'Contract' }; + const contract = { ...twoContracts.tableData.get(1) as Contract, name: 'Contract' }; const result = contractsPage.isValid(contract); expect(result).toEqual(true); }); @@ -190,7 +191,7 @@ const noContracts: Props = { tenantId: 0, title: 'Contracts', columnTitles: ['Name', 'Max Hours Per Day', 'Max Hours Per Week', 'Max Hours Per Month', 'Max Hours Per Year'], - tableData: [], + tableData: List(), addContract: jest.fn(), updateContract: jest.fn(), removeContract: jest.fn(), @@ -203,7 +204,7 @@ const twoContracts: Props = { tenantId: 0, title: 'Contracts', columnTitles: ['Name', 'Max Hours Per Day', 'Max Hours Per Week', 'Max Hours Per Month', 'Max Hours Per Year'], - tableData: [{ + tableData: List([{ id: 0, version: 0, tenantId: 0, @@ -222,7 +223,7 @@ const twoContracts: Props = { maximumMinutesPerWeek: 20, maximumMinutesPerMonth: 10, maximumMinutesPerYear: 120, - }], + }]), addContract: jest.fn(), updateContract: jest.fn(), removeContract: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/contract/__snapshots__/ContractsPage.test.tsx.snap b/optaweb-employee-rostering-frontend/src/ui/pages/contract/__snapshots__/ContractsPage.test.tsx.snap index fa82f0e85..76efba548 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/contract/__snapshots__/ContractsPage.test.tsx.snap +++ b/optaweb-employee-rostering-frontend/src/ui/pages/contract/__snapshots__/ContractsPage.test.tsx.snap @@ -447,7 +447,12 @@ exports[`Contracts page should render correctly with a few contracts 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > @@ -553,7 +558,12 @@ exports[`Contracts page should render correctly with no contracts 1`] = ` } onSort={[Function]} rows={Array []} - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx index baeef1dbe..c17dd9059 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx @@ -24,6 +24,9 @@ import { act } from 'react-dom/test-utils'; import { useTranslation, Trans } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { FileUpload } from '@patternfly/react-core'; +import { List } from 'immutable'; +import { Contract } from 'domain/Contract'; +import { Skill } from 'domain/Skill'; import { EmployeesPage, Props } from './EmployeesPage'; describe('Employees page', () => { @@ -43,14 +46,14 @@ describe('Employees page', () => { it('should render the viewer correctly', () => { const employeesPage = new EmployeesPage(twoEmployees); - const spot = twoEmployees.tableData[1]; + const spot = twoEmployees.tableData.get(1) as Employee; const viewer = shallow(employeesPage.renderViewer(spot)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const employeesPage = new EmployeesPage(twoEmployees); - const spot = twoEmployees.tableData[1]; + const spot = twoEmployees.tableData.get(1) as Employee; const editor = shallow(employeesPage.renderEditor(spot)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -68,18 +71,18 @@ describe('Employees page', () => { setProperty.mockClear(); const contractCol = mount(editor[1] as React.ReactElement); act(() => { - contractCol.find(TypeaheadSelectInput).props().onChange(twoEmployees.contractList[0]); + contractCol.find(TypeaheadSelectInput).props().onChange(twoEmployees.contractList.get(0) as Contract); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('contract', twoEmployees.contractList[0]); + expect(setProperty).toBeCalledWith('contract', twoEmployees.contractList.get(0) as Contract); setProperty.mockClear(); const skillProficiencySetCol = mount(editor[2] as React.ReactElement); act(() => { - skillProficiencySetCol.find(MultiTypeaheadSelectInput).props().onChange([twoEmployees.skillList[0]]); + skillProficiencySetCol.find(MultiTypeaheadSelectInput).props().onChange([twoEmployees.skillList.get(0) as Skill]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('skillProficiencySet', [twoEmployees.skillList[0]]); + expect(setProperty).toBeCalledWith('skillProficiencySet', [twoEmployees.skillList.get(0) as Skill]); setProperty.mockClear(); const shortIdCol = shallow(editor[3] as React.ReactElement); @@ -104,7 +107,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, tenantId: 0, id: 1, version: 0, @@ -121,7 +124,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, tenantId: 0, id: 1, version: 0, @@ -138,7 +141,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, tenantId: 0, id: 1, version: 0, @@ -154,21 +157,30 @@ describe('Employees page', () => { const employeesPage = new EmployeesPage(twoEmployees); const filter = employeesPage.getFilter(); - expect(twoEmployees.tableData.filter(filter('1'))).toEqual([twoEmployees.tableData[0], twoEmployees.tableData[1]]); - expect(twoEmployees.tableData.filter(filter('Skill 1'))).toEqual([twoEmployees.tableData[1]]); - expect(twoEmployees.tableData.filter(filter('2'))).toEqual([twoEmployees.tableData[1]]); - expect(twoEmployees.tableData.filter(filter('Contract 2'))).toEqual([twoEmployees.tableData[1]]); - expect(twoEmployees.tableData.filter(filter('Employee 2'))).toEqual([twoEmployees.tableData[1]]); + expect(twoEmployees.tableData.filter(filter('1'))).toEqual(List([ + twoEmployees.tableData.get(0) as Employee, + twoEmployees.tableData.get(1) as Employee, + ])); + expect(twoEmployees.tableData.filter(filter('Skill 1'))).toEqual(List([ + twoEmployees.tableData.get(1) as Employee, + ])); + expect(twoEmployees.tableData.filter(filter('2'))).toEqual(List([twoEmployees.tableData.get(1) as Employee])); + expect(twoEmployees.tableData.filter(filter('Contract 2'))).toEqual(List([ + twoEmployees.tableData.get(1) as Employee, + ])); + expect(twoEmployees.tableData.filter(filter('Employee 2'))).toEqual(List([ + twoEmployees.tableData.get(1) as Employee, + ])); }); it('should return a sorter that sort by name and contract', () => { const employeesPage = new EmployeesPage(twoEmployees); const nameSorter = employeesPage.getSorters()[0] as Sorter; - let list = [twoEmployees.tableData[1], twoEmployees.tableData[0]]; - expect(list.sort(nameSorter)).toEqual(twoEmployees.tableData); - list = [twoEmployees.tableData[1], twoEmployees.tableData[0]]; + let list = [twoEmployees.tableData.get(1) as Employee, twoEmployees.tableData.get(0) as Employee]; + expect(list.sort(nameSorter)).toEqual(twoEmployees.tableData.toArray()); + list = [twoEmployees.tableData.get(1) as Employee, twoEmployees.tableData.get(0) as Employee]; const contractSorter = employeesPage.getSorters()[1] as Sorter; - expect(list.sort(contractSorter)).toEqual(twoEmployees.tableData); + expect(list.sort(contractSorter)).toEqual(twoEmployees.tableData.toArray()); expect(employeesPage.getSorters()[2]).toBeNull(); }); @@ -186,7 +198,7 @@ describe('Employees page', () => { const noName = { tenantId: 0, skillProficiencySet: [], - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, }; const result1 = employeesPage.isDataComplete(noName); expect(result1).toEqual(false); @@ -194,7 +206,7 @@ describe('Employees page', () => { const noSkills = { tenantId: 0, name: 'Name', - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, }; const result2 = employeesPage.isDataComplete(noSkills); expect(result2).toEqual(false); @@ -211,7 +223,7 @@ describe('Employees page', () => { tenantId: 0, name: 'Name', skillProficiencySet: [], - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, shortId: 'N', color: '#FFFFFF', }; @@ -225,7 +237,7 @@ describe('Employees page', () => { tenantId: 0, name: '', skillProficiencySet: [], - contract: twoEmployees.contractList[0], + contract: twoEmployees.contractList.get(0) as Contract, shortId: 'N', color: '#FFFFFF', }; @@ -235,7 +247,7 @@ describe('Employees page', () => { it('should treat non-empty name as valid', () => { const employeesPage = new EmployeesPage(twoEmployees); - const components = twoEmployees.tableData[0]; + const components = twoEmployees.tableData.get(0) as Employee; const result = employeesPage.isValid(components); expect(result).toEqual(true); }); @@ -261,9 +273,9 @@ const noEmployees: Props = { tenantId: 0, title: 'Employees', columnTitles: ['Name', 'Contract', 'Skill Set'], - tableData: [], - skillList: [], - contractList: [], + tableData: List(), + skillList: List(), + contractList: List(), addEmployee: jest.fn(), updateEmployee: jest.fn(), removeEmployee: jest.fn(), @@ -278,7 +290,7 @@ const twoEmployees: Props = { tenantId: 0, title: 'Employees', columnTitles: ['Name', 'Contract', 'Skill Set'], - tableData: [{ + tableData: List([{ id: 0, version: 0, tenantId: 0, @@ -315,9 +327,9 @@ const twoEmployees: Props = { }, shortId: 'e1', color: '#FFFFFF', - }], - skillList: [{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }], - contractList: [ + }]), + skillList: List([{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }]), + contractList: List([ { tenantId: 0, id: 0, @@ -338,7 +350,7 @@ const twoEmployees: Props = { maximumMinutesPerMonth: null, maximumMinutesPerYear: null, }, - ], + ]), addEmployee: jest.fn(), updateEmployee: jest.fn(), removeEmployee: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/__snapshots__/EmployeePage.test.tsx.snap b/optaweb-employee-rostering-frontend/src/ui/pages/employee/__snapshots__/EmployeePage.test.tsx.snap index bd9c23468..f6200477c 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/__snapshots__/EmployeePage.test.tsx.snap +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/__snapshots__/EmployeePage.test.tsx.snap @@ -611,7 +611,12 @@ exports[`Employees page should render correctly with a few employees 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx index 528adb80d..ea78e3510 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx @@ -24,6 +24,7 @@ import moment from 'moment'; import { Modal, TextInput, FlexItem, Text } from '@patternfly/react-core'; import { Skill } from 'domain/Skill'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; +import { List } from 'immutable'; import { EditTimeBucketModal, TimeBucketEditor, EditTimeBucketModalProps, TimeBucketEditorProps, @@ -197,7 +198,7 @@ const timeBucket: TimeBucket = { seatList: [{ dayInRotation: 0, employee }], }; -const skillList: Skill[] = [ +const skillList: List = List([ { tenantId: 0, id: 1000, @@ -210,7 +211,7 @@ const skillList: Skill[] = [ version: 0, name: 'Skill 2', }, -]; +]); const editTimeBucketModalProps: EditTimeBucketModalProps = { isOpen: true, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx index 0e714b1cf..827d71e29 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx @@ -20,6 +20,7 @@ import { employeeSelectors } from 'store/employee'; import { shallow } from 'enzyme'; import { Button, Modal } from '@patternfly/react-core'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; +import { List } from 'immutable'; import { EmployeeNickNameProps, EmployeeNickName, EmployeeStubProps, EmployeeStub, EditEmployeeStubListModalProps, EditEmployeeStubListModal, @@ -77,10 +78,10 @@ const newEmployee: Employee = { }; -const employeeList: Employee[] = [ +const employeeList: List = List([ employee, newEmployee, -]; +]); describe('EmployeeNickName Component', () => { const baseProps: EmployeeNickNameProps = { diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx index a5b69ba68..fe5fd545e 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx @@ -28,6 +28,7 @@ import moment from 'moment'; import { Trans } from 'react-i18next'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; import { Button } from '@patternfly/react-core'; +import { List } from 'immutable'; import { SeatJigsaw } from './SeatJigsaw'; import { EmployeeStubList, Stub } from './EmployeeStub'; import { EditTimeBucketModal } from './EditTimeBucketModal'; @@ -101,7 +102,7 @@ describe('Rotation Page', () => { mockSelector(tenantSelectors.getTenantId, 0); mockSelector(rosterSelectors.getRosterState, rosterState); mockSelector(timeBucketSelectors.isLoading, false); - mockSelector(spotSelectors.getSpotList, [spot, newSpot]); + mockSelector(spotSelectors.getSpotList, List([spot, newSpot])); mockSelector(timeBucketSelectors.getTimeBucketList, [timeBucket]); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx index f385d48d5..e8a33047b 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx @@ -52,7 +52,8 @@ export const RotationPage: React.FC<{}> = () => { const [selectedStub, setSelectedStub] = useState('NO_SHIFT'); const [isEditingTimeBuckets, setIsEditingTimeBuckets] = useState(false); - const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.size > 0) ? (spotList.get(0) as Spot).name : undefined); + const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.size > 0) + ? (spotList.get(0) as Spot).name : undefined); const shownSpot = spotList.find(s => s.name === shownSpotName); const shownTimeBuckets = shownSpot ? timeBucketList.filter(tb => tb.spot.id === shownSpot.id) : []; const oldShownTimeBuckets = useRef(shownTimeBuckets.map(tb => tb.id).join(',')); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx index 3998807e9..b7738a8c1 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx @@ -27,6 +27,7 @@ import { useTranslation, Trans } from 'react-i18next'; import Actions from 'ui/components/Actions'; import Schedule from 'ui/components/calendar/Schedule'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; import { getShiftColor } from './ShiftEvent'; import { ShiftRosterPage, Props, ShiftRosterUrlProps } from './CurrentShiftRosterPage'; import ExportScheduleModal from './ExportScheduleModal'; @@ -47,8 +48,8 @@ describe('Current Shift Roster Page', () => { const shiftRosterPage = shallow(); expect(toJson(shiftRosterPage)).toMatchSnapshot(); @@ -108,8 +109,8 @@ describe('Current Shift Roster Page', () => { it('should go to the Spots page if the user click on the link', () => { const shiftRosterPage = shallow(); mount((shiftRosterPage.find(Trans).prop('components') as any)[2]).simulate('click'); @@ -128,7 +129,7 @@ describe('Current Shift Roster Page', () => { expect(baseProps.getShiftRosterFor).toBeCalledWith({ fromDate: newDateStart, toDate: newDateEnd, - spotList: baseProps.shownSpotList, + spotList: baseProps.shownSpotList.toArray(), }); }); @@ -470,8 +471,8 @@ const baseProps: Props = { tReady: true, isSolving: false, isLoading: false, - allSpotList: [spot, newSpot], - shownSpotList: [spot], + allSpotList: List([spot, newSpot]), + shownSpotList: List([spot]), spotIdToShiftListMap: new Map([ [2, [shift]], ]), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx index 7ef474f4e..837e2ee71 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx @@ -38,10 +38,10 @@ import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; +import { List } from 'immutable'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; -import { List } from 'immutable'; interface StateProps { tenantId: number; @@ -263,7 +263,8 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); - const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.shownSpotList.get(0) as Spot; + const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) + || this.props.shownSpotList.get(0) as Spot; const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx index f67a0a172..7c0fccb16 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx @@ -21,6 +21,7 @@ import { Employee } from 'domain/Employee'; import { Shift } from 'domain/Shift'; import moment from 'moment'; import { useTranslation } from 'react-i18next'; +import { List } from 'immutable'; import { EditShiftModal } from './EditShiftModal'; describe('Edit Shift Modal', () => { @@ -406,7 +407,7 @@ const baseProps = { shift: undefined, tReady: true, tenantId: 1, - spotList: [spot], - skillList: [], - employeeList: [employee], + spotList: List([spot]), + skillList: List(), + employeeList: List([employee]), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx index 11145ea82..2514a21a3 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx @@ -22,6 +22,7 @@ import moment from 'moment'; import { useTranslation } from 'react-i18next'; import { Modal } from '@patternfly/react-core'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; +import { List } from 'immutable'; import { ExportScheduleModal } from './ExportScheduleModal'; describe('Export Schedule Modal', () => { @@ -46,13 +47,13 @@ describe('Export Schedule Modal', () => { />); const urlButton = shallow(exportScheduleModal.find(Modal).prop('actions')[1]); expect(urlButton.prop('href')) - .toEqual(getExportUrlFor(baseProps.defaultFromDate, baseProps.defaultToDate, baseProps.spotList)); + .toEqual(getExportUrlFor(baseProps.defaultFromDate, baseProps.defaultToDate, baseProps.spotList.toArray())); }); it('should export to _blank if spot list is empty', () => { const exportScheduleModal = shallow(); const urlButton = shallow(exportScheduleModal.find(Modal).prop('actions')[1]); expect(urlButton.prop('href')).toEqual('_blank'); @@ -115,5 +116,5 @@ const baseProps = { defaultFromDate: moment('2018-07-01').toDate(), defaultToDate: moment('2018-07-07').toDate(), tenantId: 1, - spotList: [spot], + spotList: List([spot]), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx index a922bdd0f..e6a4e027d 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx @@ -27,6 +27,7 @@ import { rosterSelectors } from 'store/roster'; import { RosterState } from 'domain/RosterState'; import { Modal, TextInput, Checkbox, AccordionToggle, AccordionContent } from '@patternfly/react-core'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; +import { List } from 'immutable'; import { ProvisionShiftsModal, ProvisionShiftsModalProps, SpotTimeBucketSelect, SpotTimeBucketSelectProps, @@ -68,7 +69,7 @@ describe('Provision Shifts Modal', () => { mockSelectorReturnValue.clear(); mockUseEffect.mockImplementationOnce(f => f()); mockSelector(timeBucketSelectors.getTimeBucketList, [timeBucket]); - mockSelector(spotSelectors.getSpotList, [spot]); + mockSelector(spotSelectors.getSpotList, List([spot])); mockSelector(rosterSelectors.getRosterState, rosterState); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx index 8f8c2e61a..d2dac5688 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx @@ -27,6 +27,7 @@ import { useTranslation, Trans } from 'react-i18next'; import Actions from 'ui/components/Actions'; import Schedule from 'ui/components/calendar/Schedule'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; import { getShiftColor } from './ShiftEvent'; import { ShiftRosterPage, Props, ShiftRosterUrlProps } from './ShiftRosterPage'; import ExportScheduleModal from './ExportScheduleModal'; @@ -47,8 +48,8 @@ describe('Shift Roster Page', () => { const shiftRosterPage = shallow(); expect(toJson(shiftRosterPage)).toMatchSnapshot(); @@ -108,8 +109,8 @@ describe('Shift Roster Page', () => { it('should go to the Spots page if the user click on the link', () => { const shiftRosterPage = shallow(); mount((shiftRosterPage.find(Trans).prop('components') as any)[2]).simulate('click'); @@ -128,7 +129,7 @@ describe('Shift Roster Page', () => { expect(baseProps.getShiftRosterFor).toBeCalledWith({ fromDate: newDateStart, toDate: newDateEnd, - spotList: baseProps.shownSpotList, + spotList: baseProps.shownSpotList.toArray(), }); }); @@ -470,8 +471,8 @@ const baseProps: Props = { tReady: true, isSolving: false, isLoading: false, - allSpotList: [spot, newSpot], - shownSpotList: [spot], + allSpotList: List([spot, newSpot]), + shownSpotList: List([spot]), spotIdToShiftListMap: new Map([ [2, [shift]], ]), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx index e1ee31f5f..41aa9cc5e 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx @@ -38,11 +38,11 @@ import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; +import { List } from 'immutable'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; import { ProvisionShiftsModal } from './ProvisionShiftsModal'; -import { List } from 'immutable'; interface StateProps { tenantId: number; @@ -266,7 +266,8 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); - const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.shownSpotList.get(0) as Spot; + const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) + || this.props.shownSpotList.get(0) as Spot; const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx index b49ebf0ef..4a0603f9f 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx @@ -20,6 +20,7 @@ import { Skill } from 'domain/Skill'; import { Sorter, ReadonlyPartial } from 'types'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; import { SkillsPage, Props } from './SkillsPage'; describe('Skills page', () => { @@ -90,15 +91,15 @@ describe('Skills page', () => { const skillsPage = new SkillsPage(twoSkills); const filter = skillsPage.getFilter(); - expect(twoSkills.tableData.filter(filter('1'))).toEqual([twoSkills.tableData[0]]); - expect(twoSkills.tableData.filter(filter('2'))).toEqual([twoSkills.tableData[1]]); + expect(twoSkills.tableData.filter(filter('1'))).toEqual(List([twoSkills.tableData.get(0) as Skill])); + expect(twoSkills.tableData.filter(filter('2'))).toEqual(List([twoSkills.tableData.get(1) as Skill])); }); it('should return a sorter that sort by name', () => { const skillsPage = new SkillsPage(twoSkills); const sorter = skillsPage.getSorters()[0] as Sorter; - const list = [twoSkills.tableData[1], twoSkills.tableData[0]]; - expect(list.sort(sorter)).toEqual(twoSkills.tableData); + const list = [twoSkills.tableData.get(1) as Skill, twoSkills.tableData.get(0) as Skill]; + expect(list.sort(sorter)).toEqual(twoSkills.tableData.toArray()); }); it('should treat incomplete data as incomplete', () => { @@ -134,7 +135,7 @@ const noSkills: Props = { tenantId: 0, title: 'Skills', columnTitles: ['Name'], - tableData: [], + tableData: List(), addSkill: jest.fn(), updateSkill: jest.fn(), removeSkill: jest.fn(), @@ -147,7 +148,7 @@ const twoSkills: Props = { tenantId: 0, title: 'Skills', columnTitles: ['Name'], - tableData: [{ + tableData: List([{ id: 0, version: 0, tenantId: 0, @@ -158,7 +159,7 @@ const twoSkills: Props = { version: 0, tenantId: 0, name: 'Skill 2', - }], + }]), addSkill: jest.fn(), updateSkill: jest.fn(), removeSkill: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/skill/__snapshots__/SkillsPage.test.tsx.snap b/optaweb-employee-rostering-frontend/src/ui/pages/skill/__snapshots__/SkillsPage.test.tsx.snap index d10080ee7..4064a52ba 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/skill/__snapshots__/SkillsPage.test.tsx.snap +++ b/optaweb-employee-rostering-frontend/src/ui/pages/skill/__snapshots__/SkillsPage.test.tsx.snap @@ -271,7 +271,12 @@ exports[`Skills page should render correctly with a few skills 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > @@ -345,7 +350,12 @@ exports[`Skills page should render correctly with no skills 1`] = ` } onSort={[Function]} rows={Array []} - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx index 88a2d6f9b..1135463e5 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx @@ -22,6 +22,8 @@ import { Spot } from 'domain/Spot'; import { act } from 'react-dom/test-utils'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; +import { List } from 'immutable'; +import { Skill } from 'domain/Skill'; import { SpotsPage, Props } from './SpotsPage'; describe('Spots page', () => { @@ -37,14 +39,14 @@ describe('Spots page', () => { it('should render the viewer correctly', () => { const spotsPage = new SpotsPage(twoSpots); - const spot = twoSpots.tableData[1]; + const spot = twoSpots.tableData.get(1) as Spot; const viewer = shallow(spotsPage.renderViewer(spot)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const spotsPage = new SpotsPage(twoSpots); - const spot = twoSpots.tableData[1]; + const spot = twoSpots.tableData.get(1) as Spot; const editor = shallow(spotsPage.renderEditor(spot)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -61,10 +63,10 @@ describe('Spots page', () => { setProperty.mockClear(); const requiredSkillSetCol = mount(editor[1]); act(() => { - requiredSkillSetCol.find(MultiTypeaheadSelectInput).props().onChange([twoSpots.skillList[0]]); + requiredSkillSetCol.find(MultiTypeaheadSelectInput).props().onChange([twoSpots.skillList.get(0) as Skill]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('requiredSkillSet', [twoSpots.skillList[0]]); + expect(setProperty).toBeCalledWith('requiredSkillSet', [twoSpots.skillList.get(0) as Skill]); }); it('should call addSpot on addData', () => { @@ -95,17 +97,20 @@ describe('Spots page', () => { const spotsPage = new SpotsPage(twoSpots); const filter = spotsPage.getFilter(); - expect(twoSpots.tableData.filter(filter('1'))).toEqual([twoSpots.tableData[0], twoSpots.tableData[1]]); - expect(twoSpots.tableData.filter(filter('Spot 1'))).toEqual([twoSpots.tableData[0]]); - expect(twoSpots.tableData.filter(filter('2'))).toEqual([twoSpots.tableData[1]]); - expect(twoSpots.tableData.filter(filter('Skill'))).toEqual([twoSpots.tableData[1]]); + expect(twoSpots.tableData.filter(filter('1'))).toEqual(List([ + twoSpots.tableData.get(0) as Spot, + twoSpots.tableData.get(1) as Spot, + ])); + expect(twoSpots.tableData.filter(filter('Spot 1'))).toEqual(List([twoSpots.tableData.get(0) as Spot])); + expect(twoSpots.tableData.filter(filter('2'))).toEqual(List([twoSpots.tableData.get(1) as Spot])); + expect(twoSpots.tableData.filter(filter('Skill'))).toEqual(List([twoSpots.tableData.get(1) as Spot])); }); it('should return a sorter that sort by name', () => { const spotsPage = new SpotsPage(twoSpots); const sorter = spotsPage.getSorters()[0] as Sorter; - const list = [twoSpots.tableData[1], twoSpots.tableData[0]]; - expect(list.sort(sorter)).toEqual(twoSpots.tableData); + const list = [twoSpots.tableData.get(1) as Spot, twoSpots.tableData.get(0) as Spot]; + expect(list.sort(sorter)).toEqual(twoSpots.tableData.toArray()); expect(spotsPage.getSorters()[1]).toBeNull(); }); @@ -146,8 +151,8 @@ const noSpots: Props = { tenantId: 0, title: 'Spots', columnTitles: ['Name'], - tableData: [], - skillList: [], + tableData: List(), + skillList: List(), addSpot: jest.fn(), updateSpot: jest.fn(), removeSpot: jest.fn(), @@ -160,7 +165,7 @@ const twoSpots: Props = { tenantId: 0, title: 'Spots', columnTitles: ['Name'], - tableData: [{ + tableData: List([{ id: 0, version: 0, tenantId: 0, @@ -173,8 +178,8 @@ const twoSpots: Props = { tenantId: 0, name: 'Spot 2', requiredSkillSet: [{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }], - }], - skillList: [{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }], + }]), + skillList: List([{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }]), addSpot: jest.fn(), updateSpot: jest.fn(), removeSpot: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/__snapshots__/SpotsPage.test.tsx.snap b/optaweb-employee-rostering-frontend/src/ui/pages/spot/__snapshots__/SpotsPage.test.tsx.snap index 133f6672b..80c242f91 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/__snapshots__/SpotsPage.test.tsx.snap +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/__snapshots__/SpotsPage.test.tsx.snap @@ -403,7 +403,12 @@ exports[`Spots page should render correctly with a few spots 1`] = ` }, ] } - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > @@ -477,7 +482,12 @@ exports[`Spots page should render correctly with no spots 1`] = ` } onSort={[Function]} rows={Array []} - sortBy={Object {}} + sortBy={ + Object { + "direction": "asc", + "index": 0, + } + } > diff --git a/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts b/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts index f8ac69c40..150da031d 100644 --- a/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts +++ b/optaweb-employee-rostering-frontend/src/util/ImmutableCollectionOperations.ts @@ -56,19 +56,19 @@ export function mapDomainObjectToView(obj: T|DomainObjec export function createIdMapFromList(collection: T[]): Map> { let map = Map>(); - collection.forEach(ele => (map = map.set(ele.id as number, mapDomainObjectToView(ele)))); + collection.forEach((ele) => { (map = map.set(ele.id as number, mapDomainObjectToView(ele))); }); return map; } -export interface FluentValue { - result: V, - then: (map: M) => FluentValue -}; -export function conditionally(seq: Seq, mapper: (seq: Seq) => Seq | undefined): -FluentValue,(seq: Seq) => Seq | undefined> { +export interface FluentValue { + result: V; + then: (map: M) => FluentValue; +} +export function conditionally(seq: Seq, mapper: (seq: Seq) => Seq | undefined): +FluentValue, (seq: Seq) => Seq | undefined> { const out = mapper(seq) || seq; return { result: out, - then: (newMapper) => conditionally(out, newMapper), - } + then: newMapper => conditionally(out, newMapper), + }; } From daf230758046516acd1bf9db24d7796019399b9e Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 24 Nov 2020 16:57:30 -0500 Subject: [PATCH 3/6] Use ?? operator instead of type assertions Using the ?? operator allows us to throw errors instead of erasing type infomation and sliently ignoring any problem until it is too late. --- .../src/store/contract/reducers.ts | 3 +- .../src/store/contract/selectors.ts | 3 +- .../src/store/employee/reducers.ts | 3 +- .../src/store/roster/operations.ts | 6 +-- .../src/store/roster/roster.test.ts | 6 +-- .../src/store/rotation/reducers.ts | 3 +- .../src/store/skill/reducers.ts | 3 +- .../src/store/spot/reducers.ts | 3 +- .../src/store/tenant/tenant.test.ts | 4 +- .../src/types.ts | 8 +++ .../availability/AvailabilityRosterPage.tsx | 3 +- .../ui/pages/contract/ContractsPage.test.tsx | 34 ++++++------- .../ui/pages/employee/EmployeePage.test.tsx | 49 +++++++++---------- .../src/ui/pages/rotation/RotationPage.tsx | 5 +- .../ui/pages/shift/CurrentShiftRosterPage.tsx | 10 ++-- .../src/ui/pages/shift/ShiftRosterPage.tsx | 10 ++-- .../src/ui/pages/skill/SkillsPage.test.tsx | 8 +-- .../src/ui/pages/spot/SpotsPage.test.tsx | 23 +++++---- 18 files changed, 101 insertions(+), 83 deletions(-) diff --git a/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts b/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts index 30203fe1e..d12a51fc2 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/reducers.ts @@ -30,7 +30,8 @@ const contractReducer = (state = initialState, action: ContractAction): Contract case ActionType.SET_CONTRACT_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_CONTRACT: case ActionType.UPDATE_CONTRACT: { + case ActionType.ADD_CONTRACT: + case ActionType.UPDATE_CONTRACT: { return { ...state, contractMapById: state.contractMapById.set(action.contract.id as number, action.contract) }; } case ActionType.REMOVE_CONTRACT: { diff --git a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts index 48db5da78..c1b382120 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts @@ -15,6 +15,7 @@ */ import { Contract } from 'domain/Contract'; import { Map, List } from 'immutable'; +import DomainObjectView from 'domain/DomainObjectView'; import { AppState } from '../types'; export const getContractById = (state: AppState, id: number): Contract => { @@ -24,7 +25,7 @@ export const getContractById = (state: AppState, id: number): Contract => { return state.contractList.contractMapById.get(id) as Contract; }; -let oldContractMapById: Map | null = null; +let oldContractMapById: Map> | null = null; let contractListForOldContractMapById: List | null = null; export const getContractList = (state: AppState): List => { diff --git a/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts b/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts index 83141cee0..41641109c 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/reducers.ts @@ -30,7 +30,8 @@ const employeeReducer = (state = initialState, action: EmployeeAction): Employee case ActionType.SET_EMPLOYEE_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_EMPLOYEE: case ActionType.UPDATE_EMPLOYEE: { + case ActionType.ADD_EMPLOYEE: + case ActionType.UPDATE_EMPLOYEE: { return { ...state, employeeMapById: state.employeeMapById.set(action.employee.id as number, mapDomainObjectToView(action.employee)) }; diff --git a/optaweb-employee-rostering-frontend/src/store/roster/operations.ts b/optaweb-employee-rostering-frontend/src/store/roster/operations.ts index 51915a420..9b6e183c3 100644 --- a/optaweb-employee-rostering-frontend/src/store/roster/operations.ts +++ b/optaweb-employee-rostering-frontend/src/store/roster/operations.ts @@ -16,7 +16,7 @@ import { RosterState } from 'domain/RosterState'; import { ShiftRosterView } from 'domain/ShiftRosterView'; -import { PaginationData, ObjectNumberMap, mapObjectNumberMap, mapObjectStringMap } from 'types'; +import { PaginationData, ObjectNumberMap, mapObjectNumberMap, mapObjectStringMap, error } from 'types'; import moment from 'moment'; import { Spot } from 'domain/Spot'; import { alert } from 'store/alert'; @@ -176,7 +176,7 @@ ThunkCommandFactory = () => (dispatch, state) => { const startDate = moment(rosterState.firstDraftDate).startOf('week').toDate(); const endDate = moment(rosterState.firstDraftDate).endOf('week').toDate(); const spotList = spotSelectors.getSpotList(state()); - const shownSpots = (spotList.size > 0) ? [spotList.get(0) as Spot] : []; + const shownSpots = (spotList.size > 0) ? [spotList.get(0) ?? error()] : []; if (shownSpots.length > 0) { dispatch(getShiftRosterFor({ @@ -199,7 +199,7 @@ ThunkCommandFactory = () => (dispatch, state const startDate = moment(rosterState.firstDraftDate).startOf('week').toDate(); const endDate = moment(rosterState.firstDraftDate).endOf('week').toDate(); const employeeList = employeeSelectors.getEmployeeList(state()); - const shownEmployees = (employeeList.size > 0) ? [employeeList.get(0) as Employee] : []; + const shownEmployees = (employeeList.size > 0) ? [employeeList.get(0) ?? error()] : []; if (shownEmployees.length > 0) { dispatch(getAvailabilityRosterFor({ diff --git a/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts b/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts index d8374da32..3c17255f6 100644 --- a/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts @@ -27,7 +27,7 @@ import { Employee } from 'domain/Employee'; import { RosterState } from 'domain/RosterState'; import { serializeLocalDate } from 'store/rest/DataSerialization'; import { flushPromises } from 'setupTests'; -import { doNothing } from 'types'; +import { doNothing, error } from 'types'; import { Map, List } from 'immutable'; import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { availabilityRosterReducer } from './reducers'; @@ -669,7 +669,7 @@ describe('Roster operations', () => { it('should dispatch actions and call client on getInitialShiftRoster', async () => { const { store, client } = mockStore(state); const tenantId = store.getState().tenantData.currentTenantId; - const spotList: Spot[] = [spotSelectors.getSpotList(store.getState()).get(0) as Spot]; + const spotList: Spot[] = [spotSelectors.getSpotList(store.getState()).get(0) ?? error()]; const fromDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) .startOf('week').toDate(); const toDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) @@ -822,7 +822,7 @@ describe('Roster operations', () => { it('should dispatch actions and call client on getInitialAvailabilityRoster', async () => { const { store, client } = mockStore(state); const tenantId = store.getState().tenantData.currentTenantId; - const employeeList: Employee[] = [employeeSelectors.getEmployeeList(store.getState()).get(0) as Employee]; + const employeeList: Employee[] = [employeeSelectors.getEmployeeList(store.getState()).get(0) ?? error()]; const fromDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) .startOf('week').toDate(); const toDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) diff --git a/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts b/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts index 1fe4cf2c1..9f693b0f6 100644 --- a/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/rotation/reducers.ts @@ -30,7 +30,8 @@ const timeBucketReducer = (state = initialState, action: TimeBucketAction): Time case ActionType.SET_TIME_BUCKET_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_TIME_BUCKET: case ActionType.UPDATE_TIME_BUCKET: { + case ActionType.ADD_TIME_BUCKET: + case ActionType.UPDATE_TIME_BUCKET: { return { ...state, timeBucketMapById: state.timeBucketMapById.set(action.timeBucket.id as number, mapDomainObjectToView(action.timeBucket)) }; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts b/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts index ee1ec468e..22ae23fea 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/reducers.ts @@ -30,7 +30,8 @@ const skillReducer = (state = initialState, action: SkillAction): SkillList => { case ActionType.SET_SKILL_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_SKILL: case ActionType.UPDATE_SKILL: { + case ActionType.ADD_SKILL: + case ActionType.UPDATE_SKILL: { return { ...state, skillMapById: state.skillMapById.set(action.skill.id as number, action.skill) }; } case ActionType.REMOVE_SKILL: { diff --git a/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts b/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts index 86f2a214d..20b991518 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/reducers.ts @@ -30,7 +30,8 @@ const spotReducer = (state = initialState, action: SpotAction): SpotList => { case ActionType.SET_SPOT_LIST_LOADING: { return { ...state, isLoading: action.isLoading }; } - case ActionType.ADD_SPOT: case ActionType.UPDATE_SPOT: { + case ActionType.ADD_SPOT: + case ActionType.UPDATE_SPOT: { return { ...state, spotMapById: state.spotMapById.set(action.spot.id as number, mapDomainObjectToView(action.spot)) }; diff --git a/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts b/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts index b7d53142b..94ccfc50c 100644 --- a/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/tenant/tenant.test.ts @@ -23,7 +23,7 @@ import { timeBucketOperations } from 'store/rotation'; import { RosterState } from 'domain/RosterState'; import { flushPromises } from 'setupTests'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { doNothing } from 'types'; +import { doNothing, error } from 'types'; import { Map, List } from 'immutable'; import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { mockStore } from '../mockStore'; @@ -484,7 +484,7 @@ describe('Tenant reducers', () => { }); it('removeTenant', () => { expect( - reducer(storeState.tenantData, actions.removeTenant(storeState.tenantData.tenantList.get(0) as Tenant)), + reducer(storeState.tenantData, actions.removeTenant(storeState.tenantData.tenantList.get(0) ?? error())), ).toEqual({ ...storeState.tenantData, tenantList: storeState.tenantData.tenantList.delete(0), diff --git a/optaweb-employee-rostering-frontend/src/types.ts b/optaweb-employee-rostering-frontend/src/types.ts index 30bbef8c3..593e06b16 100644 --- a/optaweb-employee-rostering-frontend/src/types.ts +++ b/optaweb-employee-rostering-frontend/src/types.ts @@ -23,6 +23,14 @@ export interface PaginationData { export const doNothing = () => { /* Intentionally Empty */ }; +/** + * Used to throw an error on a condition that should never + * happen. + */ +export function error(msg?: string): never { + throw new Error(msg ?? ''); +} + export interface ObjectNumberMap { [index: number]: T; } diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx index 8496faa99..87b2bd6ae 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx @@ -41,6 +41,7 @@ import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { UrlProps, setPropsInUrl, getPropsFromUrl } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; import { List } from 'immutable'; +import { error } from 'types'; import AvailabilityEvent, { AvailabilityPopoverHeader, AvailabilityPopoverBody } from './AvailabilityEvent'; import EditAvailabilityModal from './EditAvailabilityModal'; import ShiftEvent, { ShiftPopupHeader, ShiftPopupBody } from '../shift/ShiftEvent'; @@ -292,7 +293,7 @@ export class AvailabilityRosterPage extends React.Component { }); const changedTenant = this.props.shownEmployeeList.size === 0 || (urlProps.employee !== null - && this.props.tenantId !== (this.props.shownEmployeeList.get(0) as Employee).tenantId); + && this.props.tenantId !== (this.props.shownEmployeeList.get(0) ?? error()).tenantId); if (this.props.shownEmployeeList.size === 0 || changedTenant || this.props.rosterState === null) { return ( diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx index 107042499..3278259e7 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx @@ -17,7 +17,7 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import OptionalInput from 'ui/components/OptionalInput'; -import { Sorter } from 'types'; +import { Sorter, error } from 'types'; import { Contract } from 'domain/Contract'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; @@ -37,14 +37,14 @@ describe('Contracts page', () => { it('should render the viewer correctly', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) as Contract; + const contract = twoContracts.tableData.get(1) ?? error(); const viewer = shallow(contractsPage.renderViewer(contract)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) as Contract; + const contract = twoContracts.tableData.get(1) ?? error(); const editor = shallow(contractsPage.renderEditor(contract)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -105,7 +105,7 @@ describe('Contracts page', () => { it('should call addContract on addData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) as Contract; + const contract = twoContracts.tableData.get(1) ?? error(); contractsPage.addData(contract); expect(twoContracts.addContract).toBeCalled(); expect(twoContracts.addContract).toBeCalledWith(contract); @@ -113,7 +113,7 @@ describe('Contracts page', () => { it('should call updateContract on updateData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) as Contract; + const contract = twoContracts.tableData.get(1) ?? error(); contractsPage.updateData(contract); expect(twoContracts.updateContract).toBeCalled(); expect(twoContracts.updateContract).toBeCalledWith(contract); @@ -121,7 +121,7 @@ describe('Contracts page', () => { it('should call removeContract on removeData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) as Contract; + const contract = twoContracts.tableData.get(1) ?? error(); contractsPage.removeData(contract); expect(twoContracts.removeContract).toBeCalled(); expect(twoContracts.removeContract).toBeCalledWith(contract); @@ -131,55 +131,55 @@ describe('Contracts page', () => { const contractPage = new ContractsPage(twoContracts); const filter = contractPage.getFilter(); - expect(twoContracts.tableData.filter(filter('1'))).toEqual(List([twoContracts.tableData.get(0) as Contract])); - expect(twoContracts.tableData.filter(filter('2'))).toEqual(List([twoContracts.tableData.get(1) as Contract])); + expect(twoContracts.tableData.filter(filter('1'))).toEqual(List([twoContracts.tableData.get(0) ?? error()])); + expect(twoContracts.tableData.filter(filter('2'))).toEqual(List([twoContracts.tableData.get(1) ?? error()])); }); it('should return a sorter that sort by name', () => { const contractPage = new ContractsPage(twoContracts); const sorter = contractPage.getSorters()[0] as Sorter; - const list = [twoContracts.tableData.get(1) as Contract, twoContracts.tableData.get(0) as Contract]; + const list = [twoContracts.tableData.get(1) ?? error(), twoContracts.tableData.get(0) ?? error()]; expect(list.sort(sorter)).toEqual(twoContracts.tableData.toArray()); }); it('should treat incomplete data as incomplete', () => { const contractsPage = new ContractsPage(twoContracts); - const noName = { ...twoContracts.tableData.get(1) as Contract, name: undefined }; + const noName = { ...twoContracts.tableData.get(1) ?? error(), name: undefined }; const result1 = contractsPage.isDataComplete(noName); expect(result1).toEqual(false); - const noMaxHoursPerDay = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerDay: undefined }; + const noMaxHoursPerDay = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerDay: undefined }; const result2 = contractsPage.isDataComplete(noMaxHoursPerDay); expect(result2).toEqual(false); - const noMaxHoursPerWeek = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerWeek: undefined }; + const noMaxHoursPerWeek = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerWeek: undefined }; const result3 = contractsPage.isDataComplete(noMaxHoursPerWeek); expect(result3).toEqual(false); - const noMaxHoursPerMonth = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerMonth: undefined }; + const noMaxHoursPerMonth = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerMonth: undefined }; const result4 = contractsPage.isDataComplete(noMaxHoursPerMonth); expect(result4).toEqual(false); - const noMaxHoursPerYear = { ...twoContracts.tableData.get(1) as Contract, maximumMinutesPerYear: undefined }; + const noMaxHoursPerYear = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerYear: undefined }; const result5 = contractsPage.isDataComplete(noMaxHoursPerYear); expect(result5).toEqual(false); - const completed = { ...twoContracts.tableData.get(1) as Contract }; + const completed = { ...twoContracts.tableData.get(1) ?? error() }; const result6 = contractsPage.isDataComplete(completed); expect(result6).toEqual(true); }); it('should treat empty name as invalid', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = { ...twoContracts.tableData.get(1) as Contract, name: '' }; + const contract = { ...twoContracts.tableData.get(1) ?? error(), name: '' }; const result = contractsPage.isValid(contract); expect(result).toEqual(false); }); it('should treat non-empty name as valid', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = { ...twoContracts.tableData.get(1) as Contract, name: 'Contract' }; + const contract = { ...twoContracts.tableData.get(1) ?? error(), name: 'Contract' }; const result = contractsPage.isValid(contract); expect(result).toEqual(true); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx index c17dd9059..c182c8860 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx @@ -18,15 +18,13 @@ import toJson from 'enzyme-to-json'; import * as React from 'react'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { Sorter } from 'types'; +import { Sorter, error } from 'types'; import { Employee } from 'domain/Employee'; import { act } from 'react-dom/test-utils'; import { useTranslation, Trans } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { FileUpload } from '@patternfly/react-core'; import { List } from 'immutable'; -import { Contract } from 'domain/Contract'; -import { Skill } from 'domain/Skill'; import { EmployeesPage, Props } from './EmployeesPage'; describe('Employees page', () => { @@ -46,14 +44,14 @@ describe('Employees page', () => { it('should render the viewer correctly', () => { const employeesPage = new EmployeesPage(twoEmployees); - const spot = twoEmployees.tableData.get(1) as Employee; + const spot = twoEmployees.tableData.get(1) ?? error(); const viewer = shallow(employeesPage.renderViewer(spot)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const employeesPage = new EmployeesPage(twoEmployees); - const spot = twoEmployees.tableData.get(1) as Employee; + const spot = twoEmployees.tableData.get(1) ?? error(); const editor = shallow(employeesPage.renderEditor(spot)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -71,18 +69,19 @@ describe('Employees page', () => { setProperty.mockClear(); const contractCol = mount(editor[1] as React.ReactElement); act(() => { - contractCol.find(TypeaheadSelectInput).props().onChange(twoEmployees.contractList.get(0) as Contract); + contractCol.find(TypeaheadSelectInput).props().onChange(twoEmployees.contractList.get(0) ?? error()); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('contract', twoEmployees.contractList.get(0) as Contract); + expect(setProperty).toBeCalledWith('contract', twoEmployees.contractList.get(0) ?? error()); setProperty.mockClear(); const skillProficiencySetCol = mount(editor[2] as React.ReactElement); act(() => { - skillProficiencySetCol.find(MultiTypeaheadSelectInput).props().onChange([twoEmployees.skillList.get(0) as Skill]); + skillProficiencySetCol.find(MultiTypeaheadSelectInput).props() + .onChange([twoEmployees.skillList.get(0) ?? error()]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('skillProficiencySet', [twoEmployees.skillList.get(0) as Skill]); + expect(setProperty).toBeCalledWith('skillProficiencySet', [twoEmployees.skillList.get(0) ?? error()]); setProperty.mockClear(); const shortIdCol = shallow(editor[3] as React.ReactElement); @@ -107,7 +106,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), tenantId: 0, id: 1, version: 0, @@ -124,7 +123,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), tenantId: 0, id: 1, version: 0, @@ -141,7 +140,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), tenantId: 0, id: 1, version: 0, @@ -158,27 +157,27 @@ describe('Employees page', () => { const filter = employeesPage.getFilter(); expect(twoEmployees.tableData.filter(filter('1'))).toEqual(List([ - twoEmployees.tableData.get(0) as Employee, - twoEmployees.tableData.get(1) as Employee, + twoEmployees.tableData.get(0) ?? error(), + twoEmployees.tableData.get(1) ?? error(), ])); expect(twoEmployees.tableData.filter(filter('Skill 1'))).toEqual(List([ - twoEmployees.tableData.get(1) as Employee, + twoEmployees.tableData.get(1) ?? error(), ])); - expect(twoEmployees.tableData.filter(filter('2'))).toEqual(List([twoEmployees.tableData.get(1) as Employee])); + expect(twoEmployees.tableData.filter(filter('2'))).toEqual(List([twoEmployees.tableData.get(1) ?? error()])); expect(twoEmployees.tableData.filter(filter('Contract 2'))).toEqual(List([ - twoEmployees.tableData.get(1) as Employee, + twoEmployees.tableData.get(1) ?? error(), ])); expect(twoEmployees.tableData.filter(filter('Employee 2'))).toEqual(List([ - twoEmployees.tableData.get(1) as Employee, + twoEmployees.tableData.get(1) ?? error(), ])); }); it('should return a sorter that sort by name and contract', () => { const employeesPage = new EmployeesPage(twoEmployees); const nameSorter = employeesPage.getSorters()[0] as Sorter; - let list = [twoEmployees.tableData.get(1) as Employee, twoEmployees.tableData.get(0) as Employee]; + let list = [twoEmployees.tableData.get(1) ?? error(), twoEmployees.tableData.get(0) ?? error()]; expect(list.sort(nameSorter)).toEqual(twoEmployees.tableData.toArray()); - list = [twoEmployees.tableData.get(1) as Employee, twoEmployees.tableData.get(0) as Employee]; + list = [twoEmployees.tableData.get(1) ?? error(), twoEmployees.tableData.get(0) ?? error()]; const contractSorter = employeesPage.getSorters()[1] as Sorter; expect(list.sort(contractSorter)).toEqual(twoEmployees.tableData.toArray()); expect(employeesPage.getSorters()[2]).toBeNull(); @@ -198,7 +197,7 @@ describe('Employees page', () => { const noName = { tenantId: 0, skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), }; const result1 = employeesPage.isDataComplete(noName); expect(result1).toEqual(false); @@ -206,7 +205,7 @@ describe('Employees page', () => { const noSkills = { tenantId: 0, name: 'Name', - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), }; const result2 = employeesPage.isDataComplete(noSkills); expect(result2).toEqual(false); @@ -223,7 +222,7 @@ describe('Employees page', () => { tenantId: 0, name: 'Name', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), shortId: 'N', color: '#FFFFFF', }; @@ -237,7 +236,7 @@ describe('Employees page', () => { tenantId: 0, name: '', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) as Contract, + contract: twoEmployees.contractList.get(0) ?? error(), shortId: 'N', color: '#FFFFFF', }; @@ -247,7 +246,7 @@ describe('Employees page', () => { it('should treat non-empty name as valid', () => { const employeesPage = new EmployeesPage(twoEmployees); - const components = twoEmployees.tableData.get(0) as Employee; + const components = twoEmployees.tableData.get(0) ?? error(); const result = employeesPage.isValid(components); expect(result).toEqual(true); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx index e8a33047b..afdff8855 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx @@ -33,6 +33,7 @@ import { rosterSelectors } from 'store/roster'; import { tenantSelectors } from 'store/tenant'; import moment from 'moment'; import { Employee } from 'domain/Employee'; +import { error } from 'types'; import { SeatJigsaw } from './SeatJigsaw'; import { EditTimeBucketModal } from './EditTimeBucketModal'; import { EmployeeStubList, Stub } from './EmployeeStub'; @@ -53,7 +54,7 @@ export const RotationPage: React.FC<{}> = () => { const [isEditingTimeBuckets, setIsEditingTimeBuckets] = useState(false); const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.size > 0) - ? (spotList.get(0) as Spot).name : undefined); + ? (spotList.get(0) ?? error('Spot list is not empty but does not have an element')).name : undefined); const shownSpot = spotList.find(s => s.name === shownSpotName); const shownTimeBuckets = shownSpot ? timeBucketList.filter(tb => tb.spot.id === shownSpot.id) : []; const oldShownTimeBuckets = useRef(shownTimeBuckets.map(tb => tb.id).join(',')); @@ -72,7 +73,7 @@ export const RotationPage: React.FC<{}> = () => { React.useEffect(() => { if (shownSpot === undefined && spotList.size > 0) { - setShownSpotName((spotList.get(0) as Spot).name); + setShownSpotName((spotList.get(0) ?? error()).name); } }, [spotList, shownSpot, setShownSpotName]); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx index 837e2ee71..4e9cf2638 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx @@ -39,6 +39,7 @@ import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; import { List } from 'immutable'; +import { error } from 'types'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; @@ -131,7 +132,8 @@ export class ShiftRosterPage extends React.Component { onUpdateShiftRoster(urlProps: ShiftRosterUrlProps) { if (this.props.rosterState) { - const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.allSpotList.get(0) as Spot; + const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) + || (this.props.allSpotList.get(0) /* can be undefined */); const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); @@ -236,7 +238,7 @@ export class ShiftRosterPage extends React.Component { week: null, }); const changedTenant = this.props.shownSpotList.size === 0 - || this.props.tenantId !== (this.props.shownSpotList.get(0) as Spot).tenantId; + || this.props.tenantId !== (this.props.shownSpotList.get(0) ?? error()).tenantId; if (this.props.shownSpotList.size === 0 || this.state.firstLoad || changedTenant || this.props.rosterState === null) { @@ -264,7 +266,7 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) - || this.props.shownSpotList.get(0) as Spot; + || (this.props.shownSpotList.get(0) ?? error()); const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -365,7 +367,7 @@ export class ShiftRosterPage extends React.Component { defaultToDate={endDate} /> - key={(this.props.shownSpotList.get(0) as Spot).id} + key={(this.props.shownSpotList.get(0) ?? error()).id} startDate={startDate} endDate={endDate} events={this.props.spotIdToShiftListMap.get(shownSpot.id as number) || []} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx index 41aa9cc5e..23853b182 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx @@ -39,6 +39,7 @@ import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; import { List } from 'immutable'; +import { error } from 'types'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; @@ -134,7 +135,8 @@ export class ShiftRosterPage extends React.Component { onUpdateShiftRoster(urlProps: ShiftRosterUrlProps) { if (this.props.rosterState) { - const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) || this.props.allSpotList.get(0) as Spot; + const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) + || (this.props.allSpotList.get(0) /* can be undefined */); const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); @@ -239,7 +241,7 @@ export class ShiftRosterPage extends React.Component { week: null, }); const changedTenant = this.props.shownSpotList.size === 0 - || this.props.tenantId !== (this.props.shownSpotList.get(0) as Spot).tenantId; + || this.props.tenantId !== (this.props.shownSpotList.get(0) ?? error()).tenantId; if (this.props.shownSpotList.size === 0 || this.state.firstLoad || changedTenant || this.props.rosterState === null) { @@ -267,7 +269,7 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) - || this.props.shownSpotList.get(0) as Spot; + || (this.props.shownSpotList.get(0) ?? error()); const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -382,7 +384,7 @@ export class ShiftRosterPage extends React.Component { defaultToDate={endDate} /> - key={(this.props.shownSpotList.get(0) as Spot).id} + key={(this.props.shownSpotList.get(0) ?? error()).id} startDate={startDate} endDate={endDate} events={this.props.spotIdToShiftListMap.get(shownSpot.id as number) || []} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx index 4a0603f9f..ba2686f36 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx @@ -17,7 +17,7 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { Skill } from 'domain/Skill'; -import { Sorter, ReadonlyPartial } from 'types'; +import { Sorter, ReadonlyPartial, error } from 'types'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { List } from 'immutable'; @@ -91,14 +91,14 @@ describe('Skills page', () => { const skillsPage = new SkillsPage(twoSkills); const filter = skillsPage.getFilter(); - expect(twoSkills.tableData.filter(filter('1'))).toEqual(List([twoSkills.tableData.get(0) as Skill])); - expect(twoSkills.tableData.filter(filter('2'))).toEqual(List([twoSkills.tableData.get(1) as Skill])); + expect(twoSkills.tableData.filter(filter('1'))).toEqual(List([twoSkills.tableData.get(0) ?? error()])); + expect(twoSkills.tableData.filter(filter('2'))).toEqual(List([twoSkills.tableData.get(1) ?? error()])); }); it('should return a sorter that sort by name', () => { const skillsPage = new SkillsPage(twoSkills); const sorter = skillsPage.getSorters()[0] as Sorter; - const list = [twoSkills.tableData.get(1) as Skill, twoSkills.tableData.get(0) as Skill]; + const list = [twoSkills.tableData.get(1) ?? error(), twoSkills.tableData.get(0) ?? error()]; expect(list.sort(sorter)).toEqual(twoSkills.tableData.toArray()); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx index 1135463e5..5b25b0a0c 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx @@ -17,13 +17,12 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { Sorter } from 'types'; +import { Sorter, error } from 'types'; import { Spot } from 'domain/Spot'; import { act } from 'react-dom/test-utils'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { List } from 'immutable'; -import { Skill } from 'domain/Skill'; import { SpotsPage, Props } from './SpotsPage'; describe('Spots page', () => { @@ -39,14 +38,14 @@ describe('Spots page', () => { it('should render the viewer correctly', () => { const spotsPage = new SpotsPage(twoSpots); - const spot = twoSpots.tableData.get(1) as Spot; + const spot = twoSpots.tableData.get(1) ?? error(); const viewer = shallow(spotsPage.renderViewer(spot)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const spotsPage = new SpotsPage(twoSpots); - const spot = twoSpots.tableData.get(1) as Spot; + const spot = twoSpots.tableData.get(1) ?? error(); const editor = shallow(spotsPage.renderEditor(spot)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -63,10 +62,10 @@ describe('Spots page', () => { setProperty.mockClear(); const requiredSkillSetCol = mount(editor[1]); act(() => { - requiredSkillSetCol.find(MultiTypeaheadSelectInput).props().onChange([twoSpots.skillList.get(0) as Skill]); + requiredSkillSetCol.find(MultiTypeaheadSelectInput).props().onChange([twoSpots.skillList.get(0) ?? error()]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('requiredSkillSet', [twoSpots.skillList.get(0) as Skill]); + expect(setProperty).toBeCalledWith('requiredSkillSet', [twoSpots.skillList.get(0) ?? error()]); }); it('should call addSpot on addData', () => { @@ -98,18 +97,18 @@ describe('Spots page', () => { const filter = spotsPage.getFilter(); expect(twoSpots.tableData.filter(filter('1'))).toEqual(List([ - twoSpots.tableData.get(0) as Spot, - twoSpots.tableData.get(1) as Spot, + twoSpots.tableData.get(0) ?? error(), + twoSpots.tableData.get(1) ?? error(), ])); - expect(twoSpots.tableData.filter(filter('Spot 1'))).toEqual(List([twoSpots.tableData.get(0) as Spot])); - expect(twoSpots.tableData.filter(filter('2'))).toEqual(List([twoSpots.tableData.get(1) as Spot])); - expect(twoSpots.tableData.filter(filter('Skill'))).toEqual(List([twoSpots.tableData.get(1) as Spot])); + expect(twoSpots.tableData.filter(filter('Spot 1'))).toEqual(List([twoSpots.tableData.get(0) ?? error()])); + expect(twoSpots.tableData.filter(filter('2'))).toEqual(List([twoSpots.tableData.get(1) ?? error()])); + expect(twoSpots.tableData.filter(filter('Skill'))).toEqual(List([twoSpots.tableData.get(1) ?? error()])); }); it('should return a sorter that sort by name', () => { const spotsPage = new SpotsPage(twoSpots); const sorter = spotsPage.getSorters()[0] as Sorter; - const list = [twoSpots.tableData.get(1) as Spot, twoSpots.tableData.get(0) as Spot]; + const list = [twoSpots.tableData.get(1) ?? error(), twoSpots.tableData.get(0) ?? error()]; expect(list.sort(sorter)).toEqual(twoSpots.tableData.toArray()); expect(spotsPage.getSorters()[1]).toBeNull(); }); From e7dc40fa2bb58efa7a0ccaa8944c76bb40ecb738 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Thu, 26 Nov 2020 15:46:12 -0500 Subject: [PATCH 4/6] Use arrays in UI components, sort selectors result by name --- .../src/store/contract/selectors.ts | 3 +- .../src/store/employee/selectors.ts | 3 +- .../src/store/skill/selectors.ts | 3 +- .../src/store/spot/selectors.ts | 3 +- .../src/ui/Alerts.test.tsx | 11 ++- .../src/ui/Alerts.tsx | 7 +- .../src/ui/components/DataTable.test.tsx | 21 +++-- .../src/ui/components/DataTable.tsx | 6 +- .../ui/pages/contract/ContractsPage.test.tsx | 43 +++++----- .../src/ui/pages/contract/ContractsPage.tsx | 2 +- .../ui/pages/employee/EmployeePage.test.tsx | 83 +++++++++---------- .../src/ui/pages/employee/EmployeesPage.tsx | 17 ++-- .../src/ui/pages/rotation/RotationPage.tsx | 15 ++-- .../shift/CurrentShiftRosterPage.test.tsx | 15 ++-- .../ui/pages/shift/CurrentShiftRosterPage.tsx | 26 +++--- .../ui/pages/shift/EditShiftModal.test.tsx | 7 +- .../src/ui/pages/shift/EditShiftModal.tsx | 21 +++-- .../pages/shift/ExportScheduleModal.test.tsx | 7 +- .../ui/pages/shift/ExportScheduleModal.tsx | 15 ++-- .../pages/shift/ProvisionShiftsModal.test.tsx | 1 + .../ui/pages/shift/ProvisionShiftsModal.tsx | 11 ++- .../ui/pages/shift/ShiftRosterPage.test.tsx | 15 ++-- .../src/ui/pages/shift/ShiftRosterPage.tsx | 26 +++--- .../src/ui/pages/skill/SkillsPage.test.tsx | 17 ++-- .../src/ui/pages/skill/SkillsPage.tsx | 2 +- .../src/ui/pages/spot/SpotsPage.test.tsx | 39 +++++---- .../src/ui/pages/spot/SpotsPage.tsx | 9 +- 27 files changed, 206 insertions(+), 222 deletions(-) diff --git a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts index c1b382120..1962a9b31 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts @@ -35,7 +35,8 @@ export const getContractList = (state: AppState): List => { if (oldContractMapById === state.contractList.contractMapById && contractListForOldContractMapById !== null) { return contractListForOldContractMapById; } - const out = state.contractList.contractMapById.keySeq().map(id => getContractById(state, id)).toList(); + const out = state.contractList.contractMapById.keySeq().map(id => getContractById(state, id)) + .sortBy(contract => contract.name).toList(); oldContractMapById = state.contractList.contractMapById; contractListForOldContractMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts index 85e353961..c056b7737 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts @@ -43,7 +43,8 @@ export const getEmployeeList = (state: AppState): List => { return employeeListForOldEmployeeMapById; } - const out = state.employeeList.employeeMapById.keySeq().map(id => getEmployeeById(state, id)).toList(); + const out = state.employeeList.employeeMapById.keySeq().map(id => getEmployeeById(state, id)) + .sortBy(employee => employee.name).toList(); oldEmployeeMapById = state.employeeList.employeeMapById; employeeListForOldEmployeeMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts index 103d64cf5..b8f3c81ac 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts @@ -37,7 +37,8 @@ export const getSkillList = (state: AppState): List => { return skillListForOldSkillMapById; } - const out = state.skillList.skillMapById.keySeq().map(key => getSkillById(state, key)).toList(); + const out = state.skillList.skillMapById.keySeq().map(key => getSkillById(state, key)) + .sortBy(skill => skill.name).toList(); oldSkillMapById = state.skillList.skillMapById; skillListForOldSkillMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts index 11b660aeb..5f5e1dacf 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts @@ -41,7 +41,8 @@ export const getSpotList = (state: AppState): List => { return spotListForOldSpotMapById; } - const out = state.spotList.spotMapById.keySeq().map(key => getSpotById(state, key)).toList(); + const out = state.spotList.spotMapById.keySeq().map(key => getSpotById(state, key)) + .sortBy(spot => spot.name).toList(); oldSpotMapById = state.spotList.spotMapById; spotListForOldSpotMapById = out; diff --git a/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx b/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx index 9807f0436..4e951b638 100644 --- a/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx @@ -21,7 +21,6 @@ import { mockTranslate } from 'setupTests'; import { AlertComponent } from 'store/alert/types'; import moment from 'moment'; import { ServerSideExceptionInfo, BasicObject } from 'types'; -import { List } from 'immutable'; import { Alerts, Props, mapToComponent } from './Alerts'; describe('Alerts', () => { @@ -75,13 +74,13 @@ describe('Alerts', () => { }); const noAlerts: Props = { - alerts: List(), + alerts: [], removeAlert: jest.fn(), }; const date = new Date(); // setupTests set the date to a mock date for us const someAlerts: Props = { - alerts: List([ + alerts: [ { id: 0, createdAt: date, @@ -109,12 +108,12 @@ const someAlerts: Props = { components: [], componentProps: [], }, - ]), + ], removeAlert: jest.fn(), }; const someAlertsWithComponents: Props = { - alerts: List([ + alerts: [ { id: 0, createdAt: date, @@ -138,6 +137,6 @@ const someAlertsWithComponents: Props = { }, }], }, - ]), + ], removeAlert: jest.fn(), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx b/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx index 943cd0949..79c71de95 100644 --- a/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/Alerts.tsx @@ -24,11 +24,10 @@ import * as alertOperations from 'store/alert/operations'; import moment from 'moment'; import { useInterval } from 'util/FunctionalComponentUtils'; import { BasicObject, ServerSideExceptionInfo } from 'types'; -import { List } from 'immutable'; import { ServerSideExceptionDialog } from './components/ServerSideExceptionDialog'; interface StateProps { - alerts: List; + alerts: AlertInfo[]; } interface DispatchProps { @@ -36,7 +35,7 @@ interface DispatchProps { } const mapStateToProps = (state: AppState): StateProps => ({ - alerts: state.alerts.alertList, + alerts: state.alerts.alertList.toArray(), }); const mapDispatchToProps: DispatchProps = { @@ -60,7 +59,7 @@ const Alerts: React.FC = (props) => { const [hoveredOverAlerts, hoveredOverAlertsSetter] = React.useState([] as number[]); const shouldUpdateNextSecond = props.alerts.filter(alert => hoveredOverAlerts.find( id => id === alert.id, - ) === undefined).size > 0; + ) === undefined).length > 0; const additionClassNames = (alert: AlertInfo) => { const secondsFromEvent = moment.duration(moment().diff(moment(alert.createdAt))).asSeconds(); diff --git a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx index 844b3ae1e..7896d9034 100644 --- a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.test.tsx @@ -19,7 +19,6 @@ import * as React from 'react'; import { stringSorter } from 'util/CommonSorters'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { List } from 'immutable'; import { DataTable, DataTableProps, DataTableUrlProps } from './DataTable'; interface MockData {name: string; number: number} @@ -62,11 +61,11 @@ describe('DataTable component', () => { const dataTable = new MockDataTable(twoRows); dataTable.render(); expect(dataTable.displayDataRow).toBeCalledTimes(2); - expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData.get(0)); - expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData.get(1)); + expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData[0]); + expect(dataTable.displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData[1]); expect(dataTable.editDataRow).toBeCalledTimes(2); - expect(dataTable.editDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData.get(0), expect.any(Function)); - expect(dataTable.editDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData.get(1), expect.any(Function)); + expect(dataTable.editDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData[0], expect.any(Function)); + expect(dataTable.editDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData[1], expect.any(Function)); }); it('should render viewer initially', () => { @@ -104,15 +103,15 @@ describe('DataTable component', () => { expect((dataTable.instance() as MockDataTable).getInitialStateForNewRow).toBeCalled(); expect((dataTable.instance() as MockDataTable).displayDataRow).toBeCalledTimes(2); - expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData.get(0)); - expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData.get(1)); + expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(1, twoRows.tableData[0]); + expect((dataTable.instance() as MockDataTable).displayDataRow).toHaveBeenNthCalledWith(2, twoRows.tableData[1]); expect((dataTable.instance() as MockDataTable).editDataRow).toBeCalledTimes(3); expect((dataTable.instance() as MockDataTable).editDataRow) .toHaveBeenNthCalledWith(1, {}, expect.any(Function)); expect((dataTable.instance() as MockDataTable).editDataRow) - .toHaveBeenNthCalledWith(2, twoRows.tableData.get(0), expect.any(Function)); + .toHaveBeenNthCalledWith(2, twoRows.tableData[0], expect.any(Function)); expect((dataTable.instance() as MockDataTable).editDataRow) - .toHaveBeenNthCalledWith(3, twoRows.tableData.get(1), expect.any(Function)); + .toHaveBeenNthCalledWith(3, twoRows.tableData[1], expect.any(Function)); expect(toJson(shallow(
{dataTable.instance().render()}
))).toMatchSnapshot(); }); @@ -319,7 +318,7 @@ const noRows: DataTableProps = { tReady: true, title: 'Data Table', columnTitles: ['Column 1', 'Column 2'], - tableData: List(), + tableData: [], ...getRouterProps('/table', {}), }; @@ -328,6 +327,6 @@ const twoRows: DataTableProps = { tReady: true, title: 'Data Table', columnTitles: ['Column 1', 'Column 2'], - tableData: List([{ name: 'Some Data', number: 1 }, { name: 'More Data', number: 2 }]), + tableData: [{ name: 'Some Data', number: 1 }, { name: 'More Data', number: 2 }], ...getRouterProps('/table', {}), }; diff --git a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx index f86c459e4..0e737f57e 100644 --- a/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/components/DataTable.tsx @@ -32,14 +32,14 @@ import { toggleElement, conditionally } from 'util/ImmutableCollectionOperations import { WithTranslation } from 'react-i18next'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { RouteComponentProps } from 'react-router'; -import { List } from 'immutable'; +import { List, Seq } from 'immutable'; import FilterComponent from './FilterComponent'; import { EditableComponent } from './EditableComponent'; export interface DataTableProps extends WithTranslation, RouteComponentProps { title: string; columnTitles: string[]; - tableData: List; + tableData: T[]; } interface DataTableState { @@ -297,7 +297,7 @@ export abstract class DataTable> extends React.Co ]) : List(); const sorters = this.getSorters(); - const filteredRows = conditionally(this.props.tableData.valueSeq(), + const filteredRows = conditionally(Seq(this.props.tableData), // eslint-disable-next-line consistent-return (s) => { if (urlProps.sortBy !== null) { diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx index 3278259e7..a9840db97 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.test.tsx @@ -17,11 +17,10 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import OptionalInput from 'ui/components/OptionalInput'; -import { Sorter, error } from 'types'; +import { Sorter } from 'types'; import { Contract } from 'domain/Contract'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { List } from 'immutable'; import { ContractsPage, Props } from './ContractsPage'; describe('Contracts page', () => { @@ -37,14 +36,14 @@ describe('Contracts page', () => { it('should render the viewer correctly', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) ?? error(); + const contract = twoContracts.tableData[1]; const viewer = shallow(contractsPage.renderViewer(contract)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) ?? error(); + const contract = twoContracts.tableData[1]; const editor = shallow(contractsPage.renderEditor(contract)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -105,7 +104,7 @@ describe('Contracts page', () => { it('should call addContract on addData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) ?? error(); + const contract = twoContracts.tableData[1]; contractsPage.addData(contract); expect(twoContracts.addContract).toBeCalled(); expect(twoContracts.addContract).toBeCalledWith(contract); @@ -113,7 +112,7 @@ describe('Contracts page', () => { it('should call updateContract on updateData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) ?? error(); + const contract = twoContracts.tableData[1]; contractsPage.updateData(contract); expect(twoContracts.updateContract).toBeCalled(); expect(twoContracts.updateContract).toBeCalledWith(contract); @@ -121,7 +120,7 @@ describe('Contracts page', () => { it('should call removeContract on removeData', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = twoContracts.tableData.get(1) ?? error(); + const contract = twoContracts.tableData[1]; contractsPage.removeData(contract); expect(twoContracts.removeContract).toBeCalled(); expect(twoContracts.removeContract).toBeCalledWith(contract); @@ -131,55 +130,55 @@ describe('Contracts page', () => { const contractPage = new ContractsPage(twoContracts); const filter = contractPage.getFilter(); - expect(twoContracts.tableData.filter(filter('1'))).toEqual(List([twoContracts.tableData.get(0) ?? error()])); - expect(twoContracts.tableData.filter(filter('2'))).toEqual(List([twoContracts.tableData.get(1) ?? error()])); + expect(twoContracts.tableData.filter(filter('1'))).toEqual([twoContracts.tableData[0]]); + expect(twoContracts.tableData.filter(filter('2'))).toEqual([twoContracts.tableData[1]]); }); it('should return a sorter that sort by name', () => { const contractPage = new ContractsPage(twoContracts); const sorter = contractPage.getSorters()[0] as Sorter; - const list = [twoContracts.tableData.get(1) ?? error(), twoContracts.tableData.get(0) ?? error()]; - expect(list.sort(sorter)).toEqual(twoContracts.tableData.toArray()); + const list = [twoContracts.tableData[1], twoContracts.tableData[0]]; + expect(list.sort(sorter)).toEqual(twoContracts.tableData); }); it('should treat incomplete data as incomplete', () => { const contractsPage = new ContractsPage(twoContracts); - const noName = { ...twoContracts.tableData.get(1) ?? error(), name: undefined }; + const noName = { ...twoContracts.tableData[1], name: undefined }; const result1 = contractsPage.isDataComplete(noName); expect(result1).toEqual(false); - const noMaxHoursPerDay = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerDay: undefined }; + const noMaxHoursPerDay = { ...twoContracts.tableData[1], maximumMinutesPerDay: undefined }; const result2 = contractsPage.isDataComplete(noMaxHoursPerDay); expect(result2).toEqual(false); - const noMaxHoursPerWeek = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerWeek: undefined }; + const noMaxHoursPerWeek = { ...twoContracts.tableData[1], maximumMinutesPerWeek: undefined }; const result3 = contractsPage.isDataComplete(noMaxHoursPerWeek); expect(result3).toEqual(false); - const noMaxHoursPerMonth = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerMonth: undefined }; + const noMaxHoursPerMonth = { ...twoContracts.tableData[1], maximumMinutesPerMonth: undefined }; const result4 = contractsPage.isDataComplete(noMaxHoursPerMonth); expect(result4).toEqual(false); - const noMaxHoursPerYear = { ...twoContracts.tableData.get(1) ?? error(), maximumMinutesPerYear: undefined }; + const noMaxHoursPerYear = { ...twoContracts.tableData[1], maximumMinutesPerYear: undefined }; const result5 = contractsPage.isDataComplete(noMaxHoursPerYear); expect(result5).toEqual(false); - const completed = { ...twoContracts.tableData.get(1) ?? error() }; + const completed = { ...twoContracts.tableData[1] }; const result6 = contractsPage.isDataComplete(completed); expect(result6).toEqual(true); }); it('should treat empty name as invalid', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = { ...twoContracts.tableData.get(1) ?? error(), name: '' }; + const contract = { ...twoContracts.tableData[1], name: '' }; const result = contractsPage.isValid(contract); expect(result).toEqual(false); }); it('should treat non-empty name as valid', () => { const contractsPage = new ContractsPage(twoContracts); - const contract = { ...twoContracts.tableData.get(1) ?? error(), name: 'Contract' }; + const contract = { ...twoContracts.tableData[1], name: 'Contract' }; const result = contractsPage.isValid(contract); expect(result).toEqual(true); }); @@ -191,7 +190,7 @@ const noContracts: Props = { tenantId: 0, title: 'Contracts', columnTitles: ['Name', 'Max Hours Per Day', 'Max Hours Per Week', 'Max Hours Per Month', 'Max Hours Per Year'], - tableData: List(), + tableData: [], addContract: jest.fn(), updateContract: jest.fn(), removeContract: jest.fn(), @@ -204,7 +203,7 @@ const twoContracts: Props = { tenantId: 0, title: 'Contracts', columnTitles: ['Name', 'Max Hours Per Day', 'Max Hours Per Week', 'Max Hours Per Month', 'Max Hours Per Year'], - tableData: List([{ + tableData: [{ id: 0, version: 0, tenantId: 0, @@ -223,7 +222,7 @@ const twoContracts: Props = { maximumMinutesPerWeek: 20, maximumMinutesPerMonth: 10, maximumMinutesPerYear: 120, - }]), + }], addContract: jest.fn(), updateContract: jest.fn(), removeContract: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx index 413d1a602..9b164511c 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx @@ -37,7 +37,7 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ title: ownProps.t('contracts'), columnTitles: [ownProps.t('name'), ownProps.t('maxMinutesPerDay'), ownProps.t('maxMinutesPerWeek'), ownProps.t('maxMinutesPerMonth'), ownProps.t('maxMinutesPerYear')], - tableData: contractSelectors.getContractList(state), + tableData: contractSelectors.getContractList(state).toArray(), tenantId: state.tenantData.currentTenantId, }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx index c182c8860..daa8f1f98 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeePage.test.tsx @@ -18,13 +18,12 @@ import toJson from 'enzyme-to-json'; import * as React from 'react'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { Sorter, error } from 'types'; +import { Sorter } from 'types'; import { Employee } from 'domain/Employee'; import { act } from 'react-dom/test-utils'; import { useTranslation, Trans } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import { FileUpload } from '@patternfly/react-core'; -import { List } from 'immutable'; import { EmployeesPage, Props } from './EmployeesPage'; describe('Employees page', () => { @@ -44,14 +43,14 @@ describe('Employees page', () => { it('should render the viewer correctly', () => { const employeesPage = new EmployeesPage(twoEmployees); - const spot = twoEmployees.tableData.get(1) ?? error(); + const spot = twoEmployees.tableData[1]; const viewer = shallow(employeesPage.renderViewer(spot)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const employeesPage = new EmployeesPage(twoEmployees); - const spot = twoEmployees.tableData.get(1) ?? error(); + const spot = twoEmployees.tableData[1]; const editor = shallow(employeesPage.renderEditor(spot)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -69,19 +68,19 @@ describe('Employees page', () => { setProperty.mockClear(); const contractCol = mount(editor[1] as React.ReactElement); act(() => { - contractCol.find(TypeaheadSelectInput).props().onChange(twoEmployees.contractList.get(0) ?? error()); + contractCol.find(TypeaheadSelectInput).props().onChange(twoEmployees.contractList[0]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('contract', twoEmployees.contractList.get(0) ?? error()); + expect(setProperty).toBeCalledWith('contract', twoEmployees.contractList[0]); setProperty.mockClear(); const skillProficiencySetCol = mount(editor[2] as React.ReactElement); act(() => { skillProficiencySetCol.find(MultiTypeaheadSelectInput).props() - .onChange([twoEmployees.skillList.get(0) ?? error()]); + .onChange([twoEmployees.skillList[0]]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('skillProficiencySet', [twoEmployees.skillList.get(0) ?? error()]); + expect(setProperty).toBeCalledWith('skillProficiencySet', [twoEmployees.skillList[0]]); setProperty.mockClear(); const shortIdCol = shallow(editor[3] as React.ReactElement); @@ -106,7 +105,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], tenantId: 0, id: 1, version: 0, @@ -123,7 +122,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], tenantId: 0, id: 1, version: 0, @@ -140,7 +139,7 @@ describe('Employees page', () => { const employee: Employee = { name: 'Employee', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], tenantId: 0, id: 1, version: 0, @@ -156,30 +155,30 @@ describe('Employees page', () => { const employeesPage = new EmployeesPage(twoEmployees); const filter = employeesPage.getFilter(); - expect(twoEmployees.tableData.filter(filter('1'))).toEqual(List([ - twoEmployees.tableData.get(0) ?? error(), - twoEmployees.tableData.get(1) ?? error(), - ])); - expect(twoEmployees.tableData.filter(filter('Skill 1'))).toEqual(List([ - twoEmployees.tableData.get(1) ?? error(), - ])); - expect(twoEmployees.tableData.filter(filter('2'))).toEqual(List([twoEmployees.tableData.get(1) ?? error()])); - expect(twoEmployees.tableData.filter(filter('Contract 2'))).toEqual(List([ - twoEmployees.tableData.get(1) ?? error(), - ])); - expect(twoEmployees.tableData.filter(filter('Employee 2'))).toEqual(List([ - twoEmployees.tableData.get(1) ?? error(), - ])); + expect(twoEmployees.tableData.filter(filter('1'))).toEqual([ + twoEmployees.tableData[0], + twoEmployees.tableData[1], + ]); + expect(twoEmployees.tableData.filter(filter('Skill 1'))).toEqual([ + twoEmployees.tableData[1], + ]); + expect(twoEmployees.tableData.filter(filter('2'))).toEqual([twoEmployees.tableData[1]]); + expect(twoEmployees.tableData.filter(filter('Contract 2'))).toEqual([ + twoEmployees.tableData[1], + ]); + expect(twoEmployees.tableData.filter(filter('Employee 2'))).toEqual([ + twoEmployees.tableData[1], + ]); }); it('should return a sorter that sort by name and contract', () => { const employeesPage = new EmployeesPage(twoEmployees); const nameSorter = employeesPage.getSorters()[0] as Sorter; - let list = [twoEmployees.tableData.get(1) ?? error(), twoEmployees.tableData.get(0) ?? error()]; - expect(list.sort(nameSorter)).toEqual(twoEmployees.tableData.toArray()); - list = [twoEmployees.tableData.get(1) ?? error(), twoEmployees.tableData.get(0) ?? error()]; + let list = [twoEmployees.tableData[1], twoEmployees.tableData[0]]; + expect(list.sort(nameSorter)).toEqual(twoEmployees.tableData); + list = [twoEmployees.tableData[1], twoEmployees.tableData[0]]; const contractSorter = employeesPage.getSorters()[1] as Sorter; - expect(list.sort(contractSorter)).toEqual(twoEmployees.tableData.toArray()); + expect(list.sort(contractSorter)).toEqual(twoEmployees.tableData); expect(employeesPage.getSorters()[2]).toBeNull(); }); @@ -197,7 +196,7 @@ describe('Employees page', () => { const noName = { tenantId: 0, skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], }; const result1 = employeesPage.isDataComplete(noName); expect(result1).toEqual(false); @@ -205,7 +204,7 @@ describe('Employees page', () => { const noSkills = { tenantId: 0, name: 'Name', - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], }; const result2 = employeesPage.isDataComplete(noSkills); expect(result2).toEqual(false); @@ -222,7 +221,7 @@ describe('Employees page', () => { tenantId: 0, name: 'Name', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], shortId: 'N', color: '#FFFFFF', }; @@ -236,7 +235,7 @@ describe('Employees page', () => { tenantId: 0, name: '', skillProficiencySet: [], - contract: twoEmployees.contractList.get(0) ?? error(), + contract: twoEmployees.contractList[0], shortId: 'N', color: '#FFFFFF', }; @@ -246,7 +245,7 @@ describe('Employees page', () => { it('should treat non-empty name as valid', () => { const employeesPage = new EmployeesPage(twoEmployees); - const components = twoEmployees.tableData.get(0) ?? error(); + const components = twoEmployees.tableData[0]; const result = employeesPage.isValid(components); expect(result).toEqual(true); }); @@ -272,9 +271,9 @@ const noEmployees: Props = { tenantId: 0, title: 'Employees', columnTitles: ['Name', 'Contract', 'Skill Set'], - tableData: List(), - skillList: List(), - contractList: List(), + tableData: [], + skillList: [], + contractList: [], addEmployee: jest.fn(), updateEmployee: jest.fn(), removeEmployee: jest.fn(), @@ -289,7 +288,7 @@ const twoEmployees: Props = { tenantId: 0, title: 'Employees', columnTitles: ['Name', 'Contract', 'Skill Set'], - tableData: List([{ + tableData: [{ id: 0, version: 0, tenantId: 0, @@ -326,9 +325,9 @@ const twoEmployees: Props = { }, shortId: 'e1', color: '#FFFFFF', - }]), - skillList: List([{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }]), - contractList: List([ + }], + skillList: [{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }], + contractList: [ { tenantId: 0, id: 0, @@ -349,7 +348,7 @@ const twoEmployees: Props = { maximumMinutesPerMonth: null, maximumMinutesPerYear: null, }, - ]), + ], addEmployee: jest.fn(), updateEmployee: jest.fn(), removeEmployee: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx index d4a132fd5..98b69e634 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx @@ -49,12 +49,11 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { withTranslation, WithTranslation, Trans } from 'react-i18next'; import moment from 'moment'; import { ColorPicker, StatefulColorPicker, defaultColorList } from 'ui/components/ColorPicker'; -import { List } from 'immutable'; interface StateProps extends DataTableProps { tenantId: number; - skillList: List; - contractList: List; + skillList: Skill[]; + contractList: Contract[]; } const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ @@ -67,9 +66,9 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ ownProps.t('shortId'), ownProps.t('color'), ], - tableData: employeeSelectors.getEmployeeList(state), - skillList: skillSelectors.getSkillList(state), - contractList: contractSelectors.getContractList(state), + tableData: employeeSelectors.getEmployeeList(state).toArray(), + skillList: skillSelectors.getSkillList(state).toArray(), + contractList: contractSelectors.getContractList(state).toArray(), tenantId: state.tenantData.currentTenantId, }); @@ -162,13 +161,13 @@ export class EmployeesPage extends DataTable { emptyText={this.props.t('selectAContract')} optionToStringMap={c => c.name} value={data.contract} - options={this.props.contractList.toArray()} + options={this.props.contractList} onChange={contract => setProperty('contract', contract)} />, skill.name} value={data.skillProficiencySet ? data.skillProficiencySet : []} onChange={selected => setProperty('skillProficiencySet', selected)} @@ -249,7 +248,7 @@ export class EmployeesPage extends DataTable { /> ); - if (this.props.contractList.size === 0) { + if (this.props.contractList.length === 0) { return ( {importElement} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx index afdff8855..e50492cec 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx @@ -33,7 +33,6 @@ import { rosterSelectors } from 'store/roster'; import { tenantSelectors } from 'store/tenant'; import moment from 'moment'; import { Employee } from 'domain/Employee'; -import { error } from 'types'; import { SeatJigsaw } from './SeatJigsaw'; import { EditTimeBucketModal } from './EditTimeBucketModal'; import { EmployeeStubList, Stub } from './EmployeeStub'; @@ -45,7 +44,7 @@ export const RotationPage: React.FC<{}> = () => { const tenantId = useSelector(tenantSelectors.getTenantId); const rosterState = useSelector(rosterSelectors.getRosterState); const isLoading = useSelector(timeBucketSelectors.isLoading); - const spotList = useSelector(spotSelectors.getSpotList); + const spotList = useSelector(spotSelectors.getSpotList).toArray(); const timeBucketList = useSelector(timeBucketSelectors.getTimeBucketList); const dispatch = useDispatch(); @@ -53,8 +52,8 @@ export const RotationPage: React.FC<{}> = () => { const [selectedStub, setSelectedStub] = useState('NO_SHIFT'); const [isEditingTimeBuckets, setIsEditingTimeBuckets] = useState(false); - const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.size > 0) - ? (spotList.get(0) ?? error('Spot list is not empty but does not have an element')).name : undefined); + const [shownSpotName, setShownSpotName] = useUrlState('spot', (spotList.length > 0) + ? spotList[0].name : undefined); const shownSpot = spotList.find(s => s.name === shownSpotName); const shownTimeBuckets = shownSpot ? timeBucketList.filter(tb => tb.spot.id === shownSpot.id) : []; const oldShownTimeBuckets = useRef(shownTimeBuckets.map(tb => tb.id).join(',')); @@ -72,8 +71,8 @@ export const RotationPage: React.FC<{}> = () => { const [stubList, setStubList] = useState(getEmployeesInTimeBuckets()); React.useEffect(() => { - if (shownSpot === undefined && spotList.size > 0) { - setShownSpotName((spotList.get(0) ?? error()).name); + if (shownSpot === undefined && spotList.length > 0) { + setShownSpotName((spotList[0]).name); } }, [spotList, shownSpot, setShownSpotName]); @@ -88,7 +87,7 @@ export const RotationPage: React.FC<{}> = () => { }, [oldShownTimeBuckets, shownSpotName, spotList, timeBucketList, getEmployeesInTimeBuckets]); - if (rosterState === null || isLoading || spotList.size <= 0 || shownSpotName === null) { + if (rosterState === null || isLoading || spotList.length <= 0 || shownSpotName === null) { return ( @@ -116,7 +115,7 @@ export const RotationPage: React.FC<{}> = () => { aria-label="Select Spot" emptyText={t('selectSpot')} optionToStringMap={spot => spot.name} - options={spotList.toArray()} + options={spotList} value={shownSpot} onChange={(s) => { setShownSpotName(s ? s.name : null); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx index b7738a8c1..3998807e9 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.test.tsx @@ -27,7 +27,6 @@ import { useTranslation, Trans } from 'react-i18next'; import Actions from 'ui/components/Actions'; import Schedule from 'ui/components/calendar/Schedule'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { List } from 'immutable'; import { getShiftColor } from './ShiftEvent'; import { ShiftRosterPage, Props, ShiftRosterUrlProps } from './CurrentShiftRosterPage'; import ExportScheduleModal from './ExportScheduleModal'; @@ -48,8 +47,8 @@ describe('Current Shift Roster Page', () => { const shiftRosterPage = shallow(); expect(toJson(shiftRosterPage)).toMatchSnapshot(); @@ -109,8 +108,8 @@ describe('Current Shift Roster Page', () => { it('should go to the Spots page if the user click on the link', () => { const shiftRosterPage = shallow(); mount((shiftRosterPage.find(Trans).prop('components') as any)[2]).simulate('click'); @@ -129,7 +128,7 @@ describe('Current Shift Roster Page', () => { expect(baseProps.getShiftRosterFor).toBeCalledWith({ fromDate: newDateStart, toDate: newDateEnd, - spotList: baseProps.shownSpotList.toArray(), + spotList: baseProps.shownSpotList, }); }); @@ -471,8 +470,8 @@ const baseProps: Props = { tReady: true, isSolving: false, isLoading: false, - allSpotList: List([spot, newSpot]), - shownSpotList: List([spot]), + allSpotList: [spot, newSpot], + shownSpotList: [spot], spotIdToShiftListMap: new Map([ [2, [shift]], ]), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx index 4e9cf2638..b4bd7dda2 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx @@ -38,8 +38,6 @@ import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; -import { List } from 'immutable'; -import { error } from 'types'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; @@ -48,8 +46,8 @@ interface StateProps { tenantId: number; isSolving: boolean; isLoading: boolean; - allSpotList: List; - shownSpotList: List; + allSpotList: Spot[]; + shownSpotList: Spot[]; spotIdToShiftListMap: Map; totalNumOfSpots: number; rosterState: RosterState | null; @@ -67,10 +65,10 @@ const mapStateToProps = (state: AppState): StateProps => ({ tenantId: state.tenantData.currentTenantId, isSolving: state.solverState.solverStatus !== 'NOT_SOLVING', isLoading: rosterSelectors.isShiftRosterLoading(state), - allSpotList: spotSelectors.getSpotList(state), + allSpotList: spotSelectors.getSpotList(state).toArray(), // The use of "x = isLoading? x : getUpdatedData()" is a way to use old value if data is still loading - shownSpotList: List(lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList - : rosterSelectors.getSpotListInShiftRoster(state)), + shownSpotList: lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList + : rosterSelectors.getSpotListInShiftRoster(state), spotIdToShiftListMap: lastSpotIdToShiftListMap = rosterSelectors.getSpotListInShiftRoster(state) .reduce((prev, curr) => prev.set(curr.id as number, rosterSelectors.getShiftListForSpot(state, curr)), @@ -133,7 +131,7 @@ export class ShiftRosterPage extends React.Component { onUpdateShiftRoster(urlProps: ShiftRosterUrlProps) { if (this.props.rosterState) { const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) - || (this.props.allSpotList.get(0) /* can be undefined */); + || (this.props.allSpotList[0] /* can be undefined */); const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); @@ -237,10 +235,10 @@ export class ShiftRosterPage extends React.Component { spot: null, week: null, }); - const changedTenant = this.props.shownSpotList.size === 0 - || this.props.tenantId !== (this.props.shownSpotList.get(0) ?? error()).tenantId; + const changedTenant = this.props.shownSpotList.length === 0 + || this.props.tenantId !== (this.props.shownSpotList[0]).tenantId; - if (this.props.shownSpotList.size === 0 || this.state.firstLoad + if (this.props.shownSpotList.length === 0 || this.state.firstLoad || changedTenant || this.props.rosterState === null) { return ( @@ -266,7 +264,7 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || new Date()).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) - || (this.props.shownSpotList.get(0) ?? error()); + || (this.props.shownSpotList[0]); const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -312,7 +310,7 @@ export class ShiftRosterPage extends React.Component { aria-label="Select Spot" emptyText={t('selectSpot')} optionToStringMap={spot => spot.name} - options={this.props.allSpotList.toArray()} + options={this.props.allSpotList} value={shownSpot} onChange={(s) => { this.onUpdateShiftRoster({ @@ -367,7 +365,7 @@ export class ShiftRosterPage extends React.Component { defaultToDate={endDate} /> - key={(this.props.shownSpotList.get(0) ?? error()).id} + key={(this.props.shownSpotList[0]).id} startDate={startDate} endDate={endDate} events={this.props.spotIdToShiftListMap.get(shownSpot.id as number) || []} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx index 7c0fccb16..f67a0a172 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.test.tsx @@ -21,7 +21,6 @@ import { Employee } from 'domain/Employee'; import { Shift } from 'domain/Shift'; import moment from 'moment'; import { useTranslation } from 'react-i18next'; -import { List } from 'immutable'; import { EditShiftModal } from './EditShiftModal'; describe('Edit Shift Modal', () => { @@ -407,7 +406,7 @@ const baseProps = { shift: undefined, tReady: true, tenantId: 1, - spotList: List([spot]), - skillList: List(), - employeeList: List([employee]), + spotList: [spot], + skillList: [], + employeeList: [employee], }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx index 98dec720c..37f3b9cbc 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx @@ -34,15 +34,14 @@ import { Skill } from 'domain/Skill'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; import moment from 'moment'; import { useValidators } from 'util/ValidationUtils'; -import { List } from 'immutable'; interface Props { tenantId: number; shift?: Shift; isOpen: boolean; - skillList: List; - employeeList: List; - spotList: List; + skillList: Skill[]; + employeeList: Employee[]; + spotList: Spot[]; onSave: (shift: Shift) => void; onDelete: (shift: Shift) => void; onClose: () => void; @@ -57,9 +56,9 @@ const mapStateToProps = (state: AppState, ownProps: { }): Props => ({ ...ownProps, tenantId: state.tenantData.currentTenantId, - skillList: skillSelectors.getSkillList(state), - employeeList: employeeSelectors.getEmployeeList(state), - spotList: spotSelectors.getSpotList(state), + skillList: skillSelectors.getSkillList(state).toArray(), + employeeList: employeeSelectors.getEmployeeList(state).toArray(), + spotList: spotSelectors.getSpotList(state).toArray(), }); interface State { @@ -213,7 +212,7 @@ export class EditShiftModal extends React.Component spot.name} onChange={spot => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, spot }, @@ -226,7 +225,7 @@ export class EditShiftModal extends React.Component skill.name} onChange={requiredSkillSet => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, requiredSkillSet }, @@ -240,7 +239,7 @@ export class EditShiftModal extends React.Component (employee ? employee.name : t('unassigned'))} onChange={employee => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, employee: (employee !== undefined) ? employee : null }, @@ -265,7 +264,7 @@ export class EditShiftModal extends React.Component (employee ? employee.name : t('none'))} onChange={employee => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, rotationEmployee: (employee !== undefined) ? employee : null }, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx index 2514a21a3..11145ea82 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.test.tsx @@ -22,7 +22,6 @@ import moment from 'moment'; import { useTranslation } from 'react-i18next'; import { Modal } from '@patternfly/react-core'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { List } from 'immutable'; import { ExportScheduleModal } from './ExportScheduleModal'; describe('Export Schedule Modal', () => { @@ -47,13 +46,13 @@ describe('Export Schedule Modal', () => { />); const urlButton = shallow(exportScheduleModal.find(Modal).prop('actions')[1]); expect(urlButton.prop('href')) - .toEqual(getExportUrlFor(baseProps.defaultFromDate, baseProps.defaultToDate, baseProps.spotList.toArray())); + .toEqual(getExportUrlFor(baseProps.defaultFromDate, baseProps.defaultToDate, baseProps.spotList)); }); it('should export to _blank if spot list is empty', () => { const exportScheduleModal = shallow(); const urlButton = shallow(exportScheduleModal.find(Modal).prop('actions')[1]); expect(urlButton.prop('href')).toEqual('_blank'); @@ -116,5 +115,5 @@ const baseProps = { defaultFromDate: moment('2018-07-01').toDate(), defaultToDate: moment('2018-07-07').toDate(), tenantId: 1, - spotList: List([spot]), + spotList: [spot], }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx index aa12061cc..dc4654e91 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx @@ -23,11 +23,10 @@ import { AppState } from 'store/types'; import { spotSelectors } from 'store/spot'; import { connect } from 'react-redux'; import moment from 'moment'; -import { List } from 'immutable'; interface StateProps { tenantId: number; - spotList: List; + spotList: Spot[]; } interface OwnProps { @@ -40,14 +39,14 @@ interface OwnProps { const mapStateToProps = (state: AppState, ownProps: OwnProps): StateProps & OwnProps => ({ ...ownProps, tenantId: state.tenantData.currentTenantId, - spotList: spotSelectors.getSpotList(state), + spotList: spotSelectors.getSpotList(state).toArray(), }); export const ExportScheduleModal: React.FC = (props) => { const { t } = useTranslation('ExportScheduleModal'); const [fromDate, setFromDate] = React.useState(props.defaultFromDate); const [toDate, setToDate] = React.useState(props.defaultToDate); - const [exportedSpots, setExportedSpots] = React.useState>(props.spotList); + const [exportedSpots, setExportedSpots] = React.useState(props.spotList); // Work around since useEffect use shallowEquality, and the same date created at different times are not equal const defaultFromDateTime = props.defaultFromDate.getTime(); @@ -61,7 +60,7 @@ export const ExportScheduleModal: React.FC = (props) => { } }, [props.isOpen, defaultFromDateTime, defaultToDateTime, props.spotList]); - const spotSet = (exportedSpots.size > 0) ? exportedSpots.map(s => `${s.id}`).join(',') : null; + const spotSet = (exportedSpots.length > 0) ? exportedSpots.map(s => `${s.id}`).join(',') : null; let exportUrl = '_blank'; if (spotSet && toDate && fromDate) { @@ -122,10 +121,10 @@ export const ExportScheduleModal: React.FC = (props) => { spot.name} - onChange={newExportedSpots => setExportedSpots(List(newExportedSpots))} + onChange={newExportedSpots => setExportedSpots(newExportedSpots)} /> diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx index e6a4e027d..748565a89 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx @@ -28,6 +28,7 @@ import { RosterState } from 'domain/RosterState'; import { Modal, TextInput, Checkbox, AccordionToggle, AccordionContent } from '@patternfly/react-core'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; import { List } from 'immutable'; + import { ProvisionShiftsModal, ProvisionShiftsModalProps, SpotTimeBucketSelect, SpotTimeBucketSelectProps, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx index 0887be36f..9ca42aaa8 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx @@ -28,7 +28,6 @@ import { spotSelectors } from 'store/spot'; import { Spot } from 'domain/Spot'; import { TimeBucket } from 'domain/TimeBucket'; import moment from 'moment'; -import { List } from 'immutable'; export interface SpotTimeBucketSelectProps { spot: Spot; @@ -132,11 +131,11 @@ export const ProvisionShiftsModal: React.FC = (props) const [fromDate, setFromDate] = React.useState(props.defaultFromDate); const [toDate, setToDate] = React.useState(props.defaultToDate); const [rotationOffset, setRotationOffset] = React.useState(0); - const [provisionedSpots, setProvisionedSpots] = React.useState>(List()); + const [provisionedSpots, setProvisionedSpots] = React.useState([]); const [provisionedTimeBuckets, setProvisionedTimeBuckets] = React.useState([]); const timeBucketList = useSelector(timeBucketSelectors.getTimeBucketList); - const spotList = useSelector(spotSelectors.getSpotList); + const spotList = useSelector(spotSelectors.getSpotList).toArray(); const rosterState = useSelector(rosterSelectors.getRosterState); const dispatch = useDispatch(); @@ -218,11 +217,11 @@ export const ProvisionShiftsModal: React.FC = (props) spot.name} onChange={(newSpotList) => { - setProvisionedSpots(List(newSpotList)); + setProvisionedSpots(newSpotList); let newTimeBucketList = provisionedTimeBuckets; newSpotList.filter(spot => !provisionedSpots.includes(spot)).forEach((spot) => { // New spot added diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx index d2dac5688..8f8c2e61a 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.test.tsx @@ -27,7 +27,6 @@ import { useTranslation, Trans } from 'react-i18next'; import Actions from 'ui/components/Actions'; import Schedule from 'ui/components/calendar/Schedule'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { List } from 'immutable'; import { getShiftColor } from './ShiftEvent'; import { ShiftRosterPage, Props, ShiftRosterUrlProps } from './ShiftRosterPage'; import ExportScheduleModal from './ExportScheduleModal'; @@ -48,8 +47,8 @@ describe('Shift Roster Page', () => { const shiftRosterPage = shallow(); expect(toJson(shiftRosterPage)).toMatchSnapshot(); @@ -109,8 +108,8 @@ describe('Shift Roster Page', () => { it('should go to the Spots page if the user click on the link', () => { const shiftRosterPage = shallow(); mount((shiftRosterPage.find(Trans).prop('components') as any)[2]).simulate('click'); @@ -129,7 +128,7 @@ describe('Shift Roster Page', () => { expect(baseProps.getShiftRosterFor).toBeCalledWith({ fromDate: newDateStart, toDate: newDateEnd, - spotList: baseProps.shownSpotList.toArray(), + spotList: baseProps.shownSpotList, }); }); @@ -471,8 +470,8 @@ const baseProps: Props = { tReady: true, isSolving: false, isLoading: false, - allSpotList: List([spot, newSpot]), - shownSpotList: List([spot]), + allSpotList: [spot, newSpot], + shownSpotList: [spot], spotIdToShiftListMap: new Map([ [2, [shift]], ]), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx index 23853b182..6bb3ba5ce 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx @@ -38,8 +38,6 @@ import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { getPropsFromUrl, setPropsInUrl, UrlProps } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; -import { List } from 'immutable'; -import { error } from 'types'; import ShiftEvent, { getShiftColor, ShiftPopupHeader, ShiftPopupBody } from './ShiftEvent'; import EditShiftModal from './EditShiftModal'; import ExportScheduleModal from './ExportScheduleModal'; @@ -49,8 +47,8 @@ interface StateProps { tenantId: number; isSolving: boolean; isLoading: boolean; - allSpotList: List; - shownSpotList: List; + allSpotList: Spot[]; + shownSpotList: Spot[]; spotIdToShiftListMap: Map; totalNumOfSpots: number; rosterState: RosterState | null; @@ -68,10 +66,10 @@ const mapStateToProps = (state: AppState): StateProps => ({ tenantId: state.tenantData.currentTenantId, isSolving: state.solverState.solverStatus !== 'NOT_SOLVING', isLoading: rosterSelectors.isShiftRosterLoading(state), - allSpotList: spotSelectors.getSpotList(state), + allSpotList: spotSelectors.getSpotList(state).toArray(), // The use of "x = isLoading? x : getUpdatedData()" is a way to use old value if data is still loading - shownSpotList: List(lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList - : rosterSelectors.getSpotListInShiftRoster(state)), + shownSpotList: lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList + : rosterSelectors.getSpotListInShiftRoster(state), spotIdToShiftListMap: lastSpotIdToShiftListMap = rosterSelectors.getSpotListInShiftRoster(state) .reduce((prev, curr) => prev.set(curr.id as number, rosterSelectors.getShiftListForSpot(state, curr)), @@ -136,7 +134,7 @@ export class ShiftRosterPage extends React.Component { onUpdateShiftRoster(urlProps: ShiftRosterUrlProps) { if (this.props.rosterState) { const spot = this.props.allSpotList.find(s => s.name === urlProps.spot) - || (this.props.allSpotList.get(0) /* can be undefined */); + || (this.props.allSpotList[0] /* can be undefined */); const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); @@ -240,10 +238,10 @@ export class ShiftRosterPage extends React.Component { spot: null, week: null, }); - const changedTenant = this.props.shownSpotList.size === 0 - || this.props.tenantId !== (this.props.shownSpotList.get(0) ?? error()).tenantId; + const changedTenant = this.props.shownSpotList.length === 0 + || this.props.tenantId !== (this.props.shownSpotList[0]).tenantId; - if (this.props.shownSpotList.size === 0 || this.state.firstLoad + if (this.props.shownSpotList.length === 0 || this.state.firstLoad || changedTenant || this.props.rosterState === null) { return ( @@ -269,7 +267,7 @@ export class ShiftRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const shownSpot = this.props.allSpotList.find(s => s.name === urlProps.spot) - || (this.props.shownSpotList.get(0) ?? error()); + || (this.props.shownSpotList[0]); const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -324,7 +322,7 @@ export class ShiftRosterPage extends React.Component { aria-label="Select Spot" emptyText={t('selectSpot')} optionToStringMap={spot => spot.name} - options={this.props.allSpotList.toArray()} + options={this.props.allSpotList} value={shownSpot} onChange={(s) => { this.onUpdateShiftRoster({ @@ -384,7 +382,7 @@ export class ShiftRosterPage extends React.Component { defaultToDate={endDate} /> - key={(this.props.shownSpotList.get(0) ?? error()).id} + key={(this.props.shownSpotList[0]).id} startDate={startDate} endDate={endDate} events={this.props.spotIdToShiftListMap.get(shownSpot.id as number) || []} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx index ba2686f36..b49ebf0ef 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.test.tsx @@ -17,10 +17,9 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { Skill } from 'domain/Skill'; -import { Sorter, ReadonlyPartial, error } from 'types'; +import { Sorter, ReadonlyPartial } from 'types'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { List } from 'immutable'; import { SkillsPage, Props } from './SkillsPage'; describe('Skills page', () => { @@ -91,15 +90,15 @@ describe('Skills page', () => { const skillsPage = new SkillsPage(twoSkills); const filter = skillsPage.getFilter(); - expect(twoSkills.tableData.filter(filter('1'))).toEqual(List([twoSkills.tableData.get(0) ?? error()])); - expect(twoSkills.tableData.filter(filter('2'))).toEqual(List([twoSkills.tableData.get(1) ?? error()])); + expect(twoSkills.tableData.filter(filter('1'))).toEqual([twoSkills.tableData[0]]); + expect(twoSkills.tableData.filter(filter('2'))).toEqual([twoSkills.tableData[1]]); }); it('should return a sorter that sort by name', () => { const skillsPage = new SkillsPage(twoSkills); const sorter = skillsPage.getSorters()[0] as Sorter; - const list = [twoSkills.tableData.get(1) ?? error(), twoSkills.tableData.get(0) ?? error()]; - expect(list.sort(sorter)).toEqual(twoSkills.tableData.toArray()); + const list = [twoSkills.tableData[1], twoSkills.tableData[0]]; + expect(list.sort(sorter)).toEqual(twoSkills.tableData); }); it('should treat incomplete data as incomplete', () => { @@ -135,7 +134,7 @@ const noSkills: Props = { tenantId: 0, title: 'Skills', columnTitles: ['Name'], - tableData: List(), + tableData: [], addSkill: jest.fn(), updateSkill: jest.fn(), removeSkill: jest.fn(), @@ -148,7 +147,7 @@ const twoSkills: Props = { tenantId: 0, title: 'Skills', columnTitles: ['Name'], - tableData: List([{ + tableData: [{ id: 0, version: 0, tenantId: 0, @@ -159,7 +158,7 @@ const twoSkills: Props = { version: 0, tenantId: 0, name: 'Skill 2', - }]), + }], addSkill: jest.fn(), updateSkill: jest.fn(), removeSkill: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx index 94371985c..11a580a03 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx @@ -35,7 +35,7 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ ...ownProps, title: ownProps.t('skills'), columnTitles: [ownProps.t('name')], - tableData: skillSelectors.getSkillList(state), + tableData: skillSelectors.getSkillList(state).toArray(), tenantId: state.tenantData.currentTenantId, }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx index 5b25b0a0c..3443fc0c1 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.test.tsx @@ -17,12 +17,11 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { Sorter, error } from 'types'; +import { Sorter } from 'types'; import { Spot } from 'domain/Spot'; import { act } from 'react-dom/test-utils'; import { useTranslation } from 'react-i18next'; import { getRouterProps } from 'util/BookmarkableTestUtils'; -import { List } from 'immutable'; import { SpotsPage, Props } from './SpotsPage'; describe('Spots page', () => { @@ -38,14 +37,14 @@ describe('Spots page', () => { it('should render the viewer correctly', () => { const spotsPage = new SpotsPage(twoSpots); - const spot = twoSpots.tableData.get(1) ?? error(); + const spot = twoSpots.tableData[1]; const viewer = shallow(spotsPage.renderViewer(spot)); expect(toJson(viewer)).toMatchSnapshot(); }); it('should render the editor correctly', () => { const spotsPage = new SpotsPage(twoSpots); - const spot = twoSpots.tableData.get(1) ?? error(); + const spot = twoSpots.tableData[1]; const editor = shallow(spotsPage.renderEditor(spot)); expect(toJson(editor)).toMatchSnapshot(); }); @@ -62,10 +61,10 @@ describe('Spots page', () => { setProperty.mockClear(); const requiredSkillSetCol = mount(editor[1]); act(() => { - requiredSkillSetCol.find(MultiTypeaheadSelectInput).props().onChange([twoSpots.skillList.get(0) ?? error()]); + requiredSkillSetCol.find(MultiTypeaheadSelectInput).props().onChange([twoSpots.skillList[0]]); }); expect(setProperty).toBeCalled(); - expect(setProperty).toBeCalledWith('requiredSkillSet', [twoSpots.skillList.get(0) ?? error()]); + expect(setProperty).toBeCalledWith('requiredSkillSet', [twoSpots.skillList[0]]); }); it('should call addSpot on addData', () => { @@ -96,20 +95,20 @@ describe('Spots page', () => { const spotsPage = new SpotsPage(twoSpots); const filter = spotsPage.getFilter(); - expect(twoSpots.tableData.filter(filter('1'))).toEqual(List([ - twoSpots.tableData.get(0) ?? error(), - twoSpots.tableData.get(1) ?? error(), - ])); - expect(twoSpots.tableData.filter(filter('Spot 1'))).toEqual(List([twoSpots.tableData.get(0) ?? error()])); - expect(twoSpots.tableData.filter(filter('2'))).toEqual(List([twoSpots.tableData.get(1) ?? error()])); - expect(twoSpots.tableData.filter(filter('Skill'))).toEqual(List([twoSpots.tableData.get(1) ?? error()])); + expect(twoSpots.tableData.filter(filter('1'))).toEqual([ + twoSpots.tableData[0], + twoSpots.tableData[1], + ]); + expect(twoSpots.tableData.filter(filter('Spot 1'))).toEqual([twoSpots.tableData[0]]); + expect(twoSpots.tableData.filter(filter('2'))).toEqual([twoSpots.tableData[1]]); + expect(twoSpots.tableData.filter(filter('Skill'))).toEqual([twoSpots.tableData[1]]); }); it('should return a sorter that sort by name', () => { const spotsPage = new SpotsPage(twoSpots); const sorter = spotsPage.getSorters()[0] as Sorter; - const list = [twoSpots.tableData.get(1) ?? error(), twoSpots.tableData.get(0) ?? error()]; - expect(list.sort(sorter)).toEqual(twoSpots.tableData.toArray()); + const list = [twoSpots.tableData[1], twoSpots.tableData[0]]; + expect(list.sort(sorter)).toEqual(twoSpots.tableData); expect(spotsPage.getSorters()[1]).toBeNull(); }); @@ -150,8 +149,8 @@ const noSpots: Props = { tenantId: 0, title: 'Spots', columnTitles: ['Name'], - tableData: List(), - skillList: List(), + tableData: [], + skillList: [], addSpot: jest.fn(), updateSpot: jest.fn(), removeSpot: jest.fn(), @@ -164,7 +163,7 @@ const twoSpots: Props = { tenantId: 0, title: 'Spots', columnTitles: ['Name'], - tableData: List([{ + tableData: [{ id: 0, version: 0, tenantId: 0, @@ -177,8 +176,8 @@ const twoSpots: Props = { tenantId: 0, name: 'Spot 2', requiredSkillSet: [{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }], - }]), - skillList: List([{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }]), + }], + skillList: [{ tenantId: 0, name: 'Skill 1' }, { tenantId: 0, name: 'Skill 2' }], addSpot: jest.fn(), updateSpot: jest.fn(), removeSpot: jest.fn(), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx index e9201198b..d1cd52e8f 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx @@ -30,19 +30,18 @@ import { stringFilter } from 'util/CommonFilters'; import { withTranslation, WithTranslation } from 'react-i18next'; import { withRouter } from 'react-router'; import { ArrowIcon } from '@patternfly/react-icons'; -import { List } from 'immutable'; interface StateProps extends DataTableProps { tenantId: number; - skillList: List; + skillList: Skill[]; } const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ ...ownProps, title: ownProps.t('spots'), columnTitles: [ownProps.t('name'), ownProps.t('requiredSkillSet')], - tableData: spotSelectors.getSpotList(state), - skillList: skillSelectors.getSkillList(state), + tableData: spotSelectors.getSpotList(state).toArray(), + skillList: skillSelectors.getSkillList(state).toArray(), tenantId: state.tenantData.currentTenantId, }); @@ -112,7 +111,7 @@ export class SpotsPage extends DataTable { skill.name} value={data.requiredSkillSet ? data.requiredSkillSet : []} onChange={selected => setProperty('requiredSkillSet', selected)} From 65fa278a3b8251744b95df553ad1d1e4dbc88c8f Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 1 Dec 2020 13:47:05 -0500 Subject: [PATCH 5/6] Return mutable array in selector All usage in the UI already converts the list into a mutable array, and they do not modify the mutable array, so it much more useful for the selector to directly return the mutable array. --- .../src/store/contract/contract.test.ts | 7 +++--- .../src/store/contract/selectors.ts | 12 +++++----- .../src/store/employee/employee.test.ts | 11 ++++----- .../src/store/employee/selectors.ts | 12 +++++----- .../src/store/roster/operations.ts | 6 ++--- .../src/store/roster/roster.test.ts | 6 ++--- .../src/store/skill/selectors.ts | 12 +++++----- .../src/store/skill/skill.test.ts | 7 +++--- .../src/store/spot/selectors.ts | 12 +++++----- .../src/store/spot/spot.test.ts | 9 ++++--- .../AvailabilityRosterPage.test.tsx | 19 +++++++-------- .../availability/AvailabilityRosterPage.tsx | 24 +++++++++---------- .../EditAvailabilityModal.test.tsx | 3 +-- .../availability/EditAvailabilityModal.tsx | 5 ++-- .../src/ui/pages/contract/ContractsPage.tsx | 2 +- .../src/ui/pages/employee/EmployeesPage.tsx | 6 ++--- .../rotation/EditTimeBucketModal.test.tsx | 5 ++-- .../ui/pages/rotation/EditTimeBucketModal.tsx | 2 +- .../ui/pages/rotation/EmployeeStub.test.tsx | 5 ++-- .../src/ui/pages/rotation/EmployeeStub.tsx | 2 +- .../ui/pages/rotation/RotationPage.test.tsx | 3 +-- .../src/ui/pages/rotation/RotationPage.tsx | 2 +- .../ui/pages/shift/CurrentShiftRosterPage.tsx | 4 ++-- .../src/ui/pages/shift/EditShiftModal.tsx | 6 ++--- .../ui/pages/shift/ExportScheduleModal.tsx | 2 +- .../pages/shift/ProvisionShiftsModal.test.tsx | 3 +-- .../ui/pages/shift/ProvisionShiftsModal.tsx | 2 +- .../src/ui/pages/shift/ShiftRosterPage.tsx | 4 ++-- .../src/ui/pages/skill/SkillsPage.tsx | 2 +- .../src/ui/pages/spot/SpotsPage.tsx | 4 ++-- 30 files changed, 93 insertions(+), 106 deletions(-) diff --git a/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts b/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts index 6f03ba776..88d71842d 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/contract.test.ts @@ -18,7 +18,6 @@ import { alert } from 'store/alert'; import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete } from 'store/rest/RestTestUtils'; import { Contract } from 'domain/Contract'; -import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -270,12 +269,12 @@ describe('Contract selectors', () => { ...storeState, contractList: { ...storeState.contractList, isLoading: true }, }); - expect(contractList).toEqual(List()); + expect(contractList).toEqual([]); }); it('should return a list of all contracts', () => { const contractList = contractSelectors.getContractList(storeState); - expect(contractList.toArray()).toEqual(expect.arrayContaining([ + expect(contractList).toEqual(expect.arrayContaining([ { tenantId: 0, id: 0, @@ -307,6 +306,6 @@ describe('Contract selectors', () => { maximumMinutesPerYear: 100, }, ])); - expect(contractList.size).toEqual(3); + expect(contractList.length).toEqual(3); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts index 1962a9b31..6183c3efa 100644 --- a/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/contract/selectors.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { Contract } from 'domain/Contract'; -import { Map, List } from 'immutable'; +import { Map } from 'immutable'; import DomainObjectView from 'domain/DomainObjectView'; import { AppState } from '../types'; @@ -26,11 +26,11 @@ export const getContractById = (state: AppState, id: number): Contract => { }; let oldContractMapById: Map> | null = null; -let contractListForOldContractMapById: List | null = null; +let contractListForOldContractMapById: Contract[] | null = null; -export const getContractList = (state: AppState): List => { +export const getContractList = (state: AppState): Contract[] => { if (state.contractList.isLoading) { - return List(); + return []; } if (oldContractMapById === state.contractList.contractMapById && contractListForOldContractMapById !== null) { return contractListForOldContractMapById; @@ -39,7 +39,7 @@ export const getContractList = (state: AppState): List => { .sortBy(contract => contract.name).toList(); oldContractMapById = state.contractList.contractMapById; - contractListForOldContractMapById = out; + contractListForOldContractMapById = out.toArray(); - return out; + return contractListForOldContractMapById; }; diff --git a/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts b/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts index 114e0d4fe..c852a14d4 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/employee.test.ts @@ -20,7 +20,6 @@ import { onGet, onPost, onDelete, onUploadFile } from 'store/rest/RestTestUtils' import { Employee } from 'domain/Employee'; import * as skillActions from 'store/skill/actions'; import * as contractActions from 'store/contract/actions'; -import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -351,22 +350,22 @@ describe('Employee selectors', () => { ...storeState, skillList: { ...storeState.skillList, isLoading: true }, }); - expect(employeeList).toEqual(List()); + expect(employeeList).toEqual([]); employeeList = employeeSelectors.getEmployeeList({ ...storeState, contractList: { ...storeState.contractList, isLoading: true }, }); - expect(employeeList).toEqual(List()); + expect(employeeList).toEqual([]); employeeList = employeeSelectors.getEmployeeList({ ...storeState, employeeList: { ...storeState.employeeList, isLoading: true }, }); - expect(employeeList).toEqual(List()); + expect(employeeList).toEqual([]); }); it('should return a list of all employee', () => { const employeeList = employeeSelectors.getEmployeeList(storeState); - expect(employeeList.toArray()).toEqual(expect.arrayContaining([ + expect(employeeList).toEqual(expect.arrayContaining([ { tenantId: 0, id: 1, @@ -413,6 +412,6 @@ describe('Employee selectors', () => { color: '#FFFFFF', }, ])); - expect(employeeList.size).toEqual(2); + expect(employeeList.length).toEqual(2); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts index c056b7737..ce3c2e731 100644 --- a/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/employee/selectors.ts @@ -17,7 +17,7 @@ import { contractSelectors } from 'store/contract'; import { skillSelectors } from 'store/skill'; import { Employee } from 'domain/Employee'; import DomainObjectView from 'domain/DomainObjectView'; -import { Map, List } from 'immutable'; +import { Map } from 'immutable'; import { AppState } from '../types'; export const getEmployeeById = (state: AppState, id: number): Employee => { @@ -33,11 +33,11 @@ export const getEmployeeById = (state: AppState, id: number): Employee => { }; let oldEmployeeMapById: Map> | null = null; -let employeeListForOldEmployeeMapById: List | null = null; +let employeeListForOldEmployeeMapById: Employee[] | null = null; -export const getEmployeeList = (state: AppState): List => { +export const getEmployeeList = (state: AppState): Employee[] => { if (state.employeeList.isLoading || state.skillList.isLoading || state.contractList.isLoading) { - return List(); + return []; } if (oldEmployeeMapById === state.employeeList.employeeMapById && employeeListForOldEmployeeMapById !== null) { return employeeListForOldEmployeeMapById; @@ -47,6 +47,6 @@ export const getEmployeeList = (state: AppState): List => { .sortBy(employee => employee.name).toList(); oldEmployeeMapById = state.employeeList.employeeMapById; - employeeListForOldEmployeeMapById = out; - return out; + employeeListForOldEmployeeMapById = out.toArray(); + return employeeListForOldEmployeeMapById; }; diff --git a/optaweb-employee-rostering-frontend/src/store/roster/operations.ts b/optaweb-employee-rostering-frontend/src/store/roster/operations.ts index 9b6e183c3..3d5b17b91 100644 --- a/optaweb-employee-rostering-frontend/src/store/roster/operations.ts +++ b/optaweb-employee-rostering-frontend/src/store/roster/operations.ts @@ -16,7 +16,7 @@ import { RosterState } from 'domain/RosterState'; import { ShiftRosterView } from 'domain/ShiftRosterView'; -import { PaginationData, ObjectNumberMap, mapObjectNumberMap, mapObjectStringMap, error } from 'types'; +import { PaginationData, ObjectNumberMap, mapObjectNumberMap, mapObjectStringMap } from 'types'; import moment from 'moment'; import { Spot } from 'domain/Spot'; import { alert } from 'store/alert'; @@ -176,7 +176,7 @@ ThunkCommandFactory = () => (dispatch, state) => { const startDate = moment(rosterState.firstDraftDate).startOf('week').toDate(); const endDate = moment(rosterState.firstDraftDate).endOf('week').toDate(); const spotList = spotSelectors.getSpotList(state()); - const shownSpots = (spotList.size > 0) ? [spotList.get(0) ?? error()] : []; + const shownSpots = (spotList.length > 0) ? [spotList[0]] : []; if (shownSpots.length > 0) { dispatch(getShiftRosterFor({ @@ -199,7 +199,7 @@ ThunkCommandFactory = () => (dispatch, state const startDate = moment(rosterState.firstDraftDate).startOf('week').toDate(); const endDate = moment(rosterState.firstDraftDate).endOf('week').toDate(); const employeeList = employeeSelectors.getEmployeeList(state()); - const shownEmployees = (employeeList.size > 0) ? [employeeList.get(0) ?? error()] : []; + const shownEmployees = (employeeList.length > 0) ? [employeeList[0]] : []; if (shownEmployees.length > 0) { dispatch(getAvailabilityRosterFor({ diff --git a/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts b/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts index 3c17255f6..a85860b64 100644 --- a/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/roster/roster.test.ts @@ -27,7 +27,7 @@ import { Employee } from 'domain/Employee'; import { RosterState } from 'domain/RosterState'; import { serializeLocalDate } from 'store/rest/DataSerialization'; import { flushPromises } from 'setupTests'; -import { doNothing, error } from 'types'; +import { doNothing } from 'types'; import { Map, List } from 'immutable'; import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { availabilityRosterReducer } from './reducers'; @@ -669,7 +669,7 @@ describe('Roster operations', () => { it('should dispatch actions and call client on getInitialShiftRoster', async () => { const { store, client } = mockStore(state); const tenantId = store.getState().tenantData.currentTenantId; - const spotList: Spot[] = [spotSelectors.getSpotList(store.getState()).get(0) ?? error()]; + const spotList: Spot[] = [spotSelectors.getSpotList(store.getState())[0]]; const fromDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) .startOf('week').toDate(); const toDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) @@ -822,7 +822,7 @@ describe('Roster operations', () => { it('should dispatch actions and call client on getInitialAvailabilityRoster', async () => { const { store, client } = mockStore(state); const tenantId = store.getState().tenantData.currentTenantId; - const employeeList: Employee[] = [employeeSelectors.getEmployeeList(store.getState()).get(0) ?? error()]; + const employeeList: Employee[] = [employeeSelectors.getEmployeeList(store.getState())[0]]; const fromDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) .startOf('week').toDate(); const toDate = moment((store.getState().rosterState.rosterState as RosterState).firstDraftDate) diff --git a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts index b8f3c81ac..a4bdb183c 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/selectors.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { Skill } from 'domain/Skill'; -import { List, Map } from 'immutable'; +import { Map } from 'immutable'; import DomainObjectView from 'domain/DomainObjectView'; import { AppState } from '../types'; @@ -27,11 +27,11 @@ export const getSkillById = (state: AppState, id: number): Skill => { let oldSkillMapById: Map>| null = null; -let skillListForOldSkillMapById: List | null = null; +let skillListForOldSkillMapById: Skill[] | null = null; -export const getSkillList = (state: AppState): List => { +export const getSkillList = (state: AppState): Skill[] => { if (state.skillList.isLoading) { - return List(); + return []; } if (oldSkillMapById === state.skillList.skillMapById && skillListForOldSkillMapById !== null) { return skillListForOldSkillMapById; @@ -41,6 +41,6 @@ export const getSkillList = (state: AppState): List => { .sortBy(skill => skill.name).toList(); oldSkillMapById = state.skillList.skillMapById; - skillListForOldSkillMapById = out; - return out; + skillListForOldSkillMapById = out.toArray(); + return skillListForOldSkillMapById; }; diff --git a/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts b/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts index c310d6f33..5b9628fcc 100644 --- a/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/skill/skill.test.ts @@ -18,7 +18,6 @@ import { alert } from 'store/alert'; import { createIdMapFromList } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete } from 'store/rest/RestTestUtils'; import { Skill } from 'domain/Skill'; -import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -202,12 +201,12 @@ describe('Skill selectors', () => { ...storeState, skillList: { ...storeState.skillList, isLoading: true }, }); - expect(skillList).toEqual(List()); + expect(skillList).toEqual([]); }); it('should return a list of all skills', () => { const skillList = skillSelectors.getSkillList(storeState); - expect(skillList.toArray()).toEqual(expect.arrayContaining([ + expect(skillList).toEqual(expect.arrayContaining([ { tenantId: 0, id: 1234, @@ -221,6 +220,6 @@ describe('Skill selectors', () => { name: 'Skill 3', }, ])); - expect(skillList.size).toEqual(2); + expect(skillList.length).toEqual(2); }); }); diff --git a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts index 5f5e1dacf..d43bab14c 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/selectors.ts @@ -16,7 +16,7 @@ import { skillSelectors } from 'store/skill'; import { Spot } from 'domain/Spot'; import DomainObjectView from 'domain/DomainObjectView'; -import { Map, List } from 'immutable'; +import { Map } from 'immutable'; import { AppState } from '../types'; export const getSpotById = (state: AppState, id: number): Spot => { @@ -31,11 +31,11 @@ export const getSpotById = (state: AppState, id: number): Spot => { }; let oldSpotMapById: Map> | null = null; -let spotListForOldSpotMapById: List | null = null; +let spotListForOldSpotMapById: Spot[] | null = null; -export const getSpotList = (state: AppState): List => { +export const getSpotList = (state: AppState): Spot[] => { if (state.spotList.isLoading || state.skillList.isLoading) { - return List(); + return []; } if (oldSpotMapById === state.spotList.spotMapById && spotListForOldSpotMapById !== null) { return spotListForOldSpotMapById; @@ -45,6 +45,6 @@ export const getSpotList = (state: AppState): List => { .sortBy(spot => spot.name).toList(); oldSpotMapById = state.spotList.spotMapById; - spotListForOldSpotMapById = out; - return out; + spotListForOldSpotMapById = out.toArray(); + return spotListForOldSpotMapById; }; diff --git a/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts b/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts index b4369c6c7..b95f5c3f7 100644 --- a/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts +++ b/optaweb-employee-rostering-frontend/src/store/spot/spot.test.ts @@ -18,7 +18,6 @@ import { alert } from 'store/alert'; import { createIdMapFromList, mapDomainObjectToView } from 'util/ImmutableCollectionOperations'; import { onGet, onPost, onDelete } from 'store/rest/RestTestUtils'; import { Spot } from 'domain/Spot'; -import { List } from 'immutable'; import { mockStore } from '../mockStore'; import { AppState } from '../types'; import * as actions from './actions'; @@ -263,17 +262,17 @@ describe('Spot selectors', () => { ...storeState, skillList: { ...storeState.skillList, isLoading: true }, }); - expect(spotList).toEqual(List()); + expect(spotList).toEqual([]); spotList = spotSelectors.getSpotList({ ...storeState, spotList: { ...storeState.spotList, isLoading: true }, }); - expect(spotList).toEqual(List()); + expect(spotList).toEqual([]); }); it('should return a list of all spots', () => { const spotList = spotSelectors.getSpotList(storeState); - expect(spotList.toArray()).toEqual(expect.arrayContaining([ + expect(spotList).toEqual(expect.arrayContaining([ { tenantId: 0, id: 1234, @@ -296,6 +295,6 @@ describe('Spot selectors', () => { requiredSkillSet: [], }, ])); - expect(spotList.size).toEqual(2); + expect(spotList.length).toEqual(2); }); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx index 78879a958..bcdf780b5 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.test.tsx @@ -27,7 +27,6 @@ import { useTranslation, Trans } from 'react-i18next'; import Actions from 'ui/components/Actions'; import { getRouterProps } from 'util/BookmarkableTestUtils'; import Schedule from 'ui/components/calendar/Schedule'; -import { List } from 'immutable'; import { AvailabilityRosterPage, Props, ShiftOrAvailability, isShift, isAvailability, isAllDayAvailability, isDay, AvailabilityRosterUrlProps, @@ -49,8 +48,8 @@ describe('Availability Roster Page', () => { const availabilityRosterPage = shallow(); @@ -60,8 +59,8 @@ describe('Availability Roster Page', () => { it('should render correctly when there are no employees', () => { const availabilityRosterPage = shallow(); @@ -98,7 +97,7 @@ describe('Availability Roster Page', () => { expect(baseProps.getAvailabilityRosterFor).toBeCalledWith({ fromDate: newDateStart, toDate: newDateEnd, - employeeList: baseProps.shownEmployeeList.toArray(), + employeeList: baseProps.shownEmployeeList, }); }); @@ -119,8 +118,8 @@ describe('Availability Roster Page', () => { it('should go to the Employees page if the user click on the link', () => { const availabilityRosterPage = shallow(); @@ -640,8 +639,8 @@ const baseProps: Props = { isLoading: false, totalNumOfSpots: 1, rosterState, - allEmployeeList: List([employee, newEmployee]), - shownEmployeeList: List([employee]), + allEmployeeList: [employee, newEmployee], + shownEmployeeList: [employee], employeeIdToShiftListMap: new Map([ [4, [shift]], ]), diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx index 87b2bd6ae..02d915dd9 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/AvailabilityRosterPage.tsx @@ -40,8 +40,6 @@ import { HardMediumSoftScore } from 'domain/HardMediumSoftScore'; import { ScoreDisplay } from 'ui/components/ScoreDisplay'; import { UrlProps, setPropsInUrl, getPropsFromUrl } from 'util/BookmarkableUtils'; import { IndictmentSummary } from 'domain/indictment/IndictmentSummary'; -import { List } from 'immutable'; -import { error } from 'types'; import AvailabilityEvent, { AvailabilityPopoverHeader, AvailabilityPopoverBody } from './AvailabilityEvent'; import EditAvailabilityModal from './EditAvailabilityModal'; import ShiftEvent, { ShiftPopupHeader, ShiftPopupBody } from '../shift/ShiftEvent'; @@ -51,8 +49,8 @@ interface StateProps { tenantId: number; isSolving: boolean; isLoading: boolean; - allEmployeeList: List; - shownEmployeeList: List; + allEmployeeList: Employee[]; + shownEmployeeList: Employee[]; employeeIdToShiftListMap: Map; employeeIdToAvailabilityListMap: Map; totalNumOfSpots: number; @@ -74,8 +72,8 @@ const mapStateToProps = (state: AppState): StateProps => ({ isSolving: state.solverState.solverStatus !== 'NOT_SOLVING', isLoading: rosterSelectors.isAvailabilityRosterLoading(state), allEmployeeList: employeeSelectors.getEmployeeList(state), - shownEmployeeList: List(lastShownEmployeeList = rosterSelectors.isAvailabilityRosterLoading(state) - ? lastShownEmployeeList : rosterSelectors.getEmployeeListInAvailabilityRoster(state)), + shownEmployeeList: lastShownEmployeeList = rosterSelectors.isAvailabilityRosterLoading(state) + ? lastShownEmployeeList : rosterSelectors.getEmployeeListInAvailabilityRoster(state), employeeIdToShiftListMap: lastEmployeeIdToShiftListMap = rosterSelectors .getEmployeeListInAvailabilityRoster(state) .reduce((prev, curr) => prev.set(curr.id as number, @@ -87,7 +85,7 @@ const mapStateToProps = (state: AppState): StateProps => ({ rosterSelectors.getAvailabilityListForEmployee(state, curr)), rosterSelectors.isAvailabilityRosterLoading(state) ? lastEmployeeIdToAvailabilityListMap : new Map()), - totalNumOfSpots: spotSelectors.getSpotList(state).size, + totalNumOfSpots: spotSelectors.getSpotList(state).length, rosterState: state.rosterState.rosterState, score: state.availabilityRoster.availabilityRosterView ? state.availabilityRoster.availabilityRosterView.score : null, indictmentSummary: state.availabilityRoster.availabilityRosterView @@ -177,7 +175,7 @@ export class AvailabilityRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const employee = this.props.allEmployeeList - .find(e => e.name === urlProps.employee) || this.props.allEmployeeList.get(0); + .find(e => e.name === urlProps.employee) || this.props.allEmployeeList[0]; if (employee) { this.props.getAvailabilityRosterFor({ fromDate: startDate, @@ -291,11 +289,11 @@ export class AvailabilityRosterPage extends React.Component { employee: null, week: null, }); - const changedTenant = this.props.shownEmployeeList.size === 0 + const changedTenant = this.props.shownEmployeeList.length === 0 || (urlProps.employee !== null - && this.props.tenantId !== (this.props.shownEmployeeList.get(0) ?? error()).tenantId); + && this.props.tenantId !== (this.props.shownEmployeeList[0]).tenantId); - if (this.props.shownEmployeeList.size === 0 || changedTenant || this.props.rosterState === null) { + if (this.props.shownEmployeeList.length === 0 || changedTenant || this.props.rosterState === null) { return ( @@ -320,7 +318,7 @@ export class AvailabilityRosterPage extends React.Component { const startDate = moment(urlProps.week || moment(this.props.rosterState.firstDraftDate)).startOf('week').toDate(); const endDate = moment(startDate).endOf('week').toDate(); const shownEmployee = this.props.allEmployeeList.find(e => e.name === urlProps.employee) - || this.props.shownEmployeeList.get(0) as Employee; + || this.props.shownEmployeeList[0]; const score: HardMediumSoftScore = this.props.score || { hardScore: 0, mediumScore: 0, softScore: 0 }; const indictmentSummary: IndictmentSummary = this.props.indictmentSummary || { constraintToCountMap: {}, constraintToScoreImpactMap: {} }; @@ -382,7 +380,7 @@ export class AvailabilityRosterPage extends React.Component { aria-label="Select Employee" emptyText={t('selectEmployee')} optionToStringMap={employee => employee.name} - options={this.props.allEmployeeList.toArray()} + options={this.props.allEmployeeList} value={shownEmployee} onChange={(e) => { this.onUpdateAvailabilityRoster({ diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx index 97dba1abe..994dd1ebc 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.test.tsx @@ -20,7 +20,6 @@ import { Employee } from 'domain/Employee'; import moment from 'moment'; import { EmployeeAvailability } from 'domain/EmployeeAvailability'; import { useTranslation } from 'react-i18next'; -import { List } from 'immutable'; import { EditAvailabilityModal } from './EditAvailabilityModal'; describe('Edit Availability Modal', () => { @@ -324,5 +323,5 @@ const baseProps = { tReady: true, tenantId: 1, isOpen: true, - employeeList: List([employee]), + employeeList: [employee], }; diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx index 5c253f67d..f49907850 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/availability/EditAvailabilityModal.tsx @@ -27,13 +27,12 @@ import { employeeSelectors } from 'store/employee'; import 'react-datepicker/dist/react-datepicker.css'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; import { withTranslation, WithTranslation } from 'react-i18next'; -import { List } from 'immutable'; interface Props { tenantId: number; availability?: EmployeeAvailability; isOpen: boolean; - employeeList: List; + employeeList: Employee[]; onSave: (availability: EmployeeAvailability) => void; onDelete: (availability: EmployeeAvailability) => void; onClose: () => void; @@ -157,7 +156,7 @@ export class EditAvailabilityModal extends React.Component employee.name} onChange={employee => this.setState(prevState => ({ editedValue: { ...prevState.editedValue, employee }, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx index 9b164511c..413d1a602 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/contract/ContractsPage.tsx @@ -37,7 +37,7 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ title: ownProps.t('contracts'), columnTitles: [ownProps.t('name'), ownProps.t('maxMinutesPerDay'), ownProps.t('maxMinutesPerWeek'), ownProps.t('maxMinutesPerMonth'), ownProps.t('maxMinutesPerYear')], - tableData: contractSelectors.getContractList(state).toArray(), + tableData: contractSelectors.getContractList(state), tenantId: state.tenantData.currentTenantId, }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx index 98b69e634..a337c09a3 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/employee/EmployeesPage.tsx @@ -66,9 +66,9 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ ownProps.t('shortId'), ownProps.t('color'), ], - tableData: employeeSelectors.getEmployeeList(state).toArray(), - skillList: skillSelectors.getSkillList(state).toArray(), - contractList: contractSelectors.getContractList(state).toArray(), + tableData: employeeSelectors.getEmployeeList(state), + skillList: skillSelectors.getSkillList(state), + contractList: contractSelectors.getContractList(state), tenantId: state.tenantData.currentTenantId, }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx index ea78e3510..528adb80d 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.test.tsx @@ -24,7 +24,6 @@ import moment from 'moment'; import { Modal, TextInput, FlexItem, Text } from '@patternfly/react-core'; import { Skill } from 'domain/Skill'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { List } from 'immutable'; import { EditTimeBucketModal, TimeBucketEditor, EditTimeBucketModalProps, TimeBucketEditorProps, @@ -198,7 +197,7 @@ const timeBucket: TimeBucket = { seatList: [{ dayInRotation: 0, employee }], }; -const skillList: List = List([ +const skillList: Skill[] = [ { tenantId: 0, id: 1000, @@ -211,7 +210,7 @@ const skillList: List = List([ version: 0, name: 'Skill 2', }, -]); +]; const editTimeBucketModalProps: EditTimeBucketModalProps = { isOpen: true, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx index 328a3af31..737a83b44 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EditTimeBucketModal.tsx @@ -85,7 +85,7 @@ export const TimeBucketEditor: React.FC = (props) => {
skill.name} emptyText="Select additional skills..." autoSize={false} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx index 827d71e29..0e714b1cf 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.test.tsx @@ -20,7 +20,6 @@ import { employeeSelectors } from 'store/employee'; import { shallow } from 'enzyme'; import { Button, Modal } from '@patternfly/react-core'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; -import { List } from 'immutable'; import { EmployeeNickNameProps, EmployeeNickName, EmployeeStubProps, EmployeeStub, EditEmployeeStubListModalProps, EditEmployeeStubListModal, @@ -78,10 +77,10 @@ const newEmployee: Employee = { }; -const employeeList: List = List([ +const employeeList: Employee[] = [ employee, newEmployee, -]); +]; describe('EmployeeNickName Component', () => { const baseProps: EmployeeNickNameProps = { diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx index 234df9829..672b0bb6a 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/EmployeeStub.tsx @@ -164,7 +164,7 @@ export const EditEmployeeStubListModal: React.FC > ((typeof o === 'string') ? t('unassigned') : o.name)} autoSize={false} diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx index fe5fd545e..a5b69ba68 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.test.tsx @@ -28,7 +28,6 @@ import moment from 'moment'; import { Trans } from 'react-i18next'; import TypeaheadSelectInput from 'ui/components/TypeaheadSelectInput'; import { Button } from '@patternfly/react-core'; -import { List } from 'immutable'; import { SeatJigsaw } from './SeatJigsaw'; import { EmployeeStubList, Stub } from './EmployeeStub'; import { EditTimeBucketModal } from './EditTimeBucketModal'; @@ -102,7 +101,7 @@ describe('Rotation Page', () => { mockSelector(tenantSelectors.getTenantId, 0); mockSelector(rosterSelectors.getRosterState, rosterState); mockSelector(timeBucketSelectors.isLoading, false); - mockSelector(spotSelectors.getSpotList, List([spot, newSpot])); + mockSelector(spotSelectors.getSpotList, [spot, newSpot]); mockSelector(timeBucketSelectors.getTimeBucketList, [timeBucket]); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx index e50492cec..f910bcca8 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/rotation/RotationPage.tsx @@ -44,7 +44,7 @@ export const RotationPage: React.FC<{}> = () => { const tenantId = useSelector(tenantSelectors.getTenantId); const rosterState = useSelector(rosterSelectors.getRosterState); const isLoading = useSelector(timeBucketSelectors.isLoading); - const spotList = useSelector(spotSelectors.getSpotList).toArray(); + const spotList = useSelector(spotSelectors.getSpotList); const timeBucketList = useSelector(timeBucketSelectors.getTimeBucketList); const dispatch = useDispatch(); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx index b4bd7dda2..3bd631795 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/CurrentShiftRosterPage.tsx @@ -65,7 +65,7 @@ const mapStateToProps = (state: AppState): StateProps => ({ tenantId: state.tenantData.currentTenantId, isSolving: state.solverState.solverStatus !== 'NOT_SOLVING', isLoading: rosterSelectors.isShiftRosterLoading(state), - allSpotList: spotSelectors.getSpotList(state).toArray(), + allSpotList: spotSelectors.getSpotList(state), // The use of "x = isLoading? x : getUpdatedData()" is a way to use old value if data is still loading shownSpotList: lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList : rosterSelectors.getSpotListInShiftRoster(state), @@ -74,7 +74,7 @@ const mapStateToProps = (state: AppState): StateProps => ({ rosterSelectors.getShiftListForSpot(state, curr)), // reducing an empty array returns the starting value rosterSelectors.isShiftRosterLoading(state) ? lastSpotIdToShiftListMap : new Map()), - totalNumOfSpots: spotSelectors.getSpotList(state).size, + totalNumOfSpots: spotSelectors.getSpotList(state).length, rosterState: state.rosterState.rosterState, score: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.score : null, indictmentSummary: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.indictmentSummary : null, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx index 37f3b9cbc..0690a682b 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/EditShiftModal.tsx @@ -56,9 +56,9 @@ const mapStateToProps = (state: AppState, ownProps: { }): Props => ({ ...ownProps, tenantId: state.tenantData.currentTenantId, - skillList: skillSelectors.getSkillList(state).toArray(), - employeeList: employeeSelectors.getEmployeeList(state).toArray(), - spotList: spotSelectors.getSpotList(state).toArray(), + skillList: skillSelectors.getSkillList(state), + employeeList: employeeSelectors.getEmployeeList(state), + spotList: spotSelectors.getSpotList(state), }); interface State { diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx index dc4654e91..7ff4e17bf 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ExportScheduleModal.tsx @@ -39,7 +39,7 @@ interface OwnProps { const mapStateToProps = (state: AppState, ownProps: OwnProps): StateProps & OwnProps => ({ ...ownProps, tenantId: state.tenantData.currentTenantId, - spotList: spotSelectors.getSpotList(state).toArray(), + spotList: spotSelectors.getSpotList(state), }); export const ExportScheduleModal: React.FC = (props) => { diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx index 748565a89..7201ed33c 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.test.tsx @@ -27,7 +27,6 @@ import { rosterSelectors } from 'store/roster'; import { RosterState } from 'domain/RosterState'; import { Modal, TextInput, Checkbox, AccordionToggle, AccordionContent } from '@patternfly/react-core'; import MultiTypeaheadSelectInput from 'ui/components/MultiTypeaheadSelectInput'; -import { List } from 'immutable'; import { ProvisionShiftsModal, ProvisionShiftsModalProps, SpotTimeBucketSelect, @@ -70,7 +69,7 @@ describe('Provision Shifts Modal', () => { mockSelectorReturnValue.clear(); mockUseEffect.mockImplementationOnce(f => f()); mockSelector(timeBucketSelectors.getTimeBucketList, [timeBucket]); - mockSelector(spotSelectors.getSpotList, List([spot])); + mockSelector(spotSelectors.getSpotList, [spot]); mockSelector(rosterSelectors.getRosterState, rosterState); }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx index 9ca42aaa8..d0890d55f 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ProvisionShiftsModal.tsx @@ -135,7 +135,7 @@ export const ProvisionShiftsModal: React.FC = (props) const [provisionedTimeBuckets, setProvisionedTimeBuckets] = React.useState([]); const timeBucketList = useSelector(timeBucketSelectors.getTimeBucketList); - const spotList = useSelector(spotSelectors.getSpotList).toArray(); + const spotList = useSelector(spotSelectors.getSpotList); const rosterState = useSelector(rosterSelectors.getRosterState); const dispatch = useDispatch(); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx index 6bb3ba5ce..7c1213ec3 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/shift/ShiftRosterPage.tsx @@ -66,7 +66,7 @@ const mapStateToProps = (state: AppState): StateProps => ({ tenantId: state.tenantData.currentTenantId, isSolving: state.solverState.solverStatus !== 'NOT_SOLVING', isLoading: rosterSelectors.isShiftRosterLoading(state), - allSpotList: spotSelectors.getSpotList(state).toArray(), + allSpotList: spotSelectors.getSpotList(state), // The use of "x = isLoading? x : getUpdatedData()" is a way to use old value if data is still loading shownSpotList: lastShownSpotList = rosterSelectors.isShiftRosterLoading(state) ? lastShownSpotList : rosterSelectors.getSpotListInShiftRoster(state), @@ -75,7 +75,7 @@ const mapStateToProps = (state: AppState): StateProps => ({ rosterSelectors.getShiftListForSpot(state, curr)), // reducing an empty array returns the starting value rosterSelectors.isShiftRosterLoading(state) ? lastSpotIdToShiftListMap : new Map()), - totalNumOfSpots: spotSelectors.getSpotList(state).size, + totalNumOfSpots: spotSelectors.getSpotList(state).length, rosterState: state.rosterState.rosterState, score: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.score : null, indictmentSummary: state.shiftRoster.shiftRosterView ? state.shiftRoster.shiftRosterView.indictmentSummary : null, diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx index 11a580a03..94371985c 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/skill/SkillsPage.tsx @@ -35,7 +35,7 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ ...ownProps, title: ownProps.t('skills'), columnTitles: [ownProps.t('name')], - tableData: skillSelectors.getSkillList(state).toArray(), + tableData: skillSelectors.getSkillList(state), tenantId: state.tenantData.currentTenantId, }); diff --git a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx index d1cd52e8f..d580e6c63 100644 --- a/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/pages/spot/SpotsPage.tsx @@ -40,8 +40,8 @@ const mapStateToProps = (state: AppState, ownProps: Props): StateProps => ({ ...ownProps, title: ownProps.t('spots'), columnTitles: [ownProps.t('name'), ownProps.t('requiredSkillSet')], - tableData: spotSelectors.getSpotList(state).toArray(), - skillList: skillSelectors.getSkillList(state).toArray(), + tableData: spotSelectors.getSpotList(state), + skillList: skillSelectors.getSkillList(state), tenantId: state.tenantData.currentTenantId, }); From 57d90b8bdb7102282c68c2e2727c4afd5267ebcb Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Fri, 11 Dec 2020 13:52:25 -0500 Subject: [PATCH 6/6] Remove redundant type casts from test --- .../src/ui/Alerts.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx b/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx index 4e951b638..4cb0631f0 100644 --- a/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx +++ b/optaweb-employee-rostering-frontend/src/ui/Alerts.test.tsx @@ -85,7 +85,7 @@ const someAlerts: Props = { id: 0, createdAt: date, i18nKey: 'infoMessage', - variant: 'info' as 'info', + variant: 'info', params: {}, components: [], componentProps: [], @@ -94,7 +94,7 @@ const someAlerts: Props = { id: 1, createdAt: moment(date).add(4, 'seconds').toDate(), i18nKey: 'successMessage', - variant: 'success' as 'success', + variant: 'success', params: {}, components: [], componentProps: [], @@ -103,7 +103,7 @@ const someAlerts: Props = { id: 2, createdAt: moment(date).add(11, 'seconds').toDate(), i18nKey: 'dangerMessage', - variant: 'danger' as 'danger', + variant: 'danger', params: {}, components: [], componentProps: [], @@ -118,7 +118,7 @@ const someAlertsWithComponents: Props = { id: 0, createdAt: date, i18nKey: 'exception', - variant: 'danger' as 'danger', + variant: 'danger', params: {}, components: [AlertComponent.SERVER_SIDE_EXCEPTION_DIALOG], componentProps: [{