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:
@@ -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,
|
||||||
|
|||||||
21
packages/core-analytics/src/analytics.interface.ts
Normal file
21
packages/core-analytics/src/analytics.interface.ts
Normal 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>;
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
|||||||
54
packages/core-analytics/src/noop-analytics.test.ts
Normal file
54
packages/core-analytics/src/noop-analytics.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
23
packages/core-analytics/src/noop-analytics.ts
Normal file
23
packages/core-analytics/src/noop-analytics.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user