initial code commit
This commit is contained in:
commit
27bb45f7df
56 changed files with 15106 additions and 0 deletions
151
client/src/__tests__/appStore.test.ts
Normal file
151
client/src/__tests__/appStore.test.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
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);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue