eletrotupi / tcc/ frontend/tests/history/mappers.unit.test.tsdcee93d0c11ceff985805b7785c7db54c2acc4dd
11.0 KB | 436 lines Raw
1
import {
2
  mapCareActionToHistoryCard,
3
  mapMoodToHistoryCard,
4
  mapSleepToHistoryCard,
5
  mapTriggerToHistoryCard,
6
} from "@/lib/history/mappers";
7
import type {
8
  ActivityType,
9
  AppointmentType,
10
  CareAction,
11
  MoodEntry,
12
  SleepRecord,
13
  Trigger,
14
} from "@/lib/api";
15
import type { MoodComponent } from "@/lib/api/types";
16

17
const FIXED = new Date(2025, 0, 15, 10, 0, 0);
18

19
function mood(overrides: Partial<MoodEntry> = {}): MoodEntry {
20
  return {
21
    id: 1,
22
    annotation: "",
23
    moment: FIXED,
24
    selectedMood: "good",
25
    anxietyLevel: 2,
26
    energyLevel: 4,
27
    stressLevel: 1,
28
    moodComponents: [],
29
    createdAt: FIXED,
30
    updatedAt: FIXED,
31
    ...overrides,
32
  };
33
}
34

35
function moodComponent(component: string): MoodComponent {
36
  return {
37
    id: 1,
38
    component,
39
    intensity: "MILD",
40
    createdAt: FIXED,
41
    updatedAt: FIXED,
42
  };
43
}
44

45
function sleep(overrides: Partial<SleepRecord> = {}): SleepRecord {
46
  return {
47
    id: 1,
48
    annotations: "",
49
    date: FIXED,
50
    average: 8,
51
    createdAt: FIXED,
52
    updatedAt: FIXED,
53
    ...overrides,
54
  };
55
}
56

57
function trigger(overrides: Partial<Trigger> = {}): Trigger {
58
  return {
59
    id: 1,
60
    comment: "",
61
    category: "Trabalho",
62
    moment: FIXED,
63
    createdAt: FIXED,
64
    updatedAt: FIXED,
65
    ...overrides,
66
  };
67
}
68

69
function careAction(overrides: Partial<CareAction> = {}): CareAction {
70
  return {
71
    id: 1,
72
    type: "MEDICINE",
73
    moment: FIXED,
74
    userId: 1,
75
    createdAt: FIXED,
76
    updatedAt: FIXED,
77
    ...overrides,
78
  };
79
}
80

81
describe("mapMoodToHistoryCard", () => {
82
  it("maps the record into a mood card", () => {
83
    const record = mood({
84
      id: 7,
85
      annotation: "Me senti bem hoje",
86
      moment: new Date(2025, 5, 10, 14, 30),
87
      selectedMood: "great",
88
      moodComponents: [moodComponent("ANXIOUS"), moodComponent("CALM")],
89
    });
90

91
    expect(mapMoodToHistoryCard(record)).toStrictEqual({
92
      id: "mood-7",
93
      category: "mood",
94
      timestamp: record.moment.toString(),
95
      title: "Ótimo",
96
      summary: "Anxious, Calm · Me senti bem hoje",
97
      badge: undefined,
98
      raw: record,
99
    });
100
  });
101

102
  it("falls back to a generic title for an unknown mood", () => {
103
    expect(mapMoodToHistoryCard(mood({ selectedMood: "unmapped" })).title).toBe(
104
      "Humor",
105
    );
106
  });
107

108
  describe("summary", () => {
109
    it("joins only the components when there is no annotation", () => {
110
      const card = mapMoodToHistoryCard(
111
        mood({
112
          annotation: "",
113
          moodComponents: [moodComponent("ANXIOUS"), moodComponent("CALM")],
114
        }),
115
      );
116

117
      expect(card.summary).toBe("Anxious, Calm");
118
    });
119

120
    it("uses only the annotation when there are no components", () => {
121
      const card = mapMoodToHistoryCard(
122
        mood({ annotation: "Tarde tranquila", moodComponents: [] }),
123
      );
124

125
      expect(card.summary).toBe("Tarde tranquila");
126
    });
127

128
    it("joins components and annotation when both are present", () => {
129
      const card = mapMoodToHistoryCard(
130
        mood({
131
          annotation: "Tarde tranquila",
132
          moodComponents: [moodComponent("CALM")],
133
        }),
134
      );
135

136
      expect(card.summary).toBe("Calm · Tarde tranquila");
137
    });
138

139
    it("falls back when neither is present", () => {
140
      const card = mapMoodToHistoryCard(
141
        mood({ annotation: "", moodComponents: [] }),
142
      );
143

144
      expect(card.summary).toBe("Sem detalhes");
145
    });
146
  });
147
});
148

