172 lines
5.8 KiB
JavaScript
172 lines
5.8 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);
|
|
const Link = ({ children, href }) =>
|
|
React.createElement('a', { href }, children);
|
|
const Empty = () => null;
|
|
const mocks = {
|
|
'@inertiajs/react': {
|
|
Link,
|
|
useForm: (data) => ({ data, processing: false, errors: {} }),
|
|
},
|
|
'@/lib/crm-record-edit': {
|
|
useCrmRecordEdit: ({ values }) => ({
|
|
form: { processing: false },
|
|
values,
|
|
blocked: false,
|
|
error: () => undefined,
|
|
}),
|
|
},
|
|
'@/components/crm-edit-comparison': { default: Empty },
|
|
'../management/shared': {
|
|
Management: ({ children }) =>
|
|
React.createElement('main', null, children),
|
|
Field: ({ label, children }) =>
|
|
React.createElement('label', null, label, children),
|
|
button: '',
|
|
control: '',
|
|
},
|
|
'./types': {
|
|
leadStates: {
|
|
new: 'Novo',
|
|
qualified: 'Qualificado',
|
|
disqualified: 'Desqualificado',
|
|
converted: 'Convertido',
|
|
},
|
|
taskStates: { open: 'Aberta', done: 'Concluída' },
|
|
},
|
|
};
|
|
function load(path) {
|
|
const code = ts.transpileModule(
|
|
fs.readFileSync(new URL('../../' + path, 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;
|
|
}
|
|
mocks['@/pages/management/shared'] = mocks['../management/shared'];
|
|
mocks['./operation-key'] = {
|
|
createOperationKey: () => '00000000-0000-4000-8000-000000000001',
|
|
};
|
|
mocks['./profile-json'] = load('resources/js/lib/profile-json.ts');
|
|
mocks['./crm-task-preparation'] = load(
|
|
'resources/js/lib/crm-task-preparation.ts',
|
|
);
|
|
mocks['@/lib/crm-task-creation'] = load(
|
|
'resources/js/lib/crm-task-creation.ts',
|
|
);
|
|
mocks['@/components/crm-task-creation'] = load(
|
|
'resources/js/components/crm-task-creation.tsx',
|
|
);
|
|
const Detail = load('resources/js/pages/crm/lead.tsx').default;
|
|
function render(patch = {}) {
|
|
return renderToStaticMarkup(
|
|
React.createElement(Detail, {
|
|
lead: {
|
|
id: 'one',
|
|
version_no: 1,
|
|
title: 'SYNTHETIC lead',
|
|
status: 'new',
|
|
owner_user_id: 1,
|
|
tasks: [],
|
|
},
|
|
taskCreation: { lead_id: 'one', actor_id: 1, commands: [] },
|
|
contacts: [],
|
|
owners: [{ id: 1, name: 'SYNTHETIC manager' }],
|
|
nextAction: { future_tasks: 0 },
|
|
...patch,
|
|
}),
|
|
);
|
|
}
|
|
|
|
test('an active lead without an available future task explains the existing requirement and links to its creation form', () => {
|
|
const html = render();
|
|
assert.match(html, /0 tarefas disponíveis com prazo futuro/);
|
|
assert.match(html, /role="status"/);
|
|
assert.match(html, /dossier encerrado ficam no histórico/);
|
|
assert.match(html, /href="#new-crm-task"/);
|
|
assert.match(html, /id="new-crm-task"/);
|
|
assert.doesNotMatch(html, /disabled=""/); // Disqualification and creation remain available; the server checks the chosen operation.
|
|
});
|
|
|
|
test('a future action count does not claim that qualification or completion has already occurred', () => {
|
|
const singular = render({ nextAction: { future_tasks: 1 } });
|
|
assert.match(singular, /1 tarefa disponível com prazo futuro/);
|
|
assert.doesNotMatch(singular, /1 tarefas/);
|
|
const html = render({ nextAction: { future_tasks: 2 } });
|
|
assert.match(html, /2 tarefas disponíveis com prazo futuro/);
|
|
assert.doesNotMatch(
|
|
html,
|
|
/role="status"|acompanhamento qualificado|licença aprovada/,
|
|
);
|
|
});
|
|
|
|
test('a suspended task retains its title, deadline and history without a response button', () => {
|
|
const html = render({
|
|
lead: {
|
|
id: 'one',
|
|
version_no: 1,
|
|
title: 'SYNTHETIC lead',
|
|
status: 'qualified',
|
|
owner_user_id: 1,
|
|
tasks: [
|
|
{
|
|
id: 'paused',
|
|
version_no: 1,
|
|
title: 'SYNTHETIC preserved task',
|
|
status: 'open',
|
|
due_at: '2026-11-05T10:00:00Z',
|
|
assignee_user_id: 1,
|
|
can_update: false,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
assert.match(html, /SYNTHETIC preserved task/);
|
|
assert.match(html, /Dossier associado encerrado/);
|
|
assert.match(html, /Maputo/);
|
|
assert.match(html, /id="crm-task-paused"/);
|
|
assert.doesNotMatch(html, /Actualizar tarefa<\/button>/);
|
|
});
|
|
|
|
test('an older response without availability metadata does not invent a zero', () => {
|
|
const html = render({ nextAction: undefined });
|
|
assert.doesNotMatch(
|
|
html,
|
|
/tarefas disponíveis com prazo futuro|role="status"/,
|
|
);
|
|
assert.match(html, /Registar próxima acção/);
|
|
});
|
|
|
|
test('disqualified leads retain their form without the active-lead requirement notice', () => {
|
|
const html = render({
|
|
lead: {
|
|
id: 'one',
|
|
version_no: 1,
|
|
title: 'SYNTHETIC disqualified lead',
|
|
status: 'disqualified',
|
|
owner_user_id: 1,
|
|
tasks: [],
|
|
},
|
|
});
|
|
assert.match(html, /0 tarefas disponíveis com prazo futuro/);
|
|
assert.doesNotMatch(html, /role="status"|disabled=""/);
|
|
});
|