Skip to content
161 changes: 146 additions & 15 deletions __tests__/config/analyticsConfig.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
import {
AnalyticsEvent,
AnalyticsFlow,
ROUTE_TO_ANALYTICS_EVENT_MAP,
ROUTES_WITHOUT_ANALYTICS,
transformRouteToEventName,
routeToScreenName,
processRouteForAnalytics,
buildScreenViewedProps,
getScreenViewedProps,
isScreenViewEvent,
} from "config/analyticsConfig";

describe("Analytics Configuration", () => {
describe("Route Transformation", () => {
it("should transform route names correctly", () => {
expect(transformRouteToEventName("WelcomeScreen")).toBe(
"loaded screen: welcome",
);
expect(transformRouteToEventName("SettingsScreen")).toBe(
"loaded screen: settings",
);
expect(transformRouteToEventName("SwapAmountScreen")).toBe(
"loaded screen: swap amount",
);
it("should derive canonical screen_names from route names", () => {
expect(routeToScreenName("WelcomeScreen")).toBe("welcome");
expect(routeToScreenName("SettingsScreen")).toBe("settings");
expect(routeToScreenName("SwapAmountScreen")).toBe("swap_amount");
});

it("should handle routes without Screen suffix", () => {
expect(transformRouteToEventName("Home")).toBe("loaded screen: home");
expect(transformRouteToEventName("History")).toBe(
"loaded screen: history",
);
expect(routeToScreenName("Home")).toBe("home");
expect(routeToScreenName("History")).toBe("history");
});
});

Expand Down Expand Up @@ -77,4 +73,139 @@ describe("Analytics Configuration", () => {
expect(ROUTE_TO_ANALYTICS_EVENT_MAP.AuthStack).toBeNull();
});
});

describe("screen.viewed consolidation (Slice B, #2883)", () => {
it("exposes the single canonical screen event", () => {
expect(AnalyticsEvent.SCREEN_VIEWED).toBe("screen.viewed");
});

describe("routeToScreenName", () => {
it("collapses each run of non-alphanumeric chars into a single underscore", () => {
expect(routeToScreenName("ReAuthDetailsScreen")).toBe(
"re_auth_details",
);
});

it("yields a valid slug for every route in the map", () => {
Object.values(ROUTE_TO_ANALYTICS_EVENT_MAP)
.filter((name): name is string => name !== null)
.forEach((name) => {
expect(name).toMatch(/^[a-z0-9]+(?:_[a-z0-9]+)*$/);
});
});
});

describe("isScreenViewEvent", () => {
it("recognises catalogued screen_names and rejects the rest", () => {
expect(isScreenViewEvent(AnalyticsEvent.VIEW_HOME)).toBe(true);
expect(isScreenViewEvent(AnalyticsEvent.SCREEN_VIEWED)).toBe(false);
expect(isScreenViewEvent(AnalyticsEvent.SEND_PAYMENT_SUCCESS)).toBe(
false,
);
});
});

describe("buildScreenViewedProps", () => {
it("uses the catalogued screen_name and assigns the screen's flow", () => {
expect(buildScreenViewedProps(AnalyticsEvent.VIEW_SEND_AMOUNT)).toEqual(
{
screen_name: "send_payment_amount",
flow: AnalyticsFlow.SEND,
},
);
expect(buildScreenViewedProps(AnalyticsEvent.VIEW_DISCOVERY)).toEqual({
screen_name: "discover",
flow: AnalyticsFlow.DISCOVERY,
});
});

it("adds a step for completion/sub-step screens", () => {
expect(
buildScreenViewedProps(AnalyticsEvent.VIEW_SEND_CONFIRM),
).toEqual({
screen_name: "send_payment_confirm",
flow: AnalyticsFlow.SEND,
step: "confirm",
});
expect(
buildScreenViewedProps(AnalyticsEvent.VIEW_SEND_PROCESSING),
).toEqual({
screen_name: "send_payment_processing",
flow: AnalyticsFlow.SEND,
step: "processing",
});
expect(
buildScreenViewedProps(AnalyticsEvent.VIEW_SWAP_CONFIRM),
).toEqual({
screen_name: "swap_confirm",
flow: AnalyticsFlow.SWAP,
step: "confirm",
});
});

it("carries no flow for an uncatalogued (auto-mapped) route", () => {
// Auto-mapped routes not in SCREEN_CATALOG still emit screen.viewed
// with their route-derived screen_name but carry no flow.
expect(
buildScreenViewedProps(routeToScreenName("SomeFutureScreen")),
).toEqual({ screen_name: "some_future" });
});

it("produces a valid screen_name for every catalogued screen", () => {
Object.values(AnalyticsEvent)
.filter((value) => isScreenViewEvent(value))
.forEach((screenName) => {
const props = buildScreenViewedProps(screenName);
expect(props.screen_name).toMatch(/^[a-z0-9]+(?:_[a-z0-9]+)*$/);
if (props.flow) {
expect(Object.values(AnalyticsFlow)).toContain(props.flow);
}
});
});
});

describe("getScreenViewedProps (manual retarget helper)", () => {
it("retargets legacy screen events (incl. bottom-sheet ones) to screen.viewed props", () => {
expect(getScreenViewedProps(AnalyticsEvent.VIEW_SEND_CONFIRM)).toEqual({
screen_name: "send_payment_confirm",
flow: AnalyticsFlow.SEND,
step: "confirm",
});
expect(
getScreenViewedProps(
AnalyticsEvent.VIEW_SIGN_DAPP_TRANSACTION_DETAILS,
),
).toEqual({
screen_name: "sign_transaction_details",
flow: AnalyticsFlow.SIGNING,
});
});

it("returns null for non-screen events so they pass through unchanged", () => {
expect(
getScreenViewedProps(AnalyticsEvent.SEND_PAYMENT_SUCCESS),
).toBeNull();
expect(getScreenViewedProps(AnalyticsEvent.SCREEN_VIEWED)).toBeNull();
});
});

describe("route path feeds screen.viewed (hard cutover)", () => {
it("resolves routes to a screen_name that maps to screen.viewed props", () => {
// processRouteForAnalytics returns the canonical screen_name; the
// navigation hook feeds it through buildScreenViewedProps.
const welcome = processRouteForAnalytics("WelcomeScreen");
expect(welcome).toBe(AnalyticsEvent.VIEW_WELCOME);
expect(buildScreenViewedProps(welcome!)).toEqual({
screen_name: "welcome",
flow: AnalyticsFlow.ONBOARDING,
});

const settings = processRouteForAnalytics("SettingsScreen");
expect(buildScreenViewedProps(settings!)).toEqual({
screen_name: "settings",
flow: AnalyticsFlow.SETTINGS,
});
});
});
});
});
15 changes: 13 additions & 2 deletions src/components/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { BottomSheetViewProps } from "@gorhom/bottom-sheet/lib/typescript/components/bottomSheetView/types";
import Icon from "components/sds/Icon";
import { Text } from "components/sds/Typography";
import { AnalyticsEvent } from "config/analyticsConfig";
import { AnalyticsEvent, getScreenViewedProps } from "config/analyticsConfig";
import { DEFAULT_PADDING } from "config/constants";
import { pxValue } from "helpers/dimensions";
import useColors from "hooks/useColors";
Expand Down Expand Up @@ -150,7 +150,18 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
(index: number) => {
// index >= 0 means sheet is visible
if (analyticsEvent && index >= 0 && !hasTrackedRef.current) {
track(analyticsEvent, analyticsProps);
// Screens presented via a bottom sheet are screen loads too: retarget
// any catalogued screen-view analyticsEvent to the single canonical
// `screen.viewed` event. Non-screen events pass through unchanged.
const screenViewedProps = getScreenViewedProps(analyticsEvent);
if (screenViewedProps) {
track(AnalyticsEvent.SCREEN_VIEWED, {
...screenViewedProps,
...analyticsProps,
});
} else {
track(analyticsEvent, analyticsProps);
}
hasTrackedRef.current = true;
}

Expand Down
Loading
Loading