@yoroi/portfolio
Version:
The Portfolio package of Yoroi SDK
543 lines (540 loc) • 18.3 kB
JavaScript
"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