Files
multica/apps/web/components/pageview-tracker.test.tsx
Bohan Jiang 2e50df9a6a perf(analytics): report $pageview at section granularity, drop web query-string churn (#3813)
capturePageview now section-normalizes the path (strip query/hash, collapse
UUID and issue-key resource segments) and dedupes consecutive same-section
views, so navigating between issues/agents/etc. no longer fires a billed
PostHog event per resource. The web tracker keys on pathname only (not
searchParams), removing ~17% pure query-string-churn pageviews and keeping
OAuth code/state out of $current_url.

MUL-3081

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-05 15:33:32 +08:00

53 lines
1.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render } from "@testing-library/react";
// Mutable pathname + a spy for the shared capture helper. The tracker reads
// usePathname() and forwards it to capturePageview; section-normalization and
// dedup live in @multica/core/analytics and are unit-tested there, so here we
// only assert the wiring (which path is forwarded, and that the query string
// never re-triggers the effect).
const { state, capturePageview } = vi.hoisted(() => ({
state: { pathname: "/" as string | null },
capturePageview: vi.fn<(path?: string) => void>(),
}));
vi.mock("next/navigation", () => ({
usePathname: () => state.pathname,
}));
vi.mock("@multica/core/analytics", () => ({
capturePageview,
}));
import { PageviewTracker } from "./pageview-tracker";
beforeEach(() => {
state.pathname = "/";
capturePageview.mockClear();
});
describe("web PageviewTracker", () => {
it("captures the pathname on mount and on each pathname change", () => {
const { rerender } = render(<PageviewTracker />);
expect(capturePageview).toHaveBeenCalledTimes(1);
expect(capturePageview).toHaveBeenLastCalledWith("/");
state.pathname = "/acme/issues";
rerender(<PageviewTracker />);
expect(capturePageview).toHaveBeenCalledTimes(2);
expect(capturePageview).toHaveBeenLastCalledWith("/acme/issues");
});
it("does not re-capture on a query-string-only navigation", () => {
state.pathname = "/acme/issues";
const { rerender } = render(<PageviewTracker />);
expect(capturePageview).toHaveBeenCalledTimes(1);
// A filter/sort/search change alters only the query string, which the
// tracker no longer reads — usePathname() is unchanged so the effect's
// dependency does not change and no new pageview fires.
rerender(<PageviewTracker />);
expect(capturePageview).toHaveBeenCalledTimes(1);
});
});