Skip to content
Draft
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
56 changes: 32 additions & 24 deletions app/components/Base/RemoteImage/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -632,34 +632,42 @@ describe('RemoteImage', () => {
});

it('renders token image with full ratio and dimensions', async () => {
jest.spyOn(Dimensions, 'get').mockReturnValue({
width: 400,
height: 800,
scale: 1,
fontScale: 1,
});
jest.useFakeTimers();
try {
jest.spyOn(Dimensions, 'get').mockReturnValue({
width: 400,
height: 800,
scale: 1,
fontScale: 1,
});

const { UNSAFE_getByType } = render(
<RemoteImage
fadeIn
isTokenImage
isFullRatio
source={{ uri: 'https://example.com/token.png' }}
/>,
);
const { UNSAFE_getByType } = render(
<RemoteImage
fadeIn
isTokenImage
isFullRatio
source={{ uri: 'https://example.com/token.png' }}
/>,
);

await act(async () => {
const image = UNSAFE_getByType(Image);
image.props.onLoad({
source: { width: 600, height: 400 },
jest.clearAllTimers();

await act(async () => {
const image = UNSAFE_getByType(Image);
image.props.onLoad({
source: { width: 600, height: 400 },
});
});
});

await waitFor(() => {
const image = UNSAFE_getByType(Image);
expect(image.props.style.width).toBe(368);
expect(image.props.style.height).toBeCloseTo(245.33, 1);
});
await waitFor(() => {
const image = UNSAFE_getByType(Image);
expect(image.props.style.width).toBe(368);
expect(image.props.style.height).toBeCloseTo(245.33, 1);
});
} finally {
jest.runOnlyPendingTimers();
jest.useRealTimers();
}
});

it('renders token image with chainId prop', async () => {
Expand Down
7 changes: 6 additions & 1 deletion app/components/Views/ChoosePassword/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
Checkbox,
} from '@metamask/design-system-react-native';
import StorageWrapper from '../../../store/storage-wrapper';
import { useDispatch } from 'react-redux';
import { useDispatch, useStore } from 'react-redux';
import { saveOnboardingEvent as saveEvent } from '../../../actions/onboarding';
import {
passwordSet as passwordSetAction,
Expand Down Expand Up @@ -95,6 +95,8 @@ import { UserProfileProperty } from '../../../util/metrics/UserSettingsAnalytics
import generateDeviceAnalyticsMetaData, {
UserSettingsAnalyticsMetaData as generateUserSettingsAnalyticsMetaData,
} from '../../../util/metrics';
import { getWalletSetupCompletedAttributionProperties } from '../../../util/analytics/getWalletSetupCompletedAttributionProperties';
import type { RootState } from '../../../reducers';

interface KeyringState {
type: string;
Expand Down Expand Up @@ -125,6 +127,7 @@ const ChoosePassword = () => {
useRoute<RouteProp<{ params: ChoosePasswordRouteParams }, 'params'>>();

const dispatch = useDispatch();
const store = useStore<RootState>();
const metrics = useAnalytics();

const [isSelected, setIsSelected] = useState(false);
Expand Down Expand Up @@ -489,6 +492,7 @@ const ChoosePassword = () => {
wallet_setup_type: 'new',
new_wallet: true,
account_type: accountType,
...getWalletSetupCompletedAttributionProperties(store.getState()),
});
endTrace({ name: TraceName.OnboardingSRPAccountCreationTime });
} catch (err) {
Expand All @@ -505,6 +509,7 @@ const ChoosePassword = () => {
handlePostWalletCreation,
handleWalletCreationError,
metrics,
store,
]);

const onPasswordChange = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Platform,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import { connect, useStore } from 'react-redux';
import {
KeyboardAwareScrollView,
KeyboardProvider,
Expand Down Expand Up @@ -97,6 +97,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
import SrpInputGrid from '../../UI/SrpInputGrid';
import SrpWordSuggestions from '../../UI/SrpWordSuggestions';
import { getWalletSetupCompletedAttributionProperties } from '../../../util/analytics/getWalletSetupCompletedAttributionProperties';

const SCREEN_WIDTH = Dimensions.get('window').width;

Expand All @@ -115,6 +116,8 @@ const ImportFromSecretRecoveryPhrase = ({
}) => {
const { colors, themeAppearance } = useTheme();
const tw = useTailwind();
/** @type {import('redux').Store<import('../../../reducers').RootState>} */
const store = useStore();

const confirmPasswordInput = useRef();

Expand Down Expand Up @@ -464,6 +467,7 @@ const ImportFromSecretRecoveryPhrase = ({
wallet_setup_type: 'import',
new_wallet: false,
account_type: AccountType.Imported,
...getWalletSetupCompletedAttributionProperties(store.getState()),
});

fetchAccountsWithActivity();
Expand Down
128 changes: 128 additions & 0 deletions app/components/Views/Onboarding/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ jest.mock('../../../core/Analytics/MetaMetrics', () => ({
.MetaMetricsEvents,
}));

jest.mock(
'../../../util/analytics/getWalletSetupCompletedAttributionProperties',
() => ({
getWalletSetupCompletedAttributionProperties: jest.fn().mockReturnValue({}),
}),
);

interface EventBuilder {
addProperties: () => EventBuilder;
build: () => Record<string, unknown>;
Expand Down Expand Up @@ -3056,6 +3063,127 @@ describe('Onboarding', () => {

Platform.OS = 'ios';
});

describe('Social Login Completed attribution', () => {
const mockGetWalletSetupCompletedAttributionProperties = jest.requireMock(
'../../../util/analytics/getWalletSetupCompletedAttributionProperties',
).getWalletSetupCompletedAttributionProperties;

beforeEach(() => {
mockSeedlessOnboardingEnabled.mockReturnValue(true);
(StorageWrapper.getItem as jest.Mock).mockResolvedValue(null);
(Device.isIos as jest.Mock).mockReturnValue(false);
(Device.comparePlatformVersionTo as jest.Mock).mockReturnValue(1);
Platform.OS = 'android';
mockGetWalletSetupCompletedAttributionProperties.mockClear();
});

afterEach(() => {
jest.clearAllMocks();
mockNavigate.mockReset();
mockSeedlessOnboardingEnabled.mockReset();
Platform.OS = 'ios';
});

it('reads attribution properties from store when Social Login Completed fires with persisted attribution', async () => {
const mockUtmProperties = {
utm_source: 'google_ads',
utm_medium: 'cpc',
utm_campaign: 'mobile_acquisition',
};
mockGetWalletSetupCompletedAttributionProperties.mockReturnValue(
mockUtmProperties,
);

const mockCreateLoginHandlerLocal = jest.requireMock(
'../../../core/OAuthService/OAuthLoginHandlers',
).createLoginHandler;
const mockOAuthServiceLocal = jest.requireMock(
'../../../core/OAuthService/OAuthService',
).default;

mockCreateLoginHandlerLocal.mockReturnValue('mockGoogleHandler');
mockOAuthServiceLocal.handleOAuthLogin.mockResolvedValue({
type: 'success',
existingUser: false,
accountName: 'test@example.com',
});

const { getByTestId } = renderScreen(
Onboarding,
{ name: 'Onboarding' },
{ state: mockInitialState },
);

const createWalletButton = getByTestId(
OnboardingSelectorIDs.NEW_WALLET_BUTTON,
);
await act(async () => {
fireEvent.press(createWalletButton);
});

const navCall = mockNavigate.mock.calls.find(
(call) =>
call[0] === Routes.MODAL.ROOT_MODAL_FLOW &&
call[1]?.screen === Routes.SHEET.ONBOARDING_SHEET,
);
const googleOAuthFunction = navCall[1].params.onPressContinueWithGoogle;

await act(async () => {
await googleOAuthFunction(true);
});

expect(
mockGetWalletSetupCompletedAttributionProperties,
).toHaveBeenCalledWith(mockInitialState);
});

it('reads attribution properties from store when Apple Social Login Completed fires', async () => {
mockGetWalletSetupCompletedAttributionProperties.mockReturnValue({});

const mockCreateLoginHandlerLocal = jest.requireMock(
'../../../core/OAuthService/OAuthLoginHandlers',
).createLoginHandler;
const mockOAuthServiceLocal = jest.requireMock(
'../../../core/OAuthService/OAuthService',
).default;

mockCreateLoginHandlerLocal.mockReturnValue('mockAppleHandler');
mockOAuthServiceLocal.handleOAuthLogin.mockResolvedValue({
type: 'success',
existingUser: false,
accountName: 'test@icloud.com',
});

const { getByTestId } = renderScreen(
Onboarding,
{ name: 'Onboarding' },
{ state: mockInitialState },
);

const createWalletButton = getByTestId(
OnboardingSelectorIDs.NEW_WALLET_BUTTON,
);
await act(async () => {
fireEvent.press(createWalletButton);
});

const navCall = mockNavigate.mock.calls.find(
(call) =>
call[0] === Routes.MODAL.ROOT_MODAL_FLOW &&
call[1]?.screen === Routes.SHEET.ONBOARDING_SHEET,
);
const appleOAuthFunction = navCall[1].params.onPressContinueWithApple;

await act(async () => {
await appleOAuthFunction(true);
});

expect(
mockGetWalletSetupCompletedAttributionProperties,
).toHaveBeenCalledWith(mockInitialState);
});
});
});

describe('Error Report Sent Notification', () => {
Expand Down
2 changes: 2 additions & 0 deletions app/components/Views/Onboarding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import {
presentIosGoogleLoginVersionWarningSheet,
} from './OnboardingIosPrompt';
import { SafeAreaView } from 'react-native-safe-area-context';
import { getWalletSetupCompletedAttributionProperties } from '../../../util/analytics/getWalletSetupCompletedAttributionProperties';
import FoxAnimation from '../../UI/FoxAnimation/FoxAnimation';
import OnboardingAnimation from '../../UI/OnboardingAnimation/OnboardingAnimation';
import {
Expand Down Expand Up @@ -460,6 +461,7 @@ const Onboarding = () => {

track(MetaMetricsEvents.SOCIAL_LOGIN_COMPLETED, {
account_type: accountType,
...getWalletSetupCompletedAttributionProperties(store.getState()),
});
if (createWallet) {
if (result.existingUser) {
Expand Down
Loading
Loading