|
3 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | 4 |
|
5 | 5 | import React from 'react'; |
6 | | -import { render, screen } from '@testing-library/react'; |
| 6 | +import { render, screen, waitFor } from '@testing-library/react'; |
7 | 7 | import { NimbusProvider, useNimbusContext } from './NimbusContext'; |
8 | 8 | import { AppContext, AppContextValue } from './AppContext'; |
9 | 9 | import { useDynamicLocalization } from '../../contexts/DynamicLocalizationContext'; |
10 | 10 | import { initializeNimbus, NimbusResult } from '../../lib/nimbus'; |
11 | 11 | import * as Sentry from '@sentry/react'; |
12 | | -import { currentAccount } from '../../lib/cache'; |
13 | | -import { StoredAccountData } from '../../lib/storage-utils'; |
| 12 | +import { useLocalStorageSync } from '../../lib/hooks/useLocalStorageSync'; |
14 | 13 |
|
15 | 14 | jest.mock('../../contexts/DynamicLocalizationContext'); |
16 | 15 | jest.mock('../../lib/nimbus'); |
17 | 16 | jest.mock('@sentry/react'); |
18 | | -jest.mock('../../lib/cache', () => ({ |
19 | | - currentAccount: jest.fn(), |
20 | | -})); |
| 17 | +jest.mock('../../lib/hooks/useLocalStorageSync'); |
21 | 18 |
|
22 | 19 | const mockUseDynamicLocalization = useDynamicLocalization as jest.MockedFunction<typeof useDynamicLocalization>; |
23 | 20 | const mockInitializeNimbus = initializeNimbus as jest.MockedFunction<typeof initializeNimbus>; |
24 | 21 | const mockSentryCaptureException = Sentry.captureException as jest.MockedFunction<typeof Sentry.captureException>; |
25 | | -const mockCurrentAccount = currentAccount as jest.MockedFunction<typeof currentAccount>; |
| 22 | +const mockUseLocalStorageSync = useLocalStorageSync as jest.MockedFunction<typeof useLocalStorageSync>; |
26 | 23 |
|
27 | 24 | const TestComponent = () => { |
28 | 25 | const { experiments, loading, error } = useNimbusContext(); |
@@ -61,9 +58,16 @@ describe('NimbusContext', () => { |
61 | 58 | clearLanguagePreference: jest.fn(), |
62 | 59 | isLoading: false |
63 | 60 | }); |
64 | | - mockCurrentAccount.mockReturnValue({ |
65 | | - metricsEnabled: true, |
66 | | - } as StoredAccountData); |
| 61 | + // Mock useLocalStorageSync to return undefined by default (no account) |
| 62 | + mockUseLocalStorageSync.mockImplementation((key: string) => { |
| 63 | + if (key === 'currentAccountUid') { |
| 64 | + return undefined; |
| 65 | + } |
| 66 | + if (key === 'accounts') { |
| 67 | + return undefined; |
| 68 | + } |
| 69 | + return undefined; |
| 70 | + }); |
67 | 71 | Object.defineProperty(window, 'location', { |
68 | 72 | value: { search: '' }, |
69 | 73 | writable: true |
@@ -121,32 +125,39 @@ describe('NimbusContext', () => { |
121 | 125 | expect(screen.getByTestId('loading')).toHaveTextContent('false'); |
122 | 126 | }); |
123 | 127 |
|
124 | | - it('does not fetch when metrics are disabled', async () => { |
125 | | - mockCurrentAccount.mockReturnValue({ |
126 | | - metricsEnabled: false, |
127 | | - } as StoredAccountData); |
| 128 | + it('does not fetch when metrics are disabled via localStorage', async () => { |
| 129 | + const accountUid = 'test-account-uid'; |
| 130 | + mockUseLocalStorageSync.mockImplementation((key: string) => { |
| 131 | + if (key === 'currentAccountUid') return accountUid; |
| 132 | + if (key === 'accounts') return { [accountUid]: { metricsEnabled: false } }; |
| 133 | + return undefined; |
| 134 | + }); |
128 | 135 |
|
129 | 136 | renderWithProviders(); |
130 | 137 |
|
131 | | - expect(mockInitializeNimbus).not.toHaveBeenCalled(); |
132 | | - expect(screen.getByTestId('loading')).toHaveTextContent('false'); |
| 138 | + await waitFor(() => { |
| 139 | + expect(mockInitializeNimbus).not.toHaveBeenCalled(); |
| 140 | + }); |
133 | 141 | expect(screen.getByTestId('experiments')).toHaveTextContent('no-experiments'); |
134 | 142 | }); |
135 | 143 |
|
136 | | - it('fetches when metrics are enabled', async () => { |
137 | | - mockCurrentAccount.mockReturnValue({ |
138 | | - metricsEnabled: true, |
139 | | - } as StoredAccountData); |
140 | | - const mockExperiments: NimbusResult = { |
| 144 | + it('fetches when metrics are enabled via localStorage', async () => { |
| 145 | + const accountUid = 'test-account-uid'; |
| 146 | + mockUseLocalStorageSync.mockImplementation((key: string) => { |
| 147 | + if (key === 'currentAccountUid') return accountUid; |
| 148 | + if (key === 'accounts') return { [accountUid]: { metricsEnabled: true } }; |
| 149 | + return undefined; |
| 150 | + }); |
| 151 | + mockInitializeNimbus.mockResolvedValue({ |
141 | 152 | features: { 'test-feature': { enabled: true } }, |
142 | 153 | nimbusUserId: 'test-user-id' |
143 | | - }; |
144 | | - mockInitializeNimbus.mockResolvedValue(mockExperiments); |
| 154 | + }); |
145 | 155 |
|
146 | 156 | renderWithProviders(); |
147 | 157 |
|
148 | | - expect(mockInitializeNimbus).toHaveBeenCalled(); |
149 | | - await screen.findByTestId('experiments'); |
| 158 | + await waitFor(() => { |
| 159 | + expect(mockInitializeNimbus).toHaveBeenCalled(); |
| 160 | + }); |
150 | 161 | expect(screen.getByTestId('experiments')).toHaveTextContent('has-experiments'); |
151 | 162 | }); |
152 | 163 |
|
@@ -191,17 +202,15 @@ describe('NimbusContext', () => { |
191 | 202 | expect(screen.getByTestId('experiments')).toHaveTextContent('no-experiments'); |
192 | 203 | }); |
193 | 204 |
|
194 | | - it('handles fetch error', async () => { |
| 205 | + it('handles fetch error without duplicate error handling', async () => { |
195 | 206 | const error = new Error('Network error'); |
196 | 207 | mockInitializeNimbus.mockRejectedValue(error); |
197 | 208 |
|
198 | 209 | renderWithProviders(); |
199 | 210 |
|
200 | 211 | await screen.findByTestId('error'); |
201 | 212 | expect(screen.getByTestId('error')).toHaveTextContent('Network error'); |
202 | | - expect(mockSentryCaptureException).toHaveBeenCalledWith(error, expect.objectContaining({ |
203 | | - tags: { area: 'NimbusProvider', component: 'NimbusContext' } |
204 | | - })); |
| 213 | + expect(mockSentryCaptureException).toHaveBeenCalledTimes(1); |
205 | 214 | }); |
206 | 215 |
|
207 | 216 | it('handles preview mode from config', async () => { |
@@ -234,13 +243,19 @@ describe('NimbusContext', () => { |
234 | 243 | ); |
235 | 244 | }); |
236 | 245 |
|
237 | | - it('cleans up on unmount', () => { |
| 246 | + it('cleans up on unmount', async () => { |
| 247 | + mockInitializeNimbus.mockResolvedValue({ |
| 248 | + features: { 'test-feature': { enabled: true } }, |
| 249 | + nimbusUserId: 'test-user-id' |
| 250 | + }); |
| 251 | + |
238 | 252 | const { unmount } = renderWithProviders(); |
239 | 253 |
|
240 | | - unmount(); |
| 254 | + await waitFor(() => { |
| 255 | + expect(mockInitializeNimbus).toHaveBeenCalled(); |
| 256 | + }); |
241 | 257 |
|
242 | | - // Should not throw or cause memory leaks |
243 | | - expect(mockInitializeNimbus).toHaveBeenCalled(); |
| 258 | + expect(() => unmount()).not.toThrow(); |
244 | 259 | }); |
245 | 260 | }); |
246 | 261 | }); |
0 commit comments