diff --git a/src/components/settings/PushNotifications/PushNotificationsBanner/PushNotificationsBanner.test.ts b/src/components/settings/PushNotifications/PushNotificationsBanner/PushNotificationsBanner.test.ts deleted file mode 100644 index e091df0af6..0000000000 --- a/src/components/settings/PushNotifications/PushNotificationsBanner/PushNotificationsBanner.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { _getSafesToRegister } from '.' -import type { AddedSafesOnChain } from '@/store/addedSafesSlice' -import type { PushNotificationPreferences } from '@/services/push-notifications/preferences' - -describe('PushNotificationsBanner', () => { - describe('getSafesToRegister', () => { - it('should return all added safes if no preferences exist', () => { - const addedSafesOnChain = { - '0x123': {}, - '0x456': {}, - } as unknown as AddedSafesOnChain - const allPreferences = undefined - - const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences) - - expect(result).toEqual({ - '1': ['0x123', '0x456'], - }) - }) - - it('should return only newly added safes if preferences exist', () => { - const addedSafesOnChain = { - '0x123': {}, - '0x456': {}, - } as unknown as AddedSafesOnChain - const allPreferences = { - '1:0x123': { - safeAddress: '0x123', - chainId: '1', - }, - '4:0x789': { - safeAddress: '0x789', - chainId: '4', - }, - } as unknown as PushNotificationPreferences - - const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences) - - expect(result).toEqual({ - '1': ['0x456'], - }) - }) - - it('should return all added safes if no preferences match', () => { - const addedSafesOnChain = { - '0x123': {}, - '0x456': {}, - } as unknown as AddedSafesOnChain - const allPreferences = { - '1:0x111': { - safeAddress: '0x111', - chainId: '1', - }, - '4:0x222': { - safeAddress: '0x222', - chainId: '4', - }, - } as unknown as PushNotificationPreferences - - const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences) - - expect(result).toEqual({ - '1': ['0x123', '0x456'], - }) - }) - }) -}) diff --git a/src/components/settings/PushNotifications/PushNotificationsBanner/PushNotificationsBanner.test.tsx b/src/components/settings/PushNotifications/PushNotificationsBanner/PushNotificationsBanner.test.tsx new file mode 100644 index 0000000000..2ed0aa6aac --- /dev/null +++ b/src/components/settings/PushNotifications/PushNotificationsBanner/PushNotificationsBanner.test.tsx @@ -0,0 +1,310 @@ +import 'fake-indexeddb/auto' +import { set } from 'idb-keyval' +import { hexZeroPad } from 'ethers/lib/utils' +import * as navigation from 'next/navigation' +import type { ChainInfo, SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' + +import { PushNotificationsBanner, _getSafesToRegister } from '.' +import { createPushNotificationPrefsIndexedDb } from '@/services/push-notifications/preferences' +import { render } from '@/tests/test-utils' +import type { AddedSafesOnChain } from '@/store/addedSafesSlice' +import type { PushNotificationPreferences } from '@/services/push-notifications/preferences' + +Object.defineProperty(globalThis, 'crypto', { + value: { + randomUUID: () => Math.random().toString(), + }, +}) + +describe('PushNotificationsBanner', () => { + describe('getSafesToRegister', () => { + it('should return all added safes if no preferences exist', () => { + const addedSafesOnChain = { + '0x123': {}, + '0x456': {}, + } as unknown as AddedSafesOnChain + const allPreferences = undefined + + const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences) + + expect(result).toEqual({ + '1': ['0x123', '0x456'], + }) + }) + + it('should return only newly added safes if preferences exist', () => { + const addedSafesOnChain = { + '0x123': {}, + '0x456': {}, + } as unknown as AddedSafesOnChain + const allPreferences = { + '1:0x123': { + safeAddress: '0x123', + chainId: '1', + }, + '4:0x789': { + safeAddress: '0x789', + chainId: '4', + }, + } as unknown as PushNotificationPreferences + + const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences) + + expect(result).toEqual({ + '1': ['0x456'], + }) + }) + + it('should return all added safes if no preferences match', () => { + const addedSafesOnChain = { + '0x123': {}, + '0x456': {}, + } as unknown as AddedSafesOnChain + const allPreferences = { + '1:0x111': { + safeAddress: '0x111', + chainId: '1', + }, + '4:0x222': { + safeAddress: '0x222', + chainId: '4', + }, + } as unknown as PushNotificationPreferences + + const result = _getSafesToRegister('1', addedSafesOnChain, allPreferences) + + expect(result).toEqual({ + '1': ['0x123', '0x456'], + }) + }) + }) + + describe('PushNotificationsBanner', () => { + beforeEach(() => { + // Reset indexedDB + indexedDB = new IDBFactory() + + window.localStorage.clear() + + jest.spyOn(navigation, 'useParams').mockReturnValue({ + safe: `eth:${hexZeroPad('0x123', 20)}`, + }) + }) + + it('should display the banner', () => { + const result = render( + + <> + , + { + routerProps: { + query: { + safe: `eth:${hexZeroPad('0x123', 20)}`, + }, + }, + initialReduxState: { + chains: { + loading: false, + error: undefined, + data: [ + { + chainId: '1', + features: ['PUSH_NOTIFICATIONS'], + } as unknown as ChainInfo, + ], + }, + addedSafes: { + '1': { + [hexZeroPad('0x123', 20)]: {}, + } as unknown as AddedSafesOnChain, + }, + safeInfo: { + loading: false, + error: undefined, + data: { + chainId: '1', + address: { + value: hexZeroPad('0x123', 20), + }, + } as unknown as SafeInfo, + }, + }, + }, + ) + + expect(result.getByText('Get notified about pending signatures', { exact: false })).toBeInTheDocument() + }) + + it('should not show the banner if notifications are not enabled', () => { + const result = render( + + <> + , + { + routerProps: { + query: { + safe: `eth:${hexZeroPad('0x123', 20)}`, + }, + }, + initialReduxState: { + chains: { + loading: false, + error: undefined, + data: [ + { + chainId: '1', + features: [], // Not enabled + } as unknown as ChainInfo, + ], + }, + addedSafes: { + '1': { + [hexZeroPad('0x123', 20)]: {}, + } as unknown as AddedSafesOnChain, + }, + safeInfo: { + loading: false, + error: undefined, + data: { + chainId: '1', + address: { + value: hexZeroPad('0x123', 20), + }, + } as unknown as SafeInfo, + }, + }, + }, + ) + + expect(result.queryByText('Get notified about pending signatures', { exact: false })).not.toBeInTheDocument() + }) + + it('should not show the banner if the user has dismissed it', () => { + window.localStorage.setItem( + 'SAFE_v2__dismissPushNotifications', + JSON.stringify({ '1': { [hexZeroPad('0x123', 20)]: true } }), + ) + + const result = render( + + <> + , + { + initialReduxState: { + chains: { + loading: false, + error: undefined, + data: [ + { + chainId: '1', + features: ['PUSH_NOTIFICATIONS'], + } as unknown as ChainInfo, + ], + }, + addedSafes: { + '1': { + [hexZeroPad('0x123', 20)]: {}, + } as unknown as AddedSafesOnChain, + }, + safeInfo: { + loading: false, + error: undefined, + data: { + chainId: '1', + address: { + value: hexZeroPad('0x123', 20), + }, + } as unknown as SafeInfo, + }, + }, + }, + ) + + expect(result.queryByText('Get notified about pending signatures', { exact: false })).not.toBeInTheDocument() + }) + + it('should not show the banner if the Safe is not added', () => { + const result = render( + + <> + , + { + initialReduxState: { + chains: { + loading: false, + error: undefined, + data: [ + { + chainId: '1', + features: ['PUSH_NOTIFICATIONS'], + } as unknown as ChainInfo, + ], + }, + addedSafes: {}, // Not added + safeInfo: { + loading: false, + error: undefined, + data: { + chainId: '1', + address: { + value: hexZeroPad('0x123', 20), + }, + } as unknown as SafeInfo, + }, + }, + }, + ) + + expect(result.queryByText('Get notified about pending signatures', { exact: false })).not.toBeInTheDocument() + }) + + it('should not show the banner if the user has already registered for notifications', () => { + set( + `1:${hexZeroPad('0x123', 20)}`, // Registered + { + safeAddress: hexZeroPad('0x123', 20), + chainId: '1', + preferences: {}, + }, + createPushNotificationPrefsIndexedDb(), + ) + + const result = render( + + <> + , + { + initialReduxState: { + chains: { + loading: false, + error: undefined, + data: [ + { + chainId: '1', + features: ['PUSH_NOTIFICATIONS'], + } as unknown as ChainInfo, + ], + }, + addedSafes: { + '1': { + [hexZeroPad('0x123', 20)]: {}, + } as unknown as AddedSafesOnChain, + }, + safeInfo: { + loading: false, + error: undefined, + data: { + chainId: '1', + address: { + value: hexZeroPad('0x123', 20), + }, + } as unknown as SafeInfo, + }, + }, + }, + ) + + expect(result.queryByText('Get notified about pending signatures', { exact: false })).not.toBeInTheDocument() + }) + }) +}) diff --git a/src/components/settings/PushNotifications/PushNotificationsBanner/index.tsx b/src/components/settings/PushNotifications/PushNotificationsBanner/index.tsx index 96332f1d2b..5627acd456 100644 --- a/src/components/settings/PushNotifications/PushNotificationsBanner/index.tsx +++ b/src/components/settings/PushNotifications/PushNotificationsBanner/index.tsx @@ -87,7 +87,7 @@ export const _getSafesToRegister = ( } export const PushNotificationsBanner = ({ children }: { children: ReactElement }): ReactElement => { - const isNotificationsEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) + const isNotificationFeatureEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) const chain = useCurrentChain() const totalAddedSafes = useAppSelector(selectTotalAdded) const { safe, safeAddress } = useSafeInfo() @@ -95,13 +95,15 @@ export const PushNotificationsBanner = ({ children }: { children: ReactElement } const { query } = useRouter() const onboard = useOnboard() + const { getPreferences, getAllPreferences } = useNotificationPreferences() const { dismissPushNotificationBanner, isPushNotificationBannerDismissed } = useDismissPushNotificationsBanner() const isSafeAdded = !!addedSafesOnChain?.[safeAddress] - const shouldShowBanner = isNotificationsEnabled && !isPushNotificationBannerDismissed && isSafeAdded + const isSafeRegistered = getPreferences(safe.chainId, safeAddress) + const shouldShowBanner = + isNotificationFeatureEnabled && !isPushNotificationBannerDismissed && isSafeAdded && !isSafeRegistered const { registerNotifications } = useNotificationRegistrations() - const { getAllPreferences } = useNotificationPreferences() const dismissBanner = useCallback(() => { trackEvent(PUSH_NOTIFICATION_EVENTS.DISMISS_BANNER) diff --git a/src/components/settings/PushNotifications/hooks/useNotificationTracking.ts b/src/components/settings/PushNotifications/hooks/useNotificationTracking.ts index 5c0500422e..0a6b37d238 100644 --- a/src/components/settings/PushNotifications/hooks/useNotificationTracking.ts +++ b/src/components/settings/PushNotifications/hooks/useNotificationTracking.ts @@ -70,11 +70,11 @@ const handleTrackCachedNotificationEvents = async ( } export const useNotificationTracking = (): void => { - const isNotificationsEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) + const isNotificationFeatureEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) useEffect(() => { - if (typeof indexedDB !== 'undefined' && isNotificationsEnabled) { + if (typeof indexedDB !== 'undefined' && isNotificationFeatureEnabled) { handleTrackCachedNotificationEvents(createNotificationTrackingIndexedDb()) } - }, [isNotificationsEnabled]) + }, [isNotificationFeatureEnabled]) } diff --git a/src/components/settings/SettingsHeader/index.tsx b/src/components/settings/SettingsHeader/index.tsx index 63f3e54be8..5d35b01d1b 100644 --- a/src/components/settings/SettingsHeader/index.tsx +++ b/src/components/settings/SettingsHeader/index.tsx @@ -11,10 +11,10 @@ import { FEATURES } from '@/utils/chains' const SettingsHeader = (): ReactElement => { const safeAddress = useSafeAddress() - const isNotificationsEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) + const isNotificationFeatureEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) const navItems = safeAddress ? settingsNavItems : generalSettingsNavItems - const filteredNavItems = isNotificationsEnabled + const filteredNavItems = isNotificationFeatureEnabled ? navItems : navItems.filter((item) => item.href !== AppRoutes.settings.notifications) diff --git a/src/pages/settings/notifications.tsx b/src/pages/settings/notifications.tsx index a739f3d5f4..66dc3cda37 100644 --- a/src/pages/settings/notifications.tsx +++ b/src/pages/settings/notifications.tsx @@ -7,9 +7,9 @@ import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' const NotificationsPage: NextPage = () => { - const isNotificationsEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) + const isNotificationFeatureEnabled = useHasFeature(FEATURES.PUSH_NOTIFICATIONS) - if (!isNotificationsEnabled) { + if (!isNotificationFeatureEnabled) { return null }