import {storageSerializer} from '@yoroi/common' import {App, Portfolio} from '@yoroi/types' import {portfolioBalanceStorageMaker} from './balance-storage-maker' import {tokenMocks} from '../token.mocks' import {deserializers} from '../../transformers/deserializers' import {tokenBalanceMocks} from '../token-balance.mocks' describe('portfolioBalanceStorageMaker', () => { let balanceStorage: App.ObservableStorage let primaryBreakdownStorage: App.ObservableStorage beforeEach(() => { balanceStorage = createMockStorage( 'v2/wallets/id/balance/', deserializers.tokenAmount, ) primaryBreakdownStorage = createMockStorage( 'v2/wallets/id/primary-breakdown/', deserializers.primaryBreakdown, ) balanceStorage.clear() primaryBreakdownStorage.clear() jest.clearAllMocks() }) it('should instantiate storage', () => { const storage = portfolioBalanceStorageMaker({ balanceStorage, primaryBreakdownStorage, primaryTokenId: tokenMocks.primaryETH.info.id, }) expect(storage).toBeDefined() expect(Object.isFrozen(storage)).toBe(true) }) it('should save balances entries', () => { const {balances} = portfolioBalanceStorageMaker({ balanceStorage, primaryBreakdownStorage, primaryTokenId: tokenMocks.primaryETH.info.id, }) balances.save(tokenBalanceMocks.storage.entries1) expect(balanceStorage.multiSet).toHaveBeenCalledWith( tokenBalanceMocks.storage.entries1, storageSerializer, ) }) it('should read balances entries', () => { const nftCryptoKitty = tokenMocks.nftCryptoKitty.info const rnftWhatever = tokenMocks.rnftWhatever.info const {balances} = portfolioBalanceStorageMaker({ balanceStorage, primaryBreakdownStorage, primaryTokenId: tokenMocks.primaryETH.info.id, }) balances.save(tokenBalanceMocks.storage.entries1) const keys = [nftCryptoKitty.id, rnftWhatever.id] const result = balances.read(keys) expect(result).toEqual([ [nftCryptoKitty.id, tokenBalanceMocks.nftCryptoKitty], [rnftWhatever.id, tokenBalanceMocks.rnftWhatever], ]) expect(balanceStorage.multiGet).toHaveBeenCalledWith( keys, deserializers.tokenAmount, ) }) it('should clear all portfolio records', () => { const {clear, balances, primaryBreakdown} = portfolioBalanceStorageMaker({ balanceStorage, primaryBreakdownStorage, primaryTokenId: tokenMocks.primaryETH.info.id, }) balances.save(tokenBalanceMocks.storage.entries1) primaryBreakdown.save(tokenBalanceMocks.primaryETHBreakdown) let balanceResult = balances.all() let primaryBreakdownResult = primaryBreakdown.read() expect(balanceResult).toEqual(tokenBalanceMocks.storage.entries1) expect(primaryBreakdownResult).toEqual( tokenBalanceMocks.primaryETHBreakdown, ) clear() balanceResult = balances.all() primaryBreakdownResult = primaryBreakdown.read() // keys are gone expect(balanceResult).toEqual([]) expect(primaryBreakdownResult).toEqual(null) expect(balanceStorage.clear).toHaveBeenCalled() expect(primaryBreakdownStorage.clear).toHaveBeenCalled() }) it('should return all keys', () => { const {balances, primaryBreakdown} = portfolioBalanceStorageMaker({ balanceStorage, primaryBreakdownStorage, primaryTokenId: tokenMocks.primaryETH.info.id, }) balances.save(tokenBalanceMocks.storage.entries1) primaryBreakdown.save(tokenBalanceMocks.primaryETHBreakdown) let balanceResult = balances.keys() expect(balanceResult).toEqual([ ...new Map(tokenBalanceMocks.storage.entries1).keys(), ]) }) it('should call clear', () => { const {balances, primaryBreakdown} = portfolioBalanceStorageMaker({ balanceStorage, primaryBreakdownStorage, primaryTokenId: tokenMocks.primaryETH.info.id, }) balances.clear() primaryBreakdown.clear() expect(balanceStorage.clear).toHaveBeenCalled() expect(primaryBreakdownStorage.clear).toHaveBeenCalled() }) }) function createMockStorage( path: App.StorageFolderName = '/', deserializer = JSON.parse, serializer = storageSerializer, storage = new Map(), subscriptions = new Set(), ): App.ObservableStorage { const withPath = (key: string) => `${path}${key}` return { join: jest.fn((folderName: App.StorageFolderName) => createMockStorage( `${path}${folderName}`, deserializer, serializer, storage, subscriptions, ), ), getItem: jest.fn((key, parser = deserializer as any) => { const item = storage.get(withPath(key)) ?? null return parser(item) }), multiGet: jest.fn((keys, parser = deserializer as any) => { return keys.map((key) => [ key, parser(storage.get(withPath(key)) ?? null), ]) }), setItem: jest.fn((key, value, stringifier = serializer) => { storage.set(withPath(key), stringifier(value)) }), multiSet: jest.fn((tuples, stringifier = serializer) => { tuples.forEach(([key, value]) => { storage.set(withPath(key), stringifier(value)) }) }), removeItem: jest.fn((key) => { storage.delete(withPath(key)) }), removeFolder: jest.fn((folderName: App.StorageFolderName) => { const prefix = `${path}${folderName}` const keysToRemove = [...storage.keys()].filter((key) => key.startsWith(prefix), ) keysToRemove.forEach((key) => storage.delete(key)) }), multiRemove: jest.fn((keys) => { keys.forEach((key: string) => storage.delete(withPath(key))) }), getAllKeys: jest.fn(() => { const withoutPath = (absolutePath: string) => absolutePath.slice(path.length) return [...storage.keys()] .filter((key) => key.startsWith(path)) .map((key) => withoutPath(key)) as unknown as ReadonlyArray }), clear: jest.fn(() => { storage.clear() }), onChange: jest.fn((_keysToObserve, callback) => { subscriptions.add(callback) return { unsubscribe: jest.fn(() => { subscriptions.delete(callback) }), } as any }), } }