import test from 'node:test'; import assert from 'node:assert/strict'; import { harness, props, nodes, text, } from './helpers/partnership-candidacy.mjs'; const reason = (h) => nodes(h.tree).find( (node) => node.type === 'label' && node.props.label === 'Motivo da ação', )?.props.children; const field = (h) => nodes(reason(h)).find((node) => node.type === 'textarea'); test('publication reason survives a proposal version refresh', () => { const p = props({ owner: true, canEdit: true, record: { ...props().record, status: 'draft' }, publicationActions: ['submit', 'archive'], publicationCommands: [], }); const h = harness(p); field(h).props.onChange({ target: { value: 'SYNTHETIC prepared reason' } }); h.render(); h.render({ ...p, record: { ...p.record, version: p.record.version + 1 } }); assert.equal(field(h).props.value, 'SYNTHETIC prepared reason'); }); const prepared = (patch = {}) => harness( props({ owner: true, canEdit: true, record: { ...props().record, status: 'draft' }, publicationActions: ['submit', 'archive'], publicationCommands: [], ...patch, }), ); function change(h, value) { field(h).props.onChange({ target: { value } }); h.render(); } const adopt = 'Revi esta versão; conservar o motivo e preparar nova acção'; const compare = 'Comparar com a proposta actual'; function saved(h, request = h.sent.at(-1), patch = {}) { const status = { submit: 'submitted', publish: 'published', return: 'returned', reject: 'rejected', suspend: 'suspended', archive: 'archived', renew: 'draft', }[request.data.action]; return { ...h.props, record: { ...h.props.record, version: request.data.version + 1, status, }, publicationActions: [], publicationCommands: [ { key: request.data.client_key, action: request.data.action, version: request.data.version + 1, }, ], ...patch, }; } test('publication sends the reviewed version with a private operation key and one frozen reason', () => { const h = prepared(); change(h, 'SYNTHETIC reason'); const request = h.click('Submeter para publicação'); assert.equal(request.url, '/parcerias/1/estado'); assert.equal(request.data.version, 4); assert.equal(request.data.action, 'submit'); assert.equal(request.data.reason, 'SYNTHETIC reason'); assert.match(request.data.client_key, /^[0-9a-f-]{36}$/); }); test('a content refresh requires explicit review and retains the prepared reason', () => { const h = prepared(); change(h, 'SYNTHETIC deliberate reason'); h.render({ ...h.props, record: { ...h.props.record, version: 5, title: 'SYNTHETIC revised title', }, }); assert.equal(h.button('Submeter para publicação').props.disabled, true); assert.ok(text(h.tree).includes('SYNTHETIC revised title')); h.click(adopt); const request = h.click('Submeter para publicação'); assert.equal(request.data.version, 5); assert.equal(request.data.reason, 'SYNTHETIC deliberate reason'); }); test('an untouched publication adopts current data without a stale empty form', () => { const h = prepared(); h.render({ ...h.props, record: { ...h.props.record, version: 5 } }); change(h, 'SYNTHETIC new preparation'); assert.equal(h.button('Submeter para publicação').props.disabled, false); assert.equal(h.click('Submeter para publicação').data.version, 5); }); test('network recovery repeats the original command and preserves text typed afterwards', () => { const h = prepared(); change(h, 'SYNTHETIC original reason'); const first = h.click('Submeter para publicação'); change(h, 'SYNTHETIC later reason'); h.fail(first); const retry = h.click( 'Recuperar a mesma tentativa: Submeter para publicação', ); assert.deepEqual(retry.data, first.data); h.success(saved(h, retry), retry); assert.equal(field(h).props.value, 'SYNTHETIC later reason'); }); test('successful acknowledgement clears only the unchanged reason', () => { const h = prepared(); change(h, 'SYNTHETIC sent reason'); const request = h.click('Submeter para publicação'); h.success(saved(h, request), request); assert.equal( nodes(h.tree).find((node) => node.props?.['data-publication-form']) !== undefined, true, ); assert.equal(h.leave(), false); assert.equal(h.sent.length, 1); }); test('deliberate changes back to an earlier string are preserved by their generation', () => { const h = prepared(); change(h, 'SYNTHETIC sent'); const request = h.click('Submeter para publicação'); change(h, 'SYNTHETIC different'); change(h, 'SYNTHETIC sent'); h.success(saved(h, request), request); assert.equal(field(h).props.value, 'SYNTHETIC sent'); }); test('a conflict requires an actual refresh before adopting a new attempt', () => { const h = prepared(); change(h, 'SYNTHETIC retained'); const request = h.click('Submeter para publicação'); h.fail(request, 409); assert.equal(h.button(adopt).props.disabled, true); h.click(compare); h.refreshed({ ...h.props, record: { ...h.props.record, version: 6 } }); assert.equal(h.button(adopt).props.disabled, false); h.click(adopt); const retry = h.click('Submeter para publicação'); assert.equal(retry.data.version, 6); assert.notEqual(retry.data.client_key, request.data.client_key); assert.equal(retry.data.reason, 'SYNTHETIC retained'); }); test('a refresh can recognize a lost response from the actors own command ledger', () => { const h = prepared(); change(h, 'SYNTHETIC sent'); const request = h.click('Submeter para publicação'); h.fail(request); h.click(compare); h.refreshed(saved(h, request)); assert.equal( h.button('Recuperar a mesma tentativa: Submeter para publicação'), undefined, ); assert.equal(h.leave(), false); }); test('a recognised operation does not silently adopt later proposal edits', () => { const h = prepared(); change(h, 'SYNTHETIC sent'); const request = h.click('Submeter para publicação'); change(h, 'SYNTHETIC next reason'); h.success( saved(h, request, { record: { ...h.props.record, version: 7, status: 'draft', title: 'SYNTHETIC changed later', }, publicationActions: ['submit', 'archive'], }), request, ); assert.equal(field(h).props.value, 'SYNTHETIC next reason'); assert.equal(h.button('Submeter para publicação').props.disabled, true); h.click(adopt); assert.equal(h.click('Submeter para publicação').data.version, 7); }); test('an incompatible response does not confirm the attempt or clear the reason', () => { const h = prepared(); change(h, 'SYNTHETIC conserved'); const request = h.click('Submeter para publicação'); h.success(saved(h, request, { publicationCommands: [] }), request); assert.equal(field(h).props.value, 'SYNTHETIC conserved'); assert.ok( h.button('Recuperar a mesma tentativa: Submeter para publicação'), ); assert.equal(h.button(adopt).props.disabled, true); }); test('loss of authority keeps local preparation and disables recovery', () => { const h = prepared(); change(h, 'SYNTHETIC preserved'); const request = h.click('Submeter para publicação'); h.fail(request); h.render({ ...h.props, owner: false, canEdit: false, moderator: false, publicationActions: [], publicationCommands: [], }); assert.equal(field(h).props.value, 'SYNTHETIC preserved'); assert.equal( h.button('Recuperar a mesma tentativa: Submeter para publicação').props .disabled, true, ); assert.equal(h.button('Submeter para publicação'), undefined); }); test('a different actor starts with a separate preparation and ignores an old callback', () => { const h = prepared(); change(h, 'SYNTHETIC former actor reason'); const request = h.click('Submeter para publicação'); h.render({ ...h.props, actorId: 99 }); assert.equal(field(h).props.value, ''); request.options.onSuccess({ props: saved(h, request) }); request.form.processing = false; h.render(); assert.equal(field(h).props.value, ''); }); test('publication and candidacy drafts share one leave warning', () => { const h = prepared(); change(h, 'SYNTHETIC publication preparation'); h.change( 'decision:10', 'Motivo da decisão ou pedido de esclarecimento', 'SYNTHETIC candidacy preparation', ); assert.equal(h.leave(), true); assert.equal(h.confirmCalls, 1); assert.equal(h.unload(), true); }); test('suspension is displayed when authorized by the server and uses its existing command', () => { const h = prepared({ owner: false, canEdit: false, moderator: true, record: { ...props().record, status: 'published' }, publicationActions: ['suspend'], }); change(h, 'SYNTHETIC suspension'); assert.equal(h.click('Suspender publicação').data.action, 'suspend'); assert.equal(h.button('Publicar'), undefined); }); test('an empty API payload dictionary remains a valid draft for archival', () => { const h = prepared({ record: { ...props().record, payload: [], status: 'draft' }, }); change(h, 'SYNTHETIC incomplete idea'); assert.equal(h.button('Arquivar publicação').props.disabled, false); });