mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Enable and stabilise e2e tests for Swaps, add debouncing (#16326)
This commit is contained in:
parent
129ba1290e
commit
d634ff89df
@ -189,19 +189,10 @@ async function setupMocking(server, testSpecificMock) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// It disables loading of token icons, e.g. this URL: https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x0000000000000000000000000000000000000000.png
|
||||||
await server
|
await server
|
||||||
.forGet(
|
.forGet(
|
||||||
'https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x0d8775f648430679a709e98d2b0cb6250d2887ef.png',
|
/^https:\/\/static\.metaswap\.codefi\.network\/api\/v1\/tokenIcons\/1337\/.*\.png/u,
|
||||||
)
|
|
||||||
.thenCallback(() => {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await server
|
|
||||||
.forGet(
|
|
||||||
'https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x2efa2cb29c2341d8e5ba7d3262c9e9d6f1bf3711.png',
|
|
||||||
)
|
)
|
||||||
.thenCallback(() => {
|
.thenCallback(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -51,8 +51,7 @@ async function main() {
|
|||||||
if (!snaps) {
|
if (!snaps) {
|
||||||
testPaths = [
|
testPaths = [
|
||||||
...testPaths,
|
...testPaths,
|
||||||
// TODO: Enable the next line once the Swaps E2E tests are stable.
|
...(await getTestPathsForTestDir(path.join(__dirname, 'swaps'))),
|
||||||
// ...(await getTestPathsForTestDir(path.join(__dirname, 'swaps'))),
|
|
||||||
path.join(__dirname, 'metamask-ui.spec.js'),
|
path.join(__dirname, 'metamask-ui.spec.js'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
const FixtureBuilder = require('../fixture-builder');
|
||||||
|
|
||||||
const ganacheOptions = {
|
const ganacheOptions = {
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
@ -8,6 +10,11 @@ const ganacheOptions = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const withFixturesOptions = {
|
||||||
|
fixtures: new FixtureBuilder().build(),
|
||||||
|
ganacheOptions,
|
||||||
|
};
|
||||||
|
|
||||||
const loadSwaps = async (driver) => {
|
const loadSwaps = async (driver) => {
|
||||||
await driver.navigate();
|
await driver.navigate();
|
||||||
await driver.fill('#password', 'correct horse battery staple');
|
await driver.fill('#password', 'correct horse battery staple');
|
||||||
@ -28,19 +35,37 @@ const buildQuote = async (driver, options) => {
|
|||||||
await driver.wait(async () => {
|
await driver.wait(async () => {
|
||||||
const tokens = await driver.findElements('.searchable-item-list__item');
|
const tokens = await driver.findElements('.searchable-item-list__item');
|
||||||
return tokens.length > 1;
|
return tokens.length > 1;
|
||||||
}, 10000);
|
});
|
||||||
await driver.clickElement('.searchable-item-list__labels');
|
await driver.clickElement('.searchable-item-list__labels');
|
||||||
await driver.clickElement('.dropdown-input-pair__to');
|
await driver.clickElement('.dropdown-input-pair__to');
|
||||||
await driver.clickElement('[placeholder="Search name or paste address"]');
|
await driver.clickElement('input[data-testid="search-list-items"]');
|
||||||
await driver.fill(
|
await driver.fill(
|
||||||
'[placeholder="Search name or paste address"]',
|
'input[data-testid="search-list-items"]',
|
||||||
options.swapTo,
|
options.swapTo || options.swapToContractAddress,
|
||||||
);
|
);
|
||||||
|
if (options.swapTo) {
|
||||||
|
await driver.wait(async () => {
|
||||||
|
const tokenNames = await driver.findElements(
|
||||||
|
'.searchable-item-list__primary-label',
|
||||||
|
);
|
||||||
|
if (tokenNames.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const tokenName = await tokenNames[0].getText();
|
||||||
|
return tokenName === options.swapTo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (options.swapToContractAddress) {
|
||||||
|
await driver.waitForSelector({
|
||||||
|
css: '.searchable-item-list__item button.btn-primary',
|
||||||
|
text: 'Import',
|
||||||
|
});
|
||||||
|
}
|
||||||
await driver.clickElement('.searchable-item-list__primary-label');
|
await driver.clickElement('.searchable-item-list__primary-label');
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ganacheOptions,
|
withFixturesOptions,
|
||||||
loadSwaps,
|
loadSwaps,
|
||||||
buildQuote,
|
buildQuote,
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
const { strict: assert } = require('assert');
|
const { strict: assert } = require('assert');
|
||||||
|
|
||||||
const { withFixtures } = require('../helpers');
|
const { withFixtures } = require('../helpers');
|
||||||
const FixtureBuilder = require('../fixture-builder');
|
const { withFixturesOptions, loadSwaps, buildQuote } = require('./shared');
|
||||||
const { ganacheOptions, loadSwaps, buildQuote } = require('./shared');
|
|
||||||
|
|
||||||
describe('Swap Eth for another Token', function () {
|
describe('Swap Eth for another Token', function () {
|
||||||
it('Completes a Swap between Eth and Matic', async function () {
|
it('Completes a Swap between Eth and Matic', async function () {
|
||||||
await withFixtures(
|
await withFixtures(
|
||||||
{
|
{
|
||||||
fixtures: new FixtureBuilder().build(),
|
...withFixturesOptions,
|
||||||
ganacheOptions,
|
|
||||||
failOnConsoleError: false,
|
|
||||||
title: this.test.title,
|
title: this.test.title,
|
||||||
},
|
},
|
||||||
async ({ driver }) => {
|
async ({ driver }) => {
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
const { strict: assert } = require('assert');
|
const { strict: assert } = require('assert');
|
||||||
|
|
||||||
const { withFixtures } = require('../helpers');
|
const { withFixtures } = require('../helpers');
|
||||||
const FixtureBuilder = require('../fixture-builder');
|
|
||||||
const { ganacheOptions, loadSwaps, buildQuote } = require('./shared');
|
const { withFixturesOptions, loadSwaps, buildQuote } = require('./shared');
|
||||||
|
|
||||||
describe('Swaps - notifications', function () {
|
describe('Swaps - notifications', function () {
|
||||||
it('tests notifications for verified token on 1 source and price difference', async function () {
|
it('tests notifications for verified token on 1 source and price difference', async function () {
|
||||||
await withFixtures(
|
await withFixtures(
|
||||||
{
|
{
|
||||||
fixtures: new FixtureBuilder().build(),
|
...withFixturesOptions,
|
||||||
ganacheOptions,
|
|
||||||
failOnConsoleError: false,
|
|
||||||
title: this.test.title,
|
title: this.test.title,
|
||||||
},
|
},
|
||||||
async ({ driver }) => {
|
async ({ driver }) => {
|
||||||
@ -46,19 +44,52 @@ describe('Swaps - notifications', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('tests a notification for not enough balance', async function () {
|
||||||
|
await withFixtures(
|
||||||
|
{
|
||||||
|
...withFixturesOptions,
|
||||||
|
title: this.test.title,
|
||||||
|
},
|
||||||
|
async ({ driver }) => {
|
||||||
|
await loadSwaps(driver);
|
||||||
|
await buildQuote(driver, {
|
||||||
|
amount: 50,
|
||||||
|
swapTo: 'USDC',
|
||||||
|
});
|
||||||
|
const reviewSwapButton = await driver.findElement(
|
||||||
|
'[data-testid="page-container-footer-next"]',
|
||||||
|
);
|
||||||
|
assert.equal(await reviewSwapButton.getText(), 'Review swap');
|
||||||
|
assert.equal(await reviewSwapButton.isEnabled(), true);
|
||||||
|
await reviewSwapButton.click();
|
||||||
|
await driver.waitForSelector({
|
||||||
|
css: '[class*="box--align-items-center"]',
|
||||||
|
text: 'Estimated gas fee',
|
||||||
|
});
|
||||||
|
await driver.waitForSelector({
|
||||||
|
css: '[class*="actionable-message__message"]',
|
||||||
|
text: 'You need 43.4467 more TESTETH to complete this swap',
|
||||||
|
});
|
||||||
|
const swapButton = await driver.findElement(
|
||||||
|
'[data-testid="page-container-footer-next"]',
|
||||||
|
);
|
||||||
|
assert.equal(await swapButton.getText(), 'Swap');
|
||||||
|
assert.equal(await swapButton.isEnabled(), false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('tests notifications for verified token on 0 sources and high slippage', async function () {
|
it('tests notifications for verified token on 0 sources and high slippage', async function () {
|
||||||
await withFixtures(
|
await withFixtures(
|
||||||
{
|
{
|
||||||
fixtures: new FixtureBuilder().build(),
|
...withFixturesOptions,
|
||||||
ganacheOptions,
|
|
||||||
failOnConsoleError: false,
|
|
||||||
title: this.test.title,
|
title: this.test.title,
|
||||||
},
|
},
|
||||||
async ({ driver }) => {
|
async ({ driver }) => {
|
||||||
await loadSwaps(driver);
|
await loadSwaps(driver);
|
||||||
await buildQuote(driver, {
|
await buildQuote(driver, {
|
||||||
amount: 2,
|
amount: 2,
|
||||||
swapTo: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf',
|
swapToContractAddress: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf',
|
||||||
});
|
});
|
||||||
await driver.waitForSelector({
|
await driver.waitForSelector({
|
||||||
css: '.popover-header__title',
|
css: '.popover-header__title',
|
||||||
@ -96,41 +127,4 @@ describe('Swaps - notifications', function () {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('tests a notification for not enough balance', async function () {
|
|
||||||
await withFixtures(
|
|
||||||
{
|
|
||||||
fixtures: new FixtureBuilder().build(),
|
|
||||||
ganacheOptions,
|
|
||||||
failOnConsoleError: false,
|
|
||||||
title: this.test.title,
|
|
||||||
},
|
|
||||||
async ({ driver }) => {
|
|
||||||
await loadSwaps(driver);
|
|
||||||
await buildQuote(driver, {
|
|
||||||
amount: 50,
|
|
||||||
swapTo: 'USDC',
|
|
||||||
});
|
|
||||||
const reviewSwapButton = await driver.findElement(
|
|
||||||
'[data-testid="page-container-footer-next"]',
|
|
||||||
);
|
|
||||||
assert.equal(await reviewSwapButton.getText(), 'Review swap');
|
|
||||||
assert.equal(await reviewSwapButton.isEnabled(), true);
|
|
||||||
await reviewSwapButton.click();
|
|
||||||
await driver.waitForSelector({
|
|
||||||
css: '[class*="box--align-items-center"]',
|
|
||||||
text: 'Estimated gas fee',
|
|
||||||
});
|
|
||||||
await driver.waitForSelector({
|
|
||||||
css: '[class*="actionable-message__message"]',
|
|
||||||
text: 'You need 43.4467 more TESTETH to complete this swap',
|
|
||||||
});
|
|
||||||
const swapButton = await driver.findElement(
|
|
||||||
'[data-testid="page-container-footer-next"]',
|
|
||||||
);
|
|
||||||
assert.equal(await swapButton.getText(), 'Swap');
|
|
||||||
assert.equal(await swapButton.isEnabled(), false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export { screen, fireEvent } from '@testing-library/react';
|
export { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
export { createSwapsMockStore } from './mock-store';
|
export { createSwapsMockStore } from './mock-store';
|
||||||
export { renderWithProvider } from './rendering';
|
export { renderWithProvider } from './rendering';
|
||||||
export { setBackgroundConnection } from './background';
|
export { setBackgroundConnection } from './background';
|
||||||
|
@ -192,5 +192,5 @@ FeeCard.propTypes = {
|
|||||||
onQuotesClick: PropTypes.func.isRequired,
|
onQuotesClick: PropTypes.func.isRequired,
|
||||||
numberOfQuotes: PropTypes.number.isRequired,
|
numberOfQuotes: PropTypes.number.isRequired,
|
||||||
chainId: PropTypes.string.isRequired,
|
chainId: PropTypes.string.isRequired,
|
||||||
isBestQuote: PropTypes.bool.isRequired,
|
isBestQuote: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -67,7 +67,7 @@ export default function ImportToken({
|
|||||||
fontWeight={FONT_WEIGHT.BOLD}
|
fontWeight={FONT_WEIGHT.BOLD}
|
||||||
boxProps={{ marginTop: 2, marginBottom: 3 }}
|
boxProps={{ marginTop: 2, marginBottom: 3 }}
|
||||||
>
|
>
|
||||||
{tokenForImport.name}
|
{tokenForImport.name || ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant={TYPOGRAPHY.H6}>{t('contract')}:</Typography>
|
<Typography variant={TYPOGRAPHY.H6}>{t('contract')}:</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
@ -75,7 +75,7 @@ export default function ImportToken({
|
|||||||
variant={TYPOGRAPHY.H7}
|
variant={TYPOGRAPHY.H7}
|
||||||
boxProps={{ marginBottom: 6 }}
|
boxProps={{ marginBottom: 6 }}
|
||||||
>
|
>
|
||||||
{tokenForImport.address}
|
{tokenForImport.address || ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -17,6 +17,8 @@ const renderAdornment = () => (
|
|||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let timeoutIdForSearch;
|
||||||
|
|
||||||
export default function ListItemSearch({
|
export default function ListItemSearch({
|
||||||
onSearch,
|
onSearch,
|
||||||
error,
|
error,
|
||||||
@ -37,7 +39,6 @@ export default function ListItemSearch({
|
|||||||
* @param {string} contractAddress
|
* @param {string} contractAddress
|
||||||
*/
|
*/
|
||||||
const handleSearchTokenForImport = async (contractAddress) => {
|
const handleSearchTokenForImport = async (contractAddress) => {
|
||||||
setSearchQuery(contractAddress);
|
|
||||||
try {
|
try {
|
||||||
const token = await fetchToken(contractAddress, chainId);
|
const token = await fetchToken(contractAddress, chainId);
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -60,22 +61,32 @@ export default function ListItemSearch({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = async (newSearchQuery) => {
|
const handleSearch = async (newSearchQuery) => {
|
||||||
const trimmedNewSearchQuery = newSearchQuery.trim();
|
|
||||||
const validHexAddress = isValidHexAddress(trimmedNewSearchQuery);
|
|
||||||
const fuseSearchResult = fuseRef.current.search(newSearchQuery);
|
|
||||||
const results =
|
|
||||||
defaultToAll && newSearchQuery === '' ? listToSearch : fuseSearchResult;
|
|
||||||
if (shouldSearchForImports && results.length === 0 && validHexAddress) {
|
|
||||||
await handleSearchTokenForImport(trimmedNewSearchQuery);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSearchQuery(newSearchQuery);
|
setSearchQuery(newSearchQuery);
|
||||||
onSearch({
|
if (timeoutIdForSearch) {
|
||||||
searchQuery: newSearchQuery,
|
clearTimeout(timeoutIdForSearch);
|
||||||
results,
|
}
|
||||||
});
|
timeoutIdForSearch = setTimeout(async () => {
|
||||||
|
timeoutIdForSearch = null;
|
||||||
|
const trimmedNewSearchQuery = newSearchQuery.trim();
|
||||||
|
const validHexAddress = isValidHexAddress(trimmedNewSearchQuery);
|
||||||
|
const fuseSearchResult = fuseRef.current.search(newSearchQuery);
|
||||||
|
const results =
|
||||||
|
defaultToAll && newSearchQuery === '' ? listToSearch : fuseSearchResult;
|
||||||
|
if (shouldSearchForImports && results.length === 0 && validHexAddress) {
|
||||||
|
await handleSearchTokenForImport(trimmedNewSearchQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSearch({
|
||||||
|
searchQuery: newSearchQuery,
|
||||||
|
results,
|
||||||
|
});
|
||||||
|
}, 350);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => clearTimeout(timeoutIdForSearch);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fuseRef.current) {
|
if (!fuseRef.current) {
|
||||||
fuseRef.current = new Fuse(listToSearch, {
|
fuseRef.current = new Fuse(listToSearch, {
|
||||||
@ -128,6 +139,6 @@ ListItemSearch.propTypes = {
|
|||||||
searchPlaceholderText: PropTypes.string,
|
searchPlaceholderText: PropTypes.string,
|
||||||
defaultToAll: PropTypes.bool,
|
defaultToAll: PropTypes.bool,
|
||||||
shouldSearchForImports: PropTypes.bool,
|
shouldSearchForImports: PropTypes.bool,
|
||||||
searchQuery: PropTypes.func,
|
searchQuery: PropTypes.string,
|
||||||
setSearchQuery: PropTypes.func,
|
setSearchQuery: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@ -74,6 +74,7 @@ describe('ListItemSearch', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('changes the search query', () => {
|
it('changes the search query', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
const store = configureMockStore(middleware)(createSwapsMockStore());
|
const store = configureMockStore(middleware)(createSwapsMockStore());
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
const { getByTestId } = renderWithProvider(
|
const { getByTestId } = renderWithProvider(
|
||||||
@ -82,6 +83,7 @@ describe('ListItemSearch', () => {
|
|||||||
);
|
);
|
||||||
const input = getByTestId('search-list-items');
|
const input = getByTestId('search-list-items');
|
||||||
fireEvent.change(input, { target: { value: 'USD' } });
|
fireEvent.change(input, { target: { value: 'USD' } });
|
||||||
|
jest.runAllTimers();
|
||||||
expect(props.setSearchQuery).toHaveBeenCalledWith('USD');
|
expect(props.setSearchQuery).toHaveBeenCalledWith('USD');
|
||||||
expect(props.onSearch).toHaveBeenCalledWith({
|
expect(props.onSearch).toHaveBeenCalledWith({
|
||||||
searchQuery: 'USD',
|
searchQuery: 'USD',
|
||||||
@ -90,6 +92,7 @@ describe('ListItemSearch', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('imports a token', async () => {
|
it('imports a token', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
const store = configureMockStore(middleware)(createSwapsMockStore());
|
const store = configureMockStore(middleware)(createSwapsMockStore());
|
||||||
const props = createProps({ shouldSearchForImports: true });
|
const props = createProps({ shouldSearchForImports: true });
|
||||||
const { getByTestId } = renderWithProvider(
|
const { getByTestId } = renderWithProvider(
|
||||||
@ -98,6 +101,7 @@ describe('ListItemSearch', () => {
|
|||||||
);
|
);
|
||||||
const input = getByTestId('search-list-items');
|
const input = getByTestId('search-list-items');
|
||||||
await fireEvent.change(input, { target: { value: token.address } });
|
await fireEvent.change(input, { target: { value: token.address } });
|
||||||
|
await jest.runAllTimers();
|
||||||
expect(props.setSearchQuery).toHaveBeenCalledWith(token.address);
|
expect(props.setSearchQuery).toHaveBeenCalledWith(token.address);
|
||||||
expect(props.onSearch).toHaveBeenCalledWith({
|
expect(props.onSearch).toHaveBeenCalledWith({
|
||||||
searchQuery: token.address,
|
searchQuery: token.address,
|
||||||
|
@ -93,6 +93,6 @@ SearchableItemList.propTypes = {
|
|||||||
hideItemIf: PropTypes.func,
|
hideItemIf: PropTypes.func,
|
||||||
listContainerClassName: PropTypes.string,
|
listContainerClassName: PropTypes.string,
|
||||||
shouldSearchForImports: PropTypes.bool,
|
shouldSearchForImports: PropTypes.bool,
|
||||||
searchQuery: PropTypes.func,
|
searchQuery: PropTypes.string,
|
||||||
setSearchQuery: PropTypes.func,
|
setSearchQuery: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@ -741,7 +741,7 @@ export default function ViewQuote() {
|
|||||||
const priceSlippageUnknownFiatValue =
|
const priceSlippageUnknownFiatValue =
|
||||||
!priceSlippageFromSource ||
|
!priceSlippageFromSource ||
|
||||||
!priceSlippageFromDestination ||
|
!priceSlippageFromDestination ||
|
||||||
usedQuote?.priceSlippage?.calculationError;
|
Boolean(usedQuote?.priceSlippage?.calculationError);
|
||||||
|
|
||||||
let priceDifferencePercentage = 0;
|
let priceDifferencePercentage = 0;
|
||||||
if (usedQuote?.priceSlippage?.ratio) {
|
if (usedQuote?.priceSlippage?.ratio) {
|
||||||
@ -1038,7 +1038,7 @@ export default function ViewQuote() {
|
|||||||
}
|
}
|
||||||
hideCancel
|
hideCancel
|
||||||
disabled={isSwapButtonDisabled}
|
disabled={isSwapButtonDisabled}
|
||||||
className={isShowingWarning && 'view-quote__thin-swaps-footer'}
|
className={isShowingWarning ? 'view-quote__thin-swaps-footer' : ''}
|
||||||
showTopBorder
|
showTopBorder
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user