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) { 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'])( (name) => mocks[name] ?? require(name), module, module.exports, ); return module.exports.default; } function nodes(tree) { if (Array.isArray(tree)) return tree.flatMap(nodes); if (!tree || typeof tree !== 'object') return []; return [tree, ...nodes(tree.props?.children)]; } const text = (node) => typeof node === 'string' ? node : Array.isArray(node) ? node.map(text).join('') : node?.props ? text(node.props.children) : typeof node === 'number' ? String(node) : ''; const document = { id: 'source-one', document_id: 'original-one', file_name: 'SYNTHETIC source.txt', version_no: 3, source_reference: 'SYNTHETIC exact source', category: 'project_evidence', stage: 'E2', valid_until: '2028-01-02', origin_label: 'SYNTHETIC previous dossier', status: 'active', malware_scan_status: 'clean', media_type: 'text/plain', size_bytes: 100, }; function harness(handler) { const cells = [], calls = [], selections = []; let cursor = 0, props, tree; const Component = load( '../../resources/js/components/desk-document-picker.tsx', { react: { useId: () => 'synthetic-picker', useRef(initial) { const i = cursor++; cells[i] ??= { current: initial }; return cells[i]; }, useState(initial) { const i = cursor++; if (!(i in cells)) cells[i] = initial; return [ cells[i], (value) => { cells[i] = typeof value === 'function' ? value(cells[i]) : value; }, ]; }, }, '@/lib/profile-json': { async profileJson(url, body, method) { calls.push({ url, body, method }); return handler(url); }, }, }, ); props = { caseId: 'synthetic-case', disabled: false, selected: null, onSelect: (doc) => { selections.push(doc); props.selected = doc; }, }; const render = (updates) => { props = { ...props, ...updates }; cursor = 0; tree = Component(props); return tree; }; const find = (type, label) => nodes(tree).find( (n) => n.type === type && (!label || text(n).includes(label)), ); render(); return { render, find, all: () => nodes(tree), calls, selections, text: () => text(tree), }; } const settle = async () => { await new Promise((resolve) => setImmediate(resolve)); }; test('search encodes literal input, returns names/provenance and never associates on search or selection', async () => { const h = harness(async () => ({ documents: [document], next_offset: null, })); h.find('input').props.onChange({ target: { value: '%_ source & dossier' }, }); h.render(); h.find('button', 'Pesquisar documentos').props.onClick(); await settle(); h.render(); assert.equal(h.calls.length, 1); assert.equal(h.calls[0].method, 'GET'); const params = new URL(h.calls[0].url, 'http://localhost').searchParams; assert.equal(params.get('q'), '%_ source & dossier'); assert.equal(params.get('offset'), '0'); assert.match( h.text(), /SYNTHETIC source\.txt.*versão 3.*SYNTHETIC previous dossier.*exact source.*2028-01-02/, ); h.all() .find((n) => n.type === 'input' && n.props.type === 'radio') .props.onChange(); h.render(); assert.deepEqual(h.selections, [document]); assert.equal(h.calls.length, 1); assert.match(h.text(), /Origem seleccionada/); h.find('button', 'Retirar origem seleccionada').props.onClick(); assert.equal(h.selections.at(-1), null); }); test('pagination does not erase a selected source and new searches start at zero', async () => { const h = harness(async (url) => ({ documents: [document], next_offset: url.includes('offset=0') ? 25 : null, })); h.render({ selected: document }); h.find('button', 'Pesquisar documentos').props.onClick(); await settle(); h.render(); h.find('button', 'Próxima página').props.onClick(); await settle(); h.render(); assert.equal( new URL(h.calls.at(-1).url, 'http://localhost').searchParams.get( 'offset', ), '25', ); assert.match(h.text(), /Origem seleccionada/); h.find('button', 'Pesquisar documentos').props.onClick(); await settle(); h.render(); assert.equal( new URL(h.calls.at(-1).url, 'http://localhost').searchParams.get( 'offset', ), '0', ); assert.equal(h.selections.length, 0); }); test('failed or malformed responses retain selected source and do not expose partial replacements', async () => { let failed = true; const h = harness(async () => { if (failed) throw Error('SYNTHETIC denied'); return { documents: {}, next_offset: null }; }); h.render({ selected: document }); h.find('button', 'Pesquisar documentos').props.onClick(); await settle(); h.render(); assert.match(h.text(), /SYNTHETIC denied/); assert.match(h.text(), /Origem seleccionada/); failed = false; h.find('button', 'Pesquisar documentos').props.onClick(); await settle(); h.render(); assert.match(h.text(), /selecção anterior foi conservada/); assert.equal(h.selections.length, 0); }); test('editing the search removes the old page cursor while retaining a deliberate document selection', async () => { const h = harness(async () => ({ documents: [document], next_offset: 25 })); h.render({ selected: document }); h.find('button', 'Pesquisar documentos').props.onClick(); await settle(); h.render(); assert.ok(h.find('button', 'Próxima página')); h.find('input').props.onChange({ target: { value: 'different source' } }); h.render(); assert.equal(h.find('button', 'Próxima página'), undefined); assert.match(h.text(), /Pesquisa alterada/); assert.match(h.text(), /Origem seleccionada/); assert.equal(h.selections.length, 0); }); test('search pending prevents duplicate requests and stale dossier responses are ignored', async () => { let resolve; const h = harness( () => new Promise((r) => { resolve = r; }), ); h.find('button', 'Pesquisar documentos').props.onClick(); h.render(); assert.equal(h.find('button', 'Pesquisar documentos').props.disabled, true); h.find('button', 'Pesquisar documentos').props.onClick(); assert.equal(h.calls.length, 1); h.render({ caseId: 'different-case' }); resolve({ documents: [document], next_offset: null }); await settle(); h.render(); assert.equal( h.all().filter((n) => n.type === 'input' && n.props.type === 'radio') .length, 0, ); h.render({ disabled: true }); h.find('button', 'Pesquisar documentos').props.onClick(); assert.equal(h.calls.length, 1); }); test('Enter searches without submitting the surrounding form and an unlinked upload remains selectable', async () => { const h = harness(async () => ({ documents: [ { ...document, id: null, origin_label: null, source_reference: null, }, ], next_offset: null, })); let prevented = false; h.find('input').props.onKeyDown({ key: 'Enter', preventDefault() { prevented = true; }, }); await settle(); h.render(); assert.equal(prevented, true); assert.match(h.text(), /Ficheiro enviado por si.*Referência por completar/); h.all() .find((n) => n.type === 'input' && n.props.type === 'radio') .props.onChange(); assert.equal(h.selections[0].id, null); assert.equal(h.selections[0].document_id, document.document_id); assert.ok( h .all() .filter((n) => n.type === 'button') .every((n) => n.props.type === 'button'), ); }); test('general and question consumers send chosen source identity without asking for UUIDs or rewriting known source', () => { const general = fs.readFileSync( new URL('../../resources/js/pages/desk/case.tsx', import.meta.url), 'utf8', ); const field = fs.readFileSync( new URL( '../../resources/js/components/desk-attachment-field.tsx', import.meta.url, ), 'utf8', ); assert.match(general, / null; const Component = load('../../resources/js/pages/desk/case.tsx', { react: { useRef(initial) { const i = cursor++; cells[i] ??= { current: initial }; return cells[i]; }, useState(initial) { const i = cursor++; if (!(i in cells)) cells[i] = initial; return [ cells[i], (value) => { cells[i] = typeof value === 'function' ? value(cells[i]) : value; }, ]; }, }, '@/lib/operation-key': { createOperationKey: () => 'synthetic-key' }, '@inertiajs/react': { Link: 'a', router: { reload() {} }, usePage: () => ({ props: { auth: { user: { id: 1 } } }, url: '/balcao-virtual/dossiers/synthetic-case', }), useForm(initial) { const i = formCursor++; forms[i] ??= { data: { ...initial }, errors: {}, processing: false, setData(key, value) { this.data[key] = value; }, transform(callback) { this.transformer = callback; }, post(url, options) { posts.push({ url, data: this.transformer ? this.transformer(this.data) : this.data, options, }); }, reset(...keys) { for (const key of keys) this.data[key] = initial[key]; }, }; return forms[i]; }, }, '@/pages/investment/shared': { Frame: 'main', card: '', input: '', button: '', }, '@/components/investor-guidance': { InvestorGuidance: () => null }, '@/components/desk-case-state-form': { default: () => null }, '@/components/desk-internal-operations': { DocumentReview: () => null }, '@/components/desk-document-picker': { default: Picker }, }); const props = { case: { id: 'synthetic-case', title: 'SYNTHETIC dossier', version: 7, workflow: {}, payload: {}, }, canWrite: true, manager: false, requests: [], services: [], documents: [], workflow: {}, stepStates: {}, states: {}, guidance: null, events: [], links: [], legacyRequest: null, }; function render() { cursor = 0; formCursor = 0; tree = Component(props); } render(); return { render, forms, posts, picker: () => nodes(tree).find((n) => n.type === Picker), all: () => nodes(tree), submit() { nodes(tree) .find( (n) => n.type === 'form' && nodes(n).some((child) => child.type === Picker), ) .props.onSubmit({ preventDefault() {} }); }, }; } test('general association sends the selected link, original version context and consent, preserving selection on failure', () => { const h = generalHarness(); h.forms[2].setData('file', { name: 'SYNTHETIC old local choice' }); h.forms[2].setData('consent', true); h.picker().props.onSelect(document); h.render(); assert.equal(h.forms[2].data.file, null); assert.equal(h.forms[2].data.consent, false); h.forms[2].setData('consent', true); h.submit(); const posted = h.posts[0]; assert.equal(posted.data.version, 7); assert.equal(posted.data.document_id, document.document_id); assert.equal(posted.data.reuse_link_id, document.id); assert.equal(posted.data.file, null); assert.equal(posted.data.source_reference, undefined); h.render(); assert.equal(h.picker().props.selected.id, document.id); posted.options.onSuccess(); h.render(); assert.equal(h.picker().props.selected, null); assert.equal(h.forms[2].data.consent, false); }); test('general association asks only for missing provenance on unlinked uploads and preserves entered text until success', () => { const h = generalHarness(); h.picker().props.onSelect({ ...document, id: null, source_reference: null, }); h.render(); const source = h.all().find((n) => n.type === 'input' && n.props.required); assert.ok(source); source.props.onChange({ target: { value: 'SYNTHETIC missing provenance supplied' }, }); h.render(); h.forms[2].setData('consent', true); h.submit(); assert.equal(h.posts[0].data.document_id, document.document_id); assert.equal(h.posts[0].data.reuse_link_id, ''); assert.equal( h.posts[0].data.source_reference, 'SYNTHETIC missing provenance supplied', ); h.render(); assert.equal( h.forms[2].data.source_reference, 'SYNTHETIC missing provenance supplied', ); h.posts[0].options.onSuccess(); assert.equal(h.forms[2].data.source_reference, ''); });