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 = {}, suffix = '') { const code = ts.transpileModule( fs.readFileSync(new URL(file, import.meta.url), 'utf8') + suffix, { compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, target: ts.ScriptTarget.ES2022, }, }, ).outputText; const module = { exports: {} }; compileFunction(code, ['require', 'module', 'exports'])( (name) => mocks[name] ?? require(name), module, module.exports, ); return module.exports; } 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 = (tree) => typeof tree === 'string' ? tree : Array.isArray(tree) ? tree.map(text).join('') : tree?.props ? text(tree.props.children) : typeof tree === 'number' ? String(tree) : ''; const Empty = () => null; function harness(file, initial, extra = {}, suffix = '') { const cells = [], forms = [], sent = []; let cursor = 0, formCursor = 0, props = initial, tree; const mocks = { react: { 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; }, ]; }, useRef(initial) { const i = cursor++; cells[i] ??= { current: initial }; return cells[i]; }, }, '@inertiajs/react': { Link: 'a', router: { reload() {}, post() {} }, usePage: () => ({ url: '/synthetic', props: { auth: { user: { id: 1 } } }, }), useForm(initial) { const i = formCursor++; forms[i] ??= { data: structuredClone(initial), errors: {}, processing: false, setData(key, value) { if (typeof key === 'function') this.data = key(this.data); else if (typeof key === 'object') this.data = key; else this.data[key] = value; }, clearErrors() {}, setDefaults() {}, transform(callback) { this.transformer = callback; }, post(url, options) { sent.push({ url, options, data: this.transformer ? this.transformer(this.data) : this.data, }); }, patch(url, options) { sent.push({ url, options, data: this.data }); }, }; return forms[i]; }, }, '@/pages/investment/shared': { Frame: 'main', card: '', input: '', button: '', }, '@/lib/operation-key': { createOperationKey: () => 'synthetic-operation', }, '@/lib/desk-field-values': load( '../../resources/js/lib/desk-field-values.ts', ), '@/lib/desk-field-visibility': load( '../../resources/js/lib/desk-field-visibility.ts', ), '@/components/dossier-prefill': { DossierPrefill: Empty }, '@/components/desk-typed-choice': { default: Empty }, '@/components/desk-draft-recovery': { default: Empty }, '@/components/desk-attachment-field': { default: Empty }, '@/components/country-select': { default: Empty }, '@/components/investment-territory': { TerritoryFields: Empty }, '@/components/desk-internal-operations': { DocumentReview: Empty, RequestOperations: Empty, }, '@/components/investor-guidance': { InvestorGuidance: Empty }, '@/components/desk-case-state-form': { default: Empty }, '@/components/desk-document-picker': { default: Empty }, ...extra, }; mocks['@/lib/crm-record-edit'] = load( '../../resources/js/lib/crm-record-edit.ts', mocks, ); mocks['@/components/crm-edit-comparison'] = { default: Empty }; mocks['@/components/crm-task-creation'] = { default: Empty }; const exports = load(file, mocks, suffix); const Component = exports.TaskRow ?? exports.default ?? exports.InvestorGuidance; function render(patch = {}) { props = { ...props, ...patch }; cursor = 0; formCursor = 0; tree = Component(props); } render(); return { forms, sent, render, all: () => nodes(tree), text: () => text(tree), find: (type, label) => nodes(tree).find( (n) => n.type === type && (!label || text(n).includes(label)), ), }; } const request = { id: 'request-one', version: 1, status: 'draft', payload: { title: 'SYNTHETIC saved title' }, protocol: null, editable_fields: [], analyst_id: null, due_at: null, }; const props = { request, case: { id: 'case-one', title: 'SYNTHETIC dossier' }, definition: { title: 'SYNTHETIC service', fields: { title: { label: 'Project title', type: 'text', required: true }, }, result: 'SYNTHETIC', }, owner: true, manager: false, analyst: false, fieldsEditable: true, operationsEditable: true, states: { draft: 'Rascunho', submitted: 'Submetido', reviewing: 'Em análise', }, transitions: [], findings: [], messages: [], analysts: [], territoryPaths: {}, events: [], documents: [], payments: [], caseVersion: 1, countries: [], toolPrefill: null, }; const page = (patch) => harness('../../resources/js/pages/desk/request.tsx', { ...props, ...patch, }); test('owner cancellation uses the existing transition and restores saved server payload only after success', () => { const h = page(); h.forms[0].setData('payload', { title: 'SYNTHETIC unsaved edit' }); h.forms[1].setData('reason', 'SYNTHETIC deliberate cancellation'); h.find('form', 'Cancelar este pedido').props.onSubmit({ preventDefault() {}, }); assert.equal(h.sent[0].data.status, 'cancelled'); assert.equal(h.sent[0].data.version, 1); assert.equal(h.sent[0].data.reason, 'SYNTHETIC deliberate cancellation'); assert.equal(h.forms[0].data.payload.title, 'SYNTHETIC unsaved edit'); h.sent[0].options.onSuccess({ props: { request: { ...request, status: 'cancelled', version: 2 } }, }); h.render({ request: { ...request, status: 'cancelled', version: 2 }, fieldsEditable: false, operationsEditable: false, }); assert.equal(h.forms[0].data.payload.title, 'SYNTHETIC saved title'); assert.equal(h.forms[0].data.consent, false); assert.equal(h.find('form', 'Cancelar este pedido'), undefined); }); test('cancellation waits for recovery work and is unavailable to submitted requests, reviewers and closed dossiers', () => { const Recovery = () => null; const h = harness('../../resources/js/pages/desk/request.tsx', props, { '@/components/desk-draft-recovery': { default: Recovery }, }); h.all() .find((n) => n.type === Recovery) .props.onBusy(true); h.render(); assert.equal( h.find('button', 'Confirmar cancelamento').props.disabled, true, ); h.find('form', 'Cancelar este pedido').props.onSubmit({ preventDefault() {}, }); assert.equal(h.sent.length, 0); for (const patch of [ { request: { ...request, status: 'submitted' } }, { owner: false, manager: true }, { operationsEditable: false }, ]) assert.equal( page(patch).find('form', 'Cancelar este pedido'), undefined, ); }); test('closed dossier request hides response, assignment and message actions while keeping previous messages visible', () => { const open = { request: { ...request, status: 'submitted' }, owner: false, manager: true, fieldsEditable: false, transitions: ['reviewing'], analysts: [{ id: 2, name: 'SYNTHETIC analyst' }], messages: [ { id: 'message-one', body: 'SYNTHETIC historical message', name: 'SYNTHETIC author', kind: 'responsible_response', created_at: '2026-11-03', }, ], }; const h = page({ ...open, operationsEditable: false }); assert.match(h.text(), /SYNTHETIC historical message/); assert.equal(h.find('button', 'Atribuir acompanhamento'), undefined); assert.equal( h.find('form', 'Resposta / análise do responsável'), undefined, ); assert.equal(h.find('form', 'Mensagem privada'), undefined); assert.ok(page(open).find('form', 'Resposta / análise do responsável')); assert.ok(page(open).find('form', 'Mensagem privada')); }); test('correction explanation applies to returned requests and does not ask draft authors an inapplicable question', () => { assert.doesNotMatch(page().text(), /Justificação de correcção/); assert.match( page({ request: { ...request, status: 'returned', editable_fields: ['title'], }, }).text(), /Justificação de correcção/, ); }); test('closed case keeps workflow and documentary context without stage or review operations', () => { const Review = () => null; const base = { case: { id: 'case-one', title: 'SYNTHETIC dossier', version: 1, closed_at: '2026-11-03', workflow: {}, }, canWrite: false, manager: true, requests: [], services: [], documents: [], workflow: {}, stepStates: {}, states: {}, guidance: null, events: [], links: [], legacyRequest: null, }; const h = harness('../../resources/js/pages/desk/case.tsx', base, { '@/components/desk-internal-operations': { DocumentReview: Review }, }); assert.match(h.text(), /Dossier encerrado/); assert.equal(h.find('button', 'Registar etapa'), undefined); assert.equal( h.all().some((n) => n.type === Review), false, ); h.render({ case: { ...base.case, closed_at: null } }); assert.ok(h.find('button', 'Registar etapa')); assert.equal( h.all().some((n) => n.type === Review), true, ); }); test('CRM linked task preserves its title, status and deadline without an update action when dossier is closed', () => { const task = { id: 'task-one', title: 'SYNTHETIC linked task', status: 'open', due_at: '2026-11-05T12:00:00Z', assignee_user_id: 1, version_no: 1, can_update: false, }; const h = harness( '../../resources/js/pages/crm/lead.tsx', { task, leadId: 'lead-one' }, { '../management/shared': { Management: 'main', Field: 'label', button: '', control: '', }, './types': { taskStates: { open: 'Aberta', done: 'Concluída' }, leadStates: {}, }, }, '\nexport { TaskRow };', ); assert.match(h.text(), /SYNTHETIC linked task/); assert.equal(h.find('select').props.disabled, true); assert.equal(h.find('button', 'Actualizar tarefa'), undefined); h.find('form').props.onSubmit({ preventDefault() {} }); assert.equal(h.sent.length, 0); h.render({ task: { ...task, can_update: true } }); assert.ok(h.find('button', 'Actualizar tarefa')); }); test('guidance hides task changes on a closed dossier but keeps task history and consulted sources', () => { const guidance = { version: 1, fingerprint: 'x'.repeat(64), rules_version: 'SYNTHETIC', computed_at: '2026-11-03', can_write: false, can_update_tasks: false, facts: {}, financial_draft: null, consulted: [], unavailable: [], catalogue_checks: [], phases: [ { id: 'one', objective: 'SYNTHETIC phase', known: [], questions: [], evidence: [], recommendations: [], risks: [], }, ], tasks: [ { id: 'task-one', title: 'SYNTHETIC saved task', status: 'open', due_at: '2026-11-05', assignee_user_id: 1, assignee_name: 'SYNTHETIC actor', version_no: 1, accepted_at: '2026-11-03', desk_request_id: null, }, ], calculator_versions: {}, crm: { reason: 'SYNTHETIC internal context' }, }; const h = harness( '../../resources/js/components/investor-guidance.tsx', { caseId: 'case-one', guidance, manager: true, actorId: 1, requests: [], }, { '@/lib/investor-guidance-finance': { guidanceFinance: () => ({ reason: 'SYNTHETIC no financial draft', result: null, }), guidanceFinancialTools: () => null, }, }, ); assert.match(h.text(), /SYNTHETIC saved task/); assert.doesNotMatch(h.text(), /Resultado ou motivo/); h.render({ guidance: { ...guidance, can_update_tasks: true } }); assert.match(h.text(), /Resultado ou motivo/); });