// Mock @uniswap/v4-sdk before any imports to prevent ethers v5 constants error at module load time. // { virtual: true } prevents Jest from resolving the actual module path (which triggers ethers v5 compat code). jest.mock( '@uniswap/v4-sdk', () => ({ Pool: { getPoolId: jest.fn(), }, }), { virtual: true }, ); // Mock the missing StateView.json file to return an undefined abi jest.mock( '../../../../artifacts/abis/StateView.json', () => ({ abi: undefined, }), { virtual: true }, ); // Mock the entire 'ethers' module to control its exports for Contract and Provider. jest.mock('ethers', () => ({ Contract: jest.fn((_address: string, abi: any, _provider: any) => { if (abi === undefined || abi === null) { throw new Error( 'ABI cannot be undefined when creating a Contract instance.', ); } return { getSlot0: jest .fn() .mockResolvedValue({ sqrtPriceX96: BigInt(1), tick: 1 }), }; }), Provider: jest.fn(() => ({ getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), getBlockNumber: jest.fn().mockResolvedValue(123), })), })); import { UniswapV4QuoteEngine, UniswapV4PoolKey, } from '../../src/engines/uniswap-v4-quote-engine'; describe('UniswapV4QuoteEngine', () => { describe('getCurrentPoolState', () => { // Use 'any' for mockQuoter and mockProvider to fully bypass external type resolution issues, // as their behavior is controlled by the ethers mock and specific test expectations. let mockQuoter: any; let mockProvider: any; let mockPoolKey: UniswapV4PoolKey; const mockStateViewAddress = '0xMockStateViewAddress'; const mockChainId = 1; let MockEthersContract: jest.Mock; let MockEthersProviderConstructor: jest.Mock; let MockQuoterV2Constructor: jest.Mock; // Still need this for the constructor, but won't be explicitly imported type let MockUniswapV4PoolObject: { getPoolId: jest.Mock }; beforeEach(() => { jest.clearAllMocks(); // Retrieve the mocked constructors from the 'ethers' module MockEthersContract = jest.requireMock('ethers').Contract; MockEthersProviderConstructor = jest.requireMock('ethers').Provider; MockQuoterV2Constructor = jest.fn(() => ({ // Mock QuoterV2 directly here getFunction: jest.fn().mockReturnValue({ staticCall: jest.fn(), }), })) as jest.Mock; MockUniswapV4PoolObject = jest.requireMock('@uniswap/v4-sdk').Pool; // Re-configure the mock implementation for Contract for each test MockEthersContract.mockImplementation( (_address: string, abi: any, _provider: any) => { if (abi === undefined || abi === null) { throw new Error( 'ABI cannot be undefined when creating a Contract instance.', ); } return { getSlot0: jest .fn() .mockResolvedValue({ sqrtPriceX96: BigInt(1), tick: 1 }), }; }, ); // Re-configure the mock implementation for Provider for each test MockEthersProviderConstructor.mockImplementation(() => ({ getNetwork: jest.fn().mockResolvedValue({ chainId: mockChainId }), // Fixed chainId directly in mock getBlockNumber: jest.fn().mockResolvedValue(123), })); // Re-configure the mock implementation for Pool.getPoolId MockUniswapV4PoolObject.getPoolId.mockReturnValue('0xMockPoolId'); // Instantiate the mocked objects mockQuoter = new MockQuoterV2Constructor(); mockProvider = new MockEthersProviderConstructor(); mockPoolKey = { currency0: '0xCurrency0', currency1: '0xCurrency1', fee: 500, tickSpacing: 60, hooks: '0xHooks', }; }); it('should throw an error if StateViewAbi is undefined when creating Contract for getCurrentPoolState', async () => { const engine = new UniswapV4QuoteEngine( mockQuoter, mockPoolKey, mockChainId, mockStateViewAddress, mockProvider, ); // This expectation directly verifies the "Red" phase: // UniswapV4QuoteEngine will attempt to create an ethers.Contract with StateViewAbi. // If StateViewAbi is undefined (which it will be due to the missing file and the global ignore in the original source), // our mocked ethers.Contract will throw the specified error. await expect(engine.getCurrentPoolState()).rejects.toThrow( 'ABI cannot be undefined when creating a Contract instance.', ); }); }); });