| 1 | import { describe, it, expect } from 'vitest'
|
| 2 | import {
|
| 3 | BaseMoodOption,
|
| 4 | MoodComponentOption,
|
| 5 | IntensityLevel,
|
| 6 | } from '@prisma/client'
|
| 7 | import {
|
| 8 | buildMoodDistribution,
|
| 9 | buildDailyMoods,
|
| 10 | buildTopComponents,
|
| 11 | buildCheckinLog,
|
| 12 | } from '@app/reports/report.data'
|
| 13 |
|
| 14 | const mood = (selectedMood: BaseMoodOption) => ({ selectedMood })
|
| 15 |
|
| 16 | // Local-noon instants keep the yyyy-MM-dd grouping stable across timezones
|
| 17 | const at = (year: number, month: number, day: number, hour = 12) =>
|
| 18 | new Date(year, month - 1, day, hour)
|
| 19 |
|
| 20 | describe('buildMoodDistribution', () => {
|
| 21 | it('counts each base mood and computes its share of the period', () => {
|
| 22 | const distribution = buildMoodDistribution(
|
| 23 | [mood('GREAT'), mood('GREAT'), mood('GOOD')],
|
| 24 | 3,
|
| 25 | )
|
| 26 |
|
| 27 | expect(distribution).toEqual([
|
| 28 | { mood: 'GREAT', count: 2, pct: 66.7 },
|
| 29 | { mood: 'GOOD', count: 1, pct: 33.3 },
|
| 30 | { mood: 'NEUTRAL', count: 0, pct: 0 },
|
| 31 | { mood: 'SAD', count: 0, pct: 0 },
|
| 32 | { mood: 'ANGRY', count: 0, pct: 0 },
|
| 33 | ])
|
| 34 | })
|
| 35 |
|
| 36 | it('reports all zeros for an empty period', () => {
|
| 37 | const order: BaseMoodOption[] = [
|
| 38 | 'GREAT',
|
| 39 | 'GOOD',
|
| 40 | 'NEUTRAL',
|
| 41 | 'SAD',
|
| 42 | 'ANGRY',
|
| 43 | ]
|
| 44 |
|
| 45 | expect(buildMoodDistribution([], 0)).toEqual(
|
| 46 | order.map(mood => ({ mood, count: 0, pct: 0 })),
|
| 47 | )
|
| 48 | })
|
| 49 | })
|
| 50 |
|
| 51 | describe('buildDailyMoods', () => {
|
| 52 | it('keeps the latest Mood Entry of each day, whatever the order', () => {
|
| 53 | const daily = buildDailyMoods(
|
| 54 | [
|
| 55 | { moment: at(2026, 10, 17, 20), selectedMood: 'GREAT' },
|
| 56 | { moment: at(2026, 10, 17, 8), selectedMood: 'SAD' },
|
| 57 | ],
|
| 58 | [at(2026, 10, 17)],
|
| 59 | )
|
| 60 |
|
| 61 | expect(daily).toEqual([{ date: '2026-10-17', mood: 'GREAT' }])
|
| 62 | })
|
| 63 |
|
| 64 | it('yields a null mood for a day with no entry', () => {
|
| 65 | const daily = buildDailyMoods(
|
| 66 | [{ moment: at(2026, 10, 17, 9), selectedMood: 'GOOD' }],
|
| 67 | [at(2026, 10, 16), at(2026, 10, 17), at(2026, 10, 18)],
|
| 68 | )
|
| 69 |
|
| 70 | expect(daily).toEqual([
|
| 71 | { date: '2026-10-16', mood: null },
|
| 72 | { date: '2026-10-17', mood: 'GOOD' },
|
| 73 | { date: '2026-10-18', mood: null },
|
| 74 | ])
|
| 75 | })
|
| 76 | })
|
| 77 |
|
| 78 | describe('buildTopComponents', () => {
|
| 79 | it('counts each component and keeps the most frequent intensity', () => {
|
| 80 | const components = buildTopComponents([
|
| 81 | {
|
| 82 | moodComponents: [
|
| 83 | { component: 'JOY', intensity: 'LIGHT' },
|
| 84 | { component: 'CALM', intensity: 'HIGH' },
|
| 85 | ],
|
| 86 | },
|
| 87 | {
|
| 88 | moodComponents: [
|
| 89 | { component: 'JOY', intensity: 'LIGHT' },
|
| 90 | { component: 'JOY', intensity: 'HIGH' },
|
| 91 | ],
|
| 92 | },
|
| 93 | ])
|
| 94 |
|
| 95 | expect(components).toEqual([
|
| 96 | { component: 'JOY', count: 3, avgIntensity: 'LIGHT' },
|
| 97 | { component: 'CALM', count: 1, avgIntensity: 'HIGH' },
|
| 98 | ])
|
| 99 | })
|
| 100 |
|
| 101 | it('keeps only the ten most frequent components', () => {
|
| 102 | const components: MoodComponentOption[] = [
|
| 103 | 'JOY', 'ANGER', 'SADNESS', 'FEAR', 'GUILT', 'FRUSTRATION',
|
| 104 | 'CALM', 'MOTIVATED', 'TIREDNESS', 'GRATITUDE', 'FOCUS',
|
| 105 | ]
|
| 106 |
|
| 107 | const moods = components.flatMap((component, i) =>
|
| 108 | Array.from({ length: 11 - i }, () => ({
|
| 109 | moodComponents: [{ component, intensity: 'LIGHT' as IntensityLevel }],
|
| 110 | })),
|
| 111 | )
|
| 112 |
|
| 113 | const top = buildTopComponents(moods)
|
| 114 |
|
| 115 | expect(top).toHaveLength(10)
|
| 116 | expect(top[0]).toEqual({
|
| 117 | component: 'JOY',
|
| 118 | count: 11,
|
| 119 | avgIntensity: 'LIGHT',
|
| 120 | })
|
| 121 | expect(top.map(c => c.component)).not.toContain('FOCUS')
|
| 122 | })
|
| 123 | })
|
| 124 |
|
| 125 | describe('buildCheckinLog', () => {
|
| 126 | it('projects the log fields and renames the mood', () => {
|
| 127 | const moment = at(2026, 10, 17, 8)
|
| 128 |
|
| 129 | const log = buildCheckinLog([
|
| 130 | {
|
| 131 | moment,
|
| 132 | selectedMood: 'GOOD',
|
| 133 | anxietyLevel: 2,
|
| 134 | stressLevel: 3,
|
| 135 | energyLevel: 7,
|
| 136 | annotation: 'manhã calma',
|
| 137 | },
|
| 138 | ])
|
| 139 |
|
| 140 | expect(log).toEqual([
|
| 141 | {
|
| 142 | moment,
|
| 143 | mood: 'GOOD',
|
| 144 | anxietyLevel: 2,
|
| 145 | stressLevel: 3,
|
| 146 | energyLevel: 7,
|
| 147 | annotation: 'manhã calma',
|
| 148 | },
|
| 149 | ])
|
| 150 | })
|
| 151 |
|
| 152 | it('keeps at most the fifty most recent entries', () => {
|
| 153 | const entry = (i: number) => ({
|
| 154 | moment: at(2026, 10, 1, 0),
|
| 155 | selectedMood: 'GOOD' as BaseMoodOption,
|
| 156 | anxietyLevel: 1,
|
| 157 | stressLevel: 1,
|
| 158 | energyLevel: 1,
|
| 159 | annotation: `n${i}`,
|
| 160 | })
|
| 161 |
|
| 162 | const log = buildCheckinLog(Array.from({ length: 51 }, (_, i) => entry(i)))
|
| 163 |
|
| 164 | expect(log).toHaveLength(50)
|
| 165 | expect(log[0].annotation).toBe('n0')
|
| 166 | expect(log[49].annotation).toBe('n49')
|
| 167 | })
|
| 168 | })
|