diff --git a/src/components/Canary/CanaryStatusChart.tsx b/src/components/Canary/CanaryStatusChart.tsx index 89245df9b..baa73c40c 100644 --- a/src/components/Canary/CanaryStatusChart.tsx +++ b/src/components/Canary/CanaryStatusChart.tsx @@ -85,7 +85,10 @@ export function CanaryStatusChart({ // we need to lift this up const { data: graphData, isLoading } = useCanaryGraphQuery(timeRange, check); - const data = useMemo(() => (graphData?.status ?? []).reverse(), [graphData]); + const data = useMemo( + () => [...(graphData?.status ?? [])].reverse(), + [graphData] + ); useEffect(() => { const updatedFormat = getUpdatedFormat(timeRange); diff --git a/src/components/Canary/__tests__/CanaryStatusChart.unit.test.tsx b/src/components/Canary/__tests__/CanaryStatusChart.unit.test.tsx new file mode 100644 index 000000000..72004b6d1 --- /dev/null +++ b/src/components/Canary/__tests__/CanaryStatusChart.unit.test.tsx @@ -0,0 +1,99 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen } from "@testing-library/react"; +import * as topologyApi from "../../../api/services/topology"; +import { CanaryStatusChart } from "../CanaryStatusChart"; + +jest.mock("../../../api/services/topology", () => ({ + getCanaryGraph: jest.fn() +})); + +jest.mock("recharts", () => ({ + ResponsiveContainer: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + ScatterChart: ({ + children, + data + }: { + children: React.ReactNode; + data: { time: string }[]; + }) => ( +
+ {children} +
+ ), + Scatter: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + CartesianGrid: () => null, + Cell: () => null, + Tooltip: () => null, + XAxis: () => null, + YAxis: () => null +})); + +const statuses = [ + { time: "2025-01-01T10:00:00Z", status: true, duration: 100 }, + { time: "2025-01-01T10:01:00Z", status: false, duration: 300 }, + { time: "2025-01-01T10:02:00Z", status: true, duration: 200 } +]; + +const graphData = { + duration: 200, + runnerName: "runner", + status: statuses, + latency: { rollingIn: 200 }, + uptime: { passed: 2, failed: 1 } +}; + +const queryKey = ["canaryGraph", "check-1", "1h"]; + +function renderChart(queryClient: QueryClient) { + return render( + + + + ); +} + +describe("CanaryStatusChart", () => { + it("preserves cached graph data across unmounts while keeping display order stable", async () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Infinity + } + } + }); + (topologyApi.getCanaryGraph as jest.Mock).mockResolvedValue({ + data: graphData + }); + + const expectedDisplayOrder = [...statuses].reverse(); + const view = renderChart(queryClient); + + expect(await screen.findByTestId("canary-status-chart")).toHaveAttribute( + "data-times", + JSON.stringify(expectedDisplayOrder) + ); + expect(queryClient.getQueryData(queryKey)).toEqual(graphData); + expect(graphData.status).toEqual(statuses); + + view.unmount(); + renderChart(queryClient); + + expect(await screen.findByTestId("canary-status-chart")).toHaveAttribute( + "data-times", + JSON.stringify(expectedDisplayOrder) + ); + expect(queryClient.getQueryData(queryKey)).toEqual(graphData); + expect(graphData.status).toEqual(statuses); + expect(topologyApi.getCanaryGraph).toHaveBeenCalledTimes(1); + }); +});