import assert from "node:assert/strict"; import { test } from "node:test"; import { renderToStaticMarkup } from "react-dom/server"; import { EntityTable } from "./components/entity-list"; import { readColumnVisibility, writeColumnVisibility, } from "./lib/entity-column-visibility-storage"; function withStorage(run: (values: Map) => void) { const original = Object.getOwnPropertyDescriptor(globalThis, "window"); const values = new Map(); Object.defineProperty(globalThis, "window", { configurable: true, value: { localStorage: { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => values.set(key, value), }, }, }); try { run(values); } finally { if (original) { Object.defineProperty(globalThis, "window", original); } else { Reflect.deleteProperty(globalThis, "window"); } } } test("las columnas elegidas se restauran al volver a crear la tabla, incluidas las que llegan después", () => { withStorage((storage) => { writeColumnVisibility("assets", { detail: true, later: true, optional: false, name: false, actions: false, }); const render = (includeLater: boolean) => renderToStaticMarkup( "Equipo" }, { id: "detail", label: "Detalle", defaultVisible: false, render: () => "Dato elegido" }, { id: "optional", label: "Optativa", render: () => "Dato oculto" }, ...(includeLater ? [ { id: "later", label: "Campo posterior", defaultVisible: false, render: () => "Columna asíncrona", }, ] : []), { id: "actions", label: "Acciones", render: () => , }, ]} columnVisibilityStorageKey="assets" items={[{ id: "one" }]} title="Activos" /> ); const initial = render(false); const restored = render(true); assert.ok(initial.includes("Dato elegido")); assert.ok(restored.includes("Dato elegido")); assert.ok(restored.includes("Columna asíncrona")); assert.ok(restored.includes("Equipo")); assert.ok(restored.includes("Editar")); assert.equal(restored.includes("Dato oculto"), false); assert.equal(readColumnVisibility("assets").later, true); assert.equal(storage.has("entity-page-size"), false); }); }); test("las preferencias quedan aisladas por lista y no se aplican cuando no se solicita persistencia", () => { withStorage(() => { writeColumnVisibility("assets", { detail: true }); writeColumnVisibility("picker", { detail: false }); assert.deepEqual(readColumnVisibility("assets"), { detail: true }); assert.deepEqual(readColumnVisibility("picker"), { detail: false }); assert.deepEqual(readColumnVisibility(undefined), {}); const html = renderToStaticMarkup( "Preferencia ajena", }, ]} items={[{ id: "one" }]} title="Selector" /> ); assert.equal(html.includes("Preferencia ajena"), false); }); }); test("datos locales inválidos no rompen el listado y solo se aceptan valores booleanos", () => { withStorage((storage) => { for (const serialized of ["{invalid", "[]", "null", '"text"', "42"]) { storage.set("assets", serialized); assert.deepEqual(readColumnVisibility("assets"), {}); } storage.set("assets", '{"detail":true,"optional":false,"text":"true","object":{},"array":[]}'); assert.deepEqual(readColumnVisibility("assets"), { detail: true, optional: false }); }); }); test("almacenamiento bloqueado o sin cuota permite seguir utilizando la selección local", () => { const original = Object.getOwnPropertyDescriptor(globalThis, "window"); Object.defineProperty(globalThis, "window", { configurable: true, value: { localStorage: { getItem: () => { throw new Error("Storage blocked"); }, setItem: () => { throw new Error("Quota exceeded"); }, }, }, }); try { assert.deepEqual(readColumnVisibility("assets"), {}); assert.doesNotThrow(() => writeColumnVisibility("assets", { detail: true })); } finally { if (original) { Object.defineProperty(globalThis, "window", original); } else { Reflect.deleteProperty(globalThis, "window"); } } });