Ctrl K
ComunidadGuías…

tests/designStructures.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// THE ARRANGEMENT FIELDS — a list's layout, a card's anatomy, a hero's
// treatment, a divider's ornament.
//
// Four fields on three section kinds, and every one of them is a place the
// two halves of the schema could drift. What is pinned here:
//
//   1. ABSENT IS THE OLD BEHAVIOUR. A `designs.json` written before these
//      fields existed — which is every design on every instance that upgrades
//      — must validate, and must come back drawing exactly what it drew
//      yesterday. That is the whole reason they are enum-with-default rather
//      than a schema bump.
//   2. A VALUE THIS BUILD DOES NOT KNOW IS A NAMED 400. The path in the error
//      is what the admin notice prints and what makes a hand-edited file
//      fixable without a debugger, so the test reads the path, not just the
//      throw.
//   3. THE ALLOWLIST LET THEM THROUGH. `KIND_KEYS` is the other half of strict
//      validation: a key the list does not carry is refused by name, so a
//      field added to the interface and forgotten there validates as an error
//      on every save. It has happened; it is one line to forget.
//   4. THE FLOOR IS UNCHANGED. `stockSections()` is what "reset to stock"
//      means, and it is a river with boxed cards forever — the new layouts are
//      choices, not a new default.

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
  DesignError,
  newSection,
  stockSections,
  validateDesign,
  validateSection,
  type DividerSection,
  type HeroSection,
  type PostGridSection,
  type PostListSection,
} from "../shared/design.ts";

/** The shape a stored document has, with `sections` swapped in. */
function doc(sections: unknown[]): Record<string, unknown> {
  return {
    id: "d1",
    name: "A design",
    schema: 1,
    theme: null,
    site: { width: 720, density: "regular" },
    chrome: {},
    sections,
    article: {},
    createdMs: 1,
    updatedMs: 1,
  };
}

describe("design structures: the arrangement fields", () => {
  it("a section written before the field existed keeps the shape it had", () => {
    const list = validateSection(
      { id: "writings", kind: "postList", heading: "", limit: 20, tag: "", showExcerpt: true, showDate: true },
      "sections",
      0,
    ) as PostListSection;
    assert.equal(list.layout, "river");

    const grid = validateSection(
      { id: "leads", kind: "postGrid", heading: "", limit: 3, columns: 3, tag: "" },
      "sections",
      1,
    ) as PostGridSection;
    assert.equal(grid.card, "boxed");

    const hero = validateSection({ id: "top", kind: "hero" }, "sections", 2) as HeroSection;
    assert.equal(hero.treatment, "panel");
  });

  it("takes every layout, card, treatment and divider style this build draws", () => {
    for (const layout of ["river", "ledger", "index", "numbered", "dateline"]) {
      const out = validateSection(
        { id: "l", kind: "postList", layout },
        "sections",
        0,
      ) as PostListSection;
      assert.equal(out.layout, layout);
    }
    for (const card of ["boxed", "bare", "overlay", "ledger", "masonry"]) {
      const out = validateSection({ id: "g", kind: "postGrid", card }, "sections", 0) as PostGridSection;
      assert.equal(out.card, card);
    }
    for (const treatment of ["panel", "band", "split"]) {
      const out = validateSection({ id: "h", kind: "hero", treatment }, "sections", 0) as HeroSection;
      assert.equal(out.treatment, treatment);
    }
    for (const style of ["rule", "dots", "ornament", "blank"]) {
      const out = validateSection({ id: "d", kind: "divider", style }, "sections", 0) as DividerSection;
      assert.equal(out.style, style);
    }
  });

  it("refuses a value it cannot draw, and NAMES the offending path", () => {
    const cases: [Record<string, unknown>, string][] = [
      [{ id: "l", kind: "postList", layout: "spiral" }, "sections[0].layout"],
      [{ id: "g", kind: "postGrid", card: "hexagon" }, "sections[0].card"],
      [{ id: "h", kind: "hero", treatment: "diorama" }, "sections[0].treatment"],
      [{ id: "d", kind: "divider", style: "filigree" }, "sections[0].style"],
    ];
    for (const [raw, path] of cases) {
      assert.throws(
        () => validateSection(raw, "sections", 0),
        (err: unknown) => err instanceof DesignError && err.message.startsWith(`${path}:`),
        `expected a named rejection at ${path}`,
      );
    }
  });

  it("the key allowlist carries them, so a whole document round-trips", () => {
    const design = validateDesign(
      doc([
        { id: "top", kind: "hero", treatment: "split", height: "tall" },
        { id: "leads", kind: "postGrid", columns: 3, card: "overlay" },
        { id: "orn", kind: "divider", style: "ornament", space: 48 },
        { id: "run", kind: "postList", limit: 30, layout: "dateline" },
      ]),
    );
    assert.deepEqual(
      design.sections.map((s) =>
        s.kind === "postList"
          ? s.layout
          : s.kind === "postGrid"
            ? s.card
            : s.kind === "hero"
              ? s.treatment
              : s.kind === "divider"
                ? s.style
                : s.kind,
      ),
      ["split", "overlay", "ornament", "dateline"],
    );
  });

  it("a new section is born at the default, and the stock floor is a river", () => {
    assert.equal((newSection("postList", "s1") as PostListSection).layout, "river");
    assert.equal((newSection("postGrid", "s2") as PostGridSection).card, "boxed");
    assert.equal((newSection("hero", "s3") as HeroSection).treatment, "panel");
    const floor = stockSections();
    assert.equal(floor.length, 1);
    assert.equal((floor[0] as PostListSection).layout, "river");
  });
});