@oneuptime/common 12.0.29 → 12.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Index.ts +11 -0
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
- package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
- package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
- package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
- package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
- package/Server/API/OnCallCalendarAPI.ts +2194 -0
- package/Server/EnvironmentConfig.ts +138 -0
- package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
- package/Server/Services/Index.ts +12 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
- package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
- package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
- package/Server/Services/TeamMemberService.ts +489 -1
- package/Server/Services/TeamService.ts +50 -0
- package/Server/Services/UserNotificationSettingService.ts +44 -1
- package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
- package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
- package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
- package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
- package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
- package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
- package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
- package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
- package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
- package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
- package/Server/Utils/Response.ts +134 -0
- package/Server/Utils/StartServer.ts +10 -0
- package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
- package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
- package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
- package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
- package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
- package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
- package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
- package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
- package/Tests/Server/API/Helpers.ts +8 -0
- package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
- package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
- package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
- package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
- package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
- package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
- package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
- package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
- package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
- package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
- package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
- package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
- package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
- package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
- package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
- package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
- package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
- package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
- package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
- package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
- package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
- package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
- package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
- package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
- package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
- package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Types/Calendar/ICalendar.ts +442 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
- package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
- package/Types/OnCallDutyPolicy/Layer.ts +190 -35
- package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
- package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
- package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
- package/build/dist/Models/DatabaseModels/Index.js +11 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
- package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
- package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +102 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
- package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
- package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
- package/build/dist/Server/Services/Index.js +12 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
- package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/TeamMemberService.js +396 -1
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +43 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
- package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
- package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
- package/build/dist/Server/Utils/Response.js +97 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +9 -1
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
- package/build/dist/Types/Calendar/ICalendar.js +290 -0
- package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
- package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
- package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
- package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
jest,
|
|
8
|
+
test,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
import {
|
|
11
|
+
cleanup,
|
|
12
|
+
fireEvent,
|
|
13
|
+
render,
|
|
14
|
+
screen,
|
|
15
|
+
waitFor,
|
|
16
|
+
within,
|
|
17
|
+
} from "@testing-library/react";
|
|
18
|
+
import React from "react";
|
|
19
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* The two cards on the Calendar Feed page that are NOT about the link: the
|
|
23
|
+
* reminder chips (one UserOnCallShiftReminder row per chip) and the live
|
|
24
|
+
* "Upcoming shifts" list from /my-shifts.
|
|
25
|
+
*
|
|
26
|
+
* Reminders: every chip click is one create or one delete of exactly the row
|
|
27
|
+
* it represents, the custom modal refuses what the service would refuse (with
|
|
28
|
+
* a reason, before the round trip), and a duplicate lead time is caught
|
|
29
|
+
* locally. Upcoming shifts: the window sent is the 30-day one, rows are
|
|
30
|
+
* grouped by day, the "covering for" and policy-variant facts survive to the
|
|
31
|
+
* DOM, and every row can reach "Get cover".
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const getMock: MockFunction = getJestMockFunction();
|
|
35
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
36
|
+
const createMock: MockFunction = getJestMockFunction();
|
|
37
|
+
const deleteItemMock: MockFunction = getJestMockFunction();
|
|
38
|
+
|
|
39
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
40
|
+
return {
|
|
41
|
+
__esModule: true,
|
|
42
|
+
default: {
|
|
43
|
+
get: (...args: Array<any>) => {
|
|
44
|
+
return getMock(...args);
|
|
45
|
+
},
|
|
46
|
+
getFriendlyMessage: (error: unknown): string => {
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
return error.message;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
error &&
|
|
53
|
+
typeof error === "object" &&
|
|
54
|
+
"message" in (error as Record<string, unknown>)
|
|
55
|
+
) {
|
|
56
|
+
return String((error as Record<string, unknown>)["message"]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return "Something went wrong";
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
66
|
+
return {
|
|
67
|
+
__esModule: true,
|
|
68
|
+
default: {
|
|
69
|
+
getCommonHeaders: (): Record<string, string> => {
|
|
70
|
+
return {};
|
|
71
|
+
},
|
|
72
|
+
getList: (...args: Array<any>) => {
|
|
73
|
+
return getListMock(...args);
|
|
74
|
+
},
|
|
75
|
+
create: (...args: Array<any>) => {
|
|
76
|
+
return createMock(...args);
|
|
77
|
+
},
|
|
78
|
+
deleteItem: (...args: Array<any>) => {
|
|
79
|
+
return deleteItemMock(...args);
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
jest.mock("react-i18next", () => {
|
|
86
|
+
return {
|
|
87
|
+
useTranslation: () => {
|
|
88
|
+
return {
|
|
89
|
+
t: (key: string, options?: { defaultValue?: string }): string => {
|
|
90
|
+
return options?.defaultValue ?? key;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
98
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
99
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
100
|
+
import Route from "../../../Types/API/Route";
|
|
101
|
+
import { JSONArray, JSONObject } from "../../../Types/JSON";
|
|
102
|
+
import { MaterializedShiftJson } from "../../../Types/OnCallDutyPolicy/MaterializedShift";
|
|
103
|
+
import UserOnCallShiftReminder from "../../../Models/DatabaseModels/UserOnCallShiftReminder";
|
|
104
|
+
import ShiftRemindersCard from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/ShiftRemindersCard";
|
|
105
|
+
import UpcomingShiftsCard from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/UpcomingShiftsCard";
|
|
106
|
+
import {
|
|
107
|
+
MY_SHIFTS_PATH,
|
|
108
|
+
STANDING_ASSIGNMENTS_COPY,
|
|
109
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/OnCallPolicy/CalendarFeed/CalendarFeedUtil";
|
|
110
|
+
import PageMap from "../../../../App/FeatureSet/Dashboard/src/Utils/PageMap";
|
|
111
|
+
import RouteMap, {
|
|
112
|
+
RouteUtil,
|
|
113
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap";
|
|
114
|
+
|
|
115
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
116
|
+
"8f2a1b3c-4d5e-4f60-9a7b-1c2d3e4f5a6b",
|
|
117
|
+
);
|
|
118
|
+
const USER_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
119
|
+
const NOW: Date = new Date("2026-08-31T12:00:00.000Z");
|
|
120
|
+
|
|
121
|
+
type ReminderRowFunction = (
|
|
122
|
+
id: string,
|
|
123
|
+
minutes: number,
|
|
124
|
+
) => UserOnCallShiftReminder;
|
|
125
|
+
|
|
126
|
+
const reminderRow: ReminderRowFunction = (
|
|
127
|
+
id: string,
|
|
128
|
+
minutes: number,
|
|
129
|
+
): UserOnCallShiftReminder => {
|
|
130
|
+
const row: UserOnCallShiftReminder = new UserOnCallShiftReminder();
|
|
131
|
+
row.id = new ObjectID(id);
|
|
132
|
+
row.minutesBeforeShift = minutes;
|
|
133
|
+
return row;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type ListFunction = (rows: Array<UserOnCallShiftReminder>) => {
|
|
137
|
+
data: Array<UserOnCallShiftReminder>;
|
|
138
|
+
count: number;
|
|
139
|
+
skip: number;
|
|
140
|
+
limit: number;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const list: ListFunction = (
|
|
144
|
+
rows: Array<UserOnCallShiftReminder>,
|
|
145
|
+
): {
|
|
146
|
+
data: Array<UserOnCallShiftReminder>;
|
|
147
|
+
count: number;
|
|
148
|
+
skip: number;
|
|
149
|
+
limit: number;
|
|
150
|
+
} => {
|
|
151
|
+
return { data: rows, count: rows.length, skip: 0, limit: 10 };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type ShiftFunction = (
|
|
155
|
+
overrides: Partial<MaterializedShiftJson> & { shiftKey: string },
|
|
156
|
+
) => MaterializedShiftJson;
|
|
157
|
+
|
|
158
|
+
const shift: ShiftFunction = (
|
|
159
|
+
overrides: Partial<MaterializedShiftJson> & { shiftKey: string },
|
|
160
|
+
): MaterializedShiftJson => {
|
|
161
|
+
return {
|
|
162
|
+
contentHash: "hash",
|
|
163
|
+
projectId: PROJECT_ID.toString(),
|
|
164
|
+
scheduleId: "schedule-1",
|
|
165
|
+
scheduleName: "Primary",
|
|
166
|
+
scheduleTimezone: "Europe/Stockholm",
|
|
167
|
+
userId: USER_ID.toString(),
|
|
168
|
+
userName: "Jane",
|
|
169
|
+
start: "2026-09-01T07:00:00.000Z",
|
|
170
|
+
end: "2026-09-01T15:00:00.000Z",
|
|
171
|
+
coverageSeconds: 8 * 3600,
|
|
172
|
+
policies: [],
|
|
173
|
+
isPast: false,
|
|
174
|
+
lastModifiedAt: "2026-08-30T00:00:00.000Z",
|
|
175
|
+
shiftConfigVersion: 3,
|
|
176
|
+
...overrides,
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
type MyShiftsJsonFunction = (
|
|
181
|
+
shifts: Array<MaterializedShiftJson>,
|
|
182
|
+
truncated: boolean,
|
|
183
|
+
) => JSONObject;
|
|
184
|
+
|
|
185
|
+
const myShiftsJson: MyShiftsJsonFunction = (
|
|
186
|
+
shifts: Array<MaterializedShiftJson>,
|
|
187
|
+
truncated: boolean,
|
|
188
|
+
): JSONObject => {
|
|
189
|
+
return {
|
|
190
|
+
shifts: shifts as unknown as JSONArray,
|
|
191
|
+
truncated: truncated,
|
|
192
|
+
generatedAt: NOW.toISOString(),
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
type OkFunction = (json: JSONObject) => HTTPResponse<JSONObject>;
|
|
197
|
+
|
|
198
|
+
const ok: OkFunction = (json: JSONObject): HTTPResponse<JSONObject> => {
|
|
199
|
+
return new HTTPResponse<JSONObject>(200, json, {});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
function goToCalendarFeedPage(): void {
|
|
203
|
+
window.history.pushState(
|
|
204
|
+
{},
|
|
205
|
+
"",
|
|
206
|
+
`/dashboard/${PROJECT_ID.toString()}/user-settings/calendar-feed`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function resetMocks(): void {
|
|
211
|
+
getMock.mockReset();
|
|
212
|
+
getListMock.mockReset();
|
|
213
|
+
createMock.mockReset();
|
|
214
|
+
deleteItemMock.mockReset();
|
|
215
|
+
createMock.mockResolvedValue(new UserOnCallShiftReminder());
|
|
216
|
+
deleteItemMock.mockResolvedValue(undefined);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
describe("ShiftRemindersCard", () => {
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
resetMocks();
|
|
222
|
+
goToCalendarFeedPage();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
afterEach(() => {
|
|
226
|
+
cleanup();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("lists the caller's reminder rows in this project and lights the matching chips", async () => {
|
|
230
|
+
getListMock.mockResolvedValue(
|
|
231
|
+
list([reminderRow("aaaaaaaa-0000-4000-8000-000000000001", 1440)]),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(screen.getByTestId("shift-reminder-chip-1440")).toHaveAttribute(
|
|
238
|
+
"aria-pressed",
|
|
239
|
+
"true",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(screen.getByTestId("shift-reminder-chip-10080")).toHaveAttribute(
|
|
244
|
+
"aria-pressed",
|
|
245
|
+
"false",
|
|
246
|
+
);
|
|
247
|
+
expect(screen.getByTestId("shift-reminder-chip-60")).toHaveAttribute(
|
|
248
|
+
"aria-pressed",
|
|
249
|
+
"false",
|
|
250
|
+
);
|
|
251
|
+
expect(screen.getByTestId("shift-reminder-chip-15")).toHaveAttribute(
|
|
252
|
+
"aria-pressed",
|
|
253
|
+
"false",
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const call: Record<string, unknown> = getListMock.mock
|
|
257
|
+
.calls[0]![0] as Record<string, unknown>;
|
|
258
|
+
expect(call["modelType"]).toBe(UserOnCallShiftReminder);
|
|
259
|
+
const query: Record<string, unknown> = call["query"] as Record<
|
|
260
|
+
string,
|
|
261
|
+
unknown
|
|
262
|
+
>;
|
|
263
|
+
expect(String(query["projectId"])).toBe(PROJECT_ID.toString());
|
|
264
|
+
expect(String(query["userId"])).toBe(USER_ID.toString());
|
|
265
|
+
expect(call["select"]).toEqual({ _id: true, minutesBeforeShift: true });
|
|
266
|
+
|
|
267
|
+
// The reminders card points at Notification Settings for the channels.
|
|
268
|
+
const expectedRoute: Route = RouteUtil.populateRouteParams(
|
|
269
|
+
RouteMap[PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS] as Route,
|
|
270
|
+
);
|
|
271
|
+
expect(
|
|
272
|
+
screen.getByText("Choose how reminders reach you").closest("a"),
|
|
273
|
+
).toHaveAttribute("href", expectedRoute.toString());
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("clicking an unlit chip creates exactly that row, letting the service default userId", async () => {
|
|
277
|
+
getListMock
|
|
278
|
+
.mockResolvedValueOnce(list([]))
|
|
279
|
+
.mockResolvedValueOnce(
|
|
280
|
+
list([reminderRow("aaaaaaaa-0000-4000-8000-000000000002", 60)]),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
284
|
+
|
|
285
|
+
const chip: HTMLElement = await screen.findByTestId(
|
|
286
|
+
"shift-reminder-chip-60",
|
|
287
|
+
);
|
|
288
|
+
fireEvent.click(chip);
|
|
289
|
+
|
|
290
|
+
await waitFor(() => {
|
|
291
|
+
expect(createMock).toHaveBeenCalledTimes(1);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const call: Record<string, unknown> = createMock.mock
|
|
295
|
+
.calls[0]![0] as Record<string, unknown>;
|
|
296
|
+
expect(call["modelType"]).toBe(UserOnCallShiftReminder);
|
|
297
|
+
const model: UserOnCallShiftReminder = call[
|
|
298
|
+
"model"
|
|
299
|
+
] as UserOnCallShiftReminder;
|
|
300
|
+
expect(model.minutesBeforeShift).toBe(60);
|
|
301
|
+
expect(String(model.projectId)).toBe(PROJECT_ID.toString());
|
|
302
|
+
expect(model.userId).toBeUndefined();
|
|
303
|
+
|
|
304
|
+
await waitFor(() => {
|
|
305
|
+
expect(screen.getByTestId("shift-reminder-chip-60")).toHaveAttribute(
|
|
306
|
+
"aria-pressed",
|
|
307
|
+
"true",
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
expect(getListMock).toHaveBeenCalledTimes(2);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("clicking a lit chip deletes exactly that row", async () => {
|
|
314
|
+
getListMock
|
|
315
|
+
.mockResolvedValueOnce(
|
|
316
|
+
list([reminderRow("aaaaaaaa-0000-4000-8000-000000000003", 15)]),
|
|
317
|
+
)
|
|
318
|
+
.mockResolvedValueOnce(list([]));
|
|
319
|
+
|
|
320
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
321
|
+
|
|
322
|
+
await waitFor(() => {
|
|
323
|
+
expect(screen.getByTestId("shift-reminder-chip-15")).toHaveAttribute(
|
|
324
|
+
"aria-pressed",
|
|
325
|
+
"true",
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
fireEvent.click(screen.getByTestId("shift-reminder-chip-15"));
|
|
330
|
+
|
|
331
|
+
await waitFor(() => {
|
|
332
|
+
expect(deleteItemMock).toHaveBeenCalledTimes(1);
|
|
333
|
+
});
|
|
334
|
+
const call: Record<string, unknown> = deleteItemMock.mock
|
|
335
|
+
.calls[0]![0] as Record<string, unknown>;
|
|
336
|
+
expect(call["modelType"]).toBe(UserOnCallShiftReminder);
|
|
337
|
+
expect(String(call["id"])).toBe("aaaaaaaa-0000-4000-8000-000000000003");
|
|
338
|
+
expect(createMock).not.toHaveBeenCalled();
|
|
339
|
+
|
|
340
|
+
await waitFor(() => {
|
|
341
|
+
expect(screen.getByTestId("shift-reminder-chip-15")).toHaveAttribute(
|
|
342
|
+
"aria-pressed",
|
|
343
|
+
"false",
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("a non-preset row appears as its own lit chip with a human label", async () => {
|
|
349
|
+
getListMock.mockResolvedValue(
|
|
350
|
+
list([reminderRow("aaaaaaaa-0000-4000-8000-000000000004", 90)]),
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
354
|
+
|
|
355
|
+
const chip: HTMLElement = await screen.findByTestId(
|
|
356
|
+
"shift-reminder-chip-90",
|
|
357
|
+
);
|
|
358
|
+
expect(chip).toHaveAttribute("aria-pressed", "true");
|
|
359
|
+
expect(chip).toHaveTextContent("90 minutes");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("the custom modal validates locally before any round trip", async () => {
|
|
363
|
+
getListMock.mockResolvedValue(
|
|
364
|
+
list([reminderRow("aaaaaaaa-0000-4000-8000-000000000005", 1440)]),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
368
|
+
|
|
369
|
+
fireEvent.click(await screen.findByTestId("shift-reminder-chip-custom"));
|
|
370
|
+
|
|
371
|
+
const modal: HTMLElement = await screen.findByTestId("modal");
|
|
372
|
+
const input: HTMLElement = within(modal).getByTestId(
|
|
373
|
+
"shift-reminder-custom-minutes",
|
|
374
|
+
);
|
|
375
|
+
const submit: HTMLElement = within(modal).getByRole("button", {
|
|
376
|
+
name: "Add reminder",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
fireEvent.click(submit);
|
|
380
|
+
await waitFor(() => {
|
|
381
|
+
expect(modal).toHaveTextContent(
|
|
382
|
+
"Enter how many minutes before the shift.",
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
fireEvent.change(input, { target: { value: "12.5" } });
|
|
387
|
+
fireEvent.click(submit);
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(modal).toHaveTextContent("Enter a whole number of minutes.");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
fireEvent.change(input, { target: { value: "5" } });
|
|
393
|
+
fireEvent.click(submit);
|
|
394
|
+
await waitFor(() => {
|
|
395
|
+
expect(modal).toHaveTextContent(
|
|
396
|
+
"Reminders can be sent between 15 minutes and 14 days before a shift.",
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// 1440 already exists as a row: caught locally, not by a 400.
|
|
401
|
+
fireEvent.change(input, { target: { value: "1440" } });
|
|
402
|
+
fireEvent.click(submit);
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
expect(modal).toHaveTextContent(
|
|
405
|
+
"You already have a reminder at that lead time.",
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
expect(createMock).not.toHaveBeenCalled();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("a valid custom lead time creates the row and closes the modal", async () => {
|
|
413
|
+
getListMock
|
|
414
|
+
.mockResolvedValueOnce(list([]))
|
|
415
|
+
.mockResolvedValueOnce(
|
|
416
|
+
list([reminderRow("aaaaaaaa-0000-4000-8000-000000000006", 120)]),
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
420
|
+
|
|
421
|
+
fireEvent.click(await screen.findByTestId("shift-reminder-chip-custom"));
|
|
422
|
+
|
|
423
|
+
const modal: HTMLElement = await screen.findByTestId("modal");
|
|
424
|
+
fireEvent.change(
|
|
425
|
+
within(modal).getByTestId("shift-reminder-custom-minutes"),
|
|
426
|
+
{
|
|
427
|
+
target: { value: "120" },
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
fireEvent.click(
|
|
431
|
+
within(modal).getByRole("button", { name: "Add reminder" }),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
await waitFor(() => {
|
|
435
|
+
expect(createMock).toHaveBeenCalledTimes(1);
|
|
436
|
+
});
|
|
437
|
+
const model: UserOnCallShiftReminder = (
|
|
438
|
+
createMock.mock.calls[0]![0] as Record<string, unknown>
|
|
439
|
+
)["model"] as UserOnCallShiftReminder;
|
|
440
|
+
expect(model.minutesBeforeShift).toBe(120);
|
|
441
|
+
|
|
442
|
+
await waitFor(() => {
|
|
443
|
+
expect(screen.queryByTestId("modal")).not.toBeInTheDocument();
|
|
444
|
+
});
|
|
445
|
+
await waitFor(() => {
|
|
446
|
+
expect(screen.getByTestId("shift-reminder-chip-120")).toHaveTextContent(
|
|
447
|
+
"2 hours",
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("a failed write surfaces the server's message on the card", async () => {
|
|
453
|
+
getListMock.mockResolvedValue(list([]));
|
|
454
|
+
createMock.mockRejectedValue(
|
|
455
|
+
new HTTPErrorResponse(
|
|
456
|
+
400,
|
|
457
|
+
{
|
|
458
|
+
message: "You already have a reminder 60 minutes before your shifts.",
|
|
459
|
+
},
|
|
460
|
+
{},
|
|
461
|
+
),
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
render(<ShiftRemindersCard projectId={PROJECT_ID} userId={USER_ID} />);
|
|
465
|
+
|
|
466
|
+
fireEvent.click(await screen.findByTestId("shift-reminder-chip-60"));
|
|
467
|
+
|
|
468
|
+
await waitFor(() => {
|
|
469
|
+
expect(
|
|
470
|
+
screen.getByText(
|
|
471
|
+
"You already have a reminder 60 minutes before your shifts.",
|
|
472
|
+
),
|
|
473
|
+
).toBeInTheDocument();
|
|
474
|
+
});
|
|
475
|
+
expect(screen.getByTestId("shift-reminder-chip-60")).toHaveAttribute(
|
|
476
|
+
"aria-pressed",
|
|
477
|
+
"false",
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("without a project or user the chips are inert and nothing is fetched", async () => {
|
|
482
|
+
render(<ShiftRemindersCard projectId={null} userId={USER_ID} />);
|
|
483
|
+
|
|
484
|
+
await waitFor(() => {
|
|
485
|
+
expect(screen.getByTestId("shift-reminder-chip-custom")).toBeDisabled();
|
|
486
|
+
});
|
|
487
|
+
expect(screen.getByTestId("shift-reminder-chip-60")).toBeDisabled();
|
|
488
|
+
expect(getListMock).not.toHaveBeenCalled();
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe("UpcomingShiftsCard", () => {
|
|
493
|
+
beforeEach(() => {
|
|
494
|
+
resetMocks();
|
|
495
|
+
goToCalendarFeedPage();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
afterEach(() => {
|
|
499
|
+
cleanup();
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("asks /my-shifts for the next 30 days and groups the answer by day", async () => {
|
|
503
|
+
getMock.mockResolvedValue(
|
|
504
|
+
ok(
|
|
505
|
+
myShiftsJson(
|
|
506
|
+
[
|
|
507
|
+
shift({
|
|
508
|
+
shiftKey: "later",
|
|
509
|
+
start: "2026-09-03T07:00:00.000Z",
|
|
510
|
+
end: "2026-09-03T15:00:00.000Z",
|
|
511
|
+
scheduleName: "Secondary",
|
|
512
|
+
layerName: "Weekdays",
|
|
513
|
+
}),
|
|
514
|
+
shift({ shiftKey: "first" }),
|
|
515
|
+
shift({
|
|
516
|
+
shiftKey: "cover",
|
|
517
|
+
start: "2026-09-01T15:00:00.000Z",
|
|
518
|
+
end: "2026-09-01T23:00:00.000Z",
|
|
519
|
+
projectName: "Payments",
|
|
520
|
+
override: {
|
|
521
|
+
originalUserId: "user-2",
|
|
522
|
+
originalUserName: "Bob",
|
|
523
|
+
overrideStartsAt: "2026-09-01T15:00:00.000Z",
|
|
524
|
+
overrideEndsAt: "2026-09-01T23:00:00.000Z",
|
|
525
|
+
},
|
|
526
|
+
policyVariantOf: {
|
|
527
|
+
policyId: "policy-1",
|
|
528
|
+
policyName: "Checkout",
|
|
529
|
+
globalUserId: "user-3",
|
|
530
|
+
},
|
|
531
|
+
}),
|
|
532
|
+
],
|
|
533
|
+
false,
|
|
534
|
+
),
|
|
535
|
+
),
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
render(<UpcomingShiftsCard now={NOW} />);
|
|
539
|
+
|
|
540
|
+
await waitFor(() => {
|
|
541
|
+
expect(screen.getAllByTestId("upcoming-shift-row").length).toBe(3);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const url: string = String(
|
|
545
|
+
(getMock.mock.calls[0]![0] as Record<string, unknown>)["url"],
|
|
546
|
+
);
|
|
547
|
+
expect(url).toContain(MY_SHIFTS_PATH);
|
|
548
|
+
expect(url).toContain("from=");
|
|
549
|
+
expect(url).toContain("to=");
|
|
550
|
+
|
|
551
|
+
const days: Array<HTMLElement> =
|
|
552
|
+
screen.getAllByTestId("upcoming-shift-day");
|
|
553
|
+
expect(days.length).toBe(2);
|
|
554
|
+
expect(within(days[0]!).getAllByTestId("upcoming-shift-row").length).toBe(
|
|
555
|
+
2,
|
|
556
|
+
);
|
|
557
|
+
expect(within(days[1]!).getAllByTestId("upcoming-shift-row").length).toBe(
|
|
558
|
+
1,
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
expect(screen.getByText("Covering for Bob")).toBeInTheDocument();
|
|
562
|
+
expect(screen.getByText("Only on policy Checkout")).toBeInTheDocument();
|
|
563
|
+
expect(screen.getByText("Weekdays")).toBeInTheDocument();
|
|
564
|
+
expect(screen.getByText("Secondary")).toBeInTheDocument();
|
|
565
|
+
expect(screen.getByText(/Payments/)).toBeInTheDocument();
|
|
566
|
+
|
|
567
|
+
const expectedOverridesRoute: Route = RouteUtil.populateRouteParams(
|
|
568
|
+
RouteMap[PageMap.ON_CALL_DUTY_POLICY_USER_OVERRIDES] as Route,
|
|
569
|
+
);
|
|
570
|
+
const coverLinks: Array<HTMLElement> = screen.getAllByText("Get cover");
|
|
571
|
+
expect(coverLinks.length).toBe(3);
|
|
572
|
+
expect(coverLinks[0]!.closest("a")).toHaveAttribute(
|
|
573
|
+
"href",
|
|
574
|
+
expectedOverridesRoute.toString(),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
expect(screen.getByText(STANDING_ASSIGNMENTS_COPY)).toBeInTheDocument();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("an empty answer says so rather than rendering a blank card", async () => {
|
|
581
|
+
getMock.mockResolvedValue(ok(myShiftsJson([], false)));
|
|
582
|
+
|
|
583
|
+
render(<UpcomingShiftsCard now={NOW} />);
|
|
584
|
+
|
|
585
|
+
await waitFor(() => {
|
|
586
|
+
expect(screen.getByTestId("upcoming-shifts-empty")).toHaveTextContent(
|
|
587
|
+
"no shifts on any schedule in the next 30 days",
|
|
588
|
+
);
|
|
589
|
+
});
|
|
590
|
+
expect(
|
|
591
|
+
screen.queryByTestId("upcoming-shifts-truncated"),
|
|
592
|
+
).not.toBeInTheDocument();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
/*
|
|
596
|
+
* A schedule that hits the simulation cap emits NOTHING for the capped layer
|
|
597
|
+
* (Layer.ts would otherwise attribute shifts to the wrong person), so
|
|
598
|
+
* {shifts: [], truncated: true} is a real answer from /my-shifts - and the
|
|
599
|
+
* only honest reading of it is "we could not compute this", never "you have
|
|
600
|
+
* no shifts".
|
|
601
|
+
*/
|
|
602
|
+
test("an empty BUT truncated answer keeps the warning and does not claim there are no shifts", async () => {
|
|
603
|
+
getMock.mockResolvedValue(ok(myShiftsJson([], true)));
|
|
604
|
+
|
|
605
|
+
render(<UpcomingShiftsCard now={NOW} />);
|
|
606
|
+
|
|
607
|
+
await waitFor(() => {
|
|
608
|
+
expect(screen.getByTestId("upcoming-shifts-truncated")).toHaveTextContent(
|
|
609
|
+
"too complex to compute fully",
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
expect(screen.getByTestId("upcoming-shifts-empty")).toHaveTextContent(
|
|
614
|
+
"No shifts could be listed for the next 30 days.",
|
|
615
|
+
);
|
|
616
|
+
expect(screen.getByTestId("upcoming-shifts-empty")).not.toHaveTextContent(
|
|
617
|
+
"You have no shifts",
|
|
618
|
+
);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("a truncated answer is flagged above the list", async () => {
|
|
622
|
+
getMock.mockResolvedValue(
|
|
623
|
+
ok(myShiftsJson([shift({ shiftKey: "only" })], true)),
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
render(<UpcomingShiftsCard now={NOW} />);
|
|
627
|
+
|
|
628
|
+
await waitFor(() => {
|
|
629
|
+
expect(screen.getByTestId("upcoming-shifts-truncated")).toHaveTextContent(
|
|
630
|
+
"some shifts may be missing",
|
|
631
|
+
);
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("a failed fetch shows the reason, and Refresh asks again", async () => {
|
|
636
|
+
getMock
|
|
637
|
+
.mockResolvedValueOnce(
|
|
638
|
+
new HTTPErrorResponse(503, { message: "Render cap reached" }, {}),
|
|
639
|
+
)
|
|
640
|
+
.mockResolvedValueOnce(ok(myShiftsJson([], false)));
|
|
641
|
+
|
|
642
|
+
render(<UpcomingShiftsCard now={NOW} />);
|
|
643
|
+
|
|
644
|
+
await waitFor(() => {
|
|
645
|
+
expect(screen.getByText("Render cap reached")).toBeInTheDocument();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
|
|
649
|
+
|
|
650
|
+
await waitFor(() => {
|
|
651
|
+
expect(screen.getByTestId("upcoming-shifts-empty")).toBeInTheDocument();
|
|
652
|
+
});
|
|
653
|
+
expect(getMock).toHaveBeenCalledTimes(2);
|
|
654
|
+
});
|
|
655
|
+
});
|