Ctrl K
ComunidadGuías…

test/shared/admin-pages.test.ts

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

Archivo original ↗Preguntar a KiBot
import { assert, assertArrayIncludes } from "@std/assert";
import { expect } from "@std/expect";
import { describe, it as test } from "@std/testing/bdd";
import {
  parseEnabledFeatures,
  setFeatureEnabled,
} from "#shared/admin-features.ts";
import {
  adminLandingPath,
  entityReturnPath,
  readOnlyGetRoutePatterns,
  readOnlyWriteRoutePatterns,
  visibleSections,
  visibleTopLevel,
} from "#shared/admin-pages.ts";
import type { AdminDestinationDef } from "#shared/admin-surface/definitions.ts";
import { ADMIN_SURFACE } from "#shared/admin-surface.ts";
import { normalizePath } from "#shared/path.ts";
import { type AdminLevel, ALL_ADMIN_LEVELS } from "#types";

const DEFAULT_ENABLED_FEATURES = parseEnabledFeatures("");

describe("admin navigation", () => {
  test("shows the Site section to editors only when Site is enabled", () => {
    const context = {
      active: "/admin",
      adminLevel: "editor" as const,
      builder: false,
      enabledFeatures: DEFAULT_ENABLED_FEATURES,
      isReadOnly: false,
      storage: false,
      support: false,
    };
    assert(
      !visibleTopLevel(context)
        .map((link) => link.href)
        .includes("/admin/site"),
    );
    assertArrayIncludes(
      visibleTopLevel({
        ...context,
        enabledFeatures: setFeatureEnabled(
          DEFAULT_ENABLED_FEATURES,
          "site",
          true,
        ),
      }).map((link) => link.href),
      ["/admin/site"],
    );
  });

  test("applies per-link feature visibility", () => {
    const context = {
      active: "/admin/settings",
      adminLevel: "owner" as const,
      builder: false,
      enabledFeatures: DEFAULT_ENABLED_FEATURES,
      isReadOnly: false,
      storage: false,
      support: false,
    };
    const hidden = visibleTopLevel(context).map((link) => link.href);
    const visible = visibleTopLevel({
      ...context,
      builder: true,
      support: true,
    }).map((link) => link.href);
    // Builds is a top-level section now: it leaves the Settings sub-nav and
    // answers only to the builder flag.
    assert(!hidden.includes("/admin/built-sites"));
    assert(
      !visibleSections({ ...context, support: true })
        .flatMap((section) => section.items)
        .map((link) => link.href)
        .includes("/admin/built-sites"),
    );
    assertArrayIncludes(visible, ["/admin/built-sites"]);
    const subNav = visibleSections({ ...context, support: true })
      .flatMap((section) => section.items)
      .map((link) => link.href);
    assertArrayIncludes(subNav, ["/admin/support"]);
    const featureLinks = visibleSections({
      ...context,
      enabledFeatures: setFeatureEnabled(
        setFeatureEnabled(DEFAULT_ENABLED_FEATURES, "attributes", true),
        "questions",
        true,
      ),
    }).flatMap((section) => section.items);
    assertArrayIncludes(
      featureLinks.map((link) => link.href),
      ["/admin/attributes"],
    );
    assertArrayIncludes(
      featureLinks.map((link) => link.href),
      ["/admin/questions"],
    );
  });

  test("shows the Invoices section only when Invoices is enabled", () => {
    const context = {
      active: "/admin",
      adminLevel: "owner" as const,
      builder: false,
      enabledFeatures: DEFAULT_ENABLED_FEATURES,
      isReadOnly: false,
      storage: false,
      support: false,
    };
    assert(
      !visibleTopLevel(context)
        .map((link) => link.href)
        .includes("/admin/invoices"),
    );
    assertArrayIncludes(
      visibleTopLevel({
        ...context,
        enabledFeatures: setFeatureEnabled(
          DEFAULT_ENABLED_FEATURES,
          "invoices",
          true,
        ),
      }).map((link) => link.href),
      ["/admin/invoices"],
    );
  });

  test("omits sections with no sub-navigation", () => {
    const sections = visibleSections({
      active: "/admin/ledger",
      adminLevel: "owner",
      builder: false,
      enabledFeatures: DEFAULT_ENABLED_FEATURES,
      isReadOnly: false,
      storage: false,
      support: false,
    });
    assert(
      !sections.map((section) => section.topHref).includes("/admin/ledger"),
    );
  });

  test("derives read-only blocks from destination intent", () => {
    assertArrayIncludes(readOnlyGetRoutePatterns(), ["/admin/listing/new"]);
    assert(!readOnlyGetRoutePatterns().includes("/admin/listings"));
  });

  test("keeps only the declared writes open, by mode", () => {
    // Update mode keeps sign-in and sign-out, and nothing else.
    expect(readOnlyWriteRoutePatterns(true)).toEqual([
      "/admin/login",
      "/admin/logout",
    ]);
    // Ordinary read-only mode also keeps the operations a working site
    // cannot pause.
    assertArrayIncludes(readOnlyWriteRoutePatterns(false), [
      "/admin/login",
      "/admin/support",
      "/admin/listing/:id/scan",
      "/admin/listing/:listingId/attendee/:attendeeId/checkin",
    ]);
  });
});

