124 lines
3.6 KiB
TypeScript
124 lines
3.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|||
|
|
import { channelsToGraph, statsToGraph } from "./graph";
|
||
|
|
import { computeLayout, radiusFor } from "./layout";
|
||
|
|
|
||
|
|
const statsFixture = {
|
||
|
|
total_messages: 1200,
|
||
|
|
total_users: 40,
|
||
|
|
total_flagged: 30,
|
||
|
|
total_clean: 1100,
|
||
|
|
total_warned: 12,
|
||
|
|
total_error: 2,
|
||
|
|
total_voice_recordings: 8,
|
||
|
|
total_profiles: 40,
|
||
|
|
today_messages: 55,
|
||
|
|
today_flagged: 3,
|
||
|
|
active_users_24h: 18,
|
||
|
|
top_channels: [
|
||
|
|
{ channel_id: "c1", channel_name: "general", message_count: 400 },
|
||
|
|
{ channel_id: "c2", channel_name: "random", message_count: 200 },
|
||
|
|
],
|
||
|
|
moderation_overview: { pending: 1, processing: 0, error: 0 },
|
||
|
|
};
|
||
|
|
|
||
|
|
describe("statsToGraph", () => {
|
||
|
|
test("guild center + channel nodes + edges", () => {
|
||
|
|
const g = statsToGraph(statsFixture);
|
||
|
|
expect(g.nodes.length).toBe(3);
|
||
|
|
expect(g.nodes[0]?.id).toBe("guild");
|
||
|
|
expect(g.edges.length).toBe(2);
|
||
|
|
expect(g.edges[0]?.source).toBe("guild");
|
||
|
|
});
|
||
|
|
|
||
|
|
test("bigger channel gets bigger value", () => {
|
||
|
|
const g = statsToGraph(statsFixture);
|
||
|
|
const c1 = g.nodes.find((n) => n.label === "general");
|
||
|
|
const c2 = g.nodes.find((n) => n.label === "random");
|
||
|
|
expect((c1?.value ?? 0) > (c2?.value ?? 0)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("empty top_channels still yields guild node", () => {
|
||
|
|
const g = statsToGraph({ ...statsFixture, top_channels: [] });
|
||
|
|
expect(g.nodes.length).toBe(1);
|
||
|
|
expect(g.edges.length).toBe(0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("channelsToGraph", () => {
|
||
|
|
test("maps every channel", () => {
|
||
|
|
const chans = [
|
||
|
|
{
|
||
|
|
channel_id: "a",
|
||
|
|
channel_name: "alpha",
|
||
|
|
total_messages: 10,
|
||
|
|
flagged_count: 1,
|
||
|
|
},
|
||
|
|
{ channel_id: "b", total_messages: 5, flagged_count: 0 },
|
||
|
|
];
|
||
|
|
const g = channelsToGraph(chans);
|
||
|
|
expect(g.nodes.length).toBe(2);
|
||
|
|
expect(g.nodes[1]?.label).toBe("b"); // falls back to id when name null
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("computeLayout", () => {
|
||
|
|
const nodes = [
|
||
|
|
{ id: "guild", kind: "guild", value: 1 },
|
||
|
|
{ id: "channel:c1", kind: "channel", value: 0.8 },
|
||
|
|
{ id: "channel:c2", kind: "channel", value: 0.4 },
|
||
|
|
];
|
||
|
|
const edges = [
|
||
|
|
{ source: "guild", target: "channel:c1" },
|
||
|
|
{ source: "guild", target: "channel:c2" },
|
||
|
|
];
|
||
|
|
|
||
|
|
test("deterministic for same seed", () => {
|
||
|
|
const a = computeLayout(nodes, edges, {
|
||
|
|
width: 900,
|
||
|
|
height: 700,
|
||
|
|
seed: 42,
|
||
|
|
});
|
||
|
|
const b = computeLayout(nodes, edges, {
|
||
|
|
width: 900,
|
||
|
|
height: 700,
|
||
|
|
seed: 42,
|
||
|
|
});
|
||
|
|
expect(a.map((n) => [n.id, Math.round(n.x), Math.round(n.y)])).toEqual(
|
||
|
|
b.map((n) => [n.id, Math.round(n.x), Math.round(n.y)]),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("all finite positions within canvas bounds", () => {
|
||
|
|
const out = computeLayout(nodes, edges, {
|
||
|
|
width: 900,
|
||
|
|
height: 700,
|
||
|
|
seed: 7,
|
||
|
|
});
|
||
|
|
for (const n of out) {
|
||
|
|
expect(Number.isFinite(n.x)).toBe(true);
|
||
|
|
expect(Number.isFinite(n.y)).toBe(true);
|
||
|
|
expect(n.x).toBeGreaterThanOrEqual(-50);
|
||
|
|
expect(n.x).toBeLessThanOrEqual(950);
|
||
|
|
expect(n.y).toBeGreaterThanOrEqual(-50);
|
||
|
|
expect(n.y).toBeLessThanOrEqual(750);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test("reduced motion returns static ring", () => {
|
||
|
|
const out = computeLayout(nodes, edges, {
|
||
|
|
width: 900,
|
||
|
|
height: 700,
|
||
|
|
reduced: true,
|
||
|
|
});
|
||
|
|
expect(out.length).toBe(3);
|
||
|
|
expect(Number.isFinite(out[0]?.y ?? NaN)).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("radiusFor", () => {
|
||
|
|
test("guild larger than channel larger than message", () => {
|
||
|
|
expect(radiusFor("guild", 1)).toBeGreaterThan(radiusFor("channel", 1));
|
||
|
|
expect(radiusFor("channel", 1)).toBeGreaterThan(radiusFor("message", 1));
|
||
|
|
});
|
||
|
|
});
|