import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { createRequire } from 'node:module'; import { compileFunction } from 'node:vm'; import ts from 'typescript'; const require = createRequire(import.meta.url); function load(file, mocks = {}, globals = {}) { const output = ts.transpileModule( fs.readFileSync(new URL(file, import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX, }, }, ).outputText; const module = { exports: {} }; compileFunction(output, [ 'require', 'module', 'exports', ...Object.keys(globals), ])( (name) => mocks[name] ?? require(name), module, module.exports, ...Object.values(globals), ); return module.exports; } const lib = load('../../resources/js/lib/desk-checkpoints.ts'); class ValidationError extends Error { constructor(message, fields = {}) { super(message); this.fields = fields; } } function nodes(tree) { if (Array.isArray(tree)) return tree.flatMap(nodes); if (!tree || typeof tree !== 'object') return []; return [tree, ...nodes(tree.props?.children)]; } const request = { id: 'synthetic-request', version: 1, status: 'draft', payload: { title: 'Original', choices: ['0'], declaration: null }, editable_fields: [], can_edit: true, protocol: null, analyst_id: null, due_at: null, }; const initialView = () => ({ installed: true, request: structuredClone(request), checkpoint: null, }); function serverCheckpoint(body) { return { version: body.version + 1, request_version: body.request_version, updated_at: '2026-11-03T03:00:00Z', payload: structuredClone(body.payload), reason: body.reason, base_payload: structuredClone(request.payload), edited_keys: [...body.edited_keys], }; } function harness(handler) { const cells = [], timers = new Map(), listeners = new Map(), before = []; let cursor = 0, dirty = true, tree, props, sequence = 0, clock = 0; const effects = [], calls = [], recovered = [], blocked = [], busy = []; const react = { useState(initial) { const i = cursor++; if (!cells[i]) cells[i] = { value: typeof initial === 'function' ? initial() : initial, }; return [ cells[i].value, (value) => { const next = typeof value === 'function' ? value(cells[i].value) : value; if (!Object.is(next, cells[i].value)) { cells[i].value = next; dirty = true; } }, ]; }, useRef(initial) { const i = cursor++; cells[i] ??= { current: initial }; return cells[i]; }, useEffect(effect, deps) { const i = cursor++, old = cells[i]; if (!old || deps.some((v, j) => !Object.is(v, old.deps[j]))) { cells[i] = { deps, cleanup: old?.cleanup }; effects.push(() => { cells[i].cleanup?.(); cells[i].cleanup = effect(); }); } }, }; const Component = load( '../../resources/js/components/desk-draft-recovery.tsx', { react, '@inertiajs/react': { router: { on(name, callback) { before.push(callback); return () => { before.splice(before.indexOf(callback), 1); }; }, }, }, '@/lib/profile-json': { ProfileValidationError: ValidationError, async profileJson(url, body = {}, method = 'POST') { calls.push({ url, body: structuredClone(body), method }); return handler(url, body, method); }, }, '@/lib/operation-key': { createOperationKey: () => `synthetic-operation-${++sequence}`, }, '@/lib/desk-checkpoints': lib, }, { window: { addEventListener: (name, fn) => listeners.set(name, fn), removeEventListener: (name) => listeners.delete(name), confirm: () => false, }, setTimeout: (fn) => { const id = ++clock; timers.set(id, fn); return id; }, clearTimeout: (id) => timers.delete(id), }, ).default; props = { id: request.id, version: 1, initial: structuredClone(request.payload), payload: structuredClone(request.payload), reason: '', fields: { title: { label: 'Title' }, choices: { label: 'Choices' }, declaration: { label: 'Declaration' }, }, paused: false, onBusy: (value) => busy.push(value), onBlocked: (value) => blocked.push(value), onRecover: (payload, reason, current) => { recovered.push({ payload, reason, current }); props = { ...props, payload, reason, version: current.version }; dirty = true; }, }; function render(patch = {}) { props = { ...props, ...patch }; dirty = true; flush(); return tree; } function flush() { let loops = 0; while (dirty || effects.length) { if (++loops > 25) throw Error('Effect loop'); if (dirty) { dirty = false; cursor = 0; tree = Component(props); } while (effects.length) effects.shift()(); } return tree; } async function settle() { for (let i = 0; i < 10; i++) { await Promise.resolve(); flush(); } return tree; } return { render, settle, calls, recovered, blocked, busy, listeners, before, timers, get tree() { return tree; }, get props() { return props; }, async advance() { for (const [id, fn] of timers) { timers.delete(id); fn(); } await settle(); }, button(label) { const node = nodes(tree).find( (n) => n.type === 'button' && n.props.children === label, ); assert.ok(node, `Button missing: ${label}`); return node; }, unmount() { for (const cell of cells) cell?.cleanup?.(); }, }; } await test('recovery preserves typed choices, explicit zero and deliberate reverts; concurrent answers require a choice', () => { const base = { title: 'Original', amount: 0, choices: ['0'], declaration: 'declined', }; const saved = { base_payload: base, payload: { ...base, title: 'Saved', choices: ['cotton', '0'] }, edited_keys: ['title', 'choices', 'declaration'], }; const current = { ...base, title: 'External', declaration: 'accepted' }; const result = lib.checkpointRecovery( base, base, [], current, saved, Object.keys(base), ); assert.deepEqual(result.conflicts, ['title', 'declaration']); const chosen = lib.checkpointRecovery( base, base, [], current, saved, Object.keys(base), { title: 'current', declaration: 'saved' }, ); assert.deepEqual(chosen.conflicts, []); assert.equal(chosen.payload.title, 'External'); assert.equal(chosen.payload.declaration, 'declined'); assert.equal(chosen.payload.amount, 0); assert.deepEqual(chosen.payload.choices, ['cotton', '0']); const returned = lib.checkpointRecovery( base, { ...base, title: 'Forbidden' }, ['title'], current, saved, ['amount'], ); assert.equal(returned.payload.title, 'External'); assert.equal( lib.checkpointSignature({ choices: ['0', 'cotton'] }), lib.checkpointSignature({ choices: ['cotton', '0'] }), ); }); await test('opportunity and its preserved context are recovered as one choice without displaying technical snapshots', () => { const base = { context_opportunity: 'old', context_opportunity_snapshot: JSON.stringify({ choice: { opportunity: { title: 'Old opportunity' } }, revision: 1, }), context_reuse: 'old metadata', }; const page = { context_opportunity: 'local', context_opportunity_snapshot: JSON.stringify({ choice: { opportunity: { title: 'Local opportunity' } }, revision: 2, }), context_reuse: 'local metadata', }; const current = { context_opportunity: 'remote', context_opportunity_snapshot: JSON.stringify({ choice: { opportunity: { title: 'Remote opportunity' } }, revision: 3, }), context_reuse: 'remote metadata', }; const result = lib.checkpointRecovery( base, page, Object.keys(page), current, null, Object.keys(page), { context_opportunity: 'page' }, ); assert.deepEqual(result.payload, page); assert.equal(result.rows.length, 1); assert.equal(result.rows[0].page, 'Local opportunity · revisão 2'); assert.deepEqual(result.intent, Object.keys(page)); assert.ok(!JSON.stringify(result.rows).includes('local metadata')); }); await test('autosave is debounced, captures edits during a pending write and never sends submission or consent flags', async () => { let view = initialView(), resolveFirst; const h = harness(async (url, body, method) => { if (method === 'GET') return structuredClone(view); if (!resolveFirst) return new Promise((resolve) => { resolveFirst = () => { view.checkpoint = serverCheckpoint(body); resolve(structuredClone(view)); }; }); view.checkpoint = serverCheckpoint(body); return structuredClone(view); }); h.render(); await h.settle(); assert.equal(h.timers.size, 0); h.render({ payload: { ...request.payload, title: 'First' } }); assert.equal(h.timers.size, 1); await h.advance(); assert.equal(h.calls.filter((c) => c.method === 'POST').length, 1); h.render({ payload: { ...request.payload, title: 'Newer while saving' } }); assert.equal(h.timers.size, 0); resolveFirst(); await h.settle(); assert.equal(h.timers.size, 1); await h.advance(); const posts = h.calls.filter((c) => c.method === 'POST'); assert.equal(posts.length, 2); assert.equal(posts[1].body.version, 1); assert.equal(posts[1].body.payload.title, 'Newer while saving'); assert.equal(Object.hasOwn(posts[0].body, 'submit'), false); assert.equal(Object.hasOwn(posts[0].body, 'consent'), false); assert.equal(Object.hasOwn(posts[0].body, 'context_reviewed'), false); assert.equal(h.timers.size, 0); assert.deepEqual(h.busy, [true, false, true, false]); h.unmount(); }); await test('ambiguous network failure repeats the exact attempt before saving newer local answers', async () => { let view = initialView(), failed = false; const h = harness(async (url, body, method) => { if (method === 'GET') return structuredClone(view); if (!failed) { failed = true; view.checkpoint = serverCheckpoint(body); throw Error('SYNTHETIC connection lost after commit'); } if (body.version === 0) return structuredClone(view); view.checkpoint = serverCheckpoint(body); return structuredClone(view); }); h.render(); await h.settle(); h.render({ payload: { ...request.payload, title: 'Committed but unacknowledged' }, }); await h.advance(); h.render({ payload: { ...request.payload, title: 'Newer' } }); assert.equal(h.timers.size, 0); h.button('Tentar novamente a mesma gravação').props.onClick(); await h.settle(); let posts = h.calls.filter((c) => c.method === 'POST'); assert.deepEqual(posts[0].body, posts[1].body); assert.equal(h.props.payload.title, 'Newer'); await h.advance(); posts = h.calls.filter((c) => c.method === 'POST'); assert.equal(posts[2].body.version, 1); assert.notEqual(posts[2].body.operation_key, posts[1].body.operation_key); h.unmount(); }); await test('server normalization does not cause autosave loops and validation failures wait for an actual edit', async () => { let view = initialView(), invalid = true; const h = harness(async (url, body, method) => { if (method === 'GET') return structuredClone(view); if (invalid) throw new ValidationError('SYNTHETIC invalid type', { 'checkpoint.payload.title': 'Invalid', }); view.checkpoint = serverCheckpoint(body); view.checkpoint.payload.title = body.payload.title.trim() || null; return structuredClone(view); }); h.render(); await h.settle(); h.render({ payload: { ...request.payload, title: ' text ' } }); await h.advance(); await h.settle(); assert.equal(h.timers.size, 0); invalid = false; h.render({ payload: { ...request.payload, title: ' corrected ' } }); await h.settle(); assert.equal(h.timers.size, 1); await h.advance(); assert.equal(h.timers.size, 0); assert.equal(h.calls.filter((c) => c.method === 'POST').length, 2); h.unmount(); }); await test('stored drafts are compared before adoption and a failed recovery is retried without changing its chosen answers', async () => { let view = initialView(), failed = false; view.request.version = 2; view.request.payload.title = 'External'; view.checkpoint = { ...serverCheckpoint({ version: 0, request_version: 1, payload: { ...request.payload, title: 'Saved', declaration: 'declined', }, reason: 'Saved reason', edited_keys: ['title', 'declaration'], }), base_payload: structuredClone(request.payload), }; const h = harness(async (url, body, method) => { if (method === 'GET') return structuredClone(view); view.checkpoint = serverCheckpoint(body); if (!failed) { failed = true; throw Error('SYNTHETIC recovery response lost'); } return structuredClone(view); }); h.render(); await h.settle(); assert.equal(h.recovered.length, 0); assert.equal( h.button('Recuperar as respostas escolhidas').props.disabled, true, ); const select = nodes(h.tree).find( (n) => n.type === 'select' && n.props.value === '', ); select.props.onChange({ target: { value: 'saved' } }); await h.settle(); assert.equal( h.button('Recuperar as respostas escolhidas').props.disabled, false, ); h.button('Recuperar as respostas escolhidas').props.onClick(); await h.settle(); assert.equal(h.recovered.length, 0); h.button('Recuperar as respostas escolhidas').props.onClick(); await h.settle(); assert.equal(h.recovered.length, 1); assert.equal(h.recovered[0].current.version, 2); assert.equal(h.recovered[0].payload.title, 'Saved'); assert.equal(h.recovered[0].payload.declaration, 'declined'); const posts = h.calls.filter((c) => c.method === 'POST'); assert.deepEqual(posts[0].body, posts[1].body); assert.equal(h.blocked.at(-1), false); h.unmount(); }); await test('manual operations pause autosave, unavailable installation leaves manual access, and unsaved navigation can be cancelled', async () => { let view = initialView(); const h = harness(async (url, body, method) => { if (method === 'GET') return structuredClone(view); view.checkpoint = serverCheckpoint(body); return structuredClone(view); }); h.render(); await h.settle(); h.render({ payload: { ...request.payload, title: 'Unsaved' }, paused: true, }); assert.equal(h.timers.size, 0); let prevented = false; h.before[0]({ detail: { visit: { method: 'get' } }, preventDefault() { prevented = true; }, }); assert.equal(prevented, true); h.render({ paused: false }); await h.advance(); assert.equal(h.timers.size, 0); h.unmount(); assert.equal(h.before.length, 0); assert.equal(h.listeners.size, 0); view.installed = false; const fallback = harness(async () => structuredClone(view)); fallback.render(); await fallback.settle(); fallback.render({ payload: { title: 'Manual still available' } }); assert.equal(fallback.timers.size, 0); assert.ok(!fallback.blocked.includes(true)); fallback.unmount(); }); await test('request recovery adopts the actual server version and returned scope while clearing submission and context approvals', () => { const cells = [], forms = [], sent = []; let stateCursor = 0, formCursor = 0; const Empty = () => null, Recovery = () => null, Attachment = () => null; const Page = load('../../resources/js/pages/desk/request.tsx', { react: { useState(initial) { const i = stateCursor++; if (!(i in cells)) cells[i] = initial; return [ cells[i], (value) => (cells[i] = typeof value === 'function' ? value(cells[i]) : value), ]; }, }, '@inertiajs/react': { Link: 'a', useForm(initial) { const i = formCursor++; forms[i] ??= { data: structuredClone(initial), errors: {}, processing: false, setData(value, next) { this.data = typeof value === 'function' ? value(this.data) : { ...this.data, [value]: next }; }, transform(fn) { this.mapping = fn; }, put(url) { sent.push({ url, data: this.mapping ? this.mapping(this.data) : this.data, }); }, }; return forms[i]; }, }, '@/components/desk-draft-recovery': { __esModule: true, default: Recovery, }, '@/components/dossier-prefill': { DossierPrefill: Empty }, '@/components/country-select': { default: Empty }, '@/components/investment-territory': { TerritoryFields: Empty }, '@/components/desk-internal-operations': { DocumentReview: Empty, RequestOperations: Empty, }, '@/components/desk-typed-choice': { default: Empty }, '@/components/desk-attachment-field': { default: Attachment }, '@/lib/operation-key': { createOperationKey: () => 'synthetic' }, '@/lib/desk-field-visibility': load( '../../resources/js/lib/desk-field-visibility.ts', ), '@/lib/desk-field-values': load( '../../resources/js/lib/desk-field-values.ts', ), '@/pages/investment/shared': { Frame: 'main', card: '', input: '', button: '', }, }).default; const props = { request: { ...request, analyst_id: null, due_at: null }, case: { id: 'synthetic-case', title: 'Synthetic' }, definition: { title: 'Synthetic', result: 'Internal', fields: { title: { type: 'text', label: 'Title', required: true }, support: { type: 'attachment', label: 'Support', required: false, max_files: 2, max_size_kb: 2, mime_types: ['text/plain'], }, choices: { type: 'multiselect', label: 'Choices', required: false, options: { 0: 'Zero' }, }, declaration: { type: 'consent', label: 'Declaration', required: false, statement: 'Synthetic', }, }, }, owner: true, manager: false, analyst: false, fieldsEditable: true, states: { draft: 'Draft', returned: 'Returned' }, transitions: [], findings: [], messages: [], analysts: [], territoryPaths: {}, events: [], documents: [], payments: [], caseVersion: 1, countries: [], toolPrefill: null, }; const render = () => { stateCursor = 0; formCursor = 0; return Page(props); }; let tree = render(); forms[0].data.payload.title = 'Deliberate edit during upload'; nodes(tree) .find((n) => n.type === Attachment) .props.onBusy(true); tree = render(); assert.equal( nodes(tree).find((n) => n.type === Recovery).props.pendingWork, true, ); nodes(tree) .find((n) => n.type === 'form') .props.onSubmit({ preventDefault() {} }); assert.equal(sent.length, 0); const uploaded = { id: 'link-first', document_id: 'binary-one', file_name: 'Synthetic original', status: 'quarantined', malware_scan_status: 'pending', }; nodes(tree) .find((n) => n.type === Attachment) .props.onInventory( { documents: [uploaded], case_version: 2, link_id: uploaded.id }, true, ); nodes(tree) .find((n) => n.type === Attachment) .props.onBusy(false); tree = render(); assert.equal(forms[0].data.payload.title, 'Deliberate edit during upload'); assert.deepEqual(forms[0].data.payload.support, ['link-first']); const alias = { ...uploaded, id: 'link-alias' }; nodes(tree) .find((n) => n.type === Attachment) .props.onInventory( { documents: [uploaded, alias], case_version: 2, link_id: alias.id, }, true, ); tree = render(); assert.deepEqual(forms[0].data.payload.support, ['link-first']); props.caseVersion = 2; props.documents = [ { ...uploaded, status: 'active', malware_scan_status: 'clean' }, ]; tree = render(); assert.equal( nodes(tree).find((n) => n.type === Attachment).props.documents[0] .malware_scan_status, 'clean', ); const refreshed = { ...uploaded, malware_scan_status: 'synthetic-new-status', }; nodes(tree) .find((n) => n.type === Attachment) .props.onInventory({ documents: [refreshed], case_version: 2 }, false); tree = render(); assert.equal( nodes(tree).find((n) => n.type === Attachment).props.documents[0] .malware_scan_status, 'synthetic-new-status', ); forms[0].data.consent = true; forms[0].data.context_reviewed = true; forms[0].data.context_revision = 8; const restore = nodes(tree).find((n) => n.type === Recovery); restore.props.onRecover( { title: 'Current official draft', choices: ['0'], declaration: 'declined', }, 'Recovered reason', { ...request, version: 3, status: 'returned', editable_fields: ['choices'], }, ); tree = render(); assert.equal(forms[0].data.version, 3); assert.equal(forms[0].data.consent, false); assert.equal(forms[0].data.context_reviewed, false); assert.equal(forms[0].data.context_revision, 0); assert.equal(forms[0].data.payload.declaration, 'declined'); const form = nodes(tree).find((n) => n.type === 'form'); form.props.onSubmit({ preventDefault() {} }); assert.equal(sent[0].data.version, 3); assert.equal(sent[0].data.submit, false); nodes(tree) .find((n) => n.type === Recovery) .props.onBusy(true); tree = render(); nodes(tree) .find((n) => n.type === 'form') .props.onSubmit({ preventDefault() {} }); assert.equal(sent.length, 1); });