import { OTHER_KEY, clientKey, qtypeKey, routeKey, seriesColor } from "./seriesColors";

test("the four source-less route kinds and other are fixed, so they mean one thing everywhere", () => {
	expect(seriesColor(routeKey("blocked", null))).toBe("#ef4444");
	expect(seriesColor(routeKey("cache", null))).toBe("#059669");
	expect(seriesColor(routeKey("local", null))).toBe("#8b5cf6");
	expect(seriesColor(routeKey("rejected", null))).toBe("#f59e0b");
	expect(seriesColor(OTHER_KEY)).toBe("#71717a");
});

test("the colour of a key depends on the key and on nothing else", () => {
	// Rank churn and membership churn at once: the panel a poll later is in the
	// opposite order, has gained a client and has lost one. Every entry that
	// survived keeps its colour, because nothing here reads the set.
	const survivors = [clientKey("192.0.2.30"), clientKey("192.0.2.31"), clientKey("192.0.2.32"), OTHER_KEY];
	const before = survivors.map(seriesColor);
	const after = [clientKey("192.0.2.10"), ...survivors].reverse().map(seriesColor);
	for (const [i, key] of survivors.entries()) {
		expect(seriesColor(key)).toBe(before[i]);
		expect(after).toContain(before[i]);
	}
});

test("a panel of realistic entries gets a spread of hues, not one colour repeated", () => {
	// The degenerate implementation this refutes: a dynamic branch that returns
	// one constant would satisfy every stability test in this file. It also states
	// the real cost of hashing without assignment — eight clients come out in five
	// hues here, seven query types in four — which is why the donut strokes its
	// arcs and the client chart strokes its segments.
	const clients = [
		"192.0.2.30",
		"192.0.2.31",
		"192.0.2.32",
		"192.0.2.40",
		"10.0.0.5",
		"10.0.0.6",
		"fd00::1",
		"laptop.lan",
	];
	const types = [1, 28, 65, 12, 16, 33];
	const routes = ["https://dns.example/dns-query", "https://dns2.example/dns-query", "lan"];

	const spreadOf = (keys: string[]) => new Set(keys.map(seriesColor)).size;
	expect(spreadOf(clients.map(clientKey))).toBeGreaterThan(1);
	expect(spreadOf([...types.map(qtypeKey), qtypeKey(null)])).toBeGreaterThan(1);
	expect(spreadOf(routes.map((source) => routeKey("upstream", source)))).toBeGreaterThan(1);
	// Half the panel distinct at worst, which is what makes the legend readable
	// rather than a list of identical swatches.
	expect(spreadOf(clients.map(clientKey))).toBeGreaterThanOrEqual(clients.length / 2);
});

test("a dynamic entry never takes a fixed entry's colour", () => {
	// The bug this rules out: a nameless upstream row coming out the same red as
	// the Blocked slice beside it in the same ring.
	const fixedColors = new Set(["#ef4444", "#059669", "#8b5cf6", "#f59e0b", "#71717a"]);
	const keys = [routeKey("upstream", null), routeKey("forward_zone", "lan"), qtypeKey(28), qtypeKey(null)];
	for (const key of keys) expect(fixedColors.has(seriesColor(key))).toBe(false);
});

test("the same name under two route kinds is two identities", () => {
	expect(routeKey("upstream", "lan")).not.toBe(routeKey("forward_zone", "lan"));
});

test("two upstreams are two identities: the pair, not the route kind, is the key", () => {
	expect(routeKey("upstream", "https://dns.example/dns-query")).not.toBe(
		routeKey("upstream", "https://dns2.example/dns-query"),
	);
});

test("a null qtype is its own entry rather than folded into a real one", () => {
	expect(qtypeKey(null)).not.toBe(qtypeKey(1));
	expect(seriesColor(qtypeKey(null))).toBe(seriesColor(qtypeKey(null)));
});
