// @vitest-environment jsdom import { ReactNode } from "react"; import { describe, test, expect, beforeEach, vi, afterEach } from "vitest"; import { render, waitFor, screen } from "@testing-library/react"; import { loader, TimeSeriesCard } from "../resources.timeseries"; import * as RemixReact from "react-router"; import "vitest-dom/extend-expect"; import { getDefaultContext } from "./testutils"; vi.mock("~/lib/auth", () => ({ requireAuth: vi.fn(), })); // Mock the useFetcher hook vi.mock("react-router", async () => { const actual = await vi.importActual("react-router"); return { ...actual, useFetcher: vi.fn(), }; }); describe("resources.timeseries loader", () => { const { context } = getDefaultContext(); beforeEach(() => { vi.spyOn( context.analyticsEngine, "getViewsGroupedByInterval", ).mockResolvedValue([ ["2024-01-15T00:00:00Z", { views: 100, visitors: 0, bounces: 0 }], ["2024-01-16T00:00:00Z", { views: 200, visitors: 0, bounces: 0 }], ]); // mock out responsive container to just return a standard div, otherwise // recharts doesnt render underneath vi.mock("recharts", async () => { const OriginalModule = await vi.importActual("recharts"); return { ...OriginalModule, ResponsiveContainer: ({ children, }: { children: ReactNode; }) =>
{children}
, }; }); }); afterEach(() => { vi.restoreAllMocks(); }); test("processes data correctly", async () => { const request = new Request( "http://test.com?interval=7d&site=test-site&timezone=UTC", ); // @ts-expect-error we don't need to provide all the properties of the context object const result = await loader({ context, request, }); const data = await result; expect(data.chartData).toEqual([ { date: "2024-01-15T00:00:00Z", views: 100, visitors: 0, bounceRate: 0, }, { date: "2024-01-16T00:00:00Z", views: 200, visitors: 0, bounceRate: 0, }, ]); expect(data.intervalType).toBe("DAY"); expect( context.analyticsEngine.getViewsGroupedByInterval, ).toHaveBeenCalledWith( "test-site", "DAY", expect.any(Date), expect.any(Date), "UTC", {}, ); }); }); describe("TimeSeriesCard", () => { const mockFetcher = { submit: vi.fn(), data: { chartData: [ { date: "2024-01-15T00:00:00Z", views: 100 }, { date: "2024-01-16T00:00:00Z", views: 200 }, ], intervalType: "DAY", }, }; beforeEach(() => { // Clear mock call counts before each test mockFetcher.submit.mockClear(); // @ts-expect-error we don't need to provide all the properties of the mockFetcher vi.mocked(RemixReact.useFetcher).mockReturnValue(mockFetcher); // Mock ResizeObserver for recharts global.ResizeObserver = vi.fn().mockImplementation(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn(), })); }); afterEach(() => { vi.restoreAllMocks(); }); test("fetches data on mount", () => { const props = { siteId: "test-site", interval: "7d", filters: {}, timezone: "UTC", }; render(); expect(mockFetcher.submit).toHaveBeenCalledWith( { site: "test-site", interval: "7d", timezone: "UTC", }, { method: "get", action: "/resources/timeseries", }, ); }); test("renders TimeSeriesChart when data is available", async () => { const props = { siteId: "test-site", interval: "7d", filters: {}, timezone: "UTC", }; render(); // Wait for the chart to be rendered await waitFor(() => screen.getAllByText("Mon, Jan 15").length > 0); }); test("refetches when props change", () => { expect(mockFetcher.submit).toHaveBeenCalledTimes(0); const props = { siteId: "test-site", interval: "7d", filters: {}, timezone: "UTC", }; const { rerender } = render(); // Change interval rerender(); expect(mockFetcher.submit).toHaveBeenCalledTimes(2); expect(mockFetcher.submit).toHaveBeenLastCalledWith( { site: "test-site", interval: "1d", timezone: "UTC", }, { method: "get", action: "/resources/timeseries", }, ); }); });