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 { renderToStaticMarkup } from 'react-dom/server'; import ts from 'typescript'; const require = createRequire(import.meta.url); function load(file, mocks, suffix = '') { const output = ts.transpileModule( fs.readFileSync(new URL(file, import.meta.url), 'utf8') + suffix, { 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; } function nodes(tree) { if (!tree || typeof tree !== 'object') return []; if (Array.isArray(tree)) return tree.flatMap(nodes); return [tree, ...nodes(tree.props?.children)]; } function form(initial) { return { data: initial, errors: {}, processing: false, setData(value, next) { this.data = typeof value === 'function' ? value(this.data) : { ...this.data, [value]: next }; }, }; } const service = { code: 'project-change', version: 2, effective_from: '2026-11-12', activeVersion: 1, definition: { title: 'SYNTHETIC scheduled form', theme: 'QA', profile: 'Investor', entity: 'Internal', result: 'Internal preparation', stage: 'E10', fields: { question: { label: 'Synthetic question', type: 'text', required: false, }, }, }, }; function editorHarness() { let saved, sent; const Editor = load( '../../resources/js/pages/desk/service-editor.tsx', { react: { useState: (initial) => [initial, () => {}] }, '@inertiajs/react': { Link: 'a', useForm(key, initial) { saved ??= { ...form(initial), put(url) { sent = { url, data: structuredClone(this.data) }; }, }; return saved; }, }, '@/pages/investment/shared': { Frame: 'main', card: '', input: '', button: '', }, '@/components/desk-service-history': { default: () => null }, }, '\nexport { Editor };', ).Editor; return { render: (props = {}) => Editor({ service, schedulingAvailable: true, today: '2026-11-02', calendarTimezone: 'UTC', ...props, }), data: () => saved, sent: () => sent, }; } await test('editing a scheduled edition keeps its future date until changed deliberately and date-only changes require fresh confirmation', () => { const before = JSON.stringify(service); const h = editorHarness(); let tree = h.render(); assert.equal(h.data().data.effective_from, '2026-11-12'); const date = nodes(tree).find( (node) => node.type === 'input' && node.props.type === 'date', ); assert.equal(date.props.min, '2026-11-02'); h.data().data.confirm = true; date.props.onChange({ target: { value: '2026-11-02' } }); assert.equal(h.data().data.confirm, false); tree = h.render(); let html = renderToStaticMarkup(tree); assert.match(html, /Versão actualmente disponível para novos pedidos: 1/); assert.match(html, /Entrada em vigor proposta: 2026-11-02/); assert.match(html, /não cancela uma edição já agendada/); assert.match(html, /Calendário do portal: UTC/); h.data().data.confirm = true; tree = h.render(); const publish = nodes(tree).find( (node) => node.type === 'button' && node.props.children === 'Publicar nova versão', ); assert.equal(publish.props.disabled, false); tree.props.onSubmit({ preventDefault() {} }); assert.equal(h.sent().data.effective_from, '2026-11-02'); assert.equal(h.sent().data.base_version, 2); assert.equal(JSON.stringify(service), before); date.props.onChange({ target: { value: '' } }); html = renderToStaticMarkup(h.render()); assert.match(html, /Entrada em vigor proposta: 2026-11-02/); }); await test('destinations without the migration retain immediate publication and cannot silently present a scheduling control', () => { const h = editorHarness(); const tree = h.render({ schedulingAvailable: false }); assert.equal(h.data().data.effective_from, ''); assert.equal( nodes(tree).some( (node) => node.type === 'input' && node.props.type === 'date', ), false, ); assert.match( renderToStaticMarkup(tree), /agendamento requer a actualização/, ); }); await test('advancing the calendar does not silently propose an unchanged edition, and upcoming editions remain visible', () => { const h = editorHarness(); const props = { service: { ...service, effective_from: '2026-11-01', activeVersion: 2, upcoming: [{ version: 1, effective_from: '2026-11-12' }], }, }; let tree = h.render(props); h.data().data.confirm = true; tree = h.render(props); let publish = nodes(tree).find( (node) => node.type === 'button' && node.props.children === 'Publicar nova versão', ); assert.equal(publish.props.disabled, true); const html = renderToStaticMarkup(tree); assert.match(html, /Edições já agendadas/); assert.match(html, /Versão 1: 2026-11-12/); nodes(tree) .find((node) => node.type === 'input' && node.props.type === 'date') .props.onChange({ target: { value: '2026-11-02' } }); h.data().data.confirm = true; tree = h.render(props); publish = nodes(tree).find( (node) => node.type === 'button' && node.props.children === 'Publicar nova versão', ); assert.equal(publish.props.disabled, false); }); await test('refreshing services retains other case forms and requires an explicit choice when the selected edition is no longer listed', () => { let cursor = 0, stateCursor = 0, refreshed, submitted; const forms = [], states = []; const Case = load('../../resources/js/pages/desk/case.tsx', { react: { useState(initial) { const index = stateCursor++; if (!(index in states)) states[index] = initial; return [ states[index], (value) => { states[index] = typeof value === 'function' ? value(states[index]) : value; }, ]; }, useRef(initial) { const index = stateCursor++; states[index] ??= { current: initial }; return states[index]; }, }, '@/lib/operation-key': { createOperationKey: () => 'qa-operation' }, '@inertiajs/react': { Link: 'a', usePage: () => ({ props: { auth: { user: { id: 3 } } }, url: '/balcao-virtual/dossiers/qa', }), router: { reload(options) { refreshed = options; }, }, useForm(initial) { const index = cursor++; forms[index] ??= { ...form(initial), post(url) { submitted = { url, data: this.data }; }, }; return forms[index]; }, }, '@/pages/investment/shared': { Frame: 'main', card: '', input: '', button: '', }, '@/components/investor-guidance': { InvestorGuidance: () => null }, '@/components/desk-case-state-form': { default: () => null }, '@/components/desk-document-picker': { default: () => null }, '@/components/desk-internal-operations': { DocumentReview: () => null }, }).default; const props = { case: { id: 'qa', title: 'Synthetic case', version: 1, workflow: {} }, canWrite: true, manager: false, requests: [], services: [{ id: 'v1', title: 'Current', version: 1 }], documents: [], workflow: {}, stepStates: {}, states: {}, guidance: null, events: [], links: [], legacyRequest: null, }; const render = (services) => { cursor = 0; stateCursor = 0; return Case({ ...props, services }); }; let tree = render(props.services); forms[1].data.reason = 'Unsaved stage evidence'; tree = render([ { id: 'v2', title: 'New current', version: 2, effective_from: '2026-11-03', }, ]); const html = renderToStaticMarkup(tree); assert.match(html, /versão seleccionada já não está na lista/); assert.match(html, /desde 2026-11-03/); const create = nodes(tree).find( (node) => node.type === 'button' && node.props.children === 'Preparar rascunho de pedido', ); assert.equal(create.props.disabled, true); const requestForm = nodes(tree).find( (node) => node.type === 'form' && nodes(node).includes(create), ); requestForm.props.onSubmit({ preventDefault() {} }); assert.equal(submitted, undefined); nodes(tree) .find( (node) => node.type === 'button' && node.props.children === 'Actualizar versões disponíveis', ) .props.onClick(); assert.deepEqual(refreshed.only, ['services']); assert.equal(forms[1].data.reason, 'Unsaved stage evidence'); const select = nodes(tree).find( (node) => node.type === 'select' && node.props.value === 'v1', ); select.props.onChange({ target: { value: 'v2' } }); tree = render([{ id: 'v2', title: 'New current', version: 2 }]); const ready = nodes(tree).find( (node) => node.type === 'button' && node.props.children === 'Preparar rascunho de pedido', ); assert.equal(ready.props.disabled, false); }); await test('catalogue shows the selected calendar date with the distinction between new and existing requests', () => { const Catalogue = load('../../resources/js/pages/desk/catalogue.tsx', { '@inertiajs/react': { Link: 'a' }, '@/pages/investment/shared': { Frame: 'main', card: '' }, }).default; const html = renderToStaticMarkup( Catalogue({ installed: true, services: [ { id: 'v2', version: 2, effective_from: '2026-11-03', definition: { title: '', result: 'Internal preparation', }, }, ], prepared: {}, }), ); assert.match(html, /novos pedidos desde 2026-11-03/); assert.match(html, /Pedidos anteriores mantêm/); assert.match(html, /<Synthetic public form>/); });