149
describe("mapSleepToHistoryCard", () => {
150
  it("maps the record into a sleep card", () => {
151
    const record = sleep({
152
      id: 4,
153
      average: 7.5,
154
      annotations: "Dormi bem",
155
      createdAt: new Date(2025, 0, 15, 8, 0, 0),
156
    });
157

158
    expect(mapSleepToHistoryCard(record)).toStrictEqual({
159
      id: "sleep-4",
160
      category: "sleep",
161
      timestamp: record.createdAt.toISOString(),
162
      title: "Sono",
163
      summary: "7h 30min · Dormi bem",
164
      badge: { label: "Excelente", variant: "green" },
165
      raw: record,
166
    });
167
  });
168

169
  it("keeps a string timestamp as-is", () => {
170
    const timestamp = "2025-01-15T13:00:00.000Z";
171
    const card = mapSleepToHistoryCard(
172
      sleep({ createdAt: timestamp as unknown as Date }),
173
    );
174

175
    expect(card.timestamp).toBe(timestamp);
176
  });
177

178
  it("omits the separator when there is no annotation", () => {
179
    expect(mapSleepToHistoryCard(sleep({ average: 8 })).summary).toBe(
180
      "8h 00min",
181
    );
182
  });
183

184
  describe("quality badge thresholds", () => {
185
    it.each([
186
      [9, "Excelente", "green"],
187
      [7, "Excelente", "green"],
188
      [6.99, "Regular", "yellow"],
189
      [5, "Regular", "yellow"],
190
      [4.99, "Ruim", "red"],
191
      [0, "Ruim", "red"],
192
    ])("labels %p hours as %p", (average, label, variant) => {
193
      expect(mapSleepToHistoryCard(sleep({ average })).badge).toEqual({
194
        label,
195
        variant,
196
      });
197
    });
198
  });
199

200
  it("pads the minutes to two digits", () => {
201
    expect(mapSleepToHistoryCard(sleep({ average: 6.5 })).summary).toBe(
202
      "6h 30min",
203
    );
204
    expect(mapSleepToHistoryCard(sleep({ average: 6.0167 })).summary).toBe(
205
      "6h 01min",
206
    );
207
  });
208
});
209

210
describe("mapTriggerToHistoryCard", () => {
211
  it("maps the record into a trigger card", () => {
212
    const record = trigger({
213
      id: 3,
214
      category: "Trabalho",
215
      comment: "Muito estresse",
216
      moment: new Date(2025, 2, 1, 9, 0, 0),
217
    });
218

219
    expect(mapTriggerToHistoryCard(record)).toStrictEqual({
220
      id: "trigger-3",
221
      category: "trigger",
222
      timestamp: record.moment.toString(),
223
      title: "Gatilho",
224
      summary: "Trabalho · Muito estresse",
225
      badge: undefined,
226
      raw: record,
227
    });
228
  });
229

230
  it("drops the separator when there is no comment", () => {
231
    const card = mapTriggerToHistoryCard(
232
      trigger({ category: "Sono", comment: "" }),
233
    );
234

235
    expect(card.summary).toBe("Sono");
236
  });
237
});
238

239
describe("mapCareActionToHistoryCard", () => {
240
  describe("medicine", () => {
241
    it("uses the regimen name and dosage", () => {
242
      const record = careAction({
243
        id: 10,
244
        type: "MEDICINE",
245
        medicineLog: {
246
          id: 1,
247
          careActionId: 10,
248
          takenAt: FIXED,
249
          regimen: {
250
            id: 1,
251
            name: "Ritalina",
252
            dosage: "10mg",
253
            periodicity: "DAILY",
254
            scheduledAt: [],
255
            active: true,
256
            userId: 1,
257
            createdAt: FIXED,
258
            updatedAt: FIXED,
259
          },
260
        },
261
      });
262

263
      expect(mapCareActionToHistoryCard(record)).toStrictEqual({
264
        id: "care_action-10",
265
        category: "care_action",
266
        subtype: "medicine",
267
        timestamp: record.moment.toString(),
268
        title: "Ritalina",
269
        summary: "10mg",
270
        raw: record,
271
      });
272
    });
273

274
    it("falls back when there is no regimen", () => {
275
      const card = mapCareActionToHistoryCard(careAction({ type: "MEDICINE" }));
276

277
      expect(card.title).toBe("Medicamento");
278
      expect(card.summary).toBe("Sem dosagem registrada");
279
    });
280
  });
281

282
  describe("appointment", () => {
283
    it("joins the duration and the note", () => {
284
      const record = careAction({
285
        id: 20,
286
        type: "APPOINTMENT",
287
        appointment: {
288
          id: 1,
289
          careActionId: 20,
290
          type: "ANALYST",
291
          duration: 50,
292
          note: "Boa sessão",
293
        },
294
      });
295

296
      expect(mapCareActionToHistoryCard(record)).toStrictEqual({
297
        id: "care_action-20",
298
        category: "care_action",
299
        subtype: "appointment",
300
        timestamp: record.moment.toString(),
301
        title: "Consulta · Analista",
302
        summary: "50 min · Boa sessão",
303
        badge: undefined,
304
        raw: record,
305
      });
306
    });
307

308
    it("falls back when there are no details", () => {
309
      const card = mapCareActionToHistoryCard(
310
        careAction({ type: "APPOINTMENT", appointment: undefined }),
311
      );
312

313
      expect(card.summary).toBe("Sem anotações");
314
      expect(card.title).toBe("Consulta · Consulta");
315
    });
316

317
    it.each([
318
      ["ANALYST", "Analista"],
319
      ["PSYCHIATRIST", "Psiquiatra"],
320
      ["GP", "Clínico geral"],
321
      ["NUTRITIONIST", "Nutricionista"],
322
      ["PHYSIOTHERAPIST", "Fisioterapeuta"],
323
      ["OTHER", "Consulta"],
324
      ["unmapped", "Consulta"],
325
    ] as const)("labels the %p appointment as %p", (type, label) => {
326
      const card = mapCareActionToHistoryCard(
327
        careAction({
328
          type: "APPOINTMENT",
329
          appointment: {
330
            id: 1,
331
            careActionId: 1,
332
            type: type as AppointmentType,
333
            duration: 30,
334
          },
335
        }),
336
      );
337

338
      expect(card.title).toBe(`Consulta · ${label}`);
339
    });
340

341
    describe("vínculo badge", () => {
342
      it("is absent with no links", () => {
343
        expect(
344
          mapCareActionToHistoryCard(careAction({ type: "APPOINTMENT" })).badge,
345
        ).toBeUndefined();
346
      });
347

348
      it("uses the singular for a single link", () => {
349
        expect(
350
          mapCareActionToHistoryCard(
351
            careAction({ type: "APPOINTMENT", triggerId: 5 }),
352
          ).badge,
353
        ).toEqual({ label: "1 vínculo", variant: "blue" });
354
      });
355

356
      it("uses the plural for two links", () => {
357
        expect(
358
          mapCareActionToHistoryCard(
359
            careAction({ type: "APPOINTMENT", triggerId: 5, moodId: 6 }),
360
          ).badge,
361
        ).toEqual({ label: "2 vínculos", variant: "blue" });
362
      });
363
    });
364
  });
365

366
  describe("activity", () => {
367
    it("uses the activity label and duration", () => {
368
      const record = careAction({
369
        id: 30,
370
        type: "ACTIVITY",
371
        activity: { id: 1, careActionId: 30, type: "WALK", duration: 30 },
372
      });
373

374
      expect(mapCareActionToHistoryCard(record)).toStrictEqual({
375
        id: "care_action-30",
376
        category: "care_action",
377
        subtype: "activity",
378
        timestamp: record.moment.toString(),
379
        title: "Caminhada",
380
        summary: "30 min",
381
        raw: record,
382
      });
383
    });
384

385
    it("falls back when there is no duration", () => {
386
      const card = mapCareActionToHistoryCard(
387
        careAction({
388
          type: "ACTIVITY",
389
          activity: { id: 1, careActionId: 1, type: "YOGA" },
390
        }),
391
      );
392

393
      expect(card.summary).toBe("Sem duração registrada");
394
    });
395

396
    it.each([
397
      ["WALK", "Caminhada"],
398
      ["YOGA", "Yoga"],
399
      ["GYM", "Academia"],
400
      ["MEDITATION", "Meditação"],
401
      ["SOCIAL", "Momento social"],
402
      ["CREATIVE", "Atividade criativa"],
403
      ["OTHER", "Atividade"],
404
      ["unmapped", "Atividade"],
405
    ] as const)("labels the %p activity as %p", (type, label) => {
406
      const card = mapCareActionToHistoryCard(
407
        careAction({
408
          type: "ACTIVITY",
409
          activity: {
410
            id: 1,
411
            careActionId: 1,
412
            type: type as ActivityType,
413
          },
414
        }),
415
      );
416

417
      expect(card.title).toBe(label);
418
    });
419
  });
420

421
  it("falls back for an unknown care-action type", () => {
422
    const record = careAction({
423
      id: 40,
424
      type: "SOMETHING_ELSE" as unknown as CareAction["type"],
425
    });
426

427
    expect(mapCareActionToHistoryCard(record)).toStrictEqual({
428
      id: "care_action-40",
429
      category: "care_action",
430
      timestamp: record.moment.toString(),
431
      title: "Ação de cuidado",
432
      summary: "",
433
      raw: record,
434
    });
435
  });
436
});