This commit is contained in:
1 parent
578aafbf91
commit
b0ba4227ac
5 files changed
+426
-8
No files matched your search
@@ -7,9 +7,10 @@ import { MatOptionHarness } from '@angular/material/core/testing';
|
||||
import { MatDialogHarness } from '@angular/material/dialog/testing';
|
||||
import { MatSelectHarness } from '@angular/material/select/testing';
|
||||
import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { type MockInstance, vi } from 'vitest';
|
||||
import { App } from './app';
|
||||
import { UserInputService } from './services/user-input-service';
|
||||
import { CalendarService } from './services/calendar-service';
|
||||
import { calculatePlanningInternal } from './services/leave-planner.core';
|
||||
|
||||
describe('Application Settings', () => {
|
||||
const savedBalances = JSON.stringify([
|
||||
@@ -25,6 +26,7 @@ describe('Application Settings', () => {
|
||||
let loader: HarnessLoader;
|
||||
let opener: HTMLButtonElement;
|
||||
let mediaQuery: EventTarget & { matches: boolean; media: string };
|
||||
let calculate: MockInstance<CalendarService['createPlanningCalculation']>;
|
||||
|
||||
beforeEach(async () => {
|
||||
localStorage.clear();
|
||||
@@ -50,7 +52,7 @@ describe('Application Settings', () => {
|
||||
imports: [App],
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
}).compileComponents();
|
||||
TestBed.inject(UserInputService).vacationNumberSignal.set([]);
|
||||
calculate = vi.spyOn(TestBed.inject(CalendarService), 'createPlanningCalculation');
|
||||
fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
@@ -93,6 +95,121 @@ describe('Application Settings', () => {
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
async function revisitApp(isLight: boolean): Promise<void> {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
mediaQuery = Object.assign(new EventTarget(), {
|
||||
matches: isLight,
|
||||
media: '(prefers-color-scheme: light)',
|
||||
});
|
||||
await createApp();
|
||||
}
|
||||
|
||||
it('passes restored expired balances to the initial calculation without rewriting storage', () => {
|
||||
const root = fixture.nativeElement as HTMLElement;
|
||||
expect(
|
||||
root.querySelector<HTMLInputElement>('app-day-input input[type="number"]')?.value,
|
||||
).toBe('3');
|
||||
expect(root.querySelectorAll<HTMLInputElement>('.date-fields input')[1].value).toBe(
|
||||
'31/05/2026',
|
||||
);
|
||||
expect(calculate).toHaveBeenCalledOnce();
|
||||
const request = calculate.mock.calls[0][0];
|
||||
expect(request.balances).toMatchObject([
|
||||
{
|
||||
id: 'saved-balance',
|
||||
type: 'cp',
|
||||
quantity: 3,
|
||||
availableFrom: '2026-01-01',
|
||||
expiryDate: '2026-05-31',
|
||||
},
|
||||
]);
|
||||
const outcome = calculatePlanningInternal(request);
|
||||
expect(outcome.kind).toBe('Plan');
|
||||
if (outcome.kind !== 'Plan')
|
||||
throw new Error('Expected the restored balance to be readable.');
|
||||
expect(outcome.remainingBalances).toMatchObject([
|
||||
{
|
||||
id: 'saved-balance',
|
||||
quantity: 3,
|
||||
availableFrom: '2026-01-01',
|
||||
expiryDate: '2026-05-31',
|
||||
classification: 'unavoidable-expiry',
|
||||
},
|
||||
]);
|
||||
expect(localStorage.getItem('leaveBalances')).toBe(savedBalances);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['negative quantity', { numberOfDays: -1 }, 'balance-quantity'],
|
||||
['reversed dates', { availableDate: '2026-06-01' }, 'balance-dates'],
|
||||
])('leaves readable saved inputs (%s) to planner validation', async (_, invalid, reason) => {
|
||||
const saved = JSON.stringify([
|
||||
{
|
||||
id: 'saved-balance',
|
||||
type: 'cp',
|
||||
numberOfDays: 3,
|
||||
availableDate: '2026-01-01',
|
||||
expiryDate: '2026-05-31',
|
||||
...invalid,
|
||||
},
|
||||
]);
|
||||
localStorage.setItem('leaveBalances', saved);
|
||||
await revisitApp(true);
|
||||
await expect
|
||||
.poll(() => fixture.nativeElement.querySelector('.reason-list')?.textContent)
|
||||
.toContain(reason.replaceAll('-', ' '));
|
||||
expect(localStorage.getItem('leaveBalances')).toBe(saved);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['light', 'Clair', false],
|
||||
['dark', 'Sombre', true],
|
||||
] as const)(
|
||||
'restores %s, language, and week numbers before presenting their controls',
|
||||
async (preference, label, isLight) => {
|
||||
localStorage.setItem('theme.preference', JSON.stringify(preference));
|
||||
localStorage.setItem('app.language', '"fr"');
|
||||
localStorage.setItem('calendarSettings.showWeekNumbers', 'true');
|
||||
await revisitApp(isLight);
|
||||
|
||||
expect(document.body.classList.contains(`${preference}-mode`)).toBe(true);
|
||||
expect(fixture.nativeElement.querySelector('.week-number')).not.toBeNull();
|
||||
const dialog = await openSettings();
|
||||
const [theme, language] = await dialog.getAllHarnesses(MatSelectHarness);
|
||||
expect(await dialog.getTitleText()).toBe('Paramètres');
|
||||
expect(await theme.getValueText()).toBe(label);
|
||||
expect(await language.getValueText()).toBe('Français');
|
||||
expect(await (await dialog.getHarness(MatSlideToggleHarness)).isChecked()).toBe(true);
|
||||
changeSystemAppearance(!isLight);
|
||||
changeSystemAppearance(isLight);
|
||||
expect(document.body.classList.contains(`${preference}-mode`)).toBe(true);
|
||||
expect(localStorage.getItem('theme.preference')).toBe(JSON.stringify(preference));
|
||||
expect(localStorage.getItem('leaveBalances')).toBe(savedBalances);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([true, false])(
|
||||
'restores System using the current device appearance (%s)',
|
||||
async (isLight) => {
|
||||
localStorage.setItem('theme.preference', '"system"');
|
||||
await revisitApp(isLight);
|
||||
expect(document.body.classList.contains(isLight ? 'light-mode' : 'dark-mode')).toBe(
|
||||
true,
|
||||
);
|
||||
const dialog = await openSettings();
|
||||
const [theme] = await dialog.getAllHarnesses(MatSelectHarness);
|
||||
expect(await theme.getValueText()).toBe('System');
|
||||
changeSystemAppearance(!isLight);
|
||||
expect(document.body.classList.contains(isLight ? 'dark-mode' : 'light-mode')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(await theme.getValueText()).toBe('System');
|
||||
expect(localStorage.getItem('theme.preference')).toBe('"system"');
|
||||
expect(localStorage.getItem('leaveBalances')).toBe(savedBalances);
|
||||
},
|
||||
);
|
||||
|
||||
it('opens one labelled Settings dialog from the header and keeps planner inputs in place', async () => {
|
||||
const dialog = await openSettings();
|
||||
expect(await loader.getAllHarnesses(MatDialogHarness)).toHaveLength(1);
|
||||
|
||||
Reference in new issue
Block a user