Skip to content

Commit 956fcda

Browse files
committed
added tests
1 parent 53b96ef commit 956fcda

3 files changed

Lines changed: 187 additions & 1 deletion

File tree

app/components/UI/Assets/PriceAlerts/Views/CreatePriceAlertView/CreatePriceAlertView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const CreatePriceAlertView: React.FC = () => {
9191
navigation.goBack();
9292
}, [navigation]);
9393

94+
// Narrow the alert union so each form receives only its supported alert type.
9495
const editingAbsoluteAlert =
9596
editingAlert?.type === 'absolute_price' ? editingAlert : undefined;
9697
const editingPercentAlert =

app/components/UI/Assets/PriceAlerts/Views/CreatePriceAlertView/CreatePriceAlertView.view.test.tsx

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import {
44
setupPriceAlertsApiMock,
55
setupPriceAlertsPostMock,
66
setupPriceAlertsPatchMock,
7+
setupPercentPriceAlertsPostMock,
8+
setupPercentPriceAlertsPatchMock,
79
clearPriceAlertsApiMocks,
810
} from '../../../../../../../tests/component-view/api-mocking/priceAlerts';
911
import { describeForPlatforms } from '../../../../../../../tests/component-view/platform';
1012
import { act, fireEvent, waitFor } from '@testing-library/react-native';
1113
import {
1214
type AbsolutePriceAlert,
1315
CreatePriceAlertTestIds,
16+
type PercentChangeAlert,
1417
} from '../../constants';
1518

1619
beforeEach(() => {
@@ -32,6 +35,19 @@ const editingAlert: AbsolutePriceAlert = {
3235
createdAt: '2025-01-01T00:00:00.000Z',
3336
};
3437

38+
const editingPercentAlert: PercentChangeAlert = {
39+
id: 'percent-alert-1',
40+
userId: 'user-1',
41+
asset: 'eip155:1/slip44:60',
42+
type: 'percent_change',
43+
threshold: 10.5,
44+
period: '1h',
45+
direction: 'down',
46+
recurring: true,
47+
active: true,
48+
createdAt: '2025-01-01T00:00:00.000Z',
49+
};
50+
3551
describeForPlatforms('CreatePriceAlertView', () => {
3652
it('renders the Price Reaches form with keypad and quick-percentage pills', async () => {
3753
const { getByTestId, getByText } = renderCreatePriceAlertViewWithRoutes();
@@ -176,4 +192,128 @@ describeForPlatforms('CreatePriceAlertView', () => {
176192

177193
await waitFor(() => expect(scope.isDone()).toBe(true));
178194
});
195+
196+
describe('percent-change alerts', () => {
197+
it('switches to the percent form and updates direction and period', async () => {
198+
const { getByTestId, getByText, queryByTestId } =
199+
renderCreatePriceAlertViewWithRoutes();
200+
201+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.TYPE_SEGMENT_CHANGE));
202+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.DIRECTION_TOGGLE));
203+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.PERIOD_SEGMENT_1H));
204+
205+
await waitFor(() => {
206+
expect(
207+
getByTestId(CreatePriceAlertTestIds.PERCENT_INPUT),
208+
).toBeOnTheScreen();
209+
expect(
210+
queryByTestId(CreatePriceAlertTestIds.TARGET_PRICE_INPUT),
211+
).not.toBeOnTheScreen();
212+
expect(getByText('When price moves down')).toBeOnTheScreen();
213+
expect(
214+
getByTestId(CreatePriceAlertTestIds.PERIOD_SEGMENT_1H),
215+
).toHaveProp(
216+
'accessibilityState',
217+
expect.objectContaining({ selected: true }),
218+
);
219+
});
220+
});
221+
222+
it('submits the selected percent configuration to the percent endpoint', async () => {
223+
const scope = setupPercentPriceAlertsPostMock({
224+
asset: 'eip155:1/slip44:60',
225+
threshold: 10,
226+
period: '1h',
227+
direction: 'down',
228+
recurring: false,
229+
});
230+
const { getByTestId, getByText } = renderCreatePriceAlertViewWithRoutes({
231+
routeParams: { initialType: 'percent_change' },
232+
});
233+
234+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.DIRECTION_TOGGLE));
235+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.PERIOD_SEGMENT_1H));
236+
fireEvent.press(getByText('1'));
237+
fireEvent.press(getByText('0'));
238+
fireEvent(
239+
getByTestId(CreatePriceAlertTestIds.RECURRING_TOGGLE),
240+
'valueChange',
241+
false,
242+
);
243+
244+
await act(async () => {
245+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.SET_ALERT_BUTTON));
246+
});
247+
248+
await waitFor(() => expect(scope.isDone()).toBe(true));
249+
});
250+
251+
it('prevents saving a duplicate percent configuration', async () => {
252+
const { getByTestId, getByText, findByText } =
253+
renderCreatePriceAlertViewWithRoutes({
254+
routeParams: {
255+
initialType: 'percent_change',
256+
existingPercentAlerts: [
257+
{
258+
...editingPercentAlert,
259+
threshold: 10,
260+
period: '24h',
261+
direction: 'up',
262+
},
263+
],
264+
},
265+
});
266+
267+
fireEvent.press(getByText('1'));
268+
fireEvent.press(getByText('0'));
269+
270+
expect(
271+
await findByText('An alert with this configuration already exists.'),
272+
).toBeOnTheScreen();
273+
expect(
274+
getByTestId(CreatePriceAlertTestIds.SET_ALERT_BUTTON),
275+
).toBeDisabled();
276+
});
277+
278+
it('locks immutable controls and PATCHes editable percent fields', async () => {
279+
const scope = setupPercentPriceAlertsPatchMock(editingPercentAlert.id, {
280+
threshold: 10.5,
281+
recurring: false,
282+
});
283+
const { getByTestId } = renderCreatePriceAlertViewWithRoutes({
284+
routeParams: {
285+
editingAlert: editingPercentAlert,
286+
existingPercentAlerts: [editingPercentAlert],
287+
},
288+
});
289+
290+
fireEvent(
291+
getByTestId(CreatePriceAlertTestIds.RECURRING_TOGGLE),
292+
'valueChange',
293+
false,
294+
);
295+
296+
expect(
297+
getByTestId(CreatePriceAlertTestIds.TYPE_SEGMENT_TARGET),
298+
).toBeDisabled();
299+
expect(
300+
getByTestId(CreatePriceAlertTestIds.TYPE_SEGMENT_CHANGE),
301+
).toBeDisabled();
302+
expect(
303+
getByTestId(CreatePriceAlertTestIds.PERIOD_SEGMENT_1H),
304+
).toBeDisabled();
305+
expect(
306+
getByTestId(CreatePriceAlertTestIds.PERIOD_SEGMENT_24H),
307+
).toBeDisabled();
308+
expect(
309+
getByTestId(CreatePriceAlertTestIds.DIRECTION_TOGGLE),
310+
).toBeDisabled();
311+
312+
await act(async () => {
313+
fireEvent.press(getByTestId(CreatePriceAlertTestIds.SET_ALERT_BUTTON));
314+
});
315+
316+
await waitFor(() => expect(scope.isDone()).toBe(true));
317+
});
318+
});
179319
});

