From 290941648726dad09813c1836f791d3471d5f0c1 Mon Sep 17 00:00:00 2001 From: Vladyslav Yatsenko <52505649+yivlad@users.noreply.github.com> Date: Mon, 20 Jun 2022 15:41:41 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9B=BA=EF=B8=8F=20Add=20tests=20with=20refre?= =?UTF-8?q?sh=20field=20for=20useCall=20hook=20(#832)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/chilly-wasps-stare.md | 5 + packages/core/package.json | 7 +- .../core/src/constants/abi/BlockNumber.json | 19 ++++ packages/core/src/constants/abi/index.ts | 3 + packages/core/src/hooks/useCall.test.tsx | 101 ++++++++++++++++++ .../chainState/common/callsReducer.ts | 2 +- .../common/chainStateReducer.test.ts | 8 +- .../chainState/common/chainStateReducer.ts | 21 ++-- .../src/testing/utils/getResultProperty.ts | 11 ++ packages/core/src/testing/utils/index.ts | 1 + pnpm-lock.yaml | 2 + 11 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 .changeset/chilly-wasps-stare.md create mode 100644 packages/core/src/constants/abi/BlockNumber.json create mode 100644 packages/core/src/testing/utils/getResultProperty.ts diff --git a/.changeset/chilly-wasps-stare.md b/.changeset/chilly-wasps-stare.md new file mode 100644 index 000000000..347b1ee1e --- /dev/null +++ b/.changeset/chilly-wasps-stare.md @@ -0,0 +1,5 @@ +--- +'@usedapp/core': patch +--- + +Fix refresh field in QueryOptions diff --git a/packages/core/package.json b/packages/core/package.json index 85b4f75da..51c67f290 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,8 +21,8 @@ "nanoid": "3.1.22" }, "peerDependencies": { - "react": "*", - "ethers": "*" + "ethers": "*", + "react": "*" }, "devDependencies": { "@ethereum-waffle/provider": "4.0.0-alpha.0", @@ -53,7 +53,8 @@ "react": "^17.0.1", "solc": "^0.8.12", "typechain": "^7.0.0", - "typescript": "^4.6.2" + "typescript": "^4.6.2", + "wait-for-expect": "^3.0.2" }, "scripts": { "build": "yarn run build:esm && yarn run build:cjs", diff --git a/packages/core/src/constants/abi/BlockNumber.json b/packages/core/src/constants/abi/BlockNumber.json new file mode 100644 index 000000000..120276b18 --- /dev/null +++ b/packages/core/src/constants/abi/BlockNumber.json @@ -0,0 +1,19 @@ +{ + "contractName": "BlockNumber", + "abi": [ + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5060b58061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806342cbb15c14602d575b600080fd5b60336047565b604051603e9190605c565b60405180910390f35b600043905090565b6056816075565b82525050565b6000602082019050606f6000830184604f565b92915050565b600081905091905056fea26469706673582212204740e85f94e6d1887dda3a56af161b21a85b8ddb19dab72de0279a74db1a727064736f6c63430008070033" +} diff --git a/packages/core/src/constants/abi/index.ts b/packages/core/src/constants/abi/index.ts index a4d3595b0..5ca904d81 100644 --- a/packages/core/src/constants/abi/index.ts +++ b/packages/core/src/constants/abi/index.ts @@ -3,6 +3,7 @@ import MultiCall from './MultiCall.json' import MultiCall2 from './MultiCall2.json' import ERC20 from './ERC20.json' import ERC20Mock from './ERC20Mock.json' +import BlockNumberContract from './BlockNumber.json' const Interface = utils.Interface @@ -21,3 +22,5 @@ export { ERC20, ERC20Interface } const ERC20MockInterface = new Interface(ERC20Mock.abi) export { ERC20Mock, ERC20MockInterface } + +export { BlockNumberContract } diff --git a/packages/core/src/hooks/useCall.test.tsx b/packages/core/src/hooks/useCall.test.tsx index 8548d6e87..b18907cc7 100644 --- a/packages/core/src/hooks/useCall.test.tsx +++ b/packages/core/src/hooks/useCall.test.tsx @@ -8,9 +8,13 @@ import { MOCK_TOKEN_INITIAL_BALANCE, SECOND_TEST_CHAIN_ID, SECOND_MOCK_TOKEN_INITIAL_BALANCE, + getResultPropery, } from '../testing' import { ChainId } from '../constants/chainId' import { BigNumber } from 'ethers' +import { deployContract } from 'ethereum-waffle' +import { BlockNumberContract } from '../constants' +import waitForExpect from 'wait-for-expect' describe('useCall', () => { const mockProvider = new MockProvider() @@ -19,10 +23,14 @@ describe('useCall', () => { const [secondDeployer] = secondMockProvider.getWallets() let token: Contract let secondToken: Contract + let blockNumberContract: Contract + let secondBlockNumberContract: Contract beforeEach(async () => { token = await deployMockToken(deployer) secondToken = await deployMockToken(secondDeployer, SECOND_MOCK_TOKEN_INITIAL_BALANCE) + blockNumberContract = await deployContract(deployer, BlockNumberContract) + secondBlockNumberContract = await deployContract(deployer, BlockNumberContract) }) it('initial test balance to be correct', async () => { @@ -74,4 +82,97 @@ describe('useCall', () => { expect(result.error).to.be.undefined expect(result.current?.value[0]).to.eq(endValue) } + + it('Properly handles two calls', async () => { + const { result, waitForCurrent, mineBlock } = await renderWeb3Hook( + () => { + const balance = useCall({ + contract: token, + method: 'balanceOf', + args: [deployer.address], + }) + const block = useCall({ + contract: blockNumberContract, + method: 'getBlockNumber', + args: [], + }) + + return { balance, block } + }, + { + mockProvider, + } + ) + + const blockNumber = await mockProvider.getBlockNumber() + + await waitForCurrent(({ balance, block }) => !!(balance && block)) + expect(result.error).to.be.undefined + expect(getResultPropery(result, 'balance')).to.eq(MOCK_TOKEN_INITIAL_BALANCE) + expect(getResultPropery(result, 'block')).to.eq(blockNumber) + + await mineBlock() + + await waitForExpect(() => { + expect(getResultPropery(result, 'balance')).to.eq(MOCK_TOKEN_INITIAL_BALANCE) + expect(getResultPropery(result, 'block')).to.eq(blockNumber + 1) + }) + }) + + it('Properly handles refresh per block', async () => { + const { result, waitForCurrent, mineBlock } = await renderWeb3Hook( + () => { + const block1 = useCall({ + contract: blockNumberContract, + method: 'getBlockNumber', + args: [], + }) + const block2 = useCall( + { + // TODO: add similar test but with the same contract (blockNumberContract). It would currently fail + contract: secondBlockNumberContract, + method: 'getBlockNumber', + args: [], + }, + { + refresh: 2, + } + ) + + return { block1, block2 } + }, + { + mockProvider, + } + ) + + const blockNumber = await mockProvider.getBlockNumber() + + await waitForCurrent(({ block1, block2 }) => !!(block1 && block2)) + expect(result.error).to.be.undefined + expect(getResultPropery(result, 'block1')).to.eq(blockNumber) + expect(getResultPropery(result, 'block2')).to.eq(blockNumber) + + await mineBlock() + + await waitForCurrent(({ block1 }) => block1 !== undefined && block1.value[0].toNumber() === blockNumber + 1) + expect(getResultPropery(result, 'block1')).to.eq(blockNumber + 1) + expect(getResultPropery(result, 'block2')).to.eq(blockNumber) + + await mineBlock() + + await waitForExpect(() => { + expect(getResultPropery(result, 'block1')).to.eq(blockNumber + 2) + expect(getResultPropery(result, 'block2')).to.eq(blockNumber + 2) + }) + + for (let i = 0; i < 3; i++) { + await mineBlock() + } + + await waitForExpect(() => { + expect(getResultPropery(result, 'block1')).to.eq(blockNumber + 5) + expect(getResultPropery(result, 'block2')).to.eq(blockNumber + 5) + }) + }) }) diff --git a/packages/core/src/providers/chainState/common/callsReducer.ts b/packages/core/src/providers/chainState/common/callsReducer.ts index af2935f78..24ded14d6 100644 --- a/packages/core/src/providers/chainState/common/callsReducer.ts +++ b/packages/core/src/providers/chainState/common/callsReducer.ts @@ -58,7 +58,7 @@ export function callsReducer(state: RawCall[] = [], action: Action) { } const blockNumber = action.blockNumber if (call.refreshPerBlocks && call.lastUpdatedBlockNumber) { - return call.lastUpdatedBlockNumber + call.refreshPerBlocks === blockNumber + return call.lastUpdatedBlockNumber + call.refreshPerBlocks <= blockNumber ? { ...call, lastUpdatedBlockNumber: blockNumber, diff --git a/packages/core/src/providers/chainState/common/chainStateReducer.test.ts b/packages/core/src/providers/chainState/common/chainStateReducer.test.ts index dd56cc214..e1523e5e9 100644 --- a/packages/core/src/providers/chainState/common/chainStateReducer.test.ts +++ b/packages/core/src/providers/chainState/common/chainStateReducer.test.ts @@ -37,7 +37,7 @@ describe('chainStateReducer', () => { expect(result).to.deep.equal(state) }) - it('overwrites with updates from newer blocks', () => { + it('merges with updates from newer blocks', () => { const state: State = { [Mainnet.chainId]: { blockNumber: 1234, @@ -68,6 +68,12 @@ describe('chainStateReducer', () => { [Mainnet.chainId]: { blockNumber: 1235, state: { + [ADDRESS_A]: { + '0xdead': { + value: '0xbeef', + success: true, + }, + }, [ADDRESS_B]: { '0xabcd': { value: '0x5678', diff --git a/packages/core/src/providers/chainState/common/chainStateReducer.ts b/packages/core/src/providers/chainState/common/chainStateReducer.ts index 5e7701b41..d9efdbd9d 100644 --- a/packages/core/src/providers/chainState/common/chainStateReducer.ts +++ b/packages/core/src/providers/chainState/common/chainStateReducer.ts @@ -40,18 +40,15 @@ export function chainStateReducer(state: State = {}, action: ChainStateAction) { if (!current || action.blockNumber >= current) { if (action.type === 'FETCH_SUCCESS') { let newState = action.state - if (action.blockNumber === current) { - // merge with existing state to prevent requests coming out of order - // from overwriting the data - const oldState = state[action.chainId]?.state ?? {} - for (const [address, entries] of Object.entries(oldState)) { - newState = { - ...newState, - [address]: { - ...entries, - ...newState[address], - }, - } + // merge with existing state + const oldState = state[action.chainId]?.state ?? {} + for (const [address, entries] of Object.entries(oldState)) { + newState = { + ...newState, + [address]: { + ...entries, + ...newState[address], + }, } } return { diff --git a/packages/core/src/testing/utils/getResultProperty.ts b/packages/core/src/testing/utils/getResultProperty.ts new file mode 100644 index 000000000..3eb8ae79e --- /dev/null +++ b/packages/core/src/testing/utils/getResultProperty.ts @@ -0,0 +1,11 @@ +import { RenderResult } from '@testing-library/react-hooks' +import { Contract } from 'ethers' +import { CallResult } from '../../helpers' + +export type HookResult = { + [key: string]: CallResult +} + +export const getResultPropery = (result: RenderResult, property: keyof T) => { + return result.current?.[property]?.value[0] +} diff --git a/packages/core/src/testing/utils/index.ts b/packages/core/src/testing/utils/index.ts index d2d81936b..7537cb2f8 100644 --- a/packages/core/src/testing/utils/index.ts +++ b/packages/core/src/testing/utils/index.ts @@ -7,3 +7,4 @@ export * from './waitUntil' export * from './waitUtils' export * from './deployMockToken' export * from './setupTestingConfig' +export * from './getResultProperty' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66db8f434..365007c59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,6 +102,7 @@ importers: solc: ^0.8.12 typechain: ^7.0.0 typescript: ^4.6.2 + wait-for-expect: ^3.0.2 dependencies: '@metamask/detect-provider': 1.2.0 '@uniswap/token-lists': 1.0.0-beta.27 @@ -139,6 +140,7 @@ importers: solc: 0.8.12 typechain: 7.0.1_typescript@4.6.2 typescript: 4.6.2 + wait-for-expect: 3.0.2 packages/docs: specifiers: