import { expect, test } from "bun:test"; import { readFileSync } from "node:fs"; import { chartConfiguration, chartTypes, chartTickLabel, chartTooltipLines, } from "../src/chart-config"; import { renderComponent, mountHtml } from "../../test/src/index"; import { discoverPlugins, createPluginRunner } from "../../plugin/src/index"; import { resolve } from "node:path"; const source = readFileSync(new URL("../components/Chart.wrn", import.meta.url), "utf8"); test("compact category ticks preserve the complete tooltip text", () => { const label = "2026-10-17T10:03:00 · completed · audit-four"; expect(chartTickLabel(label, 18)).toHaveLength(18); expect(chartTickLabel(label, 18)).toContain("…"); expect(chartTickLabel("Short", 18)).toBe("Short"); const lines = chartTooltipLines(label, 18); expect(lines.every((line) => line.length <= 18)).toBe(true); expect(lines.join("")).toBe(label); }); for (const type of chartTypes) { test(`Chart.js ${type} preserves native datasets`, () => { const data = { labels: ["A", "B"], datasets: [ { label: "Example", data: type === "scatter" || type === "bubble" ? [{ x: 1, y: -2, r: 5 }] : [-20, 40], }, ], }; const config = chartConfiguration({ type, data }); expect(config.type).toBe(type); expect(config.data).toEqual(data); expect(config.data).not.toBe(data); if (["pie", "doughnut", "radar", "polarArea"].includes(type)) expect(config.options?.scales).toBeUndefined(); }); } test("legacy rows preserve negatives, gaps, zero, and small decimal values", () => { const config = chartConfiguration({ items: [ { label: "Loss", value: -20 }, { label: "Gap", value: null }, { label: "Invalid", value: "oops" }, { label: "Zero", value: 0 }, { label: "Tiny", value: 0.0002 }, ], }); expect(config.data.datasets[0].data).toEqual([-20, null, null, 0, 0.0002]); expect(config.data.labels).toEqual(["Loss", "Gap", "Invalid", "Zero", "Tiny"]); }); test("native dataset options, mixed types, area fills, and stacked horizontal axes survive", () => { const data = { labels: ["A"], datasets: [ { type: "bar" as const, data: [20] }, { type: "line" as const, data: [10], borderColor: "red" }, ], }; const options = { indexAxis: "y", scales: { x: { stacked: true }, y: { stacked: true } }, plugins: { legend: { display: false } }, }; const config = chartConfiguration({ type: "bar", data, options }); expect(config.data).toEqual(data); expect(config.options?.indexAxis).toBe("y"); expect((config.options?.scales?.x as any)?.stacked).toBe(true); expect(config.options?.plugins?.legend?.display).toBe(false); const area = chartConfiguration({ type: "area", data: { datasets: [{ data: [1, 2] }] } }); expect(area.type).toBe("line"); expect((area.data.datasets[0] as any).fill).toBe(true); expect(() => chartConfiguration({ type: "unknown" })).toThrow("Unsupported chart type"); }); test("separate datasets and custom legacy keys remain supported", () => { expect(chartConfiguration({ labels: ["A"], datasets: [{ data: [3] }] }).data).toEqual({ labels: ["A"], datasets: [{ data: [3] }], }); expect( chartConfiguration({ items: [{ name: "A", amount: -2 }], labelKey: "name", valueKey: "amount" }) .data.datasets[0].data, ).toEqual([-2]); }); test("SSR safely carries native chart config and typed public outputs", async () => { const data = { labels: [""], datasets: [{ label: "Sample", data: [-2] }] }; const dom = mountHtml(await renderComponent(source, { type: "radar", data })); const root = dom.querySelector('[data-ui-component="Chart"]') as HTMLElement; expect(root.dataset.wrnexusRuntime).toBe("chartjs"); expect(JSON.parse(root.dataset.chartConfig!).data).toEqual(data); expect(root.querySelector("canvas")?.getAttribute("role")).toBe("img"); expect(root.querySelector("script")).toBeNull(); }); test("UI package discovers its bundled Chart.js runtime automatically", async () => { const plugins = await discoverPlugins(resolve("examples/component-showcase"), [], { strict: true, }); const runner = createPluginRunner(plugins, { root: resolve("examples/component-showcase"), mode: "development", command: "dev", metadata: new Map(), warn: () => {}, }); const contributions = await runner.contributions(); expect( contributions.clientRuntimes.some((runtime) => runtime.id === "chartjs" && runtime.bundle), ).toBe(true); });