Files
hyperframes/packages/studio/src/telemetry/events.test.ts
T
James da38de1b12 test+fix(telemetry): address PR review — dev-mode gate, session-storage dedupe, payload tests
Addresses review comments on #982:

- studio shouldTrack(): adds VITE_HYPERFRAMES_NO_TELEMETRY (mirrors CLI's
  HYPERFRAMES_NO_TELEMETRY) and import.meta.env.DEV gates so dev / CI
  studio builds don't pollute production telemetry. shouldTrack() is now
  exported for testability.
- App.tsx session dedupe: moves the once-per-session check from a useRef
  (which resets on HMR / remount) to sessionStorage via new
  hasFiredSessionStart / markSessionStartFired helpers in config.ts.
- studioRenderTelemetry.ts: documents why `workers` is intentionally
  omitted from emitStudioRenderError (studio renders don't accept a
  user-supplied worker count, so early failures genuinely don't know one).
- client.ts flush(): documents fire-and-forget no-retry design so future
  hands don't accidentally add retry logic that double-counts.

Tests:
- studioRenderTelemetry.test.ts (8 tests): perfPayload mapping for every
  RenderPerfSummary field, undefined-perf path, missing-extract path,
  zero-elapsed edge case, error event shape.
- studio/telemetry/events.test.ts (4 tests): pin event names
  (studio_session_start, studio_render_start) and payload shape.
- studio/telemetry/client.test.ts (9 tests): shouldTrack() returns false
  for non-phc_ key, opt-out, doNotTrack, build-time env, vite dev mode;
  memoization.
2026-05-20 14:53:38 -04:00

58 lines
1.9 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest";
// Mock client.trackEvent so we can assert event names and payloads without
// firing network requests or relying on memoized shouldTrack() state.
const trackEvent = vi.fn();
vi.mock("./client", () => ({
trackEvent: (...args: unknown[]) => trackEvent(...args),
}));
const { trackStudioSessionStart, trackStudioRenderStart } = await import("./events");
describe("studio telemetry events", () => {
beforeEach(() => {
trackEvent.mockClear();
});
it("trackStudioSessionStart emits 'studio_session_start' with has_project", () => {
trackStudioSessionStart({ has_project: true });
expect(trackEvent).toHaveBeenCalledOnce();
expect(trackEvent).toHaveBeenCalledWith("studio_session_start", { has_project: true });
});
it("trackStudioSessionStart preserves false for has_project (scratch open)", () => {
trackStudioSessionStart({ has_project: false });
expect(trackEvent).toHaveBeenCalledWith("studio_session_start", { has_project: false });
});
it("trackStudioRenderStart emits 'studio_render_start' with all render opts", () => {
trackStudioRenderStart({
fps: 30,
quality: "standard",
format: "mp4",
resolution: "landscape",
composition: "intro.html",
});
expect(trackEvent).toHaveBeenCalledOnce();
expect(trackEvent).toHaveBeenCalledWith("studio_render_start", {
fps: 30,
quality: "standard",
format: "mp4",
resolution: "landscape",
composition: "intro.html",
});
});
it("trackStudioRenderStart leaves optional fields undefined when omitted", () => {
trackStudioRenderStart({ fps: 60, quality: "high", format: "webm" });
const payload = trackEvent.mock.calls[0][1];
expect(payload).toEqual({
fps: 60,
quality: "high",
format: "webm",
resolution: undefined,
composition: undefined,
});
});
});