/*
Unit tests for section cta covering ctaVariantSchema; ctaSizeSchema; ctaIconPositionSchema. It also guards the documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the section suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect } from "vitest";
import {
ctaVariantSchema,
ctaSizeSchema,
ctaIconPositionSchema,
ctaTargetSchema,
} from "../section-cta.ts";
describe("ctaVariantSchema", () => {
it("accepts valid variants", () => {
for (const v of ["primary", "secondary", "ghost", "danger"]) {
expect(ctaVariantSchema.parse(v)).toBe(v);
}
});
it("rejects invalid variant", () => {
expect(() => ctaVariantSchema.parse("link")).toThrow();
});
});
describe("ctaSizeSchema", () => {
it("accepts valid sizes", () => {
for (const s of ["sm", "md", "lg"]) {
expect(ctaSizeSchema.parse(s)).toBe(s);
}
});
it("rejects invalid size", () => {
expect(() => ctaSizeSchema.parse("xl")).toThrow();
});
});
describe("ctaIconPositionSchema", () => {
it("accepts valid positions", () => {
expect(ctaIconPositionSchema.parse("leading")).toBe("leading");
expect(ctaIconPositionSchema.parse("trailing")).toBe("trailing");
});
it("rejects invalid position", () => {
expect(() => ctaIconPositionSchema.parse("top")).toThrow();
});
});
// RFC-1257: CtaTarget is a discriminated union — internal (with bounded
// query map), external, and same-page anchor. Legacy bare-string targets are
// rejected after the migration.
describe("ctaTargetSchema (RFC-1257)", () => {
it("accepts an internal target with pageId only", () => {
expect(ctaTargetSchema.parse({ kind: "internal", pageId: "contact" })).toEqual({
kind: "internal",
pageId: "contact",
});
});
it("accepts an internal target with query and anchor", () => {
expect(
ctaTargetSchema.parse({
kind: "internal",
pageId: "contact",
query: { scenario: "bestand" },
anchor: "send-message",
}),
).toEqual({
kind: "internal",
pageId: "contact",
query: { scenario: "bestand" },
anchor: "send-message",
});
});
it("accepts external and anchor targets", () => {
expect(ctaTargetSchema.parse({ kind: "external", href: "https://example.com" })).toEqual({
kind: "external",
href: "https://example.com",
});
expect(ctaTargetSchema.parse({ kind: "anchor", anchor: "zusagen" })).toEqual({
kind: "anchor",
anchor: "zusagen",
});
});
it("rejects legacy bare-string targets", () => {
expect(ctaTargetSchema.safeParse("contact").success).toBe(false);
expect(ctaTargetSchema.safeParse("#zusagen").success).toBe(false);
});
it("rejects unknown kinds and extra keys", () => {
expect(ctaTargetSchema.safeParse({ kind: "mailto", href: "x@y.z" }).success).toBe(false);
expect(
ctaTargetSchema.safeParse({ kind: "internal", pageId: "contact", extra: 1 }).success,
).toBe(false);
});
it("bounds query keys and values by the shared charset patterns", () => {
const base = { kind: "internal", pageId: "contact" } as const;
// key too long / illegal charset
expect(ctaTargetSchema.safeParse({ ...base, query: { ["a".repeat(33)]: "v" } }).success).toBe(
false,
);
expect(ctaTargetSchema.safeParse({ ...base, query: { "bad key!": "v" } }).success).toBe(false);
// value too long / illegal charset
expect(ctaTargetSchema.safeParse({ ...base, query: { k: "v".repeat(129) } }).success).toBe(
false,
);
expect(ctaTargetSchema.safeParse({ ...base, query: { k: "v v" } }).success).toBe(false);
// legal percent/underscore/dash/dot values pass
expect(
ctaTargetSchema.safeParse({ ...base, query: { ref: "ost-post", t: "20%off_x.y" } }).success,
).toBe(true);
});
it("rejects query on external and anchor variants", () => {
expect(
ctaTargetSchema.safeParse({ kind: "external", href: "https://x.y", query: {} }).success,
).toBe(false);
expect(ctaTargetSchema.safeParse({ kind: "anchor", anchor: "a", query: {} }).success).toBe(
false,
);
});
});