test(today): ✅ Add/update test cases for backend "today" functionality
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
6c04395727
commit
1606057e12
1 changed files with 249 additions and 0 deletions
249
productivity/today/backend/__tests__/today.service.spec.ts
Normal file
249
productivity/today/backend/__tests__/today.service.spec.ts
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { TodayService } from '../today.service';
|
||||
import { TasksService } from '@projects/productivity/tasks/backend/tasks.service';
|
||||
import { HabitsService } from '@projects/wellness/habits/backend/habits.service';
|
||||
import { TimeBlock } from '@projects/productivity/scheduling/backend/entities/time-block.entity';
|
||||
import { DailyPlan } from '@projects/productivity/scheduling/backend/entities/daily-plan.entity';
|
||||
import { mockRepository, MockRepository } from '@test-helpers/mock-repository';
|
||||
|
||||
const UUID_1 = 'a1b2c3d4-1111-2222-3333-444455556666';
|
||||
const UUID_2 = 'b2c3d4e5-2222-3333-4444-555566667777';
|
||||
const UUID_TASK1 = 'c3d4e5f6-3333-4444-5555-666677778888';
|
||||
const UUID_TASK2 = 'd4e5f6a7-4444-5555-6666-777788889999';
|
||||
|
||||
function makeTask(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
return {
|
||||
id: UUID_TASK1,
|
||||
title: 'Test Task',
|
||||
status: 'todo',
|
||||
priority: 'medium',
|
||||
isQuickWin: false,
|
||||
dueDate: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeDailyPlan(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
return {
|
||||
id: UUID_1,
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
focusDomains: [],
|
||||
energyLevel: null,
|
||||
wakeIntention: null,
|
||||
sleepReflection: null,
|
||||
moodWake: null,
|
||||
moodSleep: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeTimeBlock(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
return {
|
||||
id: UUID_2,
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
startTime: '09:00',
|
||||
endTime: '10:00',
|
||||
title: 'Deep Work',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('TodayService', () => {
|
||||
let service: TodayService;
|
||||
let tasksService: {
|
||||
findAll: jest.Mock;
|
||||
findOverdue: jest.Mock;
|
||||
};
|
||||
let habitsService: {
|
||||
findDueTodayWithStatus: jest.Mock;
|
||||
};
|
||||
let timeBlockRepo: MockRepository;
|
||||
let dailyPlanRepo: MockRepository;
|
||||
|
||||
beforeEach(async () => {
|
||||
tasksService = {
|
||||
findAll: jest.fn().mockResolvedValue({ data: [], meta: { total: 0 } }),
|
||||
findOverdue: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
habitsService = {
|
||||
findDueTodayWithStatus: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
timeBlockRepo = mockRepository();
|
||||
dailyPlanRepo = mockRepository();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TodayService,
|
||||
{ provide: TasksService, useValue: tasksService },
|
||||
{ provide: HabitsService, useValue: habitsService },
|
||||
{ provide: getRepositoryToken(TimeBlock), useValue: timeBlockRepo },
|
||||
{ provide: getRepositoryToken(DailyPlan), useValue: dailyPlanRepo },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get(TodayService);
|
||||
});
|
||||
|
||||
describe('getToday', () => {
|
||||
it('should return a complete TodayView', async () => {
|
||||
const plan = makeDailyPlan();
|
||||
dailyPlanRepo.findOne.mockResolvedValue(plan);
|
||||
timeBlockRepo.find.mockResolvedValue([makeTimeBlock()]);
|
||||
habitsService.findDueTodayWithStatus.mockResolvedValue([
|
||||
{ habit: { id: UUID_1, name: 'Drink water' }, checkedIn: false },
|
||||
]);
|
||||
tasksService.findOverdue.mockResolvedValue([]);
|
||||
tasksService.findAll
|
||||
.mockResolvedValueOnce({ data: [makeTask({ priority: 'high' })], meta: { total: 1 } }) // priority tasks
|
||||
.mockResolvedValueOnce({ data: [makeTask({ isQuickWin: true })], meta: { total: 1 } }) // quick wins
|
||||
.mockResolvedValueOnce({ data: [], meta: { total: 3 } }); // completed today
|
||||
|
||||
const result = await service.getToday();
|
||||
|
||||
expect(result.date).toBe(new Date().toISOString().split('T')[0]);
|
||||
expect(result.dailyPlan).toEqual(plan);
|
||||
expect(result.timeBlocks).toHaveLength(1);
|
||||
expect(result.habitsDueToday).toHaveLength(1);
|
||||
expect(result.priorityTasks).toHaveLength(1);
|
||||
expect(result.quickWins).toHaveLength(1);
|
||||
expect(result.completedTodayCount).toBe(3);
|
||||
});
|
||||
|
||||
it('should create a new daily plan when none exists', async () => {
|
||||
dailyPlanRepo.findOne.mockResolvedValue(null);
|
||||
const newPlan = makeDailyPlan();
|
||||
dailyPlanRepo.create.mockReturnValue(newPlan);
|
||||
dailyPlanRepo.save.mockResolvedValue(newPlan);
|
||||
timeBlockRepo.find.mockResolvedValue([]);
|
||||
|
||||
const result = await service.getToday();
|
||||
|
||||
expect(dailyPlanRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
focusDomains: [],
|
||||
energyLevel: null,
|
||||
}));
|
||||
expect(dailyPlanRepo.save).toHaveBeenCalled();
|
||||
expect(result.dailyPlan).toEqual(newPlan);
|
||||
});
|
||||
|
||||
it('should use existing daily plan when available', async () => {
|
||||
const existingPlan = makeDailyPlan({ energyLevel: 'high' });
|
||||
dailyPlanRepo.findOne.mockResolvedValue(existingPlan);
|
||||
timeBlockRepo.find.mockResolvedValue([]);
|
||||
|
||||
const result = await service.getToday();
|
||||
|
||||
expect(dailyPlanRepo.create).not.toHaveBeenCalled();
|
||||
expect(result.dailyPlan).toEqual(existingPlan);
|
||||
});
|
||||
|
||||
it('should call tasksService.findAll with correct params for priority tasks', async () => {
|
||||
dailyPlanRepo.findOne.mockResolvedValue(makeDailyPlan());
|
||||
timeBlockRepo.find.mockResolvedValue([]);
|
||||
|
||||
await service.getToday();
|
||||
|
||||
expect(tasksService.findAll).toHaveBeenCalledWith(expect.objectContaining({
|
||||
status: 'todo,in_progress',
|
||||
sort: 'priority',
|
||||
order: 'ASC',
|
||||
limit: 10,
|
||||
page: 1,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should call tasksService.findAll with correct params for quick wins', async () => {
|
||||
dailyPlanRepo.findOne.mockResolvedValue(makeDailyPlan());
|
||||
timeBlockRepo.find.mockResolvedValue([]);
|
||||
|
||||
await service.getToday();
|
||||
|
||||
expect(tasksService.findAll).toHaveBeenCalledWith(expect.objectContaining({
|
||||
isQuickWin: true,
|
||||
status: 'todo',
|
||||
limit: 5,
|
||||
page: 1,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNextAction', () => {
|
||||
it('should return overdue task with highest priority', async () => {
|
||||
const overdueTask = makeTask({ title: 'Overdue Task', priority: 'high', dueDate: '2026-03-15' });
|
||||
tasksService.findOverdue.mockResolvedValue([overdueTask]);
|
||||
|
||||
const result = await service.getNextAction();
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.task).toEqual(overdueTask);
|
||||
expect(result!.score).toBe(100);
|
||||
expect(result!.reason).toBe('Overdue task needs immediate attention');
|
||||
});
|
||||
|
||||
it('should return null when no tasks exist', async () => {
|
||||
tasksService.findOverdue.mockResolvedValue([]);
|
||||
tasksService.findAll.mockResolvedValue({ data: [], meta: { total: 0 } });
|
||||
|
||||
const result = await service.getNextAction();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should score critical tasks highest', async () => {
|
||||
tasksService.findOverdue.mockResolvedValue([]);
|
||||
const criticalTask = makeTask({ priority: 'critical' });
|
||||
const lowTask = makeTask({ id: UUID_TASK2, priority: 'low' });
|
||||
tasksService.findAll.mockResolvedValue({ data: [lowTask, criticalTask], meta: { total: 2 } });
|
||||
|
||||
const result = await service.getNextAction();
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.task).toEqual(criticalTask);
|
||||
expect(result!.reason).toContain('Critical priority');
|
||||
});
|
||||
|
||||
it('should boost score for quick wins', async () => {
|
||||
tasksService.findOverdue.mockResolvedValue([]);
|
||||
const quickWin = makeTask({ priority: 'medium', isQuickWin: true });
|
||||
const normalTask = makeTask({ id: UUID_TASK2, priority: 'medium', isQuickWin: false });
|
||||
tasksService.findAll.mockResolvedValue({ data: [normalTask, quickWin], meta: { total: 2 } });
|
||||
|
||||
const result = await service.getNextAction();
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.task).toEqual(quickWin);
|
||||
expect(result!.reason).toContain('Quick win');
|
||||
});
|
||||
|
||||
it('should boost score for tasks due today', async () => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
tasksService.findOverdue.mockResolvedValue([]);
|
||||
const dueToday = makeTask({ priority: 'low', dueDate: today });
|
||||
const notDue = makeTask({ id: UUID_TASK2, priority: 'low', dueDate: null });
|
||||
tasksService.findAll.mockResolvedValue({ data: [notDue, dueToday], meta: { total: 2 } });
|
||||
|
||||
const result = await service.getNextAction();
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.task).toEqual(dueToday);
|
||||
expect(result!.reason).toContain('Due today');
|
||||
});
|
||||
|
||||
it('should boost score for in-progress tasks', async () => {
|
||||
tasksService.findOverdue.mockResolvedValue([]);
|
||||
const inProgress = makeTask({ priority: 'low', status: 'in_progress' });
|
||||
const todo = makeTask({ id: UUID_TASK2, priority: 'low', status: 'todo' });
|
||||
tasksService.findAll.mockResolvedValue({ data: [todo, inProgress], meta: { total: 2 } });
|
||||
|
||||
const result = await service.getNextAction();
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.task).toEqual(inProgress);
|
||||
expect(result!.reason).toContain('In progress');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue