test: Add Pattern Analysis service tests (790 lines, 29 tests)
Created comprehensive test suite for analytics pattern detection service: - Analyze all patterns for a child (sleep, feeding, diaper) - Sleep pattern analysis (duration, bedtime, wake time, night wakings, naps, consistency, trend) - Feeding pattern analysis (interval, duration, methods, consistency, trend) - Diaper pattern analysis (wet/dirty counts, intervals, health assessment) - Trend detection (improving/stable/declining for sleep, increasing/stable/decreasing for feeding) - Generate personalized recommendations based on patterns - Detect health concerns (declining sleep, frequent wakings, low feeding, unhealthy diaper output) - Helper methods (average time calculation, standard deviation, age in months) Tests cover: - Insufficient data handling (return null when < 3 activities) - Sleep trend detection (improving, stable, declining based on recent vs older averages) - Feeding method tracking (bottle, nursing, solids) - Healthy vs unhealthy diaper patterns (age-appropriate output) - Recommendation generation (bedtime routine, night wakings, sleep duration, feeding schedule) - Concern detection (declining trends, frequent wakings, low output) - Statistical calculations (average time, standard deviation) - Edge cases (empty arrays, missing durations) Total: 790 lines, 29 test cases Coverage: Pattern analysis, trend detection, health recommendations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,790 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { Repository, Between } from 'typeorm';
|
||||||
|
import {
|
||||||
|
PatternAnalysisService,
|
||||||
|
SleepPattern,
|
||||||
|
FeedingPattern,
|
||||||
|
DiaperPattern,
|
||||||
|
} from './pattern-analysis.service';
|
||||||
|
import { Activity, ActivityType } from '../../database/entities/activity.entity';
|
||||||
|
import { Child } from '../../database/entities/child.entity';
|
||||||
|
|
||||||
|
describe('PatternAnalysisService', () => {
|
||||||
|
let service: PatternAnalysisService;
|
||||||
|
let activityRepository: Repository<Activity>;
|
||||||
|
let childRepository: Repository<Child>;
|
||||||
|
|
||||||
|
const mockChild = {
|
||||||
|
id: 'child_123',
|
||||||
|
name: 'Baby Jane',
|
||||||
|
birthDate: new Date(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000), // 3 months old
|
||||||
|
familyId: 'family_123',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockActivityRepository = {
|
||||||
|
find: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockChildRepository = {
|
||||||
|
find: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
PatternAnalysisService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Activity),
|
||||||
|
useValue: mockActivityRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Child),
|
||||||
|
useValue: mockChildRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<PatternAnalysisService>(PatternAnalysisService);
|
||||||
|
activityRepository = module.get<Repository<Activity>>(
|
||||||
|
getRepositoryToken(Activity),
|
||||||
|
);
|
||||||
|
childRepository = module.get<Repository<Child>>(
|
||||||
|
getRepositoryToken(Child),
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('analyzePatterns', () => {
|
||||||
|
it('should analyze all patterns for a child', async () => {
|
||||||
|
const sleepActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-01T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-02T06:00:00'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-02T13:00:00'),
|
||||||
|
endedAt: new Date('2025-01-02T15:00:00'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-02T20:30:00'),
|
||||||
|
endedAt: new Date('2025-01-03T07:00:00'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const feedingActivities = [
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T08:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T11:00:00'),
|
||||||
|
metadata: { method: 'nursing' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T14:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const diaperActivities = [
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T09:00:00'),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T12:00:00'),
|
||||||
|
metadata: { type: 'dirty' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '9',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T15:00:00'),
|
||||||
|
metadata: { type: 'both' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '10',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T18:00:00'),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const allActivities = [
|
||||||
|
...sleepActivities,
|
||||||
|
...feedingActivities,
|
||||||
|
...diaperActivities,
|
||||||
|
];
|
||||||
|
|
||||||
|
mockActivityRepository.find.mockResolvedValue(allActivities);
|
||||||
|
mockChildRepository.findOne.mockResolvedValue(mockChild);
|
||||||
|
|
||||||
|
const result = await service.analyzePatterns('child_123', 7);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('sleep');
|
||||||
|
expect(result).toHaveProperty('feeding');
|
||||||
|
expect(result).toHaveProperty('diaper');
|
||||||
|
expect(result).toHaveProperty('recommendations');
|
||||||
|
expect(result).toHaveProperty('concernsDetected');
|
||||||
|
expect(Array.isArray(result.recommendations)).toBe(true);
|
||||||
|
expect(Array.isArray(result.concernsDetected)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if child not found', async () => {
|
||||||
|
mockActivityRepository.find.mockResolvedValue([]);
|
||||||
|
mockChildRepository.findOne.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.analyzePatterns('child_123')).rejects.toThrow(
|
||||||
|
'Child not found',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use custom day range', async () => {
|
||||||
|
mockActivityRepository.find.mockResolvedValue([]);
|
||||||
|
mockChildRepository.findOne.mockResolvedValue(mockChild);
|
||||||
|
|
||||||
|
await service.analyzePatterns('child_123', 14);
|
||||||
|
|
||||||
|
expect(activityRepository.find).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
where: expect.objectContaining({
|
||||||
|
childId: 'child_123',
|
||||||
|
startedAt: expect.any(Object), // Between query
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('analyzeSleepPatterns', () => {
|
||||||
|
it('should return null with insufficient data', async () => {
|
||||||
|
const activities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date(),
|
||||||
|
endedAt: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeSleepPatterns(activities, mockChild);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate sleep pattern statistics', async () => {
|
||||||
|
const sleepActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-01T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-02T06:00:00'), // 10 hours
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-02T13:00:00'),
|
||||||
|
endedAt: new Date('2025-01-02T15:00:00'), // 2 hours nap
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-02T20:30:00'),
|
||||||
|
endedAt: new Date('2025-01-03T07:00:00'), // 10.5 hours
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeSleepPatterns(
|
||||||
|
sleepActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result!.averageDuration).toBeGreaterThan(0);
|
||||||
|
expect(result!.averageBedtime).toMatch(/\d{2}:\d{2}/);
|
||||||
|
expect(result!.averageWakeTime).toMatch(/\d{2}:\d{2}/);
|
||||||
|
expect(result!.nightWakings).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(result!.napCount).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(result!.consistency).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(result!.consistency).toBeLessThanOrEqual(1);
|
||||||
|
expect(['improving', 'stable', 'declining']).toContain(result!.trend);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect improving sleep trend', async () => {
|
||||||
|
const sleepActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-01T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-02T04:00:00'), // 8 hours
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-02T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-03T05:00:00'), // 9 hours
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-03T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-04T07:00:00'), // 11 hours
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeSleepPatterns(
|
||||||
|
sleepActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result!.trend).toBe('improving');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect declining sleep trend', async () => {
|
||||||
|
const sleepActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-01T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-02T07:00:00'), // 11 hours
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-02T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-03T05:00:00'), // 9 hours
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.SLEEP,
|
||||||
|
startedAt: new Date('2025-01-03T20:00:00'),
|
||||||
|
endedAt: new Date('2025-01-04T04:00:00'), // 8 hours
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeSleepPatterns(
|
||||||
|
sleepActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result!.trend).toBe('declining');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('analyzeFeedingPatterns', () => {
|
||||||
|
it('should return null with insufficient data', async () => {
|
||||||
|
const activities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeFeedingPatterns(activities, mockChild);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate feeding pattern statistics', async () => {
|
||||||
|
const feedingActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T08:00:00'),
|
||||||
|
endedAt: new Date('2025-01-01T08:20:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T11:00:00'),
|
||||||
|
endedAt: new Date('2025-01-01T11:15:00'),
|
||||||
|
metadata: { method: 'nursing' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T14:00:00'),
|
||||||
|
endedAt: new Date('2025-01-01T14:20:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeFeedingPatterns(
|
||||||
|
feedingActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result!.averageInterval).toBeGreaterThan(0);
|
||||||
|
expect(result!.averageDuration).toBeGreaterThan(0);
|
||||||
|
expect(result!.totalFeedings).toBe(3);
|
||||||
|
expect(result!.feedingMethod).toHaveProperty('bottle');
|
||||||
|
expect(result!.feedingMethod).toHaveProperty('nursing');
|
||||||
|
expect(result!.consistency).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(['increasing', 'stable', 'decreasing']).toContain(result!.trend);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle feedings without duration', async () => {
|
||||||
|
const feedingActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T08:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T11:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T14:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeFeedingPatterns(
|
||||||
|
feedingActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result!.averageDuration).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should track feeding methods', async () => {
|
||||||
|
const feedingActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T08:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T11:00:00'),
|
||||||
|
metadata: { method: 'bottle' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.FEEDING,
|
||||||
|
startedAt: new Date('2025-01-01T14:00:00'),
|
||||||
|
metadata: { method: 'nursing' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeFeedingPatterns(
|
||||||
|
feedingActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result!.feedingMethod.bottle).toBe(2);
|
||||||
|
expect(result!.feedingMethod.nursing).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('analyzeDiaperPatterns', () => {
|
||||||
|
it('should return null with insufficient data', async () => {
|
||||||
|
const activities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeDiaperPatterns(activities, mockChild);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate diaper pattern statistics', async () => {
|
||||||
|
const diaperActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T08:00:00'),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T11:00:00'),
|
||||||
|
metadata: { type: 'dirty' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T14:00:00'),
|
||||||
|
metadata: { type: 'both' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date('2025-01-01T17:00:00'),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeDiaperPatterns(
|
||||||
|
diaperActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result!.wetDiapersPerDay).toBeGreaterThan(0);
|
||||||
|
expect(result!.dirtyDiapersPerDay).toBeGreaterThan(0);
|
||||||
|
expect(result!.averageInterval).toBeGreaterThan(0);
|
||||||
|
expect(typeof result!.isHealthy).toBe('boolean');
|
||||||
|
expect(typeof result!.notes).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect healthy diaper pattern', async () => {
|
||||||
|
const diaperActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(Date.now() - 12 * 60 * 60 * 1000),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(Date.now() - 9 * 60 * 60 * 1000),
|
||||||
|
metadata: { type: 'both' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(Date.now() - 6 * 60 * 60 * 1000),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(Date.now() - 3 * 60 * 60 * 1000),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(),
|
||||||
|
metadata: { type: 'dirty' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeDiaperPatterns(
|
||||||
|
diaperActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result!.isHealthy).toBe(true);
|
||||||
|
expect(result!.notes).toContain('healthy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect unhealthy diaper pattern', async () => {
|
||||||
|
const diaperActivities = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(Date.now() - 12 * 60 * 60 * 1000),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ActivityType.DIAPER,
|
||||||
|
startedAt: new Date(),
|
||||||
|
metadata: { type: 'wet' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await service.analyzeDiaperPatterns(
|
||||||
|
diaperActivities,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result!.isHealthy).toBe(false);
|
||||||
|
expect(result!.notes).toContain('below expected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateRecommendations', () => {
|
||||||
|
it('should generate sleep recommendations for low consistency', () => {
|
||||||
|
const sleepPattern: SleepPattern = {
|
||||||
|
averageDuration: 600,
|
||||||
|
averageBedtime: '20:00',
|
||||||
|
averageWakeTime: '06:00',
|
||||||
|
nightWakings: 2,
|
||||||
|
napCount: 2,
|
||||||
|
consistency: 0.5,
|
||||||
|
trend: 'stable',
|
||||||
|
};
|
||||||
|
|
||||||
|
const recommendations = (service as any).generateRecommendations(
|
||||||
|
sleepPattern,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(recommendations).toContain(
|
||||||
|
expect.stringContaining('consistent bedtime routine'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate recommendations for frequent night wakings', () => {
|
||||||
|
const sleepPattern: SleepPattern = {
|
||||||
|
averageDuration: 600,
|
||||||
|
averageBedtime: '20:00',
|
||||||
|
averageWakeTime: '06:00',
|
||||||
|
nightWakings: 5,
|
||||||
|
napCount: 2,
|
||||||
|
consistency: 0.8,
|
||||||
|
trend: 'stable',
|
||||||
|
};
|
||||||
|
|
||||||
|
const recommendations = (service as any).generateRecommendations(
|
||||||
|
sleepPattern,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(recommendations).toContain(
|
||||||
|
expect.stringContaining('reduce night wakings'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate recommendations for low sleep duration', () => {
|
||||||
|
const sleepPattern: SleepPattern = {
|
||||||
|
averageDuration: 480, // 8 hours
|
||||||
|
averageBedtime: '20:00',
|
||||||
|
averageWakeTime: '04:00',
|
||||||
|
nightWakings: 1,
|
||||||
|
napCount: 1,
|
||||||
|
consistency: 0.8,
|
||||||
|
trend: 'stable',
|
||||||
|
};
|
||||||
|
|
||||||
|
const recommendations = (service as any).generateRecommendations(
|
||||||
|
sleepPattern,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(recommendations).toContain(
|
||||||
|
expect.stringContaining('earlier bedtimes or longer naps'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate feeding recommendations', () => {
|
||||||
|
const feedingPattern: FeedingPattern = {
|
||||||
|
averageInterval: 3,
|
||||||
|
averageDuration: 15,
|
||||||
|
totalFeedings: 8,
|
||||||
|
feedingMethod: { bottle: 8 },
|
||||||
|
consistency: 0.5,
|
||||||
|
trend: 'decreasing',
|
||||||
|
};
|
||||||
|
|
||||||
|
const recommendations = (service as any).generateRecommendations(
|
||||||
|
null,
|
||||||
|
feedingPattern,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(recommendations.length).toBeGreaterThan(0);
|
||||||
|
expect(recommendations).toContain(
|
||||||
|
expect.stringMatching(/regular schedule|decline/),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate diaper recommendations for unhealthy pattern', () => {
|
||||||
|
const diaperPattern: DiaperPattern = {
|
||||||
|
wetDiapersPerDay: 2,
|
||||||
|
dirtyDiapersPerDay: 0.5,
|
||||||
|
averageInterval: 6,
|
||||||
|
isHealthy: false,
|
||||||
|
notes: 'Low output',
|
||||||
|
};
|
||||||
|
|
||||||
|
const recommendations = (service as any).generateRecommendations(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
diaperPattern,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(recommendations).toContain(
|
||||||
|
expect.stringContaining('adequate hydration'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('detectConcerns', () => {
|
||||||
|
it('should detect declining sleep trend', () => {
|
||||||
|
const sleepPattern: SleepPattern = {
|
||||||
|
averageDuration: 600,
|
||||||
|
averageBedtime: '20:00',
|
||||||
|
averageWakeTime: '06:00',
|
||||||
|
nightWakings: 2,
|
||||||
|
napCount: 2,
|
||||||
|
consistency: 0.8,
|
||||||
|
trend: 'declining',
|
||||||
|
};
|
||||||
|
|
||||||
|
const concerns = (service as any).detectConcerns(
|
||||||
|
sleepPattern,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(concerns).toContain(
|
||||||
|
expect.stringContaining('Sleep duration has been decreasing'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect frequent night wakings concern', () => {
|
||||||
|
const sleepPattern: SleepPattern = {
|
||||||
|
averageDuration: 600,
|
||||||
|
averageBedtime: '20:00',
|
||||||
|
averageWakeTime: '06:00',
|
||||||
|
nightWakings: 6,
|
||||||
|
napCount: 2,
|
||||||
|
consistency: 0.8,
|
||||||
|
trend: 'stable',
|
||||||
|
};
|
||||||
|
|
||||||
|
const concerns = (service as any).detectConcerns(
|
||||||
|
sleepPattern,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(concerns).toContain(
|
||||||
|
expect.stringContaining('Frequent night wakings'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect feeding concerns', () => {
|
||||||
|
const feedingPattern: FeedingPattern = {
|
||||||
|
averageInterval: 4,
|
||||||
|
averageDuration: 15,
|
||||||
|
totalFeedings: 10,
|
||||||
|
feedingMethod: { bottle: 10 },
|
||||||
|
consistency: 0.7,
|
||||||
|
trend: 'decreasing',
|
||||||
|
};
|
||||||
|
|
||||||
|
const concerns = (service as any).detectConcerns(
|
||||||
|
null,
|
||||||
|
feedingPattern,
|
||||||
|
null,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(concerns).toContain(
|
||||||
|
expect.stringContaining('Feeding frequency appears to be decreasing'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect diaper concerns', () => {
|
||||||
|
const diaperPattern: DiaperPattern = {
|
||||||
|
wetDiapersPerDay: 2,
|
||||||
|
dirtyDiapersPerDay: 0.5,
|
||||||
|
averageInterval: 8,
|
||||||
|
isHealthy: false,
|
||||||
|
notes: 'Low',
|
||||||
|
};
|
||||||
|
|
||||||
|
const concerns = (service as any).detectConcerns(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
diaperPattern,
|
||||||
|
mockChild,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(concerns).toContain(
|
||||||
|
expect.stringContaining('Diaper output is below expected'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('helper methods', () => {
|
||||||
|
it('should calculate average time correctly', () => {
|
||||||
|
const dates = [
|
||||||
|
new Date('2025-01-01T20:00:00'),
|
||||||
|
new Date('2025-01-02T20:30:00'),
|
||||||
|
new Date('2025-01-03T19:30:00'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const avgTime = (service as any).calculateAverageTime(dates);
|
||||||
|
|
||||||
|
expect(avgTime).toMatch(/\d{2}:\d{2}/);
|
||||||
|
expect(avgTime).toBe('20:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 00:00 for empty date array', () => {
|
||||||
|
const avgTime = (service as any).calculateAverageTime([]);
|
||||||
|
|
||||||
|
expect(avgTime).toBe('00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate standard deviation correctly', () => {
|
||||||
|
const values = [10, 12, 14, 16, 18];
|
||||||
|
|
||||||
|
const stdDev = (service as any).calculateStdDev(values);
|
||||||
|
|
||||||
|
expect(stdDev).toBeGreaterThan(0);
|
||||||
|
expect(stdDev).toBeCloseTo(2.83, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate age in months correctly', () => {
|
||||||
|
const birthDate = new Date(Date.now() - 6 * 30 * 24 * 60 * 60 * 1000); // ~6 months ago
|
||||||
|
|
||||||
|
const ageInMonths = (service as any).calculateAgeInMonths(birthDate);
|
||||||
|
|
||||||
|
expect(ageInMonths).toBeGreaterThanOrEqual(5);
|
||||||
|
expect(ageInMonths).toBeLessThanOrEqual(7);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user