eletrotupi / tcc/ frontend/tests/history/mappers.unit.test.ts6974d5508f215de8c94b04469cf20d238069477f
11.3 KB | 445 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
  it("carries rounded minutes into the hour", () => {
210
    expect(mapSleepToHistoryCard(sleep({ average: 7.999 })).summary).toBe(
211
      "8h 00min",
212
    );
213
    expect(mapSleepToHistoryCard(sleep({ average: 5.999 })).summary).toBe(
214
      "6h 00min",
215
    );
216
  });
217
});
218

219
describe("mapTriggerToHistoryCard", () => {
220
  it("maps the record into a trigger card", () => {
221
    const record = trigger({
222
      id: 3,
223
      category: "Trabalho",
224
      comment: "Muito estresse",
225
      moment: new Date(2025, 2, 1, 9, 0, 0),
226
    });
227

228
    expect(mapTriggerToHistoryCard(record)).toStrictEqual({
229
      id: "trigger-3",
230
      category: "trigger",
231
      timestamp: record.moment.toString(),
232
      title: "Gatilho",
233
      summary: "Trabalho · Muito estresse",
234
      badge: undefined,
235
      raw: record,
236
    });
237
  });
238

239
  it("drops the separator when there is no comment", () => {
240
    const card = mapTriggerToHistoryCard(
241
      trigger({ category: "Sono", comment: "" }),
242
    );
243

244
    expect(card.summary).toBe("Sono");
245
  });
246
});
247

248
describe("mapCareActionToHistoryCard", () => {
249
  describe("medicine", () => {
250
    it("uses the regimen name and dosage", () => {
251
      const record = careAction({
252
        id: 10,
253
        type: "MEDICINE",
254
        medicineLog: {
255
          id: 1,
256
          careActionId: 10,
257
          takenAt: FIXED,
258
          regimen: {
259
            id: 1,
260
            name: "Ritalina",
261
            dosage: "10mg",
262
            periodicity: "DAILY",
263
            scheduledAt: [],
264
            active: true,
265
            userId: 1,
266
            createdAt: FIXED,
267
            updatedAt: FIXED,
268
          },
269
        },
270
      });
271

272
      expect(mapCareActionToHistoryCard(record)).toStrictEqual({
273
        id: "care_action-10",
274
        category: "care_action",
275
        subtype: "medicine",
276
        timestamp: record.moment.toString(),
277
        title: "Ritalina",
278
        summary: "10mg",
279
        raw: record,
280
      });
281
    });
282

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

286
      expect(card.title).toBe("Medicamento");
287
      expect(card.summary).toBe("Sem dosagem registrada");
288
    });
289
  });
290

