22 vitest cases for two pure helpers that had none: - columnSort: numeric-vs-locale comparison, asc/desc, no-mutation, the null/junk coercion in parseSortState, and the unsorted→asc→desc→unsorted click cycle. - linkify: http/www/bare-domain URLs, the version-number non-match, the "<platform>: @handle" mention (handle linked, prefix kept as text), alias→canonical base URL, bare @handle/#hashtag left plain, and multi-link ordering. Nodes are inspected structurally, so the existing node-env vitest needs no DOM. useCardPager (a hook) is deferred to when component/hook test infra lands with the backend pytest sprint.
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { sortRows, parseSortState, cycleSort, type SortState } from "./columnSort";
|
|
|
|
interface Row {
|
|
name: string;
|
|
views: number;
|
|
}
|
|
const rows: Row[] = [
|
|
{ name: "beta", views: 30 },
|
|
{ name: "alpha", views: 100 },
|
|
{ name: "gamma", views: 5 },
|
|
];
|
|
const columns = [
|
|
{ key: "name", sortValue: (r: Row) => r.name },
|
|
{ key: "views", sortValue: (r: Row) => r.views },
|
|
{ key: "nosort" }, // a column with no sortValue
|
|
];
|
|
|
|
describe("sortRows", () => {
|
|
it("returns the same array reference when there is no sort", () => {
|
|
expect(sortRows(rows, columns, null)).toBe(rows);
|
|
});
|
|
|
|
it("returns the input unchanged for a column that has no sortValue", () => {
|
|
expect(sortRows(rows, columns, { key: "nosort", dir: "asc" })).toBe(rows);
|
|
});
|
|
|
|
it("sorts numbers by numeric difference, not string order", () => {
|
|
// string order would put "100" before "30"; numeric must not.
|
|
const asc = sortRows(rows, columns, { key: "views", dir: "asc" }).map((r) => r.views);
|
|
expect(asc).toEqual([5, 30, 100]);
|
|
const desc = sortRows(rows, columns, { key: "views", dir: "desc" }).map((r) => r.views);
|
|
expect(desc).toEqual([100, 30, 5]);
|
|
});
|
|
|
|
it("sorts strings with locale compare, ascending and descending", () => {
|
|
expect(sortRows(rows, columns, { key: "name", dir: "asc" }).map((r) => r.name)).toEqual([
|
|
"alpha",
|
|
"beta",
|
|
"gamma",
|
|
]);
|
|
expect(sortRows(rows, columns, { key: "name", dir: "desc" }).map((r) => r.name)).toEqual([
|
|
"gamma",
|
|
"beta",
|
|
"alpha",
|
|
]);
|
|
});
|
|
|
|
it("does not mutate the input array", () => {
|
|
const before = rows.map((r) => r.name);
|
|
sortRows(rows, columns, { key: "name", dir: "asc" });
|
|
expect(rows.map((r) => r.name)).toEqual(before);
|
|
});
|
|
});
|
|
|
|
describe("parseSortState", () => {
|
|
it("accepts a well-formed state", () => {
|
|
expect(parseSortState({ key: "views", dir: "desc" })).toEqual({ key: "views", dir: "desc" });
|
|
});
|
|
|
|
it.each([
|
|
["null", null],
|
|
["a string", "views"],
|
|
["missing dir", { key: "views" }],
|
|
["a bad dir", { key: "views", dir: "sideways" }],
|
|
["a non-string key", { key: 3, dir: "asc" }],
|
|
])("coerces %s to null rather than passing junk to the comparator", (_label, raw) => {
|
|
expect(parseSortState(raw)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("cycleSort", () => {
|
|
it("walks unsorted → asc → desc → unsorted for the same key", () => {
|
|
let s: SortState = null;
|
|
s = cycleSort(s, "views");
|
|
expect(s).toEqual({ key: "views", dir: "asc" });
|
|
s = cycleSort(s, "views");
|
|
expect(s).toEqual({ key: "views", dir: "desc" });
|
|
s = cycleSort(s, "views");
|
|
expect(s).toBeNull();
|
|
});
|
|
|
|
it("jumps straight to ascending when a different column is clicked", () => {
|
|
expect(cycleSort({ key: "name", dir: "desc" }, "views")).toEqual({ key: "views", dir: "asc" });
|
|
});
|
|
});
|