From 6f22242038b69f37b9fefd930f0681c6683838a4 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:07:29 +0000 Subject: [PATCH] chore(js-ts): Convert app/core/Engine.test.js to TypeScript --- app/core/Engine.test.ts | 164 ++++++++++++++++++++++++++++++++++++---- app/core/Engine.ts | 16 +--- 2 files changed, 152 insertions(+), 28 deletions(-) diff --git a/app/core/Engine.test.ts b/app/core/Engine.test.ts index 227ec1f679d..997bb30411a 100644 --- a/app/core/Engine.test.ts +++ b/app/core/Engine.test.ts @@ -1,41 +1,65 @@ -import Engine from './Engine'; +import EngineModule, { EngineState } from './Engine'; import { backgroundState } from '../util/test/initial-root-state'; import { createMockAccountsControllerState } from '../util/test/accountsControllerTestUtils'; import { mockNetworkState } from '../util/test/network'; -import type { EngineState } from './Engine'; import type { NetworkState, RpcEndpointType } from '@metamask/network-controller'; import type { CurrencyRateState } from '@metamask/assets-controllers'; jest.unmock('./Engine'); jest.mock('../store', () => ({ store: { getState: jest.fn(() => ({})) } })); +// Helper function to create Engine instances +const createEngine = (state: Partial = {}) => EngineModule.init(state as unknown as Record); + describe('Engine', () => { it('should expose an API', () => { - Engine.init({}); - // Existing expect statements remain unchanged + const engine = createEngine({}); + expect(engine.context).toHaveProperty('AccountTrackerController'); + expect(engine.context).toHaveProperty('AddressBookController'); + expect(engine.context).toHaveProperty('AssetsContractController'); + expect(engine.context).toHaveProperty('TokenListController'); + expect(engine.context).toHaveProperty('TokenDetectionController'); + expect(engine.context).toHaveProperty('NftDetectionController'); + expect(engine.context).toHaveProperty('NftController'); + expect(engine.context).toHaveProperty('CurrencyRateController'); + expect(engine.context).toHaveProperty('KeyringController'); + expect(engine.context).toHaveProperty('NetworkController'); + expect(engine.context).toHaveProperty('PhishingController'); + expect(engine.context).toHaveProperty('PreferencesController'); + expect(engine.context).toHaveProperty('SignatureController'); + expect(engine.context).toHaveProperty('TokenBalancesController'); + expect(engine.context).toHaveProperty('TokenRatesController'); + expect(engine.context).toHaveProperty('TokensController'); + expect(engine.context).toHaveProperty('LoggingController'); + expect(engine.context).toHaveProperty('TransactionController'); + expect(engine.context).toHaveProperty('SmartTransactionsController'); + expect(engine.context).toHaveProperty('AuthenticationController'); + expect(engine.context).toHaveProperty('UserStorageController'); + expect(engine.context).toHaveProperty('NotificationServicesController'); + expect(engine.context).toHaveProperty('SelectedNetworkController'); }); - it('calling Engine.init twice returns the same instance', () => { - const engine = Engine.init({}); - const newEngine = Engine.init({}); + it('calling Engine twice returns the same instance', () => { + const engine = createEngine({}); + const newEngine = createEngine({}); expect(engine).toStrictEqual(newEngine); }); it('calling Engine.destroy deletes the old instance', async () => { - const engine = Engine.init({}); + const engine = createEngine({}); await engine.destroyEngineInstance(); - const newEngine = Engine.init({}); + const newEngine = createEngine({}); expect(engine).not.toStrictEqual(newEngine); }); it('matches initial state fixture', () => { - const engine = Engine.init({}); + const engine = createEngine({}); const initialBackgroundState = engine.datamodel.state; expect(initialBackgroundState).toStrictEqual(backgroundState); }); it('setSelectedAccount throws an error if no account exists for the given address', () => { - const engine = Engine.init(backgroundState as unknown as Record); + const engine = createEngine(backgroundState as unknown as Record); const invalidAddress = '0xInvalidAddress'; expect(() => engine.setSelectedAccount(invalidAddress)).toThrow( `No account found for address: ${invalidAddress}`, @@ -43,7 +67,7 @@ describe('Engine', () => { }); describe('getTotalFiatAccountBalance', () => { - let engine: ReturnType | null = null; + let engine: ReturnType | null = null; afterEach(() => engine?.destroyEngineInstance()); const selectedAddress = '0x9DeE4BF1dE9E3b930E511Db5cEBEbC8d6F855Db0'; @@ -77,7 +101,7 @@ describe('Engine', () => { }; it('calculates when theres no balances', () => { - engine = Engine.init(state as unknown as Record); + engine = createEngine(state as unknown as Record); const totalFiatBalance = engine.getTotalFiatAccountBalance(); expect(totalFiatBalance).toStrictEqual({ ethFiat: 0, @@ -87,6 +111,118 @@ describe('Engine', () => { }); }); - // Existing test cases for 'calculates when theres only ETH' and 'calculates when there are ETH and tokens' remain unchanged + it('calculates when theres only ETH', () => { + const ethBalance = 1; // 1 ETH + const ethPricePercentChange1d = 5; // up 5% + const chainId = '0x1'; + + engine = createEngine({ + ...state, + AccountTrackerController: { + accountsByChainId: { + [chainId]: { + [selectedAddress]: { balance: ethBalance * 1e18 }, + }, + }, + }, + TokenRatesController: { + marketData: { + [chainId]: { + '0x0000000000000000000000000000000000000000': { + pricePercentChange1d: ethPricePercentChange1d, + }, + }, + }, + }, + } as unknown as Record); + + const totalFiatBalance = engine.getTotalFiatAccountBalance(); + + const ethFiat = ethBalance * ethConversionRate; + expect(totalFiatBalance).toStrictEqual({ + ethFiat, + ethFiat1dAgo: ethFiat / (1 + ethPricePercentChange1d / 100), + tokenFiat: 0, + tokenFiat1dAgo: 0, + }); + }); + + it('calculates when there are ETH and tokens', () => { + const ethBalance = 1; + const ethPricePercentChange1d = 5; + const chainId = '0x1'; + + const tokens = [ + { + address: '0x001', + balance: 1, + price: '1', + pricePercentChange1d: -1, + }, + { + address: '0x002', + balance: 2, + price: '2', + pricePercentChange1d: 2, + }, + ]; + + engine = createEngine({ + ...state, + AccountTrackerController: { + accountsByChainId: { + [chainId]: { + [selectedAddress]: { balance: ethBalance * 1e18 }, + }, + }, + }, + TokensController: { + tokens: tokens.map((token) => ({ + address: token.address, + balance: token.balance, + })), + }, + TokenRatesController: { + marketData: { + [chainId]: { + '0x0000000000000000000000000000000000000000': { + pricePercentChange1d: ethPricePercentChange1d, + }, + ...tokens.reduce( + (acc, token) => ({ + ...acc, + [token.address]: { + price: token.price, + pricePercentChange1d: token.pricePercentChange1d, + }, + }), + {} as Record, + ), + }, + }, + }, + } as unknown as Record); + + const totalFiatBalance = engine.getTotalFiatAccountBalance(); + + const ethFiat = ethBalance * ethConversionRate; + const [tokenFiat, tokenFiat1dAgo] = tokens.reduce( + ([fiat, fiat1d], token) => { + const value = token.balance * parseFloat(token.price) * ethConversionRate; + return [ + fiat + value, + fiat1d + value / (1 + token.pricePercentChange1d / 100), + ]; + }, + [0, 0], + ); + + expect(totalFiatBalance).toStrictEqual({ + ethFiat, + ethFiat1dAgo: ethFiat / (1 + ethPricePercentChange1d / 100), + tokenFiat, + tokenFiat1dAgo, + }); + }); }); }); diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 95199c1b9eb..4d8d946feda 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -439,7 +439,7 @@ export type ControllerMessenger = ExtendedControllerMessenger< * Core controller responsible for composing other metamask controllers together * and exposing convenience methods for common wallet operations. */ -class Engine { +export class Engine { /** * The global Engine singleton */ @@ -2124,16 +2124,15 @@ function assertEngineExists( let instance: Engine | null; export default { + Engine, get context() { assertEngineExists(instance); return instance.context; }, - get controllerMessenger() { assertEngineExists(instance); return instance.controllerMessenger; }, - get state() { assertEngineExists(instance); const { @@ -2216,44 +2215,36 @@ export default { AccountsController, }; }, - get datamodel() { assertEngineExists(instance); return instance.datamodel; }, - getTotalFiatAccountBalance() { assertEngineExists(instance); return instance.getTotalFiatAccountBalance(); }, - hasFunds() { assertEngineExists(instance); return instance.hasFunds(); }, - resetState() { assertEngineExists(instance); return instance.resetState(); }, - destroyEngine() { instance?.destroyEngineInstance(); instance = null; }, - init(state: Record | undefined, keyringState = null) { instance = Engine.instance || new Engine(state, keyringState); Object.freeze(instance); return instance; }, - acceptPendingApproval: async ( id: string, requestData?: Record, opts?: AcceptOptions & { handleErrors?: boolean }, ) => instance?.acceptPendingApproval(id, requestData, opts), - rejectPendingApproval: ( id: string, reason: Error, @@ -2262,17 +2253,14 @@ export default { logErrors?: boolean; } = {}, ) => instance?.rejectPendingApproval(id, reason, opts), - setSelectedAddress: (address: string) => { assertEngineExists(instance); instance.setSelectedAccount(address); }, - setAccountLabel: (address: string, label: string) => { assertEngineExists(instance); instance.setAccountLabel(address, label); }, - getGlobalEthQuery: (): EthQuery => { assertEngineExists(instance); return instance.getGlobalEthQuery();