metis-desk/modules/companies/src/web/processes/processes.test.tsx
David dffb4b2794 feat: add process management features including feedback, help, and templates
- Implemented ProcessFeedback component for error handling in process forms.
- Created ProcessHelp component to provide contextual assistance for users.
- Added internationalization support for process-related strings in Spanish and English.
- Developed ProcessStartForm for initiating new processes with person and template selection.
- Introduced ProcessTemplateForm for creating and editing process templates.
- Built ProcessTemplatesPage to display and manage process templates.
- Enhanced ProcessesPage to list current processes and allow for creation and detail viewing.
- Added unit tests for various components to ensure functionality and correctness.
2026-10-07 09:34:18 +02:00

158 lines
5.6 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createInstance } from "i18next";
import type { ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nextProvider } from "react-i18next";
import { PersonProcessSchema } from "../../common/processes";
import { companiesEnglish, companiesSpanish } from "../i18n";
import { processKeys } from "./process-api";
import { ProcessDetail } from "./process-detail";
import { ProcessEditForm } from "./process-edit-form";
import { ProcessStartForm } from "./process-start-form";
import { ProcessTemplateForm } from "./process-template-form";
import { ProcessTemplatesPage } from "./process-templates-page";
import { ProcessesPage } from "./processes-page";
const company = {
id: "0199b200-0000-7000-8000-000000000001",
name: "Empresa",
active: true,
people: [],
contacts: [],
sites: [],
departments: [],
assignments: [],
};
async function render(language: string, node: ReactNode) {
const i18n = createInstance();
await i18n.init({
lng: language,
resources: { es: { companies: companiesSpanish }, en: { companies: companiesEnglish } },
});
const client = new QueryClient({
defaultOptions: { queries: { retry: false, staleTime: Number.POSITIVE_INFINITY } },
});
client.setQueryData(processKeys.company(company.id), { processes: [] });
client.setQueryData(processKeys.catalog(), { templates: [] });
try {
return renderToStaticMarkup(
<I18nextProvider i18n={i18n}>
<QueryClientProvider client={client}>{node}</QueryClientProvider>
</I18nextProvider>
).replaceAll("&#x27;", "'");
} finally {
client.clear();
}
}
test("procesos: listados es/en sin formularios junto a los datos", async () => {
for (const language of ["es", "en"]) {
const text = language === "es" ? companiesSpanish : companiesEnglish;
const html = await render(language, <ProcessesPage company={company} csrfToken="test" />);
assert.ok(html.includes(text.processes.new) && html.includes(text.processes.templates));
assert.ok(!html.includes("<form"));
const templates = await render(language, <ProcessTemplatesPage csrfToken="test" />);
assert.ok(templates.includes(text.processes.globalTemplates));
assert.ok(!templates.includes("<form"));
}
});
test("procesos: alta rápida y ayuda inferior antes del pie", async () => {
for (const language of ["es", "en"]) {
const text = language === "es" ? companiesSpanish : companiesEnglish;
const html = await render(
language,
<ProcessStartForm company={company} csrfToken="test" onSaved={() => undefined} />
);
assert.ok(html.includes(text.newPerson) && html.includes(text.processes.newTemplate));
assert.ok(html.indexOf(text.processes.help.title) > html.indexOf(text.mail.notes));
assert.ok(html.indexOf(text.save) > html.indexOf(text.processes.help.title));
}
});
test("plantillas: instrucciones, desactivación y ayuda global es/en sin datos de compañía", async () => {
for (const language of ["es", "en"]) {
const text = language === "es" ? companiesSpanish : companiesEnglish;
const html = await render(
language,
<ProcessTemplateForm csrfToken="test" onSaved={() => undefined} />
);
assert.ok(html.includes(text.processes.instructionsHelp));
assert.ok(html.includes(text.processes.templateHelp.scope));
assert.ok(html.indexOf(text.save) > html.indexOf(text.processes.templateHelp.title));
}
});
test("seguimiento: origen personal, revisión actual y edición sin afirmar retirada real; cerrados sin editar", async () => {
const reference = {
kind: "remote",
id: company.id,
label: "VPN de trabajo",
sourceKind: "personal_mobile",
};
const values = {
personId: company.id,
templateId: company.id,
templateName: "Salida",
kind: "offboarding",
name: "Salida Ana",
notes: null,
state: "open",
tasks: [
{
id: company.id,
title: "VPN de trabajo",
status: "done",
notes: "Conserva hasta entrega",
reference,
},
],
};
const process = PersonProcessSchema.parse({
id: company.id,
companyId: company.id,
values,
review: [reference],
history: [
{
id: company.id,
actorId: company.id,
actorName: "Técnico",
recordedAt: "2026-10-06T12:00:00.000Z",
values,
},
],
});
for (const language of ["es", "en"]) {
const text = language === "es" ? companiesSpanish : companiesEnglish;
const html = await render(
language,
<ProcessDetail csrfToken="test" person="Ana" process={process} />
);
assert.ok(html.includes(text.remote.personal_mobile));
assert.ok(html.includes(text.processes.stillRecorded));
assert.ok(
html.includes(text.processes.resolutionHelp) && html.includes(text.processes.refresh)
);
assert.ok(!html.includes("<form"));
const editor = await render(
language,
<ProcessEditForm csrfToken="test" onSaved={() => undefined} process={process} />
);
assert.ok(editor.includes(text.processes.taskNotes));
assert.ok(editor.includes(text.processes.resolutionHelp));
assert.ok(editor.indexOf(text.save) > editor.indexOf(text.processes.help.title));
const closed = await render(
language,
<ProcessDetail
csrfToken="test"
person="Ana"
process={{ ...process, values: { ...process.values, state: "completed" } }}
/>
);
assert.ok(!(closed.includes(text.processes.update) || closed.includes(text.processes.refresh)));
}
});