test: implement resource template propagation tests for resource revisions feat: create prepareResourceTemplateUpdates function for handling resource template updates feat: add resource revision specifications for managing resource revisions in the database test: add tests for entity column visibility and local storage handling feat: implement entity column visibility storage for managing column preferences feat: create synchronizeTemplateStructure utility for managing template field and section synchronization
154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
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<string, string>) => void) {
|
|
const original = Object.getOwnPropertyDescriptor(globalThis, "window");
|
|
const values = new Map<string, string>();
|
|
|
|
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(
|
|
<EntityTable
|
|
columns={[
|
|
{ id: "name", label: "Nombre", hideable: false, render: () => "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: () => <button type="button">Editar</button>,
|
|
},
|
|
]}
|
|
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(
|
|
<EntityTable
|
|
columns={[
|
|
{
|
|
id: "detail",
|
|
label: "Detalle",
|
|
defaultVisible: false,
|
|
render: () => "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");
|
|
}
|
|
}
|
|
});
|