Skip to content

Commit 1cb15db

Browse files
committed
YAY
1 parent c83255e commit 1cb15db

File tree

7 files changed

+160
-157
lines changed

7 files changed

+160
-157
lines changed

site/src/api/queries/settings.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,10 @@ export const userQuietHoursSchedule = (
2727
export const updateUserQuietHoursSchedule = (
2828
userId: string,
2929
queryClient: QueryClient,
30-
): MutationOptions<
31-
UserQuietHoursScheduleResponse,
32-
unknown,
33-
UpdateUserQuietHoursScheduleRequest
34-
> => {
30+
) => {
3531
return {
36-
mutationFn: (request) => API.updateUserQuietHoursSchedule(userId, request),
32+
mutationFn: (request: UpdateUserQuietHoursScheduleRequest) =>
33+
API.updateUserQuietHoursSchedule(userId, request),
3734
onSuccess: async () => {
3835
await queryClient.invalidateQueries(userQuietHoursScheduleKey(userId));
3936
},

site/src/pages/UserSettingsPage/SchedulePage/ScheduleForm.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ const validationSchema = Yup.object({
4242
export interface ScheduleFormProps {
4343
isLoading: boolean;
4444
initialValues: UserQuietHoursScheduleResponse;
45-
refetch: () => Promise<void>;
4645
mutationError: unknown;
4746
onSubmit: (data: UpdateUserQuietHoursScheduleRequest) => void;
4847
// now can be set to force the time used for "Next occurrence" in tests.
@@ -52,7 +51,6 @@ export interface ScheduleFormProps {
5251
export const ScheduleForm: FC<React.PropsWithChildren<ScheduleFormProps>> = ({
5352
isLoading,
5453
initialValues,
55-
refetch,
5654
mutationError,
5755
onSubmit,
5856
now,
@@ -87,8 +85,6 @@ export const ScheduleForm: FC<React.PropsWithChildren<ScheduleFormProps>> = ({
8785
onSubmit({
8886
schedule: timeToCron(values.startTime, values.timezone),
8987
});
90-
91-
await refetch();
9288
},
9389
});
9490
const getFieldHelpers = getFormHelpers<ScheduleFormValues>(

site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx

Lines changed: 84 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,37 @@
11
import { fireEvent, screen, waitFor, within } from "@testing-library/react";
2-
import * as API from "../../../api/api";
3-
import { renderWithAuth } from "../../../testHelpers/renderHelpers";
2+
import userEvent from "@testing-library/user-event";
3+
import { renderWithAuth } from "testHelpers/renderHelpers";
44
import { SchedulePage } from "./SchedulePage";
5-
6-
const renderPage = () => {
7-
return renderWithAuth(<SchedulePage />);
8-
};
5+
import { server } from "testHelpers/server";
6+
import { MockUser } from "testHelpers/entities";
7+
import { rest } from "msw";
98

109
const fillForm = async ({
11-
hours,
12-
minutes,
10+
hour,
11+
minute,
1312
timezone,
1413
}: {
15-
hours: number;
16-
minutes: number;
14+
hour: number;
15+
minute: number;
1716
timezone: string;
1817
}) => {
19-
await waitFor(() => screen.findByLabelText("Hours"));
20-
await waitFor(() => screen.findByLabelText("Minutes"));
21-
fireEvent.change(screen.getByLabelText("Hours"), {
22-
target: { value: hours },
23-
});
24-
fireEvent.change(screen.getByLabelText("Minutes"), {
25-
target: { value: minutes },
18+
const user = userEvent.setup();
19+
await waitFor(() => screen.findByLabelText("Start time"));
20+
const HH = hour.toString().padStart(2, "0");
21+
const mm = minute.toString().padStart(2, "0");
22+
fireEvent.change(screen.getByLabelText("Start time"), {
23+
target: { value: `${HH}:${mm}` },
2624
});
2725

28-
await waitFor(() => screen.findByLabelText("Timezone"));
29-
fireEvent.click(screen.getByLabelText("Timezone"));
30-
// TODO: fix the options targeting
31-
const optionsList = screen.getByRole("listbox");
32-
const option = within(optionsList).getByText(timezone);
33-
fireEvent.click(option);
26+
const timezoneDropdown = screen.getByLabelText("Timezone");
27+
await user.click(timezoneDropdown);
28+
const list = screen.getByRole("listbox");
29+
const option = within(list).getByText(timezone);
30+
await user.click(option);
3431
};
3532

3633
const readCronExpression = () => {
37-
return screen.getByLabelText("Cron schedule").getAttribute("value");
38-
};
39-
40-
const readNextOccurrence = () => {
41-
return screen.getByLabelText("Next occurrence").getAttribute("value");
34+
return;
4235
};
4336

4437
const submitForm = async () => {
@@ -56,75 +49,83 @@ const defaultQuietHoursResponse = {
5649
const cronTests = [
5750
{
5851
timezone: "Australia/Sydney",
59-
hours: 0,
60-
minutes: 0,
61-
currentTime: new Date("2023-09-06T15:00:00.000+10:00Z"),
62-
expectedNext: "12:00AM tomorrow (in 9 hours)",
52+
hour: 0,
53+
minute: 0,
6354
},
64-
];
55+
] as const;
6556

6657
describe("SchedulePage", () => {
67-
describe("cron tests", () => {
68-
for (let i = 0; i < cronTests.length; i++) {
69-
const test = cronTests[i];
70-
describe(`case ${i}`, () => {
71-
it("has the correct expected time", async () => {
72-
jest
73-
.spyOn(API, "getUserQuietHoursSchedule")
74-
.mockImplementationOnce(() =>
75-
Promise.resolve(defaultQuietHoursResponse),
76-
);
77-
jest
78-
.spyOn(API, "updateUserQuietHoursSchedule")
79-
.mockImplementationOnce((userId, data) => {
80-
return Promise.resolve({
81-
raw_schedule: data.schedule,
82-
user_set: true,
83-
time: `${test.hours.toString().padStart(2, "0")}:${test.minutes
84-
.toString()
85-
.padStart(2, "0")}`,
86-
timezone: test.timezone,
87-
next: "", // This value isn't used in the UI, the UI generates it.
88-
});
89-
});
90-
const { user } = renderPage();
58+
beforeEach(() => {
59+
// appear logged out
60+
server.use(
61+
rest.get(`/api/v2/users/${MockUser.id}/quiet-hours`, (req, res, ctx) => {
62+
return res(ctx.status(200), ctx.json(defaultQuietHoursResponse));
63+
}),
64+
);
65+
});
9166

92-
await fillForm(test);
67+
describe("cron tests", () => {
68+
it.each(cronTests)(
69+
"case %# has the correct expected time",
70+
async (test) => {
71+
server.use(
72+
rest.put(
73+
`/api/v2/users/${MockUser.id}/quiet-hours`,
74+
async (req, res, ctx) => {
75+
const data = await req.json();
76+
return res(
77+
ctx.status(200),
78+
ctx.json({
79+
response: {},
80+
raw_schedule: data.schedule,
81+
user_set: true,
82+
time: `${test.hour.toString().padStart(2, "0")}:${test.minute
83+
.toString()
84+
.padStart(2, "0")}`,
85+
timezone: test.timezone,
86+
next: "", // This value isn't used in the UI, the UI generates it.
87+
}),
88+
);
89+
},
90+
),
91+
);
9392

94-
const expectedCronSchedule = `CRON_TZ=${test.timezone} ${test.minutes} ${test.hours} * * *`;
95-
expect(readCronExpression()).toEqual(expectedCronSchedule);
96-
expect(readNextOccurrence()).toEqual(test.expectedNext);
93+
const expectedCronSchedule = `CRON_TZ=${test.timezone} ${test.minute} ${test.hour} * * *`;
94+
renderWithAuth(<SchedulePage />);
95+
await fillForm(test);
96+
const cron = screen.getByLabelText("Cron schedule");
97+
expect(cron.getAttribute("value")).toEqual(expectedCronSchedule);
9798

98-
await submitForm();
99-
const successMessage = await screen.findByText(
100-
"Schedule updated successfully",
101-
);
102-
expect(successMessage).toBeDefined();
103-
expect(API.updateUserQuietHoursSchedule).toBeCalledTimes(1);
104-
expect(API.updateUserQuietHoursSchedule).toBeCalledWith(user.id, {
105-
schedule: expectedCronSchedule,
106-
});
107-
});
108-
});
109-
}
99+
await submitForm();
100+
const successMessage = await screen.findByText(
101+
"Schedule updated successfully",
102+
);
103+
expect(successMessage).toBeDefined();
104+
},
105+
);
110106
});
111107

112108
describe("when it is an unknown error", () => {
113109
it("shows a generic error message", async () => {
114-
jest
115-
.spyOn(API, "getUserQuietHoursSchedule")
116-
.mockImplementationOnce(() =>
117-
Promise.resolve(defaultQuietHoursResponse),
118-
);
119-
jest.spyOn(API, "updateUserQuietHoursSchedule").mockRejectedValueOnce({
120-
data: "unknown error",
121-
});
110+
server.use(
111+
rest.put(
112+
`/api/v2/users/${MockUser.id}/quiet-hours`,
113+
(req, res, ctx) => {
114+
return res(
115+
ctx.status(500),
116+
ctx.json({
117+
message: "oh no!",
118+
}),
119+
);
120+
},
121+
),
122+
);
122123

123-
renderPage();
124+
renderWithAuth(<SchedulePage />);
124125
await fillForm(cronTests[0]);
125126
await submitForm();
126127

127-
const errorMessage = await screen.findByText("Something went wrong");
128+
const errorMessage = await screen.findByText("oh no!");
128129
expect(errorMessage).toBeDefined();
129130
});
130131
});

site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
88
import {
99
updateUserQuietHoursSchedule,
1010
userQuietHoursSchedule,
11-
userQuietHoursScheduleKey,
1211
} from "api/queries/settings";
12+
import { displaySuccess } from "components/GlobalSnackbar/utils";
1313

1414
export const SchedulePage: FC = () => {
1515
const me = useMe();
@@ -22,11 +22,18 @@ export const SchedulePage: FC = () => {
2222
isError,
2323
} = useQuery(userQuietHoursSchedule(me.id));
2424

25+
const updateSchedule = updateUserQuietHoursSchedule(me.id, queryClient);
2526
const {
2627
mutate: onSubmit,
2728
error: mutationError,
2829
isLoading: mutationLoading,
29-
} = useMutation(updateUserQuietHoursSchedule(me.id, queryClient));
30+
} = useMutation({
31+
...updateSchedule,
32+
onSuccess: async () => {
33+
await updateSchedule.onSuccess();
34+
displaySuccess("Schedule updated successfully");
35+
},
36+
});
3037

3138
if (isLoading) {
3239
return <Loader />;
@@ -45,9 +52,6 @@ export const SchedulePage: FC = () => {
4552
<ScheduleForm
4653
isLoading={mutationLoading}
4754
initialValues={quietHoursSchedule}
48-
refetch={async () => {
49-
await queryClient.invalidateQueries(userQuietHoursScheduleKey(me.id));
50-
}}
5155
mutationError={mutationError}
5256
onSubmit={onSubmit}
5357
/>

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,8 @@ import {
55
formValuesToAutostartRequest,
66
formValuesToTTLRequest,
77
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/formToRequest";
8-
import {
9-
Autostart,
10-
scheduleToAutostart,
11-
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule";
12-
import {
13-
Autostop,
14-
ttlMsToAutostop,
15-
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/ttl";
16-
import * as TypesGen from "../../../api/typesGenerated";
8+
import { scheduleToAutostart } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule";
9+
import { ttlMsToAutostop } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/ttl";
1710
import {
1811
WorkspaceScheduleFormValues,
1912
Language as FormLanguage,
@@ -40,9 +33,7 @@ const validValues: WorkspaceScheduleFormValues = {
4033

4134
describe("WorkspaceSchedulePage", () => {
4235
describe("formValuesToAutostartRequest", () => {
43-
it.each<
44-
[WorkspaceScheduleFormValues, TypesGen.UpdateWorkspaceAutostartRequest]
45-
>([
36+
it.each([
4637
[
4738
// Empty case
4839
{
@@ -143,13 +134,16 @@ describe("WorkspaceSchedulePage", () => {
143134
schedule: "20 16 * * 1,3,5",
144135
},
145136
],
146-
])(`formValuesToAutostartRequest(%p) return %p`, (values, request) => {
147-
expect(formValuesToAutostartRequest(values)).toEqual(request);
148-
});
137+
] as const)(
138+
`formValuesToAutostartRequest(%p) return %p`,
139+
(values, request) => {
140+
expect(formValuesToAutostartRequest(values)).toEqual(request);
141+
},
142+
);
149143
});
150144

151145
describe("formValuesToTTLRequest", () => {
152-
it.each<[WorkspaceScheduleFormValues, TypesGen.UpdateWorkspaceTTLRequest]>([
146+
it.each([
153147
[
154148
// 0 case
155149
{
@@ -180,13 +174,13 @@ describe("WorkspaceSchedulePage", () => {
180174
ttl_ms: 28_800_000,
181175
},
182176
],
183-
])(`formValuesToTTLRequest(%p) returns %p`, (values, request) => {
177+
] as const)(`formValuesToTTLRequest(%p) returns %p`, (values, request) => {
184178
expect(formValuesToTTLRequest(values)).toEqual(request);
185179
});
186180
});
187181

188182
describe("scheduleToAutostart", () => {
189-
it.each<[string | undefined, Autostart]>([
183+
it.each([
190184
// Empty case
191185
[
192186
undefined,
@@ -237,20 +231,20 @@ describe("WorkspaceSchedulePage", () => {
237231
timezone: "Canada/Eastern",
238232
},
239233
],
240-
])(`scheduleToAutostart(%p) returns %p`, (schedule, autostart) => {
234+
] as const)(`scheduleToAutostart(%p) returns %p`, (schedule, autostart) => {
241235
expect(scheduleToAutostart(schedule)).toEqual(autostart);
242236
});
243237
});
244238

245239
describe("ttlMsToAutostop", () => {
246-
it.each<[number | undefined, Autostop]>([
240+
it.each([
247241
// empty case
248242
[undefined, { autostopEnabled: false, ttl: 0 }],
249243
// zero
250244
[0, { autostopEnabled: false, ttl: 0 }],
251245
// basic case
252246
[28_800_000, { autostopEnabled: true, ttl: 8 }],
253-
])(`ttlMsToAutostop(%p) returns %p`, (ttlMs, autostop) => {
247+
] as const)(`ttlMsToAutostop(%p) returns %p`, (ttlMs, autostop) => {
254248
expect(ttlMsToAutostop(ttlMs)).toEqual(autostop);
255249
});
256250
});

0 commit comments

Comments
 (0)