mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 15:10:48 +01:00
The Vite/React/TS dice solver had no git history yet. Commit it alongside a run.sh that ensures Node/pnpm are present, installs deps, and starts the dev server with a single command. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { fuzzyFilter, fuzzyMatch } from "./fuzzy.ts";
|
|
import { mulberry32, randomInt } from "./rng.ts";
|
|
|
|
describe("fuzzyMatch", () => {
|
|
it("matches a subsequence", () => {
|
|
expect(fuzzyMatch("wei", "Weighted die")).not.toBeNull();
|
|
expect(fuzzyMatch("wgt", "Weighted die")).not.toBeNull();
|
|
});
|
|
|
|
it("rejects letters that are not present in order", () => {
|
|
expect(fuzzyMatch("zzz", "Weighted die")).toBeNull();
|
|
expect(fuzzyMatch("eiw", "Weighted die")).toBeNull();
|
|
});
|
|
|
|
it("treats an empty query as a match with no highlights", () => {
|
|
expect(fuzzyMatch("", "Weighted die")).toEqual({ score: 0, indices: [] });
|
|
});
|
|
|
|
it("ignores case and spaces in the query", () => {
|
|
const match = fuzzyMatch("W E I", "Weighted die");
|
|
expect(match?.indices).toEqual([0, 1, 2]);
|
|
});
|
|
|
|
it("reports where it matched, for highlighting", () => {
|
|
// The best alignment, not the leftmost one: "die" underlines the word
|
|
// "die" rather than binding its 'd' to the end of "Weighted".
|
|
expect(fuzzyMatch("die", "Weighted die")?.indices).toEqual([9, 10, 11]);
|
|
expect(fuzzyMatch("die", "Ordinary die")?.indices).toEqual([9, 10, 11]);
|
|
});
|
|
|
|
it("prefers a whole-word run over an earlier scattered one", () => {
|
|
// "pain" could scatter across "Painter's" or land on it contiguously.
|
|
expect(fuzzyMatch("paint", "Painter's die B")?.indices).toEqual([0, 1, 2, 3, 4]);
|
|
});
|
|
|
|
it("scores consecutive matches above scattered ones", () => {
|
|
const consecutive = fuzzyMatch("wei", "Weighted die");
|
|
const scattered = fuzzyMatch("wei", "Wagoner's engraved ivory");
|
|
expect(consecutive?.score).toBeGreaterThan(scattered?.score ?? Infinity);
|
|
});
|
|
|
|
it("rewards matching at a word boundary", () => {
|
|
// The 'd' of "die" starts a word; the 'd' inside "Wisdom" does not.
|
|
const boundary = fuzzyMatch("d", "a die");
|
|
const inside = fuzzyMatch("d", "Wisdom");
|
|
expect(boundary?.score).toBeGreaterThan(inside?.score ?? Infinity);
|
|
});
|
|
});
|
|
|
|
describe("fuzzyFilter", () => {
|
|
const names = [
|
|
"Weighted die",
|
|
"Wisdom tooth die",
|
|
"Ordinary die",
|
|
"Painter's die B",
|
|
"Painter's die R",
|
|
];
|
|
|
|
it("finds the die a short prefix is aiming at", () => {
|
|
const results = fuzzyFilter("wei", names, (name) => name);
|
|
expect(results[0].item).toBe("Weighted die");
|
|
});
|
|
|
|
it("narrows to one die as the query gets specific", () => {
|
|
const results = fuzzyFilter("paintb", names, (name) => name);
|
|
expect(results[0].item).toBe("Painter's die B");
|
|
});
|
|
|
|
it("drops non-matches", () => {
|
|
expect(fuzzyFilter("zzzz", names, (name) => name)).toHaveLength(0);
|
|
});
|
|
|
|
it("keeps the original order for an empty query", () => {
|
|
const results = fuzzyFilter(" ", names, (name) => name);
|
|
expect(results.map((result) => result.item)).toEqual(names);
|
|
});
|
|
});
|
|
|
|
describe("mulberry32", () => {
|
|
it("is deterministic for a seed", () => {
|
|
const a = mulberry32(42);
|
|
const b = mulberry32(42);
|
|
expect([a(), a(), a()]).toEqual([b(), b(), b()]);
|
|
});
|
|
|
|
it("differs between seeds", () => {
|
|
expect(mulberry32(1)()).not.toBe(mulberry32(2)());
|
|
});
|
|
|
|
it("stays within [0, 1)", () => {
|
|
const random = mulberry32(7);
|
|
for (let i = 0; i < 1000; i += 1) {
|
|
const value = random();
|
|
expect(value).toBeGreaterThanOrEqual(0);
|
|
expect(value).toBeLessThan(1);
|
|
}
|
|
});
|
|
|
|
it("bounds randomInt below the given bound", () => {
|
|
const random = mulberry32(9);
|
|
for (let i = 0; i < 500; i += 1) {
|
|
const value = randomInt(random, 5);
|
|
expect(value).toBeGreaterThanOrEqual(0);
|
|
expect(value).toBeLessThan(5);
|
|
expect(Number.isInteger(value)).toBe(true);
|
|
}
|
|
});
|
|
});
|