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
5 changes: 5 additions & 0 deletions .changeset/calm-clouds-debug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": patch
---

Upgrade analytics consent QA debug screen to scenario parity
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,16 @@ import { act, renderHook } from "tests/testSetup";
import { INITIAL_STATE } from "~/renderer/reducers/settings";
import { track } from "~/renderer/analytics/segment";
import {
ANALYTICS_CONSENT_FLOW,
ANALYTICS_CONSENT_DIALOG_PAGE,
useAnalyticsConsentDialogViewModel,
} from "../hooks/useAnalyticsConsentDialogViewModel";

const mockUseMatch = jest.fn();

const FIXED_NOW = new Date("2024-06-15T12:00:00.000Z");

jest.mock("react-router", () => ({
...jest.requireActual("react-router"),
useMatch: (args: unknown) => mockUseMatch(args),
}));

jest.mock("~/renderer/hooks/useLocalizedUrls", () => ({
useLocalizedUrl: (url: string) => url,
}));

const dialogClosedPayload = {
page: ANALYTICS_CONSENT_DIALOG_PAGE,
flow: ANALYTICS_CONSENT_FLOW,
};

type ViewModel = ReturnType<typeof useAnalyticsConsentDialogViewModel>;
type SettingsState = typeof INITIAL_STATE;
type SettingsOverrides = Partial<SettingsState> & {
Expand Down Expand Up @@ -124,20 +111,12 @@ describe("useAnalyticsConsentDialogViewModel", () => {
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });
jest.setSystemTime(FIXED_NOW);
jest.clearAllMocks();
mockUseMatch.mockReturnValue({});
});

afterEach(() => {
jest.useRealTimers();
});

it("keeps phase closed when portfolio route is not focused", () => {
mockUseMatch.mockReturnValue(null);
const { result } = renderReconfirmViewModel();

expectClosed(result.current);
});

it("opens consentReconfirm when renewal is needed, policy is current, and share analytics is on", async () => {
const { result } = renderReconfirmViewModel();

Expand Down Expand Up @@ -298,19 +277,4 @@ describe("useAnalyticsConsentDialogViewModel", () => {
true,
);
});

it("tracks drawer_closed when leaving portfolio while modal is open", async () => {
const { result, rerender } = renderReconfirmViewModel();

await flushEffects();
expectConsentReconfirm(result.current);

act(() => {
mockUseMatch.mockReturnValue(null);
rerender(undefined);
});

expect(jest.mocked(track)).toHaveBeenCalledWith("drawer_closed", dialogClosedPayload);
expectClosed(result.current);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react";
import logger from "~/renderer/logger";
import { useTranslation } from "react-i18next";
import { useMatch } from "react-router";
import { useDispatch, useSelector } from "LLD/hooks/redux";
import {
analyticsConsentInfoSelector,
Expand Down Expand Up @@ -37,8 +36,6 @@ export function useAnalyticsConsentDialogViewModel() {
const { t } = useTranslation();
const dispatch = useDispatch();
const privacyPolicyUrl = useLocalizedUrl(urls.privacyPolicy);
const portfolioRouteMatch = useMatch({ path: "/", end: true });
const isPortfolioRouteFocused = Boolean(portfolioRouteMatch);

const hasCompletedOnboarding = useSelector(hasCompletedOnboardingSelector);
const consentInfo = useSelector(analyticsConsentInfoSelector);
Expand Down Expand Up @@ -83,7 +80,7 @@ export function useAnalyticsConsentDialogViewModel() {
};

useEffect(() => {
if (!isPortfolioRouteFocused || !shouldOffer) {
if (!shouldOffer) {
setPhase(current => {
if (current !== "closed") {
track("drawer_closed", dialogClosedPayload);
Expand All @@ -96,7 +93,7 @@ export function useAnalyticsConsentDialogViewModel() {
if (current === "preferences") return current;
return resolveAnalyticsConsentPhase(current, decision, shareAnalytics);
});
}, [isPortfolioRouteFocused, shouldOffer, decision, shareAnalytics]);
}, [shouldOffer, decision, shareAnalytics]);

const persistConsentCompletion = async () => {
dispatch(
Expand Down
28 changes: 1 addition & 27 deletions apps/ledger-live-desktop/src/renderer/actions/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,35 +72,9 @@ export const setSharePersonalizedRecommendations = (sharePersonalizedRecommandat
sharePersonalizedRecommandations,
});

export const setAnalyticsConsentInfo = (info: AnalyticsConsentInfo) =>
export const setAnalyticsConsentInfo = (info: Partial<AnalyticsConsentInfo>) =>
saveAnalyticsConsentInfo(info);

/**
* @deprecated QA / developer tools only. Do not use in production flows.
* Merges a partial patch into `analyticsConsentInfo` (e.g. force stale privacy version or clear consent date).
*/
export const DANGEROUSLY_setAnalyticsConsentInfoForQa = (patch: Partial<AnalyticsConsentInfo>) =>
saveAnalyticsConsentInfo(patch);

/**
* @deprecated QA / developer tools only. Do not use in production flows.
* Clears consent metadata and turns off analytics sharing flags to simulate a pre-consent state.
*/
export const DANGEROUSLY_resetAnalyticsOptInStateForQa = () => (dispatch: AppDispatch) => {
dispatch(
saveSettings({
shareAnalytics: false,
sharePersonalizedRecommandations: false,
}),
);
dispatch(
saveAnalyticsConsentInfo({
consentDate: null,
privacyPolicyVersion: null,
}),
);
};

/**
* @deprecated QA / developer tools only. Do not use in production flows.
* Replaces `devicesModelList` so debug screens can simulate audience gates (e.g. pretend Nano seen).
Expand Down
Loading
Loading