import assert from "node:assert/strict";
import { test } from "node:test";
import { catalogKeys, inventoryKeys } from "@metis/assets/client";
import { companyKeys } from "@metis/companies/client";
import { identityKeys } from "@metis/identity/client";
import type { SessionResponseDTO, UserDTO } from "@metis/identity/common";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nextProvider } from "react-i18next";
import {
DashboardNavigationButton,
DashboardStateBadge,
DashboardTable,
dashboardKeys,
entryDestination,
} from "../../../modules/companies/src/web/dashboard";
import { App } from "./app";
import { createAppI18n } from "./i18n";
import { workspaceAssets, workspaceCatalog, workspaceCompany } from "./test-fixtures/workspace";
const user: UserDTO = {
id: "c90b4d83-b06a-4d27-a402-e64899a2dfc2",
name: "Usuario de prueba",
email: "user@example.test",
active: true,
isInitialAdmin: false,
};
test("las acciones del dashboard conservan nombre, selección y deshabilitado sin enviar formularios", () => {
const markup = renderToStaticMarkup(
2
);
assert.ok(markup.includes('aria-label="Open tickets: 2"'));
assert.ok(markup.includes('aria-pressed="true"'));
assert.ok(markup.includes('disabled=""'));
assert.ok(markup.includes('type="button"'));
assert.ok(markup.includes(">2"));
});
test("los estados del dashboard mantienen texto observable además del color", () => {
for (const state of ["urgent", "high", "in_progress", "open", null]) {
const label = state ?? "Unknown";
const markup = renderToStaticMarkup(
{label}
);
assert.ok(markup.includes(`>${label}`));
if (state === null) {
assert.equal(markup.includes("data-state="), false);
} else {
assert.ok(markup.includes(`data-state="${state}"`));
}
}
});
test("las tablas del dashboard conservan caption, cabeceras semánticas y contenido", () => {
const markup = renderToStaticMarkup(
| Reference |
| T-2026-001 |
);
assert.match(markup, /
]*>Pending work<\/caption>/);
assert.ok(markup.includes('Reference | '));
assert.ok(markup.includes("T-2026-001 | "));
});
async function renderPage(
language: string,
session?: SessionResponseDTO,
seed?: (client: QueryClient) => void
) {
const { instance, initialization } = createAppI18n(language);
await initialization;
const client = new QueryClient({
defaultOptions: {
queries: { retry: false, retryOnMount: false, staleTime: Number.POSITIVE_INFINITY },
},
});
if (session) {
client.setQueryData(identityKeys.session, session);
}
seed?.(client);
try {
return renderToStaticMarkup(
);
} finally {
client.clear();
}
}
test("el acceso protege el contenido y traduce la carga de sesión", async () => {
for (const [language, pending] of [
["es", "Comprobando acceso"],
["en", "Checking access"],
] as const) {
const markup = await renderPage(language);
assert.equal(markup.includes("Tu espacio de soporte"), false);
assert.equal(markup.includes("Your support workspace"), false);
assert.match(markup, /role="combobox"/);
assert.ok(markup.includes(language === "es" ? "Español" : "English"));
assert.ok(markup.includes(pending));
assert.ok(markup.includes('role="status"'));
}
});
test("la configuración única y el login presentan sus formularios traducidos sin acceso al contenido", async () => {
for (const [language, setupTitle, loginTitle] of [
["es", "Configura Metis Desk", "Accede a Metis Desk"],
["en", "Set up Metis Desk", "Sign in to Metis Desk"],
] as const) {
const snapshot = { user: null, csrfToken: "a".repeat(64), setupRequired: true };
const setup = await renderPage(language, snapshot);
assert.ok(setup.includes(setupTitle));
assert.match(setup, /autoComplete="new-password"/);
assert.match(setup, /minLength="8"/);
assert.equal(setup.includes("Tu espacio de soporte"), false);
const login = await renderPage(language, { ...snapshot, setupRequired: false });
assert.ok(login.includes(loginTitle));
assert.match(login, /autoComplete="current-password"/);
assert.match(login, /minLength="1"/);
assert.equal(login.includes('autoComplete="name"'), false);
}
});
test("solo el administrador inicial ve la navegación de gestión de usuarios", async () => {
const snapshot = { user, csrfToken: "a".repeat(64), setupRequired: false };
const normal = await renderPage("es", snapshot);
assert.ok(normal.includes("Tu espacio de soporte"));
assert.ok(normal.includes("Mi contraseña"));
assert.ok(normal.includes("Salir"));
assert.equal(normal.includes('href="/settings/users"'), false);
const admin = await renderPage("es", { ...snapshot, user: { ...user, isInitialAdmin: true } });
assert.ok(admin.includes('href="/settings/users"'));
});
test("el espacio abre Inicio tras el login y mantiene acceso a las compañías sin formularios visibles", async () => {
for (const [language, spaces, panel] of [
["es", "Espacios de compañías", "Ficha del activo seleccionado"],
["en", "Company spaces", "Selected asset details"],
]) {
const html = await renderPage(
language ?? "es",
{ user, csrfToken: "a".repeat(64), setupRequired: false },
(client) => {
client.setQueryData(companyKeys.list, { companies: [workspaceCompany] });
client.setQueryData(companyKeys.detail(workspaceCompany.id), { company: workspaceCompany });
client.setQueryData(inventoryKeys.company(workspaceCompany.id), {
assets: workspaceAssets,
relationships: [],
workstations: [],
});
client.setQueryData(catalogKeys.scope(workspaceCompany.id), workspaceCatalog);
}
);
assert.ok(html.includes(`aria-label="${spaces}"`));
assert.ok(html.includes(language === "es" ? "Trabajo pendiente" : "Pending work"));
assert.ok(html.includes(language === "es" ? "Resumen por compañías" : "Company summary"));
assert.ok(html.includes(`aria-label="${language === "es" ? "Inicio" : "Home"}"`));
assert.equal(html.includes(`aria-label="${panel}"`), false);
assert.ok(
html.includes(language === "es" ? "Buscar en Metis Desk..." : "Search Metis Desk...")
);
assert.ok(html.includes(language === "es" ? "Configuración" : "Settings"));
assert.equal(html.includes("shell.catalogs"), false);
assert.ok(html.includes(user.name));
assert.equal(html.includes("