import test from 'node:test'; import assert from 'node:assert/strict'; import { harness, props, text, nodes, } from './helpers/partnership-candidacy.mjs'; import fs from 'node:fs'; import { createRequire } from 'node:module'; import { compileFunction } from 'node:vm'; import ts from 'typescript'; import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; test('detail presents the publication decision using business language', () => { const h = harness( props({ owner: true, historySummary: { limit: 5, has_more: false, href: '/parcerias/1/historico', entries: [ { version: 5, restricted: false, action: 'edit', actor_name: 'SYNTHETIC editor', occurred_at: '2026-11-03', reason: 'SYNTHETIC reviewed content', status_before: 'draft', status_after: 'draft', }, ], }, }), ); assert.match(text(h.tree), /Edição do rascunho/); }); const entry = () => ({ version: 4, restricted: false, action: 'edit', actor_name: 'SYNTHETIC editor', occurred_at: '2026-11-03', reason: 'SYNTHETIC saved reason', status_before: 'draft', status_after: 'draft', }); const summary = () => ({ limit: 5, has_more: false, href: '/parcerias/1/historico', entries: [entry()], }); const section = (h) => nodes(h.tree).find( (node) => node.props?.['aria-label'] === 'Histórico e decisões', ); const reasonField = (h) => nodes( nodes(h.tree).find( (node) => node.type === 'label' && node.props.label === 'Motivo da ação', ), ).find((node) => node.type === 'textarea'); test('reserved metadata never renders even if extra private fields are supplied', () => { const value = summary(); value.entries[0] = { ...entry(), restricted: true, actor_name: 'SYNTHETIC PRIVATE actor', occurred_at: 'SYNTHETIC PRIVATE date', reason: 'SYNTHETIC PRIVATE reason', action: 'publish', status_before: 'SYNTHETIC PRIVATE state', status_after: 'published', actor_id: 999999, }; const h = harness(props({ owner: true, historySummary: value })); assert.match(text(section(h)), /Conteúdo reservado/); assert.doesNotMatch(text(section(h)), /PRIVATE|999999|Publicação/); }); test('public and candidate details do not display a supplied private summary or its history link', () => { const h = harness( props({ owner: false, moderator: false, historySummary: summary() }), ); assert.equal(section(h), undefined); assert.equal( nodes(h.tree).some( (node) => node.props?.href === '/parcerias/1/historico', ), false, ); assert.doesNotMatch(text(h.tree), /SYNTHETIC saved reason/); }); test('empty and limited summaries explain their scope and have only one complete-history link', () => { const value = { ...summary(), entries: [] }; const h = harness(props({ owner: true, historySummary: value })); assert.match(text(section(h)), /Ainda não existem alterações guardadas/); assert.doesNotMatch(text(section(h)), /Existem alterações anteriores/); assert.equal( nodes(h.tree).filter((node) => node.props?.href === value.href).length, 1, ); h.render({ ...h.props, historySummary: { ...summary(), limit: 1, has_more: true }, }); assert.match(text(section(h)), /Até 1 alteração mais recente/); assert.match(text(section(h)), /Existem alterações anteriores/); assert.equal( nodes(h.tree).filter((node) => node.props?.href === value.href).length, 1, ); }); test('legacy unknown actions and states are not presented as technical identifiers or inferred decisions', () => { const value = summary(); value.entries[0] = { ...entry(), version: null, action: 'partnership_opportunity.changed.v1', status_before: null, status_after: 'legacy_internal_status_v2', actor_name: null, occurred_at: null, reason: null, actor_id: 999999, }; const h = harness(props({ owner: true, historySummary: value })); assert.match(text(section(h)), /Versão não registada/); assert.match(text(section(h)), /Alteração registada/); assert.match(text(section(h)), /Autor não registado/); assert.match(text(section(h)), /Estado não identificado/); assert.doesNotMatch( text(section(h)), /partnership_opportunity|legacy_internal|999999/, ); }); test('refreshing the summary keeps the prepared publication reason and the existing leave guard', () => { const h = harness( props({ owner: true, canEdit: true, publicationActions: ['submit', 'archive'], record: { ...props().record, status: 'draft' }, historySummary: summary(), }), ); reasonField(h).props.onChange({ target: { value: 'SYNTHETIC unsent decision reason' }, }); h.render(); h.click('Comparar com a proposta actual'); assert.ok(h.reloads.at(-1).only.includes('historySummary')); h.refreshed({ ...h.props, historySummary: { ...summary(), has_more: true }, }); h.click('Comparar com os dados actuais da candidatura'); for (const key of [ 'historySummary', 'moderator', 'publicationActions', 'publicationCommands', ]) assert.ok(h.reloads.at(-1).only.includes(key), key); h.refreshed({ ...h.props, historySummary: summary() }); assert.equal( reasonField(h).props.value, 'SYNTHETIC unsent decision reason', ); assert.equal(h.leave(), true); assert.equal(h.sent.length, 0); assert.equal( nodes(h.tree).filter( (node) => node.props?.href === '/parcerias/1/historico', ).length, 1, ); }); test('summary strings are escaped by the actual React component', () => { const require = createRequire(import.meta.url); const modules = {}; const labels = compileFunction( `return (${fs.readFileSync('resources/js/pages/partnerships/shared.tsx', 'utf8').match(/export const labels: Record = (\{[\s\S]*?\n\});/)[1]});`, [], )(); function load(file) { const module = { exports: {} }; const code = ts.transpileModule(fs.readFileSync(file, 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, }, }).outputText; compileFunction(code, ['require', 'module', 'exports'])( (id) => modules[id] ?? (id === '@inertiajs/react' ? { Link: ({ children, ...p }) => React.createElement('a', p, children), } : id === '@/pages/partnerships/shared' ? { labels } : require(id)), module, module.exports, ); return module.exports; } modules['@/lib/partnership-history'] = load( 'resources/js/lib/partnership-history.ts', ); const Component = load( 'resources/js/components/partnership-publication-summary.tsx', ).default; const value = summary(); value.entries[0].reason = ''; value.entries[0].actor_name = 'SYNTHETIC actor'; const html = renderToStaticMarkup( React.createElement(Component, { value }), ); assert.ok(html.includes('<script>SYNTHETIC</script>')); assert.ok(html.includes('<a>SYNTHETIC actor</a>')); assert.doesNotMatch(html, /