test.social/core/validators/loop.validator.test.ts

test.social/core/validators/loop.validator.test.ts is a file in Codebase Testing. 66 lines of code and 0 definitions.

import type { CardInput, Layer } from "@banes-lab/social-share/types/card.types.ts";
import { LOOP_SEAM, SHADER_READS_TIME } from "@banes-lab/social-share/configuration/strings/card.strings.ts";
import { between, swing, wave } from "@banes-lab/social-share/core/evaluators/card.fragment.evaluator.ts";
import { describe, expect, it } from "vitest";
import { loopFindings, sameText, textTokens } from "@banes-lab/social-share/core/validators/loop.validator.ts";
import { PROFILES } from "@banes-lab/social-share/configuration/configs/card.config.ts";
import { createCard } from "@banes-lab/social-share/core/factories/card.factory.ts";

const cardOf = function cardOf(layers: readonly Layer[], loop = true): ReturnType<typeof createCard> {
    const input: CardInput = {
        alt: "A loop.",
        id: "loop",
        layers,
        page: "home",
        stylesheet: "",
        timeline: { frames: 40, loop },
        tone: "page-gold",
    };
    return createCard(input);
};

describe("textTokens and sameText", () => {
    it("reads signed, fractional and exponent numbers and leaves a lone operator as text", () => {
        expect(textTokens("x - 0.5")).toStrictEqual(["x - ", 0.5, ""]);
        expect(textTokens("-2px")).toStrictEqual(["", -2, "px"]);
        expect(textTokens(".5 1.1e-16%")).toStrictEqual(["", 0.5, " ", 1.1e-16, "%"]);
        expect(textTokens("vec2f(uv.x - 0.5)")).toStrictEqual(["vec", 2, "f(uv.x - ", 0.5, ")"]);
    });

    it("treats numbers within the seam tolerance as the same and anything else as different", () => {
        expect(sameText("[1]", "[0.9999999999999999]")).toBe(true);
        expect(sameText("0%", "1.1102230246251565e-14%")).toBe(true);
        expect(sameText("12px", "13px")).toBe(false);
        expect(sameText("left", "center")).toBe(false);
    });
});

describe("loopFindings", () => {
    it("passes motion built from whole cycles of the loop, a crest sitting on the seam included", () => {
        const layers: readonly Layer[] = [
            {
                id: "orb",
                kind: "box",
                opacity: (frame) => wave(frame.progress, 2),
                placement: { x: (frame) => 0.5 + 0.1 * swing(frame.progress), y: 0 },
            },
            { id: "crest", kind: "box", placement: { x: (frame) => 0.5 + 0.1 * swing(frame.progress, 1, 0.25), y: 0 } },
        ];
        expect(loopFindings(cardOf(layers), PROFILES)).toStrictEqual([]);
    });

    it("names every layer whose last frame does not lead back to its first", () => {
        const layers: readonly Layer[] = [
            { id: "fade", kind: "box", opacity: (frame) => between(frame.progress, 0, 0.3), placement: { x: 0, y: 0 } },
            { id: "steady", kind: "box", placement: { x: 0, y: 0 } },
            { id: "half", kind: "box", placement: { x: (frame) => 0.5 + 0.1 * swing(frame.progress, 0.5), y: 0 } },
        ];
        expect(loopFindings(cardOf(layers), PROFILES)).toStrictEqual([`${LOOP_SEAM} fade, half`]);
    });

    it("reports a looping shader that reads the frame time, and ignores a card that plays once", () => {
        const shader: Layer = {
            id: "field",
            kind: "shader",
            placement: { x: 0, y: 0 },
            shader: "fn shade(uv: vec2f, frame: Frame) → vec4f { return vec4f(frame.time); }",
            uniforms: {},
        };
        expect(loopFindings(cardOf([shader]), PROFILES)).toStrictEqual([SHADER_READS_TIME]);
        expect(loopFindings(cardOf([shader], false), PROFILES)).toStrictEqual([]);
    });
});