frontend: cover the constants lookups and the query keys
The mood, trigger, care-action and notification lookup tables and every react-query key builder had no offline check, so a drifted label, a lost fallback or a reshaped key could only surface as a stale cache on a device. Each key's base, list, detail and filtered shapes are pinned, along with the day segment the insight key embeds
Pedro Lucas Porcellis porcellis@eletrotupi.com · 9 days ago
b3ead45a73252ca3006b75f6c77a3d72e8055586 Browse filesParents
ff054a2Changed files
6
Additions
+395
Deletions
0
/dev/null → frontend/tests/constants/care-action-categories.unit.test.ts
1 +
import {
2 +
getActivityCategory,
3 +
getCareActionMeta,
4 +
} from "@/constants/care-action-categories";
5 +
import type {
6 +
AppointmentType,
7 +
CareAction,
8 +
CareActionType,
9 +
MedicineRegimen,
10 +
} from "@/lib/api";
11 +
12 +
const FIXED = new Date(2025, 0, 15, 10, 0, 0);
13 +
14 +
const GENERIC_ACTIVITY = {
15 +
icon: "star",
16 +
color: "#6B7280",
17 +
iconBackground: "#F3F4F6",
18 +
label: "Atividade",
19 +
};
20 +
21 +
const APPOINTMENTS: { type: AppointmentType; label: string }[] = [
22 +
{ type: "ANALYST", label: "Analista" },
23 +
{ type: "PSYCHIATRIST", label: "Psiquiatra" },
24 +
{ type: "GP", label: "Clínico" },
25 +
{ type: "NUTRITIONIST", label: "Nutricionista" },
26 +
{ type: "PHYSIOTHERAPIST", label: "Fisioterapeuta" },
27 +
{ type: "OTHER", label: "Consulta" },
28 +
];
29 +
30 +
function regimen(overrides: Partial<MedicineRegimen> = {}): MedicineRegimen {
31 +
return {
32 +
id: 1,
33 +
name: "Sertralina",
34 +
dosage: "50mg",
35 +
periodicity: "DAILY",
36 +
scheduledAt: ["08:00"],
37 +
active: true,
38 +
userId: 1,
39 +
createdAt: FIXED,
40 +
updatedAt: FIXED,
41 +
...overrides,
42 +
};
43 +
}
44 +
45 +
function careAction(overrides: Partial<CareAction> = {}): CareAction {
46 +
return {
47 +
id: 1,
48 +
type: "ACTIVITY",
49 +
moment: FIXED,
50 +
userId: 1,
51 +
createdAt: FIXED,
52 +
updatedAt: FIXED,
53 +
...overrides,
54 +
};
55 +
}
56 +
57 +
describe("getCareActionMeta", () => {
58 +
it.each(APPOINTMENTS)("labels a $type appointment", ({ type, label }) => {
59 +
expect(
60 +
getCareActionMeta(
61 +
careAction({
62 +
type: "APPOINTMENT",
63 +
appointment: { id: 1, careActionId: 1, type, duration: 50 },
64 +
}),
65 +
),
66 +
).toStrictEqual({
67 +
icon: "psychology",
68 +
color: "#1D4ED8",
69 +
iconBackground: "#DBEAFE",
70 +
label,
71 +
});
72 +
});
73 +
74 +
it("falls back to a generic appointment without a detail", () => {
75 +
expect(getCareActionMeta(careAction({ type: "APPOINTMENT" }))).toStrictEqual(
76 +
{
77 +
icon: "psychology",
78 +
color: "#1D4ED8",
79 +
iconBackground: "#DBEAFE",
80 +
label: "Consulta",
81 +
},
82 +
);
83 +
});
84 +
85 +
it("labels medicine by the regimen name", () => {
86 +
expect(
87 +
getCareActionMeta(
88 +
careAction({
89 +
type: "MEDICINE",
90 +
medicineLog: {
91 +
id: 1,
92 +
careActionId: 1,
93 +
takenAt: FIXED,
94 +
regimen: regimen(),
95 +
},
96 +
}),
97 +
),
98 +
).toStrictEqual({
99 +
icon: "medication",
100 +
color: "#7C3AED",
101 +
iconBackground: "#EDE9FE",
102 +
label: "Sertralina",
103 +
});
104 +
});
105 +
106 +
it("falls back to a generic medicine label without a regimen", () => {
107 +
expect(getCareActionMeta(careAction({ type: "MEDICINE" }))).toStrictEqual({
108 +
icon: "medication",
109 +
color: "#7C3AED",
110 +
iconBackground: "#EDE9FE",
111 +
label: "Medicação",
112 +
});
113 +
});
114 +
115 +
it("labels an activity by its activity type", () => {
116 +
expect(
117 +
getCareActionMeta(
118 +
careAction({
119 +
type: "ACTIVITY",
120 +
activity: { id: 1, careActionId: 1, type: "WALK" },
121 +
}),
122 +
),
123 +
).toStrictEqual({
124 +
icon: "directions-walk",
125 +
color: "#059669",
126 +
iconBackground: "#D1FAE5",
127 +
label: "Caminhada",
128 +
});
129 +
});
130 +
131 +
it("falls back to a generic activity without a detail", () => {
132 +
expect(getCareActionMeta(careAction({ type: "ACTIVITY" }))).toStrictEqual(
133 +
GENERIC_ACTIVITY,
134 +
);
135 +
});
136 +
137 +
it("falls back to a generic care action for a type it does not know", () => {
138 +
const unknown = careAction({ type: "UNKNOWN" as unknown as CareActionType });
139 +
140 +
expect(getCareActionMeta(unknown)).toStrictEqual({
141 +
icon: "star",
142 +
color: "#6B7280",
143 +
iconBackground: "#F3F4F6",
144 +
label: "Cuidado",
145 +
});
146 +
});
147 +
});
148 +
149 +
describe("getActivityCategory", () => {
150 +
it("finds an activity by its type", () => {
151 +
expect(getActivityCategory("YOGA")).toStrictEqual({
152 +
icon: "self-improvement",
153 +
color: "#7C3AED",
154 +
iconBackground: "#EDE9FE",
155 +
label: "Yoga",
156 +
});
157 +
});
158 +
159 +
it("falls back to the catch-all activity", () => {
160 +
expect(getActivityCategory()).toStrictEqual(GENERIC_ACTIVITY);
161 +
expect(getActivityCategory("SKATE")).toStrictEqual(GENERIC_ACTIVITY);
162 +
});
163 +
});
/dev/null → frontend/tests/constants/mood-components.unit.test.ts
1 +
import {
2 +
getMoodComponent,
3 +
intensityLabel,
4 +
intensityToValue,
5 +
} from "@/constants/mood-components";
6 +
7 +
describe("getMoodComponent", () => {
8 +
it("finds a component by its id", () => {
9 +
expect(getMoodComponent("joy")).toStrictEqual({
10 +
id: "joy",
11 +
label: "Alegria",
12 +
color: "#FBBF24",
13 +
});
14 +
});
15 +
16 +
it("returns undefined for an unknown id", () => {
17 +
expect(getMoodComponent("ennui")).toBeUndefined();
18 +
});
19 +
});
20 +
21 +
describe("intensityLabel", () => {
22 +
it.each([
23 +
{ value: 1, label: "Suave" },
24 +
{ value: 2, label: "Moderada" },
25 +
{ value: 3, label: "Intensa" },
26 +
])("labels the numeric intensity $value", ({ value, label }) => {
27 +
expect(intensityLabel(value)).toBe(label);
28 +
});
29 +
30 +
it.each([
31 +
{ value: "LIGHT", label: "Suave" },
32 +
{ value: "MODERATE", label: "Moderada" },
33 +
{ value: "HIGH", label: "Intensa" },
34 +
])("labels the string intensity $value", ({ value, label }) => {
35 +
expect(intensityLabel(value)).toBe(label);
36 +
});
37 +
});
38 +
39 +
describe("intensityToValue", () => {
40 +
it.each([
41 +
{ value: 1, expected: "LIGHT" },
42 +
{ value: 2, expected: "MODERATE" },
43 +
{ value: 3, expected: "HIGH" },
44 +
])("maps the numeric intensity $value", ({ value, expected }) => {
45 +
expect(intensityToValue(value)).toBe(expected);
46 +
});
47 +
48 +
it("round-trips every value back to its label", () => {
49 +
for (const value of [1, 2, 3]) {
50 +
expect(intensityLabel(intensityToValue(value))).toBe(
51 +
intensityLabel(value),
52 +
);
53 +
}
54 +
});
55 +
});
/dev/null → frontend/tests/constants/moods.unit.test.ts
1 +
import { getMood } from "@/constants/moods";
2 +
3 +
describe("getMood", () => {
4 +
it("finds a mood by its id", () => {
5 +
expect(getMood("good")).toStrictEqual({
6 +
id: "good",
7 +
icon: "😊",
8 +
label: "Bem",
9 +
});
10 +
});
11 +
12 +
it("returns undefined for an unknown id", () => {
13 +
expect(getMood("elated")).toBeUndefined();
14 +
});
15 +
});
/dev/null → frontend/tests/constants/notification-kinds.unit.test.ts
1 +
import { NOTIFICATION_KINDS } from "@/constants/notification-kinds";
2 +
3 +
describe("NOTIFICATION_KINDS", () => {
4 +
it("carries the icon and bubble for a daily reminder", () => {
5 +
expect(NOTIFICATION_KINDS.DAILY_REMINDER).toStrictEqual({
6 +
icon: "mood",
7 +
color: "#0EA5E9",
8 +
iconBackground: "#E0F2FE",
9 +
});
10 +
});
11 +
12 +
it("carries the icon and bubble for a medicine reminder", () => {
13 +
expect(NOTIFICATION_KINDS.MEDICINE_REMINDER).toStrictEqual({
14 +
icon: "medication",
15 +
color: "#7C3AED",
16 +
iconBackground: "#EDE9FE",
17 +
});
18 +
});
19 +
20 +
it("carries the icon and bubble for an insight alert", () => {
21 +
expect(NOTIFICATION_KINDS.INSIGHT_ALERT).toStrictEqual({
22 +
icon: "lightbulb-outline",
23 +
color: "#D97706",
24 +
iconBackground: "#FEF3C7",
25 +
});
26 +
});
27 +
});
/dev/null → frontend/tests/constants/trigger-categories.unit.test.ts
1 +
import { getTriggerCategory } from "@/constants/trigger-categories";
2 +
3 +
describe("getTriggerCategory", () => {
4 +
it("finds a category by its id", () => {
5 +
expect(getTriggerCategory("WORK")).toStrictEqual({
6 +
id: "WORK",
7 +
label: "Trabalho",
8 +
icon: "work",
9 +
color: "#3b82f6",
10 +
iconBackground: "#dbeafe",
11 +
variant: "blue",
12 +
});
13 +
});
14 +
15 +
it("falls back to the catch-all category for an unknown id", () => {
16 +
expect(getTriggerCategory("UNKNOWN")).toStrictEqual({
17 +
id: "OTHER",
18 +
label: "Outro",
19 +
icon: "help-outline",
20 +
color: "#94a3b8",
21 +
iconBackground: "#f1f5f9",
22 +
variant: "gray",
23 +
});
24 +
});
25 +
});
/dev/null → frontend/tests/hooks/queryKeys.unit.test.ts
1 +
import { careActionKeys } from "@/hooks/useCareAction.queries";
2 +
import { insightKeys } from "@/hooks/useInsights.queries";
3 +
import { medicineRegimenKeys } from "@/hooks/useMedicineRegimen.queries";
4 +
import { medicineTodayKeys } from "@/hooks/useMedicineToday.queries";
5 +
import { moodKeys } from "@/hooks/useMoodEntries.queries";
6 +
import { notificationKeys } from "@/hooks/useNotifications.queries";
7 +
import { sleepRecordKeys } from "@/hooks/useSleepRecord.queries";
8 +
import { triggerKeys } from "@/hooks/useTrigger.queries";
9 +
10 +
const nested = [
11 +
{ name: "moodKeys", keys: moodKeys, base: "mood-entries" },
12 +
{ name: "triggerKeys", keys: triggerKeys, base: "triggers" },
13 +
{ name: "sleepRecordKeys", keys: sleepRecordKeys, base: "sleep-records" },
14 +
{ name: "careActionKeys", keys: careActionKeys, base: "careActions" },
15 +
];
16 +
17 +
describe.each(nested)("$name", ({ keys, base }) => {
18 +
it("nests the list and detail builders under the base", () => {
19 +
expect(keys.all()).toStrictEqual([base]);
20 +
expect(keys.lists()).toStrictEqual([base, "list"]);
21 +
expect(keys.detail("9")).toStrictEqual([base, "detail", "9"]);
22 +
});
23 +
24 +
it("carries the filters into the list key", () => {
25 +
expect(keys.list({ limit: 10 })).toStrictEqual([
26 +
base,
27 +
"list",
28 +
{ limit: 10 },
29 +
]);
30 +
expect(keys.list()).toStrictEqual([base, "list", undefined]);
31 +
});
32 +
});
33 +
34 +
describe("medicineRegimenKeys", () => {
35 +
it("nests the unfiltered list and detail builders under the base", () => {
36 +
expect(medicineRegimenKeys.all()).toStrictEqual(["medicineRegimens"]);
37 +
expect(medicineRegimenKeys.lists()).toStrictEqual([
38 +
"medicineRegimens",
39 +
"list",
40 +
]);
41 +
expect(medicineRegimenKeys.list()).toStrictEqual([
42 +
"medicineRegimens",
43 +
"list",
44 +
]);
45 +
expect(medicineRegimenKeys.detail("2")).toStrictEqual([
46 +
"medicineRegimens",
47 +
"detail",
48 +
"2",
49 +
]);
50 +
});
51 +
});
52 +
53 +
describe("medicineTodayKeys", () => {
54 +
it("nests a single list builder under the base", () => {
55 +
expect(medicineTodayKeys.all()).toStrictEqual(["medicineToday"]);
56 +
expect(medicineTodayKeys.list()).toStrictEqual(["medicineToday", "list"]);
57 +
});
58 +
});
59 +
60 +
describe("notificationKeys", () => {
61 +
it("nests the infinite builder under the list segment", () => {
62 +
expect(notificationKeys.all()).toStrictEqual(["notifications"]);
63 +
expect(notificationKeys.lists()).toStrictEqual(["notifications", "list"]);
64 +
expect(notificationKeys.infinite()).toStrictEqual([
65 +
"notifications",
66 +
"list",
67 +
"infinite",
68 +
]);
69 +
});
70 +
});
71 +
72 +
describe("insightKeys", () => {
73 +
beforeEach(() => {
74 +
jest.useFakeTimers();
75 +
});
76 +
77 +
afterEach(() => {
78 +
jest.useRealTimers();
79 +
});
80 +
81 +
it("nests the list builder under the base", () => {
82 +
expect(insightKeys.all()).toStrictEqual(["insights"]);
83 +
expect(insightKeys.lists()).toStrictEqual(["insights", "list"]);
84 +
expect(insightKeys.list({ period: "DAILY" })).toStrictEqual([
85 +
"insights",
86 +
"list",
87 +
{ period: "DAILY" },
88 +
]);
89 +
});
90 +
91 +
it("scopes a type key to the local day and rolls it over at midnight", () => {
92 +
jest.setSystemTime(new Date(2025, 0, 15, 23, 59));
93 +
94 +
expect(insightKeys.byType("DAILY_SLEEP")).toStrictEqual([
95 +
"insights",
96 +
"type",
97 +
"DAILY_SLEEP",
98 +
"2025-01-15",
99 +
]);
100 +
101 +
jest.setSystemTime(new Date(2025, 0, 16, 0, 1));
102 +
103 +
expect(insightKeys.byType("DAILY_SLEEP")).toStrictEqual([
104 +
"insights",
105 +
"type",
106 +
"DAILY_SLEEP",
107 +
"2025-01-16",
108 +
]);
109 +
});
110 +
});