W0 web foundation: Vite + React SPA under /app/, sync-pull ledger, auth, tokens, shared components
Build Kvitt images / build (push) Failing after 1h14m5s
Build Kvitt images / build (push) Failing after 1h14m5s
web/ is the browser client's scaffold: Vite 8, React 19, TypeScript 5.9 strict, React Router 7 (basename /app, every fixed route with a lazy placeholder page), TanStack Query 5 (refetch on focus and every 60 s), openapi-typescript types generated from contracts/openapi.yaml with openapi-fetch. Reads go through GET /sync (no REST reads exist for the ledger, SPEC §7): useLedger() pulls from cursor 0 and then incrementally, restarting on a new epoch or cursor_ahead. Login pairs the browser as "Web · <browser>" with an invite code (E-3); every call sends the SPEC §14.3.1 headers, a 401 signs out. kvitt-domain's split, balances, allocation and settlement rules are ported to web/src/lib and pass every case of contracts/test-vectors. Design tokens from design-spec §2 in tokens.css (light-dark()), Material Symbols Rounded (Apache-2.0) as generated inline SVG, sv/en catalogs generated from docs/ux/strings.csv plus web-strings.csv. Gate: cd web && npm run check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useState } from 'react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Language } from '../i18n/language';
|
||||
import { renderWithProviders } from '../test/render';
|
||||
import { AmountField } from './AmountField';
|
||||
|
||||
function Controlled({ onOre }: { onOre: (ore: number | null) => void }) {
|
||||
const [text, setText] = useState('');
|
||||
return (
|
||||
<AmountField
|
||||
label="Belopp"
|
||||
value={text}
|
||||
onChange={(next, ore) => {
|
||||
setText(next);
|
||||
onOre(ore);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
describe('AmountField', () => {
|
||||
it.each<[Language, string]>([
|
||||
['sv', 'kr'],
|
||||
['en', 'SEK'],
|
||||
])('converts typed text to öre and shows the unit (%s)', async (language, unit) => {
|
||||
let ore: number | null = null;
|
||||
renderWithProviders(<Controlled onOre={(value) => (ore = value)} />, { language });
|
||||
const input = screen.getByLabelText('Belopp');
|
||||
await userEvent.type(input, '1247,605-x');
|
||||
expect(input).toHaveValue('1247,60');
|
||||
expect(ore).toBe(124760);
|
||||
expect(screen.getByText(unit)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user