308 lines
11 KiB
TypeScript
308 lines
11 KiB
TypeScript
import { App } from '@inertiajs/react';
|
|
import type { Page } from '@inertiajs/core';
|
|
import { renderToString } from 'react-dom/server';
|
|
import { expect, test } from 'vitest';
|
|
import PeopleIndex from '@/pages/people';
|
|
import PeopleShow from '@/pages/people/show';
|
|
import PeopleAccess from '@/pages/people/access';
|
|
import { PeopleClarifications } from '@/components/people-clarifications';
|
|
import type { ClarificationData } from '@/components/people-clarifications';
|
|
import { PeopleOperationalInsights } from '@/components/people-operational-insights';
|
|
|
|
const base = {
|
|
component: 'people/index',
|
|
url: '/pessoas',
|
|
version: 'people-regression',
|
|
props: { errors: {} },
|
|
clearHistory: false,
|
|
encryptHistory: false,
|
|
} satisfies Page;
|
|
|
|
test('operational reading explains its scope and missing deadlines without suggesting candidate decisions', () => {
|
|
function Screen() {
|
|
return (
|
|
<PeopleOperationalInsights
|
|
data={{
|
|
method: 'operational_facts_v1',
|
|
generated_at: '2026-10-30T12:00:00Z',
|
|
coverage: { processes: 2, tasks: 3, approvals: 1 },
|
|
overdue: {
|
|
items: 2,
|
|
processes: 1,
|
|
maximum_days: 7,
|
|
oldest: [
|
|
{
|
|
id: 'clarification-7',
|
|
reference: 'PES-42',
|
|
label: 'Prazo vencido',
|
|
due_on: '2026-10-23',
|
|
days_overdue: 7,
|
|
href: '/pessoas/42#clarification-7',
|
|
},
|
|
],
|
|
},
|
|
task_actions: { evaluate: 3 },
|
|
tasks_without_deadline: 3,
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
const html = renderToString(
|
|
<App initialPage={base} initialComponent={Screen} />,
|
|
);
|
|
const text = html.replace(/<!--.*?-->/g, '');
|
|
expect(text).toContain('2 processos autorizados');
|
|
expect(text).toContain('2 prazos vencidos em');
|
|
expect(text).toContain('7 dias de calendário');
|
|
expect(html).toContain('/pessoas/42#clarification-7');
|
|
expect(html).toContain('esta ausência não indica incumprimento');
|
|
expect(html).toContain('sem previsão por machine learning');
|
|
expect(html).not.toContain('<form');
|
|
});
|
|
|
|
test('both hiring paths are available in the real creation form', () => {
|
|
const page = {
|
|
...base,
|
|
props: {
|
|
...base.props,
|
|
ready: true,
|
|
kinds: ['service', 'staff'],
|
|
canPrepare: ['service', 'staff'],
|
|
processes: {
|
|
data: [
|
|
{
|
|
id: 1,
|
|
title: 'Processo fictício',
|
|
kind: 'service',
|
|
status: 'contract_draft',
|
|
closes_on: '2026-10-30',
|
|
},
|
|
],
|
|
links: [],
|
|
},
|
|
},
|
|
};
|
|
const html = renderToString(
|
|
<App initialPage={page} initialComponent={PeopleIndex} />,
|
|
);
|
|
expect(html).toContain('Prestador individual');
|
|
expect(html).toContain('Trabalhador para o quadro');
|
|
expect(html).toContain('Critérios de avaliação');
|
|
expect(html).toContain('Minuta contratual preparada');
|
|
});
|
|
|
|
test.each(['service', 'staff'])(
|
|
'the %s hiring report and evaluation forms render without object children',
|
|
(kind) => {
|
|
const process = {
|
|
id: 1,
|
|
title: 'Processo fictício',
|
|
purpose: 'Âmbito fictício',
|
|
kind,
|
|
status: 'assessment',
|
|
version: 4,
|
|
closes_on: '2026-10-30',
|
|
rules: {
|
|
minimum_reviews: 1,
|
|
criteria: [{ label: 'Competências', weight: 100 }],
|
|
},
|
|
selection: null,
|
|
selected_application_id: null,
|
|
contract: null,
|
|
};
|
|
const page = {
|
|
...base,
|
|
component: 'people/show',
|
|
props: {
|
|
...base.props,
|
|
process,
|
|
brief: {
|
|
method: 'people_evidence_brief_v1',
|
|
generated_at: '2026-10-30T12:00:00Z',
|
|
basis_sha256: 'a'.repeat(64),
|
|
finance_visible: false,
|
|
claims: [
|
|
{
|
|
id: 'candidates',
|
|
text: 'Matriz actual, sem selecção automática.',
|
|
basis_sha256: 'b'.repeat(64),
|
|
source: {
|
|
href: '#people-report-candidates',
|
|
label: 'Candidaturas e avaliação',
|
|
pointer: 'candidates',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
candidates: [
|
|
{
|
|
id: 1,
|
|
name: 'Pessoa fictícia',
|
|
reference: 'CAND-1',
|
|
summary: 'Resumo profissional fictício',
|
|
received_on: '2026-10-30',
|
|
evaluation_count: 0,
|
|
score_minor: null,
|
|
reviews: [],
|
|
},
|
|
],
|
|
canPrepare: true,
|
|
canReview: true,
|
|
history: [],
|
|
},
|
|
};
|
|
const html = renderToString(
|
|
<App initialPage={page} initialComponent={PeopleShow} />,
|
|
);
|
|
expect(html).toContain('Registar avaliação');
|
|
expect(html).toContain('Propor selecção');
|
|
expect(html).toContain('Pessoa fictícia');
|
|
expect(html).toContain('Síntese com fontes');
|
|
expect(html).toContain('href="#people-report-candidates"');
|
|
expect(html).toContain('id="people-report-candidates"');
|
|
expect(html).toContain('id="people-report-scope"');
|
|
},
|
|
);
|
|
|
|
test('team configuration renders without requiring or displaying candidate content', () => {
|
|
const page = {
|
|
...base,
|
|
component: 'people/access',
|
|
props: {
|
|
...base.props,
|
|
process: { id: 1, kind: 'staff', status: 'assessment' },
|
|
version: 3,
|
|
scopes: { view: 'Consultar processo', approve: 'Decidir selecção' },
|
|
users: [{ id: 1, name: 'Responsável fictício' }],
|
|
assignments: [
|
|
{
|
|
user_id: 1,
|
|
scopes: ['view'],
|
|
expires_at: '2030-01-01 12:00:00',
|
|
},
|
|
],
|
|
canView: false,
|
|
},
|
|
};
|
|
const html = renderToString(
|
|
<App initialPage={page} initialComponent={PeopleAccess} />,
|
|
);
|
|
expect(html).toContain('Responsável fictício');
|
|
expect(html).not.toContain('href="/pessoas/1"');
|
|
expect(html).toContain('Guardar atribuição');
|
|
});
|
|
|
|
test('personal tasks and approvals render as separate queues linked to original processes', () => {
|
|
const item = {
|
|
id: '1:evaluate:99',
|
|
process_id: 1,
|
|
reference: 'PES-1',
|
|
title: 'Vaga fictícia',
|
|
kind: 'staff',
|
|
status: 'assessment',
|
|
action: 'evaluate',
|
|
label: 'Avaliar candidatura #99',
|
|
due_on: null,
|
|
href: '/pessoas/1?candidatura=99#people-evaluate',
|
|
};
|
|
const page = {
|
|
...base,
|
|
props: {
|
|
...base.props,
|
|
ready: true,
|
|
kinds: ['staff'],
|
|
canPrepare: [],
|
|
processes: { data: [], links: [] },
|
|
company: { name: 'Entidade fictícia' },
|
|
workdesk: {
|
|
updated_at: '2026-10-30T12:00:00Z',
|
|
counts: {
|
|
processes: 1,
|
|
active: 1,
|
|
contract_drafts: 0,
|
|
tasks: 1,
|
|
approvals: 0,
|
|
overdue: 0,
|
|
},
|
|
tasks: [item],
|
|
approvals: [],
|
|
alerts: [],
|
|
},
|
|
},
|
|
};
|
|
const html = renderToString(
|
|
<App initialPage={page} initialComponent={PeopleIndex} />,
|
|
);
|
|
expect(html).toContain('As minhas tarefas');
|
|
expect(html).toContain('Sem decisões formais pendentes');
|
|
expect(html).toContain('href="/pessoas/1?candidatura=99#people-evaluate"');
|
|
expect(html).toContain('Entidade fictícia');
|
|
expect(html).toContain('Actualizar');
|
|
});
|
|
|
|
test('assigned clarification response renders without requiring broader interview access', () => {
|
|
const data: ClarificationData = {
|
|
ready: true,
|
|
sources: [],
|
|
users: [],
|
|
items: [
|
|
{
|
|
id: 7,
|
|
application_id: 1,
|
|
version: 1,
|
|
status: 'open',
|
|
due_on: '2026-10-30',
|
|
outcome: null,
|
|
question: 'Confirmar o comprovativo documental.',
|
|
canRespond: true,
|
|
canReview: false,
|
|
canReassign: false,
|
|
responses: [],
|
|
decisions: [],
|
|
},
|
|
],
|
|
};
|
|
function Screen() {
|
|
return <PeopleClarifications processId={1} data={data} />;
|
|
}
|
|
const html = renderToString(
|
|
<App initialPage={base} initialComponent={Screen} />,
|
|
);
|
|
expect(html).toContain('Confirmar o comprovativo documental.');
|
|
expect(html).toContain('Registar resposta');
|
|
expect(html).not.toContain('Registar conferência');
|
|
expect(html).toContain('id="clarification-7"');
|
|
});
|
|
|
|
test('restricted clarification metadata distinguishes unproven information without exposing evidence', () => {
|
|
const data: ClarificationData = {
|
|
ready: true,
|
|
sources: [],
|
|
users: [],
|
|
preservedDecision: true,
|
|
items: [
|
|
{
|
|
id: 7,
|
|
application_id: 1,
|
|
version: 3,
|
|
status: 'resolved',
|
|
due_on: '2026-10-30',
|
|
outcome: 'insufficient',
|
|
canRespond: false,
|
|
canReview: false,
|
|
canReassign: false,
|
|
},
|
|
],
|
|
};
|
|
function Screen() {
|
|
return <PeopleClarifications processId={1} data={data} />;
|
|
}
|
|
const html = renderToString(
|
|
<App initialPage={base} initialComponent={Screen} />,
|
|
);
|
|
expect(html).toContain('Informação permanece por comprovar');
|
|
expect(html).toContain('Conteúdo reservado');
|
|
expect(html).toContain('conserva a base registada na decisão');
|
|
expect(html).not.toContain('<form');
|
|
});
|