feat(planner): implement cancellation for planning calculations
- Refactor leave-planner.worker.ts to support cancellation of ongoing calculations. - Introduce `calculatePlanningAsyncInternal` for asynchronous planning calculations. - Add cancellation handling in the worker message event. - Update planning-date.ts with new utility functions for date manipulation. - Extend translation-service.ts and translation files (en.json, de.json, es.json, fr.json) to include new strings related to planning horizon and cancellation. - Create documentation for the cancellation feature in planner-cancellation.md. - Add unit tests for cancellation functionality in leave-planner-cancellation.spec.ts and leave-planner.worker.spec.ts. #57 #58
This commit is contained in:
1 parent
5c35a59805
commit
2edf2004f5
31 files changed
+2275
-304
No files matched your search
@@ -1,21 +1,114 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideZonelessChangeDetection } from '@angular/core';
|
||||
import { CalendarSettingsService } from './calendar-settings-service';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
describe('CalendarSettingsService', () => {
|
||||
let service: CalendarSettingsService;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
vi.useFakeTimers({ toFake: ['Date'] });
|
||||
vi.setSystemTime(new Date('2026-10-04T22:30:00Z'));
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
service = TestBed.inject(CalendarSettingsService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('defaults to the Paris date and displays the day before the anniversary as the last planning date', () => {
|
||||
expect(service.planningDates()).toEqual({
|
||||
start: '2026-10-05',
|
||||
lastPlanningDate: '2027-10-04',
|
||||
});
|
||||
expect(service.planningHorizon()).toEqual({ start: '2026-10-05', end: '2027-10-05' });
|
||||
expect(localStorage.getItem('calendarSettings.planningDates')).toBeNull();
|
||||
});
|
||||
|
||||
it('saves and restores fixed historical dates before applying a later default', () => {
|
||||
service.setPlanningDates('2024-02-29', '2026-02-28');
|
||||
vi.setSystemTime(new Date('2029-08-01T12:00:00Z'));
|
||||
const restored = TestBed.runInInjectionContext(() => new CalendarSettingsService());
|
||||
expect(restored.planningDates()).toEqual({
|
||||
start: '2024-02-29',
|
||||
lastPlanningDate: '2026-02-28',
|
||||
});
|
||||
expect(restored.planningHorizon()).toEqual({ start: '2024-02-29', end: '2026-03-01' });
|
||||
});
|
||||
|
||||
it('uses the existing February 29 anniversary convention for the unsaved default', () => {
|
||||
vi.setSystemTime(new Date('2024-02-29T12:00:00Z'));
|
||||
const leapDay = TestBed.runInInjectionContext(() => new CalendarSettingsService());
|
||||
expect(leapDay.planningDates()).toEqual({
|
||||
start: '2024-02-29',
|
||||
lastPlanningDate: '2025-02-27',
|
||||
});
|
||||
expect(leapDay.planningHorizon()).toEqual({ start: '2024-02-29', end: '2025-02-28' });
|
||||
});
|
||||
|
||||
it.each([
|
||||
['2024-02-29', '2024-03-01'],
|
||||
['2026-03-29', '2026-03-30'],
|
||||
['2026-10-25', '2026-10-26'],
|
||||
['2026-12-31', '2027-01-01'],
|
||||
])(
|
||||
'converts a single inclusive planning date %s across leap days, clock changes and years',
|
||||
(date, end) => {
|
||||
const previousTimeZone = process.env['TZ'];
|
||||
process.env['TZ'] = 'Pacific/Auckland';
|
||||
try {
|
||||
service.setPlanningDates(date, date);
|
||||
expect(service.planningHorizon()).toEqual({ start: date, end });
|
||||
} finally {
|
||||
if (previousTimeZone === undefined) delete process.env['TZ'];
|
||||
else process.env['TZ'] = previousTimeZone;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
['2024-02-30', '2024-03-01', 'invalidDate'],
|
||||
['2024-02-29', '2024-13-01', 'invalidDate'],
|
||||
['29/02/2024', '2024-03-01', 'invalidDate'],
|
||||
['', '2024-03-01', 'invalidDate'],
|
||||
['2024-03-02', '2024-03-01', 'horizonOrder'],
|
||||
])(
|
||||
'keeps invalid input visible without replacing the last valid saved dates (%s, %s)',
|
||||
(start, last, error) => {
|
||||
service.setPlanningDates('2024-02-29', '2027-02-28');
|
||||
service.setPlanningDates(start, last);
|
||||
expect(service.planningDates()).toEqual({ start, lastPlanningDate: last });
|
||||
expect(service.planningHorizon()).toBeNull();
|
||||
expect(service.planningHorizonError()).toBe(error);
|
||||
const restored = TestBed.runInInjectionContext(() => new CalendarSettingsService());
|
||||
expect(restored.planningHorizon()).toEqual({ start: '2024-02-29', end: '2027-03-01' });
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
'{',
|
||||
'null',
|
||||
'{}',
|
||||
'{"start":"2024-02-30","lastPlanningDate":"2024-03-01"}',
|
||||
'{"start":"2024-03-02","lastPlanningDate":"2024-03-01"}',
|
||||
])('uses an unsaved default when saved dates are unreadable: %s', (saved) => {
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
localStorage.setItem('calendarSettings.planningDates', saved);
|
||||
const restored = TestBed.runInInjectionContext(() => new CalendarSettingsService());
|
||||
expect(restored.planningHorizon()).toEqual({ start: '2026-10-05', end: '2027-10-05' });
|
||||
expect(localStorage.getItem('calendarSettings.planningDates')).toBe(saved);
|
||||
});
|
||||
|
||||
it('should initialize with default values', () => {
|
||||
expect(service.get('showWeekNumbers')()).toBe(false);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user