UNPKG

@tevm/test-matchers

Version:

Vite test matchers for Tevm or EVM-related testing in TypeScript.

101 lines (89 loc) 3.35 kB
import { AbiItem } from 'ox' import type { Abi, AbiEvent, ContractEventName, Hex } from 'viem' import { decodeEventLog, encodeEventTopics, getAddress, isHex } from 'viem' import type { MatcherResult } from '../../chainable/types.js' import type { ContainsContractAbi, ContainsTransactionLogs } from '../../common/types.js' import type { ToEmitState } from './types.js' // Vitest-style matcher function export const toEmit = async < TAbi extends Abi | undefined = Abi | undefined, TEventName extends TAbi extends Abi ? ContractEventName<TAbi> : never = TAbi extends Abi ? ContractEventName<TAbi> : never, TContract extends TAbi extends Abi ? ContainsContractAbi<TAbi> : never = TAbi extends Abi ? ContainsContractAbi<TAbi> : never, >( received: ContainsTransactionLogs | Promise<ContainsTransactionLogs>, contractOrEventIdentifier: TContract | Hex | string, eventName?: TEventName, ): Promise<MatcherResult<ToEmitState>> => { const logs = (await received).logs // Handle event signature or selector if (typeof contractOrEventIdentifier === 'string') { const eventSelector = isHex(contractOrEventIdentifier) ? contractOrEventIdentifier : AbiItem.getSelector(contractOrEventIdentifier) const matchedLogs = logs.filter((log) => log.topics[0]?.startsWith(eventSelector)) const pass = matchedLogs.length > 0 return { pass, actual: logs.map((log) => log.topics[0]).filter(Boolean), expected: `logs containing event selector ${eventSelector}`, message: () => pass ? `Expected event ${contractOrEventIdentifier} not to be emitted` : `Expected event ${contractOrEventIdentifier} to be emitted`, state: { matchedLogs, eventIdentifier: contractOrEventIdentifier, }, } } // Contract + event name case if (!eventName) throw new Error('You need to provide an event name as a third argument') const contract = contractOrEventIdentifier const eventAbi = contract.abi.find((item): item is AbiEvent => item.type === 'event' && item.name === eventName) if (!eventAbi) throw new Error( `Event ${eventName} not found in contract ABI. Please make sure you've compiled the latest version before running the test.`, ) const eventTopic = encodeEventTopics({ abi: contract.abi, eventName: eventName, })[0] const matchedLogs = logs.filter((log) => { const topicMatches = log.topics[0] === eventTopic const addressMatches = contract?.address ? getAddress(log.address) === getAddress(contract.address) : true return topicMatches && addressMatches }) const pass = matchedLogs.length > 0 // Create meaningful actual/expected values for the diff const actualEvents = logs .filter((log) => (contract?.address ? getAddress(log.address) === getAddress(contract.address) : true)) .map((log) => { try { const decoded = decodeEventLog({ abi: contract.abi, data: log.data, topics: log.topics, }) return `${decoded.eventName}(${decoded.args ? Object.values(decoded.args).join(', ') : ''})` } catch { return `UnknownEvent(${log.topics[0]})` } }) return { pass, actual: actualEvents, expected: `event "${eventName}" to be emitted`, message: () => pass ? `Expected event ${eventName} not to be emitted` : `Expected event ${eventName} to be emitted`, state: { matchedLogs, contract, eventName, eventAbi, }, } }