tests/component-view/api-mocking/priceAlerts.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111
// eslint-disable-next-line import-x/no-extraneous-dependencies
1212
import nock, { type Scope } from 'nock';
1313
import { clearAllNockMocks, disableNetConnect } from './nockHelpers';
14-
import type { AbsolutePriceAlert } from '../../../app/components/UI/Assets/PriceAlerts/constants';
14+
import type {
15+
AbsolutePriceAlert,
16+
PercentChangeAlert,
17+
SavePercentAlertParams,
18+
UpdatePercentAlertParams,
19+
} from '../../../app/components/UI/Assets/PriceAlerts/constants';
1520

1621
const PRICE_ALERTS_ORIGIN = 'https://price-alerts.dev-api.cx.metamask.io';
1722
const ALERTS_PATH = '/v1/alerts';
23+
const PERCENT_ALERTS_PATH = `${ALERTS_PATH}/percent-change`;
1824

1925
export const mockPriceAlertsData: AbsolutePriceAlert[] = [
2026
{
@@ -51,6 +57,20 @@ export const mockCreatedAlert: AbsolutePriceAlert = {
5157
createdAt: '2025-06-01T00:00:00.000Z',
5258
};
5359

60+
/** Fixture for the alert returned by POST /v1/alerts/percent-change in tests. */
61+
export const mockCreatedPercentAlert: PercentChangeAlert = {
62+
id: 'percent-alert-new',
63+
userId: 'user-1',
64+
asset: 'eip155:1/slip44:60',
65+
type: 'percent_change',
66+
threshold: 10,
67+
period: '1h',
68+
direction: 'down',
69+
recurring: false,
70+
active: true,
71+
createdAt: '2025-06-01T00:00:00.000Z',
72+
};
73+
5474
/**
5575
* Sets up nock interceptors for the Price Alerts API.
5676
* Call in beforeEach of price-alerts view tests.
@@ -105,6 +125,31 @@ export function setupPriceAlertsPatchMock(alertId: string): Scope {
105125
.reply(200);
106126
}
107127

128+
/**
129+
* Registers a one-shot POST /v1/alerts/percent-change interceptor.
130+
* Passing the expected body makes scope consumption verify the complete payload.
131+
*/
132+
export function setupPercentPriceAlertsPostMock(
133+
expectedBody: SavePercentAlertParams,
134+
): Scope {
135+
return nock(PRICE_ALERTS_ORIGIN)
136+
.post(PERCENT_ALERTS_PATH, expectedBody)
137+
.reply(201, mockCreatedPercentAlert);
138+
}
139+
140+
/**
141+
* Registers a one-shot PATCH /v1/alerts/percent-change/:id interceptor.
142+
* Passing the expected body makes scope consumption verify the update payload.
143+
*/
144+
export function setupPercentPriceAlertsPatchMock(
145+
alertId: string,
146+
expectedBody: UpdatePercentAlertParams,
147+
): Scope {
148+
return nock(PRICE_ALERTS_ORIGIN)
149+
.patch(`${PERCENT_ALERTS_PATH}/${alertId}`, expectedBody)
150+
.reply(200);
151+
}
152+
108153
/**
109154
* Sets up a nock interceptor for GET /v1/alerts/supported-chains.
110155
* Used by TokenDetails to determine if the bell icon should be shown.

0 commit comments

Comments
 (0)