diff --git a/packages/buckaroo-js-core/src/components/DFViewerParts/ChartCell.render.test.tsx b/packages/buckaroo-js-core/src/components/DFViewerParts/ChartCell.render.test.tsx new file mode 100644 index 000000000..d627027cd --- /dev/null +++ b/packages/buckaroo-js-core/src/components/DFViewerParts/ChartCell.render.test.tsx @@ -0,0 +1,77 @@ +/** + * Sparkline rendering contract for the chart cell (pinned rows and data + * columns): + * - every line series draws at 1px with per-point dots disabled — the + * 100x24 chart gets CSS-scaled to fill its grid cell, which turns the + * recharts defaults (dots + scaled stroke) into a fat blobby squiggle + * - a hidden YAxis pads the plot by 2px top and bottom so a series whose + * values sit exactly at dataMin/dataMax (e.g. the "after" side of a + * diff whose values collapsed) isn't drawn on the plot border where + * the clip rect swallows half the stroke + */ +jest.mock("react", () => { + const actual = jest.requireActual("react"); + return { __esModule: true, default: actual, ...actual }; +}); + +import { render } from "@testing-library/react"; +import { getChartCell, LineObservation } from "./ChartCell"; + +// recharts pulls in DOM-measurement code that doesn't run cleanly under +// jsdom — stub the exports ChartCell touches, keeping Line/YAxis props +// inspectable in the rendered tree. +jest.mock("recharts", () => { + const React = require("react"); + return { + Area: () => null, + Bar: () => null, + Line: ({ dataKey, dot, strokeWidth }: any) => + React.createElement("div", { + "data-testid": `line-${dataKey}`, + "data-dot": String(dot), + "data-stroke-width": String(strokeWidth), + }), + Tooltip: () => null, + YAxis: ({ hide, padding }: any) => + React.createElement("div", { + "data-testid": "yaxis-mock", + "data-hide": String(hide), + "data-padding-top": String(padding?.top), + "data-padding-bottom": String(padding?.bottom), + }), + ComposedChart: ({ children }: any) => + React.createElement("div", { "data-testid": "composedchart-mock" }, children), + }; +}); + +const ChartCell = getChartCell({ displayer: "chart" }); + +const validChart: LineObservation[] = [{ lineRed: 10 }, { lineRed: 20 }]; + +const mkProps = (value: any) => ({ + value, + api: {} as any, + colDef: { cellClass: "" } as any, + column: {} as any, + context: {}, +}); + +describe("ChartCell sparkline rendering", () => { + it("draws every line series at 1px with dots disabled", () => { + const { container } = render(); + const lines = container.querySelectorAll('[data-testid^="line-"]'); + expect(lines.length).toBeGreaterThan(0); + lines.forEach((line) => { + expect(line.getAttribute("data-dot")).toBe("false"); + expect(line.getAttribute("data-stroke-width")).toBe("1"); + }); + }); + + it("pads the y domain via a hidden axis so edge-hugging series are not clipped", () => { + const { getByTestId } = render(); + const axis = getByTestId("yaxis-mock"); + expect(axis.getAttribute("data-hide")).toBe("true"); + expect(axis.getAttribute("data-padding-top")).toBe("2"); + expect(axis.getAttribute("data-padding-bottom")).toBe("2"); + }); +}); diff --git a/packages/buckaroo-js-core/src/components/DFViewerParts/HistogramCell.colors.test.tsx b/packages/buckaroo-js-core/src/components/DFViewerParts/HistogramCell.colors.test.tsx new file mode 100644 index 000000000..a367434f7 --- /dev/null +++ b/packages/buckaroo-js-core/src/components/DFViewerParts/HistogramCell.colors.test.tsx @@ -0,0 +1,79 @@ +/** + * Per-bar color support on the histogram cell: a HistogramBar may carry + * `color`, and the population Bar renders one recharts Cell per datum so + * the color lands on that individual bar. Diff views use this to paint the + * change-distribution histogram with the same color key as the data cells; + * bars without a color keep the scheme default. + */ +// jsdom's `crypto` lacks `randomUUID`; HistogramCell's gensym() uses it. +{ + let n = 0; + const existing: any = (globalThis as any).crypto || {}; + if (typeof existing.randomUUID !== "function") { + try { + Object.defineProperty(existing, "randomUUID", { + configurable: true, + value: () => `test-uuid-${++n}`, + }); + } catch { + Object.defineProperty(globalThis, "crypto", { + configurable: true, + value: { ...existing, randomUUID: () => `test-uuid-${++n}` }, + }); + } + } +} + +// ts-jest in this repo doesn't apply esModuleInterop, so the default import +// `import React from "react"` in HistogramCell.tsx resolves to `undefined` +// at runtime without a `.default` on the mock. +jest.mock("react", () => { + const actual = jest.requireActual("react"); + return { __esModule: true, default: actual, ...actual }; +}); + +import { render } from "@testing-library/react"; +import type { ColDef, Column, Context, GridApi } from "ag-grid-community"; +import { HistogramCell } from "./HistogramCell"; + +// recharts pulls in DOM-measurement code that doesn't run cleanly under +// jsdom — stub the exports HistogramCell touches, keeping Bar/Cell props +// inspectable in the rendered tree. +jest.mock("recharts", () => { + const React = require("react"); + return { + Bar: ({ children, dataKey }: any) => + React.createElement("div", { "data-testid": `bar-${dataKey}` }, children), + BarChart: ({ children }: any) => + React.createElement("div", { "data-testid": "barchart-mock" }, children), + Cell: ({ fill }: any) => + React.createElement("div", { "data-testid": "cell-mock", "data-fill": fill }), + Tooltip: () => null, + }; +}); + +const mkProps = (value: any) => ({ + value, + api: {} as GridApi, + colDef: { cellClass: "" } as ColDef, + column: {} as Column, + context: {} as Context, +}); + +describe("HistogramCell per-bar colors", () => { + it("renders one Cell per datum on the population bar, honoring bar.color", () => { + const bars = [ + { name: "<-50%", population: 20, color: "#d62728" }, + { name: "~0%", population: 80 }, + ]; + const { getByTestId } = render(); + const popBar = getByTestId("bar-population"); + const cells = popBar.querySelectorAll('[data-testid="cell-mock"]'); + expect(cells).toHaveLength(2); + expect(cells[0].getAttribute("data-fill")).toBe("#d62728"); + // Uncolored bars keep the scheme default fill — set, and not the + // colored bar's value. + expect(cells[1].getAttribute("data-fill")).toBeTruthy(); + expect(cells[1].getAttribute("data-fill")).not.toBe("#d62728"); + }); +}); diff --git a/tests/unit/compare_test.py b/tests/unit/compare_test.py index 33d6136bd..e912e060b 100644 --- a/tests/unit/compare_test.py +++ b/tests/unit/compare_test.py @@ -464,3 +464,20 @@ def test_merge_column_in_input_rejected(): with pytest.raises(ValueError, match="__buckaroo_merge"): col_join_dfs(df1, df2, join_columns=["id"], how="outer") + + +def test_join_key_columns_use_color_static(): + """Join-key columns get the constant color_static rule. + + The categorical-map-of-identical-colors workaround predates color_static + landing in the compiled JS; the overrides should now emit the rule + directly so the Python types and the wire config say what they mean. + """ + df1 = pd.DataFrame({"id": [1, 2, 3], "val": [10, 20, 30]}) + df2 = pd.DataFrame({"id": [1, 2, 3], "val": [10, 25, 30]}) + + _m_df, overrides, _eqs = col_join_dfs(df1, df2, join_columns=["id"], how="outer") + + cfg = overrides["id"]["color_map_config"] + assert cfg["color_rule"] == "color_static" + assert cfg["color"]