From 0d7126422f2934ef699a989d1833aa6c6d0f4b74 Mon Sep 17 00:00:00 2001 From: kai392 Date: Fri, 31 Jul 2026 15:43:20 +0800 Subject: [PATCH] fix(ui-kit): render chart tooltip zero inside tabular-nums span Replace the truthiness guard on ChartTooltipContent value with an explicit nullish check so 0 and empty string go through toLocaleString in the formatted span instead of a bare React text child. Closes #10051 Co-authored-by: Cursor --- .../src/components/chart.test.tsx | 85 +++++++++++++++++++ .../loopover-ui-kit/src/components/chart.tsx | 4 +- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/loopover-ui-kit/src/components/chart.test.tsx b/packages/loopover-ui-kit/src/components/chart.test.tsx index f23723865a..9cf80af706 100644 --- a/packages/loopover-ui-kit/src/components/chart.test.tsx +++ b/packages/loopover-ui-kit/src/components/chart.test.tsx @@ -125,6 +125,91 @@ describe("ChartTooltipContent (recharts v3, #8610)", () => { expect(chart.getByText("$120")).toBeTruthy(); }); + // #10051: a numeric 0 is a real data point — the old `item.value &&` guard rendered it as a bare + // React text child (losing tabular-nums / toLocaleString). Pin both arms of the nullish guard and + // assert the digit sits inside the formatted span, not beside it. + it("REGRESSION (#10051): zero value renders inside the tabular-nums span, not as a bare text child", () => { + const zeroPayload = [ + { + dataKey: "revenue", + name: "revenue", + value: 0, + color: "#0ea5e9", + payload: { month: "Jan", revenue: 0 }, + }, + ]; + const chart = renderInChart( + , + ); + + const valueSpan = chart.slot().querySelector("span.tabular-nums"); + expect(valueSpan).not.toBeNull(); + expect(valueSpan!.className).toContain("font-mono"); + expect(valueSpan!.className).toContain("tabular-nums"); + expect(valueSpan!.textContent).toBe("0"); + + // The formatted "0" must be a descendant of the span — not a direct text child of the row. + const row = valueSpan!.closest(".flex.w-full"); + expect(row).not.toBeNull(); + const directZero = Array.from(row!.childNodes).some( + (node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() === "0", + ); + expect(directZero).toBe(false); + expect(chart.getByText("Revenue")).toBeTruthy(); + }); + + it("omits the value span only when value is undefined or null (#10051)", () => { + const undefinedPayload = [ + { + dataKey: "revenue", + name: "revenue", + value: undefined, + color: "#0ea5e9", + payload: { month: "Jan", revenue: 0 }, + }, + ]; + const missing = renderInChart( + , + ); + expect(missing.slot().textContent).toContain("Revenue"); + expect(missing.slot().querySelector("span.tabular-nums")).toBeNull(); + missing.unmount(); + + const nullPayload = [ + { + dataKey: "revenue", + name: "revenue", + value: null, + color: "#0ea5e9", + payload: { month: "Jan", revenue: 0 }, + }, + ]; + const nullish = renderInChart( + , + ); + expect(nullish.slot().textContent).toContain("Revenue"); + expect(nullish.slot().querySelector("span.tabular-nums")).toBeNull(); + }); + + it("renders an empty-string value through the tabular-nums span (#10051)", () => { + const emptyPayload = [ + { + dataKey: "revenue", + name: "revenue", + value: "", + color: "#0ea5e9", + payload: { month: "Jan", revenue: "" }, + }, + ]; + const chart = renderInChart( + , + ); + const valueSpan = chart.slot().querySelector("span.tabular-nums"); + expect(valueSpan).not.toBeNull(); + expect(valueSpan!.textContent).toBe(""); + expect(chart.getByText("Revenue")).toBeTruthy(); + }); + it("resolves a series by nameKey when the payload's own key is not the config key", () => { const chart = renderInChart( - {item.value && ( + {item.value !== undefined && item.value !== null ? ( {item.value.toLocaleString()} - )} + ) : null} )}