test(schedule): add comprehensive unit tests for navigation

- Implement full test suite for ScheduleComponent covering:
  - Navigation state management (_selectedDate signal)
  - Week/month navigation (goToNextPeriod, goToPreviousPeriod, goToToday)
  - Today detection (isViewingToday computed)
  - Context-aware time calculations (_contextNow)
  - Schedule days computation with contextNow/realNow
  - Current time row display logic
  - Days to show calculations for week and month views

- Add ScheduleService tests for:
  - getDaysToShow with reference dates
  - getMonthDaysToShow with padding days
  - buildScheduleDays parameter handling
  - getDayClass with reference month support

- Fix test setup:
  - Use TranslateModule.forRoot() for proper i18n support
  - Add complete mock methods for ScheduleService
  - Handle timing-sensitive tests with appropriate tolerances
  - Use correct FH constant value (12) for time row calculations

- Remove unused CollapsibleComponent import from config-page

All 32 ScheduleComponent tests passing
This commit is contained in:
Johannes Millan 2026-01-20 21:36:50 +01:00
parent e43adba618
commit e673d74b55
3 changed files with 874 additions and 25 deletions

View file

@ -12,6 +12,7 @@ import { TaskService } from '../tasks/task.service';
describe('ScheduleService', () => {
let service: ScheduleService;
let dateService: DateService;
beforeEach(() => {
TestBed.configureTestingModule({
@ -43,12 +44,87 @@ describe('ScheduleService', () => {
],
});
service = TestBed.inject(ScheduleService);
dateService = TestBed.inject(DateService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('getDaysToShow', () => {
it('should return correct number of days when referenceDate is null', () => {
// Arrange
const nrOfDaysToShow = 5;
// Act
const result = service.getDaysToShow(nrOfDaysToShow, null);
// Assert
expect(result.length).toBe(5);
});
it('should return days starting from today when referenceDate is null', () => {
// Arrange
const nrOfDaysToShow = 3;
const expectedTodayStr = dateService.todayStr();
// Act
const result = service.getDaysToShow(nrOfDaysToShow, null);
// Assert
expect(result[0]).toBe(expectedTodayStr);
});
it('should return days starting from referenceDate when provided', () => {
// Arrange
const nrOfDaysToShow = 3;
const referenceDate = new Date(2026, 0, 25); // Jan 25, 2026
const expectedFirstDay = dateService.todayStr(referenceDate.getTime());
// Act
const result = service.getDaysToShow(nrOfDaysToShow, referenceDate);
// Assert
expect(result[0]).toBe(expectedFirstDay);
expect(result.length).toBe(3);
});
it('should return consecutive days from referenceDate', () => {
// Arrange
const nrOfDaysToShow = 7;
const referenceDate = new Date(2026, 0, 20); // Jan 20, 2026
// Act
const result = service.getDaysToShow(nrOfDaysToShow, referenceDate);
// Assert
expect(result.length).toBe(7);
// Check that each day is consecutive
for (let i = 0; i < result.length - 1; i++) {
const currentDay = new Date(result[i]);
const nextDay = new Date(result[i + 1]);
const dayDiff =
(nextDay.getTime() - currentDay.getTime()) / (1000 * 60 * 60 * 24);
expect(dayDiff).toBe(1);
}
});
it('should handle transition across months', () => {
// Arrange
const nrOfDaysToShow = 5;
const referenceDate = new Date(2026, 0, 30); // Jan 30, 2026
// Act
const result = service.getDaysToShow(nrOfDaysToShow, referenceDate);
// Assert
expect(result.length).toBe(5);
// Last days should be in February
const lastDay = new Date(result[4]);
expect(lastDay.getMonth()).toBe(1); // February
});
});
describe('getMonthDaysToShow', () => {
it('should return correct number of days', () => {
const numberOfWeeks = 5;
@ -138,5 +214,252 @@ describe('ScheduleService', () => {
jasmine.clock().uninstall();
});
it('should use referenceDate to determine the month to display', () => {
// Arrange
const numberOfWeeks = 4;
const firstDayOfWeek = 1; // Monday
const referenceDate = new Date(2026, 5, 15); // June 15, 2026
// Act
const result = service.getMonthDaysToShow(
numberOfWeeks,
firstDayOfWeek,
referenceDate,
);
// Assert
expect(result.length).toBe(28); // 4 weeks
// The month view should include days from June 2026
const juneFirst = new Date(2026, 5, 1); // June 1, 2026
const juneFirstStr = dateService.todayStr(juneFirst.getTime());
expect(result).toContain(juneFirstStr);
});
it('should include padding days from previous and next month', () => {
// Arrange
const numberOfWeeks = 5;
const firstDayOfWeek = 0; // Sunday
const referenceDate = new Date(2026, 0, 15); // Jan 15, 2026
// Act
const result = service.getMonthDaysToShow(
numberOfWeeks,
firstDayOfWeek,
referenceDate,
);
// Assert
// 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)
// Should also have some days from February if weeks extend past January
const lastDay = new Date(result[result.length - 1]);
// With 5 weeks starting from late December, we should reach into February
expect(lastDay.getMonth()).toBeGreaterThanOrEqual(0);
});
});
describe('buildScheduleDays', () => {
it('should return empty array when timelineTasks is null', () => {
// Arrange
const params = {
daysToShow: ['2026-01-20', '2026-01-21'],
timelineTasks: null,
taskRepeatCfgs: { withStartTime: [], withoutStartTime: [] },
icalEvents: [],
plannerDayMap: {},
timelineCfg: null,
currentTaskId: null,
};
// Act
const result = service.buildScheduleDays(params);
// Assert
expect(result).toEqual([]);
});
it('should return empty array when taskRepeatCfgs is null', () => {
// Arrange
const params = {
daysToShow: ['2026-01-20', '2026-01-21'],
timelineTasks: { unPlanned: [], planned: [] },
taskRepeatCfgs: null,
icalEvents: [],
plannerDayMap: {},
timelineCfg: null,
currentTaskId: null,
};
// Act
const result = service.buildScheduleDays(params);
// Assert
expect(result).toEqual([]);
});
it('should return empty array when plannerDayMap is null', () => {
// Arrange
const params = {
daysToShow: ['2026-01-20', '2026-01-21'],
timelineTasks: { unPlanned: [], planned: [] },
taskRepeatCfgs: { withStartTime: [], withoutStartTime: [] },
icalEvents: [],
plannerDayMap: null,
timelineCfg: null,
currentTaskId: null,
};
// Act
const result = service.buildScheduleDays(params);
// Assert
expect(result).toEqual([]);
});
it('should pass realNow parameter through to mapToScheduleDays', () => {
// Arrange
const realNow = Date.now();
const params = {
now: Date.now(),
realNow,
daysToShow: ['2026-01-20'],
timelineTasks: { unPlanned: [], planned: [] },
taskRepeatCfgs: { withStartTime: [], withoutStartTime: [] },
icalEvents: [],
plannerDayMap: {},
timelineCfg: null,
currentTaskId: null,
};
// Act
const result = service.buildScheduleDays(params);
// Assert
// The function should not throw and should process with realNow
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
});
it('should default now to Date.now() when not provided', () => {
// Arrange
const params = {
daysToShow: ['2026-01-20'],
timelineTasks: { unPlanned: [], planned: [] },
taskRepeatCfgs: { withStartTime: [], withoutStartTime: [] },
icalEvents: [],
plannerDayMap: {},
timelineCfg: null,
};
// Act
const result = service.buildScheduleDays(params);
// Assert
// Should work without throwing
expect(result).toBeDefined();
});
});
describe('getDayClass', () => {
it('should return empty string for a day in current month when no referenceMonth provided', () => {
// Arrange
const today = new Date();
const dayInCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 15);
const dayStr = dateService.todayStr(dayInCurrentMonth.getTime());
// Act
const result = service.getDayClass(dayStr);
// Assert
// Should not have 'other-month' class
expect(result).not.toContain('other-month');
});
it('should return "today" class for today without referenceMonth', () => {
// Arrange
const today = new Date();
const todayStr = dateService.todayStr(today.getTime());
// Act
const result = service.getDayClass(todayStr);
// Assert
expect(result).toContain('today');
});
it('should return "other-month" class for a day in a different month when referenceMonth provided', () => {
// Arrange
const referenceMonth = new Date(2026, 5, 15); // June 2026
const dayInMay = new Date(2026, 4, 31); // May 31, 2026
const dayStr = dateService.todayStr(dayInMay.getTime());
// Act
const result = service.getDayClass(dayStr, referenceMonth);
// Assert
expect(result).toContain('other-month');
});
it('should not return "other-month" class for a day in the reference month', () => {
// Arrange
const referenceMonth = new Date(2026, 5, 15); // June 2026
const dayInJune = new Date(2026, 5, 20); // June 20, 2026
const dayStr = dateService.todayStr(dayInJune.getTime());
// Act
const result = service.getDayClass(dayStr, referenceMonth);
// Assert
expect(result).not.toContain('other-month');
});
it('should return "today" class even when using referenceMonth', () => {
// Arrange
const today = new Date();
const todayStr = dateService.todayStr(today.getTime());
const referenceMonth = new Date(today.getFullYear(), today.getMonth(), 15);
// Act
const result = service.getDayClass(todayStr, referenceMonth);
// Assert
expect(result).toContain('today');
});
it('should handle year boundaries correctly', () => {
// Arrange
const referenceMonth = new Date(2026, 0, 15); // January 2026
const dayInDecember2025 = new Date(2025, 11, 31); // Dec 31, 2025
const dayStr = dateService.todayStr(dayInDecember2025.getTime());
// Act
const result = service.getDayClass(dayStr, referenceMonth);
// Assert
expect(result).toContain('other-month');
});
it('should combine "today" and "other-month" classes when applicable', () => {
// Arrange - This is an edge case where today is in a different month than reference
const today = new Date(2026, 0, 20); // Jan 20, 2026
jasmine.clock().install();
jasmine.clock().mockDate(today);
const todayStr = dateService.todayStr(today.getTime());
const referenceMonth = new Date(2025, 11, 15); // December 2025
// Act
const result = service.getDayClass(todayStr, referenceMonth);
// Assert
expect(result).toContain('today');
expect(result).toContain('other-month');
jasmine.clock().uninstall();
});
});
});

View file

@ -1,23 +1,551 @@
// import { ComponentFixture, TestBed } from '@angular/core/testing';
//
// import { ScheduleComponent } from './schedule.component';
//
// describe('ScheduleComponent', () => {
// let component: ScheduleComponent;
// let fixture: ComponentFixture<ScheduleComponent>;
//
// beforeEach(async () => {
// await TestBed.configureTestingModule({
// imports: [ScheduleComponent]
// })
// .compileComponents();
//
// fixture = TestBed.createComponent(ScheduleComponent);
// component = fixture.componentInstance;
// fixture.detectChanges();
// });
//
// it('should create', () => {
// expect(component).toBeTruthy();
// });
// });
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ScheduleComponent } from './schedule.component';
import { TaskService } from '../../tasks/task.service';
import { LayoutService } from '../../../core-ui/layout/layout.service';
import { ScheduleService } from '../schedule.service';
import { MatDialog } from '@angular/material/dialog';
import { GlobalTrackingIntervalService } from '../../../core/global-tracking-interval/global-tracking-interval.service';
import { DateAdapter } from '@angular/material/core';
import { signal } from '@angular/core';
import { of } from 'rxjs';
import { provideMockStore } from '@ngrx/store/testing';
import { TranslateModule } from '@ngx-translate/core';
describe('ScheduleComponent', () => {
let component: ScheduleComponent;
let fixture: ComponentFixture<ScheduleComponent>;
let mockTaskService: jasmine.SpyObj<TaskService>;
let mockLayoutService: jasmine.SpyObj<LayoutService>;
let mockScheduleService: jasmine.SpyObj<ScheduleService>;
let mockMatDialog: jasmine.SpyObj<MatDialog>;
let mockGlobalTrackingIntervalService: jasmine.SpyObj<GlobalTrackingIntervalService>;
let mockDateAdapter: jasmine.SpyObj<DateAdapter<any>>;
beforeEach(async () => {
// Create mock services
mockTaskService = jasmine.createSpyObj('TaskService', ['currentTaskId']);
(mockTaskService as any).currentTaskId = signal(null);
mockLayoutService = jasmine.createSpyObj('LayoutService', [], {
selectedTimeView: signal('week'),
});
mockScheduleService = jasmine.createSpyObj('ScheduleService', [
'getDaysToShow',
'getMonthDaysToShow',
'buildScheduleDays',
'scheduleRefreshTick',
'getTodayStr',
'createScheduleDaysWithContext',
'getDayClass',
'hasEventsForDay',
'getEventsForDay',
]);
mockScheduleService.getDaysToShow.and.returnValue([
'2026-01-20',
'2026-01-21',
'2026-01-22',
]);
mockScheduleService.getMonthDaysToShow.and.returnValue([
'2026-01-01',
'2026-01-02',
'2026-01-03',
]);
mockScheduleService.buildScheduleDays.and.returnValue([]);
mockScheduleService.getTodayStr.and.callFake((timestamp?: number | Date) => {
const date = timestamp ? new Date(timestamp) : new Date();
return date.toISOString().split('T')[0];
});
mockScheduleService.createScheduleDaysWithContext.and.returnValue([]);
mockScheduleService.getDayClass.and.returnValue('');
mockScheduleService.hasEventsForDay.and.returnValue(false);
mockScheduleService.getEventsForDay.and.returnValue([]);
(mockScheduleService as any).scheduleRefreshTick = signal(0);
mockMatDialog = jasmine.createSpyObj('MatDialog', ['open']);
mockGlobalTrackingIntervalService = jasmine.createSpyObj(
'GlobalTrackingIntervalService',
[],
{
todayDateStr$: of('2026-01-20'),
},
);
mockDateAdapter = jasmine.createSpyObj('DateAdapter', ['getFirstDayOfWeek']);
mockDateAdapter.getFirstDayOfWeek.and.returnValue(0);
await TestBed.configureTestingModule({
imports: [ScheduleComponent, TranslateModule.forRoot()],
providers: [
provideMockStore({ initialState: {} }),
{ provide: TaskService, useValue: mockTaskService },
{ provide: LayoutService, useValue: mockLayoutService },
{ provide: ScheduleService, useValue: mockScheduleService },
{ provide: MatDialog, useValue: mockMatDialog },
{
provide: GlobalTrackingIntervalService,
useValue: mockGlobalTrackingIntervalService,
},
{ provide: DateAdapter, useValue: mockDateAdapter },
],
}).compileComponents();
fixture = TestBed.createComponent(ScheduleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('_selectedDate signal', () => {
it('should initialize as null (viewing today)', () => {
expect(component['_selectedDate']()).toBeNull();
});
it('should update when goToNextPeriod is called in week view', () => {
// Arrange - default is week view
const initialDate = component['_selectedDate']();
expect(initialDate).toBeNull();
// Act
component.goToNextPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate).not.toBeNull();
expect(newDate?.getTime()).toBeGreaterThan(Date.now() - 1000); // Should be around now + 7 days
});
it('should update when goToPreviousPeriod is called in week view', () => {
// Arrange - start viewing today (null)
expect(component['_selectedDate']()).toBeNull();
// Act - navigate to previous week
component.goToPreviousPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate).not.toBeNull();
expect(newDate?.getTime()).toBeLessThan(Date.now());
});
it('should update when goToNextPeriod is called in month view', () => {
// Arrange - switch to month view
mockLayoutService.selectedTimeView.set('month');
fixture.detectChanges();
// Act
component.goToNextPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate).not.toBeNull();
// Should be first of next month
expect(newDate?.getDate()).toBe(1);
});
it('should update when goToPreviousPeriod is called in month view', () => {
// Arrange - switch to month view
mockLayoutService.selectedTimeView.set('month');
fixture.detectChanges();
// Act
component.goToPreviousPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate).not.toBeNull();
// Should be first of previous month
expect(newDate?.getDate()).toBe(1);
});
});
describe('isViewingToday computed', () => {
it('should return true when _selectedDate is null', () => {
// Arrange
component['_selectedDate'].set(null);
// Act & Assert
expect(component.isViewingToday()).toBe(true);
});
it('should return true when _selectedDate matches today', () => {
// Arrange - set to today
const today = new Date();
component['_selectedDate'].set(today);
// Act & Assert
expect(component.isViewingToday()).toBe(true);
});
it('should return false when _selectedDate is in the future', () => {
// Arrange
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 7);
component['_selectedDate'].set(futureDate);
// Act & Assert
expect(component.isViewingToday()).toBe(false);
});
it('should return false when _selectedDate is in the past', () => {
// Arrange
const pastDate = new Date();
pastDate.setDate(pastDate.getDate() - 7);
component['_selectedDate'].set(pastDate);
// Act & Assert
expect(component.isViewingToday()).toBe(false);
});
});
describe('goToPreviousPeriod', () => {
it('should subtract 7 days in week view when viewing a future date', () => {
// Arrange
const startDate = new Date(2026, 0, 20); // Jan 20, 2026
component['_selectedDate'].set(startDate);
// Act
component.goToPreviousPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getDate()).toBe(13); // Jan 13, 2026
});
it('should subtract 7 days from today when _selectedDate is null in week view', () => {
// Arrange
component['_selectedDate'].set(null);
const expectedDate = new Date();
expectedDate.setDate(expectedDate.getDate() - 7);
// Act
component.goToPreviousPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getDate()).toBe(expectedDate.getDate());
});
it('should go to previous month in month view', () => {
// Arrange
mockLayoutService.selectedTimeView.set('month');
const startDate = new Date(2026, 1, 15); // Feb 15, 2026
component['_selectedDate'].set(startDate);
// Act
component.goToPreviousPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getMonth()).toBe(0); // January
expect(newDate?.getDate()).toBe(1); // First of month
});
it('should go to previous year when navigating from January in month view', () => {
// Arrange
mockLayoutService.selectedTimeView.set('month');
const startDate = new Date(2026, 0, 15); // Jan 15, 2026
component['_selectedDate'].set(startDate);
// Act
component.goToPreviousPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getFullYear()).toBe(2025);
expect(newDate?.getMonth()).toBe(11); // December
});
});
describe('goToNextPeriod', () => {
it('should add 7 days in week view', () => {
// Arrange
const startDate = new Date(2026, 0, 20); // Jan 20, 2026
component['_selectedDate'].set(startDate);
// Act
component.goToNextPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getDate()).toBe(27); // Jan 27, 2026
});
it('should add 7 days from today when _selectedDate is null in week view', () => {
// Arrange
component['_selectedDate'].set(null);
const expectedDate = new Date();
expectedDate.setDate(expectedDate.getDate() + 7);
// Act
component.goToNextPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getDate()).toBe(expectedDate.getDate());
});
it('should go to next month in month view', () => {
// Arrange
mockLayoutService.selectedTimeView.set('month');
const startDate = new Date(2026, 0, 15); // Jan 15, 2026
component['_selectedDate'].set(startDate);
// Act
component.goToNextPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getMonth()).toBe(1); // February
expect(newDate?.getDate()).toBe(1); // First of month
});
it('should go to next year when navigating from December in month view', () => {
// Arrange
mockLayoutService.selectedTimeView.set('month');
const startDate = new Date(2025, 11, 15); // Dec 15, 2025
component['_selectedDate'].set(startDate);
// Act
component.goToNextPeriod();
// Assert
const newDate = component['_selectedDate']();
expect(newDate?.getFullYear()).toBe(2026);
expect(newDate?.getMonth()).toBe(0); // January
});
});
describe('goToToday', () => {
it('should reset _selectedDate to null', () => {
// Arrange
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 7);
component['_selectedDate'].set(futureDate);
// Act
component.goToToday();
// Assert
expect(component['_selectedDate']()).toBeNull();
});
it('should make isViewingToday return true', () => {
// Arrange
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 7);
component['_selectedDate'].set(futureDate);
expect(component.isViewingToday()).toBe(false);
// Act
component.goToToday();
// Assert
expect(component.isViewingToday()).toBe(true);
});
});
describe('_contextNow computed', () => {
it('should return current time when viewing today (selectedDate is null)', () => {
// Arrange
component['_selectedDate'].set(null);
// Act
const contextNow = component['_contextNow']();
// Assert - just check it's a reasonable timestamp (within last hour and next minute)
const now = Date.now();
// eslint-disable-next-line no-mixed-operators
const oneHourAgo = now - 60 * 60 * 1000;
// eslint-disable-next-line no-mixed-operators
const oneMinuteFromNow = now + 60 * 1000;
expect(contextNow).toBeGreaterThan(oneHourAgo);
expect(contextNow).toBeLessThan(oneMinuteFromNow);
});
it('should return midnight of selected date when viewing a different date', () => {
// Arrange
const selectedDate = new Date(2026, 0, 25, 14, 30, 45); // Jan 25, 2026, 2:30:45 PM
component['_selectedDate'].set(selectedDate);
// Act
const contextNow = component['_contextNow']();
const contextDate = new Date(contextNow);
// Assert
expect(contextDate.getHours()).toBe(0);
expect(contextDate.getMinutes()).toBe(0);
expect(contextDate.getSeconds()).toBe(0);
expect(contextDate.getMilliseconds()).toBe(0);
expect(contextDate.getDate()).toBe(25);
expect(contextDate.getMonth()).toBe(0);
expect(contextDate.getFullYear()).toBe(2026);
});
});
describe('scheduleDays computed', () => {
it('should call createScheduleDaysWithContext with contextNow', () => {
// Arrange
const selectedDate = new Date(2026, 0, 25);
component['_selectedDate'].set(selectedDate);
mockScheduleService.createScheduleDaysWithContext.calls.reset();
// Act
component.scheduleDays();
// Assert
expect(mockScheduleService.createScheduleDaysWithContext).toHaveBeenCalled();
const callArgs =
mockScheduleService.createScheduleDaysWithContext.calls.mostRecent().args[0];
expect(callArgs.contextNow).toBeDefined();
// Context now should be midnight of selected date
const contextDate = new Date(callArgs.contextNow);
expect(contextDate.getHours()).toBe(0);
expect(contextDate.getMinutes()).toBe(0);
});
it('should always pass realNow as actual current time', () => {
// Arrange
const selectedDate = new Date(2026, 0, 25);
component['_selectedDate'].set(selectedDate);
mockScheduleService.createScheduleDaysWithContext.calls.reset();
const before = Date.now();
// Act
component.scheduleDays();
const after = Date.now();
// Assert
const callArgs =
mockScheduleService.createScheduleDaysWithContext.calls.mostRecent().args[0];
expect(callArgs.realNow).toBeGreaterThanOrEqual(before);
expect(callArgs.realNow).toBeLessThanOrEqual(after);
});
it('should pass both contextNow and realNow when viewing a future date', () => {
// Arrange
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 7);
component['_selectedDate'].set(futureDate);
mockScheduleService.createScheduleDaysWithContext.calls.reset();
// Act
component.scheduleDays();
// Assert
const callArgs =
mockScheduleService.createScheduleDaysWithContext.calls.mostRecent().args[0];
expect(callArgs.contextNow).toBeDefined();
expect(callArgs.realNow).toBeDefined();
// contextNow should be different from realNow when viewing future
expect(callArgs.contextNow).not.toBe(callArgs.realNow);
});
});
describe('currentTimeRow computed', () => {
it('should return null when not viewing today', () => {
// Arrange
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 7);
component['_selectedDate'].set(futureDate);
// Act
const timeRow = component.currentTimeRow();
// Assert
expect(timeRow).toBeNull();
});
it('should return a number when viewing today', () => {
// Arrange
component['_selectedDate'].set(null);
// Act
const timeRow = component.currentTimeRow();
// Assert
expect(timeRow).not.toBeNull();
expect(typeof timeRow).toBe('number');
});
it('should calculate time row based on current time', () => {
// Arrange
component['_selectedDate'].set(null);
// Act
const timeRow = component.currentTimeRow();
// Assert - check it's a reasonable value (0-288 for 24 hours * FH=12)
expect(timeRow).not.toBeNull();
expect(timeRow).toBeGreaterThanOrEqual(0);
expect(timeRow).toBeLessThanOrEqual(288); // 24 hours * 12 rows per hour
});
});
describe('daysToShow computed', () => {
it('should call getDaysToShow in week view', () => {
// Arrange
mockLayoutService.selectedTimeView.set('week');
// Trigger a change to force recomputation
component['_selectedDate'].set(new Date(2026, 0, 20));
mockScheduleService.getDaysToShow.calls.reset();
// Act
component.daysToShow();
// Assert
expect(mockScheduleService.getDaysToShow).toHaveBeenCalled();
});
it('should call getMonthDaysToShow in month view', () => {
// Arrange
mockLayoutService.selectedTimeView.set('month');
mockScheduleService.getMonthDaysToShow.calls.reset();
component['_selectedDate'].set(null);
// Act
component.daysToShow();
// Assert
expect(mockScheduleService.getMonthDaysToShow).toHaveBeenCalled();
});
it('should pass selectedDate to getDaysToShow', () => {
// Arrange
const testDate = new Date(2026, 0, 25);
component['_selectedDate'].set(testDate);
mockScheduleService.getDaysToShow.calls.reset();
// Act
component.daysToShow();
// Assert
expect(mockScheduleService.getDaysToShow).toHaveBeenCalledWith(
jasmine.any(Number),
testDate,
);
});
it('should pass selectedDate to getMonthDaysToShow', () => {
// Arrange
mockLayoutService.selectedTimeView.set('month');
const testDate = new Date(2026, 0, 25);
component['_selectedDate'].set(testDate);
mockScheduleService.getMonthDaysToShow.calls.reset();
// Act
component.daysToShow();
// Assert
expect(mockScheduleService.getMonthDaysToShow).toHaveBeenCalledWith(
jasmine.any(Number),
jasmine.any(Number),
testDate,
);
});
});
});

View file

@ -41,7 +41,6 @@ import { SyncConfigService } from '../../imex/sync/sync-config.service';
import { WebdavApi } from '../../op-log/sync-providers/file-based/webdav/webdav-api';
import { AsyncPipe } from '@angular/common';
import { PluginManagementComponent } from '../../plugins/ui/plugin-management/plugin-management.component';
import { CollapsibleComponent } from '../../ui/collapsible/collapsible.component';
import { PluginBridgeService } from '../../plugins/plugin-bridge.service';
import { createPluginShortcutFormItems } from '../../features/config/form-cfgs/plugin-keyboard-shortcuts';
import { PluginShortcutCfg } from '../../plugins/plugin-api.model';
@ -74,7 +73,6 @@ import { MatTooltip } from '@angular/material/tooltip';
TranslatePipe,
AsyncPipe,
PluginManagementComponent,
CollapsibleComponent,
MatTabGroup,
MatTab,
MatTabLabel,