291
  describe("appointment", () => {
292
    it("joins the duration and the note", () => {
293
      const record = careAction({
294
        id: 20,
295
        type: "APPOINTMENT",
296
        appointment: {
297
          id: 1,
298
          careActionId: 20,
299
          type: "ANALYST",
300
          duration: 50,
301
          note: "Boa sessão",
302
        },
303
      });
304

305
      expect(mapCareActionToHistoryCard(record)).toStrictEqual({
306
        id: "care_action-20",
307
        category: "care_action",
308
        subtype: "appointment",
309
        timestamp: record.moment.toString(),
310
        title: "Consulta · Analista",
311
        summary: "50 min · Boa sessão",
312
        badge: undefined,
313
        raw: record,
314
      });
315
    });
316

317
    it("falls back when there are no details", () => {
318
      const card = mapCareActionToHistoryCard(
319
        careAction({ type: "APPOINTMENT", appointment: undefined }),
320
      );
321

322
      expect(card.summary).toBe("Sem anotações");
323
      expect(card.title).toBe("Consulta · Consulta");
324
    });
325

326
    it.each([
327
      ["ANALYST", "Analista"],
328
      ["PSYCHIATRIST", "Psiquiatra"],
329
      ["GP", "Clínico geral"],
330
      ["NUTRITIONIST", "Nutricionista"],
331
      ["PHYSIOTHERAPIST", "Fisioterapeuta"],
332
      ["OTHER", "Consulta"],
333
      ["unmapped", "Consulta"],
334
    ] as const)("labels the %p appointment as %p", (type, label) => {
335
      const card = mapCareActionToHistoryCard(
336
        careAction({
337
          type: "APPOINTMENT",
338
          appointment: {
339
            id: 1,
340
            careActionId: 1,
341
            type: type as AppointmentType,
342
            duration: 30,
343
          },
344
        }),
345
      );
346

347
      expect(card.title).toBe(`Consulta · ${label}`);
348
    });
349

350
    describe("vínculo badge", () => {
351
      it("is absent with no links", () => {
352
        expect(
353
          mapCareActionToHistoryCard(careAction({ type: "APPOINTMENT" })).badge,
354
        ).toBeUndefined();
355
      });
356

357
      it("uses the singular for a single link", () => {
358
        expect(
359
          mapCareActionToHistoryCard(
360
            careAction({ type: "APPOINTMENT", triggerId: 5 }),
361
          ).badge,
362
        ).toEqual({ label: "1 vínculo", variant: "blue" });
363
      });
364

365
      it("uses the plural for two links", () => {
366
        expect(
367
          mapCareActionToHistoryCard(
368
            careAction({ type: "APPOINTMENT", triggerId: 5, moodId: 6 }),
369
          ).badge,
370
        ).toEqual({ label: "2 vínculos", variant: "blue" });
371
      });
372
    });
373
  });
374

375
  describe("activity", () => {
376
    it("uses the activity label and duration", () => {
377
      const record = careAction({
378
        id: 30,
379
        type: "ACTIVITY",
380
        activity: { id: 1, careActionId: 30, type: "WALK", duration: 30 },
381
      });
382

383
      expect(mapCareActionToHistoryCard(record)).toStrictEqual({
384
        id: "care_action-30",
385
        category: "care_action",
386
        subtype: "activity",
387
        timestamp: record.moment.toString(),
388
        title: "Caminhada",
389
        summary: "30 min",
390
        raw: record,
391
      });
392
    });
393

394
    it("falls back when there is no duration", () => {
395
      const card = mapCareActionToHistoryCard(
396
        careAction({
397
          type: "ACTIVITY",
398
          activity: { id: 1, careActionId: 1, type: "YOGA" },
399
        }),
400
      );
401

402
      expect(card.summary).toBe("Sem duração registrada");
403
    });
404

405
    it.each([
406
      ["WALK", "Caminhada"],
407
      ["YOGA", "Yoga"],
408
      ["GYM", "Academia"],
409
      ["MEDITATION", "Meditação"],
410
      ["SOCIAL", "Momento social"],
411
      ["CREATIVE", "Atividade criativa"],
412
      ["OTHER", "Atividade"],
413
      ["unmapped", "Atividade"],
414
    ] as const)("labels the %p activity as %p", (type, label) => {
415
      const card = mapCareActionToHistoryCard(
416
        careAction({
417
          type: "ACTIVITY",
418
          activity: {
419
            id: 1,
420
            careActionId: 1,
421
            type: type as ActivityType,
422
          },
423
        }),
424
      );
425

426
      expect(card.title).toBe(label);
427
    });
428
  });
429

430
  it("falls back for an unknown care-action type", () => {
431
    const record = careAction({
432
      id: 40,
433
      type: "SOMETHING_ELSE" as unknown as CareAction["type"],
434
    });
435

436
    expect(mapCareActionToHistoryCard(record)).toStrictEqual({
437
      id: "care_action-40",
438
      category: "care_action",
439
      timestamp: record.moment.toString(),
440
      title: "Ação de cuidado",
441
      summary: "",
442
      raw: record,
443
    });
444
  });
445
});