From b76e311576bccb92a71819459f84f44d3c86fdd4 Mon Sep 17 00:00:00 2001
From: Nessar
Date: Sun, 4 Oct 2026 23:33:07 +0200
Subject: [PATCH] #23
---
src/app/app-settings.spec.ts | 106 +++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
diff --git a/src/app/app-settings.spec.ts b/src/app/app-settings.spec.ts
index a647928..80dbcfe 100644
--- a/src/app/app-settings.spec.ts
+++ b/src/app/app-settings.spec.ts
@@ -62,6 +62,7 @@ describe('Application Settings', () => {
afterEach(() => {
fixture?.destroy();
vi.restoreAllMocks();
+ vi.useRealTimers();
localStorage.clear();
document.body.classList.remove('light-mode', 'dark-mode');
});
@@ -105,6 +106,111 @@ describe('Application Settings', () => {
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('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('.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(
+ '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(