Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ebdf980
chore: adds the global onfig for notifications
zone-live Apr 16, 2026
13debeb
Merge branch 'main' into TSA-369-topTraders-notification-preferences-…
zone-live Apr 17, 2026
33b1de0
Merge branch 'main' into TSA-369-topTraders-notification-preferences-…
zone-live Apr 17, 2026
f944793
Merge branch 'main' into TSA-369-topTraders-notification-preferences-…
zone-live Apr 20, 2026
b64315c
chore: clean up
zone-live Apr 20, 2026
04686d0
chore: clean up
zone-live Apr 20, 2026
2aeb861
chore: tests update
zone-live Apr 20, 2026
3c1f735
chore: formatThreshold
zone-live Apr 20, 2026
664db93
chore: review point
zone-live Apr 20, 2026
5036f25
Merge branch 'main' into TSA-369-topTraders-notification-preferences-…
zone-live Apr 20, 2026
94e9f05
chore: update by removing addressOrUid
zone-live Apr 20, 2026
b92cb5a
chore: adds the auth-user-storage and updates logic
zone-live Apr 21, 2026
e09f32a
Merge branch 'main' into TSA-369-integrate-authenticated-user-storage…
zone-live Apr 21, 2026
00668c3
chore: review points
zone-live Apr 21, 2026
d852911
chore: small update and test
zone-live Apr 21, 2026
9e55724
chore: small update
zone-live Apr 22, 2026
4633401
chore: update
zone-live Apr 22, 2026
29bf113
chore: muted lists as sets
zone-live Apr 22, 2026
f96f42e
chore: fix UI to adapt to requests
zone-live Apr 22, 2026
0696d48
chore: clean up
zone-live Apr 22, 2026
686b937
chore: add auth storage service to DATA_SERVICES
zone-live Apr 22, 2026
a17c632
Merge branch 'main' into TSA-369-integrate-authenticated-user-storage…
zone-live Apr 22, 2026
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
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React from 'react';
import { act, fireEvent, screen } from '@testing-library/react-native';
import { fireEvent, screen } from '@testing-library/react-native';
import renderWithProvider from '../../../../util/test/renderWithProvider';
import { mockTheme } from '../../../../util/theme';
import { strings } from '../../../../../locales/i18n';
import { useFollowedTraders } from './hooks';
import { useFollowedTraders, useNotificationPreferences } from './hooks';
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
import NotificationPreferencesView from './NotificationPreferencesView';
import { NotificationPreferencesViewSelectorsIDs } from './NotificationPreferencesView.testIds';
import type {
FollowedTrader,
UseFollowedTradersResult,
} from './hooks/useFollowedTraders';
import type {
UseNotificationPreferencesResult,
NotificationPreferences,
} from './hooks/useNotificationPreferences';

