feat(core-analytics): add IAnalytics interface, types, and NoopAnalytics

Replaces generator placeholder with IAnalytics interface (track, identify,
pageView, flush), AnalyticsAttributeValue + AnalyticsUser types, and
NoopAnalytics implementation. Adds sibling tests covering all four methods
with 100% coverage. All conformance + coverage gates pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 11:31:58 +00:00
parent 80c702eb20
commit 563eab06a6
5 changed files with 129 additions and 13 deletions

View File

@@ -1,18 +1,18 @@
{ {
"generatedAt": "2026-05-13T18:36:10.133Z", "generatedAt": "2026-05-18T11:31:45.093Z",
"commit": "9800fed", "commit": "80c702e",
"repo": { "repo": {
"statements": 95.86, "statements": 95.87,
"branches": 88.91, "branches": 89.02,
"functions": 100, "functions": 100,
"lines": 95.86, "lines": 95.87,
"counts": { "counts": {
"lf": 3113, "lf": 3121,
"lh": 2984, "lh": 2992,
"brf": 487, "brf": 492,
"brh": 433, "brh": 438,
"fnf": 142, "fnf": 147,
"fnh": 142 "fnh": 147
} }
}, },
"byPackage": { "byPackage": {
@@ -44,6 +44,20 @@
"fnh": 29 "fnh": 29
} }
}, },
"@repo/core-analytics": {
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100,
"counts": {
"lf": 8,
"lh": 8,
"brf": 5,
"brh": 5,
"fnf": 5,
"fnh": 5
}
},
"@repo/marketing-pages": { "@repo/marketing-pages": {
"statements": 95.64, "statements": 95.64,
"branches": 83.93, "branches": 83.93,

View File

@@ -0,0 +1,21 @@
export type AnalyticsAttributeValue = string | number | boolean;
export type AnalyticsUser = {
id: string;
};
export interface IAnalytics {
track(
event: string,
attributes?: Record<string, AnalyticsAttributeValue>,
): void;
identify(
user: AnalyticsUser,
attributes?: Record<string, AnalyticsAttributeValue>,
): void;
pageView(
path: string,
attributes?: Record<string, AnalyticsAttributeValue>,
): void;
flush(): Promise<void>;
}

View File

@@ -1,2 +1,6 @@
// placeholder — populated by story 01-scaffold-core-analytics-package export type {
export {}; AnalyticsAttributeValue,
AnalyticsUser,
IAnalytics,
} from "./analytics.interface";
export { NoopAnalytics } from "./noop-analytics";

View File

@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import { NoopAnalytics } from "@/noop-analytics";
describe("NoopAnalytics", () => {
it("track() does not throw with event name only", () => {
const analytics = new NoopAnalytics();
expect(() => analytics.track("page_viewed")).not.toThrow();
});
it("track() does not throw with event name and attributes", () => {
const analytics = new NoopAnalytics();
expect(() =>
analytics.track("button_clicked", {
label: "signup",
count: 1,
active: true,
}),
).not.toThrow();
});
it("identify() does not throw with user only", () => {
const analytics = new NoopAnalytics();
expect(() => analytics.identify({ id: "user-123" })).not.toThrow();
});
it("identify() does not throw with user and attributes", () => {
const analytics = new NoopAnalytics();
expect(() =>
analytics.identify({ id: "user-123" }, { plan: "pro", trial: false }),
).not.toThrow();
});
it("pageView() does not throw with path only", () => {
const analytics = new NoopAnalytics();
expect(() => analytics.pageView("/dashboard")).not.toThrow();
});
it("pageView() does not throw with path and attributes", () => {
const analytics = new NoopAnalytics();
expect(() =>
analytics.pageView("/dashboard", { referrer: "/home", duration: 120 }),
).not.toThrow();
});
it("flush() resolves without throwing", async () => {
const analytics = new NoopAnalytics();
await expect(analytics.flush()).resolves.toBeUndefined();
});
it("flush() returns a Promise", () => {
const analytics = new NoopAnalytics();
expect(analytics.flush()).toBeInstanceOf(Promise);
});
});

View File

@@ -0,0 +1,23 @@
import type {
AnalyticsAttributeValue,
AnalyticsUser,
IAnalytics,
} from "./analytics.interface";
export class NoopAnalytics implements IAnalytics {
track(
_event: string,
_attributes?: Record<string, AnalyticsAttributeValue>,
): void {}
identify(
_user: AnalyticsUser,
_attributes?: Record<string, AnalyticsAttributeValue>,
): void {}
pageView(
_path: string,
_attributes?: Record<string, AnalyticsAttributeValue>,
): void {}
flush(): Promise<void> {
return Promise.resolve();
}
}