UNPKG

@yoroi/portfolio

Version:

The Portfolio package of Yoroi SDK

543 lines (540 loc) 18.3 kB
"use strict"; var _types = require("@yoroi/types"); var _rxjs = require("rxjs"); var _common = require("@yoroi/common"); var _balanceManager = require("./balance-manager"); var _tokenBalance = require("./adapters/token-balance.mocks"); var _sorting = require("./helpers/sorting"); var _token = require("./adapters/token.mocks"); var _balanceStorageMaker = require("./adapters/mmkv-storage/balance-storage-maker"); var _tokenInfo = require("./adapters/token-info.mocks"); var _isFt = require("./helpers/is-ft"); var _isNft = require("./helpers/is-nft"); var _tokenManager = require("./token-manager.mock"); const tokenInfoStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: `/test/token-info/` })); const primaryTokenId = _token.tokenMocks.primaryETH.info.id; const sourceId = 'sourceId'; describe('portfolioBalanceManagerMaker', () => { const balanceStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/balance/', id: 'balance' })); const primaryBreakdownStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/primary-balance-breakdown/', id: 'primary-balance-breakdown' })); const storage = (0, _balanceStorageMaker.portfolioBalanceStorageMaker)({ balanceStorage, primaryBreakdownStorage, primaryTokenId }); const tokenManager = (0, _tokenManager.createTokenManagerMock)(); it('should be instantiated', () => { const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }); expect(manager).toBeDefined(); }); }); describe('hydrate', () => { const balanceStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/balance/', id: 'balance' })); const primaryBreakdownStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/primary-balance-breakdown/', id: 'primary-balance-breakdown' })); const storage = (0, _balanceStorageMaker.portfolioBalanceStorageMaker)({ balanceStorage, primaryBreakdownStorage, primaryTokenId }); const tokenManager = (0, _tokenManager.createTokenManagerMock)(); afterEach(() => { storage.clear(); tokenInfoStorage.clear(); }); const primaryStated = { availableRewards: 1000000n, lockedAsStorageCost: 0n, totalFromTxs: 0n }; storage.primaryBreakdown.save(primaryStated); storage.balances.save(_tokenBalance.tokenBalanceMocks.storage.entries1); it('should hydrate data', async () => { tokenInfoStorage.multiSet(_tokenInfo.tokenInfoMocks.storage.entries1); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }); const subscriber = jest.fn(); manager.subscribe(subscriber); const sorted = (0, _sorting.sortTokenAmountsByInfo)({ primaryTokenInfo: _token.tokenMocks.primaryETH.info, amounts: [...new Map(_tokenBalance.tokenBalanceMocks.storage.entries1WithPrimary).values()] }); const sortedBalances = { records: new Map(sorted.map(amount => [amount.info.id, amount])), all: sorted, fts: sorted.filter(_ref => { let { info } = _ref; return (0, _isFt.isFt)(info); }), nfts: sorted.filter(_ref2 => { let { info } = _ref2; return (0, _isNft.isNft)(info); }) }; manager.hydrate(); expect(manager.getPrimaryBreakdown()).toEqual(primaryStated); expect(manager.getBalances()).toEqual(sortedBalances); expect(manager.getPrimaryBalance()).toEqual({ info: _token.tokenMocks.primaryETH.info, quantity: primaryStated.totalFromTxs + primaryStated.availableRewards }); expect(subscriber).toHaveBeenCalledTimes(1); expect(manager.getHasOnlyPrimary()).toBe(false); expect(manager.getIsEmpty()).toBe(false); }); }); describe('destroy', () => { const balanceStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/balance/', id: 'balance' })); const primaryBreakdownStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/primary-balance-breakdown/', id: 'primary-balance-breakdown' })); const storage = (0, _balanceStorageMaker.portfolioBalanceStorageMaker)({ balanceStorage, primaryBreakdownStorage, primaryTokenId }); const tokenManager = (0, _tokenManager.createTokenManagerMock)(); const queueDestroy = jest.fn(); const observerDestroy = jest.fn(); afterEach(() => { storage.clear(); tokenInfoStorage.clear(); }); it('should tear down observers', async () => { tokenManager.sync.mockResolvedValue(new Map(_tokenInfo.tokenInfoMocks.storage.entries1)); const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: observerDestroy, notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: queueDestroy, observable: new _rxjs.BehaviorSubject({}).asObservable() } }); manager.destroy(); expect(observerDestroy).toHaveBeenCalled(); expect(queueDestroy).toHaveBeenCalled(); expect(tokenManager.unsubscribe).toHaveBeenCalled(); }); }); describe('primary updates', () => { const balanceStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/balance/', id: 'balance' })); const primaryBreakdownStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/primary-balance-breakdown/', id: 'primary-balance-breakdown' })); const storage = (0, _balanceStorageMaker.portfolioBalanceStorageMaker)({ balanceStorage, primaryBreakdownStorage, primaryTokenId }); const tokenManager = (0, _tokenManager.createTokenManagerMock)(); afterEach(() => { storage.clear(); tokenInfoStorage.clear(); }); it('should update primary stated', () => { const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: jest.fn(), notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); const subscriber = jest.fn(); manager.subscribe(subscriber); manager.hydrate(); manager.updatePrimaryStated({ totalFromTxs: 1000001n, lockedAsStorageCost: 10n }); expect(manager.getPrimaryBreakdown()).toEqual({ totalFromTxs: 1000001n, availableRewards: 0n, lockedAsStorageCost: 10n }); expect(manager.getPrimaryBalance()).toEqual({ info: _token.tokenMocks.primaryETH.info, quantity: 1000001n }); expect(mockedNotify).toHaveBeenCalledTimes(2); expect(mockedNotify).toHaveBeenCalledWith({ on: _types.Portfolio.Event.ManagerOn.Sync, sourceId, mode: 'primary-stated' }); }); it('should update primary derived', () => { const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: jest.fn(), notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); const primaryStated = { availableRewards: 0n, lockedAsStorageCost: 0n, totalFromTxs: 1000001n }; storage.primaryBreakdown.save(primaryStated); const subscriber = jest.fn(); manager.subscribe(subscriber); manager.hydrate(); manager.updatePrimaryDerived({ availableRewards: 1000001n }); expect(manager.getPrimaryBreakdown()).toEqual({ totalFromTxs: 1000001n, availableRewards: 1000001n, lockedAsStorageCost: 0n }); expect(manager.getPrimaryBalance()).toEqual({ info: _token.tokenMocks.primaryETH.info, quantity: 2000002n }); expect(mockedNotify).toHaveBeenCalledTimes(2); expect(mockedNotify).toHaveBeenCalledWith({ on: _types.Portfolio.Event.ManagerOn.Sync, sourceId, mode: 'primary-derived' }); }); }); describe('sync & refresh', () => { const balanceStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/balance/', id: 'balance' })); const primaryBreakdownStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/primary-balance-breakdown/', id: 'primary-balance-breakdown' })); const storage = (0, _balanceStorageMaker.portfolioBalanceStorageMaker)({ balanceStorage, primaryBreakdownStorage, primaryTokenId }); const tokenManagerObservable = new _rxjs.BehaviorSubject({}); const tokenManager = (0, _tokenManager.createTokenManagerMock)(tokenManagerObservable.asObservable()); afterEach(() => { storage.clear(); tokenInfoStorage.clear(); }); const primaryStated = { totalFromTxs: BigInt(1000000), availableRewards: BigInt(0), lockedAsStorageCost: BigInt(0) }; it('should sync and respond to token event', async () => { tokenManager.sync.mockResolvedValue(new Map(_tokenInfo.tokenInfoMocks.storage.entries1)); const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: jest.fn(), notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); const subscriber = jest.fn(); manager.subscribe(subscriber); manager.hydrate(); const secondaryBalances = new Map(_tokenBalance.tokenBalanceMocks.storage.entries1.slice(0, -1)); manager.syncBalances({ primaryStated, secondaryBalances }); expect(enqueueMock).toHaveBeenCalled(); expect(tasks.length).toBe(1); for (const task of tasks) { await task(); } tasks.length = 0; expect(manager.getPrimaryBreakdown()).toEqual(expect.anything()); expect(manager.getBalances()).toEqual(expect.anything()); expect(mockedNotify).toHaveBeenCalledTimes(2); tokenManager.sync.mockResolvedValue(new Map(_tokenInfo.tokenInfoMocks.storage.entries1.slice(0, 1))); // simulate an update coming from another sync on token manager tokenManagerObservable.next({ on: _types.Portfolio.Event.ManagerOn.Sync, sourceId: 'another-source', ids: [_tokenInfo.tokenInfoMocks.nftCryptoKitty.id, _tokenInfo.tokenInfoMocks.rnftWhatever.id] }); expect(tasks.length).toBeGreaterThan(0); for (const task of tasks) { await task(); } }); it('should refresh', async () => { tokenManager.sync.mockResolvedValue(new Map(_tokenInfo.tokenInfoMocks.storage.entries1)); const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: jest.fn(), notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); const subscriber = jest.fn(); manager.subscribe(subscriber); const secondaryBalances = new Map(_tokenBalance.tokenBalanceMocks.storage.entries1.slice(0, -1)); manager.syncBalances({ primaryStated, secondaryBalances }); manager.refresh(); expect(enqueueMock).toHaveBeenCalled(); expect(tasks.length).toBe(2); for (const task of tasks) { await task(); } tasks.length = 0; expect(manager.getPrimaryBreakdown()).toEqual(expect.anything()); expect(manager.getBalances()).toEqual(expect.anything()); expect(mockedNotify).toHaveBeenCalledTimes(2); }); it('should sync', async () => { tokenManager.sync.mockResolvedValue(new Map(_tokenInfo.tokenInfoMocks.storage.entries1)); const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: jest.fn(), notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); const subscriber = jest.fn(); manager.subscribe(subscriber); const secondaryBalances = new Map(_tokenBalance.tokenBalanceMocks.storage.entries1.slice(0, -1)); manager.hydrate(); manager.syncBalances({ primaryStated, secondaryBalances }); expect(enqueueMock).toHaveBeenCalled(); expect(tasks.length).toBeGreaterThan(0); for (const task of tasks) { await task(); } expect(manager.getPrimaryBreakdown()).toEqual(expect.anything()); expect(manager.getBalances()).toEqual(expect.anything()); expect(mockedNotify).toHaveBeenCalledTimes(2); // Hydrate + Sync }); it('should throw error if token manager miss a token', async () => { // empty map, should never happen tokenManager.sync.mockResolvedValue(new Map()); const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { observer: { destroy: jest.fn(), notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); const secondaryBalances = new Map(_tokenBalance.tokenBalanceMocks.storage.entries1); manager.syncBalances({ primaryStated, secondaryBalances }); expect(enqueueMock).toHaveBeenCalled(); expect(tasks.length).toBeGreaterThan(0); for (const task of tasks) { await expect(() => task()).rejects.toThrow(); } }); }); describe('clear', () => { const balanceStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/balance/', id: 'balance' })); const primaryBreakdownStorage = (0, _common.observableStorageMaker)((0, _common.mountMMKVStorage)({ path: '/tmp/primary-balance-breakdown/', id: 'primary-breakdown' })); const storage = (0, _balanceStorageMaker.portfolioBalanceStorageMaker)({ balanceStorage, primaryBreakdownStorage, primaryTokenId }); const tokenManager = (0, _tokenManager.createTokenManagerMock)(); afterEach(() => { storage.clear(); tokenInfoStorage.clear(); }); it('should clear all data after syncing operations are complete', async () => { const tasks = []; const enqueueMock = jest.fn(task => tasks.push(task)); const mockedNotify = jest.fn(); const manager = (0, _balanceManager.portfolioBalanceManagerMaker)({ tokenManager, storage, primaryTokenInfo: _token.tokenMocks.primaryETH.info, sourceId }, { queue: { enqueue: enqueueMock, destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() }, observer: { notify: mockedNotify, subscribe: jest.fn(), unsubscribe: jest.fn(), destroy: jest.fn(), observable: new _rxjs.BehaviorSubject({}).asObservable() } }); manager.syncBalances({ primaryStated: { totalFromTxs: 500n, lockedAsStorageCost: 100n }, secondaryBalances: new Map() }); manager.clear(); expect(enqueueMock).toHaveBeenCalledTimes(2); for (const task of tasks) { await task(); } expect(storage.balances.all()).toEqual([]); expect(storage.primaryBreakdown.read()).toBeNull(); expect(mockedNotify).toHaveBeenCalledWith({ on: _types.Portfolio.Event.ManagerOn.Clear, sourceId }); }); }); //# sourceMappingURL=balance-manager.test.js.map