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 code = ts.transpileModule( fs.readFileSync(new URL(file, import.meta.url), 'utf8'), { 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; } const clone = structuredClone; test('an unconfirmed edit exposes recovery of the same submitted command', () => { const h = harness(); h.set('title', 'SYNTHETIC frozen revision'); h.edit.save(); h.sent[0].options.onNetworkError(); h.render(); assert.equal(h.edit.retryingSave, true); }); test('uncertain edit repeats the frozen operation and retains later typing', () => { const h = harness(); h.set('title', 'SYNTHETIC sent'); h.edit.save(); const first = h.sent[0]; h.set('offer', 'SYNTHETIC later offer'); first.options.onNetworkError(); h.render(); h.edit.recoverSave(); assert.deepEqual(h.sent[1].data, first.data); h.success(1, saved(h.sent[1].data)); assert.equal(h.data.offer, 'SYNTHETIC later offer'); assert.equal(h.edit.retryingSave, false); }); test('refresh recognises an already committed edit from its own operation metadata', () => { const h = harness(); h.set('title', 'SYNTHETIC saved'); h.edit.save(); h.sent[0].options.onNetworkError(); h.render(); h.edit.refresh(); const next = saved(h.sent[0].data); h.render(next); h.reloads[0].onSuccess({ props: next }); h.reloads[0].onFinish(); h.render(); assert.equal(h.edit.retryingSave, false); assert.equal(h.data.version, 3); assert.equal(h.edit.dirty, false); }); test('recognising an edit preserves post-send preparation and does not adopt a later state', () => { const h = harness(); h.set('title', 'SYNTHETIC committed'); h.edit.save(); h.set('title', 'SYNTHETIC later preparation'); h.success(0, saved(h.sent[0].data, { version: 4, status: 'submitted' })); assert.equal(h.data.title, 'SYNTHETIC later preparation'); assert.equal(h.data.version, 3); assert.equal(h.edit.stale, true); assert.equal(h.edit.editable, false); h.edit.adopt(); h.render(); assert.equal(h.data.version, 4); assert.equal(h.data.title, 'SYNTHETIC later preparation'); assert.equal(h.sent.length, 1); }); test('lost access disables recovery until a successful authorized refresh', () => { const h = harness(); h.set('title', 'SYNTHETIC retained'); h.edit.save(); h.sent[0].options.onHttpException({ status: 403 }); h.render(); assert.equal(h.edit.canRecoverSave, false); h.edit.recoverSave(); assert.equal(h.sent.length, 1); h.edit.refresh(); h.reloads[0].onSuccess({ props: props() }); h.reloads[0].onFinish(); h.render(); assert.equal(h.edit.canRecoverSave, true); }); test('new attempt after comparison gets a new key and ignores late failure of the abandoned attempt', () => { const h = harness(); h.set('title', 'SYNTHETIC original attempt'); h.edit.save(); const first = h.sent[0]; first.options.onNetworkError(); h.render(); h.set('title', 'SYNTHETIC new preparation'); h.edit.adopt(); h.render(); assert.equal(h.edit.retryingSave, true); h.edit.refresh(); h.reloads[0].onSuccess({ props: props() }); h.reloads[0].onFinish(); h.render(); h.edit.adopt(); h.render(); h.edit.save(); assert.equal(h.sent.length, 2); assert.notEqual(h.sent[1].data.operation_key, first.data.operation_key); assert.equal(h.sent[1].data.title, 'SYNTHETIC new preparation'); first.options.onNetworkError(); h.render(); assert.equal(h.edit.retryingSave, false); }); test('another account starts a separate preparation and ignores an old acknowledgement', () => { const h = harness(); h.set('title', 'SYNTHETIC former actor draft'); h.edit.save(); const first = h.sent[0], next = props({ editorActorId: 99 }); h.render(next); assert.equal(h.data.title, 'SYNTHETIC original'); first.options.onSuccess({ props: saved(first.data) }); h.render(next); assert.equal(h.data.title, 'SYNTHETIC original'); assert.equal(h.edit.retryingSave, false); }); test('response without matching operation metadata cannot clear the preparation', () => { const h = harness(); h.set('title', 'SYNTHETIC conserved'); h.edit.save(); const next = saved(h.sent[0].data); next.saveCommands = []; h.success(0, next); assert.equal(h.data.title, 'SYNTHETIC conserved'); assert.equal(h.edit.retryingSave, true); assert.equal(h.edit.blocked, true); }); test('recovery is offered outside the disabled editing fieldset in a later read-only state', () => { const h = harness(); h.set('title', 'SYNTHETIC committed'); h.edit.save(); const first = h.sent[0]; first.options.onNetworkError(); const next = saved(first.data, { version: 4, status: 'submitted' }); next.saveCommands = []; h.render(next); assert.equal(h.edit.editable, false); assert.equal(h.edit.canRecoverSave, true); assert.ok( nodes(h.panel()).some( (n) => n.type === 'button' && text(n) === 'Recuperar a mesma gravação', ), ); h.edit.recoverSave(); assert.deepEqual(h.sent[1].data, first.data); h.success(1, saved(first.data, { version: 4, status: 'submitted' })); assert.equal(h.edit.retryingSave, false); assert.equal(h.edit.editable, false); }); test('retired catalogue selections remain in the frozen recovery command', () => { const h = harness(); h.payload('mandatory', ['capacity']); h.edit.save(); const first = h.sent[0]; first.options.onNetworkError(); h.render({ ...props(), types: {}, criteria: {} }); assert.ok( h .all() .some( (node) => node.type === 'option' && text(node).includes('Tipo anteriormente indicado'), ), ); const remove = h .all() .find( (node) => node.type === 'button' && text(node) === 'Retirar da preparação', ); assert.ok(remove); remove.props.onClick(); h.render(); assert.deepEqual(h.data.payload.mandatory, []); h.edit.recoverSave(); assert.deepEqual(h.sent[1].data, first.data); assert.deepEqual(h.sent[1].data.payload.mandatory, ['capacity']); h.success(1, saved(h.sent[1].data)); assert.deepEqual(h.data.payload.mandatory, []); }); test('cancelled uncertain edits remain recoverable and protect navigation even with unchanged values', () => { const h = harness(); h.edit.save(); h.sent[0].options.onCancel(); h.render(); assert.equal(h.edit.retryingSave, true); assert.equal(h.edit.dirty, true); assert.equal(h.before(), true); assert.equal(h.unload(), true); }); test('empty API dictionaries are valid while malformed operation metadata suspends writing', () => { const p = props(); p.record.payload = []; const h = harness(p); assert.equal(h.edit.invalid, false); h.set('title', 'SYNTHETIC draft without payload'); h.edit.save(); assert.equal(h.sent.length, 1); const invalid = harness(props({ saveCommands: null })); assert.equal(invalid.edit.invalid, true); invalid.edit.save(); assert.equal(invalid.sent.length, 0); }); const key = '11111111-1111-4111-8111-111111111111'; const catalogues = { types: { joint_venture: 'Consórcio' }, criteria: { capacity: 'Capacidade', experience: 'Experiência', impact: 'Impacto', }, sectors: [{ id: 1, name: 'Agricultura' }], territories: [{ id: 'place', name: 'Local de ensaio' }], organizations: [], sources: [], }; function props(patch = {}) { return { ...catalogues, editorActorId: 2, saveCommands: [], record: { id: 1, version: 2, status: 'draft', client_key: key, title: 'SYNTHETIC original', offer: 'SYNTHETIC offer', seeking: 'SYNTHETIC collaboration', organization_id: null, sector_id: 1, kind: 'seeking', partnership_type: 'joint_venture', deadline: null, payload: { summary: 'SYNTHETIC summary', requirements: 'SYNTHETIC requirements', mandatory: ['capacity'], confidentiality: 'private', consent: true, availability: 'SYNTHETIC November availability', }, }, origins: [], locations: [], ...patch, }; } function saved(data, { id = 1, version = 3, status = 'draft' } = {}) { const trim = (value) => (typeof value === 'string' ? value.trim() : value); const record = { id, version, status, client_key: data.client_key, title: trim(data.title), offer: trim(data.offer), seeking: trim(data.seeking), organization_id: data.organization_id || null, sector_id: data.sector_id ? Number(data.sector_id) : null, kind: data.kind, partnership_type: data.partnership_type || null, deadline: data.deadline || null, payload: Object.fromEntries( Object.entries(data.payload).map(([k, v]) => [k, trim(v)]), ), }; return props({ saveCommands: data.operation_key ? [{ key: data.operation_key, version: Number(data.version) + 1 }] : [], record, origins: clone(data.origins), locations: data.locations.map((item) => ({ ...item, source_reference: trim(item.source_reference), })), }); } function nodes(tree) { return Array.isArray(tree) ? tree.flatMap(nodes) : tree && typeof tree === 'object' ? [tree, ...nodes(tree.props?.children)] : []; } function text(tree) { return typeof tree === 'string' ? tree : Array.isArray(tree) ? tree.map(text).join('') : tree?.props ? text(tree.props.children) : typeof tree === 'number' ? String(tree) : ''; } function harness(initial = props()) { let cells = [], forms = [], cursor = 0, formCursor = 0, currentProps = initial, currentKey, initialized = false, tree, editor; const sent = [], reloads = [], effects = [], before = new Set(), unload = new Set(); let confirmCalls = 0, operationSequence = 0; const windowMock = { confirm() { confirmCalls++; return false; }, addEventListener(name, fn) { if (name === 'beforeunload') unload.add(fn); }, removeEventListener(name, fn) { if (name === 'beforeunload') unload.delete(fn); }, }; const mocks = { react: { useRef(value) { const i = cursor++; cells[i] ??= { current: value }; return cells[i]; }, useState(value) { const i = cursor++; if (!(i in cells)) cells[i] = typeof value === 'function' ? value() : value; return [ cells[i], (value) => { cells[i] = typeof value === 'function' ? value(cells[i]) : value; }, ]; }, useEffect(callback, deps) { const i = cursor++; if ( !cells[i] || !deps.every((v, j) => v === cells[i].deps[j]) ) { cells[i]?.cleanup?.(); cells[i] = { deps }; effects.push(() => { const original = globalThis.window; globalThis.window = windowMock; try { cells[i].cleanup = callback(); } finally { globalThis.window = original; } }); } }, }, '@inertiajs/react': { Link: 'a', router: { reload(options) { reloads.push(options); }, on(name, callback) { if (name === 'before') before.add(callback); return () => before.delete(callback); }, }, useForm(initial) { const i = formCursor++; forms[i] ??= { data: clone(initial), errors: {}, processing: false, setData(key, value) { this.data = typeof key === 'function' ? key(this.data) : typeof key === 'object' ? key : { ...this.data, [key]: value }; }, setDefaults(value) { this.defaults = clone(value); }, clearErrors() { this.errors = {}; }, transform(callback) { this.transformer = callback; }, post(url, options) { sent.push({ method: 'post', url, options, data: clone(this.transformer(this.data)), }); }, put(url, options) { sent.push({ method: 'put', url, options, data: clone(this.transformer(this.data)), }); }, }; return forms[i]; }, }, '@/lib/operation-key': { createOperationKey: () => `00000000-0000-4000-8000-${String(++operationSequence).padStart(12, '0')}`, }, './shared': { Frame: 'main', Field: 'label', Errors: 'errors', control: '', button: '', }, '@/pages/partnerships/shared': { labels: { draft: 'Rascunho', submitted: 'Submetida', published: 'Publicada', archived: 'Arquivada', }, }, }; const actual = load('../../resources/js/lib/partnership-edit.ts', mocks); mocks['@/lib/partnership-edit'] = { usePartnershipEdit(p) { editor = actual.usePartnershipEdit(p); return editor; }, }; const Panel = load( '../../resources/js/components/partnership-edit-comparison.tsx', mocks, ).default; mocks['@/components/partnership-edit-comparison'] = { default: Panel }; const Page = load( '../../resources/js/pages/partnerships/editor.tsx', mocks, ).default; function render(next = currentProps) { const original = globalThis.window; globalThis.window = windowMock; try { currentProps = next; const root = Page(next); if (initialized && root.key !== currentKey) { for (const cell of cells) cell?.cleanup?.(); cells = []; forms = []; } currentKey = root.key; initialized = true; cursor = 0; formCursor = 0; tree = root.type(root.props); while (effects.length) effects.shift()(); return editor; } finally { globalThis.window = original; } } render(); return { sent, reloads, render, get edit() { return editor; }, get data() { return editor.form.data; }, all: () => nodes(tree), panel: () => Panel({ editor, catalogues: currentProps }), set(key, value) { editor.setData(key, value); render(); }, payload(key, value) { editor.setData('payload', { ...editor.form.data.payload, [key]: value, }); render(); }, success(index, next) { render(next); sent[index].options.onSuccess({ props: next }); render(); }, before(preserveState = false) { let prevented = false; const original = globalThis.window; globalThis.window = windowMock; try { for (const fn of before) fn({ detail: { visit: { method: 'get', preserveState } }, preventDefault() { prevented = true; }, }); } finally { globalThis.window = original; } return prevented; }, unload() { let prevented = false; for (const fn of unload) fn({ preventDefault() { prevented = true; }, returnValue: null, }); return prevented; }, get confirmCalls() { return confirmCalls; }, }; } test('availability is presented and sent independently from the deadline', () => { const h = harness(); assert.equal( h.data.payload.availability, 'SYNTHETIC November availability', ); const availability = h .all() .find( (n) => n.type === 'textarea' && n.props.value === 'SYNTHETIC November availability', ); assert.equal(availability.props.maxLength, 1000); h.payload('availability', 'SYNTHETIC December resources'); h.edit.save(); assert.equal( h.sent[0].data.payload.availability, 'SYNTHETIC December resources', ); assert.equal(h.sent[0].data.deadline, ''); }); test('refreshing another version preserves edits and merges independent fields only after adoption', () => { const h = harness(); h.set('title', 'SYNTHETIC deliberate title'); const next = props(); next.record.version = 3; next.record.payload.availability = 'SYNTHETIC remote availability'; h.render(next); assert.equal(h.data.title, 'SYNTHETIC deliberate title'); h.edit.save(); assert.equal(h.sent.length, 0); assert.deepEqual(h.edit.unresolved, []); h.edit.adopt(); h.render(); assert.equal(h.data.payload.availability, 'SYNTHETIC remote availability'); assert.equal(h.data.title, 'SYNTHETIC deliberate title'); assert.equal(h.data.version, 3); }); test('choices for multiple conflicting sections are explicit and independent', () => { const h = harness(); h.set('title', 'mine'); h.payload('availability', 'my availability'); const next = props(); next.record.version = 3; next.record.title = 'remote'; next.record.payload.availability = 'remote availability'; h.render(next); assert.equal(h.edit.unresolved.length, 2); h.edit.choose('title', 'mine'); h.render(); h.edit.choose('availability', 'current'); h.render(); assert.deepEqual(h.edit.unresolved, []); h.edit.adopt(); h.render(); assert.equal(h.data.title, 'mine'); assert.equal(h.data.payload.availability, 'remote availability'); }); test('returning deliberately to the original text does not count as permission to overwrite it', () => { const h = harness(); h.set('title', 'temporary'); h.set('title', 'SYNTHETIC original'); const next = props(); next.record.version = 3; next.record.title = 'remote'; h.render(next); assert.deepEqual(h.edit.unresolved, ['title']); assert.equal(h.edit.dirty, true); h.edit.choose('title', 'mine'); h.render(); h.edit.adopt(); h.render(); assert.equal(h.data.title, 'SYNTHETIC original'); }); test('sets ignore database order while locations, origins and criteria keep their own conflicts', () => { const initial = props({ origins: [ { type: 'project', target_id: 'a' }, { type: 'project', target_id: 'b' }, ], locations: [ { geo_location_id: 'place', role: 'primary', source_reference: 'SYNTHETIC original reference', }, ], }); const h = harness(initial); h.payload('mandatory', ['capacity', 'impact']); const next = clone(initial); next.record.version = 3; next.origins.reverse(); next.locations[0].source_reference = 'SYNTHETIC current source'; next.record.payload.mandatory = ['experience']; h.render(next); assert.ok(!h.edit.changed.includes('origins')); assert.deepEqual(h.edit.unresolved, ['mandatory']); h.edit.choose('mandatory', 'mine'); h.render(); h.edit.adopt(); h.render(); assert.deepEqual(h.data.payload.mandatory, ['capacity', 'impact']); assert.equal( h.data.locations[0].source_reference, 'SYNTHETIC current source', ); }); test('success uses normalized server values while preserving typing during the request', () => { const h = harness(); h.set('title', ' SYNTHETIC saved title '); h.edit.save(); h.set('offer', 'SYNTHETIC after send'); const next = saved(h.sent[0].data); h.success(0, next); assert.equal(h.data.title, 'SYNTHETIC saved title'); assert.equal(h.data.offer, 'SYNTHETIC after send'); assert.equal(h.data.version, 3); assert.equal(h.edit.dirty, true); }); test('new creation becomes the same editor with an assigned identity without losing in-flight edits', () => { const h = harness(props({ record: null })); h.set('title', 'SYNTHETIC first draft'); h.edit.save(); h.set('title', 'SYNTHETIC typed during creation'); h.success(0, saved(h.sent[0].data, { id: 73, version: 2 })); assert.equal(h.data.title, 'SYNTHETIC typed during creation'); h.edit.save(); assert.equal(h.sent[1].method, 'put'); assert.equal(h.sent[1].url, '/parcerias/73'); assert.equal(h.sent[1].data.version, 2); }); test('an unconfirmed creation repeats the exact original command and keeps edits made before retry', () => { const h = harness(props({ record: null })); h.set('title', 'SYNTHETIC original command'); h.edit.save(); h.sent[0].options.onNetworkError(new Error('synthetic timeout')); h.render(); h.set('title', 'SYNTHETIC later draft'); h.edit.save(); assert.deepEqual(h.sent[1].data, h.sent[0].data); h.success(1, saved(h.sent[1].data, { id: 74, version: 2 })); assert.equal(h.data.title, 'SYNTHETIC later draft'); h.edit.save(); assert.equal(h.sent[2].url, '/parcerias/74'); assert.equal(h.sent[2].data.title, 'SYNTHETIC later draft'); }); test('a version conflict is handled without discarding fields or showing a generic HTTP modal', () => { const h = harness(); h.set('title', 'draft'); h.edit.save(); assert.equal(h.sent[0].options.onHttpException({ status: 409 }), false); h.render(); assert.equal(h.edit.blocked, true); h.edit.save(); assert.equal(h.sent.length, 1); h.edit.refresh(); assert.deepEqual(h.reloads[0].only, [ 'record', 'locations', 'origins', 'editorActorId', 'saveCommands', ]); h.reloads[0].onSuccess({ props: props() }); h.reloads[0].onFinish(); h.render(); assert.equal(h.data.title, 'draft'); assert.equal(h.edit.blocked, false); }); test('submitted or archived proposals preserve the draft but disable writing in the existing workflow', () => { for (const status of ['submitted', 'archived']) { const h = harness(); h.set('title', 'unsaved'); const next = props(); next.record.version = 3; next.record.status = status; h.render(next); assert.equal(h.data.title, 'unsaved'); assert.equal(h.edit.editable, false); assert.equal( h.all().find((n) => n.type === 'fieldset').props.disabled, true, ); h.edit.save(); assert.equal(h.sent.length, 0); assert.match(text(h.panel()), /não pode ser gravada neste estado/); } }); test('a newer server revision after saving requires comparison instead of acknowledging another persons edits', () => { const h = harness(); h.set('title', 'SYNTHETIC sent'); h.edit.save(); h.set('offer', 'SYNTHETIC in flight'); const next = saved(h.sent[0].data, { version: 4 }); next.record.title = 'SYNTHETIC another author'; h.success(0, next); assert.equal(h.data.title, 'SYNTHETIC sent'); assert.equal(h.data.offer, 'SYNTHETIC in flight'); assert.equal(h.edit.blocked, true); assert.ok(h.edit.changed.includes('title')); assert.ok(!h.edit.unresolved.includes('title')); h.edit.adopt(); h.render(); assert.equal(h.data.version, 4); assert.equal(h.data.title, 'SYNTHETIC another author'); assert.equal(h.data.offer, 'SYNTHETIC in flight'); }); test('unsaved navigation and unload are protected while a preserving refresh stays available', () => { const h = harness(); assert.equal(h.unload(), false); h.set('title', 'dirty'); assert.equal(h.unload(), true); assert.equal(h.before(), true); assert.equal(h.confirmCalls, 1); assert.equal(h.before(true), false); h.edit.save(); h.success(0, saved(h.sent[0].data)); assert.equal(h.unload(), false); assert.equal(h.before(), false); }); test('wrong or malformed responses cannot reset or submit another record', () => { const h = harness(); h.set('title', 'draft'); h.edit.save(); const wrong = saved(h.sent[0].data, { id: 99 }); h.success(0, wrong); assert.equal(h.data.title, 'draft'); assert.equal(h.edit.invalid, true); h.edit.save(); assert.equal(h.sent.length, 1); const invalid = harness(); const bad = props(); bad.record.version = '3'; invalid.render(bad); assert.equal(invalid.edit.invalid, true); invalid.edit.save(); assert.equal(invalid.sent.length, 0); }); test('published revision impact and an unavailable prior sector are visible without silently changing values', () => { const initial = props(); initial.record.status = 'published'; initial.record.sector_id = 99; const h = harness(initial); assert.match(text(h.panel()), /volta a rascunho/); assert.match(text(h.panel()), /nova submissão e revisão/); assert.ok( h.all().some((n) => n.type === 'option' && n.props.value === '99'), ); assert.equal(h.data.sector_id, '99'); assert.ok( h .all() .some( (n) => n.type === 'button' && text(n).includes('Guardar revisão em rascunho'), ), ); }); test('comparison actions cannot accidentally submit the form', () => { const h = harness(); h.set('title', 'mine'); const next = props(); next.record.version = 3; next.record.title = 'remote'; h.render(next); const buttons = nodes(h.panel()).filter((n) => n.type === 'button'); assert.ok(buttons.every((n) => n.props.type === 'button')); assert.equal(buttons.at(-1).props.disabled, true); assert.match(text(h.panel()), /Actualmente guardado/); }); test('batched payload edits and removals preserve all deliberate changes', () => { const h = harness( props({ locations: [ { geo_location_id: 'place-a', role: 'primary', source_reference: 'SYNTHETIC a', }, { geo_location_id: 'place-b', role: 'coverage', source_reference: 'SYNTHETIC b', }, ], origins: [ { type: 'project', target_id: 'a' }, { type: 'project', target_id: 'b' }, ], }), ); h.edit.setData('payload', (previous) => ({ ...previous, summary: 'SYNTHETIC first change', })); h.edit.setData('payload', (previous) => ({ ...previous, availability: 'SYNTHETIC second change', })); h.render(); assert.equal(h.data.payload.summary, 'SYNTHETIC first change'); assert.equal(h.data.payload.availability, 'SYNTHETIC second change'); const remove = h .all() .filter((node) => node.type === 'button' && text(node) === 'Remover'); assert.equal(remove.length, 4); for (const button of remove) button.props.onClick(); h.render(); assert.deepEqual(h.data.locations, []); assert.deepEqual(h.data.origins, []); });