Evaluate explicit, ordered optimization configurations through one shared planner model. Add six editable presets, durable one-time legacy migration, and consistent completed and cancelled plan provenance. Include functional regressions and the agreed trace-removal and benchmark follow-up specs. Performance acceptance remains in #78 under unchanged gates. Refs: #70, #71, #72, #73, #74, #75, #76, #77, #78
510 lines
20 KiB
TypeScript
510 lines
20 KiB
TypeScript
import { provideZonelessChangeDetection } from '@angular/core';
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
|
import { HarnessLoader, TestKey } from '@angular/cdk/testing';
|
|
import { MatButtonHarness } from '@angular/material/button/testing';
|
|
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 { type MockInstance, vi } from 'vitest';
|
|
import { App } from './app';
|
|
import { CalendarService } from './services/calendar-service';
|
|
import { calculatePlanningInternal } from './services/leave-planner.core';
|
|
|
|
describe('Application Settings', () => {
|
|
const savedBalances = JSON.stringify([
|
|
{
|
|
id: 'saved-balance',
|
|
type: 'cp',
|
|
numberOfDays: 3,
|
|
availableDate: '2026-01-01',
|
|
expiryDate: '2026-05-31',
|
|
},
|
|
]);
|
|
let fixture: ComponentFixture<App>;
|
|
let loader: HarnessLoader;
|
|
let opener: HTMLButtonElement;
|
|
let mediaQuery: EventTarget & { matches: boolean; media: string };
|
|
let calculate: MockInstance<CalendarService['createPlanningCalculation']>;
|
|
|
|
beforeEach(async () => {
|
|
localStorage.clear();
|
|
localStorage.setItem('app.language', JSON.stringify('en'));
|
|
localStorage.setItem('leaveBalances', savedBalances);
|
|
localStorage.setItem('vacationRules', '{"version":1,"rules":[]}');
|
|
localStorage.setItem('unrelated.entry', 'keep this value');
|
|
mediaQuery = Object.assign(new EventTarget(), {
|
|
matches: true,
|
|
media: '(prefers-color-scheme: light)',
|
|
});
|
|
const otherMedia = window.matchMedia('(max-width: 0px)');
|
|
vi.spyOn(window, 'matchMedia').mockImplementation((query) =>
|
|
query === mediaQuery.media
|
|
? (mediaQuery as unknown as MediaQueryList)
|
|
: { ...otherMedia, media: query },
|
|
);
|
|
await createApp();
|
|
});
|
|
|
|
async function createApp(): Promise<void> {
|
|
await TestBed.configureTestingModule({
|
|
imports: [App],
|
|
providers: [provideZonelessChangeDetection()],
|
|
}).compileComponents();
|
|
calculate = vi.spyOn(TestBed.inject(CalendarService), 'createPlanningCalculation');
|
|
fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
|
|
}
|
|
|
|
afterEach(() => {
|
|
fixture?.destroy();
|
|
vi.restoreAllMocks();
|
|
vi.useRealTimers();
|
|
localStorage.clear();
|
|
document.body.classList.remove('light-mode', 'dark-mode');
|
|
});
|
|
|
|
async function openSettings(): Promise<MatDialogHarness> {
|
|
const root = fixture.nativeElement as HTMLElement;
|
|
opener = root.querySelector<HTMLButtonElement>(
|
|
'header.app-header button[aria-haspopup="dialog"]',
|
|
)!;
|
|
expect(opener).toBeDefined();
|
|
opener.focus();
|
|
opener.click();
|
|
return loader.getHarness(MatDialogHarness);
|
|
}
|
|
|
|
async function selectOption(select: MatSelectHarness, text: string): Promise<void> {
|
|
await select.open();
|
|
for (const option of await loader.getAllHarnesses(MatOptionHarness)) {
|
|
if ((await option.getText()) === text) {
|
|
await option.click();
|
|
await expect.poll(() => document.querySelector('[role="listbox"]')).toBeNull();
|
|
return;
|
|
}
|
|
}
|
|
throw new Error(`No option labelled ${text}`);
|
|
}
|
|
|
|
function changeSystemAppearance(isLight: boolean): void {
|
|
mediaQuery.matches = isLight;
|
|
mediaQuery.dispatchEvent(Object.assign(new Event('change'), { matches: isLight }));
|
|
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('keeps random starting balances unsaved through calculation and restores the first edited list', async () => {
|
|
vi.useFakeTimers({ toFake: ['Date'] });
|
|
vi.setSystemTime(new Date(2026, 9, 4, 12));
|
|
const random = vi
|
|
.spyOn(Math, 'random')
|
|
.mockReturnValueOnce(0)
|
|
.mockReturnValueOnce(0.5)
|
|
.mockReturnValue(1 - Number.EPSILON);
|
|
localStorage.removeItem('leaveBalances');
|
|
await revisitApp(true);
|
|
|
|
const root = fixture.nativeElement as HTMLElement;
|
|
expect(
|
|
Array.from(root.querySelectorAll<HTMLInputElement>('app-day-input input')).map(
|
|
(input) => input.value,
|
|
),
|
|
).toEqual([
|
|
'10',
|
|
'04/10/2026',
|
|
'04/10/2027',
|
|
'15',
|
|
'04/10/2026',
|
|
'04/10/2027',
|
|
'20',
|
|
'04/10/2026',
|
|
'04/10/2027',
|
|
]);
|
|
const types = await loader.getAllHarnesses(
|
|
MatSelectHarness.with({ selector: 'app-day-input mat-select' }),
|
|
);
|
|
expect(await Promise.all(types.map((type) => type.getValueText()))).toEqual([
|
|
'Paid Leave',
|
|
'RTT',
|
|
'Other',
|
|
]);
|
|
expect(calculate).toHaveBeenCalledOnce();
|
|
const initialRequest = calculate.mock.calls[0][0];
|
|
expect(initialRequest.balances.map((balance) => balance.quantity)).toEqual([10, 15, 20]);
|
|
expect(calculatePlanningInternal(initialRequest).kind).toBe('Plan');
|
|
await expect.poll(() => root.querySelector('.planning-status')).not.toBeNull();
|
|
expect(localStorage.getItem('leaveBalances')).toBeNull();
|
|
|
|
root.querySelector<HTMLButtonElement>('.calculate-button')!.click();
|
|
await fixture.whenStable();
|
|
expect(calculate).toHaveBeenCalledTimes(2);
|
|
expect(calculate.mock.calls[1][0].balances).toEqual(initialRequest.balances);
|
|
await expect.poll(() => root.querySelector('.planning-status')).not.toBeNull();
|
|
expect(localStorage.getItem('leaveBalances')).toBeNull();
|
|
|
|
const quantity = root.querySelector<HTMLInputElement>(
|
|
'app-day-input input[type="number"]',
|
|
)!;
|
|
quantity.value = '7';
|
|
quantity.dispatchEvent(new Event('input'));
|
|
await fixture.whenStable();
|
|
const saved = localStorage.getItem('leaveBalances');
|
|
expect(JSON.parse(saved!)).toMatchObject([
|
|
{ type: 'cp', numberOfDays: 7, availableDate: '2026-10-04', expiryDate: '2027-10-04' },
|
|
{
|
|
type: 'rtt',
|
|
numberOfDays: 15,
|
|
availableDate: '2026-10-04',
|
|
expiryDate: '2027-10-04',
|
|
},
|
|
{
|
|
type: 'other',
|
|
numberOfDays: 20,
|
|
availableDate: '2026-10-04',
|
|
expiryDate: '2027-10-04',
|
|
},
|
|
]);
|
|
|
|
random.mockReturnValue(0);
|
|
await revisitApp(true);
|
|
expect(
|
|
Array.from(
|
|
fixture.nativeElement.querySelectorAll('app-day-input input[type="number"]'),
|
|
(input) => (input as HTMLInputElement).value,
|
|
),
|
|
).toEqual(['7', '15', '20']);
|
|
expect(calculate).toHaveBeenCalledOnce();
|
|
expect(calculate.mock.calls[0][0].balances).toEqual(
|
|
initialRequest.balances.map((balance, index) =>
|
|
index === 0 ? { ...balance, quantity: 7 } : balance,
|
|
),
|
|
);
|
|
expect(localStorage.getItem('leaveBalances')).toBe(saved);
|
|
});
|
|
|
|
it('calculates with a saved empty balance list without generating replacement rows', async () => {
|
|
localStorage.setItem('leaveBalances', '[]');
|
|
const random = vi.spyOn(Math, 'random');
|
|
await revisitApp(true);
|
|
|
|
expect(fixture.nativeElement.querySelector('app-day-input')).toBeNull();
|
|
expect(calculate).toHaveBeenCalledOnce();
|
|
expect(calculate.mock.calls[0][0].balances).toEqual([]);
|
|
expect(calculatePlanningInternal(calculate.mock.calls[0][0]).kind).toBe('Plan');
|
|
await expect
|
|
.poll(() => fixture.nativeElement.querySelector('.planning-status'))
|
|
.not.toBeNull();
|
|
expect(random).not.toHaveBeenCalled();
|
|
expect(localStorage.getItem('leaveBalances')).toBe('[]');
|
|
});
|
|
|
|
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);
|
|
expect(await dialog.getTitleText()).toBe('Settings');
|
|
expect(await dialog.getRole()).toBe('dialog');
|
|
const titleId = await dialog.getAriaLabelledby();
|
|
expect(document.getElementById(titleId!)?.textContent?.trim()).toBe('Settings');
|
|
const selects = await dialog.getAllHarnesses(MatSelectHarness);
|
|
expect(selects).toHaveLength(2);
|
|
expect(await selects[0].getLabel()).toBe('Theme');
|
|
expect(await selects[1].getLabel()).toBe('Language');
|
|
expect(await selects[0].getValueText()).toBe('System');
|
|
expect(await selects[1].getValueText()).toBe('English');
|
|
expect(await dialog.getHarness(MatSlideToggleHarness)).toBeDefined();
|
|
expect(
|
|
fixture.nativeElement.querySelector('app-calendar-year app-calendar-settings'),
|
|
).toBeNull();
|
|
expect(
|
|
fixture.nativeElement.querySelector('app-user-input app-rules-panel'),
|
|
).not.toBeNull();
|
|
expect(await dialog.getAllHarnesses(MatButtonHarness)).toHaveLength(1);
|
|
});
|
|
|
|
it('saves planning dates before balances and recalculates only when Calculate is selected', async () => {
|
|
const root = fixture.nativeElement as HTMLElement;
|
|
const period = root.querySelector<HTMLElement>('app-user-input .planning-dates')!;
|
|
expect(
|
|
period.compareDocumentPosition(root.querySelector('.listOfDays')!) &
|
|
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
).toBeTruthy();
|
|
expect(period.querySelector('legend')?.textContent).toBe('Planning period');
|
|
expect(period.querySelector('#planning-dates-help')?.textContent).toContain(
|
|
'Calculate plan',
|
|
);
|
|
const start = period.querySelector<HTMLInputElement>('input[name="planningStart"]')!;
|
|
const last = period.querySelector<HTMLInputElement>('input[name="lastPlanningDate"]')!;
|
|
start.value = '29/02/2024';
|
|
start.dispatchEvent(new Event('input'));
|
|
last.value = '08/03/2024';
|
|
last.dispatchEvent(new Event('input'));
|
|
await fixture.whenStable();
|
|
expect(calculate).toHaveBeenCalledOnce();
|
|
expect(JSON.parse(localStorage.getItem('calendarSettings.planningDates')!)).toEqual({
|
|
start: '2024-02-29',
|
|
lastPlanningDate: '2024-03-08',
|
|
});
|
|
|
|
root.querySelector<HTMLButtonElement>('.calculate-button')!.click();
|
|
await fixture.whenStable();
|
|
expect(calculate).toHaveBeenCalledTimes(2);
|
|
expect(calculate.mock.calls[1][0].horizon).toEqual({
|
|
start: '2024-02-29',
|
|
end: '2024-03-09',
|
|
});
|
|
const dialog = await openSettings();
|
|
expect(await dialog.getContentText()).not.toContain('Planning period');
|
|
const [, language] = await dialog.getAllHarnesses(MatSelectHarness);
|
|
await selectOption(language, 'Français');
|
|
expect(period.querySelector('legend')?.textContent).toBe('Période à planifier');
|
|
await dialog.close();
|
|
|
|
await revisitApp(true);
|
|
expect(fixture.nativeElement.querySelector('input[name="planningStart"]').value).toBe(
|
|
'29/02/2024',
|
|
);
|
|
expect(fixture.nativeElement.querySelector('input[name="lastPlanningDate"]').value).toBe(
|
|
'08/03/2024',
|
|
);
|
|
});
|
|
|
|
it('supports keyboard theme selection and moves focus into the dialog', async () => {
|
|
const dialog = await openSettings();
|
|
const [theme] = await dialog.getAllHarnesses(MatSelectHarness);
|
|
await expect
|
|
.poll(() => document.querySelector('[role="dialog"]')?.contains(document.activeElement))
|
|
.toBe(true);
|
|
await theme.focus();
|
|
expect(await theme.isFocused()).toBe(true);
|
|
expect(document.querySelector('[role="dialog"]')?.contains(document.activeElement)).toBe(
|
|
true,
|
|
);
|
|
const control = await theme.host();
|
|
await control.sendKeys(TestKey.ENTER);
|
|
expect(await theme.isOpen()).toBe(true);
|
|
await control.sendKeys(TestKey.END, TestKey.ENTER);
|
|
expect(await theme.getValueText()).toBe('Dark');
|
|
expect(document.body.classList.contains('dark-mode')).toBe(true);
|
|
expect(localStorage.getItem('theme.preference')).toBe('"dark"');
|
|
});
|
|
|
|
it('follows live System appearance and keeps explicit theme selections fixed', async () => {
|
|
expect(document.body.classList.contains('light-mode')).toBe(true);
|
|
const dialog = await openSettings();
|
|
const [theme] = await dialog.getAllHarnesses(MatSelectHarness);
|
|
expect(await dialog.getContentText()).toMatch(/Current system appearance:\s+Light/);
|
|
expect(localStorage.getItem('theme.preference')).toBeNull();
|
|
|
|
changeSystemAppearance(false);
|
|
expect(await theme.getValueText()).toBe('System');
|
|
expect(await dialog.getContentText()).toMatch(/Current system appearance:\s+Dark/);
|
|
expect(document.body.classList.contains('dark-mode')).toBe(true);
|
|
expect(document.body.classList.contains('light-mode')).toBe(false);
|
|
expect(localStorage.getItem('theme.preference')).toBeNull();
|
|
|
|
await selectOption(theme, 'Light');
|
|
changeSystemAppearance(true);
|
|
changeSystemAppearance(false);
|
|
expect(await theme.getValueText()).toBe('Light');
|
|
expect(document.body.classList.contains('light-mode')).toBe(true);
|
|
expect(document.body.classList.contains('dark-mode')).toBe(false);
|
|
await selectOption(theme, 'Dark');
|
|
changeSystemAppearance(true);
|
|
expect(await theme.getValueText()).toBe('Dark');
|
|
expect(document.body.classList.contains('dark-mode')).toBe(true);
|
|
expect(document.body.classList.contains('light-mode')).toBe(false);
|
|
|
|
await selectOption(theme, 'System');
|
|
expect(document.body.classList.contains('light-mode')).toBe(true);
|
|
changeSystemAppearance(false);
|
|
expect(document.body.classList.contains('dark-mode')).toBe(true);
|
|
expect(localStorage.getItem('theme.preference')).toBe('"system"');
|
|
});
|
|
|
|
it.each(['Close', 'Escape'])(
|
|
'applies and saves selections immediately, retaining them after %s',
|
|
async (closing) => {
|
|
const dialog = await openSettings();
|
|
const [theme, language] = await dialog.getAllHarnesses(MatSelectHarness);
|
|
await selectOption(theme, 'Dark');
|
|
expect(document.body.classList.contains('dark-mode')).toBe(true);
|
|
expect(localStorage.getItem('theme.preference')).toBe('"dark"');
|
|
await selectOption(language, 'Français');
|
|
expect(localStorage.getItem('app.language')).toBe('"fr"');
|
|
expect(await dialog.getTitleText()).toBe('Paramètres');
|
|
expect(await theme.getValueText()).toBe('Sombre');
|
|
expect(fixture.nativeElement.querySelector('header.app-header p').textContent).toBe(
|
|
'Faites plus de place aux vacances.',
|
|
);
|
|
expect(fixture.nativeElement.querySelector('#leave-settings-title').textContent).toBe(
|
|
'Configurez vos congés',
|
|
);
|
|
expect(fixture.nativeElement.querySelector('.week-number')).toBeNull();
|
|
await (await dialog.getHarness(MatSlideToggleHarness)).check();
|
|
expect(localStorage.getItem('calendarSettings.showWeekNumbers')).toBe('true');
|
|
expect(localStorage.getItem('leaveBalances')).toBe(savedBalances);
|
|
expect(localStorage.getItem('vacationRules')).toBe('{"version":1,"rules":[]}');
|
|
expect(localStorage.getItem('unrelated.entry')).toBe('keep this value');
|
|
expect(fixture.nativeElement.querySelectorAll('.week-number').length).toBeGreaterThan(
|
|
0,
|
|
);
|
|
|
|
if (closing === 'Close') {
|
|
const closeButton = await dialog.getHarness(MatButtonHarness);
|
|
expect(await closeButton.getText()).toBe('Fermer');
|
|
await closeButton.click();
|
|
} else {
|
|
await dialog.close();
|
|
}
|
|
await expect
|
|
.poll(async () => (await loader.getAllHarnesses(MatDialogHarness)).length)
|
|
.toBe(0);
|
|
expect(document.activeElement).toBe(opener);
|
|
expect(fixture.nativeElement.querySelector('.week-number')).not.toBeNull();
|
|
const reopened = await openSettings();
|
|
const [restoredTheme, restoredLanguage] =
|
|
await reopened.getAllHarnesses(MatSelectHarness);
|
|
expect(await restoredTheme.getValueText()).toBe('Sombre');
|
|
expect(await restoredLanguage.getValueText()).toBe('Français');
|
|
expect(await (await reopened.getHarness(MatSlideToggleHarness)).isChecked()).toBe(true);
|
|
|
|
fixture.destroy();
|
|
TestBed.resetTestingModule();
|
|
await createApp();
|
|
expect(localStorage.getItem('leaveBalances')).toBe(savedBalances);
|
|
expect(localStorage.getItem('vacationRules')).toBe('{"version":1,"rules":[]}');
|
|
expect(localStorage.getItem('unrelated.entry')).toBe('keep this value');
|
|
expect(document.body.classList.contains('dark-mode')).toBe(true);
|
|
expect(
|
|
fixture.nativeElement.querySelector('header.app-header button').textContent,
|
|
).toContain('Paramètres');
|
|
expect(fixture.nativeElement.querySelector('.week-number')).not.toBeNull();
|
|
const freshDialog = await openSettings();
|
|
const [savedTheme, savedLanguage] = await freshDialog.getAllHarnesses(MatSelectHarness);
|
|
expect(await savedTheme.getValueText()).toBe('Sombre');
|
|
expect(await savedLanguage.getValueText()).toBe('Français');
|
|
expect(await (await freshDialog.getHarness(MatSlideToggleHarness)).isChecked()).toBe(
|
|
true,
|
|
);
|
|
},
|
|
);
|
|
});
|