Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useIsFocused } from "@react-navigation/native";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { act, fireEvent } from "@testing-library/react-native";
import ScanReceiveScreen from "components/screens/ScanReceiveScreen";
import { AnalyticsEvent } from "config/analyticsConfig";
import { AnalyticsEvent, AnalyticsFlow } from "config/analyticsConfig";
import { ROOT_NAVIGATOR_ROUTES, RootStackParamList } from "config/routes";
import { renderWithProviders } from "helpers/testUtils";
import React from "react";
Expand Down Expand Up @@ -79,14 +79,22 @@ describe("ScanReceiveScreen", () => {
previousAppState;
});

// Per-tab views go out as the canonical screen.viewed event carrying the
// tab's screen_name -- track() drops a VIEW_* slug passed as an event name.
it("fires the scan view event by default", () => {
renderWithProviders(<ScanReceiveScreen {...makeProps()} />);
expect(mockTrack).toHaveBeenCalledWith(AnalyticsEvent.VIEW_SCAN_QR_CODE);
expect(mockTrack).toHaveBeenCalledWith(AnalyticsEvent.SCREEN_VIEWED, {
screen_name: AnalyticsEvent.VIEW_SCAN_QR_CODE,
flow: AnalyticsFlow.ASSETS,
});
});

it("fires the account-qr view event when opened on the receive tab", () => {
renderWithProviders(<ScanReceiveScreen {...makeProps("receive")} />);
expect(mockTrack).toHaveBeenCalledWith(AnalyticsEvent.VIEW_ACCOUNT_QR_CODE);
expect(mockTrack).toHaveBeenCalledWith(AnalyticsEvent.SCREEN_VIEWED, {
screen_name: AnalyticsEvent.VIEW_ACCOUNT_QR_CODE,
flow: AnalyticsFlow.ASSETS,
});
});

it("fires the account-qr view event when switching to Receive", () => {
Expand All @@ -95,7 +103,25 @@ describe("ScanReceiveScreen", () => {
);
mockTrack.mockClear();
fireEvent.press(getByText("Receive"));
expect(mockTrack).toHaveBeenCalledWith(AnalyticsEvent.VIEW_ACCOUNT_QR_CODE);
expect(mockTrack).toHaveBeenCalledWith(AnalyticsEvent.SCREEN_VIEWED, {
screen_name: AnalyticsEvent.VIEW_ACCOUNT_QR_CODE,
flow: AnalyticsFlow.ASSETS,
});
});

it("never passes a VIEW_* slug to track() as an event name", () => {
const { getByText } = renderWithProviders(
<ScanReceiveScreen {...makeProps("scan")} />,
);
fireEvent.press(getByText("Receive"));

// Assert the calls happened before inspecting them: if both tabs stopped
// resolving to catalogued screens the screen would track nothing at all,
// and a loop over an empty call list would pass vacuously.
expect(mockTrack).toHaveBeenCalledTimes(2);
mockTrack.mock.calls.forEach(([event]) => {
expect(event).toBe(AnalyticsEvent.SCREEN_VIEWED);
});
});

it("closes via goBack when the close button is pressed", () => {
Expand Down
16 changes: 16 additions & 0 deletions __tests__/config/analyticsConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,5 +219,21 @@ describe("Analytics Configuration", () => {
// (was the legacy "loaded screen: scan qr code").
expect(AnalyticsEvent.VIEW_SCAN_QR_CODE).toBe("scan_qr_code");
});

it("catalogues both tab views so they retarget to screen.viewed", () => {
// The screen fires these two manually; an uncatalogued one would slip
// past track()'s screen-view guard and leak its bare slug as an
// Amplitude event name instead of becoming screen.viewed.
expect(getScreenViewedProps(AnalyticsEvent.VIEW_SCAN_QR_CODE)).toEqual({
screen_name: "scan_qr_code",
flow: AnalyticsFlow.ASSETS,
});
expect(getScreenViewedProps(AnalyticsEvent.VIEW_ACCOUNT_QR_CODE)).toEqual(
{
screen_name: "view_public_key_generator",
flow: AnalyticsFlow.ASSETS,
},
);
});
});
});
13 changes: 10 additions & 3 deletions src/components/screens/ScanReceiveScreen/ScanReceiveScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ReceiveTabView } from "components/screens/ScanReceiveScreen/ReceiveTabV
import { ScanTabView } from "components/screens/ScanReceiveScreen/ScanTabView";
import Icon from "components/sds/Icon";
import Tabs from "components/sds/Tabs";
import { AnalyticsEvent } from "config/analyticsConfig";
import { AnalyticsEvent, getScreenViewedProps } from "config/analyticsConfig";
import { DEFAULT_PADDING } from "config/constants";
import {
ROOT_NAVIGATOR_ROUTES,
Expand Down Expand Up @@ -96,13 +96,20 @@ const ScanReceiveScreen: React.FC<ScanReceiveScreenProps> = ({
}, [activeTab, receiveAnim]);

// Per-tab screen-view analytics (preserves prior per-screen tracking): fires
// on initial mount and on every tab switch.
// on initial mount and on every tab switch. Each tab is a screen load, so it
// goes out as the canonical `screen.viewed` event carrying that tab's
// screen_name -- passing the VIEW_* slug as the event name would be dropped
// by track()'s screen-view guard.
useEffect(() => {
track(
const screenViewedProps = getScreenViewedProps(
activeTab === "receive"
? AnalyticsEvent.VIEW_ACCOUNT_QR_CODE
: AnalyticsEvent.VIEW_SCAN_QR_CODE,
);

if (screenViewedProps) {
track(AnalyticsEvent.SCREEN_VIEWED, screenViewedProps);
}
}, [activeTab]);

const insetsTop = insets.top || pxValue(MIN_INSETS_TOP - DEFAULT_PADDING);
Expand Down
5 changes: 5 additions & 0 deletions src/config/analyticsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,14 @@ const SCREEN_CATALOG: Record<string, { flow?: AnalyticsFlow; step?: Step }> = {
[AnalyticsEvent.VIEW_TOKEN_DETAILS]: {
flow: AnalyticsFlow.ASSETS,
},
// Both tabs of the unified Scan/Receive screen. They are catalogued like any
// other screen so the manual per-tab emission retargets to `screen.viewed`.
[AnalyticsEvent.VIEW_ACCOUNT_QR_CODE]: {
flow: AnalyticsFlow.ASSETS,
},
[AnalyticsEvent.VIEW_SCAN_QR_CODE]: {
flow: AnalyticsFlow.ASSETS,
},
[AnalyticsEvent.VIEW_MANAGE_TOKENS]: {
flow: AnalyticsFlow.ASSETS,
},
Expand Down
Loading