Merge pull request #24 from THoguet/copilot/add-application-tests

Add comprehensive test suite for vac-optimizer application
This commit is contained in:
Théophyl Hoguet authored and GitHub committed 2025-11-03 20:02:28 +01:00
commit a0cf899cc3
16 files changed
+385 -37

No files matched your search

+1 -1
View File
@@ -20,6 +20,6 @@ describe('App', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, vac-optimizer');
expect(compiled.querySelector('h1')?.textContent).toContain('vac-optimizer');
});
});
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarMonth } from './calendar-month';
describe('CalendarMonth', () => {
@@ -8,7 +8,8 @@ describe('CalendarMonth', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarMonth]
imports: [CalendarMonth],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarSettings } from './calendar-settings';
describe('CalendarSettings', () => {
@@ -8,7 +8,8 @@ describe('CalendarSettings', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarSettings]
imports: [CalendarSettings],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarYear } from './calendar-year';
describe('CalendarYear', () => {
@@ -8,7 +8,8 @@ describe('CalendarYear', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarYear]
imports: [CalendarYear],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
@@ -46,6 +46,7 @@ export class CalendarYear {
this.selectedDates.optimizeVacations(
{ ...vacationData },
this.calendarSettingsService,
this.calendarService,
);
}
this.isLoading.set(false);
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { DarkLightModeButton } from './dark-light-mode-button';
describe('DarkLightModeButton', () => {
@@ -9,6 +9,7 @@ describe('DarkLightModeButton', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DarkLightModeButton],
providers: [provideZonelessChangeDetection()],
}).compileComponents();
fixture = TestBed.createComponent(DarkLightModeButton);
@@ -1,5 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { CalendarSettingsDialog } from './calendar-settings-dialog';
describe('CalendarSettingsDialog', () => {
@@ -8,7 +9,16 @@ describe('CalendarSettingsDialog', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarSettingsDialog]
imports: [CalendarSettingsDialog],
providers: [
provideZonelessChangeDetection(),
{
provide: MatDialogRef,
useValue: {
close: jasmine.createSpy('close')
}
}
]
})
.compileComponents();
@@ -20,4 +30,13 @@ describe('CalendarSettingsDialog', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have calendar settings service', () => {
expect(component['calendarSettingsService']).toBeDefined();
});
it('should close dialog when closeDialog is called', () => {
component.closeDialog();
expect(component.dialogRef.close).toHaveBeenCalled();
});
});
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { UserInput } from './user-input';
describe('UserInput', () => {
@@ -8,7 +8,8 @@ describe('UserInput', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserInput]
imports: [UserInput],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
@@ -20,4 +21,21 @@ describe('UserInput', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should initialize form with values from service', () => {
expect(component.userInputForm).toBeDefined();
expect(component.userInputForm.get('CP')).toBeDefined();
expect(component.userInputForm.get('RTT')).toBeDefined();
expect(component.userInputForm.get('Others')).toBeDefined();
});
it('should have valid form controls', () => {
const cpControl = component.userInputForm.get('CP');
const rttControl = component.userInputForm.get('RTT');
const othersControl = component.userInputForm.get('Others');
expect(cpControl).toBeTruthy();
expect(rttControl).toBeTruthy();
expect(othersControl).toBeTruthy();
});
});
@@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { UserSettings } from './user-settings';
describe('UserSettings', () => {
@@ -8,7 +8,8 @@ describe('UserSettings', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserSettings]
imports: [UserSettings],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
+228 -3
View File
@@ -1,16 +1,241 @@
import { TestBed } from '@angular/core/testing';
import { CalendarService } from './calendar-service';
import { provideZonelessChangeDetection } from '@angular/core';
import {
CalendarService,
DayType,
SelectedDate,
SelectedDates,
} from './calendar-service';
import { DateRange } from '@angular/material/datepicker';
describe('CalendarService', () => {
let service: CalendarService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(CalendarService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('getHolidays', () => {
it('should return an array of holidays', () => {
const holidays = service.getHolidays();
expect(Array.isArray(holidays)).toBe(true);
expect(holidays.length).toBeGreaterThan(0);
});
it('should cache holidays for the same year', () => {
const holidays1 = service.getHolidays();
const holidays2 = service.getHolidays();
expect(holidays1).toEqual(holidays2);
});
});
describe('static date calculation methods', () => {
it('should calculate days in a week', () => {
const week = CalendarService.daysInWeek(0, 0, 2024);
expect(week.length).toBe(7);
});
it('should calculate weeks in a month', () => {
const weeks = CalendarService.weeksInMonth(0, 2024);
expect(weeks.length).toBeGreaterThan(0);
expect(weeks.length).toBeLessThanOrEqual(6);
});
it('should calculate months in a year', () => {
const months = CalendarService.monthsInYear(2024);
expect(months.length).toBe(12);
});
it('should calculate months from now', () => {
const months = service.monthsInAYearFromNow();
expect(months.length).toBe(12);
});
it('should return week number for a date', () => {
const date = new Date(2024, 0, 1);
const weekNumber = CalendarService.getWeekNumber(date);
expect(weekNumber).toBeGreaterThan(0);
expect(weekNumber).toBeLessThanOrEqual(53);
});
});
describe('getSelectedDatesForYear', () => {
it('should return selected dates for the year', () => {
const selectedDates = service.getSelectedDatesForYear();
expect(selectedDates).toBeDefined();
expect(selectedDates._datesSelected.length).toBeGreaterThan(0);
});
it('should include weekends', () => {
const selectedDates = service.getSelectedDatesForYear();
const hasWeekend = selectedDates._datesSelected.some(
(date) => date.type === DayType.WEEKEND,
);
expect(hasWeekend).toBe(true);
});
it('should include holidays', () => {
const selectedDates = service.getSelectedDatesForYear();
const hasHoliday = selectedDates._datesSelected.some(
(date) => date.type === DayType.CLOSED_DAY,
);
expect(hasHoliday).toBe(true);
});
it('should include today', () => {
const selectedDates = service.getSelectedDatesForYear();
const hasToday = selectedDates._datesSelected.some(
(date) => date.type === DayType.TODAY,
);
expect(hasToday).toBe(true);
});
});
describe('setWeekendSelection', () => {
it('should add weekend for Saturday', () => {
const selectedDates = new SelectedDates();
const saturday = new Date(2024, 0, 6); // A Saturday
service.setWeekendSelection(saturday, selectedDates);
expect(selectedDates._datesSelected.length).toBeGreaterThan(0);
});
});
});
describe('SelectedDate', () => {
it('should check if date is selected', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
expect(selectedDate.isSelected(date)).toBe(true);
});
it('should check if date is start', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
expect(selectedDate.isStart(date)).toBe(true);
});
it('should check if date is end', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
expect(selectedDate.isEnd(date)).toBe(true);
});
it('should return correct classes for date', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const classes = selectedDate.getClasses(date);
expect(classes).toContain(DayType.CP);
});
it('should return tooltip for selected date', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const tooltip = selectedDate.getTooltip(date);
expect(tooltip).toBeTruthy();
});
it('should calculate length in days', () => {
const startDate = new Date(2024, 0, 1);
const endDate = new Date(2024, 0, 3);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(startDate, endDate),
);
expect(selectedDate.getLengthInDays()).toBe(3);
});
it('should not mark TODAY as selected', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.TODAY,
new DateRange<Date>(date, date),
);
expect(selectedDate.isSelected(date)).toBe(false);
});
});
describe('SelectedDates', () => {
it('should push selected dates', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
expect(selectedDates._datesSelected.length).toBe(1);
});
it('should check if date is selected', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
expect(selectedDates.isSelected(date)).toBe(true);
});
it('should group adjacent dates', () => {
const selectedDates = new SelectedDates();
const date1 = new Date(2024, 0, 1);
const date2 = new Date(2024, 0, 2);
selectedDates._datesSelected.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date1, date1)),
);
selectedDates._datesSelected.push(
new SelectedDate(DayType.RTT, new DateRange<Date>(date2, date2)),
);
selectedDates.update();
expect(selectedDates.datesSelected.length).toBeGreaterThan(2);
});
it('should compute heuristics', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
const heuristic = selectedDates.computeHeuristics();
expect(typeof heuristic).toBe('number');
});
it('should return correct classes', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
const classes = selectedDates.getClasses(date);
expect(classes).toContain(DayType.CP);
});
it('should return tooltip', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
const tooltip = selectedDates.getTooltip(date);
expect(tooltip).toBeTruthy();
});
});
+11 -11
View File
@@ -36,7 +36,7 @@ export class CalendarService {
getSelectedDatesForYear(): SelectedDates {
const holidays = this.getHolidays();
const monthInAYear = CalendarService.monthsInAYearFromNow();
const monthInAYear = this.monthsInAYearFromNow();
let selectedDates: SelectedDates = new SelectedDates();
// Use DateCacheService to get today's date components
@@ -167,15 +167,14 @@ export class CalendarService {
return months;
}
static monthsInAYearFromNow(): Year {
const dateCache = inject(DateCacheService);
monthsInAYearFromNow(): Year {
const months: Year = [];
const currentMonthIndex = dateCache.getCurrentMonth();
const currentYear = dateCache.getCurrentYear();
const currentMonthIndex = this.dateCache.getCurrentMonth();
const currentYear = this.dateCache.getCurrentYear();
for (let i = 0; i < 12; i++) {
const monthIndex = (currentMonthIndex + i) % 12;
const year = currentYear + Math.floor((currentMonthIndex + i) / 12);
months.push(this.weeksInMonth(monthIndex, year));
months.push(CalendarService.weeksInMonth(monthIndex, year));
}
return months;
}
@@ -539,24 +538,25 @@ export class SelectedDates implements SelectedDateInterface {
optimizeVacations(
vacationsNumber: VacationsNumber,
calendarSettingsService: CalendarSettingsService,
calendarService: CalendarService,
): void {
if (calendarSettingsService.samediMalin()) this.samediMalin(vacationsNumber);
while (vacationsNumber.cp > 0) {
const result = this.lookForVacation(DayType.CP);
const result = this.lookForVacation(DayType.CP, calendarService);
if (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {
break;
}
vacationsNumber.cp -= 1;
}
while (vacationsNumber.rtt > 0) {
const result = this.lookForVacation(DayType.RTT);
const result = this.lookForVacation(DayType.RTT, calendarService);
if (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {
break;
}
vacationsNumber.rtt -= 1;
}
while (vacationsNumber.other > 0) {
const result = this.lookForVacation(DayType.OTHER);
const result = this.lookForVacation(DayType.OTHER, calendarService);
if (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {
break;
}
@@ -719,8 +719,8 @@ export class SelectedDates implements SelectedDateInterface {
}
}
lookForVacation(vacType: DayType): { apply: () => void; heuristic: number } {
const daysOfYear = CalendarService.monthsInAYearFromNow();
lookForVacation(vacType: DayType, calendarService: CalendarService): { apply: () => void; heuristic: number } {
const daysOfYear = calendarService.monthsInAYearFromNow();
const now = new Date();
now.setHours(0, 0, 0, 0);
const nowTime = now.getTime();
@@ -1,16 +1,33 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarSettingsService } from './calendar-settings-service';
describe('CalendarSettingsService', () => {
let service: CalendarSettingsService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(CalendarSettingsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with default values', () => {
expect(service.showWeekNumbers()).toBe(false);
expect(service.samediMalin()).toBe(true);
});
it('should allow updating showWeekNumbers', () => {
service.showWeekNumbers.set(true);
expect(service.showWeekNumbers()).toBe(true);
});
it('should allow updating samediMalin', () => {
service.samediMalin.set(false);
expect(service.samediMalin()).toBe(false);
});
});
+18 -1
View File
@@ -1,11 +1,14 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { DateCacheService } from './date-cache.service';
describe('DateCacheService', () => {
let service: DateCacheService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(DateCacheService);
});
@@ -43,4 +46,18 @@ describe('DateCacheService', () => {
const year2 = service.getCurrentYear();
expect(year1).toBe(year2); // Values should be the same even after clearing
});
it('should return today with time normalized to 00:00:00.000', () => {
const today = service.getToday();
expect(today.getHours()).toBe(0);
expect(today.getMinutes()).toBe(0);
expect(today.getSeconds()).toBe(0);
expect(today.getMilliseconds()).toBe(0);
});
it('should return getNow() same as getToday()', () => {
const now = service.getNow();
const today = service.getToday();
expect(now).toBe(today);
});
});
+28 -2
View File
@@ -1,16 +1,42 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { ThemeService } from './theme-service';
describe('ThemeService', () => {
let service: ThemeService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(ThemeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with system preference', () => {
expect(service.isLightMode()).toBeDefined();
});
it('should toggle theme', () => {
const initialMode = service.isLightMode();
service.toggleTheme();
expect(service.isLightMode()).toBe(!initialMode);
});
it('should force light mode when specified', () => {
service.toggleTheme(true);
expect(service.isLightMode()).toBe(true);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
});
it('should force dark mode when specified', () => {
service.toggleTheme(false);
expect(service.isLightMode()).toBe(false);
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
});
});
+20 -2
View File
@@ -1,16 +1,34 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { UserInputService } from './user-input-service';
describe('UserInput', () => {
let service: UserInputService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(UserInputService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with random vacation numbers', () => {
const vacations = service.vacationNumberSignal();
expect(vacations.cp).toBeGreaterThanOrEqual(1);
expect(vacations.cp).toBeLessThanOrEqual(9);
expect(vacations.rtt).toBeGreaterThanOrEqual(1);
expect(vacations.rtt).toBeLessThanOrEqual(9);
expect(vacations.other).toBeGreaterThanOrEqual(1);
expect(vacations.other).toBeLessThanOrEqual(9);
});
it('should update vacation number signal', () => {
const newVacations = { cp: 5, rtt: 3, other: 2 };
service.vacationNumberSignal.set(newVacations);
expect(service.vacationNumberSignal()).toEqual(newVacations);
});
});
@@ -1,12 +1,14 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { UserSettingsService } from './user-settings-service';
describe('UserSettingsService', () => {
let service: UserSettingsService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(UserSettingsService);
});