* Add some basic integration tests and CI in GitHub

* Add push trigger

* Add badge for tests to readme

* Unit tests

* Just use the development infura URL in the tests

* Remove unused webpack config

* Make integration test run on the same port as yarn start

* make test a little bit better

* rename describe
This commit is contained in:
Moody Salem 2020-05-08 11:38:33 -04:00 committed by GitHub
parent a1a9d9f041
commit 28b24036c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1120 additions and 84 deletions

4
.env Normal file

@ -0,0 +1,4 @@
REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/b8800ce81b8c451698081d269b86692b"
REACT_APP_PORTIS_ID=""
REACT_APP_FORTMATIC_KEY=""

34
.github/workflows/tests.yaml vendored Normal file

@ -0,0 +1,34 @@
name: Tests
on:
push:
branches:
- v2
pull_request:
branches:
- v2
jobs:
integration-tests:
name: Integration tests
runs-on: ubuntu-16.04
steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12'
- run: yarn
- run: yarn integration-test
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12'
- run: yarn
- run: yarn test
# todo: add job for typescript linting

4
.gitignore vendored

@ -11,7 +11,6 @@
# misc # misc
.DS_Store .DS_Store
.env
.env.local .env.local
.env.development.local .env.development.local
.env.test.local .env.test.local
@ -29,3 +28,6 @@ notes.txt
.vscode/ .vscode/
package-lock.json package-lock.json
cypress/videos
cypress/screenshots

@ -1,13 +0,0 @@
branches:
except:
- master
language: node_js
node_js:
- '10'
cache:
directories:
- node_modules
install: yarn
script:
- yarn check:all
- yarn build

