commit9
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-30 20:02:30 +02:00
parent 6d14b4e6f0
commit c6373365bf
225 changed files with 15365 additions and 285 deletions
+104
View File
@@ -0,0 +1,104 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import { AiPlanSettings } from '@/components/ai-plan-settings';
import { AiPreparationPreview } from '@/components/ai-preparation-preview';
import type { AiPreparation } from '@/components/ai-preparation-preview';
const page = {
component: 'company/parameters',
url: '/administracao/parametrizacao',
version: 'ai-plan',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
test.each([true, false])(
'AI preparation settings clearly save a plan rather than activate a provider (%s)',
(ready) => {
function Screen() {
return (
<AiPlanSettings
ready={ready}
version={3}
domains={{
procurement: 'Compras',
people: 'Pessoas e contratações',
}}
scopes={{ process_state: 'Estado do processo' }}
plan={{
state: 'draft',
provider: 'none',
model: '',
domains: [],
data_scopes: [],
monthly_budget_usd: '0.00',
monthly_requests: 0,
max_input_bytes: 4500,
max_output_tokens: 1000,
}}
/>
);
}
const html = renderToString(
<App initialPage={page} initialComponent={Screen} />,
);
expect(html.includes('<form')).toBe(ready);
expect(html).toContain('não envia dados nem activa chamadas externas');
if (ready) {
expect(html).toContain('Guardar plano para pré-visualização');
expect(html).toContain('ainda não existe contador mensal');
expect(html).toContain('Orçamento mensal planeado (USD)');
}
expect(html).not.toContain('type="password"');
},
);
test.each([
'not_configured',
'outside_scope',
'no_permitted_facts',
'input_exceeds_limit',
'draft',
] as const)(
'AI preview handles %s without exposing an external execution action',
(status) => {
const data: AiPreparation = {
status,
external_execution_enabled: false,
usage_checked: false,
policy_revision: 2,
provider: 'openai',
model: 'gpt-5.6-terra',
max_input_bytes: 4500,
max_output_tokens: 1000,
input_bytes: 123,
facts:
status === 'draft'
? [
{
id: 'request',
text: 'Pedido em aprovação.',
source: {
href: '#report-request',
label: 'Identificação do pedido',
pointer: 'requisition.status',
},
},
]
: [],
};
const html = renderToString(<AiPreparationPreview data={data} />);
if (status === 'not_configured') expect(html).toBe('');
else {
expect(html).toContain('O plano não autoriza execução externa');
expect(html).not.toContain('<form');
expect(html).not.toContain('<button');
if (status === 'draft')
expect(html).toContain('href="#report-request"');
else expect(html).not.toContain('Pedido em aprovação.');
}
},
);
+72
View File
@@ -0,0 +1,72 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import Entry from '@/pages/candidate/entry';
import Profile from '@/pages/candidate/profile';
const page = {
component: 'candidate/entry',
url: '/candidaturas/1',
version: 'candidate',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
test.each([true, false])(
'registration availability (%s) preserves existing login and does not promise submission',
(registration_open) => {
const initialPage = {
...page,
props: {
...page.props,
company: { id: 1, name: 'Empresa fictícia' },
portal: {
version: 2,
registration_open,
notice: 'Aviso fictício da empresa.',
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Entry} />,
);
expect(html).toContain('Entrar na minha área');
expect(html.includes('Criar a minha conta')).toBe(registration_open);
expect(html).toContain('Criar um perfil não constitui candidatura');
expect(html).not.toContain('/dashboard');
},
);
test('candidate profile renders geography and retained version without internal evaluation controls', () => {
const initialPage = {
...page,
component: 'candidate/profile',
props: {
...page.props,
companyId: 1,
email: 'test@example.invalid',
profile: {
version: 3,
data: {
name: 'Pessoa fictícia',
phone: '',
summary: 'Perfil fictício.',
availability: {
province: 'Província fictícia',
district: 'Distrito fictício',
locality: 'Localidade fictícia',
},
},
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Profile} />,
);
expect(html).toContain('value="Província fictícia"');
expect(html).toContain('value="Distrito fictício"');
expect(html).toContain('value="Localidade fictícia"');
expect(html).toContain('Guardar nova versão do perfil');
expect(html).not.toContain('Aprovar');
expect(html).not.toContain('/pessoas');
});
@@ -0,0 +1,127 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import Opportunity from '@/pages/candidate/opportunity';
import Receipt from '@/pages/candidate/receipt';
import Access from '@/pages/candidate/access';
const page = {
component: 'candidate/opportunity',
url: '/candidaturas/1/oportunidades/1',
version: 'submission',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
const definition = {
fields: [
{
key: 'full_name',
label: 'Nome completo',
type: 'text',
required: true,
},
{
key: 'availability_location',
label: 'Disponibilidade',
type: 'location',
required: true,
},
],
documents: [],
};
test.each([true, false])(
'actual submission form reuses geographic profile and requires email confirmation (%s)',
(emailVerified) => {
const initialPage = {
...page,
props: {
...page.props,
companyId: 1,
email: 'candidate@example.invalid',
emailVerified,
opportunity: {
id: 1,
title: 'Oportunidade fictícia',
description: 'Descrição pública',
closes_on: '2026-11-10',
publication_version: 1,
form: { version: 2, definition },
},
profile: {
version: 3,
data: {
name: 'Nome do perfil',
summary: 'Perfil fictício',
availability: {
province: 'Província do perfil',
district: 'Distrito do perfil',
locality: 'Localidade do perfil',
},
},
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Opportunity} />,
);
expect(html).toContain('value="Nome do perfil"');
expect(html).toContain('value="Distrito do perfil"');
expect(html).toContain('Submeter candidatura e obter comprovativo');
expect(html.includes('disabled=""')).toBe(!emailVerified);
},
);
test('receipt displays user labels and geographic values without exposing internal field keys or evaluation', () => {
const initialPage = {
...page,
component: 'candidate/receipt',
props: {
...page.props,
companyId: 1,
receipt: {
reference: 'CAND-TEST',
received_at: '2026-10-30',
snapshot: {
opportunity: {
title: 'Oportunidade',
form: { definition },
},
profile: { version: 3 },
answers: {
availability_location: {
province: 'Província fictícia',
district: 'Distrito fictício',
locality: 'Localidade fictícia',
},
},
},
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Receipt} />,
);
expect(html).toContain('Disponibilidade');
expect(html).toContain('Distrito fictício');
expect(html).not.toContain('availability_location');
expect(html).not.toContain('Avaliação interna');
});
test('recovery never claims delivery when the mail transport is absent', () => {
const initialPage = {
...page,
component: 'candidate/access',
props: {
...page.props,
companyId: 1,
purpose: 'recuperar',
emailDeliveryAvailable: false,
accessStatus: null,
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Access} />,
);
expect(html).toContain('O envio de email ainda não está configurado');
expect(html).toContain('disabled=""');
});
+96
View File
@@ -0,0 +1,96 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import Submissions from '@/pages/people/submissions';
import Receipt from '@/pages/candidate/receipt';
const page = {
component: 'people/submissions',
url: '/pessoas/1/recebidas',
version: 'triage',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
const row = {
id: 7,
reference: 'CAND-TEST',
received_at: '2026-10-30',
decision: null,
snapshot: {
opportunity: {
form: {
definition: {
fields: [{ key: 'full_name', label: 'Nome completo' }],
},
},
},
profile: {
data: { name: 'Pessoa fictícia', summary: 'Perfil fictício.' },
},
answers: { full_name: 'Pessoa fictícia' },
},
};
test.each([true, false])(
'internal triage editing follows assignment (%s) and keeps private and public messages separate',
(canTriage) => {
const initialPage = {
...page,
props: {
...page.props,
process: { id: 1, title: 'Processo fictício', version: 2 },
publication: null,
publicUrl: '/candidaturas/1/oportunidades/1',
canPrepare: canTriage,
canTriage,
submissions: {
data: [row],
prev_page_url: null,
next_page_url: null,
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Submissions} />,
);
expect(html).toContain('Pessoa fictícia');
expect(html.includes('Registar decisão de triagem')).toBe(canTriage);
expect(html.includes('Motivo interno (reservado)')).toBe(canTriage);
expect(html.includes('Comunicação destinada ao candidato')).toBe(
canTriage,
);
expect(html.includes('Guardar publicação')).toBe(canTriage);
},
);
test('candidate receives a triage status that does not claim hiring or inclusion in a database', () => {
const initialPage = {
...page,
component: 'candidate/receipt',
props: {
...page.props,
companyId: 1,
receipt: {
reference: 'CAND-TEST',
received_at: '2026-10-30',
snapshot: {
opportunity: { title: 'Processo' },
profile: { version: 1 },
answers: {},
},
},
decision: {
outcome: 'accepted_for_analysis',
message: 'Comunicação aprovada para o titular.',
decided_at: '2026-10-30',
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Receipt} />,
);
expect(html).toContain('Aceite para análise');
expect(html).toContain('Comunicação aprovada para o titular.');
expect(html).not.toContain('Motivo interno');
expect(html).toContain('não significa aceitação');
});
+120
View File
@@ -0,0 +1,120 @@
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import { EvidenceBrief } from '@/components/evidence-brief';
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import ReviewReport from '@/pages/purchases/review-report';
test('real report renders source targets and preserves printable report sections', () => {
const report = {
generated_at: '2026-10-30T12:00:00Z',
generated_by: 'Pessoa fictícia',
company: 'Empresa fictícia',
requisition: {
id: 42,
number: 'REQ-42',
status: 'approved',
justification: 'Necessidade fictícia',
department: 'Operações',
requester: 'Requisitante fictício',
},
comparison: {
ready: false,
top_count: 0,
rows: [],
policy: {
revision: 0,
configured: false,
minimum_proposals: 3,
minimum_evaluators: 2,
weights: {
price: 40,
quality: 20,
delivery: 20,
history: 10,
payment: 10,
},
},
},
recommendation: null,
admission: null,
qualification: null,
opening: null,
award: null,
order: null,
fulfilment: {
receipts: [],
finance_visible: false,
invoices: null,
payments: null,
},
};
const page = {
component: 'purchases/review-report',
url: '/requisicoes/42/relatorio',
version: 'brief',
props: {
errors: {},
report,
brief: {
method: 'procurement_evidence_brief_v1',
generated_at: report.generated_at,
basis_sha256: 'a'.repeat(64),
finance_visible: false,
claims: [],
},
},
clearHistory: false,
encryptHistory: true,
} satisfies Page;
const html = renderToString(
<App initialPage={page} initialComponent={ReviewReport} />,
);
for (const section of [
'request',
'comparison',
'qualification',
'records',
'fulfilment',
])
expect(html).toContain(`id="report-${section}"`);
expect(html).toContain('Síntese com fontes');
expect(html).toContain('Imprimir esta consulta');
expect(html).toContain('Necessidade fictícia');
});
test.each([true, false])(
'evidence brief exposes provenance and the correct financial visibility (%s)',
(finance_visible) => {
const html = renderToString(
<EvidenceBrief
data={{
method: 'procurement_evidence_brief_v1',
generated_at: '2026-10-30T12:00:00Z',
basis_sha256: 'a'.repeat(64),
finance_visible,
claims: [
{
id: 'request',
text: 'A requisição encontra-se aprovada. <script>alert(1)</script>',
basis_sha256: 'b'.repeat(64),
source: {
label: 'Identificação do pedido',
href: '#report-request',
pointer: 'requisition.status',
},
},
],
}}
/>,
);
expect(html).toContain('href="#report-request"');
expect(html).toContain('sem modelo de IA activo');
expect(html).toContain('não é uma assinatura ou aprovação');
expect(html).not.toContain('<script>');
expect(html).toContain('&lt;script&gt;');
expect(html.includes('O âmbito financeiro está reservado')).toBe(
!finance_visible,
);
},
);
@@ -0,0 +1,42 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import { IntelligenceSettings } from '@/components/intelligence-settings';
test.each([true, false])(
'intelligence settings expose local domain choices only when installed (%s)',
(ready) => {
const page = {
component: 'company/parameters',
url: '/administracao/parametrizacao',
version: 'policy',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
function Screen() {
return (
<IntelligenceSettings
ready={ready}
version={2}
policy={{
revision: 1,
configured: true,
procurement_brief: false,
people_brief: true,
}}
/>
);
}
const html = renderToString(
<App initialPage={page} initialComponent={Screen} />,
);
expect(html).toContain('externo de documentos');
expect(html.includes('<form')).toBe(ready);
if (ready) {
expect(html).toContain('Síntese com fontes em Compras');
expect(html).toContain('Síntese com fontes em Pessoas');
} else expect(html).toContain('Instalação da configuração');
},
);
+307
View File
@@ -0,0 +1,307 @@
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');
});
+119
View File
@@ -0,0 +1,119 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import Registry from '@/pages/people/registry';
import PeopleShow from '@/pages/people/show';
import { PeopleRegistryChoice } from '@/components/people-registry-choice';
const page = {
component: 'people/registry',
url: '/pessoas/cadastro',
version: 'registry',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
const person = {
id: 7,
name: 'Pessoa do cadastro',
reference: 'CAD-7',
profile_summary: 'Perfil profissional reutilizável.',
active: true,
version: 2,
canEdit: false,
};
test.each([true, false])(
'registry respects directory editing authority (%s) without linking to restricted processes',
(canCreate) => {
const initialPage = {
...page,
props: {
...page.props,
ready: true,
canCreate,
records: {
data: [person],
total: 1,
prev_page_url: null,
next_page_url: null,
},
filters: {},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={Registry} />,
);
expect(html).toContain('Pessoa do cadastro');
expect(html).toContain('Perfil profissional reutilizável.');
expect(html.includes('Registar pessoa</button>')).toBe(canCreate);
expect(html).not.toContain('Guardar nova versão');
expect(html).not.toContain('/pessoas/7');
},
);
test('process registry selection identifies source version without repeating an entire registration form', () => {
const html = renderToString(
<PeopleRegistryChoice
processId={1}
selected="7"
data={{
ready: true,
canCreate: true,
search: 'CAD',
items: [person],
}}
choose={() => {}}
/>,
);
expect(html).toContain('CAD-7');
expect(html.replace(/<!--.*?-->/g, '')).toContain('v2');
expect(html).toContain('Pesquisar cadastro');
expect(html).toContain('A candidatura conserva a versão');
expect(html).not.toContain('<form');
});
test('real reception form offers reuse and saving the first profile in the same flow', () => {
const initialPage = {
...page,
component: 'people/show',
url: '/pessoas/1',
props: {
...page.props,
process: {
id: 1,
title: 'Recepção fictícia',
purpose: 'Âmbito fictício',
kind: 'service',
status: 'open',
version: 2,
closes_on: '2026-10-30',
rules: {
minimum_reviews: 1,
criteria: [{ label: 'Competências', weight: 100 }],
},
selection: null,
selected_application_id: null,
contract: null,
},
candidates: [],
canPrepare: true,
canReview: false,
history: [],
registry: {
ready: true,
canCreate: true,
search: 'CAD',
items: [person],
},
},
};
const html = renderToString(
<App initialPage={initialPage} initialComponent={PeopleShow} />,
);
expect(html).toContain('Reutilizar pessoa do cadastro');
expect(html).toContain('Guardar também no meu cadastro para reutilizar');
expect(html).toContain('CAD-7');
expect(html).toContain('Referência documental da candidatura');
});
+82
View File
@@ -0,0 +1,82 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import type { ReactNode } from 'react';
import { expect, test } from 'vitest';
import PreviewLayout from '@/layouts/preview-layout';
import Preview from '@/pages/preview';
import { previewPaths } from '@/lib/preview-navigation';
import { TooltipProvider } from '@/components/ui/tooltip';
const page = {
component: 'preview/index',
url: '/proposta/principal',
version: 'regression',
props: {
errors: {},
name: 'SGC',
canRequest: false,
auth: { user: null },
organizationAccess: {},
payablesAvailable: false,
sidebarOpen: true,
initialScreen: 'overview',
},
clearHistory: false,
encryptHistory: false,
} satisfies Page;
test('Inertia default preview layout renders the child page instead of its shared props', () => {
function Screen() {
return <main>Conteúdo da página de demonstração</main>;
}
const html = renderToString(
<App
initialPage={page}
initialComponent={Screen}
defaultLayout={() => PreviewLayout}
/>,
);
expect(html).toContain('Conteúdo da página de demonstração');
expect(html).toContain('Dados fictícios');
expect(html).toContain('sgc-main');
});
test('the former default layout reproduces the reported props object crash', () => {
const formerLayout = (props: ReactNode) => (
<PreviewLayout>{props}</PreviewLayout>
);
function Screen() {
return <main>Conteúdo</main>;
}
expect(() =>
renderToString(
<App
initialPage={page}
initialComponent={Screen}
defaultLayout={() => formerLayout}
/>,
),
).toThrow(/Objects are not valid as a React child.*initialScreen/);
});
test.each(Object.entries(previewPaths))(
'preview screen %s renders through the real Inertia adapter',
(screen, url) => {
const html = renderToString(
<TooltipProvider>
<App
initialPage={{
...page,
url,
props: { ...page.props, initialScreen: screen },
}}
initialComponent={Preview}
defaultLayout={() => PreviewLayout}
/>
</TooltipProvider>,
);
expect(html).toContain('Dados fictícios');
expect(html).toContain('sgc-main');
},
);
@@ -0,0 +1,62 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import { ProcurementOperationalInsights } from '@/components/procurement-operational-insights';
import type { ProcurementInsights } from '@/components/procurement-operational-insights';
const base = {
component: 'reports/procurement',
url: '/relatorios',
version: 'insights',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
test.each([0, 2])(
'procurement reading renders scope %i and source links with honest limitations',
(processes) => {
const data: ProcurementInsights = {
method: 'procurement_facts_v1',
generated_at: '2026-10-30T12:00:00Z',
processes,
pending: {
count: 1,
unknown_submission_age: 0,
oldest: [
{
id: 42,
number: 'REQ-42',
submitted_at: '2026-10-23T12:00:00Z',
days_since_submission: 7,
href: '/requisicoes/42/processo',
},
],
},
unapproved_past_needed_date: 1,
approved: {
count: 1,
without_registered_quotes: 0,
without_visible_order: 0,
},
};
function Screen() {
return <ProcurementOperationalInsights data={data} />;
}
const html = renderToString(
<App initialPage={base} initialComponent={Screen} />,
).replace(/<!--.*?-->/g, '');
expect(html).toContain(`${processes} requisições autorizadas`);
if (processes === 0) {
expect(html).toContain('Sem requisições neste âmbito');
expect(html).not.toContain('REQ-42');
} else {
expect(html).toContain('/requisicoes/42/processo');
expect(html).toContain('7 dias de calendário');
expect(html).toContain('não comprova atraso de entrega');
expect(html).toContain('não significam propostas válidas');
}
expect(html).not.toContain('<form');
},
);
@@ -0,0 +1,40 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import RequisitionForm from '@/pages/requisitions/form';
import Catalogue from '@/pages/stock/catalogue';
const page = {
component: 'requisitions/form', url: '/requisicoes/1/editar', version: 'catalogue',
props: { errors: {} }, clearHistory: false, encryptHistory: true,
} satisfies Page;
test('saved article version remains selected when the published source has changed', () => {
const initialPage = { ...page, props: { ...page.props, costCenters: [],
requisition: { id: 1, cost_center_id: 1, priority: 'normal', needed_by: '2026-11-10', justification: 'Pedido', items: [{
id: 4, catalogue_entry_id: 7, catalogue_snapshot: { version: 1, code: 'ART-7' },
description: 'Nome original', category: 'Material', purchase_type: 'consumable', quantity: '2', unit: 'cx', unit_price_minor: 1000, tax_rate: '16',
}] },
catalogue: { ready: true, search: '', more: false, items: [{ id: 7, version: 2, stock_item_id: 3, code: 'ART-7', description: 'Nome actualizado', unit: 'cx', category: 'Material', purchase_type: 'consumable' }] },
} };
const html = renderToString(<App initialPage={initialPage} initialComponent={RequisitionForm} />);
expect(html).toMatch(/<option value="7:1" selected="">/);
expect(html).toContain('<option value="7:2">');
expect(html).toContain('value="Nome original"');
expect(html).toMatch(/readonly=""/i);
expect(html).toContain('Preenchimento livre');
});
test.each([true, false])('catalogue editing follows authority (%s)', (canManage) => {
const initialPage = { ...page, component: 'stock/catalogue', props: { ...page.props,
ready: true, canManage, filters: {}, records: { total: 1, prev_page_url: null, next_page_url: null, data: [{
id: 3, code: 'ART-7', name: 'Artigo existente', unit: 'cx', stock_basis: 'a'.repeat(64), version: 1,
category: 'Material', purchase_type: 'consumable', published: true, source_current: true,
}] },
} };
const html = renderToString(<App initialPage={initialPage} initialComponent={Catalogue} />);
expect(html).toContain('Artigo existente');
expect(html.includes('Guardar versão do catálogo')).toBe(canManage);
});
+90
View File
@@ -0,0 +1,90 @@
import { App } from '@inertiajs/react';
import type { Page } from '@inertiajs/core';
import { renderToString } from 'react-dom/server';
import { expect, test } from 'vitest';
import SubmissionForms from '@/pages/company/submission-forms';
const page = {
component: 'company/submission-forms',
url: '/administracao/candidaturas/formularios',
version: 'forms',
props: { errors: {} },
clearHistory: false,
encryptHistory: true,
} satisfies Page;
test.each([true, false])(
'form editor preserves revision and respects write authority (%s)',
(canManage) => {
const definition = {
available: false,
fields: [
{
key: 'availability_location',
label: 'Local de disponibilidade',
type: 'location',
required: true,
},
{
key: 'full_name',
label: 'Nome completo',
type: 'text',
required: true,
},
],
documents: [
{
key: 'cv',
label: 'CV',
formats: ['pdf'],
max_kb: 2048,
required: true,
},
],
};
const initialPage = {
...page,
props: {
...page.props,
ready: true,
canManage,
externalPortalAvailable: false,
forms: [
{
audience: 'staff',
modality: 'competition',
version: 2,
definition,
history: [
{
id: 1,
version: 1,
created_by: 4,
created_at: '2026-10-30',
reason: 'Definição anterior preservada',
definition,
},
],
},
],
},
};
const html = renderToString(
<App
initialPage={initialPage}
initialComponent={SubmissionForms}
/>,
);
expect(html).toContain('Recrutamento');
expect(html).toContain('Nome completo');
expect(html).toContain('Definição anterior preservada');
expect(html).toContain('Ainda não abre candidaturas externas');
expect(html.includes('Guardar nova versão')).toBe(canManage);
expect(html).toContain('placeholder="Província"');
expect(html).toContain('placeholder="Distrito"');
expect(html).toContain('placeholder="Localidade"');
expect(html.includes('<fieldset disabled="" class="space-y-5"')).toBe(
!canManage,
);
},
);