Skip to content

Commit 5717e70

Browse files
committed
Corrected issues with options not being loaded every time and added a test to ensure settings are rendered correctly.
1 parent 39a1a01 commit 5717e70

File tree

3 files changed

+224
-15
lines changed

3 files changed

+224
-15
lines changed

web-ui/src/pages/SettingsPage.jsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,15 @@ const SettingsPage = () => {
4444
(await getAllOptions()).payload.data : [];
4545

4646
if (allOptions) {
47-
// If the option has a valid UUID, then the setting already exists.
48-
// This information is necessary to know since we must use POST to
49-
// create new settings and PUT to modify existing settings.
50-
for (let option of allOptions) {
51-
option.exists = option?.id != null;
52-
}
47+
// Sort the options by category, store them, and upate the state.
48+
setSettingsControls(
49+
allOptions.sort((l, r) => l.category.localeCompare(r.category)));
5350
}
54-
55-
// Sort the options by category, store them, and upate the state.
56-
setSettingsControls(
57-
allOptions.sort((l, r) => l.category.localeCompare(r.category)));
5851
};
59-
fetchData();
60-
}, []);
52+
if (csrf) {
53+
fetchData();
54+
}
55+
}, [state, csrf]);
6156

6257
// For specific settings, add a handleFunction to the settings object.
6358
// Format should be handleSetting and then add it to the handlers object
@@ -126,12 +121,12 @@ const SettingsPage = () => {
126121
const save = async () => {
127122
let errors;
128123
let saved = 0;
129-
for( let key of Object.keys(handlers)) {
124+
for(let key of Object.keys(handlers)) {
130125
const setting = handlers[key].setting;
131126
// The settings controller does not allow blank values.
132127
if (setting && setting.value) {
133128
let res;
134-
if (setting.exists) {
129+
if (setting.id) {
135130
res = await putOption({ name: setting.name,
136131
value: setting.value }, csrf);
137132
} else {
@@ -197,7 +192,8 @@ const SettingsPage = () => {
197192
categories[info.category] = true;
198193
return (
199194
<>
200-
<Typography variant="h4"
195+
<Typography data-testid={info.category}
196+
variant="h4"
201197
sx={{textDecoration: 'underline'}}
202198
display="inline">{titleCase(info.category)}</Typography>
203199
<Component key={index} {...info} />
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React from 'react';
2+
import SettingsPage from './SettingsPage';
3+
import { AppContextProvider } from '../context/AppContext';
4+
import { http, HttpResponse } from 'msw';
5+
import { setupServer } from 'msw/node';
6+
import { BrowserRouter } from 'react-router-dom';
7+
8+
const initialState = {
9+
state: {
10+
csrf: 'csrf',
11+
userProfile: {
12+
name: 'Current User',
13+
role: ['MEMBER'],
14+
permissions: [{ permission: 'CAN_ADMINISTER_SETTINGS' }],
15+
},
16+
loading: {
17+
teams: [],
18+
},
19+
}
20+
};
21+
22+
const server = setupServer(
23+
http.get('http://localhost:8080/services/settings/options', ({ request }) => {
24+
return HttpResponse.json([
25+
{
26+
'name': 'STRING_SETTING',
27+
'description': 'The description',
28+
'category': 'THEME',
29+
'type': 'STRING',
30+
'value': 'The value',
31+
},
32+
{
33+
'name': 'OTHER_SETTING',
34+
'description': 'The description',
35+
'category': 'THEME',
36+
'type': 'NUMBER',
37+
'value': '42',
38+
},
39+
{
40+
'name': 'ANOTHER_SETTING',
41+
'description': 'The description',
42+
'category': 'INTEGRATIONS',
43+
'type': 'BOOLEAN',
44+
'value': 'false',
45+
},
46+
]);
47+
}),
48+
);
49+
50+
beforeAll(() => server.listen());
51+
afterEach(() => server.resetHandlers());
52+
afterAll(() => server.close());
53+
54+
describe('SettingsPage', () => {
55+
it('renders correctly', async () => {
56+
await waitForSnapshot(
57+
// There are two settings with the THEME category. If the page is
58+
// rendered correctly, there will only be one THEME category heading.
59+
'THEME',
60+
<AppContextProvider value={initialState}>
61+
<BrowserRouter>
62+
<SettingsPage />
63+
</BrowserRouter>
64+
</AppContextProvider>
65+
);
66+
});
67+
68+
it('renders an error if user does not have appropriate permission', () => {
69+
snapshot(
70+
<AppContextProvider>
71+
<BrowserRouter>
72+
<SettingsPage />
73+
</BrowserRouter>
74+
</AppContextProvider>
75+
);
76+
});
77+
});
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`SettingsPage > renders an error if user does not have appropriate permission 1`] = `
4+
<div>
5+
<h3>
6+
You do not have permission to view this page.
7+
</h3>
8+
</div>
9+
`;
10+
11+
exports[`SettingsPage > renders correctly 1`] = `
12+
<div>
13+
<div
14+
class="settings-page"
15+
>
16+
<h4
17+
class="MuiTypography-root MuiTypography-h4 css-13d5t77-MuiTypography-root"
18+
data-testid="INTEGRATIONS"
19+
>
20+
Integrations
21+
</h4>
22+
<div
23+
class="settings-type"
24+
>
25+
<label
26+
for="another-setting"
27+
>
28+
<h5
29+
class="MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom css-h93ljk-MuiTypography-root"
30+
>
31+
Another Setting
32+
</h5>
33+
</label>
34+
<p>
35+
The description
36+
</p>
37+
<span
38+
class="MuiSwitch-root MuiSwitch-sizeMedium settings-control css-julti5-MuiSwitch-root"
39+
>
40+
<span
41+
class="MuiButtonBase-root MuiSwitch-switchBase MuiSwitch-colorPrimary Mui-checked PrivateSwitchBase-root MuiSwitch-switchBase MuiSwitch-colorPrimary Mui-checked Mui-checked css-byenzh-MuiButtonBase-root-MuiSwitch-switchBase"
42+
>
43+
<input
44+
checked=""
45+
class="PrivateSwitchBase-input MuiSwitch-input css-1m9pwf3"
46+
id="another-setting"
47+
type="checkbox"
48+
/>
49+
<span
50+
class="MuiSwitch-thumb css-jsexje-MuiSwitch-thumb"
51+
/>
52+
<span
53+
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
54+
/>
55+
</span>
56+
<span
57+
class="MuiSwitch-track css-1yjjitx-MuiSwitch-track"
58+
/>
59+
</span>
60+
</div>
61+
<h4
62+
class="MuiTypography-root MuiTypography-h4 css-13d5t77-MuiTypography-root"
63+
data-testid="THEME"
64+
>
65+
Theme
66+
</h4>
67+
<div
68+
class="settings-type"
69+
>
70+
<label
71+
for="string-setting"
72+
>
73+
<h5
74+
class="MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom css-h93ljk-MuiTypography-root"
75+
>
76+
String Setting
77+
</h5>
78+
</label>
79+
<p>
80+
The description
81+
</p>
82+
<div
83+
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-colorPrimary settings-control css-bz5ng3-MuiInputBase-root-MuiInput-root"
84+
>
85+
<input
86+
class="MuiInputBase-input MuiInput-input css-1x51dt5-MuiInputBase-input-MuiInput-input"
87+
id="string-setting"
88+
placeholder="Enter String Setting"
89+
type="text"
90+
value="The value"
91+
/>
92+
</div>
93+
</div>
94+
<div
95+
class="settings-type"
96+
>
97+
<label
98+
for="other-setting"
99+
>
100+
<h5
101+
class="MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom css-h93ljk-MuiTypography-root"
102+
>
103+
Other Setting
104+
</h5>
105+
</label>
106+
<p>
107+
The description
108+
</p>
109+
<div
110+
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-colorPrimary settings-control css-bz5ng3-MuiInputBase-root-MuiInput-root"
111+
>
112+
<input
113+
class="MuiInputBase-input MuiInput-input css-1x51dt5-MuiInputBase-input-MuiInput-input"
114+
id="other-setting"
115+
type="number"
116+
value="42"
117+
/>
118+
</div>
119+
</div>
120+
<div
121+
class="buttons"
122+
>
123+
<button
124+
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary css-1e6y48t-MuiButtonBase-root-MuiButton-root"
125+
tabindex="0"
126+
type="button"
127+
>
128+
Save
129+
<span
130+
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
131+
/>
132+
</button>
133+
</div>
134+
</div>
135+
</div>
136+
`;

0 commit comments

Comments
 (0)