// ---------------------------------------------------------------------------
// Mocks
Expand Down Expand Up @@ -38,6 +42,7 @@ jest.mock('../../../../util/theme', () => ({
jest.mock('./hooks', () => ({
...jest.requireActual('./hooks'),
useFollowedTraders: jest.fn(),
useNotificationPreferences: jest.fn(),
}));

jest.mock(
Expand All @@ -58,6 +63,10 @@ jest.mock('../../../../selectors/currencyRateController', () => ({
const mockUseFollowedTraders = useFollowedTraders as jest.MockedFunction<
typeof useFollowedTraders
>;
const mockUseNotificationPreferences =
useNotificationPreferences as jest.MockedFunction<
typeof useNotificationPreferences
>;
const mockSelectCurrentCurrency = selectCurrentCurrency as jest.MockedFunction<
typeof selectCurrentCurrency
>;
Expand Down Expand Up @@ -88,6 +97,32 @@ const makeUseFollowedTradersResult = (
...overrides,
});

const makePreferences = (
overrides: Partial<NotificationPreferences> = {},
): NotificationPreferences => ({
enabled: true,
txAmountLimit: 500,
mutedTraderProfileIds: [],
...overrides,
});

const makeUseNotificationPreferencesResult = (
overrides: Partial<UseNotificationPreferencesResult> = {},
): UseNotificationPreferencesResult => {
const preferences = overrides.preferences ?? makePreferences();
const muted = new Set(preferences.mutedTraderProfileIds);
return {
preferences,
isLoading: false,
error: null,
setEnabled: jest.fn().mockResolvedValue(undefined),
setTxAmountLimit: jest.fn().mockResolvedValue(undefined),
toggleTraderNotification: jest.fn().mockResolvedValue(undefined),
isTraderNotificationEnabled: (id: string) => !muted.has(id),
...overrides,
};
};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
Expand All @@ -103,6 +138,9 @@ describe('NotificationPreferencesView', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseFollowedTraders.mockReturnValue(makeUseFollowedTradersResult());
mockUseNotificationPreferences.mockReturnValue(
makeUseNotificationPreferencesResult(),
);
mockSelectCurrentCurrency.mockReturnValue('usd');
});

Expand Down Expand Up @@ -133,7 +171,7 @@ describe('NotificationPreferencesView', () => {
).toBeOnTheScreen();
});

it('renders the global toggle as enabled by default', () => {
it('renders the global toggle reflecting the hook enabled value', () => {
renderScreen();

const toggle = screen.getByTestId(
Expand All @@ -155,7 +193,7 @@ describe('NotificationPreferencesView', () => {
});
});

it('marks the $500 threshold as selected by default', () => {
it('marks the threshold option matching preferences.txAmountLimit as selected', () => {
renderScreen();

const option500 = screen.getByTestId(
Expand All @@ -164,7 +202,7 @@ describe('NotificationPreferencesView', () => {
expect(option500.props.accessibilityState.checked).toBe(true);
});

it('marks non-default thresholds as not selected by default', () => {
it('marks non-matching thresholds as not selected', () => {
renderScreen();

[10, 100, 1000].forEach((amount) => {
Expand Down Expand Up @@ -197,7 +235,7 @@ describe('NotificationPreferencesView', () => {
});
});

it('renders each trader toggle as enabled by default', () => {
it('renders each trader toggle as enabled by default (no muted ids)', () => {
renderScreen();

MOCK_TRADERS.forEach((trader) => {
Expand All @@ -208,6 +246,23 @@ describe('NotificationPreferencesView', () => {
});
});

it('renders a muted trader toggle as off', () => {
mockUseNotificationPreferences.mockReturnValue(
makeUseNotificationPreferencesResult({
preferences: makePreferences({
mutedTraderProfileIds: ['trader-1'],
}),
}),
);

renderScreen();

const mutedToggle = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
);
expect(mutedToggle.props.value).toBe(false);
});

it('renders the traders section header text', () => {
renderScreen();

Expand Down Expand Up @@ -269,14 +324,13 @@ describe('NotificationPreferencesView', () => {
).toBeOnTheScreen();
});

it('renders the error banner and retries when the fetch fails', () => {
const refresh = jest.fn().mockResolvedValue(undefined);
it('renders the error banner when the fetch fails', () => {
mockUseFollowedTraders.mockReturnValue(
makeUseFollowedTradersResult({
traders: [],
isLoading: false,
error: 'boom',
refresh,
refresh: jest.fn().mockResolvedValue(undefined),
}),
);

Expand All @@ -291,21 +345,16 @@ describe('NotificationPreferencesView', () => {
});

describe('disabled state when global toggle is off', () => {
const renderWithGlobalOff = () => {
renderScreen();
act(() => {
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.GLOBAL_TOGGLE,
),
'valueChange',
false,
);
});
};
beforeEach(() => {
mockUseNotificationPreferences.mockReturnValue(
makeUseNotificationPreferencesResult({
preferences: makePreferences({ enabled: false }),
}),
);
});

it('disables all threshold options when global toggle is off', () => {
renderWithGlobalOff();
it('disables all threshold options when preferences.enabled is false', () => {
renderScreen();

[10, 100, 500, 1000].forEach((amount) => {
const option = screen.getByTestId(
Expand All @@ -315,8 +364,8 @@ describe('NotificationPreferencesView', () => {
});
});

it('disables all trader toggles when global toggle is off', () => {
renderWithGlobalOff();
it('disables all trader toggles when preferences.enabled is false', () => {
renderScreen();

MOCK_TRADERS.forEach((trader) => {
const toggle = screen.getByTestId(
Expand All @@ -338,7 +387,12 @@ describe('NotificationPreferencesView', () => {
expect(mockGoBack).toHaveBeenCalledTimes(1);
});

it('updates the selected threshold when a different option is pressed', () => {
it('calls setTxAmountLimit when a threshold option is pressed', () => {
const setTxAmountLimit = jest.fn().mockResolvedValue(undefined);
mockUseNotificationPreferences.mockReturnValue(
makeUseNotificationPreferencesResult({ setTxAmountLimit }),
);

renderScreen();

fireEvent.press(
Expand All @@ -347,100 +401,45 @@ describe('NotificationPreferencesView', () => {
),
);

const option100 = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.THRESHOLD_OPTION(100),
);
expect(option100.props.accessibilityState.checked).toBe(true);

const option500 = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.THRESHOLD_OPTION(500),
);
expect(option500.props.accessibilityState.checked).toBe(false);
expect(setTxAmountLimit).toHaveBeenCalledWith(100);
});

it('toggles a trader notification off when its switch is pressed', () => {
renderScreen();

act(() => {
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
),
'valueChange',
false,
);
});

const toggle = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
it('calls toggleTraderNotification when a trader switch is pressed', () => {
const toggleTraderNotification = jest.fn().mockResolvedValue(undefined);
mockUseNotificationPreferences.mockReturnValue(
makeUseNotificationPreferencesResult({ toggleTraderNotification }),
);
expect(toggle.props.value).toBe(false);
});

it('does not affect other trader toggles when one is switched off', () => {
renderScreen();

act(() => {
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
),
'valueChange',
false,
);
});

const toggle2 = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-2'),
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
),
'valueChange',
false,
);
expect(toggle2.props.value).toBe(true);
});

it('restores a trader notification when its switch is toggled back on', () => {
renderScreen();

act(() => {
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
),
'valueChange',
false,
);
});
act(() => {
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
),
'valueChange',
true,
);
});
expect(toggleTraderNotification).toHaveBeenCalledWith('trader-1');
});

const toggle = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.TRADER_TOGGLE('trader-1'),
it('calls setEnabled when the global toggle is pressed', () => {
const setEnabled = jest.fn().mockResolvedValue(undefined);
mockUseNotificationPreferences.mockReturnValue(
makeUseNotificationPreferencesResult({ setEnabled }),
);
expect(toggle.props.value).toBe(true);
});

it('turns the global toggle off when it is pressed while enabled', () => {
renderScreen();

act(() => {
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.GLOBAL_TOGGLE,
),
'valueChange',
false,
);
});

const toggle = screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.GLOBAL_TOGGLE,
fireEvent(
screen.getByTestId(
NotificationPreferencesViewSelectorsIDs.GLOBAL_TOGGLE,
),
'valueChange',
false,
);
expect(toggle.props.value).toBe(false);

expect(setEnabled).toHaveBeenCalledWith(false);
});
});
});
Loading
Loading