tududi/backend/tests/integration/tasks-metrics.test.js
Chris e73c354e7e
Fix bug 733 (#735)
* Refactor today

* fixup! Refactor today

* fixup! fixup! Refactor today
2025-12-27 21:00:52 +02:00

211 lines
7.6 KiB
JavaScript

const { Task, Project } = require('../../models');
const { createTestUser } = require('../helpers/testUtils');
const {
getTaskMetrics,
} = require('../../routes/tasks/queries/metrics-computation');
const dayFromNow = (days) => new Date(Date.now() + days * 24 * 60 * 60 * 1000);
describe('Task Metrics Suggested Tasks', () => {
let user;
const createTask = async (overrides = {}) => {
const { priority, status, ...rest } = overrides;
return await Task.create({
name: rest.name || 'Suggested task',
user_id: user.id,
status:
typeof status === 'string'
? Task.getStatusValue(status)
: (status ?? Task.STATUS.NOT_STARTED),
today: false,
priority:
typeof priority === 'string'
? Task.getPriorityValue(priority)
: (priority ?? Task.PRIORITY.LOW),
parent_task_id: null,
recurring_parent_id: null,
...rest,
});
};
beforeEach(async () => {
user = await createTestUser({ email: 'metrics@example.com' });
});
it('orders suggested tasks by priority, due date, and project', async () => {
const projectAlpha = await Project.create({
name: 'Alpha Project',
user_id: user.id,
});
const projectBeta = await Project.create({
name: 'Beta Project',
user_id: user.id,
});
await createTask({
name: 'High Due Later',
priority: 'high',
due_date: dayFromNow(3),
project_id: projectBeta.id,
});
await createTask({
name: 'High Due Soon',
priority: 'high',
due_date: dayFromNow(1),
project_id: projectAlpha.id,
});
await createTask({
name: 'Medium Alpha',
priority: 'medium',
due_date: dayFromNow(4),
project_id: projectAlpha.id,
});
await createTask({
name: 'Medium Beta',
priority: 'medium',
due_date: dayFromNow(4),
project_id: projectBeta.id,
});
const metrics = await getTaskMetrics(user.id, 'UTC');
const names = metrics.suggested_tasks.map((task) => task.name);
expect(names).toEqual([
'High Due Soon',
'High Due Later',
'Medium Alpha',
'Medium Beta',
]);
});
it('excludes tasks deferred into the future from suggested results', async () => {
await createTask({ name: 'Ready Task', priority: 'high' });
await createTask({
name: 'Deferred Past Task',
defer_until: dayFromNow(-1),
});
await createTask({
name: 'Deferred Future Task',
defer_until: dayFromNow(2),
});
const metrics = await getTaskMetrics(user.id, 'UTC');
const names = metrics.suggested_tasks.map((task) => task.name);
expect(names).toContain('Ready Task');
expect(names).toContain('Deferred Past Task');
expect(names).not.toContain('Deferred Future Task');
});
it('excludes overdue tasks from suggested results (they show in Overdue section)', async () => {
// Need at least 3 open tasks for suggested to compute
await createTask({ name: 'Ready Task 1', priority: 'high' });
await createTask({ name: 'Ready Task 2', priority: 'medium' });
await createTask({ name: 'Ready Task 3', priority: 'low' });
await createTask({
name: 'Overdue Task',
due_date: dayFromNow(-3),
});
const metrics = await getTaskMetrics(user.id, 'UTC');
const suggestedNames = metrics.suggested_tasks.map((task) => task.name);
const overdueNames = metrics.tasks_overdue.map((task) => task.name);
// Overdue task should be in overdue section, not suggested
expect(overdueNames).toContain('Overdue Task');
expect(suggestedNames).not.toContain('Overdue Task');
});
});
describe('Task Metrics Overdue and Due Today Tasks', () => {
let user;
const createTask = async (overrides = {}) => {
const { priority, status, ...rest } = overrides;
return await Task.create({
name: rest.name || 'Test task',
user_id: user.id,
status:
typeof status === 'string'
? Task.getStatusValue(status)
: (status ?? Task.STATUS.NOT_STARTED),
priority:
typeof priority === 'string'
? Task.getPriorityValue(priority)
: (priority ?? Task.PRIORITY.LOW),
parent_task_id: null,
recurring_parent_id: null,
...rest,
});
};
beforeEach(async () => {
user = await createTestUser({ email: 'overdue-test@example.com' });
});
it('excludes overdue tasks with active status from tasks_overdue (they show in Planned)', async () => {
// Create an overdue task with IN_PROGRESS status (shows in Planned section)
await createTask({
name: 'Overdue In Progress',
due_date: dayFromNow(-3),
status: Task.STATUS.IN_PROGRESS,
});
// Create a regular overdue task with NOT_STARTED status
await createTask({
name: 'Regular Overdue Task',
due_date: dayFromNow(-2),
status: Task.STATUS.NOT_STARTED,
});
const metrics = await getTaskMetrics(user.id, 'UTC');
const overdueNames = metrics.tasks_overdue.map((task) => task.name);
// IN_PROGRESS task should NOT be in overdue (it's in Planned section)
expect(overdueNames).not.toContain('Overdue In Progress');
// NOT_STARTED task should be in overdue
expect(overdueNames).toContain('Regular Overdue Task');
});
it('excludes due today tasks with active status from tasks_due_today (they show in Planned)', async () => {
const today = new Date();
today.setHours(12, 0, 0, 0);
// Create a task due today with PLANNED status (shows in Planned section)
await createTask({
name: 'Due Today Planned',
due_date: today,
status: Task.STATUS.PLANNED,
});
// Create a regular due today task with NOT_STARTED status
await createTask({
name: 'Regular Due Today',
due_date: today,
status: Task.STATUS.NOT_STARTED,
});
const metrics = await getTaskMetrics(user.id, 'UTC');
const dueTodayNames = metrics.tasks_due_today.map((task) => task.name);
// PLANNED task should NOT be in due today (it's in Planned section)
expect(dueTodayNames).not.toContain('Due Today Planned');
// NOT_STARTED task should be in due today
expect(dueTodayNames).toContain('Regular Due Today');
});
it('includes tasks with WAITING status in Planned section, not in overdue', async () => {
await createTask({
name: 'Overdue Waiting',
due_date: dayFromNow(-1),
status: Task.STATUS.WAITING,
});
const metrics = await getTaskMetrics(user.id, 'UTC');
const overdueNames = metrics.tasks_overdue.map((task) => task.name);
const todayPlanNames = metrics.tasks_today_plan.map(
(task) => task.name
);
// WAITING task should be in today plan, not in overdue
expect(overdueNames).not.toContain('Overdue Waiting');
expect(todayPlanNames).toContain('Overdue Waiting');
});
});