66 lines · 2.9 KB
import { describe, expect, test } from "bun:test";
import { animatedLavaLampSvg, lavaLamp, lavaLampDataUrl, lavaLampSvg, lavaMotion } from "./lava-lamp";

const stripAnimation = (svg: string) =>
  svg.replace(/<animate [^>]*\/>/g, "").replace(/<ellipse ([^>]*)><\/ellipse>/g, "<ellipse $1/>");

describe("lavaLamp", () => {
  test("is deterministic and case-insensitive", () => {
    expect(lavaLampSvg("Taigrr")).toBe(lavaLampSvg("taigrr"));
    expect(lavaLampDataUrl("taigrr")).toBe(lavaLampDataUrl("TAIGRR"));
    expect(lavaLampDataUrl("taigrr", true)).toBe(lavaLampDataUrl("TAIGRR", true));
  });

  test("keeps shipped lamps stable", () => {
    expect(lavaLamp("taigrr").blobs.length).toBeGreaterThanOrEqual(3);
    expect(Bun.hash(lavaLampSvg("taigrr")).toString(16)).toMatchSnapshot();
  });

  test("gives distinct handles distinct lamps", () => {
    const handles = Array.from({ length: 2000 }, (_, i) => `user${i}`);
    expect(new Set(handles.map(lavaLampSvg)).size).toBe(handles.length);
  });

  test("animated lamp starts and loops on the static frame", () => {
    for (const handle of ["taigrr", "alice", "x", "gitfor"]) {
      const animated = animatedLavaLampSvg(handle);
      expect(animated).toContain("<animate ");
      expect(stripAnimation(animated)).toBe(lavaLampSvg(handle));
      for (const [, values] of animated.matchAll(/values="([^"]+)"/g)) {
        const frames = values.split(";");
        expect(frames[0]).toBe(frames.at(-1) ?? "");
      }
    }
  });

  test("rising wax clears the top of the glass before sinking back", () => {
    for (const handle of Array.from({ length: 200 }, (_, i) => `rise${i}`)) {
      const moving = lavaLamp(handle).blobs.slice(1);
      lavaMotion(handle, moving).forEach((motion, index) => {
        const blob = moving[index];