import { describe, it, expect } from 'vitest'; import { generate } from '../generate.js'; import type { ScaffoldUiPrimitivesInput } from '../types.js'; // SmartCodeField — the create-only code field of a coded entity declared // « surchargeable à la création ». Client adaptation of the socle pair // (SmartCodeField + useCodeSuggestion, NOT exported by the npm package); // transport = the package's `api` on the two public /api/codes endpoints. function fixture(overrides: Partial = {}): ScaffoldUiPrimitivesInput { return { projectPath: '/tmp/web', appCode: 'crm', force: false, ...overrides }; } function smartCodeField(): string { const f = generate(fixture()).find((x) => x.path === 'src/components/ui/SmartCodeField.tsx'); if (!f) throw new Error('missing SmartCodeField.tsx'); return f.content; } describe('scaffold-ui-primitives / SmartCodeField — contract', () => { it('is emitted with the AUTO-GENERATED header and the self-contained exports', () => { const c = smartCodeField(); expect(c).toMatch(/AUTO-GENERATED/); expect(c).toMatch(/export function SmartCodeField\(/); expect(c).toMatch(/export function useCodeSuggestion\(/); expect(c).toMatch(/export interface CodeFieldDto/); expect(c).toMatch(/export interface CodeSuggestionDto/); }); it('transports through the package api on the two public endpoints (never a local axios)', () => { const c = smartCodeField(); expect(c).toContain("import { api } from '@atlashub/smartstack';"); expect(c).toContain('/api/codes/'); expect(c).toContain("api.post('/api/codes/suggest'"); expect(c).not.toMatch(/axios|fetch\(/); }); it('sequential mode carries the sanctioned manual-entry toggle (imports/backfill)', () => { const c = smartCodeField(); expect(c).toContain('data-testid="smart-code-manual-toggle"'); expect(c).toContain('smartCode.enterManually'); // Untoggling clears the value — the form then sends NO code. expect(c).toContain("setSequentialManual(false); onChange('')"); }); it("every t() call carries a defaultValue (the package's common bundle may not ship smartCode keys)", () => { const c = smartCodeField(); const calls = [...c.matchAll(/t\('smartCode\.[^']+'/g)].length; const withDefault = [...c.matchAll(/t\('smartCode\.[^']+',\s*\{[^}]*defaultValue/g)].length; expect(calls).toBeGreaterThan(0); expect(withDefault, 'every t(smartCode.*) must pass a defaultValue').toBe(calls); }); it('uses only theme tokens — no hardcoded Tailwind palette, no dark: prefix', () => { const c = smartCodeField(); expect(c).not.toMatch(/\b(?:bg|text|border|ring)-(?:blue|gray|red|green|slate|zinc|indigo|amber)-\d{2,3}\b/); expect(c).not.toMatch(/\bdark:/); }); it('the smartCode i18n namespace ships in the 4 locale files', () => { const files = generate(fixture()); for (const loc of ['fr', 'en', 'it', 'de']) { const f = files.find((x) => x.path === `src/i18n/locales/${loc}/common.json`)!; const json = JSON.parse(f.content); expect(json.smartCode?.label, `${loc} missing smartCode`).toBeTruthy(); expect(json.smartCode?.enterManually).toBeTruthy(); } }); });