Skip to content

Commit d3918e7

Browse files
committed
chore: added tests
1 parent 5e566d0 commit d3918e7

3 files changed

Lines changed: 275 additions & 4 deletions

File tree

app/components/UI/NftGrid/NftGrid.test.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ jest.mock('@shopify/flash-list', () => ({
114114
},
115115
}));
116116

117-
// Mock ActionSheet - simplified since we don't test the action sheet behavior in this component
118-
jest.mock('@metamask/react-native-actionsheet', () => () => null);
119-
120117
// Mock child components with minimal complexity
121-
jest.mock('./NftGridItemActionSheet', () => () => null);
118+
jest.mock('./NftGridItemBottomSheet', () => {
119+
const { View } = jest.requireActual('react-native');
120+
return ({ isVisible }: { isVisible: boolean }) =>
121+
isVisible ? <View testID="nft-grid-item-bottom-sheet" /> : null;
122+
});
122123
jest.mock('./NftGridHeader', () => {
123124
const { View, Text } = jest.requireActual('react-native');
124125
return () => (
@@ -1239,6 +1240,32 @@ describe('NftGrid', () => {
12391240
});
12401241
});
12411242

1243+
it('shows bottom sheet when an NFT item is long-pressed', async () => {
1244+
const mockCollectibles = { '0x1': [mockNft] };
1245+
setupSelectorMocks({
1246+
isHomepageRedesignEnabled: false,
1247+
collectibles: mockCollectibles,
1248+
isNftFetching: false,
1249+
});
1250+
const store = mockStore(initialState);
1251+
1252+
const { getByTestId } = render(
1253+
<Provider store={store}>
1254+
<NftGrid />
1255+
</Provider>,
1256+
);
1257+
1258+
act(() => {
1259+
jest.advanceTimersByTime(100);
1260+
});
1261+
1262+
await waitFor(() => {
1263+
const nftItem = getByTestId('collectible-Test NFT-456');
1264+
fireEvent(nftItem, 'longPress');
1265+
expect(getByTestId('nft-grid-item-bottom-sheet')).toBeOnTheScreen();
1266+
});
1267+
});
1268+
12421269
describe('non-EVM account group selection (addressesOverride)', () => {
12431270
it('updates NFT display when selectedGroupAccounts changes from empty to populated', async () => {
12441271
const mockCollectibles = { '0x1': [mockNft] };
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import React from 'react';
2+
import { render, fireEvent } from '@testing-library/react-native';
3+
import { Alert } from 'react-native';
4+
import NftGridItemBottomSheet from './NftGridItemBottomSheet';
5+
import { Nft } from '@metamask/assets-controllers';
6+
import Engine from '../../../core/Engine';
7+
8+
jest.mock('react-native', () => ({
9+
...jest.requireActual('react-native'),
10+
Modal: ({
11+
children,
12+
visible,
13+
}: {
14+
children: React.ReactNode;
15+
visible: boolean;
16+
}) => {
17+
const { View } = jest.requireActual('react-native');
18+
return visible ? <View>{children}</View> : null;
19+
},
20+
}));
21+
22+
jest.mock('../../../core/Engine', () => ({
23+
context: {
24+
NftController: {
25+
removeAndIgnoreNft: jest.fn(),
26+
addNft: jest.fn(),
27+
},
28+
NetworkController: {
29+
findNetworkClientIdByChainId: jest.fn().mockReturnValue('mainnet'),
30+
},
31+
},
32+
}));
33+
34+
jest.mock('../../../../locales/i18n', () => ({
35+
strings: (key: string) => key,
36+
}));
37+
38+
jest.mock(
39+
'../../../component-library/components/BottomSheets/BottomSheet',
40+
() => {
41+
const { forwardRef } = jest.requireActual('react');
42+
return {
43+
__esModule: true,
44+
default: forwardRef(
45+
(
46+
{
47+
children,
48+
onClose,
49+
}: { children: React.ReactNode; onClose?: () => void },
50+
ref: React.Ref<{ onCloseBottomSheet: () => void }>,
51+
) => {
52+
const { View } = jest.requireActual('react-native');
53+
const React = jest.requireActual('react');
54+
React.useImperativeHandle(ref, () => ({
55+
onCloseBottomSheet: () => onClose?.(),
56+
}));
57+
return <View>{children}</View>;
58+
},
59+
),
60+
};
61+
},
62+
);
63+
64+
jest.mock('@metamask/design-system-react-native', () => {
65+
const { View, Text, TouchableOpacity } = jest.requireActual('react-native');
66+
return {
67+
Box: ({ children }: { children: React.ReactNode }) => (
68+
<View>{children}</View>
69+
),
70+
BottomSheetHeader: ({
71+
children,
72+
onClose,
73+
}: {
74+
children: React.ReactNode;
75+
onClose?: () => void;
76+
}) => (
77+
<View>
78+
{children}
79+
<TouchableOpacity
80+
testID="bottom-sheet-header-close"
81+
onPress={onClose}
82+
/>
83+
</View>
84+
),
85+
Button: ({
86+
children,
87+
onPress,
88+
testID,
89+
}: {
90+
children: React.ReactNode;
91+
onPress: () => void;
92+
testID?: string;
93+
}) => (
94+
<TouchableOpacity testID={testID} onPress={onPress}>
95+
<Text>{children}</Text>
96+
</TouchableOpacity>
97+
),
98+
ButtonVariant: { Secondary: 'Secondary', Primary: 'Primary' },
99+
Text: ({ children }: { children: React.ReactNode }) => (
100+
<Text>{children}</Text>
101+
),
102+
TextVariant: { HeadingMd: 'HeadingMd' },
103+
};
104+
});
105+
106+
jest.mock('@metamask/controller-utils', () => ({
107+
toHex: jest.fn((val) => `0x${val.toString(16)}`),
108+
}));
109+
110+
describe('NftGridItemBottomSheet', () => {
111+
const mockNft: Nft = {
112+
address: '0x123',
113+
tokenId: '456',
114+
name: 'Test NFT',
115+
image: 'https://example.com/nft.png',
116+
collection: { name: 'Test Collection' },
117+
chainId: 1,
118+
isCurrentlyOwned: true,
119+
standard: 'ERC721',
120+
} as Nft;
121+
122+
const onClose = jest.fn();
123+
let alertSpy: jest.SpyInstance;
124+
125+
beforeEach(() => {
126+
jest.clearAllMocks();
127+
alertSpy = jest.spyOn(Alert, 'alert').mockImplementation(jest.fn());
128+
});
129+
130+
afterEach(() => {
131+
alertSpy.mockRestore();
132+
});
133+
134+
it('renders nothing when not visible', () => {
135+
const { queryByTestId } = render(
136+
<NftGridItemBottomSheet
137+
isVisible={false}
138+
onClose={onClose}
139+
nft={mockNft}
140+
/>,
141+
);
142+
143+
expect(queryByTestId('nft-grid-item-bottom-sheet')).toBeNull();
144+
});
145+
146+
it('renders bottom sheet with correct options when visible', () => {
147+
const { getByText, getByTestId } = render(
148+
<NftGridItemBottomSheet isVisible onClose={onClose} nft={mockNft} />,
149+
);
150+
151+
expect(getByTestId('nft-grid-item-bottom-sheet')).toBeDefined();
152+
expect(getByText('wallet.collectible_action_title')).toBeDefined();
153+
expect(getByText('wallet.refresh_metadata')).toBeDefined();
154+
expect(getByText('wallet.remove')).toBeDefined();
155+
expect(getByText('wallet.cancel')).toBeDefined();
156+
});
157+
158+
it('calls NftController.addNft when refresh metadata is pressed', () => {
159+
const { getByTestId } = render(
160+
<NftGridItemBottomSheet isVisible onClose={onClose} nft={mockNft} />,
161+
);
162+
163+
fireEvent.press(getByTestId('nft-grid-item-bottom-sheet-refresh-button'));
164+
165+
expect(Engine.context.NftController.addNft).toHaveBeenCalledWith(
166+
'0x123',
167+
'456',
168+
'mainnet',
169+
);
170+
});
171+
172+
it('calls NftController.removeAndIgnoreNft and shows alert when remove is pressed', () => {
173+
const { getByTestId } = render(
174+
<NftGridItemBottomSheet isVisible onClose={onClose} nft={mockNft} />,
175+
);
176+
177+
fireEvent.press(getByTestId('nft-grid-item-bottom-sheet-remove-button'));
178+
179+
expect(
180+
Engine.context.NftController.removeAndIgnoreNft,
181+
).toHaveBeenCalledWith('0x123', '456', 'mainnet');
182+
expect(alertSpy).toHaveBeenCalledWith(
183+
'wallet.collectible_removed_title',
184+
'wallet.collectible_removed_desc',
185+
);
186+
});
187+
188+
it('calls onClose when cancel is pressed', () => {
189+
const { getByText } = render(
190+
<NftGridItemBottomSheet isVisible onClose={onClose} nft={mockNft} />,
191+
);
192+
193+
fireEvent.press(getByText('wallet.cancel'));
194+
195+
expect(onClose).toHaveBeenCalled();
196+
});
197+
198+
it('calls onClose when header close button is pressed', () => {
199+
const { getByTestId } = render(
200+
<NftGridItemBottomSheet isVisible onClose={onClose} nft={mockNft} />,
201+
);
202+
203+
fireEvent.press(getByTestId('bottom-sheet-header-close'));
204+
205+
expect(onClose).toHaveBeenCalled();
206+
});
207+
208+
it('does nothing when nft is null', () => {
209+
const { getByTestId } = render(
210+
<NftGridItemBottomSheet isVisible onClose={onClose} nft={null} />,
211+
);
212+
213+
fireEvent.press(getByTestId('nft-grid-item-bottom-sheet-remove-button'));
214+
fireEvent.press(getByTestId('nft-grid-item-bottom-sheet-refresh-button'));
215+
216+
expect(
217+
Engine.context.NftController.removeAndIgnoreNft,
218+
).not.toHaveBeenCalled();
219+
expect(Engine.context.NftController.addNft).not.toHaveBeenCalled();
220+
});
221+
});

app/components/Views/Homepage/Sections/NFTs/NFTsSection.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ jest.mock('./hooks', () => ({
5656
useOwnedNfts: jest.fn(() => []),
5757
}));
5858

59+
jest.mock('../../../../UI/NftGrid/NftGridItemBottomSheet', () => {
60+
const { View } = jest.requireActual('react-native');
61+
return ({ isVisible }: { isVisible: boolean }) =>
62+
isVisible ? <View testID="nft-grid-item-bottom-sheet" /> : null;
63+
});
64+
5965
jest.mock('../../hooks/useHomeViewedEvent', () => ({
6066
__esModule: true,
6167
default: jest.fn(() => ({ onLayout: jest.fn() })),
@@ -210,6 +216,23 @@ describe('NFTsSection', () => {
210216
expect(screen.getByText('NFTs')).toBeOnTheScreen();
211217
});
212218

219+
it('opens bottom sheet when an NFT item is long-pressed', () => {
220+
jest
221+
.requireMock('./hooks')
222+
.useOwnedNfts.mockReturnValue([mockNft('0x123', '1')]);
223+
224+
renderWithProvider(
225+
<NFTsSection sectionIndex={0} totalSectionsLoaded={1} />,
226+
{ state: stateWithNftPreferences },
227+
);
228+
229+
expect(screen.queryByTestId('nft-grid-item-bottom-sheet')).toBeNull();
230+
231+
fireEvent(screen.getByTestId('collectible-NFT 1-1'), 'longPress');
232+
233+
expect(screen.getByTestId('nft-grid-item-bottom-sheet')).toBeOnTheScreen();
234+
});
235+
213236
it('exposes refresh function via ref that calls useNftRefresh.onRefresh', async () => {
214237
const ref = createRef<SectionRefreshHandle>();
215238

0 commit comments

Comments
 (0)