@reown/appkit-scaffold-ui
Version:
The full stack toolkit to build onchain app UX.
330 lines • 15.3 kB
JavaScript
import { expect, fixture, html } from '@open-wc/testing';
import { afterEach, beforeEach, describe, it, vi, expect as vitestExpect } from 'vitest';
import { AccountController, ChainController, RouterController, SwapController } from '@reown/appkit-controllers';
import { EventsController } from '@reown/appkit-controllers';
import { W3mSwapView } from '../../src/views/w3m-swap-view';
const mockToken = {
address: 'eip155:1:0x123',
symbol: 'TEST',
name: 'Test Token',
quantity: {
numeric: '100',
decimals: '18'
},
decimals: 18,
logoUri: 'https://example.com/icon.png',
price: 10,
value: 1000
};
const mockChainState = {
activeChain: 'eip155',
activeCaipNetwork: {
id: 1,
name: 'Ethereum',
chainNamespace: 'eip155',
caipNetworkId: 'eip155:1',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18
},
rpcUrls: {
default: {
http: ['https://ethereum.rpc.com']
}
}
},
activeCaipAddress: 'eip155:1:0x123456789abcdef123456789abcdef123456789a',
chains: new Map(),
universalAdapter: {
networkControllerClient: {
switchCaipNetwork: vi.fn(),
getApprovedCaipNetworksData: vi.fn()
},
connectionControllerClient: {
connectWalletConnect: vi.fn(),
connectExternal: vi.fn(),
reconnectExternal: vi.fn(),
checkInstalled: vi.fn(),
disconnect: vi.fn(),
signMessage: vi.fn(),
sendTransaction: vi.fn(),
estimateGas: vi.fn(),
parseUnits: vi.fn(),
formatUnits: vi.fn(),
writeContract: vi.fn(),
getEnsAddress: vi.fn(),
getEnsAvatar: vi.fn(),
grantPermissions: vi.fn(),
revokePermissions: vi.fn(),
getCapabilities: vi.fn(),
walletGetAssets: vi.fn(),
updateBalance: vi.fn()
}
},
noAdapters: false,
isSwitchingNamespace: false
};
describe('W3mSwapView', () => {
beforeEach(() => {
vi.spyOn(SwapController, 'state', 'get').mockReturnValue({
initializing: false,
initialized: true,
loadingQuote: false,
loadingPrices: false,
loadingTransaction: false,
loadingApprovalTransaction: false,
loadingBuildTransaction: false,
fetchError: false,
approvalTransaction: undefined,
swapTransaction: undefined,
transactionError: undefined,
sourceToken: mockToken,
sourceTokenAmount: '1',
sourceTokenPriceInUSD: 10,
toToken: { ...mockToken, symbol: 'USDT' },
toTokenAmount: '10',
toTokenPriceInUSD: 1,
networkPrice: '0',
networkBalanceInUSD: '0',
networkTokenSymbol: '',
inputError: undefined,
slippage: 0.5,
tokens: [
mockToken,
Object.assign({}, mockToken, { symbol: 'AAAA' }),
Object.assign({}, mockToken, { symbol: 'BBBB' })
],
suggestedTokens: undefined,
popularTokens: undefined,
foundTokens: undefined,
myTokensWithBalance: [mockToken],
tokensPriceMap: {},
gasFee: '0',
gasPriceInUSD: 2,
priceImpact: undefined,
maxSlippage: undefined,
providerFee: undefined
});
vi.spyOn(ChainController, 'state', 'get').mockReturnValue(mockChainState);
vi.spyOn(SwapController, 'initializeState').mockImplementation(async () => { });
vi.spyOn(SwapController, 'getNetworkTokenPrice').mockImplementation(async () => { });
vi.spyOn(SwapController, 'getMyTokensWithBalance').mockImplementation(async () => { });
vi.spyOn(SwapController, 'swapTokens').mockImplementation(async () => { });
vi.spyOn(SwapController, 'switchTokens').mockImplementation(() => { });
vi.spyOn(SwapController, 'resetState').mockImplementation(() => { });
vi.spyOn(SwapController, 'setSourceToken').mockImplementation(() => { });
vi.spyOn(SwapController, 'setToToken').mockImplementation(() => { });
vi.spyOn(SwapController, 'setSourceTokenAmount').mockImplementation(() => { });
vi.spyOn(RouterController, 'push').mockImplementation(() => { });
vi.spyOn(AccountController, 'state', 'get').mockReturnValue({
...AccountController.state,
caipAddress: 'eip155:1:0x123456789abcdef123456789abcdef123456789a',
address: '0x123456789abcdef123456789abcdef123456789a'
});
});
afterEach(() => {
vi.clearAllMocks();
});
it('should render initial state with token details', async () => {
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const swapInputs = element.shadowRoot?.querySelectorAll('w3m-swap-input');
expect(swapInputs?.length).to.equal(2);
const sourceInput = swapInputs?.[0];
expect(sourceInput?.value).to.equal('1');
expect(sourceInput?.target).to.equal('sourceToken');
const toInput = swapInputs?.[1];
expect(toInput?.value).to.equal('10');
expect(toInput?.target).to.equal('toToken');
});
it('should handle token switching', async () => {
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const switchTokensSpy = vi.spyOn(SwapController, 'switchTokens');
const switchButton = element.shadowRoot?.querySelector('.replace-tokens-button-container button');
switchButton?.click();
expect(switchTokensSpy.mock.calls.length).to.equal(1);
});
it('should show loading state when initializing', async () => {
vi.spyOn(SwapController, 'state', 'get').mockReturnValue({
...SwapController.state,
initialized: false
});
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const skeletons = element.shadowRoot?.querySelectorAll('w3m-swap-input-skeleton');
expect(skeletons?.length).to.equal(2);
});
it('should handle swap preview navigation', async () => {
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const routerPushSpy = vi.spyOn(RouterController, 'push');
const actionButton = element.shadowRoot?.querySelector('[data-testid="swap-action-button"]');
actionButton?.click();
expect(routerPushSpy.mock.calls[0]?.[0]).to.equal('SwapPreview');
});
it('should show error state in action button', async () => {
vi.spyOn(SwapController, 'state', 'get').mockReturnValue({
...SwapController.state,
inputError: 'Insufficient balance'
});
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const actionButton = element.shadowRoot?.querySelector('[data-testid="swap-action-button"]');
expect(actionButton?.textContent?.trim()).to.equal('Insufficient balance');
});
it('should handle loading states', async () => {
vi.spyOn(SwapController, 'state', 'get').mockReturnValue({
...SwapController.state,
loadingQuote: true
});
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const actionButton = element.shadowRoot?.querySelector('wui-button');
expect(actionButton?.loading).to.be.true;
});
it('should cleanup on disconnect', async () => {
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
const clearIntervalSpy = vi.spyOn(window, 'clearInterval');
element.disconnectedCallback();
expect(clearIntervalSpy.mock.calls.length).to.equal(1);
});
it('should retry swap when fetchError is true', async () => {
vi.spyOn(SwapController, 'state', 'get').mockReturnValue({
...SwapController.state,
fetchError: true,
sourceToken: { ...mockToken, symbol: 'ETH' },
toToken: { ...mockToken, symbol: 'USDT' },
sourceTokenAmount: '1.5',
toTokenAmount: '2000'
});
vi.spyOn(ChainController, 'state', 'get').mockReturnValue({
...ChainController.state,
activeCaipNetwork: mockChainState.activeCaipNetwork
});
const swapTokensSpy = vi.spyOn(SwapController, 'swapTokens');
const sendEventSpy = vi.spyOn(EventsController, 'sendEvent');
const routerPushSpy = vi.spyOn(RouterController, 'push');
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const actionButton = element.shadowRoot?.querySelector('[data-testid="swap-action-button"]');
await actionButton?.click();
expect(swapTokensSpy.mock.calls.length).to.equal(1);
expect(sendEventSpy.mock.calls.length).to.equal(1);
expect(sendEventSpy.mock.calls[0]?.[0]).to.deep.equal({
type: 'track',
event: 'INITIATE_SWAP',
properties: {
network: 'eip155:1',
swapFromToken: 'ETH',
swapToToken: 'USDT',
swapFromAmount: '1.5',
swapToAmount: '2000',
isSmartAccount: false
}
});
expect(routerPushSpy.mock.calls.length).to.equal(1);
expect(routerPushSpy.mock.calls[0]?.[0]).to.equal('SwapPreview');
});
it('should handle caipAddress change', async () => {
vi.mocked(SwapController.resetState).mockClear();
vi.mocked(SwapController.initializeState).mockClear();
const subscribeKeySpy = vi.spyOn(AccountController, 'subscribeKey');
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
expect(subscribeKeySpy.mock.calls.length).to.be.greaterThan(0);
const hasCaipAddressCall = subscribeKeySpy.mock.calls.some(call => call[0] === 'caipAddress');
expect(hasCaipAddressCall).to.be.true;
const caipAddressCallArgs = subscribeKeySpy.mock.calls
.filter(call => call[0] === 'caipAddress')
.map(call => call[1]);
const callbacks = caipAddressCallArgs.map((call) => call);
expect(callbacks.length > 0).to.be.true;
const currentCaipAddress = 'eip155:1:0x123456789abcdef123456789abcdef123456789a';
vi.mocked(SwapController.resetState).mockClear();
vi.mocked(SwapController.initializeState).mockClear();
for (const callback of callbacks) {
callback(currentCaipAddress);
}
expect(vi.mocked(SwapController.resetState).mock.calls.length).to.equal(0);
expect(vi.mocked(SwapController.initializeState).mock.calls.length).to.equal(0);
vi.mocked(SwapController.resetState).mockClear();
vi.mocked(SwapController.initializeState).mockClear();
const newCaipAddress = 'eip155:1:0xabcdef123456789abcdef123456789abcdef1234';
callbacks[0](newCaipAddress);
expect(vi.mocked(SwapController.resetState).mock.calls.length).to.equal(1);
expect(vi.mocked(SwapController.initializeState).mock.calls.length).to.equal(0);
vi.mocked(SwapController.resetState).mockClear();
vi.mocked(SwapController.initializeState).mockClear();
callbacks[1](newCaipAddress);
expect(vi.mocked(SwapController.resetState).mock.calls.length).to.equal(0);
expect(vi.mocked(SwapController.initializeState).mock.calls.length).to.equal(0);
});
it('should set initial state with preselected tokens', async () => {
const element = await fixture(html `<w3m-swap-view
initialParams='{"fromToken": "AAAA","toToken":"BBBB","amount":"321.123"}'
></w3m-swap-view>`);
await element.updateComplete;
vitestExpect(SwapController.setSourceToken).toHaveBeenCalledWith(vitestExpect.objectContaining({
symbol: 'AAAA'
}));
vitestExpect(SwapController.setToToken).toHaveBeenCalledWith(vitestExpect.objectContaining({
symbol: 'BBBB'
}));
vitestExpect(SwapController.setSourceTokenAmount).toHaveBeenCalledWith('321.123');
});
it('should call unsubscribe when unmounted', async () => {
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const unsubscribeSpies = element['unsubscribe'].map(unsubscribe => {
return vi.fn(unsubscribe);
});
element['unsubscribe'] = unsubscribeSpies;
element.disconnectedCallback();
unsubscribeSpies.forEach(spy => {
expect(spy.mock.calls.length).to.equal(1);
});
});
it('should still react to network and address change events after being unmounted', async () => {
const resetStateSpy = vi.spyOn(SwapController, 'resetState');
const initializeStateSpy = vi.spyOn(SwapController, 'initializeState');
const subscribeKeySpy = vi.spyOn(ChainController, 'subscribeKey');
const accountSubscribeKeySpy = vi.spyOn(AccountController, 'subscribeKey');
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const chainCallbacks = subscribeKeySpy.mock.calls
.filter(call => call[0] === 'activeCaipNetwork')
.map(call => call[1]);
const accountCallbacks = accountSubscribeKeySpy.mock.calls
.filter(call => call[0] === 'caipAddress')
.map(call => call[1]);
vitestExpect(chainCallbacks.length).toBe(2);
vitestExpect(accountCallbacks.length).toBe(2);
element.disconnectedCallback();
resetStateSpy.mockClear();
initializeStateSpy.mockClear();
const mockNetwork = {
id: 2,
chainNamespace: 'eip155',
caipNetworkId: 'eip155:2'
};
chainCallbacks[0]?.(mockNetwork);
accountCallbacks[0]?.('eip155:2:0x456');
vitestExpect(resetStateSpy).toHaveBeenCalled();
vitestExpect(initializeStateSpy).not.toHaveBeenCalled();
});
it('should call handleChangeAmount with max value when setting max value', async () => {
vi.useFakeTimers();
const swapTokensSpy = vi.spyOn(SwapController, 'swapTokens');
const element = await fixture(html `<w3m-swap-view></w3m-swap-view>`);
await element.updateComplete;
const handleChangeAmountSpy = vi.spyOn(element, 'handleChangeAmount');
element['onSetMaxValue']('sourceToken', '100');
vitestExpect(handleChangeAmountSpy).toHaveBeenCalledWith('sourceToken', '100.00000000000000000000');
await vi.advanceTimersByTime(200);
vitestExpect(swapTokensSpy).toHaveBeenCalledOnce();
vi.useRealTimers();
});
});
//# sourceMappingURL=w3m-swap-view.test.js.map