Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0a10fcf8d | ||
|
|
7a1a476e45 | ||
|
|
b3bfc1003a | ||
|
|
3b1ef8033b | ||
|
|
803485b96a | ||
|
|
6df5d3a701 | ||
|
|
2d4eafc6b3 | ||
|
|
9b52fea58a | ||
|
|
b92b286626 | ||
|
|
a8268728d3 | ||
|
|
ab6debbf46 | ||
|
|
4c664645c6 | ||
|
|
4416a84fd7 |
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -114,7 +114,7 @@ jobs:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload source maps to Sentry
|
||||
uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd
|
||||
uses: getsentry/action-release@4744f6a65149f441c5f396d5b0877307c0db52c7
|
||||
continue-on-error: true
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
key: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-
|
||||
- run: yarn prepare
|
||||
- run: yarn test --silent --maxWorkers=100%
|
||||
- run: yarn test --coverage --maxWorkers=100%
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { USDC_MAINNET } from '../../src/constants/tokens'
|
||||
import { WETH_GOERLI } from '../fixtures/constants'
|
||||
import { HardhatProvider } from '../support/hardhat'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Swap', () => {
|
||||
let hardhat: HardhatProvider
|
||||
const verifyAmount = (field: 'input' | 'output', amountText: string | null) => {
|
||||
if (amountText === null) {
|
||||
cy.get(`#swap-currency-${field} .token-amount-input`).should('not.have.value')
|
||||
@@ -46,9 +44,7 @@ describe('Swap', () => {
|
||||
}
|
||||
|
||||
before(() => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat' }).then((window) => {
|
||||
hardhat = window.hardhat
|
||||
})
|
||||
cy.visit('/swap', { ethereum: 'hardhat' })
|
||||
})
|
||||
|
||||
it('starts with ETH selected by default', () => {
|
||||
@@ -81,35 +77,33 @@ describe('Swap', () => {
|
||||
it('can swap ETH for USDC', () => {
|
||||
const TOKEN_ADDRESS = USDC_MAINNET.address
|
||||
const BALANCE_INCREMENT = 1
|
||||
cy.visit('/swap', { ethereum: 'hardhat' })
|
||||
.then((window) => {
|
||||
hardhat = window.hardhat
|
||||
})
|
||||
.then(() => hardhat.utils.getBalance(hardhat.wallet.address, USDC_MAINNET))
|
||||
.then((balance) => Number(balance.toFixed(1)))
|
||||
.then((initialBalance) => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get(getTestSelector('token-search-input')).clear().type(TOKEN_ADDRESS)
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.get(getTestSelector('dismiss-tx-confirmation')).click()
|
||||
cy.hardhat().then((hardhat) => {
|
||||
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
|
||||
.then((balance) => Number(balance.toFixed(1)))
|
||||
.then((initialBalance) => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get(getTestSelector('token-search-input')).clear().type(TOKEN_ADDRESS)
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type(BALANCE_INCREMENT.toString())
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.get(getTestSelector('dismiss-tx-confirmation')).click()
|
||||
|
||||
cy.then(() => hardhat.send('hardhat_mine', ['0x1', '0xc'])).then(() => {
|
||||
// ui check
|
||||
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
|
||||
'have.text',
|
||||
`Balance: ${initialBalance + BALANCE_INCREMENT}`
|
||||
)
|
||||
cy.then(() => hardhat.provider.send('hardhat_mine', ['0x1', '0xc'])).then(() => {
|
||||
// ui check
|
||||
cy.get('#swap-currency-output [data-testid="balance-text"]').should(
|
||||
'have.text',
|
||||
`Balance: ${initialBalance + BALANCE_INCREMENT}`
|
||||
)
|
||||
|
||||
// chain state check
|
||||
cy.then(() => hardhat.utils.getBalance(hardhat.wallet.address, USDC_MAINNET))
|
||||
.then((balance) => Number(balance.toFixed(1)))
|
||||
.should('eq', initialBalance + BALANCE_INCREMENT)
|
||||
// chain state check
|
||||
cy.then(() => hardhat.getBalance(hardhat.wallet.address, USDC_MAINNET))
|
||||
.then((balance) => Number(balance.toFixed(1)))
|
||||
.should('eq', initialBalance + BALANCE_INCREMENT)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should have the correct default input/output and token selection should work', () => {
|
||||
@@ -162,21 +156,22 @@ describe('Swap', () => {
|
||||
})
|
||||
|
||||
it('should render and dismiss the wallet rejection modal', () => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat' }).then((window) => {
|
||||
hardhat = window.hardhat
|
||||
cy.stub(hardhat.wallet, 'sendTransaction').rejects(new Error('user cancelled'))
|
||||
cy.visit('/swap', { ethereum: 'hardhat' })
|
||||
.hardhat()
|
||||
.then((hardhat) => {
|
||||
cy.stub(hardhat.wallet, 'sendTransaction').rejects(new Error('user cancelled'))
|
||||
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get(getTestSelector('token-search-input')).clear().type(USDC_MAINNET.address)
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('1')
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.contains('Transaction rejected').should('exist')
|
||||
cy.contains('Dismiss').click()
|
||||
cy.contains('Transaction rejected').should('not.exist')
|
||||
})
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get(getTestSelector('token-search-input')).clear().type(USDC_MAINNET.address)
|
||||
cy.contains('USDC').click()
|
||||
cy.get('#swap-currency-output .token-amount-input').clear().type('1')
|
||||
cy.get('#swap-currency-input .token-amount-input').should('not.equal', '')
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').click()
|
||||
cy.contains('Transaction rejected').should('exist')
|
||||
cy.contains('Dismiss').click()
|
||||
cy.contains('Transaction rejected').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('Opens and closes the settings menu', () => {
|
||||
|
||||
@@ -6,23 +6,21 @@
|
||||
// ***********************************************************
|
||||
|
||||
import '@cypress/code-coverage/support'
|
||||
import 'cypress-hardhat/lib/browser'
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
import assert from 'assert'
|
||||
import { Network } from 'cypress-hardhat/lib/browser'
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { UserState } from '../../src/state/user/reducer'
|
||||
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
import { injected } from './ethereum'
|
||||
import { HardhatProvider } from './hardhat'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface ApplicationWindow {
|
||||
ethereum: Eip1193Bridge
|
||||
hardhat: HardhatProvider
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
@@ -39,10 +37,6 @@ declare global {
|
||||
*/
|
||||
userState?: Partial<UserState>
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
task(event: 'hardhat'): Chainable<Network>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +53,8 @@ Cypress.Commands.overwrite(
|
||||
|
||||
return cy
|
||||
.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 })
|
||||
.task('hardhat')
|
||||
.then((network) =>
|
||||
.provider()
|
||||
.then((provider) =>
|
||||
original({
|
||||
...options,
|
||||
url: hashUrl,
|
||||
@@ -84,9 +78,7 @@ Cypress.Commands.overwrite(
|
||||
|
||||
// Inject the mock ethereum provider.
|
||||
if (options?.ethereum === 'hardhat') {
|
||||
// The provider is exposed via hardhat to allow mocking / network manipulation.
|
||||
win.hardhat = new HardhatProvider(network)
|
||||
win.ethereum = win.hardhat
|
||||
win.ethereum = provider
|
||||
} else {
|
||||
win.ethereum = injected
|
||||
}
|
||||
@@ -97,28 +89,31 @@ Cypress.Commands.overwrite(
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
// Infura security policies are based on Origin headers.
|
||||
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
|
||||
cy.intercept(/infura.io/, (res) => {
|
||||
res.headers['origin'] = 'http://localhost:3000'
|
||||
res.alias = res.body.method
|
||||
res.continue()
|
||||
})
|
||||
|
||||
// Graphql security policies are based on Origin headers.
|
||||
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
|
||||
cy.intercept('https://api.uniswap.org/v1/graphql', (res) => {
|
||||
res.headers['origin'] = 'https://app.uniswap.org'
|
||||
res.continue()
|
||||
})
|
||||
cy.intercept('https://beta.api.uniswap.org/v1/graphql', (res) => {
|
||||
res.headers['origin'] = 'https://app.uniswap.org'
|
||||
res.continue()
|
||||
})
|
||||
|
||||
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (res) => {
|
||||
res.reply(JSON.stringify({}))
|
||||
// Many API calls enforce that requests come from our app, so we must mock Origin and Referer.
|
||||
cy.intercept('*', (req) => {
|
||||
req.headers['referer'] = 'https://app.uniswap.org'
|
||||
req.headers['origin'] = 'https://app.uniswap.org'
|
||||
})
|
||||
// Infura uses a test endpoint, which allow-lists http://localhost:3000 instead.
|
||||
.intercept(/infura.io/, (req) => {
|
||||
req.headers['referer'] = 'http://localhost:3000'
|
||||
req.headers['origin'] = 'http://localhost:3000'
|
||||
req.alias = req.body.method
|
||||
req.continue()
|
||||
})
|
||||
// Mock Amplitude responses to avoid analytics from tests.
|
||||
.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
|
||||
const requestBody = JSON.stringify(req.body)
|
||||
const byteSize = new Blob([requestBody]).size
|
||||
req.reply(
|
||||
JSON.stringify({
|
||||
code: 200,
|
||||
server_upload_time: Date.now(),
|
||||
payload_size_bytes: byteSize,
|
||||
events_ingested: req.body.events.length,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.on('uncaught:exception', () => {
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
import { HardhatUtils, Network } from 'cypress-hardhat/lib/browser'
|
||||
|
||||
export class HardhatProvider extends Eip1193Bridge {
|
||||
readonly utils: HardhatUtils
|
||||
readonly chainId: string
|
||||
readonly wallet: Wallet
|
||||
|
||||
isMetaMask = true
|
||||
|
||||
constructor(network: Network) {
|
||||
const utils = new HardhatUtils(network)
|
||||
const wallet = new Wallet(utils.account.privateKey, utils.provider)
|
||||
super(wallet, utils.provider)
|
||||
|
||||
this.utils = utils
|
||||
this.chainId = `0x${network.chainId.toString(16)}`
|
||||
this.wallet = wallet
|
||||
}
|
||||
|
||||
async sendAsync(...args: any[]) {
|
||||
return this.send(...args)
|
||||
}
|
||||
|
||||
async send(...args: any[]) {
|
||||
console.debug('hardhat:send', ...args)
|
||||
|
||||
// Parse callback form.
|
||||
const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'
|
||||
let callback = <T>(error: Error | null, result?: { result: T }) => {
|
||||
if (error) throw error
|
||||
return result?.result
|
||||
}
|
||||
let method
|
||||
let params
|
||||
if (isCallbackForm) {
|
||||
callback = args[1]
|
||||
method = args[0].method
|
||||
params = args[0].params
|
||||
} else {
|
||||
method = args[0]
|
||||
params = args[1]
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
switch (method) {
|
||||
case 'eth_requestAccounts':
|
||||
case 'eth_accounts':
|
||||
result = [this.wallet.address]
|
||||
break
|
||||
case 'eth_chainId':
|
||||
result = this.chainId
|
||||
break
|
||||
case 'eth_sendTransaction': {
|
||||
// Eip1193Bridge doesn't support .gas and .from directly, so we massage it to satisfy ethers' expectations.
|
||||
// See https://github.com/ethers-io/ethers.js/issues/1683.
|
||||
params[0].gasLimit = params[0].gas
|
||||
delete params[0].gas
|
||||
delete params[0].from
|
||||
|
||||
const req = JsonRpcProvider.hexlifyTransaction(params[0])
|
||||
req.gasLimit = req.gas
|
||||
delete req.gas
|
||||
|
||||
result = (await this.signer.sendTransaction(req)).hash
|
||||
break
|
||||
}
|
||||
default:
|
||||
result = await super.send(method, params)
|
||||
}
|
||||
console.debug('hardhat:receive', method, result)
|
||||
return callback(null, { result })
|
||||
} catch (error) {
|
||||
console.debug('hardhat:error', method, error)
|
||||
return callback(error as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
"serve": "serve build -l 3000",
|
||||
"lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .",
|
||||
"typecheck": "tsc",
|
||||
"test": "craco test --coverage",
|
||||
"test": "craco test",
|
||||
"test:size": "node scripts/test-size.js",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
@@ -103,7 +103,7 @@
|
||||
"@vanilla-extract/webpack-plugin": "^2.1.11",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"cypress": "10.3.1",
|
||||
"cypress-hardhat": "^1.0.1",
|
||||
"cypress-hardhat": "^2.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-plugin-import": "^2.27",
|
||||
@@ -113,6 +113,7 @@
|
||||
"ms.macro": "^2.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"serve": "^11.3.2",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"ts-transform-graphql-tag": "^0.2.1",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="200" height="200" fill="#04CD58"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100 112C87.3026 112 77 101.708 77 89C77 76.2923 87.3026 66 100 66C112.697 66 123 76.2923 123 89C123 101.708 112.697 112 100 112ZM90 89C90 94.5251 94.4794 99 100 99C105.521 99 110 94.5251 110 89C110 83.4749 105.521 79 100 79C94.4794 79 90 83.4749 90 89Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26 89.0304L70 45H130L174 89.0304L100 163L26 89.0304ZM134 72.9998C115.305 54.2224 84.6953 54.2225 66 72.9999L50 89.0001L66 105C84.6953 123.778 115.305 123.777 134 105L150 89.0001L134 72.9998Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 731 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100" height="100" fill="#2081E2"/>
|
||||
<path d="M24.6679 51.6802L24.8836 51.3411L37.8906 30.9933C38.0807 30.6954 38.5276 30.7262 38.6714 31.0498C40.8444 35.9197 42.7194 41.9763 41.841 45.7469C41.466 47.2983 40.4386 49.3993 39.2827 51.3411C39.1338 51.6237 38.9694 51.9011 38.7947 52.1682C38.7125 52.2915 38.5738 52.3634 38.4248 52.3634H25.048C24.6884 52.3634 24.4778 51.973 24.6679 51.6802Z" fill="white"/>
|
||||
<path d="M82.6444 55.461V58.6819C82.6444 58.8668 82.5314 59.0312 82.367 59.1031C81.3602 59.5346 77.9132 61.1168 76.48 63.11C72.8224 68.2008 70.0279 75.48 63.7812 75.48H37.721C28.4847 75.48 21 67.9697 21 58.7024V58.4045C21 58.1579 21.2003 57.9576 21.4469 57.9576H35.9745C36.2621 57.9576 36.4727 58.2247 36.4471 58.5072C36.3443 59.4524 36.519 60.4182 36.9659 61.2966C37.8289 63.0484 39.6166 64.1426 41.5481 64.1426H48.74V58.5278H41.6303C41.2656 58.5278 41.0499 58.1065 41.2605 57.8086C41.3375 57.6904 41.4249 57.5672 41.5173 57.4285C42.1903 56.473 43.1509 54.9884 44.1064 53.2983C44.7588 52.1579 45.3906 50.9404 45.8992 49.7178C46.002 49.4969 46.0841 49.2708 46.1663 49.0499C46.305 48.6595 46.4489 48.2948 46.5516 47.9301C46.6544 47.6218 46.7365 47.2982 46.8187 46.9951C47.0602 45.9574 47.1629 44.8581 47.1629 43.7177C47.1629 43.2708 47.1424 42.8033 47.1013 42.3564C47.0807 41.8684 47.0191 41.3803 46.9574 40.8923C46.9163 40.4608 46.8393 40.0344 46.7571 39.5875C46.6544 38.9351 46.5105 38.2879 46.3461 37.6354L46.2896 37.3889C46.1663 36.9419 46.0636 36.5156 45.9198 36.0687C45.5139 34.6662 45.0465 33.2998 44.5533 32.0207C44.3735 31.5121 44.168 31.0241 43.9625 30.5361C43.6595 29.8015 43.3512 29.1337 43.0687 28.5018C42.9249 28.2141 42.8016 27.9521 42.6783 27.685C42.5396 27.3819 42.3958 27.0788 42.2519 26.7912C42.1492 26.5703 42.031 26.3648 41.9488 26.1593L41.0704 24.536C40.9471 24.3151 41.1526 24.0531 41.394 24.1199L46.8907 25.6096H46.9061C46.9163 25.6096 46.9215 25.6148 46.9266 25.6148L47.6509 25.8151L48.4472 26.0412L48.74 26.1233V22.8562C48.74 21.2791 50.0037 20 51.5654 20C52.3462 20 53.0551 20.3185 53.5637 20.8373C54.0722 21.3562 54.3907 22.0651 54.3907 22.8562V27.7056L54.9764 27.8699C55.0226 27.8854 55.0688 27.9059 55.1099 27.9367C55.2538 28.0446 55.4592 28.2038 55.7212 28.3991C55.9267 28.5634 56.1476 28.7638 56.4147 28.9693C56.9438 29.3956 57.5757 29.9453 58.2692 30.5772C58.4541 30.7364 58.6339 30.9008 58.7983 31.0652C59.6922 31.8974 60.6939 32.8734 61.6494 33.9522C61.9165 34.2553 62.1785 34.5635 62.4456 34.8871C62.7127 35.2159 62.9953 35.5395 63.2418 35.8632C63.5655 36.2947 63.9148 36.7416 64.2179 37.2091C64.3617 37.43 64.5261 37.656 64.6648 37.8769C65.0552 38.4676 65.3994 39.079 65.7282 39.6903C65.8669 39.9728 66.0107 40.281 66.134 40.5841C66.4987 41.4009 66.7864 42.2331 66.9713 43.0653C67.0278 43.2451 67.0689 43.4403 67.0895 43.615V43.6561C67.1511 43.9026 67.1717 44.1646 67.1922 44.4317C67.2744 45.2845 67.2333 46.1372 67.0484 46.9951C66.9713 47.3599 66.8686 47.704 66.7453 48.0688C66.622 48.4181 66.4987 48.7828 66.3395 49.127C66.0313 49.841 65.6665 50.5551 65.235 51.2229C65.0963 51.4695 64.9319 51.7315 64.7675 51.9781C64.5877 52.24 64.4028 52.4866 64.2384 52.7281C64.0124 53.0363 63.771 53.3599 63.5244 53.6476C63.3035 53.9507 63.0775 54.2538 62.8309 54.5209C62.4867 54.9267 62.1579 55.312 61.8137 55.6819C61.6083 55.9233 61.3874 56.1699 61.1613 56.3908C60.9405 56.6373 60.7144 56.8582 60.5089 57.0637C60.1648 57.4079 59.8771 57.675 59.6356 57.8959L59.0706 58.4148C58.9884 58.4867 58.8805 58.5278 58.7675 58.5278H54.3907V64.1426H59.8976C61.1305 64.1426 62.3018 63.7059 63.247 62.9045C63.5706 62.622 64.9833 61.3994 66.6528 59.5552C66.7093 59.4935 66.7813 59.4473 66.8635 59.4268L82.0742 55.0295C82.3568 54.9473 82.6444 55.163 82.6444 55.461Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,20 +0,0 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="28" height="28" fill="#F9F9F9"/>
|
||||
<path d="M21.94 7.92004C20.4077 6.42277 18.3116 5.5 16 5.5C11.3056 5.5 7.5 9.30559 7.5 14C7.5 18.6944 11.3056 22.5 16 22.5C18.3116 22.5 20.4077 21.5772 21.94 20.08C20.1123 22.4633 17.2356 24 14 24C8.47715 24 4 19.5229 4 14C4 8.47715 8.47715 4 14 4C17.2356 4 20.1123 5.53668 21.94 7.92004Z" fill="url(#paint0_linear_6993_17582)"/>
|
||||
<path d="M9.64795 18.864C10.8738 20.0618 12.5507 20.8 14.4 20.8C18.1555 20.8 21.2 17.7555 21.2 14C21.2 10.2445 18.1555 7.2 14.4 7.2C12.5507 7.2 10.8738 7.9382 9.64795 9.13601C11.1102 7.22934 13.4115 6 16 6C20.4183 6 24 9.58172 24 14C24 18.4183 20.4183 22 16 22C13.4115 22 11.1102 20.7707 9.64795 18.864Z" fill="url(#paint1_linear_6993_17582)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20 14C20 17.3137 17.3137 20 14 20C10.6863 20 8 17.3137 8 14C8 10.6863 10.6863 8 14 8C17.3137 8 20 10.6863 20 14ZM18 14C18 16.2091 16.2091 18 14 18C11.7909 18 10 16.2091 10 14C10 11.7909 11.7909 10 14 10C16.2091 10 18 11.7909 18 14Z" fill="url(#paint2_linear_6993_17582)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_6993_17582" x1="4" y1="13.6552" x2="24" y2="13.6552" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00E0FF"/>
|
||||
<stop offset="1" stop-color="#562EC8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_6993_17582" x1="3.99998" y1="13.6552" x2="24" y2="13.6552" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00E0FF"/>
|
||||
<stop offset="1" stop-color="#562EC8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_6993_17582" x1="4" y1="13.6552" x2="24" y2="13.6552" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00E0FF"/>
|
||||
<stop offset="1" stop-color="#562EC8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,17 +1,16 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { WalletConnect } from '@web3-react/walletconnect'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import Modal from 'components/Modal'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { uniwalletConnectConnection } from 'connection'
|
||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
import { UniwalletConnect } from 'connection/WalletConnect'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useModalIsOpen, useToggleUniwalletModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
@@ -39,44 +38,37 @@ const Divider = styled.div`
|
||||
`
|
||||
|
||||
export default function UniwalletModal() {
|
||||
const open = useModalIsOpen(ApplicationModal.UNIWALLET_CONNECT)
|
||||
const toggle = useToggleUniwalletModal()
|
||||
|
||||
const { activationState, cancelActivation } = useActivationState()
|
||||
const [uri, setUri] = useState<string>()
|
||||
|
||||
// Displays the modal if a Uniswap Wallet Connection is pending & qrcode URI is available
|
||||
const open =
|
||||
activationState.status === ActivationStatus.PENDING &&
|
||||
activationState.connection.type === ConnectionType.UNIWALLET &&
|
||||
!!uri
|
||||
|
||||
useEffect(() => {
|
||||
;(uniwalletConnectConnection.connector as WalletConnect).events.addListener(
|
||||
UniwalletConnect.UNI_URI_AVAILABLE,
|
||||
(uri) => {
|
||||
uri && setUri(uri)
|
||||
toggle()
|
||||
}
|
||||
)
|
||||
}, [toggle])
|
||||
}, [])
|
||||
|
||||
const { account } = useWeb3React()
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
sendAnalyticsEvent('Uniswap wallet modal opened', { userConnected: !!account })
|
||||
if (account) {
|
||||
toggle()
|
||||
}
|
||||
}
|
||||
}, [account, open, toggle])
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
uniwalletConnectConnection.connector.deactivate?.()
|
||||
toggle()
|
||||
}, [toggle])
|
||||
if (open) sendAnalyticsEvent('Uniswap wallet modal opened')
|
||||
}, [open])
|
||||
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Modal isOpen={open} onDismiss={onClose}>
|
||||
<Modal isOpen={open} onDismiss={cancelActivation}>
|
||||
<UniwalletConnectWrapper>
|
||||
<HeaderRow>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Scan with Uniswap Wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<CloseIcon onClick={onClose} />
|
||||
<CloseIcon onClick={cancelActivation} />
|
||||
</HeaderRow>
|
||||
<QRCodeWrapper>
|
||||
{uri && (
|
||||
|
||||
@@ -27,6 +27,11 @@ export function useToggleAccountDrawer() {
|
||||
}, [updateAccountDrawerOpen])
|
||||
}
|
||||
|
||||
export function useCloseAccountDrawer() {
|
||||
const updateAccountDrawerOpen = useUpdateAtom(accountDrawerOpenAtom)
|
||||
return useCallback(() => updateAccountDrawerOpen(false), [updateAccountDrawerOpen])
|
||||
}
|
||||
|
||||
export function useAccountDrawer(): [boolean, () => void] {
|
||||
const accountDrawerOpen = useAtomValue(accountDrawerOpenAtom)
|
||||
return [accountDrawerOpen, useToggleAccountDrawer()]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Unicon } from 'components/Unicon'
|
||||
import { Connection, ConnectionType } from 'connection'
|
||||
import { Connection, ConnectionType } from 'connection/types'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { SupportedChainId, UniWalletSupportedChains } from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useSelectChain from 'hooks/useSelectChain'
|
||||
import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
@@ -76,7 +76,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
<Column paddingX="8">
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<ChainSelectorRow
|
||||
disabled={isUniWallet && chainId === SupportedChainId.CELO}
|
||||
disabled={isUniWallet && !UniWalletSupportedChains.includes(chainId)}
|
||||
onSelectChain={onSelectChain}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useCloseAccountDrawer } from 'components/AccountDrawer'
|
||||
import { ButtonEmpty, ButtonPrimary } from 'components/Button'
|
||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
@@ -20,13 +22,15 @@ const AlertTriangleIcon = styled(AlertTriangle)`
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
`
|
||||
|
||||
export default function ConnectionErrorView({
|
||||
retryActivation,
|
||||
openOptions,
|
||||
}: {
|
||||
retryActivation: () => void
|
||||
openOptions: () => void
|
||||
}) {
|
||||
// TODO(cartcrom): move this to a top level modal, rather than inline in the drawer
|
||||
export default function ConnectionErrorView() {
|
||||
const { activationState, tryActivation, cancelActivation } = useActivationState()
|
||||
const closeDrawer = useCloseAccountDrawer()
|
||||
|
||||
if (activationState.status !== ActivationStatus.ERROR) return null
|
||||
|
||||
const retry = () => tryActivation(activationState.connection, closeDrawer)
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<AlertTriangleIcon />
|
||||
@@ -38,11 +42,11 @@ export default function ConnectionErrorView({
|
||||
The connection attempt failed. Please click try again and follow the steps to connect in your wallet.
|
||||
</Trans>
|
||||
</ThemedText.BodyPrimary>
|
||||
<ButtonPrimary $borderRadius="16px" onClick={retryActivation}>
|
||||
<ButtonPrimary $borderRadius="16px" onClick={retry}>
|
||||
<Trans>Try Again</Trans>
|
||||
</ButtonPrimary>
|
||||
<ButtonEmpty width="fit-content" padding="0" marginTop={20}>
|
||||
<ThemedText.Link onClick={openOptions} marginBottom={12}>
|
||||
<ThemedText.Link onClick={cancelActivation} marginBottom={12}>
|
||||
<Trans>Back to wallet selection</Trans>
|
||||
</ThemedText.Link>
|
||||
</ButtonEmpty>
|
||||
|
||||
85
src/components/WalletModal/Option.test.tsx
Normal file
85
src/components/WalletModal/Option.test.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import UNIWALLET_ICON from 'assets/images/uniwallet.png'
|
||||
import { useCloseAccountDrawer } from 'components/AccountDrawer'
|
||||
import { Connection, ConnectionType } from 'connection/types'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { createDeferredPromise } from 'test-utils/promise'
|
||||
import { act, render } from 'test-utils/render'
|
||||
|
||||
import Option from './Option'
|
||||
|
||||
const mockCloseDrawer = jest.fn()
|
||||
jest.mock('components/AccountDrawer')
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'debug').mockReturnValue()
|
||||
mocked(useCloseAccountDrawer).mockReturnValue(mockCloseDrawer)
|
||||
})
|
||||
|
||||
const mockConnection1: Connection = {
|
||||
getName: () => 'Mock Connection 1',
|
||||
connector: {
|
||||
activate: jest.fn(),
|
||||
deactivate: jest.fn(),
|
||||
} as unknown as Connector,
|
||||
getIcon: () => UNIWALLET_ICON,
|
||||
type: ConnectionType.UNIWALLET,
|
||||
} as unknown as Connection
|
||||
|
||||
const mockConnection2: Connection = {
|
||||
getName: () => 'Mock Connection 2',
|
||||
connector: {
|
||||
activate: jest.fn(),
|
||||
deactivate: jest.fn(),
|
||||
} as unknown as Connector,
|
||||
getIcon: () => UNIWALLET_ICON,
|
||||
type: ConnectionType.INJECTED,
|
||||
} as unknown as Connection
|
||||
|
||||
describe('Wallet Option', () => {
|
||||
it('renders default state', () => {
|
||||
const component = render(<Option connection={mockConnection1} />)
|
||||
const option = component.getByTestId('wallet-option-UNIWALLET')
|
||||
expect(option).toBeEnabled()
|
||||
expect(option).toHaveProperty('selected', false)
|
||||
|
||||
expect(option).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('connect when clicked', async () => {
|
||||
const activationResponse = createDeferredPromise()
|
||||
mocked(mockConnection1.connector.activate).mockReturnValue(activationResponse.promise)
|
||||
|
||||
const component = render(
|
||||
<>
|
||||
<Option connection={mockConnection1} />
|
||||
<Option connection={mockConnection2} />
|
||||
</>
|
||||
)
|
||||
const option1 = component.getByTestId('wallet-option-UNIWALLET')
|
||||
const option2 = component.getByTestId('wallet-option-INJECTED')
|
||||
|
||||
expect(option1).toBeEnabled()
|
||||
expect(option1).toHaveProperty('selected', false)
|
||||
expect(option2).toBeEnabled()
|
||||
expect(option2).toHaveProperty('selected', false)
|
||||
|
||||
await act(() => option1.click())
|
||||
|
||||
expect(option1).toBeDisabled()
|
||||
expect(option1).toHaveProperty('selected', true)
|
||||
expect(option2).toBeDisabled()
|
||||
expect(option2).toHaveProperty('selected', false)
|
||||
|
||||
expect(mockCloseDrawer).toHaveBeenCalledTimes(0)
|
||||
|
||||
await act(async () => activationResponse.resolve())
|
||||
|
||||
expect(mockCloseDrawer).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(option1).toBeEnabled()
|
||||
expect(option1).toHaveProperty('selected', false)
|
||||
expect(option2).toBeEnabled()
|
||||
expect(option2).toHaveProperty('selected', false)
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,9 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useCloseAccountDrawer } from 'components/AccountDrawer'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import { Connection, ConnectionType } from 'connection'
|
||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||
import { Connection } from 'connection/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
@@ -14,27 +16,26 @@ const OptionCardLeft = styled.div`
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const OptionCardClickable = styled.button<{ isActive?: boolean; clickable?: boolean }>`
|
||||
const OptionCardClickable = styled.button<{ selected: boolean }>`
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border: none;
|
||||
width: 100% !important;
|
||||
border-color: ${({ theme, isActive }) => (isActive ? theme.accentActive : 'transparent')};
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
padding: 18px;
|
||||
|
||||
margin-top: 0;
|
||||
transition: ${({ theme }) => theme.transition.duration.fast};
|
||||
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
|
||||
opacity: ${({ disabled, selected }) => (disabled && !selected ? '0.5' : '1')};
|
||||
|
||||
&:hover {
|
||||
cursor: ${({ clickable }) => clickable && 'pointer'};
|
||||
background-color: ${({ theme, clickable }) => clickable && theme.hoverState};
|
||||
cursor: ${({ disabled }) => !disabled && 'pointer'};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.hoverState};
|
||||
}
|
||||
&:focus {
|
||||
background-color: ${({ theme, clickable }) => clickable && theme.hoverState};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.hoverState};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -62,15 +63,16 @@ const IconWrapper = styled.div`
|
||||
`};
|
||||
`
|
||||
|
||||
type OptionProps = {
|
||||
connection: Connection
|
||||
activate: () => void
|
||||
pendingConnectionType?: ConnectionType
|
||||
}
|
||||
export default function Option({ connection, pendingConnectionType, activate }: OptionProps) {
|
||||
const isPending = pendingConnectionType === connection.type
|
||||
export default function Option({ connection }: { connection: Connection }) {
|
||||
const { activationState, tryActivation } = useActivationState()
|
||||
const closeDrawer = useCloseAccountDrawer()
|
||||
const activate = () => tryActivation(connection, closeDrawer)
|
||||
|
||||
const isSomeOptionPending = activationState.status === ActivationStatus.PENDING
|
||||
const isCurrentOptionPending = isSomeOptionPending && activationState.connection.type === connection.type
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const content = (
|
||||
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={InterfaceEventName.WALLET_SELECTED}
|
||||
@@ -78,10 +80,10 @@ export default function Option({ connection, pendingConnectionType, activate }:
|
||||
element={InterfaceElementName.WALLET_TYPE_OPTION}
|
||||
>
|
||||
<OptionCardClickable
|
||||
onClick={!pendingConnectionType ? activate : undefined}
|
||||
clickable={!pendingConnectionType}
|
||||
disabled={Boolean(!isPending && !!pendingConnectionType)}
|
||||
data-testid="wallet-modal-option"
|
||||
onClick={activate}
|
||||
disabled={isSomeOptionPending}
|
||||
selected={isCurrentOptionPending}
|
||||
data-testid={`wallet-option-${connection.type}`}
|
||||
>
|
||||
<OptionCardLeft>
|
||||
<IconWrapper>
|
||||
@@ -90,10 +92,8 @@ export default function Option({ connection, pendingConnectionType, activate }:
|
||||
<HeaderText>{connection.getName()}</HeaderText>
|
||||
{connection.isNew && <NewBadge />}
|
||||
</OptionCardLeft>
|
||||
{isPending && <Loader />}
|
||||
{isCurrentOptionPending && <Loader />}
|
||||
</OptionCardClickable>
|
||||
</TraceEvent>
|
||||
)
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
132
src/components/WalletModal/__snapshots__/Option.test.tsx.snap
Normal file
132
src/components/WalletModal/__snapshots__/Option.test.tsx.snap
Normal file
@@ -0,0 +1,132 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Wallet Option renders default state 1`] = `
|
||||
.c1 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-flow: column nowrap;
|
||||
-ms-flex-flow: column nowrap;
|
||||
flex-flow: column nowrap;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
background-color: #F5F6FC;
|
||||
border: none;
|
||||
width: 100% !important;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
padding: 18px;
|
||||
-webkit-transition: 125ms;
|
||||
transition: 125ms;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c0:hover {
|
||||
cursor: pointer;
|
||||
background-color: #ADBCFF3d;
|
||||
}
|
||||
|
||||
.c0:focus {
|
||||
background-color: #ADBCFF3d;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-flow: row nowrap;
|
||||
-ms-flex-flow: row nowrap;
|
||||
flex-flow: row nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
color: #0D111C;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-flow: column nowrap;
|
||||
-ms-flex-flow: column nowrap;
|
||||
flex-flow: column nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c2 > img,
|
||||
.c2 span {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
@media (max-width:960px) {
|
||||
.c2 {
|
||||
-webkit-align-items: flex-end;
|
||||
-webkit-box-align: flex-end;
|
||||
-ms-flex-align: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
<button
|
||||
class="c0"
|
||||
data-testid="wallet-option-UNIWALLET"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<img
|
||||
alt="Icon"
|
||||
src="uniwallet.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="c3"
|
||||
>
|
||||
Mock Connection 1
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
@@ -1,18 +1,12 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import IconButton from 'components/AccountDrawer/IconButton'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { Connection, ConnectionType, getConnections, networkConnection } from 'connection'
|
||||
import { ErrorCode } from 'connection/utils'
|
||||
import { getConnections, networkConnection } from 'connection'
|
||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { Settings } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
@@ -43,35 +37,12 @@ const PrivacyPolicyWrapper = styled.div`
|
||||
padding: 0 4px;
|
||||
`
|
||||
|
||||
function didUserReject(connection: Connection, error: any): boolean {
|
||||
return (
|
||||
error?.code === ErrorCode.USER_REJECTED_REQUEST ||
|
||||
(connection.type === ConnectionType.WALLET_CONNECT && error?.toString?.() === ErrorCode.WC_MODAL_CLOSED) ||
|
||||
(connection.type === ConnectionType.COINBASE_WALLET && error?.toString?.() === ErrorCode.CB_REJECTED_REQUEST)
|
||||
)
|
||||
}
|
||||
|
||||
export default function WalletModal({ openSettings }: { openSettings: () => void }) {
|
||||
const dispatch = useAppDispatch()
|
||||
const { connector, chainId } = useWeb3React()
|
||||
const [drawerOpen, toggleWalletDrawer] = useAccountDrawer()
|
||||
|
||||
const [pendingConnection, setPendingConnection] = useState<Connection | undefined>()
|
||||
const [pendingError, setPendingError] = useState<any>()
|
||||
|
||||
const connections = getConnections()
|
||||
|
||||
useEffect(() => {
|
||||
// Clean up errors when the dropdown closes
|
||||
return () => setPendingError(undefined)
|
||||
}, [setPendingError])
|
||||
|
||||
const openOptions = useCallback(() => {
|
||||
if (pendingConnection) {
|
||||
setPendingError(undefined)
|
||||
setPendingConnection(undefined)
|
||||
}
|
||||
}, [pendingConnection, setPendingError])
|
||||
const { activationState } = useActivationState()
|
||||
|
||||
// Keep the network connector in sync with any active user connector to prevent chain-switching on wallet disconnection.
|
||||
useEffect(() => {
|
||||
@@ -80,71 +51,22 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
}
|
||||
}, [chainId, connector])
|
||||
|
||||
// Used to track the state of the drawer in async function
|
||||
const drawerOpenRef = useRef(drawerOpen)
|
||||
drawerOpenRef.current = drawerOpen
|
||||
|
||||
const tryActivation = useCallback(
|
||||
async (connection: Connection) => {
|
||||
// Skips wallet connection if the connection should override the default behavior, i.e. install metamask or launch coinbase app
|
||||
if (connection.overrideActivate?.()) return
|
||||
|
||||
// log selected wallet
|
||||
sendEvent({
|
||||
category: 'Wallet',
|
||||
action: 'Change Wallet',
|
||||
label: connection.type,
|
||||
})
|
||||
|
||||
try {
|
||||
setPendingConnection(connection)
|
||||
setPendingError(undefined)
|
||||
|
||||
await connection.connector.activate()
|
||||
console.debug(`connection activated: ${connection.getName()}`)
|
||||
dispatch(updateSelectedWallet({ wallet: connection.type }))
|
||||
if (drawerOpenRef.current) toggleWalletDrawer()
|
||||
} catch (error) {
|
||||
console.debug(`web3-react connection error: ${JSON.stringify(error)}`)
|
||||
// TODO(WEB-3162): re-add special treatment for already-pending injected errors
|
||||
if (didUserReject(connection, error)) {
|
||||
setPendingConnection(undefined)
|
||||
} else {
|
||||
setPendingError(error)
|
||||
|
||||
sendAnalyticsEvent(InterfaceEventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WalletConnectionResult.FAILED,
|
||||
wallet_type: connection.getName(),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch, setPendingError, toggleWalletDrawer]
|
||||
)
|
||||
|
||||
return (
|
||||
<Wrapper data-testid="wallet-modal">
|
||||
<AutoRow justify="space-between" width="100%" marginBottom="16px">
|
||||
<ThemedText.SubHeader>Connect a wallet</ThemedText.SubHeader>
|
||||
<IconButton Icon={Settings} onClick={openSettings} data-testid="wallet-settings" />
|
||||
</AutoRow>
|
||||
{pendingError ? (
|
||||
pendingConnection && (
|
||||
<ConnectionErrorView openOptions={openOptions} retryActivation={() => tryActivation(pendingConnection)} />
|
||||
)
|
||||
{activationState.status === ActivationStatus.ERROR ? (
|
||||
<ConnectionErrorView />
|
||||
) : (
|
||||
<AutoColumn gap="16px">
|
||||
<OptionGrid data-testid="option-grid">
|
||||
{connections.map((connection) =>
|
||||
connection.shouldDisplay() ? (
|
||||
<Option
|
||||
key={connection.getName()}
|
||||
connection={connection}
|
||||
activate={() => tryActivation(connection)}
|
||||
pendingConnectionType={pendingConnection?.type}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
{connections
|
||||
.filter((connection) => connection.shouldDisplay())
|
||||
.map((connection) => (
|
||||
<Option key={connection.getName()} connection={connection} />
|
||||
))}
|
||||
</OptionGrid>
|
||||
<PrivacyPolicyWrapper>
|
||||
<PrivacyPolicyNotice />
|
||||
|
||||
@@ -4,7 +4,8 @@ import { InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-e
|
||||
import { initializeConnector, MockEIP1193Provider } from '@web3-react/core'
|
||||
import { EIP1193 } from '@web3-react/eip1193'
|
||||
import { Provider as EIP1193Provider } from '@web3-react/types'
|
||||
import { Connection, ConnectionType, useGetConnection } from 'connection'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { Connection, ConnectionType } from 'connection/types'
|
||||
import useEagerlyConnect from 'hooks/useEagerlyConnect'
|
||||
import useOrderedConnections from 'hooks/useOrderedConnections'
|
||||
import { Provider } from 'react-redux'
|
||||
|
||||
46
src/components/swap/AdvancedSwapDetails.test.tsx
Normal file
46
src/components/swap/AdvancedSwapDetails.test.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import {
|
||||
TEST_ALLOWED_SLIPPAGE,
|
||||
TEST_TOKEN_1,
|
||||
TEST_TRADE_EXACT_INPUT,
|
||||
TEST_TRADE_EXACT_OUTPUT,
|
||||
toCurrencyAmount,
|
||||
} from 'test-utils/constants'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
|
||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||
|
||||
describe('AdvancedSwapDetails.tsx', () => {
|
||||
it('matches base snapshot', () => {
|
||||
const { asFragment } = render(
|
||||
<AdvancedSwapDetails trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders correct copy on mouseover', async () => {
|
||||
render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />)
|
||||
await act(() => userEvent.hover(screen.getByText('Price Impact')))
|
||||
expect(await screen.getByText(/The impact your trade has on the market price of this pool./i)).toBeVisible()
|
||||
await act(() => userEvent.hover(screen.getByText('Expected Output')))
|
||||
expect(await screen.getByText(/The amount you expect to receive at the current market price./i)).toBeVisible()
|
||||
await act(() => userEvent.hover(screen.getByText(/Minimum received/i)))
|
||||
expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible()
|
||||
})
|
||||
|
||||
it('renders correct tooltips for test trade with exact output and gas use estimate USD', async () => {
|
||||
TEST_TRADE_EXACT_OUTPUT.gasUseEstimateUSD = toCurrencyAmount(TEST_TOKEN_1, 1)
|
||||
render(<AdvancedSwapDetails trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />)
|
||||
await act(() => userEvent.hover(screen.getByText(/Maximum sent/i)))
|
||||
expect(await screen.getByText(/The minimum amount you are guaranteed to receive./i)).toBeVisible()
|
||||
await act(() => userEvent.hover(screen.getByText('Network Fee')))
|
||||
expect(await screen.getByText(/The fee paid to miners who process your transaction./i)).toBeVisible()
|
||||
})
|
||||
|
||||
it('renders loading rows when syncing', async () => {
|
||||
render(
|
||||
<AdvancedSwapDetails trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} syncing={true} />
|
||||
)
|
||||
expect(screen.getAllByTestId('loading-rows').length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
@@ -37,7 +37,7 @@ function TextWithLoadingPlaceholder({
|
||||
children: JSX.Element
|
||||
}) {
|
||||
return syncing ? (
|
||||
<LoadingRows>
|
||||
<LoadingRows data-testid="loading-rows">
|
||||
<div style={{ height: '15px', width: `${width}px` }} />
|
||||
</LoadingRows>
|
||||
) : (
|
||||
|
||||
43
src/components/swap/SwapDetailsDropdown.test.tsx
Normal file
43
src/components/swap/SwapDetailsDropdown.test.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TOKEN_1, TEST_TRADE_EXACT_INPUT, toCurrencyAmount } from 'test-utils/constants'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
|
||||
import SwapDetailsDropdown from './SwapDetailsDropdown'
|
||||
|
||||
describe('SwapDetailsDropdown.tsx', () => {
|
||||
it('renders a trade', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapDetailsDropdown
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
syncing={false}
|
||||
loading={false}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders loading state', () => {
|
||||
render(
|
||||
<SwapDetailsDropdown trade={undefined} syncing={true} loading={true} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
|
||||
)
|
||||
expect(screen.getByText('Fetching best price...')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('is interactive once loaded', async () => {
|
||||
TEST_TRADE_EXACT_INPUT.gasUseEstimateUSD = toCurrencyAmount(TEST_TOKEN_1, 1)
|
||||
render(
|
||||
<SwapDetailsDropdown
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
syncing={false}
|
||||
loading={false}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
/>
|
||||
)
|
||||
expect(screen.getByTestId('swap-details-header-row')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('trade-price-container')).toBeInTheDocument()
|
||||
await act(() => userEvent.click(screen.getByTestId('swap-details-header-row')))
|
||||
expect(screen.getByTestId('advanced-swap-details')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('swap-route-info')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -118,7 +118,12 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
|
||||
element={InterfaceElementName.SWAP_DETAILS_DROPDOWN}
|
||||
shouldLogImpression={!showDetails}
|
||||
>
|
||||
<StyledHeaderRow onClick={() => setShowDetails(!showDetails)} disabled={!trade} open={showDetails}>
|
||||
<StyledHeaderRow
|
||||
data-testid="swap-details-header-row"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
disabled={!trade}
|
||||
open={showDetails}
|
||||
>
|
||||
<RowFixed style={{ position: 'relative' }} align="center">
|
||||
{Boolean(loading || syncing) && (
|
||||
<StyledPolling>
|
||||
@@ -128,7 +133,7 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
|
||||
</StyledPolling>
|
||||
)}
|
||||
{trade ? (
|
||||
<LoadingOpacityContainer $loading={syncing}>
|
||||
<LoadingOpacityContainer $loading={syncing} data-testid="trade-price-container">
|
||||
<TradePrice price={trade.executionPrice} />
|
||||
</LoadingOpacityContainer>
|
||||
) : loading || syncing ? (
|
||||
@@ -159,11 +164,11 @@ export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSl
|
||||
<AnimatedDropdown open={showDetails}>
|
||||
<AutoColumn gap="sm" style={{ padding: '0', paddingBottom: '8px' }}>
|
||||
{trade ? (
|
||||
<StyledCard>
|
||||
<StyledCard data-testid="advanced-swap-details">
|
||||
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
|
||||
</StyledCard>
|
||||
) : null}
|
||||
{trade ? <SwapRoute trade={trade} syncing={syncing} /> : null}
|
||||
{trade ? <SwapRoute data-testid="swap-route-info" trade={trade} syncing={syncing} /> : null}
|
||||
</AutoColumn>
|
||||
</AnimatedDropdown>
|
||||
</AutoColumn>
|
||||
|
||||
27
src/components/swap/SwapModalFooter.test.tsx
Normal file
27
src/components/swap/SwapModalFooter.test.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
|
||||
import { render, screen } from 'test-utils/render'
|
||||
|
||||
import SwapModalFooter from './SwapModalFooter'
|
||||
|
||||
const swapErrorMessage = 'swap error'
|
||||
const fiatValue = { data: 123, isLoading: false }
|
||||
|
||||
describe('SwapModalFooter.tsx', () => {
|
||||
it('renders with a disabled button with no account', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapModalFooter
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
hash={undefined}
|
||||
onConfirm={() => null}
|
||||
disabledConfirm
|
||||
swapErrorMessage={swapErrorMessage}
|
||||
swapQuoteReceivedDate={undefined}
|
||||
fiatValueInput={fiatValue}
|
||||
fiatValueOutput={fiatValue}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(screen.getByTestId('confirm-swap-button')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
@@ -147,6 +147,7 @@ export default function SwapModalFooter({
|
||||
})}
|
||||
>
|
||||
<ButtonError
|
||||
data-testid="confirm-swap-button"
|
||||
onClick={onConfirm}
|
||||
disabled={disabledConfirm}
|
||||
style={{ margin: '10px 0 0 0' }}
|
||||
@@ -157,7 +158,6 @@ export default function SwapModalFooter({
|
||||
</Text>
|
||||
</ButtonError>
|
||||
</TraceEvent>
|
||||
|
||||
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
|
||||
</AutoRow>
|
||||
</>
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('SwapModalHeader.tsx', () => {
|
||||
sendAnalyticsEventMock = jest.fn()
|
||||
})
|
||||
|
||||
it('matches base snapshot, test trade exact input', () => {
|
||||
it('matches base snapshot for test trade with exact input', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapModalHeader
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
@@ -33,28 +33,9 @@ describe('SwapModalHeader.tsx', () => {
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-symbol')).toHaveTextContent(
|
||||
TEST_TRADE_EXACT_INPUT.inputAmount.currency.symbol ?? ''
|
||||
)
|
||||
expect(screen.getByTestId('output-symbol')).toHaveTextContent(
|
||||
TEST_TRADE_EXACT_INPUT.outputAmount.currency.symbol ?? ''
|
||||
)
|
||||
expect(screen.getByTestId('input-amount')).toHaveTextContent(TEST_TRADE_EXACT_INPUT.inputAmount.toExact())
|
||||
expect(screen.getByTestId('output-amount')).toHaveTextContent(TEST_TRADE_EXACT_INPUT.outputAmount.toExact())
|
||||
const recipientInfo = screen.getByTestId('recipient-info')
|
||||
expect(recipientInfo).toHaveTextContent(/Output will be sent to/i)
|
||||
expect(within(recipientInfo).getByText('0x0000...0004')).toBeVisible()
|
||||
expect(
|
||||
screen.getByText(
|
||||
'The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText(/The amount you expect to receive at the current market price./i)).toBeInTheDocument()
|
||||
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows accept changes section when available, and logs amplitude event when accept clicked', () => {
|
||||
it('shows accept changes section and logs amplitude event', () => {
|
||||
const setShouldLogModalCloseEventFn = jest.fn()
|
||||
mockSendAnalyticsEvent.mockImplementation(sendAnalyticsEventMock)
|
||||
render(
|
||||
@@ -76,7 +57,7 @@ describe('SwapModalHeader.tsx', () => {
|
||||
expect(sendAnalyticsEventMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('test trade exact output, no recipient', () => {
|
||||
it('renders correctly for test trade with exact output and no recipient', () => {
|
||||
const rendered = render(
|
||||
<SwapModalHeader
|
||||
trade={TEST_TRADE_EXACT_OUTPUT}
|
||||
|
||||
73
src/components/swap/UnsupportedCurrencyFooter.test.tsx
Normal file
73
src/components/swap/UnsupportedCurrencyFooter.test.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { useUnsupportedTokens } from 'hooks/Tokens'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { act, render, screen, waitForElementToBeRemoved, within } from 'test-utils/render'
|
||||
import { getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import UnsupportedCurrencyFooter from './UnsupportedCurrencyFooter'
|
||||
|
||||
const unsupportedTokenAddress = '0x4e83b6287588a96321B2661c5E041845fF7814af'
|
||||
const unsupportedTokenSymbol = 'ALTDOM-MAR2021'
|
||||
const unsupportedToken = new Token(1, unsupportedTokenAddress, 18, 'ALTDOM-MAR2021')
|
||||
const unsupportedTokenExplorerLink = 'www.blahblah.com'
|
||||
|
||||
jest.mock('../../hooks/Tokens')
|
||||
jest.mock('../../utils/getExplorerLink')
|
||||
|
||||
describe('UnsupportedCurrencyFooter.tsx with unsupported tokens', () => {
|
||||
beforeEach(() => {
|
||||
mocked(useUnsupportedTokens).mockReturnValue({ [unsupportedTokenAddress]: unsupportedToken })
|
||||
mocked(getExplorerLink).mockReturnValue(unsupportedTokenExplorerLink)
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
const { asFragment } = render(<UnsupportedCurrencyFooter show={true} currencies={[unsupportedToken]} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('works as expected when one unsupported token exists', async () => {
|
||||
const rendered = render(<UnsupportedCurrencyFooter show={true} currencies={[unsupportedToken]} />)
|
||||
await act(() => userEvent.click(screen.getByTestId('read-more-button')))
|
||||
expect(screen.getByText('Unsupported Assets')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText((content) => content.startsWith('Some assets are not available through this interface'))
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('unsupported-token-card').length).toBe(1)
|
||||
const unsupportedCard = screen.getByTestId('unsupported-token-card')
|
||||
expect(within(unsupportedCard).getByText(unsupportedTokenSymbol)).toBeInTheDocument()
|
||||
expect(within(unsupportedCard).getByText(unsupportedTokenAddress).closest('a')).toHaveAttribute(
|
||||
'href',
|
||||
unsupportedTokenExplorerLink
|
||||
)
|
||||
await act(() => userEvent.click(screen.getByTestId('close-icon')))
|
||||
await waitForElementToBeRemoved(rendered.queryByTestId('unsupported-token-card'))
|
||||
expect(rendered.queryByText('Unsupported Assets')).toBeNull()
|
||||
expect(rendered.queryByTestId('unsupported-token-card')).toBeNull()
|
||||
expect(
|
||||
rendered.queryByText((content) => content.startsWith('Some assets are not available through this interface'))
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('UnsupportedCurrencyFooter.tsx with no unsupported tokens', () => {
|
||||
beforeEach(() => {
|
||||
mocked(useUnsupportedTokens).mockReturnValue({})
|
||||
})
|
||||
|
||||
it('works as expected when no unsupported tokens exist', async () => {
|
||||
const rendered = render(<UnsupportedCurrencyFooter show={true} currencies={[unsupportedToken]} />)
|
||||
await act(() => userEvent.click(screen.getByTestId('read-more-button')))
|
||||
expect(screen.getByText('Unsupported Assets')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText((content) => content.startsWith('Some assets are not available through this interface'))
|
||||
).toBeInTheDocument()
|
||||
expect(rendered.queryByTestId('unsupported-token-card')).toBeNull()
|
||||
await act(() => userEvent.click(screen.getByTestId('close-icon')))
|
||||
await waitForElementToBeRemoved(screen.getByText('Unsupported Assets'))
|
||||
expect(rendered.queryByText('Unsupported Assets')).toBeNull()
|
||||
expect(
|
||||
rendered.queryByText((content) => content.startsWith('Some assets are not available through this interface'))
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -74,14 +74,14 @@ export default function UnsupportedCurrencyFooter({
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Unsupported Assets</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<CloseIcon onClick={() => setShowDetails(false)} />
|
||||
<CloseIcon onClick={() => setShowDetails(false)} data-testid="close-icon" />
|
||||
</RowBetween>
|
||||
{tokens.map((token) => {
|
||||
return (
|
||||
token &&
|
||||
unsupportedTokens &&
|
||||
Object.keys(unsupportedTokens).includes(token.address) && (
|
||||
<OutlineCard key={token.address?.concat('not-supported')}>
|
||||
<OutlineCard key={token.address?.concat('not-supported')} data-testid="unsupported-token-card">
|
||||
<AutoColumn gap="10px">
|
||||
<AutoRow gap="5px" align="center">
|
||||
<CurrencyLogo currency={token} size="24px" />
|
||||
@@ -108,7 +108,7 @@ export default function UnsupportedCurrencyFooter({
|
||||
</AutoColumn>
|
||||
</Card>
|
||||
</Modal>
|
||||
<StyledButtonEmpty padding="0" onClick={() => setShowDetails(true)}>
|
||||
<StyledButtonEmpty padding="0" onClick={() => setShowDetails(true)} data-testid="read-more-button">
|
||||
<ThemedText.DeprecatedBlue>
|
||||
<Trans>Read more about unsupported assets</Trans>
|
||||
</ThemedText.DeprecatedBlue>
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AdvancedSwapDetails.tsx matches base snapshot 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #D2D9EE;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: 8px;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1 c2"
|
||||
>
|
||||
<div
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="c0 c4 c5"
|
||||
>
|
||||
<div
|
||||
class="c0 c4 c6"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
Expected Output
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c8 css-q4yjm0"
|
||||
>
|
||||
0.000000000000001 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c4 c5"
|
||||
>
|
||||
<div
|
||||
class="c0 c4 c6"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
Price Impact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c8 css-q4yjm0"
|
||||
>
|
||||
<div
|
||||
class="c9 css-1aekuku"
|
||||
>
|
||||
105567.37%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10"
|
||||
/>
|
||||
<div
|
||||
class="c0 c4 c5"
|
||||
>
|
||||
<div
|
||||
class="c0 c4 c6"
|
||||
style="margin-right: 20px;"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
Minimum received after slippage (2.00%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="css-q4yjm0"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -0,0 +1,807 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
width: auto;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.c38 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
margin: -4px;
|
||||
}
|
||||
|
||||
.c22 > * {
|
||||
margin: 4px !important;
|
||||
}
|
||||
|
||||
.c39 {
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
.c39 > * {
|
||||
margin: 1px !important;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c44 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #D2D9EE;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
border: 1px solid #B8C0DC;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: 8px;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
-webkit-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
background: #E8ECFB;
|
||||
border: unset;
|
||||
border-radius: 0.5rem;
|
||||
color: #000;
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
padding: 4px 6px;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.c29 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(white 60%,#ffffff00 calc(70% + 1px));
|
||||
box-shadow: 0 0 1px white;
|
||||
}
|
||||
|
||||
.c28 {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.c41 {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.c42 {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.c43 {
|
||||
position: absolute;
|
||||
left: -10px !important;
|
||||
}
|
||||
|
||||
.c26 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c27 {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 1fr 24px;
|
||||
}
|
||||
|
||||
.c30 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
padding: 0.1rem 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c40 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 4px 4px;
|
||||
}
|
||||
|
||||
.c31 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: calc(100%);
|
||||
z-index: 1;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.c32 path {
|
||||
stroke: #98A1C0;
|
||||
}
|
||||
|
||||
.c34 {
|
||||
background-color: #E8ECFB;
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
font-size: 12px;
|
||||
grid-gap: 4px;
|
||||
grid-auto-flow: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: start;
|
||||
padding: 4px 6px 4px 4px;
|
||||
z-index: 1020;
|
||||
}
|
||||
|
||||
.c35 {
|
||||
background-color: #B8C0DC;
|
||||
border-radius: 4px;
|
||||
color: #7780A0;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
z-index: 1021;
|
||||
}
|
||||
|
||||
.c36 {
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.c23:hover {
|
||||
-webkit-filter: brightness(1.3);
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
|
||||
.c24 {
|
||||
line-height: 1rem;
|
||||
color: #40B66B;
|
||||
}
|
||||
|
||||
.c19 {
|
||||
padding: 12px 8px 12px 12px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #D2D9EE;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
margin-left: 8px;
|
||||
height: 20px;
|
||||
stroke-width: 2px;
|
||||
-webkit-transition: -webkit-transform 0.1s;
|
||||
-webkit-transition: transform 0.1s;
|
||||
transition: transform 0.1s;
|
||||
-webkit-transform: none;
|
||||
-ms-transform: none;
|
||||
transform: none;
|
||||
stroke: #98A1C0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c25:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 0.25rem;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 0;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
width: 100%;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
border-radius: inherit;
|
||||
padding: 8px 12px;
|
||||
margin-top: 0;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
padding: 12px;
|
||||
border: 1px solid #D2D9EE;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
-webkit-transform: none;
|
||||
-ms-transform: none;
|
||||
transform: none;
|
||||
-webkit-transition: -webkit-transform 0.1s linear;
|
||||
-webkit-transition: transform 0.1s linear;
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
|
||||
@supports (-webkit-background-clip:text) and (-webkit-text-fill-color:transparent) {
|
||||
.c24 {
|
||||
background-image: linear-gradient(90deg,#2172e5 0%,#54e521 163.16%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1 c2"
|
||||
style="margin-top: 0px;"
|
||||
>
|
||||
<div
|
||||
class="c3"
|
||||
style="width: 100%; margin-bottom: -8px;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c4 c5"
|
||||
data-testid="swap-details-header-row"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c6"
|
||||
style="position: relative;"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
data-testid="trade-price-container"
|
||||
>
|
||||
<button
|
||||
class="c8"
|
||||
title="1 DEF = 1.00 ABC "
|
||||
>
|
||||
<div
|
||||
class="c9 css-zhpkf8"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c6"
|
||||
>
|
||||
<svg
|
||||
class="c10"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="#98A1C0"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline
|
||||
points="6 9 12 15 18 9"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="height: 0px; overflow: hidden; width: 100%; will-change: height;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c3"
|
||||
style="padding: 0px 0px 8px 0px;"
|
||||
>
|
||||
<div
|
||||
class="c0 c11 c12 c13"
|
||||
data-testid="advanced-swap-details"
|
||||
>
|
||||
<div
|
||||
class="c0 c11 c14"
|
||||
>
|
||||
<div
|
||||
class="c3"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c6"
|
||||
>
|
||||
<div
|
||||
class="c15"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
Expected Output
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-q4yjm0"
|
||||
>
|
||||
0.000000000000001 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c6"
|
||||
>
|
||||
<div
|
||||
class="c15"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
Price Impact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-q4yjm0"
|
||||
>
|
||||
<div
|
||||
class="c16 css-1aekuku"
|
||||
>
|
||||
105567.37%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c17"
|
||||
/>
|
||||
<div
|
||||
class="c0 c1 c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c6"
|
||||
style="margin-right: 20px;"
|
||||
>
|
||||
<div
|
||||
class="c15"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
Minimum received after slippage (2.00%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="css-q4yjm0"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c18 c19"
|
||||
data-testid="swap-route-info"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c4"
|
||||
>
|
||||
<div
|
||||
class="c20 c21 c22"
|
||||
width="auto"
|
||||
>
|
||||
<svg
|
||||
class="c23"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 23 20"
|
||||
width="23"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<lineargradient
|
||||
gradientTransform="rotate(95)"
|
||||
id="AutoRouterIconGradient0"
|
||||
x1="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop
|
||||
id="stop1"
|
||||
offset="0"
|
||||
stop-color="#2274E2"
|
||||
/>
|
||||
<stop
|
||||
id="stop1"
|
||||
offset="0.5"
|
||||
stop-color="#2274E2"
|
||||
/>
|
||||
<stop
|
||||
id="stop2"
|
||||
offset="1"
|
||||
stop-color="#3FB672"
|
||||
/>
|
||||
</lineargradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z"
|
||||
stroke="url(#AutoRouterIconGradient0)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="c9 c24 css-1aekuku"
|
||||
>
|
||||
Auto Router
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
class="c25"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="5"
|
||||
y2="19"
|
||||
/>
|
||||
<line
|
||||
x1="5"
|
||||
x2="19"
|
||||
y1="12"
|
||||
y2="12"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
style="height: 0px; overflow: hidden; width: 100%; will-change: height;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c20 c21 c22"
|
||||
style="padding-top: 12px; margin: 0px;"
|
||||
width="auto"
|
||||
>
|
||||
<div
|
||||
class="c26 css-vurnku"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c27"
|
||||
>
|
||||
<div
|
||||
class="c28"
|
||||
>
|
||||
<img
|
||||
alt="ABC logo"
|
||||
class="c29"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c30"
|
||||
>
|
||||
<div
|
||||
class="c31"
|
||||
>
|
||||
<svg
|
||||
class="c32"
|
||||
>
|
||||
dot_line.svg
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="c33 c34"
|
||||
>
|
||||
<div
|
||||
class="c33 c35"
|
||||
>
|
||||
<div
|
||||
class="c36 css-15li2d9"
|
||||
>
|
||||
V3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c36 css-1aekuku"
|
||||
style="min-width: auto;"
|
||||
>
|
||||
100%
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c37 c38 c39"
|
||||
style="justify-content: space-evenly; z-index: 2;"
|
||||
width="100%"
|
||||
>
|
||||
<div
|
||||
class="c15"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c33 c40"
|
||||
>
|
||||
<div
|
||||
class="css-mbnpt3"
|
||||
>
|
||||
<div
|
||||
class="c41"
|
||||
>
|
||||
<div
|
||||
class="c42"
|
||||
>
|
||||
<div
|
||||
class="c28"
|
||||
>
|
||||
<img
|
||||
alt="DEF logo"
|
||||
class="c29"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c43"
|
||||
>
|
||||
<div
|
||||
class="c28"
|
||||
>
|
||||
<img
|
||||
alt="ABC logo"
|
||||
class="c29"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="css-1aekuku"
|
||||
>
|
||||
1%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c28"
|
||||
>
|
||||
<img
|
||||
alt="DEF logo"
|
||||
class="c29"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c17"
|
||||
/>
|
||||
<div
|
||||
class="c44 css-65u4ng"
|
||||
>
|
||||
This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
251
src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap
Normal file
251
src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap
Normal file
@@ -0,0 +1,251 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: inherit;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: white;
|
||||
background-color: primary;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.c2 > * {
|
||||
margin: !important;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: #0D111C;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-flex-wrap: nowrap;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
-webkit-transition: -webkit-transform 450ms ease;
|
||||
-webkit-transition: transform 450ms ease;
|
||||
transition: transform 450ms ease;
|
||||
-webkit-transform: perspective(1px) translateZ(0);
|
||||
-ms-transform: perspective(1px) translateZ(0);
|
||||
transform: perspective(1px) translateZ(0);
|
||||
}
|
||||
|
||||
.c4:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c4 > * {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.c4 > a {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
background-color: #FB118E;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
padding: 16px;
|
||||
color: #F5F6FC;
|
||||
}
|
||||
|
||||
.c5:focus {
|
||||
box-shadow: 0 0 0 1pt #ee0481;
|
||||
background-color: #ee0481;
|
||||
}
|
||||
|
||||
.c5:hover {
|
||||
background-color: #ee0481;
|
||||
}
|
||||
|
||||
.c5:active {
|
||||
box-shadow: 0 0 0 1pt #d50474;
|
||||
background-color: #d50474;
|
||||
}
|
||||
|
||||
.c5:disabled {
|
||||
background-color: #E8ECFB;
|
||||
color: #7780A0;
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
background-color: rgba(250,43,57,0.1);
|
||||
border-radius: 1rem;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 0.825rem;
|
||||
width: 100%;
|
||||
padding: 3rem 1.25rem 1rem 1rem;
|
||||
margin-top: -2rem;
|
||||
color: #FA2B39;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.c6 p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
background-color: rgba(250,43,57,0.1);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
border-radius: 12px;
|
||||
min-width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1 c2"
|
||||
>
|
||||
<button
|
||||
class="c3 c4 c5"
|
||||
data-testid="confirm-swap-button"
|
||||
disabled=""
|
||||
id="confirm-swap-or-send"
|
||||
style="margin: 10px 0px 0px 0px;"
|
||||
>
|
||||
<div
|
||||
class="css-10ob8xa"
|
||||
>
|
||||
Confirm Swap
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="c6"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p
|
||||
style="word-break: break-word;"
|
||||
>
|
||||
swap error
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = `
|
||||
exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact input 1`] = `
|
||||
<DocumentFragment>
|
||||
.c1 {
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UnsupportedCurrencyFooter.tsx with unsupported tokens renders 1`] = `
|
||||
<DocumentFragment>
|
||||
.c1 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: inherit;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: white;
|
||||
background-color: primary;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
color: #FB118E;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: #0D111C;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-flex-wrap: nowrap;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
-webkit-transition: -webkit-transform 450ms ease;
|
||||
-webkit-transition: transform 450ms ease;
|
||||
transition: transform 450ms ease;
|
||||
-webkit-transform: perspective(1px) translateZ(0);
|
||||
-ms-transform: perspective(1px) translateZ(0);
|
||||
transform: perspective(1px) translateZ(0);
|
||||
}
|
||||
|
||||
.c2:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c2 > * {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.c2 > a {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
background-color: transparent;
|
||||
color: #FB118E;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c3:focus {
|
||||
-webkit-text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.c3:hover {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c3:active {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c3:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
padding-top: calc(16px + 2rem);
|
||||
padding-bottom: 20px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: -2rem;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
color: #7780A0;
|
||||
background-color: #FFFFFF99;
|
||||
z-index: 0;
|
||||
-webkit-transform: translateY(0%);
|
||||
-ms-transform: translateY(0%);
|
||||
transform: translateY(0%);
|
||||
-webkit-transition: -webkit-transform 300ms ease-in-out;
|
||||
-webkit-transition: transform 300ms ease-in-out;
|
||||
transition: transform 300ms ease-in-out;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<button
|
||||
class="c1 c2 c3 c4"
|
||||
data-testid="read-more-button"
|
||||
>
|
||||
<div
|
||||
class="c5 css-8mokm4"
|
||||
>
|
||||
Read more about unsupported assets
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
250
src/connection/activate.test.ts
Normal file
250
src/connection/activate.test.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { Web3ReactHooks } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { createDeferredPromise } from 'test-utils/promise'
|
||||
|
||||
import { act, renderHook } from '../test-utils/render'
|
||||
import { ActivationStatus, useActivationState } from './activate'
|
||||
import { Connection, ConnectionType } from './types'
|
||||
import { ErrorCode } from './utils'
|
||||
|
||||
class MockConnector extends Connector {
|
||||
activate: () => void
|
||||
deactivate: () => void
|
||||
resetState = jest.fn()
|
||||
|
||||
constructor(activate: () => void, deactivate?: () => void) {
|
||||
const actions = {
|
||||
startActivation: jest.fn(),
|
||||
update: jest.fn(),
|
||||
resetState: jest.fn(),
|
||||
}
|
||||
super(actions)
|
||||
|
||||
this.activate = activate ?? jest.fn()
|
||||
this.deactivate = deactivate ?? jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
function createMockConnection(
|
||||
activate: () => void,
|
||||
deactivate?: () => void,
|
||||
type = ConnectionType.INJECTED
|
||||
): Connection {
|
||||
return {
|
||||
getName: () => 'Test Connection',
|
||||
hooks: {} as unknown as Web3ReactHooks,
|
||||
type,
|
||||
shouldDisplay: () => true,
|
||||
connector: new MockConnector(activate, deactivate),
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockReturnValue()
|
||||
jest.spyOn(console, 'debug').mockReturnValue()
|
||||
})
|
||||
|
||||
it('Should initialize with proper IDLE state', async () => {
|
||||
const result = renderHook(useActivationState).result
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
})
|
||||
|
||||
it('Should call activate function on a connection', async () => {
|
||||
const activationResponse = createDeferredPromise()
|
||||
const mockConnection = createMockConnection(jest.fn().mockImplementation(() => activationResponse.promise))
|
||||
|
||||
const result = renderHook(useActivationState).result
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
let activationCall: Promise<void> = new Promise(jest.fn())
|
||||
act(() => {
|
||||
activationCall = result.current.tryActivation(mockConnection, onSuccess)
|
||||
})
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.PENDING, connection: mockConnection })
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(console.debug).toHaveBeenLastCalledWith(`Connection activating: ${mockConnection.getName()}`)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
|
||||
await act(async () => {
|
||||
activationResponse.resolve()
|
||||
})
|
||||
await activationCall
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(console.debug).toHaveBeenLastCalledWith(`Connection activated: ${mockConnection.getName()}`)
|
||||
expect(console.debug).toHaveBeenCalledTimes(2)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('Should properly deactivate pending connection attempts', async () => {
|
||||
const mockConnection = createMockConnection(
|
||||
jest.fn().mockImplementation(() => new Promise(jest.fn())),
|
||||
jest.fn().mockImplementation(() => Promise.resolve())
|
||||
)
|
||||
|
||||
const result = renderHook(useActivationState).result
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
act(() => {
|
||||
result.current.tryActivation(mockConnection, onSuccess)
|
||||
})
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.PENDING, connection: mockConnection })
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
|
||||
await act(() => result.current.cancelActivation())
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(mockConnection.connector.deactivate).toHaveBeenCalledTimes(1)
|
||||
expect(console.debug).not.toHaveBeenLastCalledWith(`Connection activated: ${mockConnection.getName()}`)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('Should properly display error state', async () => {
|
||||
const activationResponse = createDeferredPromise()
|
||||
const mockConnection = createMockConnection(
|
||||
jest.fn().mockImplementation(() => activationResponse.promise),
|
||||
jest.fn().mockImplementation(() => Promise.resolve())
|
||||
)
|
||||
|
||||
const result = renderHook(useActivationState).result
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
act(() => {
|
||||
result.current.tryActivation(mockConnection, onSuccess)
|
||||
})
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.PENDING, connection: mockConnection })
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
|
||||
await act(async () => {
|
||||
activationResponse.reject('Failed to connect')
|
||||
})
|
||||
|
||||
expect(result.current.activationState).toEqual({
|
||||
status: ActivationStatus.ERROR,
|
||||
connection: mockConnection,
|
||||
error: 'Failed to connect',
|
||||
})
|
||||
expect(console.debug).toHaveBeenLastCalledWith(`Connection failed: ${mockConnection.getName()}`)
|
||||
expect(console.debug).toHaveBeenCalledTimes(2)
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('Should successfully retry a failed activation', async () => {
|
||||
const mockConnection = createMockConnection(
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => Promise.reject('Failed to connect'))
|
||||
.mockImplementationOnce(() => Promise.resolve())
|
||||
)
|
||||
|
||||
const result = renderHook(useActivationState).result
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
await act(() => result.current.tryActivation(mockConnection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({
|
||||
status: ActivationStatus.ERROR,
|
||||
connection: mockConnection,
|
||||
error: 'Failed to connect',
|
||||
})
|
||||
expect(console.debug).toHaveBeenLastCalledWith(`Connection failed: ${mockConnection.getName()}`)
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
|
||||
await act(() => result.current.tryActivation(mockConnection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(2)
|
||||
expect(console.debug).toHaveBeenLastCalledWith(`Connection activated: ${mockConnection.getName()}`)
|
||||
expect(console.debug).toHaveBeenCalledTimes(4)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('Should gracefully handle intentional user-rejection errors', () => {
|
||||
it('handles Injected user-rejection error', async () => {
|
||||
const result = renderHook(useActivationState).result
|
||||
|
||||
const injectedConection = createMockConnection(
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => Promise.reject({ code: ErrorCode.USER_REJECTED_REQUEST }))
|
||||
.mockImplementationOnce(() => Promise.resolve),
|
||||
jest.fn(),
|
||||
ConnectionType.INJECTED
|
||||
)
|
||||
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
await act(() => result.current.tryActivation(injectedConection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(injectedConection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
|
||||
await act(() => result.current.tryActivation(injectedConection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(injectedConection.connector.activate).toHaveBeenCalledTimes(2)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('handles Coinbase user-rejection error', async () => {
|
||||
const result = renderHook(useActivationState).result
|
||||
|
||||
const coinbaseConnection = createMockConnection(
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => Promise.reject(ErrorCode.CB_REJECTED_REQUEST))
|
||||
.mockImplementationOnce(() => Promise.resolve),
|
||||
jest.fn(),
|
||||
ConnectionType.COINBASE_WALLET
|
||||
)
|
||||
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
await act(() => result.current.tryActivation(coinbaseConnection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(coinbaseConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
|
||||
await act(() => result.current.tryActivation(coinbaseConnection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(coinbaseConnection.connector.activate).toHaveBeenCalledTimes(2)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('handles WalletConect Modal close error', async () => {
|
||||
const result = renderHook(useActivationState).result
|
||||
|
||||
const wcConnection = createMockConnection(
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => Promise.reject(ErrorCode.WC_MODAL_CLOSED))
|
||||
.mockImplementationOnce(() => Promise.resolve),
|
||||
jest.fn(),
|
||||
ConnectionType.WALLET_CONNECT
|
||||
)
|
||||
|
||||
const onSuccess = jest.fn()
|
||||
|
||||
await act(() => result.current.tryActivation(wcConnection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(wcConnection.connector.activate).toHaveBeenCalledTimes(1)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(0)
|
||||
|
||||
await act(() => result.current.tryActivation(wcConnection, onSuccess))
|
||||
|
||||
expect(result.current.activationState).toEqual({ status: ActivationStatus.IDLE })
|
||||
expect(wcConnection.connector.activate).toHaveBeenCalledTimes(2)
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
91
src/connection/activate.ts
Normal file
91
src/connection/activate.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
|
||||
import { Connection } from 'connection/types'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
|
||||
import { didUserReject } from './utils'
|
||||
|
||||
export enum ActivationStatus {
|
||||
PENDING,
|
||||
ERROR,
|
||||
IDLE,
|
||||
}
|
||||
|
||||
type ActivationPendingState = { status: ActivationStatus.PENDING; connection: Connection }
|
||||
type ActivationErrorState = { status: ActivationStatus.ERROR; connection: Connection; error: any }
|
||||
const IDLE_ACTIVATION_STATE = { status: ActivationStatus.IDLE } as const
|
||||
type ActivationState = ActivationPendingState | ActivationErrorState | typeof IDLE_ACTIVATION_STATE
|
||||
|
||||
const activationStateAtom = atom<ActivationState>(IDLE_ACTIVATION_STATE)
|
||||
|
||||
function useTryActivation() {
|
||||
const dispatch = useAppDispatch()
|
||||
const setActivationState = useUpdateAtom(activationStateAtom)
|
||||
|
||||
return useCallback(
|
||||
async (connection: Connection, onSuccess: () => void) => {
|
||||
/*
|
||||
* Skips wallet connection if the connection should override the default
|
||||
* behavior, i.e. install MetaMask or launch Coinbase app
|
||||
*/
|
||||
if (connection.overrideActivate?.()) return
|
||||
|
||||
try {
|
||||
setActivationState({ status: ActivationStatus.PENDING, connection })
|
||||
|
||||
console.debug(`Connection activating: ${connection.getName()}`)
|
||||
await connection.connector.activate()
|
||||
|
||||
console.debug(`Connection activated: ${connection.getName()}`)
|
||||
dispatch(updateSelectedWallet({ wallet: connection.type }))
|
||||
|
||||
// Clears pending connection state
|
||||
setActivationState(IDLE_ACTIVATION_STATE)
|
||||
|
||||
onSuccess()
|
||||
} catch (error) {
|
||||
// TODO(WEB-3162): re-add special treatment for already-pending injected errors & move debug to after didUserReject() check
|
||||
console.debug(`Connection failed: ${connection.getName()}`)
|
||||
console.error(error)
|
||||
|
||||
// Gracefully handles errors from the user rejecting a connection attempt
|
||||
if (didUserReject(connection, error)) {
|
||||
setActivationState(IDLE_ACTIVATION_STATE)
|
||||
return
|
||||
}
|
||||
|
||||
// Failed Connection events are logged here, while successful ones are logged by Web3Provider
|
||||
sendAnalyticsEvent(InterfaceEventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WalletConnectionResult.FAILED,
|
||||
wallet_type: connection.getName(),
|
||||
})
|
||||
setActivationState({ status: ActivationStatus.ERROR, connection, error })
|
||||
}
|
||||
},
|
||||
[dispatch, setActivationState]
|
||||
)
|
||||
}
|
||||
|
||||
function useCancelActivation() {
|
||||
const setActivationState = useUpdateAtom(activationStateAtom)
|
||||
return useCallback(
|
||||
() =>
|
||||
setActivationState((activationState) => {
|
||||
if (activationState.status !== ActivationStatus.IDLE) activationState.connection.connector.deactivate?.()
|
||||
return IDLE_ACTIVATION_STATE
|
||||
}),
|
||||
[setActivationState]
|
||||
)
|
||||
}
|
||||
|
||||
export function useActivationState() {
|
||||
const activationState = useAtomValue(activationStateAtom)
|
||||
const tryActivation = useTryActivation()
|
||||
const cancelActivation = useCancelActivation()
|
||||
|
||||
return { activationState, tryActivation, cancelActivation }
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import INJECTED_DARK_ICON from 'assets/svg/browser-wallet-dark.svg'
|
||||
import INJECTED_LIGHT_ICON from 'assets/svg/browser-wallet-light.svg'
|
||||
import { ConnectionType, getConnections, useGetConnection } from 'connection'
|
||||
import { getConnections, useGetConnection } from 'connection'
|
||||
import { renderHook } from 'test-utils/render'
|
||||
|
||||
import { ConnectionType } from './types'
|
||||
|
||||
const UserAgentMock = jest.requireMock('utils/userAgent')
|
||||
jest.mock('utils/userAgent', () => ({
|
||||
isMobile: false,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CoinbaseWallet } from '@web3-react/coinbase-wallet'
|
||||
import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
|
||||
import { initializeConnector } from '@web3-react/core'
|
||||
import { GnosisSafe } from '@web3-react/gnosis-safe'
|
||||
import { MetaMask } from '@web3-react/metamask'
|
||||
import { Network } from '@web3-react/network'
|
||||
@@ -18,29 +18,10 @@ import { isMobile, isNonIOSPhone } from 'utils/userAgent'
|
||||
|
||||
import { RPC_URLS } from '../constants/networks'
|
||||
import { RPC_PROVIDERS } from '../constants/providers'
|
||||
import { Connection, ConnectionType } from './types'
|
||||
import { getIsCoinbaseWallet, getIsInjected, getIsMetaMaskWallet } from './utils'
|
||||
import { UniwalletConnect, WalletConnectPopup } from './WalletConnect'
|
||||
|
||||
export enum ConnectionType {
|
||||
UNIWALLET = 'UNIWALLET',
|
||||
INJECTED = 'INJECTED',
|
||||
COINBASE_WALLET = 'COINBASE_WALLET',
|
||||
WALLET_CONNECT = 'WALLET_CONNECT',
|
||||
NETWORK = 'NETWORK',
|
||||
GNOSIS_SAFE = 'GNOSIS_SAFE',
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
getName(): string
|
||||
connector: Connector
|
||||
hooks: Web3ReactHooks
|
||||
type: ConnectionType
|
||||
getIcon?(isDarkMode: boolean): string
|
||||
shouldDisplay(): boolean
|
||||
overrideActivate?: () => boolean
|
||||
isNew?: boolean
|
||||
}
|
||||
|
||||
function onError(error: Error) {
|
||||
console.debug(`web3-react error: ${error}`)
|
||||
}
|
||||
|
||||
22
src/connection/types.ts
Normal file
22
src/connection/types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Web3ReactHooks } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
|
||||
export enum ConnectionType {
|
||||
UNIWALLET = 'UNIWALLET',
|
||||
INJECTED = 'INJECTED',
|
||||
COINBASE_WALLET = 'COINBASE_WALLET',
|
||||
WALLET_CONNECT = 'WALLET_CONNECT',
|
||||
NETWORK = 'NETWORK',
|
||||
GNOSIS_SAFE = 'GNOSIS_SAFE',
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
getName(): string
|
||||
connector: Connector
|
||||
hooks: Web3ReactHooks
|
||||
type: ConnectionType
|
||||
getIcon?(isDarkMode: boolean): string
|
||||
shouldDisplay(): boolean
|
||||
overrideActivate?: () => boolean
|
||||
isNew?: boolean
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Connection, ConnectionType } from 'connection/types'
|
||||
|
||||
export const getIsInjected = () => Boolean(window.ethereum)
|
||||
|
||||
// When using Brave browser, `isMetaMask` is set to true when using the built-in wallet
|
||||
@@ -25,3 +27,12 @@ export enum ErrorCode {
|
||||
WC_MODAL_CLOSED = 'Error: User closed modal',
|
||||
CB_REJECTED_REQUEST = 'Error: User denied account authorization',
|
||||
}
|
||||
|
||||
// TODO(WEB-3279): merge this function with existing didUserReject for Swap errors
|
||||
export function didUserReject(connection: Connection, error: any): boolean {
|
||||
return (
|
||||
error?.code === ErrorCode.USER_REJECTED_REQUEST ||
|
||||
(connection.type === ConnectionType.WALLET_CONNECT && error?.toString?.() === ErrorCode.WC_MODAL_CLOSED) ||
|
||||
(connection.type === ConnectionType.COINBASE_WALLET && error?.toString?.() === ErrorCode.CB_REJECTED_REQUEST)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@ export enum SupportedChainId {
|
||||
BNB = 56,
|
||||
}
|
||||
|
||||
export const UniWalletSupportedChains = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.POLYGON,
|
||||
]
|
||||
|
||||
export const CHAIN_IDS_TO_NAMES = {
|
||||
[SupportedChainId.MAINNET]: 'mainnet',
|
||||
[SupportedChainId.GOERLI]: 'goerli',
|
||||
|
||||
@@ -6,6 +6,10 @@ const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/se
|
||||
const CMC_ALL_LIST = 'https://s3.coinmarketcap.com/generated/dex/tokens/eth-tokens-all.json'
|
||||
const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
|
||||
const COINGECKO_BNB_LIST = 'https://tokens.coingecko.com/binance-smart-chain/all.json'
|
||||
const COINGECKO_ARBITRUM_LIST = 'https://tokens.coingecko.com/arbitrum-one/all.json'
|
||||
const COINGECKO_OPTIMISM_LIST = 'https://tokens.coingecko.com/optimistic-ethereum/all.json'
|
||||
const COINGECKO_CELO_LIST = 'https://tokens.coingecko.com/celo/all.json'
|
||||
const COINGECKO_POLYGON_LIST = 'https://tokens.coingecko.com/polygon-pos/all.json'
|
||||
const COMPOUND_LIST = 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json'
|
||||
const GEMINI_LIST = 'https://www.gemini.com/uniswap/manifest.json'
|
||||
const KLEROS_LIST = 't2crtokens.eth'
|
||||
@@ -28,6 +32,10 @@ export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
|
||||
CMC_ALL_LIST,
|
||||
COINGECKO_LIST,
|
||||
COINGECKO_BNB_LIST,
|
||||
COINGECKO_ARBITRUM_LIST,
|
||||
COINGECKO_OPTIMISM_LIST,
|
||||
COINGECKO_CELO_LIST,
|
||||
COINGECKO_POLYGON_LIST,
|
||||
KLEROS_LIST,
|
||||
GEMINI_LIST,
|
||||
WRAPPED_LIST,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { Connection, gnosisSafeConnection, networkConnection } from 'connection'
|
||||
import { gnosisSafeConnection, networkConnection } from 'connection'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { Connection } from 'connection/types'
|
||||
import { useEffect } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConnectionType } from 'connection'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
import { useMemo } from 'react'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TEST_NFT_ACTIVITY_EVENT } from 'test-utils/constants'
|
||||
import { TEST_NFT_ACTIVITY_EVENT } from 'test-utils/nft/fixtures'
|
||||
import { render, screen } from 'test-utils/render'
|
||||
|
||||
import { BuyCell } from './ActivityCells'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UniformAspectRatios } from 'nft/types'
|
||||
import { TEST_NFT_ASSET } from 'test-utils/constants'
|
||||
import { TEST_NFT_ASSET } from 'test-utils/nft/fixtures'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { CollectionAsset } from './CollectionAsset'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TEST_NFT_ASSET } from 'test-utils/constants'
|
||||
import { TEST_NFT_ASSET } from 'test-utils/nft/fixtures'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { DataPage } from './DataPage'
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from 'nft/components/profile/list/utils'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { ListingMarket, WalletAsset } from 'nft/types'
|
||||
import { getMarketplaceIcon } from 'nft/utils'
|
||||
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import { Dispatch, DispatchWithoutAction, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
@@ -64,11 +65,10 @@ const MarketIconWrapper = styled(Column)`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const MarketIcon = styled.img<{ index: number }>`
|
||||
const MarketIcon = styled.div<{ index: number }>`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
z-index: ${({ index }) => 2 - index};
|
||||
margin-left: ${({ index }) => `${index === 0 ? 0 : -8}px`};
|
||||
outline: 1px solid ${({ theme }) => theme.backgroundInteractive};
|
||||
@@ -214,7 +214,7 @@ export const MarketplaceRow = ({
|
||||
removeMarket && removeMarket()
|
||||
}}
|
||||
>
|
||||
<MarketIcon alt={market.name} src={market.icon} index={index} />
|
||||
<MarketIcon index={index}>{getMarketplaceIcon(market.name, '20')}</MarketIcon>
|
||||
<RemoveMarketplaceWrap hovered={marketIconHovered && (expandMarketplaceRows ?? false)}>
|
||||
<img width="20px" src="/nft/svgs/minusCircle.svg" alt="Remove item" />
|
||||
</RemoveMarketplaceWrap>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Loader from 'components/Icons/LoadingSpinner'
|
||||
import Row from 'components/Row'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { AssetRow, CollectionRow, ListingStatus } from 'nft/types'
|
||||
import { getMarketplaceIcon } from 'nft/utils'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Check, XOctagon } from 'react-feather'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
@@ -37,7 +38,7 @@ const AssetIcon = styled.img`
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const MarketplaceIcon = styled.img`
|
||||
const MarketplaceIcon = styled.div`
|
||||
border-radius: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
@@ -137,8 +138,8 @@ export const ContentRow = ({
|
||||
failed={failed}
|
||||
ref={rowRef}
|
||||
>
|
||||
{isCollectionApprovalSection ? <CollectionIcon src={row.images[0]} /> : <AssetIcon src={row.images[0]} />}
|
||||
<MarketplaceIcon src={row.images[1]} />
|
||||
{isCollectionApprovalSection ? <CollectionIcon src={row.image} /> : <AssetIcon src={row.image} />}
|
||||
<MarketplaceIcon>{getMarketplaceIcon(row.marketplace.name, '24')}</MarketplaceIcon>
|
||||
<ContentName>{row.name}</ContentName>
|
||||
{isCollectionApprovalSection && (row as CollectionRow).isVerified && <StyledVerifiedIcon />}
|
||||
<IconWrapper>
|
||||
|
||||
@@ -143,7 +143,7 @@ export const ListModalSection = ({ sectionType, active, content, toggleSection }
|
||||
{content.map((row: AssetRow) => (
|
||||
<ContentRow
|
||||
row={row}
|
||||
key={(row?.name ?? '') + row?.images[1]}
|
||||
key={row?.name ?? row.marketplace.name}
|
||||
removeRow={removeRow}
|
||||
isCollectionApprovalSection={isCollectionApprovalSection}
|
||||
/>
|
||||
|
||||
@@ -3,8 +3,8 @@ import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { getMarketplaceFee, getRoyalty } from 'nft/components/profile/list/utils'
|
||||
import { ListingMarket, WalletAsset } from 'nft/types'
|
||||
import { formatEth } from 'nft/utils'
|
||||
import styled from 'styled-components/macro'
|
||||
import { formatEth, getMarketplaceIcon } from 'nft/utils'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
const FeeWrap = styled(Row)`
|
||||
@@ -17,17 +17,22 @@ const RoyaltyContainer = styled(Column)`
|
||||
padding: 4px 0px;
|
||||
`
|
||||
|
||||
const MarketIcon = styled.img`
|
||||
const iconStyles = css`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
object-fit: cover;
|
||||
outline: 1px solid ${({ theme }) => theme.backgroundInteractive};
|
||||
margin-right: 8px;
|
||||
`
|
||||
|
||||
const CollectionIcon = styled(MarketIcon)`
|
||||
const MarketIcon = styled.div`
|
||||
border-radius: 4px;
|
||||
${iconStyles}
|
||||
`
|
||||
|
||||
const CollectionIcon = styled.img`
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
${iconStyles}
|
||||
`
|
||||
|
||||
const FeePercent = styled(ThemedText.Caption)`
|
||||
@@ -57,7 +62,7 @@ export const RoyaltyTooltip = ({
|
||||
{selectedMarkets.map((market) => (
|
||||
<FeeWrap key={asset.collection?.address ?? '' + asset.tokenId + market.name + 'fee'}>
|
||||
<Row>
|
||||
<MarketIcon src={market.icon} />
|
||||
<MarketIcon>{getMarketplaceIcon(market.name, '16')}</MarketIcon>
|
||||
<ThemedText.Caption lineHeight="16px" marginRight="12px">
|
||||
{market.name}
|
||||
<Trans>fee</Trans>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Checkbox } from 'nft/components/layout/Checkbox'
|
||||
import { buttonTextMedium, caption } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { ListingMarket } from 'nft/types'
|
||||
import { getMarketplaceIcon } from 'nft/utils'
|
||||
import { ListingMarkets } from 'nft/utils/listNfts'
|
||||
import { Dispatch, FormEvent, useMemo, useReducer, useRef } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -25,13 +26,6 @@ const MarketplaceRowWrapper = styled(Row)`
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const MarketplaceDropdownIcon = styled.img`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
`
|
||||
|
||||
const FeeText = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
@@ -60,7 +54,7 @@ const MarketplaceRow = ({ market, setSelectedMarkets, selectedMarkets }: Marketp
|
||||
return (
|
||||
<MarketplaceRowWrapper onMouseEnter={toggleHovered} onMouseLeave={toggleHovered} onClick={toggleSelected}>
|
||||
<Row gap="12" onClick={toggleSelected}>
|
||||
<MarketplaceDropdownIcon alt={market.name} src={market.icon} />
|
||||
{getMarketplaceIcon(market.name, '24')}
|
||||
<Column>
|
||||
<ThemedText.BodyPrimary>{market.name}</ThemedText.BodyPrimary>
|
||||
<FeeText className={caption}>{market.fee}% fee</FeeText>
|
||||
@@ -93,12 +87,11 @@ const HeaderButtonContentWrapper = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const MarketIcon = styled.img<{ index: number; totalSelected: number }>`
|
||||
const MarketIcon = styled.div<{ index: number; totalSelected: number }>`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 8px;
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
outline: 1px solid ${({ theme }) => theme.backgroundInteractive};
|
||||
border-radius: 4px;
|
||||
z-index: ${({ index, totalSelected }) => totalSelected - index};
|
||||
margin-left: ${({ index }) => `${index === 0 ? 0 : -18}px`};
|
||||
@@ -156,13 +149,9 @@ export const SelectMarketplacesDropdown = ({
|
||||
<HeaderButtonContentWrapper>
|
||||
{selectedMarkets.map((market, index) => {
|
||||
return (
|
||||
<MarketIcon
|
||||
key={index}
|
||||
alt={market.name}
|
||||
src={market.icon}
|
||||
totalSelected={selectedMarkets.length}
|
||||
index={index}
|
||||
/>
|
||||
<MarketIcon key={index} totalSelected={selectedMarkets.length} index={index}>
|
||||
{getMarketplaceIcon(market.name, '20')}
|
||||
</MarketIcon>
|
||||
)
|
||||
})}
|
||||
{dropdownDisplayText}
|
||||
|
||||
@@ -93,7 +93,7 @@ const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]]
|
||||
sellAssets.forEach((asset) => {
|
||||
asset.marketplaces?.forEach((marketplace: ListingMarket) => {
|
||||
const newListing = {
|
||||
images: [asset.smallImageUrl, marketplace.icon],
|
||||
image: asset.smallImageUrl,
|
||||
name: asset.name || `#${asset.tokenId}`,
|
||||
status: ListingStatus.DEFINED,
|
||||
asset,
|
||||
@@ -109,7 +109,7 @@ const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]]
|
||||
)
|
||||
) {
|
||||
const newCollectionRow = {
|
||||
images: [asset.asset_contract.image_url, marketplace.icon],
|
||||
image: asset.asset_contract.image_url,
|
||||
name: asset.asset_contract.name,
|
||||
status: ListingStatus.DEFINED,
|
||||
collectionAddress: asset.asset_contract.address,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TEST_NFT_WALLET_ASSET } from 'test-utils/constants'
|
||||
import { TEST_NFT_WALLET_ASSET } from 'test-utils/nft/fixtures'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { ViewMyNftsAsset } from './ViewMyNftsAsset'
|
||||
|
||||
@@ -5,7 +5,6 @@ import { GenieCollection, PriceInfo } from '../common'
|
||||
export interface ListingMarket {
|
||||
name: string
|
||||
fee: number
|
||||
icon: string
|
||||
}
|
||||
|
||||
export interface SellOrder {
|
||||
@@ -89,22 +88,21 @@ export enum ListingStatus {
|
||||
}
|
||||
|
||||
export interface AssetRow {
|
||||
images: (string | undefined)[]
|
||||
image: string | undefined
|
||||
name?: string
|
||||
status: ListingStatus
|
||||
marketplace: ListingMarket
|
||||
callback?: () => Promise<void>
|
||||
}
|
||||
|
||||
export interface ListingRow extends AssetRow {
|
||||
asset: WalletAsset
|
||||
marketplace: ListingMarket
|
||||
price?: number
|
||||
}
|
||||
|
||||
export interface CollectionRow extends AssetRow {
|
||||
collectionAddress?: string
|
||||
isVerified?: boolean
|
||||
marketplace: ListingMarket
|
||||
nftStandard?: NftStandard
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,10 @@ export const ListingMarkets: ListingMarket[] = [
|
||||
{
|
||||
name: 'X2Y2',
|
||||
fee: 0.5,
|
||||
icon: '/nft/svgs/marketplaces/x2y2.svg',
|
||||
},
|
||||
{
|
||||
name: 'OpenSea',
|
||||
fee: 0,
|
||||
icon: '/nft/svgs/marketplaces/opensea.svg',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'jest-styled-components' // adds style diffs to snapshot tests
|
||||
|
||||
import type { createPopper } from '@popperjs/core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import ResizeObserver from 'resize-observer-polyfill'
|
||||
import { Readable } from 'stream'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { TextDecoder, TextEncoder } from 'util'
|
||||
@@ -16,6 +17,8 @@ if (typeof global.TextEncoder === 'undefined') {
|
||||
global.TextDecoder = TextDecoder as typeof global.TextDecoder
|
||||
}
|
||||
|
||||
global.ResizeObserver = ResizeObserver
|
||||
|
||||
global.matchMedia =
|
||||
global.matchMedia ||
|
||||
function () {
|
||||
|
||||
@@ -98,10 +98,6 @@ export function useOpenModal(modal: ApplicationModal): () => void {
|
||||
return useCallback(() => dispatch(setOpenModal(modal)), [dispatch, modal])
|
||||
}
|
||||
|
||||
export function useToggleUniwalletModal(): () => void {
|
||||
return useToggleModal(ApplicationModal.UNIWALLET_CONNECT)
|
||||
}
|
||||
|
||||
export function useToggleSettingsMenu(): () => void {
|
||||
return useToggleModal(ApplicationModal.SETTINGS)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export type PopupContent =
|
||||
}
|
||||
|
||||
export enum ApplicationModal {
|
||||
UNIWALLET_CONNECT,
|
||||
ADDRESS_CLAIM,
|
||||
BLOCKED_ACCOUNT,
|
||||
CLAIM_POPUP,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
|
||||
interface ConnectionState {
|
||||
errorByConnectionType: Record<ConnectionType, string | undefined>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
import { SupportedLocale } from 'constants/locales'
|
||||
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { V3Route } from '@uniswap/smart-order-router'
|
||||
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
||||
import { NftActivityType, NftStandard, OrderStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import JSBI from 'jsbi'
|
||||
import { ActivityEvent, GenieAsset, Markets, WalletAsset } from 'nft/types'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
|
||||
export const TEST_TOKEN_1 = new Token(1, '0x0000000000000000000000000000000000000001', 18, 'ABC', 'Abc')
|
||||
@@ -57,100 +55,3 @@ export const TEST_TRADE_EXACT_OUTPUT = new InterfaceTrade({
|
||||
})
|
||||
|
||||
export const TEST_ALLOWED_SLIPPAGE = new Percent(2, 100)
|
||||
|
||||
export const TEST_NFT_ASSET: GenieAsset = {
|
||||
id: 'TmZ0QXNzZXQ6MHhlZDVhZjM4ODY1MzU2N2FmMmYzODhlNjIyNGRjN2M0YjMyNDFjNTQ0XzMzMTg=',
|
||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
notForSale: false,
|
||||
collectionName: 'Azuki',
|
||||
imageUrl:
|
||||
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png',
|
||||
marketplace: Markets.Opensea,
|
||||
name: 'Azuki #3318',
|
||||
priceInfo: {
|
||||
ETHPrice: '15800000000000000000',
|
||||
baseAsset: 'ETH',
|
||||
baseDecimals: '18',
|
||||
basePrice: '15800000000000000000',
|
||||
},
|
||||
susFlag: false,
|
||||
tokenId: '3318',
|
||||
tokenType: NftStandard.Erc721,
|
||||
totalCount: 10000,
|
||||
collectionIsVerified: true,
|
||||
rarity: {
|
||||
primaryProvider: 'Rarity Sniper',
|
||||
providers: [
|
||||
{
|
||||
rank: 7079,
|
||||
provider: 'Rarity Sniper',
|
||||
},
|
||||
],
|
||||
},
|
||||
creator: {},
|
||||
}
|
||||
|
||||
export const TEST_NFT_WALLET_ASSET: WalletAsset = {
|
||||
id: 'TmZ0QXNzZXQ6RVRIRVJFVU1fMHgyOTY1MkMyZTlEMzY1NjQzNEJjODEzM2M2OTI1OEM4ZDA1MjkwZjQxXzIzNTk=',
|
||||
imageUrl: 'https://c.neevacdn.net/image/upload/xyz/T96PksTnWGNh79CrzLn-zpYfqRWtD5wME0MBPL_Md6Q.png',
|
||||
smallImageUrl:
|
||||
'https://c.neevacdn.net/image/upload/c_limit,pg_1,h_1200,w_1200/f_webp/xyz/T96PksTnWGNh79CrzLn-zpYfqRWtD5wME0MBPL_Md6Q.webp',
|
||||
notForSale: true,
|
||||
priceInfo: {
|
||||
ETHPrice: '0',
|
||||
baseAsset: 'ETH',
|
||||
baseDecimals: '18',
|
||||
basePrice: '0',
|
||||
},
|
||||
name: 'Froggy Friend #2359',
|
||||
tokenId: '2359',
|
||||
asset_contract: {
|
||||
address: '0x29652c2e9d3656434bc8133c69258c8d05290f41',
|
||||
tokenType: NftStandard.Erc721,
|
||||
name: 'Froggy Friends Official',
|
||||
description: '4444 of the friendliest frogs in the metaverse.',
|
||||
image_url: 'https://i.seadn.io/gcs/files/84483786d97b4d471cb48d224c4c5c91.png?w=500&auto=format',
|
||||
},
|
||||
collection: {
|
||||
address: '0x29652c2e9d3656434bc8133c69258c8d05290f41',
|
||||
name: 'Froggy Friends Official',
|
||||
isVerified: true,
|
||||
imageUrl: 'https://i.seadn.io/gcs/files/84483786d97b4d471cb48d224c4c5c91.png?w=500&auto=format',
|
||||
twitterUrl: '@FroggyFriendNFT',
|
||||
},
|
||||
collectionIsVerified: true,
|
||||
lastPrice: 0,
|
||||
floorPrice: 0.0775,
|
||||
basisPoints: 0,
|
||||
date_acquired: '1682024661',
|
||||
sellOrders: [],
|
||||
}
|
||||
|
||||
export const TEST_NFT_ACTIVITY_EVENT: ActivityEvent = {
|
||||
collectionAddress: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
tokenId: '5674',
|
||||
tokenMetadata: {
|
||||
name: 'Azuki #5674',
|
||||
imageUrl:
|
||||
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/5674/b2e5cb241d4a28bb3688ff6ae12f2d60c9850721f35f5104b5c42b31511e8a42.png',
|
||||
smallImageUrl: 'https://i.seadn.io/gcs/files/e2dabe8f353ed6354f5a1927e3d8bd64.png?w=500&auto=format',
|
||||
metadataUrl: 'ipfs://QmZcH4YvBVVRJtdn4RdbaqgspFU8gH6P9vomDpBVpAL3u4/5674',
|
||||
rarity: {
|
||||
source: 'RARITY_SNIPER',
|
||||
rank: 9412,
|
||||
score: 2778,
|
||||
},
|
||||
suspiciousFlag: false,
|
||||
standard: NftStandard.Erc721,
|
||||
},
|
||||
eventType: NftActivityType.Listing,
|
||||
marketplace: 'OPENSEA',
|
||||
fromAddress: '0xbf9fda32692b25c6083cbe48399ef019b62f0712',
|
||||
toAddress: undefined,
|
||||
transactionHash: undefined,
|
||||
price: '15.2',
|
||||
orderStatus: OrderStatus.Valid,
|
||||
quantity: 1,
|
||||
url: 'https://opensea.io/assets/0xed5af388653567af2f388e6224dc7c4b3241c544/5674',
|
||||
eventTimestamp: 1682444662,
|
||||
}
|
||||
|
||||
99
src/test-utils/nft/fixtures.ts
Normal file
99
src/test-utils/nft/fixtures.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { NftActivityType, NftStandard, OrderStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { ActivityEvent, GenieAsset, Markets, WalletAsset } from 'nft/types'
|
||||
|
||||
export const TEST_NFT_ASSET: GenieAsset = {
|
||||
id: 'TmZ0QXNzZXQ6MHhlZDVhZjM4ODY1MzU2N2FmMmYzODhlNjIyNGRjN2M0YjMyNDFjNTQ0XzMzMTg=',
|
||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
notForSale: false,
|
||||
collectionName: 'Azuki',
|
||||
imageUrl:
|
||||
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png',
|
||||
marketplace: Markets.Opensea,
|
||||
name: 'Azuki #3318',
|
||||
priceInfo: {
|
||||
ETHPrice: '15800000000000000000',
|
||||
baseAsset: 'ETH',
|
||||
baseDecimals: '18',
|
||||
basePrice: '15800000000000000000',
|
||||
},
|
||||
susFlag: false,
|
||||
tokenId: '3318',
|
||||
tokenType: NftStandard.Erc721,
|
||||
totalCount: 10000,
|
||||
collectionIsVerified: true,
|
||||
rarity: {
|
||||
primaryProvider: 'Rarity Sniper',
|
||||
providers: [
|
||||
{
|
||||
rank: 7079,
|
||||
provider: 'Rarity Sniper',
|
||||
},
|
||||
],
|
||||
},
|
||||
creator: {},
|
||||
}
|
||||
|
||||
export const TEST_NFT_WALLET_ASSET: WalletAsset = {
|
||||
id: 'TmZ0QXNzZXQ6RVRIRVJFVU1fMHgyOTY1MkMyZTlEMzY1NjQzNEJjODEzM2M2OTI1OEM4ZDA1MjkwZjQxXzIzNTk=',
|
||||
imageUrl: 'https://c.neevacdn.net/image/upload/xyz/T96PksTnWGNh79CrzLn-zpYfqRWtD5wME0MBPL_Md6Q.png',
|
||||
smallImageUrl:
|
||||
'https://c.neevacdn.net/image/upload/c_limit,pg_1,h_1200,w_1200/f_webp/xyz/T96PksTnWGNh79CrzLn-zpYfqRWtD5wME0MBPL_Md6Q.webp',
|
||||
notForSale: true,
|
||||
priceInfo: {
|
||||
ETHPrice: '0',
|
||||
baseAsset: 'ETH',
|
||||
baseDecimals: '18',
|
||||
basePrice: '0',
|
||||
},
|
||||
name: 'Froggy Friend #2359',
|
||||
tokenId: '2359',
|
||||
asset_contract: {
|
||||
address: '0x29652c2e9d3656434bc8133c69258c8d05290f41',
|
||||
tokenType: NftStandard.Erc721,
|
||||
name: 'Froggy Friends Official',
|
||||
description: '4444 of the friendliest frogs in the metaverse.',
|
||||
image_url: 'https://i.seadn.io/gcs/files/84483786d97b4d471cb48d224c4c5c91.png?w=500&auto=format',
|
||||
},
|
||||
collection: {
|
||||
address: '0x29652c2e9d3656434bc8133c69258c8d05290f41',
|
||||
name: 'Froggy Friends Official',
|
||||
isVerified: true,
|
||||
imageUrl: 'https://i.seadn.io/gcs/files/84483786d97b4d471cb48d224c4c5c91.png?w=500&auto=format',
|
||||
twitterUrl: '@FroggyFriendNFT',
|
||||
},
|
||||
collectionIsVerified: true,
|
||||
lastPrice: 0,
|
||||
floorPrice: 0.0775,
|
||||
basisPoints: 0,
|
||||
date_acquired: '1682024661',
|
||||
sellOrders: [],
|
||||
}
|
||||
|
||||
export const TEST_NFT_ACTIVITY_EVENT: ActivityEvent = {
|
||||
collectionAddress: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||
tokenId: '5674',
|
||||
tokenMetadata: {
|
||||
name: 'Azuki #5674',
|
||||
imageUrl:
|
||||
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/5674/b2e5cb241d4a28bb3688ff6ae12f2d60c9850721f35f5104b5c42b31511e8a42.png',
|
||||
smallImageUrl: 'https://i.seadn.io/gcs/files/e2dabe8f353ed6354f5a1927e3d8bd64.png?w=500&auto=format',
|
||||
metadataUrl: 'ipfs://QmZcH4YvBVVRJtdn4RdbaqgspFU8gH6P9vomDpBVpAL3u4/5674',
|
||||
rarity: {
|
||||
source: 'RARITY_SNIPER',
|
||||
rank: 9412,
|
||||
score: 2778,
|
||||
},
|
||||
suspiciousFlag: false,
|
||||
standard: NftStandard.Erc721,
|
||||
},
|
||||
eventType: NftActivityType.Listing,
|
||||
marketplace: 'OPENSEA',
|
||||
fromAddress: '0xbf9fda32692b25c6083cbe48399ef019b62f0712',
|
||||
toAddress: undefined,
|
||||
transactionHash: undefined,
|
||||
price: '15.2',
|
||||
orderStatus: OrderStatus.Valid,
|
||||
quantity: 1,
|
||||
url: 'https://opensea.io/assets/0xed5af388653567af2f388e6224dc7c4b3241c544/5674',
|
||||
eventTimestamp: 1682444662,
|
||||
}
|
||||
17
src/test-utils/promise.ts
Normal file
17
src/test-utils/promise.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
type DeferredPromise<T> = {
|
||||
promise: Promise<T>
|
||||
resolve: (value: T) => void
|
||||
reject: (reason: unknown) => void
|
||||
}
|
||||
|
||||
export function createDeferredPromise<T = void>() {
|
||||
const deferedPromise = {} as DeferredPromise<T>
|
||||
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
deferedPromise.reject = reject
|
||||
deferedPromise.resolve = resolve
|
||||
})
|
||||
deferedPromise.promise = promise
|
||||
|
||||
return deferedPromise
|
||||
}
|
||||
@@ -9046,10 +9046,10 @@ cyclist@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||
|
||||
cypress-hardhat@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress-hardhat/-/cypress-hardhat-1.0.1.tgz#11b86653282769dadc0bd0c65ca41011865b0762"
|
||||
integrity sha512-eGD7fNM8BXShXEsDbO/m2jv9mx7jHs44bnuWKYxO29ySXX5Soz9+AFYelhzKDvh/T+MJy1YqApPbif9+PNA++g==
|
||||
cypress-hardhat@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress-hardhat/-/cypress-hardhat-2.0.0.tgz#98f07370270ec7c754d35f77c73b216d9f924abb"
|
||||
integrity sha512-YLLVZa/15CBo7mmu5JuIGAPg4jLbSYTw/LR690tx90WEiIaPH9diHzWAE41wB+cvmgM7fGwhKnKEEeE1s1DWKg==
|
||||
|
||||
cypress@*, cypress@10.3.1:
|
||||
version "10.3.1"
|
||||
|
||||
Reference in New Issue
Block a user