143 lines
4.9 KiB
JavaScript
143 lines
4.9 KiB
JavaScript
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';
|
|
import React from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
const require = createRequire(import.meta.url),
|
|
calls = [];
|
|
const labelsSource = fs
|
|
.readFileSync('resources/js/pages/partnerships/shared.tsx', 'utf8')
|
|
.match(
|
|
/export const labels: Record<string, string> = (\{[\s\S]*?\n\});/,
|
|
)[1];
|
|
const labels = compileFunction(`return (${labelsSource});`, [])();
|
|
const module = { exports: {} };
|
|
const source = ts.transpileModule(
|
|
fs.readFileSync('resources/js/pages/partnerships/dashboard.tsx', 'utf8'),
|
|
{
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.CommonJS,
|
|
jsx: ts.JsxEmit.ReactJSX,
|
|
},
|
|
},
|
|
).outputText;
|
|
compileFunction(source, ['require', 'module', 'exports'])(
|
|
(id) => {
|
|
if (id === './shared')
|
|
return {
|
|
Frame: ({ title, children }) =>
|
|
React.createElement(
|
|
'main',
|
|
{},
|
|
React.createElement('h1', {}, title),
|
|
children,
|
|
),
|
|
labels,
|
|
};
|
|
if (id === '@inertiajs/react')
|
|
return {
|
|
Link: ({ children, ...props }) =>
|
|
React.createElement('a', props, children),
|
|
router: { post: (...args) => calls.push(args) },
|
|
};
|
|
return require(id);
|
|
},
|
|
module,
|
|
module.exports,
|
|
);
|
|
const Dashboard = module.exports.default;
|
|
const props = () => ({
|
|
mine: [],
|
|
interests: [
|
|
{
|
|
id: 10,
|
|
partnership_id: 1,
|
|
title: null,
|
|
partnership_href: null,
|
|
status: 'withdrawn',
|
|
decision_reason: null,
|
|
room_id: null,
|
|
},
|
|
],
|
|
moderation: [],
|
|
notifications: [],
|
|
});
|
|
const render = (p) => renderToStaticMarkup(React.createElement(Dashboard, p));
|
|
const nodes = (value) =>
|
|
Array.isArray(value)
|
|
? value.flatMap(nodes)
|
|
: React.isValidElement(value)
|
|
? [value, ...nodes(value.props.children)]
|
|
: [];
|
|
test('unavailable parent has an explanation and history link without a broken proposal link', () => {
|
|
const html = render(props());
|
|
assert.ok(html.includes('consulta da proposta indisponível'));
|
|
assert.ok(!html.includes('href="/parcerias/1"'));
|
|
assert.ok(html.includes('href="/parcerias/candidaturas/10/historico"'));
|
|
});
|
|
test('authorized parent title and supplied proposal link are preserved', () => {
|
|
const p = props();
|
|
p.interests[0].title = 'SYNTHETIC published title';
|
|
p.interests[0].partnership_href = '/parcerias/1';
|
|
const html = render(p);
|
|
assert.ok(html.includes('SYNTHETIC published title'));
|
|
assert.ok(html.includes('href="/parcerias/1"'));
|
|
});
|
|
test('authorized room remains reachable independently of parent publication', () => {
|
|
const p = props();
|
|
p.interests[0].room_id = 7;
|
|
const html = render(p);
|
|
assert.ok(html.includes('href="/parcerias/salas/7"'));
|
|
assert.ok(!html.includes('href="/parcerias/1"'));
|
|
});
|
|
test('notification titles are escaped and the historical scope limitation is explained', () => {
|
|
const p = props();
|
|
p.notifications = [
|
|
{
|
|
id: 30,
|
|
title: '<script>synthetic()</script>',
|
|
href: '/parcerias/candidaturas/10/historico',
|
|
read_at: null,
|
|
},
|
|
];
|
|
const html = render(p);
|
|
assert.ok(html.includes('<script>'));
|
|
assert.ok(!html.includes('<script>'));
|
|
assert.ok(html.includes('avisos cuja consulta está autorizada'));
|
|
assert.ok(html.includes('permanecem no registo'));
|
|
});
|
|
test('only an unread supplied notice offers its own read acknowledgement', () => {
|
|
const p = props();
|
|
p.notifications = [
|
|
{
|
|
id: 30,
|
|
title: 'SYNTHETIC unread',
|
|
href: '/parcerias/1',
|
|
read_at: null,
|
|
},
|
|
{
|
|
id: 31,
|
|
title: 'SYNTHETIC read',
|
|
href: '/parcerias/1',
|
|
read_at: '2026-11-03',
|
|
},
|
|
];
|
|
const buttons = nodes(Dashboard(p)).filter((n) => n.type === 'button');
|
|
assert.equal(buttons.length, 1);
|
|
calls.length = 0;
|
|
buttons[0].props.onClick();
|
|
assert.deepEqual(calls, [['/parcerias/notificacoes/30/ler']]);
|
|
});
|
|
test('privacy-safe empty notification list does not remove the existing proposal and moderation sections', () => {
|
|
const p = props();
|
|
p.mine = [{ id: 8, title: 'SYNTHETIC own draft', status: 'draft' }];
|
|
p.moderation = [{ id: 9, title: 'SYNTHETIC pending', status: 'submitted' }];
|
|
const html = render(p);
|
|
for (const href of ['/parcerias/criar', '/parcerias/8', '/parcerias/9'])
|
|
assert.ok(html.includes(`href="${href}"`));
|
|
assert.ok(!html.includes('Marcar como lida'));
|
|
});
|