mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
tests: fix
This commit is contained in:
parent
e673d74b55
commit
97d59ffcb4
5 changed files with 1009 additions and 5 deletions
|
|
@ -0,0 +1,569 @@
|
|||
import { createScheduleDays } from './create-schedule-days';
|
||||
import { DEFAULT_TASK, TaskWithoutReminder } from '../../tasks/task.model';
|
||||
import { PlannerDayMap } from '../../planner/planner.model';
|
||||
import { BlockedBlockByDayMap } from '../schedule.model';
|
||||
|
||||
// Helper function to create test tasks with required properties
|
||||
const createTestTask = (
|
||||
id: string,
|
||||
title: string,
|
||||
options: {
|
||||
timeEstimate?: number;
|
||||
timeSpent?: number;
|
||||
dueDay?: string;
|
||||
dueWithTime?: number;
|
||||
} = {},
|
||||
): TaskWithoutReminder => {
|
||||
return {
|
||||
...DEFAULT_TASK,
|
||||
id,
|
||||
title,
|
||||
projectId: 'test-project',
|
||||
timeEstimate: options.timeEstimate ?? 3600000,
|
||||
timeSpent: options.timeSpent ?? 0,
|
||||
remindAt: undefined,
|
||||
...(options.dueDay && { dueDay: options.dueDay }),
|
||||
...(options.dueWithTime && { dueWithTime: options.dueWithTime }),
|
||||
} as TaskWithoutReminder;
|
||||
};
|
||||
|
||||
describe('createScheduleDays - Task Filtering', () => {
|
||||
let now: number;
|
||||
let realNow: number;
|
||||
let todayStr: string;
|
||||
let tomorrowStr: string;
|
||||
let nextWeekStr: string;
|
||||
let futureWeekStr: string;
|
||||
|
||||
beforeEach(() => {
|
||||
// Set up test dates
|
||||
const today = new Date(2026, 0, 20, 10, 0, 0); // Jan 20, 2026, 10:00 AM
|
||||
now = today.getTime();
|
||||
realNow = now;
|
||||
|
||||
todayStr = today.toISOString().split('T')[0];
|
||||
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
tomorrowStr = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
const nextWeek = new Date(today);
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
nextWeekStr = nextWeek.toISOString().split('T')[0];
|
||||
|
||||
const futureWeek = new Date(today);
|
||||
futureWeek.setDate(futureWeek.getDate() + 14);
|
||||
futureWeekStr = futureWeek.toISOString().split('T')[0];
|
||||
});
|
||||
|
||||
describe('Unscheduled tasks (no dueDay, no dueWithTime, no plannedForDay)', () => {
|
||||
it('should appear in current week when viewing today', () => {
|
||||
// Arrange
|
||||
const unscheduledTask = createTestTask('task1', 'Unscheduled Task');
|
||||
|
||||
const dayDates = [todayStr, tomorrowStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[unscheduledTask],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
// Unscheduled task should appear in the schedule
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task1'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
|
||||
it('should NOT appear when viewing next week (outside current week)', () => {
|
||||
// Arrange
|
||||
const unscheduledTask: TaskWithoutReminder = {
|
||||
id: 'task1',
|
||||
title: 'Unscheduled Task',
|
||||
timeEstimate: 3600000,
|
||||
timeSpent: 0,
|
||||
} as TaskWithoutReminder;
|
||||
|
||||
// Viewing a week starting 7 days from now
|
||||
const dayDates = [nextWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[unscheduledTask],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
// Unscheduled task should NOT appear when viewing future week
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task1'),
|
||||
);
|
||||
expect(hasTask).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tasks with dueDay', () => {
|
||||
it('should be filtered out when viewing next week if dueDay is today', () => {
|
||||
// Arrange
|
||||
const taskWithDueToday = createTestTask('task2', 'Task Due Today', {
|
||||
dueDay: todayStr,
|
||||
});
|
||||
|
||||
// Viewing next week
|
||||
const dayDates = [nextWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskWithDueToday],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task2'),
|
||||
);
|
||||
expect(hasTask).toBe(false);
|
||||
});
|
||||
|
||||
it('should appear when viewing a week that includes the dueDay', () => {
|
||||
// Arrange
|
||||
const taskDueNextWeek = createTestTask('task3', 'Task Due Next Week', {
|
||||
dueDay: nextWeekStr,
|
||||
});
|
||||
|
||||
// Viewing next week
|
||||
const dayDates = [nextWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskDueNextWeek],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task3'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
|
||||
it('should appear when dueDay is in the future relative to viewed week', () => {
|
||||
// Arrange
|
||||
const taskDueFutureWeek = createTestTask('task4', 'Task Due Future Week', {
|
||||
dueDay: futureWeekStr,
|
||||
});
|
||||
|
||||
// Viewing next week (before the due date)
|
||||
const dayDates = [nextWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskDueFutureWeek],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task4'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
|
||||
it('should NOT appear when dueDay is before the viewed week', () => {
|
||||
// Arrange
|
||||
const taskDueToday = createTestTask('task5', 'Task Due Today', {
|
||||
dueDay: todayStr,
|
||||
});
|
||||
|
||||
// Viewing future week (2 weeks ahead)
|
||||
const dayDates = [futureWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskDueToday],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task5'),
|
||||
);
|
||||
expect(hasTask).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tasks with plannedForDay', () => {
|
||||
it('should always appear on their planned day regardless of viewing week', () => {
|
||||
// Arrange
|
||||
const taskPlannedForNextWeek = createTestTask('task6', 'Task Planned Next Week');
|
||||
|
||||
// Planned for next week
|
||||
const plannerDayMap: PlannerDayMap = {
|
||||
[nextWeekStr]: [taskPlannedForNextWeek],
|
||||
};
|
||||
const dayDates = [nextWeekStr];
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some(
|
||||
(day) =>
|
||||
day.dayDate === nextWeekStr &&
|
||||
day.entries.some((entry) => entry.id === 'task6'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
|
||||
it('should appear on planned day even when viewing outside current week', () => {
|
||||
// Arrange
|
||||
const taskPlannedForFutureWeek = createTestTask(
|
||||
'task7',
|
||||
'Task Planned Future Week',
|
||||
);
|
||||
|
||||
// Planned for future week
|
||||
const plannerDayMap: PlannerDayMap = {
|
||||
[futureWeekStr]: [taskPlannedForFutureWeek],
|
||||
};
|
||||
const dayDates = [futureWeekStr];
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some(
|
||||
(day) =>
|
||||
day.dayDate === futureWeekStr &&
|
||||
day.entries.some((entry) => entry.id === 'task7'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initial filter when first day is outside current week', () => {
|
||||
it('should filter out unscheduled tasks before processing when first day is outside current week', () => {
|
||||
// Arrange
|
||||
const unscheduledTask = createTestTask('task8', 'Unscheduled Task');
|
||||
|
||||
const taskWithDueInFuture = createTestTask('task9', 'Task Due Future', {
|
||||
dueDay: futureWeekStr,
|
||||
});
|
||||
|
||||
// Viewing future week (outside current week)
|
||||
const dayDates = [futureWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[unscheduledTask, taskWithDueInFuture],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasUnscheduledTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task8'),
|
||||
);
|
||||
const hasTaskWithDue = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task9'),
|
||||
);
|
||||
expect(hasUnscheduledTask).toBe(false);
|
||||
expect(hasTaskWithDue).toBe(true);
|
||||
});
|
||||
|
||||
it('should keep tasks with plannedForDay even when first day is outside current week', () => {
|
||||
// Arrange
|
||||
const taskPlannedForFuture = createTestTask('task10', 'Task Planned for Future');
|
||||
|
||||
const plannerDayMap: PlannerDayMap = {
|
||||
[futureWeekStr]: [taskPlannedForFuture],
|
||||
};
|
||||
const dayDates = [futureWeekStr];
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task10'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Per-day filter for tasks flowing from previous day', () => {
|
||||
it('should filter tasks between days when viewing outside current week', () => {
|
||||
// Arrange
|
||||
const taskDueOnFirstDay = createTestTask('task11', 'Task Due on First Day', {
|
||||
dueDay: nextWeekStr,
|
||||
});
|
||||
|
||||
// Viewing two days in next week
|
||||
const secondDayNextWeek = new Date(nextWeekStr);
|
||||
secondDayNextWeek.setDate(secondDayNextWeek.getDate() + 1);
|
||||
const secondDayStr = secondDayNextWeek.toISOString().split('T')[0];
|
||||
|
||||
const dayDates = [nextWeekStr, secondDayStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskDueOnFirstDay],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
// Task should appear on first day
|
||||
const firstDayHasTask = result[0].entries.some((entry) => entry.id === 'task11');
|
||||
expect(firstDayHasTask).toBe(true);
|
||||
|
||||
// If task doesn't complete and flows to second day, check if filtering applies
|
||||
// (This depends on implementation details of budget and beyond budget logic)
|
||||
});
|
||||
});
|
||||
|
||||
describe('End-of-day filter for tasks flowing to next day', () => {
|
||||
it('should allow tasks with plannedForDay to flow to next day', () => {
|
||||
// Arrange
|
||||
const taskPlannedForToday = createTestTask('task12', 'Task Planned for Today', {
|
||||
timeEstimate: 86400000, // 24 hours - won't fit in one day
|
||||
});
|
||||
|
||||
const plannerDayMap: PlannerDayMap = {
|
||||
[todayStr]: [taskPlannedForToday],
|
||||
};
|
||||
const dayDates = [todayStr, tomorrowStr];
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
// Task should appear on today
|
||||
const todayHasTask = result[0].entries.some((entry) => entry.id === 'task12');
|
||||
expect(todayHasTask).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tasks with dueWithTime', () => {
|
||||
it('should appear when viewing a week that includes the dueWithTime', () => {
|
||||
// Arrange
|
||||
const nextWeekDate = new Date(nextWeekStr);
|
||||
nextWeekDate.setHours(14, 0, 0, 0); // 2 PM next week
|
||||
const taskWithDueTime = createTestTask('task13', 'Task With Due Time', {
|
||||
dueWithTime: nextWeekDate.getTime(),
|
||||
});
|
||||
|
||||
const dayDates = [nextWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskWithDueTime],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task13'),
|
||||
);
|
||||
expect(hasTask).toBe(true);
|
||||
});
|
||||
|
||||
it('should NOT appear when dueWithTime is before the viewed week', () => {
|
||||
// Arrange
|
||||
const todayDate = new Date(todayStr);
|
||||
todayDate.setHours(14, 0, 0, 0);
|
||||
const taskWithDueTime = createTestTask('task14', 'Task With Due Time Today', {
|
||||
dueWithTime: todayDate.getTime(),
|
||||
});
|
||||
|
||||
// Viewing future week
|
||||
const dayDates = [futureWeekStr];
|
||||
const plannerDayMap: PlannerDayMap = {};
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[taskWithDueTime],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasTask = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'task14'),
|
||||
);
|
||||
expect(hasTask).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mixed scenarios', () => {
|
||||
it('should correctly filter multiple tasks with different scheduling types when viewing future week', () => {
|
||||
// Arrange
|
||||
const unscheduledTask = createTestTask('unscheduled', 'Unscheduled');
|
||||
|
||||
const taskDueToday = createTestTask('dueToday', 'Due Today', {
|
||||
dueDay: todayStr,
|
||||
});
|
||||
|
||||
const taskDueNextWeek = createTestTask('dueNextWeek', 'Due Next Week', {
|
||||
dueDay: nextWeekStr,
|
||||
});
|
||||
|
||||
const taskPlannedNextWeek = createTestTask('plannedNextWeek', 'Planned Next Week');
|
||||
|
||||
const plannerDayMap: PlannerDayMap = {
|
||||
[nextWeekStr]: [taskPlannedNextWeek],
|
||||
};
|
||||
const dayDates = [nextWeekStr];
|
||||
const blockerBlocksDayMap: BlockedBlockByDayMap = {};
|
||||
|
||||
// Act
|
||||
const result = createScheduleDays(
|
||||
[unscheduledTask, taskDueToday, taskDueNextWeek],
|
||||
[],
|
||||
dayDates,
|
||||
plannerDayMap,
|
||||
blockerBlocksDayMap,
|
||||
undefined,
|
||||
now,
|
||||
realNow,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const hasUnscheduled = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'unscheduled'),
|
||||
);
|
||||
const hasDueToday = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'dueToday'),
|
||||
);
|
||||
const hasDueNextWeek = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'dueNextWeek'),
|
||||
);
|
||||
const hasPlannedNextWeek = result.some((day) =>
|
||||
day.entries.some((entry) => entry.id === 'plannedNextWeek'),
|
||||
);
|
||||
|
||||
expect(hasUnscheduled).toBe(false);
|
||||
expect(hasDueToday).toBe(false);
|
||||
expect(hasDueNextWeek).toBe(true);
|
||||
expect(hasPlannedNextWeek).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ScheduleMonthComponent } from './schedule-month.component';
|
||||
import { ScheduleService } from '../schedule.service';
|
||||
import { DateTimeFormatService } from '../../../core/date-time-format/date-time-format.service';
|
||||
|
||||
describe('ScheduleMonthComponent', () => {
|
||||
let component: ScheduleMonthComponent;
|
||||
let fixture: ComponentFixture<ScheduleMonthComponent>;
|
||||
let mockScheduleService: jasmine.SpyObj<ScheduleService>;
|
||||
let mockDateTimeFormatService: jasmine.SpyObj<DateTimeFormatService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockScheduleService = jasmine.createSpyObj('ScheduleService', [
|
||||
'getDayClass',
|
||||
'hasEventsForDay',
|
||||
'getEventsForDay',
|
||||
'getEventDayStr',
|
||||
]);
|
||||
mockScheduleService.getDayClass.and.returnValue('');
|
||||
mockScheduleService.hasEventsForDay.and.returnValue(false);
|
||||
mockScheduleService.getEventsForDay.and.returnValue([]);
|
||||
mockScheduleService.getEventDayStr.and.returnValue(null);
|
||||
|
||||
mockDateTimeFormatService = jasmine.createSpyObj('DateTimeFormatService', ['-'], {
|
||||
currentLocale: 'en-US',
|
||||
});
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ScheduleMonthComponent],
|
||||
providers: [
|
||||
{ provide: ScheduleService, useValue: mockScheduleService },
|
||||
{ provide: DateTimeFormatService, useValue: mockDateTimeFormatService },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ScheduleMonthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('referenceMonth computed', () => {
|
||||
it('should return current date when daysToShow is empty', () => {
|
||||
// Arrange
|
||||
fixture.componentRef.setInput('daysToShow', []);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.referenceMonth();
|
||||
|
||||
// Assert
|
||||
expect(result).toBeInstanceOf(Date);
|
||||
// Should be close to current date
|
||||
const now = new Date();
|
||||
expect(Math.abs(result.getTime() - now.getTime())).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
it('should use middle day from daysToShow as reference', () => {
|
||||
// Arrange - Create a month view for January 2026
|
||||
const days = [
|
||||
'2025-12-29', // Week 1 - padding from prev month
|
||||
'2025-12-30',
|
||||
'2025-12-31',
|
||||
'2026-01-01',
|
||||
'2026-01-02',
|
||||
'2026-01-03',
|
||||
'2026-01-04',
|
||||
'2026-01-05', // Week 2
|
||||
'2026-01-06',
|
||||
'2026-01-07',
|
||||
'2026-01-08',
|
||||
'2026-01-09',
|
||||
'2026-01-10',
|
||||
'2026-01-11',
|
||||
'2026-01-12', // Week 3 - Middle of month
|
||||
'2026-01-13',
|
||||
'2026-01-14', // Day 14 - near middle
|
||||
'2026-01-15', // Middle index (14/2 = 7, but floor(28/2) = 14)
|
||||
'2026-01-16',
|
||||
'2026-01-17',
|
||||
'2026-01-18',
|
||||
'2026-01-19', // Week 4
|
||||
'2026-01-20',
|
||||
'2026-01-21',
|
||||
'2026-01-22',
|
||||
'2026-01-23',
|
||||
'2026-01-24',
|
||||
'2026-01-25',
|
||||
];
|
||||
fixture.componentRef.setInput('daysToShow', days);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.referenceMonth();
|
||||
|
||||
// Assert
|
||||
// Middle index = floor(28/2) = 14, which is '2026-01-12'
|
||||
expect(result.getFullYear()).toBe(2026);
|
||||
expect(result.getMonth()).toBe(0); // January
|
||||
expect(result.getDate()).toBe(12);
|
||||
});
|
||||
|
||||
it('should handle a 5-week month view', () => {
|
||||
// Arrange - 35 days (5 weeks)
|
||||
const days: string[] = [];
|
||||
const startDate = new Date(2026, 0, 1); // Jan 1, 2026
|
||||
for (let i = 0; i < 35; i++) {
|
||||
const date = new Date(startDate);
|
||||
date.setDate(date.getDate() + i);
|
||||
days.push(date.toISOString().split('T')[0]);
|
||||
}
|
||||
fixture.componentRef.setInput('daysToShow', days);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.referenceMonth();
|
||||
|
||||
// Assert
|
||||
// Middle index = floor(35/2) = 17
|
||||
const middleDay = new Date(days[17]);
|
||||
expect(result.getFullYear()).toBe(middleDay.getFullYear());
|
||||
expect(result.getMonth()).toBe(middleDay.getMonth());
|
||||
expect(result.getDate()).toBe(middleDay.getDate());
|
||||
});
|
||||
|
||||
it('should handle a 6-week month view', () => {
|
||||
// Arrange - 42 days (6 weeks)
|
||||
const days: string[] = [];
|
||||
const startDate = new Date(2026, 0, 1);
|
||||
for (let i = 0; i < 42; i++) {
|
||||
const date = new Date(startDate);
|
||||
date.setDate(date.getDate() + i);
|
||||
days.push(date.toISOString().split('T')[0]);
|
||||
}
|
||||
fixture.componentRef.setInput('daysToShow', days);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.referenceMonth();
|
||||
|
||||
// Assert
|
||||
// Middle index = floor(42/2) = 21
|
||||
const middleDay = new Date(days[21]);
|
||||
expect(result.getFullYear()).toBe(middleDay.getFullYear());
|
||||
expect(result.getMonth()).toBe(middleDay.getMonth());
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDayClass', () => {
|
||||
it('should pass referenceMonth to service.getDayClass', () => {
|
||||
// Arrange
|
||||
const days = ['2026-01-15', '2026-01-16', '2026-01-17'];
|
||||
fixture.componentRef.setInput('daysToShow', days);
|
||||
fixture.detectChanges();
|
||||
mockScheduleService.getDayClass.calls.reset();
|
||||
|
||||
const testDay = '2026-01-15';
|
||||
|
||||
// Act
|
||||
component.getDayClass(testDay);
|
||||
|
||||
// Assert
|
||||
expect(mockScheduleService.getDayClass).toHaveBeenCalled();
|
||||
const args = mockScheduleService.getDayClass.calls.mostRecent().args;
|
||||
expect(args[0]).toBe(testDay);
|
||||
expect(args[1]).toBeInstanceOf(Date);
|
||||
// Reference month should be the middle day
|
||||
const referenceMonth = args[1] as Date;
|
||||
expect(referenceMonth.getFullYear()).toBe(2026);
|
||||
expect(referenceMonth.getMonth()).toBe(0); // January
|
||||
});
|
||||
|
||||
it('should return the class string from service', () => {
|
||||
// Arrange
|
||||
mockScheduleService.getDayClass.and.returnValue('test-class');
|
||||
const days = ['2026-01-15'];
|
||||
fixture.componentRef.setInput('daysToShow', days);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.getDayClass('2026-01-15');
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('test-class');
|
||||
});
|
||||
|
||||
it('should handle "other-month" class for padding days', () => {
|
||||
// Arrange
|
||||
mockScheduleService.getDayClass.and.callFake((day: string, ref?: Date) => {
|
||||
const dayDate = new Date(day);
|
||||
if (ref && dayDate.getMonth() !== ref.getMonth()) {
|
||||
return 'other-month';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const days = [
|
||||
'2025-12-31', // Previous month
|
||||
'2026-01-01', // Current month
|
||||
'2026-02-01', // Next month
|
||||
];
|
||||
fixture.componentRef.setInput('daysToShow', days);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const prevMonthClass = component.getDayClass('2025-12-31');
|
||||
const currentMonthClass = component.getDayClass('2026-01-01');
|
||||
const nextMonthClass = component.getDayClass('2026-02-01');
|
||||
|
||||
// Assert
|
||||
expect(prevMonthClass).toBe('other-month');
|
||||
expect(currentMonthClass).toBe('');
|
||||
expect(nextMonthClass).toBe('other-month');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWeekIndex', () => {
|
||||
it('should return 0 for first week (days 0-6)', () => {
|
||||
expect(component.getWeekIndex(0)).toBe(0);
|
||||
expect(component.getWeekIndex(3)).toBe(0);
|
||||
expect(component.getWeekIndex(6)).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 1 for second week (days 7-13)', () => {
|
||||
expect(component.getWeekIndex(7)).toBe(1);
|
||||
expect(component.getWeekIndex(10)).toBe(1);
|
||||
expect(component.getWeekIndex(13)).toBe(1);
|
||||
});
|
||||
|
||||
it('should return correct week index for later weeks', () => {
|
||||
expect(component.getWeekIndex(14)).toBe(2);
|
||||
expect(component.getWeekIndex(21)).toBe(3);
|
||||
expect(component.getWeekIndex(28)).toBe(4);
|
||||
expect(component.getWeekIndex(35)).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDayIndex', () => {
|
||||
it('should return 0-6 for days within a week', () => {
|
||||
expect(component.getDayIndex(0)).toBe(0);
|
||||
expect(component.getDayIndex(1)).toBe(1);
|
||||
expect(component.getDayIndex(6)).toBe(6);
|
||||
expect(component.getDayIndex(7)).toBe(0);
|
||||
expect(component.getDayIndex(13)).toBe(6);
|
||||
expect(component.getDayIndex(14)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('weekdayHeaders computed', () => {
|
||||
it('should generate 7 weekday headers', () => {
|
||||
// Arrange
|
||||
fixture.componentRef.setInput('firstDayOfWeek', 0); // Sunday
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const headers = component.weekdayHeaders();
|
||||
|
||||
// Assert
|
||||
expect(headers.length).toBe(7);
|
||||
});
|
||||
|
||||
it('should start with Sunday when firstDayOfWeek is 0', () => {
|
||||
// Arrange
|
||||
fixture.componentRef.setInput('firstDayOfWeek', 0);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const headers = component.weekdayHeaders();
|
||||
|
||||
// Assert
|
||||
// Sunday should be first
|
||||
expect(headers[0]).toContain('Sun');
|
||||
});
|
||||
|
||||
it('should start with Monday when firstDayOfWeek is 1', () => {
|
||||
// Arrange
|
||||
fixture.componentRef.setInput('firstDayOfWeek', 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const headers = component.weekdayHeaders();
|
||||
|
||||
// Assert
|
||||
// Monday should be first
|
||||
expect(headers[0]).toContain('Mon');
|
||||
});
|
||||
|
||||
it('should cycle correctly for all days of week', () => {
|
||||
// Arrange
|
||||
fixture.componentRef.setInput('firstDayOfWeek', 0); // Sunday
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const headers = component.weekdayHeaders();
|
||||
|
||||
// Assert
|
||||
expect(headers.length).toBe(7);
|
||||
// Should have all unique days
|
||||
const uniqueHeaders = new Set(headers);
|
||||
expect(uniqueHeaders.size).toBe(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Service method delegation', () => {
|
||||
it('should delegate hasEventsForDay to service', () => {
|
||||
// Arrange
|
||||
mockScheduleService.hasEventsForDay.and.returnValue(true);
|
||||
const testDay = '2026-01-15';
|
||||
const testEvents = [] as any;
|
||||
fixture.componentRef.setInput('events', testEvents);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.hasEventsForDay(testDay);
|
||||
|
||||
// Assert
|
||||
expect(mockScheduleService.hasEventsForDay).toHaveBeenCalledWith(
|
||||
testDay,
|
||||
testEvents,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should delegate getEventsForDay to service', () => {
|
||||
// Arrange
|
||||
const testEvents = [{ id: 'event1' }] as any;
|
||||
mockScheduleService.getEventsForDay.and.returnValue(testEvents);
|
||||
const testDay = '2026-01-15';
|
||||
fixture.componentRef.setInput('events', []);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Act
|
||||
const result = component.getEventsForDay(testDay);
|
||||
|
||||
// Assert
|
||||
expect(mockScheduleService.getEventsForDay).toHaveBeenCalledWith(testDay, []);
|
||||
expect(result).toEqual(testEvents);
|
||||
});
|
||||
|
||||
it('should delegate getEventDayStr to service', () => {
|
||||
// Arrange
|
||||
const testEvent = { id: 'event1' } as any;
|
||||
mockScheduleService.getEventDayStr.and.returnValue('2026-01-15');
|
||||
|
||||
// Act
|
||||
const result = component.getEventDayStr(testEvent);
|
||||
|
||||
// Assert
|
||||
expect(mockScheduleService.getEventDayStr).toHaveBeenCalledWith(testEvent);
|
||||
expect(result).toBe('2026-01-15');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input handling', () => {
|
||||
it('should accept events input', () => {
|
||||
// Arrange
|
||||
const testEvents = [{ id: 'event1' }] as any;
|
||||
|
||||
// Act
|
||||
fixture.componentRef.setInput('events', testEvents);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Assert
|
||||
expect(component.events()).toEqual(testEvents);
|
||||
});
|
||||
|
||||
it('should accept daysToShow input', () => {
|
||||
// Arrange
|
||||
const testDays = ['2026-01-01', '2026-01-02', '2026-01-03'];
|
||||
|
||||
// Act
|
||||
fixture.componentRef.setInput('daysToShow', testDays);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Assert
|
||||
expect(component.daysToShow()).toEqual(testDays);
|
||||
});
|
||||
|
||||
it('should accept weeksToShow input', () => {
|
||||
// Arrange
|
||||
const testWeeks = 5;
|
||||
|
||||
// Act
|
||||
fixture.componentRef.setInput('weeksToShow', testWeeks);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Assert
|
||||
expect(component.weeksToShow()).toBe(testWeeks);
|
||||
});
|
||||
|
||||
it('should accept firstDayOfWeek input', () => {
|
||||
// Arrange
|
||||
const testFirstDay = 1; // Monday
|
||||
|
||||
// Act
|
||||
fixture.componentRef.setInput('firstDayOfWeek', testFirstDay);
|
||||
fixture.detectChanges();
|
||||
|
||||
// Assert
|
||||
expect(component.firstDayOfWeek()).toBe(testFirstDay);
|
||||
});
|
||||
|
||||
it('should default weeksToShow to 6', () => {
|
||||
// Act & Assert
|
||||
expect(component.weeksToShow()).toBe(6);
|
||||
});
|
||||
|
||||
it('should default firstDayOfWeek to 1', () => {
|
||||
// Act & Assert
|
||||
expect(component.firstDayOfWeek()).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -253,7 +253,9 @@ describe('ScheduleService', () => {
|
|||
// January 2026 starts on a Thursday, so with Sunday start,
|
||||
// we should have padding days from December 2025
|
||||
const firstDay = new Date(result[0]);
|
||||
expect(firstDay.getMonth()).toBeLessThan(0); // December (previous year)
|
||||
// December 2025 would be month 11 (previous year)
|
||||
expect(firstDay.getMonth()).toBe(11);
|
||||
expect(firstDay.getFullYear()).toBe(2025);
|
||||
|
||||
// Should also have some days from February if weeks extend past January
|
||||
const lastDay = new Date(result[result.length - 1]);
|
||||
|
|
|
|||
|
|
@ -173,8 +173,8 @@ describe('ScheduleComponent', () => {
|
|||
});
|
||||
|
||||
it('should return true when _selectedDate matches today', () => {
|
||||
// Arrange - set to today
|
||||
const today = new Date();
|
||||
// Arrange - set to today (must match the mock todayDateStr$ value)
|
||||
const today = new Date('2026-01-20');
|
||||
component['_selectedDate'].set(today);
|
||||
|
||||
// Act & Assert
|
||||
|
|
|
|||
|
|
@ -128,11 +128,13 @@ describe('ArchiveOperationHandler', () => {
|
|||
'updateTask',
|
||||
'updateTasks',
|
||||
'hasTask',
|
||||
'hasTasksBatch',
|
||||
'removeAllArchiveTasksForProject',
|
||||
'removeTagsFromAllTasks',
|
||||
'removeRepeatCfgFromArchiveTasks',
|
||||
'unlinkIssueProviderFromArchiveTasks',
|
||||
]);
|
||||
mockTaskArchiveService.hasTasksBatch.and.returnValue(Promise.resolve(new Map()));
|
||||
mockTimeTrackingService = jasmine.createSpyObj('TimeTrackingService', [
|
||||
'cleanupDataEverywhereForProject',
|
||||
'cleanupArchiveDataForTag',
|
||||
|
|
@ -381,6 +383,16 @@ describe('ArchiveOperationHandler', () => {
|
|||
|
||||
describe('updateTasks action (batch)', () => {
|
||||
it('should update multiple archived tasks for remote operations', async () => {
|
||||
// Both tasks exist in archive
|
||||
mockTaskArchiveService.hasTasksBatch.and.returnValue(
|
||||
Promise.resolve(
|
||||
new Map([
|
||||
['task-1', true],
|
||||
['task-2', true],
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
const action = {
|
||||
type: TaskSharedActions.updateTasks.type,
|
||||
tasks: [
|
||||
|
|
@ -415,8 +427,13 @@ describe('ArchiveOperationHandler', () => {
|
|||
|
||||
it('should only update tasks that exist in archive', async () => {
|
||||
// Only task-1 is in archive, task-2 is not
|
||||
mockTaskArchiveService.hasTask.and.callFake((id: string) =>
|
||||
Promise.resolve(id === 'task-1'),
|
||||
mockTaskArchiveService.hasTasksBatch.and.returnValue(
|
||||
Promise.resolve(
|
||||
new Map([
|
||||
['task-1', true],
|
||||
['task-2', false],
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
const action = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue