Files
Investidor-V2/tests/frontend/desk-case-state.test.mjs
T

279 lines
8.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';
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.default;
}
function nodes(tree) {
if (Array.isArray(tree)) return tree.flatMap(nodes);
if (!tree || typeof tree !== 'object') return [];
return [tree, ...nodes(tree.props?.children)];
}
const text = (tree) =>
typeof tree === 'string'
? tree
: Array.isArray(tree)
? tree.map(text).join('')
: tree?.props
? text(tree.props.children)
: typeof tree === 'number'
? String(tree)
: '';
function harness(file, initial, extra = {}) {
const forms = [],
cells = [],
calls = [];
let formCursor = 0,
cursor = 0,
serial = 0,
tree,
props = initial;
const Empty = () => null;
const Component = load(file, {
react: {
useState(initial) {
const i = cursor++;
if (!(i in cells)) cells[i] = initial;
return [
cells[i],
(value) => {
cells[i] = value;
},
];
},
useRef(initial) {
const i = cursor++;
cells[i] ??= { current: initial };
return cells[i];
},
},
'@inertiajs/react': {
Link: 'a',
router: { reload() {} },
usePage: () => ({
url: '/synthetic',
props: { auth: { user: { id: 1 } } },
}),
useForm(initial) {
const i = formCursor++;
forms[i] ??= {
data: { ...initial },
errors: {},
processing: false,
setData(key, value) {
this.data[key] = value;
},
reset(...keys) {
for (const key of keys) this.data[key] = initial[key];
},
put(url, options) {
calls.push({ url, options, data: { ...this.data } });
},
};
return forms[i];
},
},
'@/lib/operation-key': {
createOperationKey: () => `SYNTHETIC-${++serial}`,
},
'@/pages/investment/shared': {
Frame: 'main',
card: '',
input: '',
button: '',
},
'@/components/investor-guidance': { InvestorGuidance: Empty },
'@/components/desk-internal-operations': { DocumentReview: Empty },
'@/components/desk-document-picker': { default: Empty },
...extra,
});
function render(patch = {}) {
props = { ...props, ...patch };
cursor = 0;
formCursor = 0;
tree = Component(props);
}
render();
return {
forms,
calls,
render,
all: () => nodes(tree),
text: () => text(tree),
find: (type, label) =>
nodes(tree).find(
(n) => n.type === type && (!label || text(n).includes(label)),
),
submit() {
nodes(tree)
.find((n) => n.type === 'form')
.props.onSubmit({ preventDefault() {} });
},
};
}
const dossier = { id: 'case-one', version: 1, closed_at: null };
const activity = { open_requests: 2, open_tasks: 1, pending_payments: 3 };
const state = (extra) =>
harness('../../resources/js/components/desk-case-state-form.tsx', {
dossier,
activity,
...extra,
});
test('owner can explicitly close with recorded pending items and sends only state decision data', () => {
const h = state();
h.find('textarea').props.onChange({
target: { value: 'SYNTHETIC owner reason' },
});
h.render();
h.submit();
assert.match(h.calls[0].url, /dossiers\/case-one\/estado$/);
assert.deepEqual(
Object.keys(h.calls[0].data).sort(),
['version', 'state', 'reason', 'client_key'].sort(),
);
assert.equal(h.calls[0].data.state, 'closed');
assert.equal(h.calls[0].data.version, 1);
assert.match(
h.text(),
/Pedidos em curso: 2.*tarefas em curso: 1.*comprovativos por conferir: 3/,
);
});
test('success adopts actual server state and version for reopening and calls parent cleanup only for the same dossier', () => {
let changed = 0;
const h = state({
onChanged() {
changed++;
},
});
h.find('textarea').props.onChange({
target: { value: 'SYNTHETIC close reason' },
});
h.render();
h.submit();
const key = h.forms[0].data.client_key;
h.calls[0].options.onSuccess({
props: { case: { ...dossier, version: 2, closed_at: '2026-11-03' } },
});
h.render({ dossier: { ...dossier, version: 2, closed_at: '2026-11-03' } });
assert.equal(h.forms[0].data.state, 'open');
assert.equal(h.forms[0].data.version, 2);
assert.equal(h.forms[0].data.reason, '');
assert.notEqual(h.forms[0].data.client_key, key);
assert.equal(changed, 1);
h.calls[0].options.onSuccess({
props: { case: { id: 'other-case', version: 9 } },
});
assert.equal(changed, 1);
assert.ok(h.find('button', 'Confirmar reabertura'));
});
test('failed retry keeps operation identity and reason while editing or explicitly adopting new state starts a new intent', () => {
const h = state();
h.find('textarea').props.onChange({
target: { value: 'SYNTHETIC deliberate reason' },
});
h.render();
h.submit();
h.submit();
assert.deepEqual(h.calls[0].data, h.calls[1].data);
const key = h.forms[0].data.client_key;
h.render({ dossier: { ...dossier, version: 3, closed_at: '2026-11-03' } });
assert.equal(h.find('button', 'Confirmar reabertura').props.disabled, true);
h.submit();
assert.equal(h.calls.length, 2);
h.find('button', 'Usar estado actual').props.onClick();
h.render();
assert.equal(h.forms[0].data.version, 3);
assert.equal(h.forms[0].data.state, 'open');
assert.equal(h.forms[0].data.reason, 'SYNTHETIC deliberate reason');
assert.notEqual(h.forms[0].data.client_key, key);
});
test('unknown optional counts remain unknown and in-flight business work disables the decision', () => {
const h = state({
activity: {
open_requests: 0,
open_tasks: null,
pending_payments: null,
},
disabled: true,
});
assert.match(
h.text(),
/Pedidos em curso: 0.*tarefas em curso: por confirmar.*comprovativos por conferir: por confirmar/,
);
assert.equal(
h.find('button', 'Confirmar encerramento').props.disabled,
true,
);
h.submit();
assert.equal(h.calls.length, 0);
});
test('case exposes owner control separately from ordinary writing and clears unassociated file consent after success', () => {
const State = () => null;
const base = {
case: {
...dossier,
closed_at: '2026-11-03',
title: 'SYNTHETIC owner dossier',
workflow: {},
},
canWrite: false,
canChangeState: true,
activity,
manager: false,
requests: [],
services: [],
documents: [],
workflow: {},
stepStates: {},
states: {},
guidance: null,
events: [],
links: [],
legacyRequest: null,
};
const h = harness('../../resources/js/pages/desk/case.tsx', base, {
'@/components/desk-case-state-form': { default: State },
});
let widget = h.all().find((n) => n.type === State);
assert.ok(widget);
h.forms[2].setData('file', { name: 'SYNTHETIC unsent file' });
h.forms[2].setData('consent', true);
widget.props.onChanged();
assert.equal(h.forms[2].data.file, null);
assert.equal(h.forms[2].data.consent, false);
h.forms[2].processing = true;
h.render();
widget = h.all().find((n) => n.type === State);
assert.equal(widget.props.disabled, true);
h.render({ canChangeState: false, canWrite: true, manager: true });
assert.equal(
h.all().some((n) => n.type === State),
false,
);
});