Files
buzz/lib/utils.test.ts

153 lines
4.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
b2s,
cn,
fileToDataUrl,
parseSearchNumber,
parseSearchString,
pausePass,
ResponseNotOkError,
shortestPrefixes,
throwNotOk,
} from "./utils";
describe("cn", () => {
test("combines conditional classes and resolves Tailwind conflicts", () => {
expect(
cn("block", false && "hidden", ["px-2", { flex: true }], "px-4"),
).toBe("flex px-4");
});
test("returns an empty string without classes", () => {
expect(cn()).toBe("");
});
});
describe("b2s", () => {
test.each([
[0, "0.0 B"],
[1, "1.0 B"],
[1023, "1023.0 B"],
[1024, "1.0KB"],
[1536, "1.5KB"],
[1024 ** 2, "1.0MB"],
[1024 ** 3, "1.0GB"],
[1024 ** 4, "1.0PB"],
])("formats %d bytes as %s", (bytes, expected) => {
expect(b2s(bytes)).toBe(expected);
});
});
describe("shortestPrefixes", () => {
test("finds the shortest distinguishing prefixes", () => {
expect([...shortestPrefixes(["apple", "apricot", "banana"])]).toEqual([
["apple", "app"],
["apricot", "apr"],
["banana", "b"],
]);
});
test("handles values that are prefixes of other values", () => {
expect([...shortestPrefixes(["a", "ab", "abc"])]).toEqual([
["a", "a"],
["ab", "ab"],
["abc", "abc"],
]);
});
test("deduplicates while preserving first-seen order", () => {
expect([...shortestPrefixes(["cat", "dog", "cat", "door"])]).toEqual([
["cat", "c"],
["dog", "dog"],
["door", "doo"],
]);
});
test("supports empty input, empty values, and Unicode code points", () => {
expect(shortestPrefixes([]).size).toBe(0);
expect(shortestPrefixes([""]).get("")).toBe("");
expect([...shortestPrefixes(["😀a", "😀b"]).values()]).toEqual([
"😀a",
"😀b",
]);
});
});
describe("fileToDataUrl", () => {
test("encodes file bytes and MIME type", async () => {
const file = new File([new Uint8Array([0, 1, 2, 255])], "bytes.bin", {
type: "application/octet-stream",
});
expect(await fileToDataUrl(file)).toBe(
"data:application/octet-stream;base64,AAEC/w==",
);
});
test("supports empty files without a MIME type", async () => {
expect(await fileToDataUrl(new File([], "empty"))).toBe("data:;base64,");
});
});
describe("search parameter parsers", () => {
test.each([
["12.5", 0, 12.5],
["12px", 0, 12],
["", 7, 7],
[undefined, -1, -1],
] as const)("parses numeric parameter %p", (param, fallback, expected) => {
expect(parseSearchNumber(param, fallback)).toBe(expected);
});
test("uses the first numeric array value", () => {
expect(parseSearchNumber(["bad", "5"], 3)).toBe(3);
expect(parseSearchNumber([], 4)).toBe(4);
});
test("preserves infinities accepted by parseFloat", () => {
expect(parseSearchNumber("Infinity")).toBe(Number.POSITIVE_INFINITY);
});
test.each([
["value", "fallback", "value"],
["", "fallback", ""],
[undefined, "fallback", "fallback"],
] as const)("parses string parameter %p", (param, fallback, expected) => {
expect(parseSearchString(param, fallback)).toBe(expected);
});
test("uses the first string array value", () => {
expect(parseSearchString(["first", "second"])).toBe("first");
expect(parseSearchString([], "fallback")).toBe("fallback");
});
});
describe("pausePass", () => {
test("resolves with the same value after the delay", async () => {
const value = { id: 1 };
const promise = pausePass<typeof value>(1)(value);
expect(promise).toBeInstanceOf(Promise);
expect(await promise).toBe(value);
});
});
describe("throwNotOk", () => {
test("returns the same successful response", () => {
const response = new Response("ok", { status: 200 });
expect(throwNotOk(response)).toBe(response);
});
test.each([400, 404, 500])("throws a typed error for status %d", (status) => {
const response = new Response("failure", { status });
try {
throwNotOk(response);
throw new Error("expected throwNotOk to throw");
} catch (error) {
expect(error).toBeInstanceOf(ResponseNotOkError);
expect(error).toMatchObject({ status, response });
expect((error as Error).message).toBe(
`RESPONSE_NOT_OK: Got status ${status}`,
);
}
});
});