-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Redesigned useNetwork to use chrome storage instead of zustand
Closes #655
- Loading branch information
1 parent
e6e77da
commit 536bcde
Showing
11 changed files
with
288 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,226 @@ | ||
import { act, useState, useEffect } from 'react'; | ||
import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
|
||
import { useNetworkStore } from '@/ui/stores/networkStore'; | ||
import { useWallet, useWalletLoaded } from '@/ui/utils/WalletContext'; | ||
|
||
import { useNetworks } from '../useNetworkHook'; | ||
import { useNetwork } from '../useNetworkHook'; | ||
|
||
// Mock React | ||
vi.mock('react', async () => { | ||
const actual = await vi.importActual('react'); | ||
return { | ||
...actual, | ||
useCallback: vi.fn().mockImplementation((fn) => fn), | ||
useEffect: vi.fn().mockImplementation((fn) => { | ||
const cleanup = fn(); | ||
return cleanup; | ||
}), | ||
useState: vi.fn().mockImplementation((initialValue) => [initialValue, vi.fn()]), | ||
}; | ||
}); | ||
|
||
// Mock the store | ||
vi.mock('@/ui/stores/networkStore', () => ({ | ||
useNetworkStore: vi.fn((selector) => | ||
selector({ | ||
currentNetwork: 'mainnet', | ||
developerMode: false, | ||
emulatorModeOn: false, | ||
setNetwork: vi.fn(), | ||
setDeveloperMode: vi.fn(), | ||
setEmulatorModeOn: vi.fn(), | ||
}) | ||
), | ||
})); | ||
|
||
// Mock the wallet context | ||
vi.mock('@/ui/utils/WalletContext', () => ({ | ||
useWalletLoaded: vi.fn().mockReturnValue(true), | ||
useWallet: vi.fn().mockReturnValue({ | ||
getNetwork: vi.fn().mockResolvedValue('mainnet'), | ||
}), | ||
})); | ||
// Mock chrome API | ||
const mockChromeStorage = { | ||
local: { | ||
get: vi.fn(), | ||
}, | ||
onChanged: { | ||
addListener: vi.fn(), | ||
removeListener: vi.fn(), | ||
}, | ||
}; | ||
|
||
vi.stubGlobal('chrome', { | ||
storage: mockChromeStorage, | ||
}); | ||
|
||
describe('useNetworkHook', () => { | ||
let mockSetNetwork: ReturnType<typeof vi.fn>; | ||
let setNetworkMock: ReturnType<typeof vi.fn>; | ||
let setDeveloperModeMock: ReturnType<typeof vi.fn>; | ||
let setEmulatorModeOnMock: ReturnType<typeof vi.fn>; | ||
|
||
beforeEach(() => { | ||
mockSetNetwork = vi.fn(); | ||
vi.mocked(useNetworkStore).mockImplementation((selector) => | ||
selector({ | ||
currentNetwork: 'mainnet', | ||
developerMode: false, | ||
emulatorModeOn: false, | ||
setNetwork: mockSetNetwork, | ||
setDeveloperMode: vi.fn(), | ||
setEmulatorModeOn: vi.fn(), | ||
}) | ||
vi.clearAllMocks(); | ||
|
||
setNetworkMock = vi.fn(); | ||
setDeveloperModeMock = vi.fn(); | ||
setEmulatorModeOnMock = vi.fn(); | ||
|
||
// Mock useState to return our mock setters | ||
vi.mocked(useState).mockImplementationOnce(() => ['mainnet', setNetworkMock]); | ||
vi.mocked(useState).mockImplementationOnce(() => [false, setDeveloperModeMock]); | ||
vi.mocked(useState).mockImplementationOnce(() => [false, setEmulatorModeOnMock]); | ||
|
||
// Reset chrome storage mock with default values | ||
mockChromeStorage.local.get.mockImplementation((key) => { | ||
if (key === 'developerMode') { | ||
return Promise.resolve({ developerMode: false }); | ||
} | ||
if (key === 'emulatorMode') { | ||
return Promise.resolve({ emulatorMode: false }); | ||
} | ||
if (key === 'userWallets') { | ||
return Promise.resolve({ userWallets: { network: 'mainnet' } }); | ||
} | ||
return Promise.resolve({}); | ||
}); | ||
}); | ||
|
||
it('should initialize with default values', () => { | ||
const { network, developerMode, emulatorModeOn } = useNetwork(); | ||
|
||
expect(network).toBe('mainnet'); | ||
expect(developerMode).toBe(false); | ||
expect(emulatorModeOn).toBe(false); | ||
expect(mockChromeStorage.onChanged.addListener).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should load initial data from storage', async () => { | ||
// Mock storage returns for initial load | ||
mockChromeStorage.local.get.mockImplementation((key) => { | ||
if (key === 'developerMode') { | ||
return Promise.resolve({ developerMode: true }); | ||
} | ||
if (key === 'emulatorMode') { | ||
return Promise.resolve({ emulatorMode: true }); | ||
} | ||
if (key === 'userWallets') { | ||
return Promise.resolve({ userWallets: { network: 'testnet' } }); | ||
} | ||
return Promise.resolve({}); | ||
}); | ||
|
||
useNetwork(); | ||
|
||
// Wait for promises to resolve | ||
await act(async () => { | ||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
}); | ||
|
||
expect(mockChromeStorage.local.get).toHaveBeenCalledWith('developerMode'); | ||
expect(mockChromeStorage.local.get).toHaveBeenCalledWith('emulatorMode'); | ||
expect(mockChromeStorage.local.get).toHaveBeenCalledWith('userWallets'); | ||
}); | ||
|
||
it('should handle storage changes for userWallets', () => { | ||
useNetwork(); | ||
|
||
// Simulate storage change event for userWallets | ||
const handleStorageChange = mockChromeStorage.onChanged.addListener.mock.calls[0][0]; | ||
|
||
handleStorageChange( | ||
{ | ||
userWallets: { | ||
newValue: { network: 'testnet', emulatorMode: true }, | ||
oldValue: { network: 'mainnet', emulatorMode: false }, | ||
}, | ||
}, | ||
'local' | ||
); | ||
|
||
expect(setNetworkMock).toHaveBeenCalledWith('testnet'); | ||
expect(setEmulatorModeOnMock).toHaveBeenCalledWith(true); | ||
}); | ||
|
||
describe('fetchNetwork', () => { | ||
it('should correctly identify network type', async () => { | ||
const hook = useNetworks(); | ||
await hook.fetchNetwork(); | ||
it('should handle storage changes for developerMode', () => { | ||
useNetwork(); | ||
|
||
// Simulate storage change event for developerMode | ||
const handleStorageChange = mockChromeStorage.onChanged.addListener.mock.calls[0][0]; | ||
|
||
handleStorageChange( | ||
{ | ||
developerMode: { | ||
newValue: true, | ||
oldValue: false, | ||
}, | ||
}, | ||
'local' | ||
); | ||
|
||
expect(setDeveloperModeMock).toHaveBeenCalledWith(true); | ||
}); | ||
|
||
it('should clean up listeners on unmount', () => { | ||
useNetwork(); | ||
|
||
// Get the cleanup function from the useEffect mock | ||
const cleanupFn = vi.mocked(useEffect).mock.calls[0][0](); | ||
|
||
if (cleanupFn) { | ||
// Call the cleanup function | ||
cleanupFn(); | ||
|
||
expect(mockChromeStorage.onChanged.removeListener).toHaveBeenCalled(); | ||
} | ||
}); | ||
|
||
expect(mockSetNetwork).toHaveBeenCalledWith('mainnet'); | ||
it('should not update state if component is unmounted', async () => { | ||
// Create a flag to simulate mounted state | ||
let mounted = true; | ||
|
||
// Override the useEffect implementation for this test only | ||
vi.mocked(useEffect).mockImplementationOnce((fn) => { | ||
fn(); | ||
return () => { | ||
mounted = false; | ||
}; | ||
}); | ||
|
||
// Override useState implementations to check mounted flag | ||
vi.mocked(useState).mockReset(); | ||
vi.mocked(useState).mockImplementationOnce(() => [ | ||
'mainnet', | ||
(val) => { | ||
if (mounted) setNetworkMock(val); | ||
}, | ||
]); | ||
vi.mocked(useState).mockImplementationOnce(() => [ | ||
false, | ||
(val) => { | ||
if (mounted) setDeveloperModeMock(val); | ||
}, | ||
]); | ||
vi.mocked(useState).mockImplementationOnce(() => [ | ||
false, | ||
(val) => { | ||
if (mounted) setEmulatorModeOnMock(val); | ||
}, | ||
]); | ||
|
||
// Mock storage to return delayed promises | ||
mockChromeStorage.local.get.mockImplementation((key) => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
if (key === 'developerMode') { | ||
resolve({ developerMode: true }); | ||
} else if (key === 'emulatorMode') { | ||
resolve({ emulatorMode: true }); | ||
} else if (key === 'userWallets') { | ||
resolve({ userWallets: { network: 'testnet' } }); | ||
} else { | ||
resolve({}); | ||
} | ||
}, 10); // Small delay to ensure it happens after unmount | ||
}); | ||
}); | ||
|
||
// Call the hook | ||
useNetwork(); | ||
|
||
// Clear mocks before unmounting | ||
setNetworkMock.mockClear(); | ||
setDeveloperModeMock.mockClear(); | ||
setEmulatorModeOnMock.mockClear(); | ||
|
||
// Simulate unmounting | ||
mounted = false; | ||
|
||
// Wait for delayed promises to resolve | ||
await act(async () => { | ||
await new Promise((resolve) => setTimeout(resolve, 20)); | ||
}); | ||
|
||
// State setters should not be called after unmount | ||
expect(setNetworkMock).not.toHaveBeenCalled(); | ||
expect(setDeveloperModeMock).not.toHaveBeenCalled(); | ||
expect(setEmulatorModeOnMock).not.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.