Skip to content

Commit

Permalink
chore: market page tests and jest environment update (#2529)
Browse files Browse the repository at this point in the history
* Update market tests

* Add Github action workflow for jest tests

* Update tests CI workflow

* Add pnpm i step to workflow

* Clean up

* Fix sdk mock

* Update Jest config

* Fix warnings

* Update tests CI config

* Update market test and add codecov action

* Rename InfoBox textValueSpan prop to textValueIcon

* Remove unused type

* Correct market closed test

* Update codecov workflow

* Temp comment out test

* fix: lint warnings

* fix: jest workflow

* fix: sentry workflow

* Mock rainbowkit for all tests

* Add codecov yml to enforce coverage

---------

Co-authored-by: Ralf <platschi@posteo.org>
  • Loading branch information
avclarke and platschi authored Jun 30, 2023
1 parent 2562318 commit ddc82c2
Show file tree
Hide file tree
Showing 50 changed files with 830 additions and 284 deletions.
21 changes: 21 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
coverage:
precision: 2
round: down
range: '50...85' # Will lift this number as coverage increases

status:
project:
default:
# basic
target: auto
threshold: 1%
base: auto
patch:
default:
# basic
target: 80%
threshold: 1%
base: auto

ignore:
- 'packages/sdk/.*' # Waiting for sdk tests
42 changes: 42 additions & 0 deletions .github/workflows/jest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: 'Jest'
on:
push:
branches:
- master
pull_request:

jobs:
sentry_release:
name: Jest tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 16.x

- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: 8
run_install: false

- name: Install dependencies
run: pnpm i

- name: Run tests
run: cd packages/app && pnpm run test:jest
env:
NEXT_PUBLIC_WALLETCONNECT_V2_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_V2_ID }}

- name: Upload coverage to codecov.io
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2 changes: 2 additions & 0 deletions .github/workflows/sentry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:

- name: Build code
run: pnpm i && cd packages/app && pnpm build
env:
NEXT_PUBLIC_WALLETCONNECT_V2_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_V2_ID }}

- name: Create Sentry release
uses: getsentry/action-release@v1
Expand Down
57 changes: 0 additions & 57 deletions packages/app/__tests__/pages/market.test.tsx

This file was deleted.

27 changes: 16 additions & 11 deletions packages/app/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
const nextJest = require('next/jest');
const nextJest = require('next/jest')

const createJestConfig = nextJest({ dir: './' });
const createJestConfig = nextJest({ dir: './' })

const customJestConfig = {
roots: ['<rootDir>'],
modulePaths: ['<rootDir>'],
moduleDirectories: ['node_modules'],
roots: ['<rootDir>', 'src'],
modulePaths: ['<rootDir>', 'src'],
moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: {
'@kwenta/sdk/(.+)$': '<rootDir>/../sdk/dist/$1',
'@kwenta/sdk': '<rootDir>/../sdk/dist/index.js',
},
globalSetup: './testing/unit/setup/global.js',
setupFilesAfterEnv: ['./testing/unit/setup/setup.js'],
testEnvironment: 'jest-environment-jsdom',
transform: {
'^.+\\.(svg)$': `jest-transformer-svg`,
},
};
}

const getCustomConfig = async () => {
// Delete next js module name mapper transform and use above svg
// transformer to avoid errors with svg and styled components
const config = await createJestConfig(customJestConfig)();
delete config['moduleNameMapper']['^.+\\.(svg)$'];
return config;
};
const config = await createJestConfig(customJestConfig)()
delete config['moduleNameMapper']['^.+\\.(svg)$']
config.transformIgnorePatterns = []
return config
}

module.exports = getCustomConfig();
module.exports = getCustomConfig()
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"export": "next export",
"check-types": "tsc --noEmit",
"jest-preview": "jest-preview",
"test:unit": "jest --coverage --detectOpenHandles",
"test:jest": "jest --coverage --detectOpenHandles",
"test:e2e": "start-server-and-test 'pnpm build && pnpm start' http-get://localhost:3000 'synpress run'",
"test:e2e:only:tests": "synpress run",
"test:e2e:open:testrunner": "synpress open",
Expand Down
196 changes: 196 additions & 0 deletions packages/app/src/__tests__/pages/market.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { FuturesMarket } from '@kwenta/sdk/dist/types'
import { wei } from '@synthetixio/wei'
import { fireEvent, render, waitFor } from '@testing-library/react'
import { ReactNode } from 'react'

import { fetchMarkets } from 'state/futures/actions'

import { mockResizeObserver } from '../../../testing/unit/mocks/app'
import { PRELOADED_STATE } from '../../../testing/unit/mocks/data/app'
import {
mockSmartMarginAccount,
preloadedStateWithSmartMarginAccount,
SDK_MARKETS,
} from '../../../testing/unit/mocks/data/futures'
import { mockUseWindowSize } from '../../../testing/unit/mocks/hooks'
import mockConnector from '../../../testing/unit/mocks/mockConnector'
import MockProviders from '../../../testing/unit/mocks/MockProviders'
import { mockReactQuery } from '../../../testing/unit/mocks/queries'
import Market from '../../pages/market'
import { selectTradePreview } from '../../state/futures/selectors'
import sdk from '../../state/sdk'
import { setupStore } from '../../state/store'

jest.mock('../../state/sdk')

jest.mock('../../queries/futures/useGetFuturesTrades', () => {
return jest.fn(() => ({
data: [],
isLoading: false,
fetchNextPage: () => {},
}))
})

jest.mock('../../components/Media', () => ({
...jest.requireActual('../../components/Media'),
DesktopOnlyView: ({ children }: { children: ReactNode }) => <div>{children}</div>,
MobileOnlyView: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}))

