Skip to content

Commit

Permalink
Merge branch 'feat/preparation_for_asset_chainId' into feature/adds_u…
Browse files Browse the repository at this point in the history
…seChainId_other_token_nft_components
  • Loading branch information
andreahaku authored Oct 14, 2024
2 parents 86a7dd8 + 5958411 commit a14979c
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 643 deletions.
1 change: 0 additions & 1 deletion app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ const MainFlow = () => (
mode={'modal'}
screenOptions={{
headerShown: false,
cardStyle: { backgroundColor: importedColors.transparent },
}}
>
<Stack.Screen name={'Main'} component={ConnectedMain} />
Expand Down
14 changes: 12 additions & 2 deletions app/components/UI/Name/Name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,21 @@ const UnknownEthereumAddress: React.FC<{ address: string }> = ({ address }) => {
);
};

const Name: React.FC<NameProperties> = ({ type, value }) => {
const Name: React.FC<NameProperties> = ({
chainId,
preferContractSymbol,
type,
value,
}) => {
if (type !== NameType.EthereumAddress) {
throw new Error('Unsupported NameType: ' + type);
}
const displayName = useDisplayName(type, value);
const displayName = useDisplayName(
type,
value,
chainId,
preferContractSymbol,
);
const { styles } = useStyles(styleSheet, {
displayNameVariant: displayName.variant,
});
Expand Down
3 changes: 3 additions & 0 deletions app/components/UI/Name/Name.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ViewProps } from 'react-native';
import { Hex } from '@metamask/utils';

/**
* The name types supported by the NameController.
Expand All @@ -11,6 +12,8 @@ export enum NameType {
}

export interface NameProperties extends ViewProps {
chainId?: Hex;
preferContractSymbol?: boolean;
type: NameType;
value: string;
}
3 changes: 2 additions & 1 deletion app/components/UI/SimulationDetails/AssetPill/AssetPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ const AssetPill: React.FC<AssetPillProperties> = ({ asset }) => {
<NativeAssetPill />
) : (
<Name
preferContractSymbol
testID="simulation-details-asset-pill-name"
type={NameType.EthereumAddress}
value={asset.address as Hex}
value={asset.address as Hex}
/>
)}
</View>
Expand Down
58 changes: 22 additions & 36 deletions app/core/AppStateEventListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { store } from '../store';
import Logger from '../util/Logger';
import { MetaMetrics, MetaMetricsEvents } from './Analytics';
import { AppStateEventListener } from './AppStateEventListener';
import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams';
import { processAttribution } from './processAttribution';

jest.mock('react-native', () => ({
AppState: {
Expand All @@ -27,12 +27,14 @@ jest.mock('../store', () => ({
},
}));

jest.mock('./DeeplinkManager/ParseManager/extractURLParams', () => jest.fn());

jest.mock('../util/Logger', () => ({
error: jest.fn(),
}));

jest.mock('./processAttribution', () => ({
processAttribution: jest.fn(),
}));

describe('AppStateEventListener', () => {
let appStateManager: AppStateEventListener;
let mockAppStateListener: (state: AppStateStatus) => void;
Expand Down Expand Up @@ -66,63 +68,47 @@ describe('AppStateEventListener', () => {
expect(Logger.error).toHaveBeenCalledWith(new Error('store is already initialized'));
});

it('tracks event when app becomes active and conditions are met', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: true },
});
(extractURLParams as jest.Mock).mockReturnValue({ params: { attributionId: 'test123' } });
it('tracks event when app becomes active and attribution data is available', () => {
const mockAttribution = {
attributionId: 'test123',
utm: 'test_utm',
utm_source: 'source',
utm_medium: 'medium',
utm_campaign: 'campaign',
};
(processAttribution as jest.Mock).mockReturnValue(mockAttribution);

appStateManager.setCurrentDeeplink('metamask://connect?attributionId=test123');
mockAppStateListener('active');
jest.advanceTimersByTime(2000);

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.APP_OPENED,
{ attributionId: 'test123' },
{ attributionId: 'test123', utm_source: 'source', utm_medium: 'medium', utm_campaign: 'campaign' },
true
);
});

it('does not track event when data collection is disabled', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: false },
});
it('does not track event when processAttribution returns undefined', () => {
(processAttribution as jest.Mock).mockReturnValue(undefined);

mockAppStateListener('active');
jest.advanceTimersByTime(2000);

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.APP_OPENED,
{},
true
);
});

it('does not track event when there is no deeplink', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: true },
});

mockAppStateListener('active');
jest.advanceTimersByTime(2000);

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.APP_OPENED,
{ attributionId: undefined },
true
);
expect(mockTrackEvent).not.toHaveBeenCalled();
});

it('handles errors gracefully', () => {
(store.getState as jest.Mock).mockImplementation(() => {
throw new Error('Test error');
const testError = new Error('Test error');
(processAttribution as jest.Mock).mockImplementation(() => {
throw testError;
});

mockAppStateListener('active');
jest.advanceTimersByTime(2000);

expect(Logger.error).toHaveBeenCalledWith(
expect.any(Error),
testError,
'AppStateManager: Error processing app state change'
);
expect(mockTrackEvent).not.toHaveBeenCalled();
Expand Down
17 changes: 10 additions & 7 deletions app/core/AppStateEventListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ export class AppStateEventListener {
}

try {
const attributionId = processAttribution({ currentDeeplink: this.currentDeeplink, store: this.store });
DevLogger.log(`AppStateManager:: processAppStateChange:: sending event 'APP_OPENED' attributionId=${attributionId}`);
MetaMetrics.getInstance().trackEvent(
MetaMetricsEvents.APP_OPENED,
{ attributionId },
true
);
const attribution = processAttribution({ currentDeeplink: this.currentDeeplink, store: this.store });
if(attribution) {
const { attributionId, utm, ...utmParams } = attribution;
DevLogger.log(`AppStateManager:: processAppStateChange:: sending event 'APP_OPENED' attributionId=${attribution.attributionId} utm=${attribution.utm}`, utmParams);
MetaMetrics.getInstance().trackEvent(
MetaMetricsEvents.APP_OPENED,
{ attributionId, ...utmParams },
true
);
}
} catch (error) {
Logger.error(error as Error, 'AppStateManager: Error processing app state change');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('extractURLParams', () => {
comm: 'test',
v: '2',
attributionId: '',
utm: '',
};

mockUrlParser.mockImplementation(
Expand Down Expand Up @@ -83,6 +84,7 @@ describe('extractURLParams', () => {
pubkey: '',
v: '',
attributionId: '',
utm: '',
});
});

Expand Down Expand Up @@ -116,6 +118,7 @@ describe('extractURLParams', () => {
pubkey: '',
v: '',
attributionId: '',
utm: '',
});

expect(alertSpy).toHaveBeenCalledWith(
Expand All @@ -137,6 +140,7 @@ describe('extractURLParams', () => {
sdkVersion: '',
pubkey: 'xyz',
attributionId: '',
utm: '',
};

mockUrlParser.mockImplementation(
Expand Down
2 changes: 2 additions & 0 deletions app/core/DeeplinkManager/ParseManager/extractURLParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface DeeplinkUrlParams {
originatorInfo?: string;
request?: string;
attributionId?: string;
utm?: string;
account?: string; // This is the format => "address@chainId"
}

Expand All @@ -41,6 +42,7 @@ function extractURLParams(url: string) {
channelId: '',
comm: '',
attributionId: '',
utm: '',
};

DevLogger.log(`extractParams:: urlObj`, urlObj);
Expand Down
5 changes: 0 additions & 5 deletions app/core/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,6 @@ class Engine {
chainId: networkController.getNetworkClientById(
networkController?.state.selectedNetworkClientId,
).configuration.chainId,
// @ts-expect-error TODO: Resolve bump the assets controller version.
getNetworkClientById:
networkController.getNetworkClientById.bind(networkController),
});
Expand Down Expand Up @@ -654,7 +653,6 @@ class Engine {
networkController?.state.selectedNetworkClientId,
).configuration.chainId,
selectedAddress: preferencesController.state.selectedAddress,
// @ts-expect-error TODO: Resolve provider type mismatch
provider: networkController.getProviderAndBlockTracker().provider,
state: initialState.TokensController,
// @ts-expect-error TODO: Resolve mismatch between base-controller versions.
Expand Down Expand Up @@ -953,7 +951,6 @@ class Engine {
networkController?.state.selectedNetworkClientId,
).configuration.chainId,
),
// @ts-expect-error TODO: Resolve mismatch between base-controller versions.
getNetworkClientById:
networkController.getNetworkClientById.bind(networkController),
});
Expand Down Expand Up @@ -1529,7 +1526,6 @@ class Engine {
selectedAddress: preferencesController.state.selectedAddress,
tokenPricesService: codefiTokenApiV2,
interval: 30 * 60 * 1000,
// @ts-expect-error TODO: Resolve mismatch between base-controller versions.
getNetworkClientById:
networkController.getNetworkClientById.bind(networkController),
}),
Expand Down Expand Up @@ -1780,7 +1776,6 @@ class Engine {
}
provider.sendAsync = provider.sendAsync.bind(provider);
AccountTrackerController.configure({ provider });
// @ts-expect-error TODO: Resolve mismatch between base-controller versions.
AssetsContractController.configure({ provider });

SwapsController.configure({
Expand Down
77 changes: 68 additions & 9 deletions app/core/processAttribution.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { store } from '../store';
import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams';
import { processAttribution } from './processAttribution';
import Logger from '../util/Logger';

jest.mock('../store', () => ({
store: {
Expand All @@ -9,30 +10,50 @@ jest.mock('../store', () => ({
}));

jest.mock('./DeeplinkManager/ParseManager/extractURLParams', () => jest.fn());
jest.mock('../util/Logger', () => ({
error: jest.fn(),
}));

describe('processAttribution', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('returns attributionId when marketing is enabled and deeplink is provided', () => {
it('returns attribution data when marketing is enabled and deeplink is provided', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: true },
});
(extractURLParams as jest.Mock).mockReturnValue({
params: { attributionId: 'test123' },
params: {
attributionId: 'test123',
utm: JSON.stringify({
source: 'twitter',
medium: 'social',
campaign: 'cmp-57731027-afbf09/',
term: null,
content: null
})
},
});

const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123', store });
expect(result).toBe('test123');
const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=...', store });
expect(result).toEqual({
attributionId: 'test123',
utm: expect.any(String),
utm_source: 'twitter',
utm_medium: 'social',
utm_campaign: 'cmp-57731027-afbf09/',
utm_term: null,
utm_content: null
});
});

it('returns undefined when marketing is disabled', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: false },
});

const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123', store });
const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=...', store });
expect(result).toBeUndefined();
});

Expand All @@ -45,15 +66,53 @@ describe('processAttribution', () => {
expect(result).toBeUndefined();
});

it('returns undefined when attributionId is not present in params', () => {
it('returns partial data when some UTM params are missing', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: true },
});
(extractURLParams as jest.Mock).mockReturnValue({
params: {},
params: {
attributionId: 'test123',
utm: JSON.stringify({
source: 'twitter',
medium: 'social'
})
},
});

const result = processAttribution({ currentDeeplink: 'metamask://connect', store });
expect(result).toBeUndefined();
const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=...', store });
expect(result).toEqual({
attributionId: 'test123',
utm: expect.any(String),
utm_source: 'twitter',
utm_medium: 'social',
utm_campaign: undefined,
utm_term: undefined,
utm_content: undefined
});
});

it('handles JSON parsing errors gracefully', () => {
(store.getState as jest.Mock).mockReturnValue({
security: { dataCollectionForMarketing: true },
});
(extractURLParams as jest.Mock).mockReturnValue({
params: {
attributionId: 'test123',
utm: 'invalid-json'
},
});

const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=invalid-json', store });
expect(result).toEqual({
attributionId: 'test123',
utm: 'invalid-json',
utm_source: undefined,
utm_medium: undefined,
utm_campaign: undefined,
utm_term: undefined,
utm_content: undefined
});
expect(Logger.error).toHaveBeenCalledWith(expect.any(Error), expect.any(Error));
});
});
Loading

0 comments on commit a14979c

Please sign in to comment.