@ -1,7 +1,7 @@
# Uniswap Frontend # Uniswap Frontend
[![Netlify Status](https://api.netlify.com/api/v1/badges/fa110555-b3c7-4eeb-b840-88a835009c62/deploy-status)](https://app.netlify.com/sites/uniswap/deploys) [![Netlify Status](https://api.netlify.com/api/v1/badges/fa110555-b3c7-4eeb-b840-88a835009c62/deploy-status)](https://app.netlify.com/sites/uniswap/deploys)
[![Build Status](https://travis-ci.org/Uniswap/uniswap-frontend.svg)](https://travis-ci.org/Uniswap/uniswap-frontend) [![Tests](https://github.com/Uniswap/uniswap-frontend/workflows/Tests/badge.svg?branch=v2)](https://github.com/Uniswap/uniswap-frontend/actions?query=workflow%3ATests)
[![Styled With Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io/) [![Styled With Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io/)
This an an open source interface for Uniswap - a protocol for decentralized exchange of Ethereum tokens. This an an open source interface for Uniswap - a protocol for decentralized exchange of Ethereum tokens.

3
cypress.json Normal file

@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost:3000"
}

@ -0,0 +1,18 @@
describe('Homepage', () => {
beforeEach(() => cy.visit('/'))
it('loads exchange page', () => {
cy.get('#exchangePage')
})
it('has url /swap', () => {
cy.url().should('include', '/swap')
})
it('can enter an amount into input', () => {
cy.get('#swapInputField').type('0.001')
})
it('can enter an amount into output', () => {
cy.get('#swapOutputField').type('0.001')
})
})

21
cypress/plugins/index.js Normal file

@ -0,0 +1,21 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

@ -0,0 +1,27 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// TODO(moodysalem): commands for connecting a mock ethereum provider.

20
cypress/support/index.ts Normal file

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.ts using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

17
cypress/tsconfig.json Normal file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "../node_modules",
"target": "es5",
"lib": [
"es5",
"dom"
],
"types": [
"cypress"
]
},
"include": [
"**/*.ts"
]
}

@ -56,7 +56,10 @@
"@types/react": "^16.9.34", "@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7", "@types/react-dom": "^16.9.7",
"@types/styled-components": "^4.2.0", "@types/styled-components": "^4.2.0",
"cypress": "^4.5.0",
"prettier": "^1.17.0", "prettier": "^1.17.0",
"serve": "^11.3.0",
"start-server-and-test": "^1.11.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"scripts": { "scripts": {
@ -71,7 +74,10 @@
"fix:all": "yarn fix:lint && yarn fix:format", "fix:all": "yarn fix:lint && yarn fix:format",
"check:lint": "yarn lint:base", "check:lint": "yarn lint:base",
"check:format": "yarn format:base --check", "check:format": "yarn format:base --check",
"check:all": "yarn check:lint && yarn check:format" "check:all": "yarn check:lint && yarn check:format",
"cy:run": "cypress run",
"serve:build": "serve -s build -l 3000",
"integration-test": "yarn build && start-server-and-test 'yarn run serve:build' http://localhost:3000 cy:run"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"

@ -151,7 +151,8 @@ export default function CurrencyInputPanel({
hideInput = false, hideInput = false,
showSendWithSwap = false, showSendWithSwap = false,
otherSelectedTokenAddress = null, otherSelectedTokenAddress = null,
advanced = false advanced = false,
inputId,
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
@ -195,6 +196,7 @@ export default function CurrencyInputPanel({
<> <>
<NumericalInput <NumericalInput
value={value} value={value}
id={inputId}
onUserInput={val => { onUserInput={val => {
onUserInput(field, val) onUserInput(field, val)
}} }}

@ -940,7 +940,7 @@ function ExchangePage({ sendingInput = false, history, params }) {
} }
return ( return (
<Wrapper> <Wrapper id="exchangePage">
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'} title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
@ -985,6 +985,7 @@ function ExchangePage({ sendingInput = false, history, params }) {
showSendWithSwap={true} showSendWithSwap={true}
advanced={advanced} advanced={advanced}
label={''} label={''}
inputId="swapInputField"
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address} otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
/> />
</InputGroup> </InputGroup>
@ -1008,6 +1009,7 @@ function ExchangePage({ sendingInput = false, history, params }) {
}} }}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)} onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address} otherSelectedTokenAddress={tokens[Field.OUTPUT]?.address}
inputId="swapInputField"
/> />
{sendingWithSwap ? ( {sendingWithSwap ? (
<ColumnCenter> <ColumnCenter>
@ -1048,6 +1050,7 @@ function ExchangePage({ sendingInput = false, history, params }) {
pair={pair} pair={pair}
advanced={advanced} advanced={advanced}
otherSelectedTokenAddress={tokens[Field.INPUT]?.address} otherSelectedTokenAddress={tokens[Field.INPUT]?.address}
inputId="swapOutputField"
/> />
{sendingWithSwap && ( {sendingWithSwap && (
<RowBetween padding="0 12px"> <RowBetween padding="0 12px">

@ -722,6 +722,7 @@ function AddLiquidity({ token0, token1, step = false }) {
error={inputError} error={inputError}
pair={pair} pair={pair}
label="Input" label="Input"
inputId="addLiquidityInput"
/> />
<ColumnCenter> <ColumnCenter>
<Plus size="16" color="#888D9B" /> <Plus size="16" color="#888D9B" />
@ -738,6 +739,7 @@ function AddLiquidity({ token0, token1, step = false }) {
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)} onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
error={outputError} error={outputError}
pair={pair} pair={pair}
inputId="addLiquidityOutput"
/> />
{tokens[Field.OUTPUT] && tokens[Field.INPUT] && ( {tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<LightCard padding="1rem" borderRadius={'20px'}> <LightCard padding="1rem" borderRadius={'20px'}>

@ -704,6 +704,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
token={pair?.liquidityToken} token={pair?.liquidityToken}
isExchange={true} isExchange={true}
pair={pair} pair={pair}
inputId="liquidityAmount"
/> />
<ColumnCenter> <ColumnCenter>
<ArrowDown size="16" color="#888D9B"/> <ArrowDown size="16" color="#888D9B"/>
@ -718,6 +719,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
error={inputError} error={inputError}
label={'Output'} label={'Output'}
disableTokenSelect disableTokenSelect
inputId="removeLiquidityToken0"
/> />
<ColumnCenter> <ColumnCenter>
<Plus size="16" color="#888D9B"/> <Plus size="16" color="#888D9B"/>
@ -732,6 +734,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
error={outputError} error={outputError}
label={'Output'} label={'Output'}
disableTokenSelect disableTokenSelect
inputId="removeLiquidityToken1"
/> />
</> </>
)} )}

12
src/utils/index.test.ts Normal file

@ -0,0 +1,12 @@
import { getEtherscanLink } from './index'
describe('utils', () => {
describe('#getEtherscanLink', () => {
it('correct for tx', () => {
expect(getEtherscanLink(1, 'abc', 'transaction')).toEqual('https://etherscan.io/tx/abc')
})
it('correct for address', () => {
expect(getEtherscanLink(1, 'abc', 'address')).toEqual('https://etherscan.io/address/abc')
})
})
})

@ -34,7 +34,7 @@ const ETHERSCAN_PREFIXES = {
42: 'kovan.' 42: 'kovan.'
} }
export function getEtherscanLink(networkId, data, type) { export function getEtherscanLink(networkId: 1 | 3 | 4 | 5 | 42 | any, data: string, type: 'transaction' | 'address') {
const prefix = `https://${ETHERSCAN_PREFIXES[networkId] || ETHERSCAN_PREFIXES[1]}etherscan.io` const prefix = `https://${ETHERSCAN_PREFIXES[networkId] || ETHERSCAN_PREFIXES[1]}etherscan.io`
switch (type) { switch (type) {

@ -21,7 +21,8 @@
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules",
"cypress"
], ],
"include": [ "include": [
"**/*.js", "**/*.js",

@ -1,9 +0,0 @@
module.exports = {
entry: [
path.join(process.cwd(), 'app/app.tsx'), // or whatever the path of your root file is
]
module: {
rules:[{ test: /\.tsx?$/, loader: 'awesome-typescript-loader' }], // other loader configuration goes in the array
resolve: {extensions: ['.js', '.jsx', '.react.js', '.ts', '.tsx']}
}
}

991
yarn.lock

File diff suppressed because it is too large Load Diff