- music landing: diverse per-genre album sliders (online counts / offline wide-probe fallback) and home-screen library shortcuts - add ArtistLinks component and shared navigation/genreDiversity utils - player/playback-mode refinements across Rust and frontend
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
||
import { selectDiverseGenres, sampleAcross } from "./genreDiversity";
|
||
|
||
const g = (...names: string[]) => names.map(name => ({ name }));
|
||
|
||
describe("sampleAcross", () => {
|
||
it("returns input unchanged when at or under the count", () => {
|
||
expect(sampleAcross([1, 2, 3], 5)).toEqual([1, 2, 3]);
|
||
});
|
||
|
||
it("spreads the sample across the whole list rather than taking a prefix", () => {
|
||
// 26 letters, want ~7: a prefix would be A–G; striding spans A→Z.
|
||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
||
const picked = sampleAcross(letters, 7);
|
||
expect(picked[0]).toBe("A");
|
||
// Reaches deep into the alphabet, not stuck near the front.
|
||
expect(picked[picked.length - 1] >= "S").toBe(true);
|
||
expect(picked.length).toBeLessThanOrEqual(7);
|
||
});
|
||
|
||
it("never exceeds the requested count", () => {
|
||
const items = Array.from({ length: 100 }, (_, i) => i);
|
||
expect(sampleAcross(items, 8).length).toBeLessThanOrEqual(8);
|
||
});
|
||
});
|
||
|
||
describe("selectDiverseGenres", () => {
|
||
it("returns input unchanged when at or under the limit", () => {
|
||
const input = g("Rock", "Jazz");
|
||
expect(selectDiverseGenres(input, 5)).toEqual(input);
|
||
});
|
||
|
||
it("seeds with the most populous genre (first in input)", () => {
|
||
const out = selectDiverseGenres(g("Rock", "Jazz", "Hip Hop"), 1);
|
||
expect(out.map(x => x.name)).toEqual(["Rock"]);
|
||
});
|
||
|
||
it("spreads the family instead of stacking near-synonyms", () => {
|
||
// Count ranking puts all the Rock variants first; a naive top-N would
|
||
// return four flavours of Rock. Diversity should pull in distinct genres.
|
||
const input = g(
|
||
"Rock",
|
||
"Hard Rock",
|
||
"Classic Rock",
|
||
"Pop Rock",
|
||
"Jazz",
|
||
"Hip Hop",
|
||
"Classical"
|
||
);
|
||
const names = selectDiverseGenres(input, 4).map(x => x.name);
|
||
|
||
expect(names[0]).toBe("Rock"); // seeded by count
|
||
expect(names).toContain("Jazz");
|
||
expect(names).toContain("Hip Hop");
|
||
expect(names).toContain("Classical");
|
||
// Only the seed represents the Rock cluster.
|
||
expect(names.filter(n => n.includes("Rock"))).toEqual(["Rock"]);
|
||
});
|
||
|
||
it("preserves count order as the tie-breaker among equally-distinct genres", () => {
|
||
// All four are mutually distinct (no shared tokens), so every pick after
|
||
// the seed is a distance tie and should follow input (count) order.
|
||
const input = g("Rock", "Jazz", "Blues", "Folk");
|
||
expect(selectDiverseGenres(input, 3).map(x => x.name)).toEqual([
|
||
"Rock",
|
||
"Jazz",
|
||
"Blues",
|
||
]);
|
||
});
|
||
|
||
it("is case- and separator-insensitive when comparing", () => {
|
||
const input = g("Hip Hop", "hip-hop", "Reggae");
|
||
// "Hip Hop" and "hip-hop" share all tokens → the second is redundant.
|
||
expect(selectDiverseGenres(input, 2).map(x => x.name)).toEqual([
|
||
"Hip Hop",
|
||
"Reggae",
|
||
]);
|
||
});
|
||
});
|