describe('Futures market page - smart margin', () => {
beforeAll(() => {
jest.setTimeout(60000)
mockUseWindowSize()
mockReactQuery()
mockResizeObserver()
mockConnector()
})

test('Calculates correct fees from trade preview', async () => {
const { findByTestId, findByText } = render(
<MockProviders
route="market/?accountType=cross_margin&asset=sETH"
preloadedState={PRELOADED_STATE}
>
<Market />
</MockProviders>
)

const marginInput = await findByTestId('set-order-margin-susd-desktop')
fireEvent.change(marginInput, { target: { value: '100' } })

const sizeInput = await findByTestId('set-order-size-amount-susd-desktop')
fireEvent.change(sizeInput, { target: { value: '1000' } })

const fees = await findByText('$1.69')
expect(fees).toBeTruthy()
})

test('Submits LONG order with correct desired fill price', async () => {
const store = setupStore(preloadedStateWithSmartMarginAccount())
const { findByTestId, findByText } = render(
<MockProviders route="market/?accountType=cross_margin&asset=sETH" store={store}>
<Market />
</MockProviders>
)

const marginInput = await findByTestId('set-order-margin-susd-desktop')
fireEvent.change(marginInput, { target: { value: '100' } })

const sizeInput = await findByTestId('set-order-size-amount-susd-desktop')
fireEvent.change(sizeInput, { target: { value: '1000' } })

const fees = await findByText('$1.69')
expect(fees).toBeTruthy()

const submitButton = await findByTestId('trade-panel-submit-button')
fireEvent.click(submitButton)

const confirmButton = await findByTestId('trade-confirm-order-button')
fireEvent.click(confirmButton)

// Preview generated fill price displayed in confirmation view
const fillPrice = await findByText('$1,847.76')
expect(fillPrice).toBeTruthy()

// Desired fill price is higher than fill price by 1%
// (as a long order the price is worse to account for slippage in delayed order)
expect(selectTradePreview(store.getState())?.desiredFillPrice.toString()).toBe(
'1866.234411491951332934'
)
})

test('Submits SHORT order with correct desired fill price', async () => {
const store = setupStore(preloadedStateWithSmartMarginAccount())
const { findByTestId, findByText } = render(
<MockProviders route="market/?accountType=cross_margin&asset=sETH" store={store}>
<Market />
</MockProviders>
)

const shortToggle = await findByTestId('position-side-short-button')
fireEvent.click(shortToggle)

const marginInput = await findByTestId('set-order-margin-susd-desktop')
fireEvent.change(marginInput, { target: { value: '100' } })

const sizeInput = await findByTestId('set-order-size-amount-susd-desktop')
fireEvent.change(sizeInput, { target: { value: '1000' } })

const fees = await findByText('$1.69')
expect(fees).toBeTruthy()

const submitButton = await findByTestId('trade-panel-submit-button')
fireEvent.click(submitButton)

const confirmButton = await findByTestId('trade-confirm-order-button')
fireEvent.click(confirmButton)

// Preview generated fill price displayed in confirmation view
const fillPrice = await findByText('$1,847.76')
expect(fillPrice).toBeTruthy()

// Desired fill price is lower than fill price by 1%
// (as a short order the price is worse to account for slippage in delayed order)
expect(selectTradePreview(store.getState())?.desiredFillPrice.toString()).toBe(
'1829.279274630724573866'
)
})

test('Displays error when trade exceeds max OI', async () => {
// Update the mock to return some different data
sdk.futures.getMarkets = () =>
Promise.resolve([{ ...SDK_MARKETS[1], marketLimitUsd: wei(100000) } as FuturesMarket])

const store = setupStore(
preloadedStateWithSmartMarginAccount(mockSmartMarginAccount('1000000'))
)
const { findByTestId, findByText } = render(
<MockProviders route="market/?accountType=cross_margin&asset=sETH" store={store}>
<Market />
</MockProviders>
)

const marginInput = await findByTestId('set-order-margin-susd-desktop')
fireEvent.change(marginInput, { target: { value: '100000' } })

const sizeInput = await findByTestId('set-order-size-amount-susd-desktop')
fireEvent.change(sizeInput, { target: { value: '1000000' } })

// OI limit warning displayed
const fillPrice = await findByText('Open interest limit exceeded')
expect(fillPrice).toBeTruthy()
})

test('Trade panel is disabled when market is closed', async () => {
sdk.futures.getMarkets = () => Promise.resolve([...SDK_MARKETS] as FuturesMarket[])
const store = setupStore(preloadedStateWithSmartMarginAccount())
const { findByTestId, findByText } = render(
<MockProviders route="market/?accountType=cross_margin&asset=sETH" store={store}>
<Market />
</MockProviders>
)

const marginInput = await findByTestId('set-order-margin-susd-desktop')
fireEvent.change(marginInput, { target: { value: '100' } })

const sizeInput = await findByTestId('set-order-size-amount-susd-desktop')
fireEvent.change(sizeInput, { target: { value: '1000' } })

const fees = await findByText('$1.69')
expect(fees).toBeTruthy()

const submitButton = await findByTestId('trade-panel-submit-button')
expect(submitButton).toBeEnabled()

sdk.futures.getMarkets = () =>
Promise.resolve([{ ...SDK_MARKETS[1], isSuspended: true } as FuturesMarket])

waitFor(() => store.dispatch(fetchMarkets()))

const message = await findByText('Market suspended')
expect(message).toBeTruthy()

expect(submitButton).toBeDisabled()
})
})
Loading

0 comments on commit ddc82c2

Please sign in to comment.