describe("entityReturnPath", () => {
  test("sends a reader to the record's own page", () => {
    expect(entityReturnPath("/admin/listings", 5)).toBe("/admin/listing/5");
    expect(entityReturnPath("/admin/groups", 7)).toBe("/admin/groups/7");
  });

  test("falls back to the list when a section has no record page", () => {
    expect(entityReturnPath("/admin/settings", 1)).toBe("/admin/settings");
  });
});

describe("links a section hides", () => {
  const ownerContext = {
    active: "/admin/settings",
    adminLevel: "owner" as const,
    builder: false,
    enabledFeatures: setFeatureEnabled(DEFAULT_ENABLED_FEATURES, "money", true),
    isReadOnly: false,
    storage: false,
    support: false,
  };

  test("drops a section whose only link is its own landing", () => {
    // Money is on, so the owner can see the Ledger section, and it still has
    // no sub-navigation of its own because it holds exactly one link.
    assertArrayIncludes(
      visibleTopLevel(ownerContext).map((link) => link.href),
      ["/admin/ledger"],
    );
    assert(
      !visibleSections(ownerContext)
        .map((section) => section.topHref)
        .includes("/admin/ledger"),
    );
  });

  test("hides an add link while the site is read only, keeping the rest", () => {
    const readOnly = visibleSections({ ...ownerContext, isReadOnly: true })
      .flatMap((section) => section.items)
      .map((link) => link.href);
    const writable = visibleSections(ownerContext)
      .flatMap((section) => section.items)
      .map((link) => link.href);
    assertArrayIncludes(writable, ["/admin/listing/new"]);
    assert(!readOnly.includes("/admin/listing/new"));
    assertArrayIncludes(readOnly, ["/admin/listings"]);
  });

  test("hides a link the viewer's role cannot reach", () => {
    const editorLinks = visibleSections({
      ...ownerContext,
      active: "/admin/listings",
      adminLevel: "editor",
    })
      .flatMap((section) => section.items)
      .map((link) => link.href);
    // Editors reach the listings pages but not the owner-only settings ones.
    assert(!editorLinks.includes("/admin/settings"));
  });
});

describe("adminLandingPath", () => {
  /** The route that serves an address a visitor can type. Request paths drop
   * a trailing slash, so the dashboard serves "/admin" for "/admin/". */
  const routeServingAt = (path: string): AdminDestinationDef => {
    const route = Object.values(ADMIN_SURFACE.destinations).find(
      ({ pattern }) => normalizePath(pattern) === path,
    );
    if (!route) throw new Error(`No admin route serves "${path}"`);
    return route;
  };

  test("lands every role on a route that role can open", () => {
    for (const adminLevel of ALL_ADMIN_LEVELS) {
      assertArrayIncludes(
        routeServingAt(adminLandingPath(adminLevel)).audience,
        [adminLevel],
      );
    }
  });

  test("keeps each role's landing page", () => {
    expect(adminLandingPath("agent")).toBe("/admin/deliveries");
    expect(adminLandingPath("editor")).toBe("/admin/listings");
    expect(adminLandingPath("scanner")).toBe("/admin/scanner");
    expect(adminLandingPath("owner")).toBe("/admin");
    expect(adminLandingPath("manager")).toBe("/admin");
  });

  test("says which role has no landing", () => {
    // Reached only past the types, which is how a role the declaration does
    // not cover shows itself.
    expect(() => adminLandingPath("spectator" as AdminLevel)).toThrow(
      'No admin landing is declared for "spectator"',
    );
  });
});