diff --git a/dangerfile.ts b/dangerfile.ts index 490ddd34d3..abfeed4a0c 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -179,6 +179,11 @@ async function checkCorePackage() { f.includes('core/tests/controllers') ) + const modified_core_controllers = modified_files.filter(f => f.includes('core/src/controllers')) + const modified_core_controllers_tests = modified_files.filter(f => + f.includes('core/tests/controllers') + ) + for (const f of created_core_controllers) { const diff = await diffForFile(f) @@ -214,6 +219,13 @@ async function checkCorePackage() { if (created_core_controllers.length && !created_core_controllers_tests.length) { fail('New controllers were added, but no tests were created') } + + if (modified_core_controllers.length && !modified_core_controllers_tests) { + message(` + The following controllers were modified, but not tests were changed: + ${modified_core_controllers.join('\n')} + `) + } } checkCorePackage() diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index c08195cb73..6656d98a03 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -27,7 +27,8 @@ type StateKey = keyof AccountControllerState const state = proxy({ isConnected: false, currentTab: 0, - tokenBalance: [] + tokenBalance: [], + smartAccountDeployed: false }) // -- Controller ---------------------------------------- // @@ -99,6 +100,7 @@ export const AccountController = { resetAccount() { state.isConnected = false + state.smartAccountDeployed = false state.currentTab = 0 state.caipAddress = undefined state.address = undefined @@ -107,7 +109,6 @@ export const AccountController = { state.profileName = undefined state.profileImage = undefined state.addressExplorerUrl = undefined - state.smartAccountDeployed = undefined state.tokenBalance = [] } } diff --git a/packages/core/tests/controllers/AccountController.test.ts b/packages/core/tests/controllers/AccountController.test.ts index 9f57384fff..18a3af255e 100644 --- a/packages/core/tests/controllers/AccountController.test.ts +++ b/packages/core/tests/controllers/AccountController.test.ts @@ -7,11 +7,17 @@ const balance = '0.100' const balanceSymbol = 'ETH' const profileName = 'john.eth' const profileImage = 'https://ipfs.com/0x123.png' +const explorerUrl = 'https://some.explorer.com/explore' // -- Tests -------------------------------------------------------------------- describe('AccountController', () => { it('should have valid default state', () => { - expect(AccountController.state).toEqual({ isConnected: false, tokenBalance: [], currentTab: 0 }) + expect(AccountController.state).toEqual({ + isConnected: false, + smartAccountDeployed: false, + currentTab: 0, + tokenBalance: [] + }) }) it('should update state correctly on setIsConnected()', () => { @@ -41,10 +47,21 @@ describe('AccountController', () => { expect(AccountController.state.profileImage).toEqual(profileImage) }) + it('should update state correctly on setAddressExplorerUrl()', () => { + AccountController.setAddressExplorerUrl(explorerUrl) + expect(AccountController.state.addressExplorerUrl).toEqual(explorerUrl) + }) + + it('shuold update state correctly on setSmartAccountDeployed()', () => { + AccountController.setSmartAccountDeployed(true) + expect(AccountController.state.smartAccountDeployed).toEqual(true) + }) + it('should update state correctly on resetAccount()', () => { AccountController.resetAccount() expect(AccountController.state).toEqual({ isConnected: false, + smartAccountDeployed: false, currentTab: 0, caipAddress: undefined, address: undefined, @@ -53,7 +70,6 @@ describe('AccountController', () => { profileName: undefined, profileImage: undefined, addressExplorerUrl: undefined, - smartAccountDeployed: undefined, tokenBalance: [] }) }) diff --git a/packages/core/tests/controllers/ApiController.test.ts b/packages/core/tests/controllers/ApiController.test.ts index e2262fa9ee..057ed99b76 100644 --- a/packages/core/tests/controllers/ApiController.test.ts +++ b/packages/core/tests/controllers/ApiController.test.ts @@ -126,7 +126,7 @@ describe('ApiController', () => { expect(fetchSpy).toHaveBeenCalledTimes(2) }) - it('should fetch network images', async () => { + it('should only fetch network images for networks with imageIds', async () => { NetworkController.setRequestedCaipNetworks([ { id: '155:1', @@ -236,7 +236,7 @@ describe('ApiController', () => { expect(fetchImageSpy).not.toHaveBeenCalled() }) - // Recommended wllets + // Recommended wallets it('should fetch recommended wallets with configured recommended wallets', async () => { const includeWalletIds = ['12341', '12342'] const excludeWalletIds = ['12343'] diff --git a/packages/core/tests/controllers/AssetController.test.ts b/packages/core/tests/controllers/AssetController.test.ts index 6393ab360e..56a65c03a9 100644 --- a/packages/core/tests/controllers/AssetController.test.ts +++ b/packages/core/tests/controllers/AssetController.test.ts @@ -1,6 +1,29 @@ import { describe, expect, it } from 'vitest' import { AssetController } from '../../index.js' +// -- Constants ---------------------------------------------------------------- +const walletImage = 'w3mWallet.png' +const walletImage2 = 'w3mWallet2.png' +const networkImage = 'ethereum.png' +const networkImage2 = 'polygon.png' +const connectorImage = 'email-connector.png' +const connectorImage2 = 'metamask-connector.png' +const tokenImage = 'eth.png' +const tokenImage2 = 'usdc.png' +const currencyImage = 'usd.png' +const currencyImage2 = 'eur.png' + +const wallet = 'w3m' +const wallet2 = 'w4m' +const network = 'ethereum' +const network2 = 'polygon' +const connector = 'w3m-email' +const connector2 = 'mm-connector' +const token = 'ETH' +const token2 = 'MATIC' +const currency = 'USD' +const currency2 = 'EUR' + // -- Tests -------------------------------------------------------------------- describe('AssetController', () => { it('should have valid default state', () => { @@ -12,4 +35,74 @@ describe('AssetController', () => { currencyImages: {} }) }) + + it('should update state properly on setWalletImage()', () => { + AssetController.setWalletImage(wallet, walletImage) + expect(AssetController.state.walletImages).toEqual({ [wallet]: walletImage }) + + AssetController.setWalletImage(wallet, walletImage2) + expect(AssetController.state.walletImages).toEqual({ [wallet]: walletImage2 }) + + AssetController.setWalletImage(wallet2, walletImage2) + expect(AssetController.state.walletImages).toEqual({ + [wallet]: walletImage2, + [wallet2]: walletImage2 + }) + }) + + it('should update state properly on setNetworkImage()', () => { + AssetController.setNetworkImage(network, networkImage) + expect(AssetController.state.networkImages).toEqual({ [network]: networkImage }) + + AssetController.setNetworkImage(network, networkImage2) + expect(AssetController.state.networkImages).toEqual({ [network]: networkImage2 }) + + AssetController.setNetworkImage(network2, networkImage2) + expect(AssetController.state.networkImages).toEqual({ + [network]: networkImage2, + [network2]: networkImage2 + }) + }) + + it('should update state properly on setConnectorImage()', () => { + AssetController.setConnectorImage(connector, connectorImage) + expect(AssetController.state.connectorImages).toEqual({ [connector]: connectorImage }) + + AssetController.setConnectorImage(connector, connectorImage2) + expect(AssetController.state.connectorImages).toEqual({ [connector]: connectorImage2 }) + + AssetController.setConnectorImage(connector2, connectorImage2) + expect(AssetController.state.connectorImages).toEqual({ + [connector]: connectorImage2, + [connector2]: connectorImage2 + }) + }) + + it('should update state properly on setTokenImage()', () => { + AssetController.setTokenImage(token, tokenImage) + expect(AssetController.state.tokenImages).toEqual({ [token]: tokenImage }) + + AssetController.setTokenImage(token, tokenImage2) + expect(AssetController.state.tokenImages).toEqual({ [token]: tokenImage2 }) + + AssetController.setTokenImage(token2, tokenImage2) + expect(AssetController.state.tokenImages).toEqual({ + [token]: tokenImage2, + [token2]: tokenImage2 + }) + }) + + it('should update state properly on setCurrencyImage()', () => { + AssetController.setCurrencyImage(currency, currencyImage) + expect(AssetController.state.currencyImages).toEqual({ [currency]: currencyImage }) + + AssetController.setCurrencyImage(currency, currencyImage2) + expect(AssetController.state.currencyImages).toEqual({ [currency]: currencyImage2 }) + + AssetController.setCurrencyImage(currency2, currencyImage2) + expect(AssetController.state.currencyImages).toEqual({ + [currency]: currencyImage2, + [currency2]: currencyImage2 + }) + }) }) diff --git a/packages/core/tests/controllers/ConnectionController.test.ts b/packages/core/tests/controllers/ConnectionController.test.ts index 3e77ae86fb..bf55b7b40a 100644 --- a/packages/core/tests/controllers/ConnectionController.test.ts +++ b/packages/core/tests/controllers/ConnectionController.test.ts @@ -1,16 +1,17 @@ -import { describe, expect, it } from 'vitest' -import type { ConnectionControllerClient } from '../../index.js' -import { ConnectionController } from '../../index.js' +import { describe, expect, it, vi } from 'vitest' +import type { ConnectionControllerClient, ConnectorType } from '../../index.js' +import { ConnectionController, ConstantsUtil, StorageUtil } from '../../index.js' // -- Setup -------------------------------------------------------------------- const walletConnectUri = 'wc://uri?=123' const externalId = 'coinbaseWallet' -const type = 'EMAIL' +const type = 'EMAIL' as ConnectorType +const storageSpy = vi.spyOn(StorageUtil, 'setConnectedConnector') const client: ConnectionControllerClient = { connectWalletConnect: async onUri => { onUri(walletConnectUri) - await Promise.resolve() + await Promise.resolve(walletConnectUri) }, disconnect: async () => Promise.resolve(), signMessage: async (message: string) => Promise.resolve(message), @@ -18,6 +19,9 @@ const client: ConnectionControllerClient = { checkInstalled: _id => true } +const clientConnectExternalSpy = vi.spyOn(client, 'connectExternal') +const clientCheckInstalledSpy = vi.spyOn(client, 'checkInstalled') + const partialClient: ConnectionControllerClient = { connectWalletConnect: async () => Promise.resolve(), disconnect: async () => Promise.resolve(), @@ -47,26 +51,49 @@ describe('ConnectionController', () => { expect(ConnectionController.state.wcPromise).toEqual(undefined) }) - it('should not throw on connectWalletConnect()', () => { + it('should update state correctly and set wcPromise on connectWalletConnect()', async () => { + // Setup timers for pairing expiry + const fakeDate = new Date(0) + vi.useFakeTimers() + vi.setSystemTime(fakeDate) + ConnectionController.connectWalletConnect() + expect(ConnectionController.state.wcPromise).toBeDefined() + + // Await on set promise and check results + await ConnectionController.state.wcPromise + expect(ConnectionController.state.wcUri).toEqual(walletConnectUri) + expect(ConnectionController.state.wcPairingExpiry).toEqual(ConstantsUtil.FOUR_MINUTES_MS) + expect(storageSpy).toHaveBeenCalledWith('WALLET_CONNECT') + + // Just in case + vi.useRealTimers() }) - it('should not throw on connectExternal()', async () => { - await ConnectionController.connectExternal({ id: externalId, type }) + it('connectExternal() should trigger internal client call and set connector in storage', async () => { + const options = { id: externalId, type } + await ConnectionController.connectExternal(options) + expect(storageSpy).toHaveBeenCalledWith(type) + expect(clientConnectExternalSpy).toHaveBeenCalledWith(options) }) - it('should not throw on checkInstalled()', () => { + it('checkInstalled() should trigger internal client call', () => { ConnectionController.checkInstalled([externalId]) + expect(clientCheckInstalledSpy).toHaveBeenCalledWith([externalId]) }) it('should not throw on checkInstalled() without ids', () => { ConnectionController.checkInstalled() + expect(clientCheckInstalledSpy).toHaveBeenCalledWith(undefined) }) it('should not throw when optional methods are undefined', async () => { ConnectionController.setClient(partialClient) await ConnectionController.connectExternal({ id: externalId, type }) ConnectionController.checkInstalled([externalId]) + expect(clientCheckInstalledSpy).toHaveBeenCalledWith([externalId]) + expect(clientCheckInstalledSpy).toHaveBeenCalledWith(undefined) + expect(ConnectionController.state._client).toEqual(partialClient) }) it('should update state correctly on resetWcConnection()', () => { diff --git a/packages/core/tests/controllers/ConnectorController.test.ts b/packages/core/tests/controllers/ConnectorController.test.ts index 20b0aa7a56..d042c78fc7 100644 --- a/packages/core/tests/controllers/ConnectorController.test.ts +++ b/packages/core/tests/controllers/ConnectorController.test.ts @@ -1,9 +1,46 @@ -import { describe, expect, it } from 'vitest' -import { ConnectorController } from '../../index.js' +import { describe, expect, it, vi } from 'vitest' +import { + ConnectorController, + OptionsController, + type Metadata, + type SdkVersion, + type ThemeMode, + type ThemeVariables +} from '../../index.js' // -- Setup -------------------------------------------------------------------- -const walletConnectConnector = { id: 'walletConnect', type: 'WALLET_CONNECT' } as const +const emailProvider = { + syncDappData: (_args: { metadata: Metadata; sdkVersion: SdkVersion; projectId: string }) => + Promise.resolve(), + syncTheme: (_args: { themeMode: ThemeMode; themeVariables: ThemeVariables }) => Promise.resolve() +} + +const walletConnectConnector = { + id: 'walletConnect', + explorerId: 'walletConnect', + type: 'WALLET_CONNECT' +} as const const externalConnector = { id: 'external', type: 'EXTERNAL' } as const +const emailConnector = { id: 'w3mEmail', type: 'EMAIL', provider: emailProvider } as const +const announcedConnector = { + id: 'announced', + type: 'ANNOUNCED', + info: { rdns: 'announced.io' } +} as const + +const syncDappDataSpy = vi.spyOn(emailProvider, 'syncDappData') +const syncThemeSpy = vi.spyOn(emailProvider, 'syncTheme') + +const mockDappData = { + metadata: { + description: 'Desc', + name: 'Name', + url: 'url.com', + icons: ['icon.png'] + }, + projectId: '1234', + sdkVersion: 'react-wagmi-4.0.13' as SdkVersion +} const metamaskConnector = { id: 'metamask', type: 'INJECTED', @@ -46,4 +83,48 @@ describe('ConnectorController', () => { expect(ConnectorController.getConnector(zerionConnector.id, '')).toBeUndefined() expect(ConnectorController.getConnector('unknown', '')).toBeUndefined() }) + + it('getEmailConnector() should not throw when email connector is not set', () => { + expect(ConnectorController.getEmailConnector()).toEqual(undefined) + }) + + it('should trigger corresponding sync methods when adding email connector', () => { + OptionsController.setMetadata(mockDappData.metadata) + OptionsController.setSdkVersion(mockDappData.sdkVersion) + OptionsController.setProjectId(mockDappData.projectId) + + ConnectorController.addConnector(emailConnector) + expect(ConnectorController.state.connectors).toEqual([ + walletConnectConnector, + externalConnector, + metamaskConnector, + emailConnector + ]) + + expect(syncDappDataSpy).toHaveBeenCalledWith(mockDappData) + expect(syncThemeSpy).toHaveBeenCalledWith({ themeMode: 'dark', themeVariables: {} }) + }) + + it('getEmailConnector() should return emailconnector when already added', () => { + expect(ConnectorController.getEmailConnector()).toEqual(emailConnector) + }) + + it('getAnnouncedConnectorRdns() should not throw when no announced connector is not set', () => { + expect(ConnectorController.getAnnouncedConnectorRdns()).toEqual([]) + }) + + it('getAnnouncedConnectorRdns() should return corresponding info array', () => { + ConnectorController.addConnector(announcedConnector) + expect(ConnectorController.getAnnouncedConnectorRdns()).toEqual(['announced.io']) + }) + + it('getConnnectors() should return all connectors', () => { + expect(ConnectorController.getConnectors()).toEqual([ + walletConnectConnector, + externalConnector, + metamaskConnector, + emailConnector, + announcedConnector + ]) + }) })