the freetype client field is gone. the picker is a select-only menu of known clients with multi-select, chips for the active set, and a 32-client cap shared with the server. the api accepts a comma-separated client list and filters any-of with bound parameters. the trigger carets come from phosphor icons, newly adopted. bundle budget rises to 900000 bytes for the picker and the icon dependency.
142 lines
4.1 KiB
TypeScript
142 lines
4.1 KiB
TypeScript
import {
|
|
validateActivitySearch,
|
|
validateBlocked,
|
|
validateMode,
|
|
validateText,
|
|
validateTimestamp,
|
|
MAX_CLIENTS,
|
|
validateClients,
|
|
} from "./search";
|
|
|
|
test("mode is the two-value union, defaulting to history", () => {
|
|
expect(validateMode("live")).toBe("live");
|
|
expect(validateMode("history")).toBe("history");
|
|
expect(validateMode(undefined)).toBe("history");
|
|
expect(validateMode("Live")).toBe("history");
|
|
expect(validateMode("")).toBe("history");
|
|
expect(validateMode(0)).toBe("history");
|
|
expect(validateMode(["live"])).toBe("history");
|
|
});
|
|
|
|
test("a bound is a safe integer or nothing at all", () => {
|
|
expect(validateTimestamp(1_700_000_000)).toBe(1_700_000_000);
|
|
expect(validateTimestamp(0)).toBe(0);
|
|
expect(validateTimestamp(-1)).toBe(-1);
|
|
});
|
|
|
|
test.each([
|
|
["a fraction", 1_700_000_000.5],
|
|
["Infinity", Number.POSITIVE_INFINITY],
|
|
["-Infinity", Number.NEGATIVE_INFINITY],
|
|
["NaN", Number.NaN],
|
|
["past the safe range", Number.MAX_SAFE_INTEGER + 1],
|
|
["1e21", 1e21],
|
|
["a numeric string", "1700000000"],
|
|
["an empty string", ""],
|
|
["null", null],
|
|
["undefined", undefined],
|
|
["a boolean", true],
|
|
["an array", [1_700_000_000]],
|
|
["a bigint", 1_700_000_000n],
|
|
])("a bound rejects %s", (_name, value) => {
|
|
expect(validateTimestamp(value)).toBeUndefined();
|
|
});
|
|
|
|
test("blocked keeps false, which is the allowed-only filter", () => {
|
|
expect(validateBlocked(true)).toBe(true);
|
|
expect(validateBlocked(false)).toBe(false);
|
|
});
|
|
|
|
test.each([
|
|
["the string true", "true"],
|
|
["the string false", "false"],
|
|
["1", 1],
|
|
["0", 0],
|
|
["null", null],
|
|
["undefined", undefined],
|
|
])("blocked rejects %s", (_name, value) => {
|
|
expect(validateBlocked(value)).toBeUndefined();
|
|
});
|
|
|
|
test("a text filter is trimmed, and an empty one is no filter", () => {
|
|
expect(validateText("ads.example")).toBe("ads.example");
|
|
expect(validateText(" ads.example ")).toBe("ads.example");
|
|
expect(validateText("")).toBeUndefined();
|
|
expect(validateText(" ")).toBeUndefined();
|
|
expect(validateText("\t\n")).toBeUndefined();
|
|
expect(validateText(42)).toBeUndefined();
|
|
expect(validateText(undefined)).toBeUndefined();
|
|
});
|
|
|
|
test("a whole search normalizes every field and drops nothing else in", () => {
|
|
expect(
|
|
validateActivitySearch({
|
|
mode: "live",
|
|
since: 1_700_000_000,
|
|
until: 1_700_000_600,
|
|
domain: " ads.example ",
|
|
client: "192.0.2.10",
|
|
blocked: false,
|
|
unknown: "kept out",
|
|
}),
|
|
).toEqual({
|
|
mode: "live",
|
|
since: 1_700_000_000,
|
|
until: 1_700_000_600,
|
|
domain: "ads.example",
|
|
client: "192.0.2.10",
|
|
blocked: false,
|
|
});
|
|
});
|
|
|
|
test("an empty search is history with no filters", () => {
|
|
expect(validateActivitySearch({})).toEqual({
|
|
mode: "history",
|
|
since: undefined,
|
|
until: undefined,
|
|
domain: undefined,
|
|
client: undefined,
|
|
blocked: undefined,
|
|
});
|
|
});
|
|
|
|
test("a search of junk applies nothing", () => {
|
|
expect(
|
|
validateActivitySearch({
|
|
mode: "HISTORY ",
|
|
since: "1700000000",
|
|
until: Number.POSITIVE_INFINITY,
|
|
domain: " ",
|
|
client: null,
|
|
blocked: "true",
|
|
}),
|
|
).toEqual({
|
|
mode: "history",
|
|
since: undefined,
|
|
until: undefined,
|
|
domain: undefined,
|
|
client: undefined,
|
|
blocked: undefined,
|
|
});
|
|
});
|
|
|
|
test("the client list is canonicalized once, so the chips and the request agree", () => {
|
|
expect(validateClients("192.0.2.10,192.0.2.11")).toBe("192.0.2.10,192.0.2.11");
|
|
|
|
// Blanks name no client and a repeat asks for the same client twice, so
|
|
// neither changes which rows come back: dropping them is the same filter
|
|
// written once, not a different one.
|
|
expect(validateClients(" 192.0.2.10 , ,192.0.2.11,192.0.2.10,")).toBe("192.0.2.10,192.0.2.11");
|
|
expect(validateClients(",,")).toBeUndefined();
|
|
expect(validateClients("")).toBeUndefined();
|
|
expect(validateClients(null)).toBeUndefined();
|
|
});
|
|
|
|
test("a pasted list past the cap is cut to what the api will accept", () => {
|
|
const addresses = Array.from({ length: MAX_CLIENTS + 8 }, (_, index) => `198.51.100.${index + 1}`);
|
|
|
|
// The API refuses a longer list outright, so keeping the extra addresses
|
|
// would show a filter that cannot be applied at all.
|
|
expect(validateClients(addresses.join(","))).toBe(addresses.slice(0, MAX_CLIENTS).join(","));
|
|
});
|