import { describe, it, expect, beforeEach } from 'vitest'; import { useAppStore } from '@/store/appStore'; describe('appStore — CRUD', () => { beforeEach(async () => { // Reset to logged-in fresh state useAppStore.setState({ data: { workEntries: [], payments: [], expenses: [], taxInputs: {}, dashboard: { charts: [], widgets: [] }, settings: { theme: 'standard', mode: 'dark', storageMode: 'cookie', defaultRate: 50 }, version: 1, }, localAuth: { unlocked: true, username: 'test' }, cloudAuth: { token: null, email: null, provider: null }, vault: null, // no persistence in tests }); }); it('addWorkEntry assigns id and timestamps', () => { const e = useAppStore.getState().addWorkEntry({ date: '2024-01-01', description: 'code review', amount: 150, }); expect(e.id).toBeTruthy(); expect(e.createdAt).toBeGreaterThan(0); expect(useAppStore.getState().data.workEntries).toHaveLength(1); }); it('updateWorkEntry patches fields', () => { const e = useAppStore.getState().addWorkEntry({ date: '2024-01-01', description: 'old', amount: 100, }); useAppStore.getState().updateWorkEntry(e.id, { description: 'new' }); const updated = useAppStore.getState().data.workEntries[0]; expect(updated.description).toBe('new'); expect(updated.amount).toBe(100); // unchanged expect(updated.updatedAt).toBeGreaterThanOrEqual(e.createdAt); }); it('deleteWorkEntry removes it', () => { const e = useAppStore.getState().addWorkEntry({ date: '2024-01-01', description: 'x', amount: 1, }); useAppStore.getState().deleteWorkEntry(e.id); expect(useAppStore.getState().data.workEntries).toHaveLength(0); }); it('addPayment / addExpense follow same pattern', () => { const p = useAppStore.getState().addPayment({ date: '2024-01-01', amount: 5000, payer: 'Acme', }); const ex = useAppStore.getState().addExpense({ date: '2024-01-01', amount: 200, description: 'laptop', deductible: true, }); expect(p.id).toBeTruthy(); expect(ex.id).toBeTruthy(); expect(useAppStore.getState().data.payments).toHaveLength(1); expect(useAppStore.getState().data.expenses).toHaveLength(1); }); it('mutations bump version counter', () => { const v0 = useAppStore.getState().data.version; useAppStore.getState().addWorkEntry({ date: '2024-01-01', description: 'x' }); const v1 = useAppStore.getState().data.version; expect(v1).toBe(v0 + 1); }); it('setTaxInputs merges per-year', () => { useAppStore.getState().setTaxInputs(2024, { priorYearAGI: 50000 }); useAppStore.getState().setTaxInputs(2024, { priorYearTax: 6000 }); const ti = useAppStore.getState().data.taxInputs[2024]; expect(ti.priorYearAGI).toBe(50000); expect(ti.priorYearTax).toBe(6000); expect(ti.filingStatus).toBe('single'); }); it('chart add/update/remove', () => { useAppStore.getState().addChart({ title: 'test chart' }); const charts = useAppStore.getState().data.dashboard.charts; expect(charts).toHaveLength(1); const id = charts[0].id; useAppStore.getState().updateChart(id, { type: 'bar' }); expect(useAppStore.getState().data.dashboard.charts[0].type).toBe('bar'); useAppStore.getState().removeChart(id); expect(useAppStore.getState().data.dashboard.charts).toHaveLength(0); }); it('setTheme updates both theme and mode', () => { useAppStore.getState().setTheme('cyberpunk', 'light'); expect(useAppStore.getState().data.settings.theme).toBe('cyberpunk'); expect(useAppStore.getState().data.settings.mode).toBe('light'); }); it('setDefaultRate updates settings', () => { useAppStore.getState().setDefaultRate(85); expect(useAppStore.getState().data.settings.defaultRate).toBe(85); }); }); describe('appStore — auth', () => { beforeEach(() => { useAppStore.getState().logout(); }); it('register creates encrypted vault and unlocks', async () => { await useAppStore.getState().register('alice', 'my-strong-password'); expect(useAppStore.getState().localAuth.unlocked).toBe(true); expect(useAppStore.getState().localAuth.username).toBe('alice'); expect(document.cookie).toContain('t99_alice'); }); it('register fails if user exists', async () => { await useAppStore.getState().register('bob', 'password123'); useAppStore.getState().logout(); await expect( useAppStore.getState().register('bob', 'different') ).rejects.toThrow(); }); it('login succeeds with correct password', async () => { await useAppStore.getState().register('carol', 'secret-pass-123'); useAppStore.getState().addWorkEntry({ date: '2024-01-01', description: 'marker', amount: 999 }); await useAppStore.getState().persist(); useAppStore.getState().logout(); await useAppStore.getState().login('carol', 'secret-pass-123'); expect(useAppStore.getState().localAuth.unlocked).toBe(true); expect(useAppStore.getState().data.workEntries[0].description).toBe('marker'); }); it('login fails with wrong password', async () => { await useAppStore.getState().register('dave', 'correct-pass'); useAppStore.getState().logout(); await expect( useAppStore.getState().login('dave', 'wrong-pass') ).rejects.toThrow(/Wrong password/); }); it('logout locks and clears data', async () => { await useAppStore.getState().register('eve', 'password123'); useAppStore.getState().addWorkEntry({ date: '2024-01-01', description: 'secret' }); useAppStore.getState().logout(); expect(useAppStore.getState().localAuth.unlocked).toBe(false); expect(useAppStore.getState().data.workEntries).toHaveLength(0); }); });