Skip to content

Commit ba16ae6

Browse files
Merge pull request #1675 from ral-facilities/create-global-toggle-criticality-#1674
create global toggle criticality #1674
2 parents 183e04e + c83d289 commit ba16ae6

15 files changed

+214
-65
lines changed

cypress/support/commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ Cypress.Commands.add('setCurrentUserToAdmin', () => {
144144

145145
document.body.appendChild(div);
146146
});
147-
cy.findByRole('menuitem', { name: 'Switch to admin mode' }).click();
147+
cy.findByRole('menuitem', { name: 'Switch admin mode on' }).click();
148148
});
149149
});
150150

src/common/authSettingsMenuItem.component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const AuthSettingsMenuItem = () => {
2424
<AdminPanelSettingsIcon />
2525
</ListItemIcon>
2626
<ListItemText
27-
primary={isAdminMode ? 'Switch to normal mode' : 'Switch to admin mode'}
27+
primary={`Switch admin mode ${isAdminMode ? 'off' : 'on'}`}
2828
/>
2929
</MenuItem>
3030
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import GppMaybeIcon from '@mui/icons-material/GppMaybe';
2+
import ListItemIcon from '@mui/material/ListItemIcon';
3+
import ListItemText from '@mui/material/ListItemText';
4+
import MenuItem from '@mui/material/MenuItem';
5+
import React from 'react';
6+
import { useAppDispatch, useAppSelector } from '../state/hook';
7+
import {
8+
selectCriticality,
9+
setIsCriticalMode,
10+
} from '../state/slices/criticalitySlice';
11+
12+
const CriticalitySettingsMenuItem = () => {
13+
const { isCriticalMode } = useAppSelector(selectCriticality);
14+
const dispatch = useAppDispatch();
15+
16+
const handleCriticalMode = React.useCallback(() => {
17+
dispatch(setIsCriticalMode(!isCriticalMode));
18+
}, [isCriticalMode, dispatch]);
19+
20+
return (
21+
<MenuItem id="item-critical-mode" onClick={handleCriticalMode}>
22+
<ListItemIcon>
23+
<GppMaybeIcon />
24+
</ListItemIcon>
25+
<ListItemText
26+
primary={`Switch critical mode ${isCriticalMode ? 'off' : 'on'}`}
27+
/>
28+
</MenuItem>
29+
);
30+
};
31+
32+
export default CriticalitySettingsMenuItem;

src/common/settingsMenuItems.component.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ describe('Settings Menu Items component', () => {
4444
};
4545
const { store } = createView(state);
4646

47-
const adminToggleButton = screen.getByText('Switch to admin mode');
47+
const adminToggleButton = screen.getByText('Switch admin mode on');
4848

4949
await user.click(adminToggleButton);
5050

5151
expect(
52-
await screen.findByText('Switch to normal mode')
52+
await screen.findByText('Switch admin mode off')
5353
).toBeInTheDocument();
5454

5555
expect(store.getState().authorisation.isAdminMode).toStrictEqual(true);
@@ -58,7 +58,7 @@ describe('Settings Menu Items component', () => {
5858
it('does not display admin toggle if user is not a admin user', () => {
5959
createView();
6060
expect(
61-
screen.queryByText('Switch to admin mode')
61+
screen.queryByText('Switch admin mode on')
6262
).not.toBeInTheDocument();
6363
});
6464
});

src/common/settingsMenuItems.component.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
33
import AuthSettingsMenuItem from './authSettingsMenuItem.component';
4+
import CriticalitySettingsMenuItem from './criticalitySettingsMenuItem.component';
45

56
const SettingsMenuItems = () => {
67
const [menuOpen, setMenuOpen] = React.useState(
@@ -41,6 +42,7 @@ const SettingsMenuItems = () => {
4142
return ReactDOM.createPortal(
4243
<>
4344
<AuthSettingsMenuItem />
45+
<CriticalitySettingsMenuItem />
4446
</>,
4547

4648
// we know this is not null from the mutation observer

src/common/storage.test.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { clearIsAdminMode, loadIsAdminMode, saveIsAdminMode } from './storage';
1+
import { createBooleanLocalStorage } from './storage';
22

3-
describe('adminModeStorage', () => {
3+
describe('createBooleanLocalStorage', () => {
4+
const testBooleanStorage = createBooleanLocalStorage('test');
45
const localStorageGetItemMock = vi.spyOn(
56
window.localStorage.__proto__,
67
'getItem'
@@ -16,18 +17,18 @@ describe('adminModeStorage', () => {
1617
'removeItem'
1718
);
1819

19-
const KEY = 'inventory-management-system:isAdminMode';
20+
const KEY = 'inventory-management-system:test';
2021

2122
afterEach(() => {
2223
vi.clearAllMocks();
2324
localStorage.clear();
2425
});
2526

26-
describe('loadIsAdminMode', () => {
27+
describe('load', () => {
2728
it('should return true when stored value is "true"', () => {
2829
localStorageGetItemMock.mockImplementationOnce(() => 'true');
2930

30-
const result = loadIsAdminMode();
31+
const result = testBooleanStorage.load();
3132

3233
expect(result).toBe(true);
3334
expect(localStorageGetItemMock).toHaveBeenCalledWith(KEY);
@@ -36,45 +37,45 @@ describe('adminModeStorage', () => {
3637
it('should return false when stored value is "false"', () => {
3738
localStorageGetItemMock.mockImplementationOnce(() => 'false');
3839

39-
const result = loadIsAdminMode();
40+
const result = testBooleanStorage.load();
4041

4142
expect(result).toBe(false);
4243
});
4344

4445
it('should return undefined when value is missing', () => {
4546
localStorageGetItemMock.mockImplementationOnce(() => null);
4647

47-
const result = loadIsAdminMode();
48+
const result = testBooleanStorage.load();
4849

4950
expect(result).toBeUndefined();
5051
});
5152

5253
it('should return undefined for an invalid value', () => {
5354
localStorageGetItemMock.mockImplementationOnce(() => 'garbage');
5455

55-
const result = loadIsAdminMode();
56+
const result = testBooleanStorage.load();
5657

5758
expect(result).toBeUndefined();
5859
});
5960
});
6061

61-
describe('saveIsAdminMode', () => {
62+
describe('save', () => {
6263
it('should store "true"', () => {
63-
saveIsAdminMode(true);
64+
testBooleanStorage.save(true);
6465

6566
expect(localStorageSetItemMock).toHaveBeenCalledWith(KEY, 'true');
6667
});
6768

6869
it('should store "false"', () => {
69-
saveIsAdminMode(false);
70+
testBooleanStorage.save(false);
7071

7172
expect(localStorageSetItemMock).toHaveBeenCalledWith(KEY, 'false');
7273
});
7374
});
7475

75-
describe('clearIsAdminMode', () => {
76+
describe('clear', () => {
7677
it('should remove the key from localStorage', () => {
77-
clearIsAdminMode();
78+
testBooleanStorage.clear();
7879

7980
expect(localStorageRemoveItemMock).toHaveBeenCalledWith(KEY);
8081
});

src/common/storage.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,26 @@ import { PluginId } from '../app.types';
22

33
const key = (suffix: string) => `${PluginId}:${suffix}`;
44

5-
export function loadIsAdminMode(): boolean | undefined {
6-
const v = localStorage.getItem(key('isAdminMode'));
7-
return v === 'true' ? true : v === 'false' ? false : undefined;
5+
export interface StorageDeps {
6+
load: () => boolean | undefined;
7+
save: (v: boolean) => void;
8+
clear: () => void;
89
}
910

10-
export function saveIsAdminMode(value: boolean) {
11-
localStorage.setItem(key('isAdminMode'), value.toString());
11+
export function createBooleanLocalStorage(keyName: string): StorageDeps {
12+
return {
13+
load(): boolean | undefined {
14+
const v = localStorage.getItem(key(keyName));
15+
return v === 'true' ? true : v === 'false' ? false : undefined;
16+
},
17+
save(value: boolean) {
18+
localStorage.setItem(key(keyName), value.toString());
19+
},
20+
clear() {
21+
localStorage.removeItem(key(keyName));
22+
},
23+
};
1224
}
1325

14-
export function clearIsAdminMode() {
15-
localStorage.removeItem(key('isAdminMode'));
16-
}
26+
export const authorisationStorage = createBooleanLocalStorage('isAdminMode');
27+
export const criticalityStorage = createBooleanLocalStorage('isCriticalMode');

src/state/middleware/authorisationMiddleware.test.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import { TokenUpdatedType } from '../actions/actions.types';
22
import { setAuthorisation, setIsAdminMode } from '../slices/authorisationSlice';
3-
import { configureAppStore, StorageDeps } from '../store';
3+
import { configureAppStore, StorageRegistry } from '../store';
44

55
describe('authListenerMiddleware', () => {
66
let store: ReturnType<typeof configureAppStore>;
77
let mockGetUserRole: () => string;
8-
let mockStorage: StorageDeps;
8+
let storageRegistryDict: StorageRegistry;
99

1010
beforeEach(() => {
1111
mockGetUserRole = vi.fn().mockReturnValue('admin');
12-
13-
mockStorage = {
14-
saveIsAdminMode: vi.fn(),
15-
clearIsAdminMode: vi.fn(),
16-
loadIsAdminMode: vi.fn(),
12+
const storageDeps = {
13+
save: vi.fn(),
14+
clear: vi.fn(),
15+
load: vi.fn(),
16+
};
17+
storageRegistryDict = {
18+
authorisation: storageDeps,
19+
criticality: storageDeps,
1720
};
1821

19-
store = configureAppStore(undefined, mockGetUserRole, mockStorage);
22+
store = configureAppStore(undefined, mockGetUserRole, storageRegistryDict);
2023
});
2124

2225
it('sets authorisation when token is updated', async () => {
@@ -36,8 +39,8 @@ describe('authListenerMiddleware', () => {
3639

3740
await Promise.resolve();
3841

39-
expect(mockStorage.saveIsAdminMode).toHaveBeenCalledWith(true);
40-
expect(mockStorage.clearIsAdminMode).not.toHaveBeenCalled();
42+
expect(storageRegistryDict.authorisation.save).toHaveBeenCalledWith(true);
43+
expect(storageRegistryDict.authorisation.clear).not.toHaveBeenCalled();
4144
});
4245

4346
it('should clear admin mode when user is not admin', async () => {
@@ -47,7 +50,7 @@ describe('authListenerMiddleware', () => {
4750

4851
await Promise.resolve();
4952

50-
expect(mockStorage.clearIsAdminMode).toHaveBeenCalled();
51-
expect(mockStorage.saveIsAdminMode).not.toHaveBeenCalled();
53+
expect(storageRegistryDict.authorisation.clear).toHaveBeenCalled();
54+
expect(storageRegistryDict.authorisation.save).not.toHaveBeenCalled();
5255
});
5356
});

src/state/middleware/authorisationMiddleware.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { createListenerMiddleware } from '@reduxjs/toolkit';
22
import { TokenUpdatedType } from '../actions/actions.types';
33
import { setAuthorisation, setIsAdminMode } from '../slices/authorisationSlice';
4-
import { RootState, StorageDeps } from '../store';
4+
import { RootState, StorageRegistry } from '../store';
55

66
export const createAuthListenerMiddleware = (
77
getUserRoleFn: () => string,
8-
storage: StorageDeps
8+
storageRegistryDict: StorageRegistry
99
) => {
1010
const middleware = createListenerMiddleware();
1111

@@ -31,9 +31,9 @@ export const createAuthListenerMiddleware = (
3131
effect: async (_, listenerApi) => {
3232
const state = listenerApi.getState() as RootState;
3333
if (state.authorisation.isAdminUser) {
34-
storage.saveIsAdminMode(state.authorisation.isAdminMode);
34+
storageRegistryDict.authorisation.save(state.authorisation.isAdminMode);
3535
} else {
36-
storage.clearIsAdminMode();
36+
storageRegistryDict.authorisation.clear();
3737
}
3838
},
3939
});
@@ -44,9 +44,9 @@ export const createAuthListenerMiddleware = (
4444
const { isAdminUser, isAdminMode } = (listenerApi.getState() as RootState)
4545
.authorisation;
4646
if (!isAdminUser) {
47-
storage.clearIsAdminMode();
47+
storageRegistryDict.authorisation.clear();
4848
} else {
49-
storage.saveIsAdminMode(isAdminMode);
49+
storageRegistryDict.authorisation.save(isAdminMode);
5050
}
5151
},
5252
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { setIsCriticalMode } from '../slices/criticalitySlice';
2+
import { configureAppStore, StorageRegistry } from '../store';
3+
4+
describe('authListenerMiddleware', () => {
5+
let store: ReturnType<typeof configureAppStore>;
6+
let storageRegistryDict: StorageRegistry;
7+
8+
beforeEach(() => {
9+
const storageDeps = {
10+
save: vi.fn(),
11+
clear: vi.fn(),
12+
load: vi.fn(),
13+
};
14+
storageRegistryDict = {
15+
authorisation: storageDeps,
16+
criticality: storageDeps,
17+
};
18+
19+
store = configureAppStore(undefined, undefined, storageRegistryDict);
20+
});
21+
22+
it('should store critical mode', async () => {
23+
store.dispatch(setIsCriticalMode(true));
24+
25+
await Promise.resolve();
26+
27+
expect(storageRegistryDict.criticality.save).toHaveBeenCalledWith(true);
28+
});
29+
});

0 commit comments

Comments
 (0)