🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m1s
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Successful in 4m7s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 19m5s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 19m12s
Build & Release / Create Release (push) Successful in 8s
79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import type { MediaItem } from "$lib/api/types";
|
|
import { buildHeroMix, shuffle } from "./heroMix";
|
|
|
|
/** Minimal MediaItem for hero tests; `art: false` strips the image tags. */
|
|
function item(id: string, art = true): MediaItem {
|
|
return {
|
|
id,
|
|
name: `Item ${id}`,
|
|
type: "Movie",
|
|
primaryImageTag: art ? `tag-${id}` : undefined,
|
|
} as MediaItem;
|
|
}
|
|
|
|
const hasArt = (i: MediaItem) => !!i.primaryImageTag;
|
|
|
|
describe("shuffle", () => {
|
|
it("returns a permutation without mutating the input", () => {
|
|
const input = [1, 2, 3, 4, 5];
|
|
const copy = [...input];
|
|
const result = shuffle(input);
|
|
expect(input).toEqual(copy);
|
|
expect([...result].sort()).toEqual([...input].sort());
|
|
});
|
|
});
|
|
|
|
describe("buildHeroMix", () => {
|
|
it("returns empty for empty pools", () => {
|
|
expect(buildHeroMix([[], []], hasArt)).toEqual([]);
|
|
});
|
|
|
|
it("caps the rotation at count", () => {
|
|
const pool = Array.from({ length: 20 }, (_, i) => item(`a${i}`));
|
|
expect(buildHeroMix([pool], hasArt, 6)).toHaveLength(6);
|
|
});
|
|
|
|
it("filters items without artwork", () => {
|
|
const result = buildHeroMix([[item("a", false), item("b")]], hasArt);
|
|
expect(result.map(i => i.id)).toEqual(["b"]);
|
|
});
|
|
|
|
it("de-duplicates across pools", () => {
|
|
const a = item("a");
|
|
const result = buildHeroMix([[a], [a, item("b")]], hasArt);
|
|
const ids = result.map(i => i.id);
|
|
expect(ids).toHaveLength(new Set(ids).size);
|
|
expect(ids).toContain("a");
|
|
expect(ids).toContain("b");
|
|
});
|
|
|
|
it("leads with an item from the first non-empty pool", () => {
|
|
const personal = [item("p1"), item("p2"), item("p3")];
|
|
const rest = [item("r1"), item("r2"), item("r3")];
|
|
for (let run = 0; run < 20; run++) {
|
|
const result = buildHeroMix([personal, rest], hasArt);
|
|
expect(["p1", "p2", "p3"]).toContain(result[0].id);
|
|
}
|
|
});
|
|
|
|
it("takes at most perPool items per pool before backfilling", () => {
|
|
const a = [item("a1"), item("a2"), item("a3"), item("a4")];
|
|
const b = [item("b1"), item("b2"), item("b3"), item("b4")];
|
|
// count 4 with perPool 2: exactly 2 from each pool, no backfill needed.
|
|
for (let run = 0; run < 20; run++) {
|
|
const result = buildHeroMix([a, b], hasArt, 4, 2);
|
|
const fromA = result.filter(i => i.id.startsWith("a")).length;
|
|
const fromB = result.filter(i => i.id.startsWith("b")).length;
|
|
expect(fromA).toBe(2);
|
|
expect(fromB).toBe(2);
|
|
}
|
|
});
|
|
|
|
it("backfills from leftovers when samples fall short of count", () => {
|
|
const a = [item("a1"), item("a2"), item("a3"), item("a4"), item("a5"), item("a6")];
|
|
const result = buildHeroMix([a], hasArt, 6, 2);
|
|
expect(result).toHaveLength(6);
|
|
});
|
|
});
|