Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brody/swap v2 e2e #5915

Merged
merged 20 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions e2e/3_homeScreen.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
checkIfExists,
checkIfExistsByText,
swipe,
waitAndTap,
afterAllcleanApp,
tap,
delayTime,
} from './helpers';

const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth';
Expand Down Expand Up @@ -41,19 +42,20 @@ describe('Home Screen', () => {
});

it('tapping "Swap" opens the swap screen', async () => {
await waitAndTap('swap-button');
await checkIfExists('exchange-modal-input-selection-button');
await swipe('exchange-modal-notch', 'down', 'slow');
await tap('swap-button');
await delayTime('long');
await checkIfExists('swap-screen');
await swipe('swap-screen', 'down', 'fast');
});

it('tapping "Send" opens the send screen', async () => {
await waitAndTap('send-button');
await tap('send-button');
await checkIfVisible('send-asset-form-field');
await swipe('send-asset-form-field', 'down');
await swipe('send-asset-form-field', 'down', 'fast');
});

it('tapping "Copy" shows copy address toast', async () => {
await waitAndTap('receive-button');
await tap('receive-button');
await checkIfVisible('address-copied-toast');
});
});
143 changes: 143 additions & 0 deletions e2e/8_swaps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* // Other tests to consider:
* - Flip assets
* - exchange button onPress
* - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged
* - swap execution
* - token search (both from userAssets and output token list)
* - custom gas panel
* - flashbots
* - slippage
* - explainer sheets
* - switching wallets inside of swap screen
*/

import {
importWalletFlow,
sendETHtoTestWallet,
checkIfVisible,
beforeAllcleanApp,
afterAllcleanApp,
fetchElementAttributes,
tap,
tapByText,
delayTime,
} from './helpers';

import { expect } from '@jest/globals';
import { WALLET_VARS } from './testVariables';

describe('Swap Sheet Interaction Flow', () => {
beforeAll(async () => {
await beforeAllcleanApp({ hardhat: true });
});
afterAll(async () => {
await afterAllcleanApp({ hardhat: true });
});

it('Import a wallet and go to welcome', async () => {
await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK);
});

it('Should send ETH to test wallet', async () => {
// send 20 eth
await sendETHtoTestWallet();
});

it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
await tap('dev-button-hardhat');
await checkIfVisible('testnet-toast-Hardhat');

// validate it has the expected funds of 20 eth
const attributes = await fetchElementAttributes('fast-coin-info');
expect(attributes.label).toContain('Ethereum');
expect(attributes.label).toContain('20');
});

it('Should open swap screen with 50% inputAmount for inputAsset', async () => {
await device.disableSynchronization();
await tap('swap-button');
await delayTime('long');
await tap('token-to-buy-dai-1');
const swapInput = await fetchElementAttributes('swap-asset-input');

// expect inputAsset === .5 * eth balance
expect(swapInput.label).toContain('ETH');
expect(swapInput.label).toContain('10');
});

it('Should be able to go to review and execute a swap', async () => {
await tapByText('Review');
const reviewActionElements = await fetchElementAttributes('swap-action-button');
expect(reviewActionElements.elements[0].label).toContain('ETH');
expect(reviewActionElements.elements[1].label).toContain('DAI');
expect(reviewActionElements.elements[2].label).toContain('Tap to Swap');

/*
*
* Everything from this point fails. Un-comment out the following line to see behavior.
* Currently some spots have chainId 1 and chainId 1337 for various things. I suspect
* there is some issue with one of these things. Log the variables in getNonceAndPerformSwap
* to see the behavior.
*
* To run this test:
*
* yarn clean:ios && yarn fast && yarn start:clean
* yarn detox:ios:build && yarn detox test -c ios.sim.debug 8_swaps.spec.ts
*
*/

// await tapByText('Tap to Swap');
});

it('Should be able to verify swap is happening', async () => {
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
// await delayTime('very-long');
// const activityListElements = await fetchElementAttributes('wallet-activity-list');
// expect(activityListElements.label).toContain('ETH');
// expect(activityListElements.label).toContain('DAI');
// await tapByText('Swapping');
// await delayTime('long');
// const transactionSheet = await checkIfVisible('transaction-details-sheet');
// expect(transactionSheet).toBeTruthy();
});

it('Should open swap screen from ProfileActionRowButton with largest user asset', async () => {
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
/**
* tap swap button
* wait for Swap header to be visible
* grab highest user asset balance from userAssetsStore
* expect inputAsset.uniqueId === highest user asset uniqueId
*/
});

it('Should open swap screen from asset chart with that asset selected', async () => {
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
/**
* tap any user asset (store const uniqueId here)
* wait for Swap header to be visible
* expect inputAsset.uniqueId === const uniqueId ^^
*/
});

it('Should open swap screen from dapp browser control panel with largest user asset', async () => {
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
/**
* tap swap button
* wait for Swap header to be visible
* grab highest user asset balance from userAssetsStore
* expect inputAsset.uniqueId === highest user asset uniqueId
*/
});

it('Should not be able to type in output amount if cross-chain quote', async () => {
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
/**
* tap swap button
* wait for Swap header to be visible
* select different chain in output list chain selector
* select any asset in output token list
* focus output amount
* attempt to type any number in the SwapNumberPad
* attempt to remove a character as well
*
* ^^ expect both of those to not change the outputAmount
*/
});
});
13 changes: 12 additions & 1 deletion e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { JsonRpcProvider } from '@ethersproject/providers';
import { Wallet } from '@ethersproject/wallet';
import { expect, device, element, by, waitFor } from 'detox';
import { parseEther } from '@ethersproject/units';
import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox';

const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608';
const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F';

const DEFAULT_TIMEOUT = 20_000;
const android = device.getPlatform() === 'android';
Expand Down Expand Up @@ -70,6 +71,16 @@ export async function tap(elementId: string | RegExp) {
}
}

interface CustomElementAttributes {
elements: Array<IosElementAttributes | AndroidElementAttributes>;
}

type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes;

export const fetchElementAttributes = async (testId: string): Promise<ElementAttributes> => {
return (await element(by.id(testId)).getAttributes()) as ElementAttributes;
};

export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) {
await delayTime('medium');
try {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
"nuke": "./scripts/nuke.sh",
"detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose",
"detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release",
"detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ",
"detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1",
"detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests",
"detox:ios": "yarn detox:ios:build && yarn detox:ios:tests",
"detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1",
"ds:install": "yarn install --cwd src/design-system/docs",
"ds": "cd src/design-system/docs && yarn dev",
Expand Down
6 changes: 4 additions & 2 deletions src/__swaps__/screens/Swap/components/CoinRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface InputCoinRowProps {
onPress: (asset: ParsedSearchAsset | null) => void;
output?: false | undefined;
uniqueId: string;
testID?: string;
}

type PartialAsset = Pick<SearchAsset, 'address' | 'chainId' | 'colors' | 'icon_url' | 'mainnetAddress' | 'name' | 'symbol' | 'uniqueId'>;
Expand All @@ -62,11 +63,12 @@ interface OutputCoinRowProps extends PartialAsset {
output: true;
nativePriceChange?: string;
isTrending?: boolean;
testID?: string;
}

type CoinRowProps = InputCoinRowProps | OutputCoinRowProps;

export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) {
export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) {
const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId)));
const outputAsset = output ? (assetProps as PartialAsset) : undefined;

Expand Down Expand Up @@ -116,7 +118,7 @@ export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniq
if (!address || !chainId) return null;

return (
<Box style={{ height: COIN_ROW_WITH_PADDING_HEIGHT, width: '100%' }}>
<Box testID={testID} style={{ height: COIN_ROW_WITH_PADDING_HEIGHT, width: '100%' }}>
<Columns alignVertical="center">
<Column>
<ButtonPressAnimation disallowInterruption onPress={onPressHandler} scaleTo={0.95}>
Expand Down
42 changes: 26 additions & 16 deletions src/__swaps__/screens/Swap/components/FadeMask.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { Box, Columns, Column, globalColors } from '@/design-system';
import LinearGradient from 'react-native-linear-gradient';
import { IS_TESTING } from 'react-native-dotenv';
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
import { View } from 'react-native';

export const FadeMask = ({
fadeEdgeInset = 6,
Expand All @@ -22,14 +24,18 @@ export const FadeMask = ({
<Box height="full" width={{ custom: fadeEdgeInset }} />
</Column>
<Column width="content">
<Box
as={LinearGradient}
colors={['transparent', globalColors.grey100]}
end={{ x: 1, y: 0.5 }}
height="full"
start={{ x: 0, y: 0.5 }}
width={{ custom: fadeWidth }}
/>
{IS_TESTING ? (
walmat marked this conversation as resolved.
Show resolved Hide resolved
<Box as={View} height="full" width={{ custom: fadeWidth }} />
) : (
<Box
as={LinearGradient}
colors={['transparent', globalColors.grey100]}
end={{ x: 1, y: 0.5 }}
height="full"
start={{ x: 0, y: 0.5 }}
width={{ custom: fadeWidth }}
/>
)}
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
</Column>
</>
) : null}
Expand All @@ -39,14 +45,18 @@ export const FadeMask = ({
{!side || side === 'right' ? (
<>
<Column width="content">
<Box
as={LinearGradient}
colors={[globalColors.grey100, 'transparent']}
end={{ x: 1, y: 0.5 }}
height="full"
start={{ x: 0, y: 0.5 }}
width={{ custom: fadeWidth }}
/>
{IS_TESTING ? (
walmat marked this conversation as resolved.
Show resolved Hide resolved
<Box as={View} height="full" width={{ custom: fadeWidth }} />
) : (
<Box
as={LinearGradient}
colors={[globalColors.grey100, 'transparent']}
end={{ x: 1, y: 0.5 }}
height="full"
start={{ x: 0, y: 0.5 }}
width={{ custom: fadeWidth }}
/>
)}
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
</Column>
<Column width="content">
<Box height="full" width={{ custom: fadeEdgeInset }} />
Expand Down
1 change: 1 addition & 0 deletions src/__swaps__/screens/Swap/components/SwapActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const SwapActionButton = ({
style={[hugContent && feedActionButtonStyles.buttonWrapper, style]}
>
<Box
testID={'swap-action-button'}
as={Animated.View}
paddingHorizontal={{ custom: small ? 14 : 20 - (outline ? 2 : 0) }}
paddingLeft={small && icon ? '10px' : undefined}
Expand Down
11 changes: 10 additions & 1 deletion src/__swaps__/screens/Swap/components/SwapBackground.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Canvas, Rect, LinearGradient, vec, Paint } from '@shopify/react-native-skia';
import React from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, View } from 'react-native';
import { useDerivedValue, withTiming } from 'react-native-reanimated';
import { ScreenCornerRadius } from 'react-native-screen-corner-radius';
import { TIMING_CONFIGS } from '@/components/animations/animationConfigs';
Expand All @@ -11,19 +11,24 @@ import { getColorValueForThemeWorklet, getTintedBackgroundColor } from '@/__swap
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils';
import { ETH_COLOR, ETH_COLOR_DARK } from '../constants';

import { IS_TESTING } from 'react-native-dotenv';
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved

const DEFAULT_BACKGROUND_COLOR = getTintedBackgroundColor({ dark: ETH_COLOR_DARK, light: ETH_COLOR });

export const SwapBackground = () => {
const { isDarkMode } = useColorMode();
const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext();

const animatedTopColor = useDerivedValue(() => {
if (IS_TESTING) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true);
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
return withTiming(
getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true),
TIMING_CONFIGS.slowFadeConfig
);
});

const animatedBottomColor = useDerivedValue(() => {
if (IS_TESTING) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true);
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
return withTiming(
getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true),
TIMING_CONFIGS.slowFadeConfig
Expand All @@ -34,6 +39,10 @@ export const SwapBackground = () => {
return [animatedTopColor.value, animatedBottomColor.value];
});

if (IS_TESTING) {
BrodyHughes marked this conversation as resolved.
Show resolved Hide resolved
return <View style={[styles.background, { backgroundColor: animatedTopColor.value }]} />;
}

return (
<Canvas style={styles.background}>
<Rect height={DEVICE_HEIGHT + (IS_ANDROID ? 24 : 0)} width={DEVICE_WIDTH} x={0} y={0}>
Expand Down
11 changes: 9 additions & 2 deletions src/__swaps__/screens/Swap/components/SwapInputAsset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ function SwapInputAmount() {
}}
>
<MaskedView maskElement={<FadeMask fadeEdgeInset={2} fadeWidth={8} height={36} side="right" />} style={styles.inputTextMask}>
<AnimatedText ellipsizeMode="clip" numberOfLines={1} size="30pt" style={SwapTextStyles.inputAmountTextStyle} weight="bold">
<AnimatedText
testID={'swap-asset-amount'}
ellipsizeMode="clip"
numberOfLines={1}
size="30pt"
style={SwapTextStyles.inputAmountTextStyle}
weight="bold"
>
{SwapInputController.formattedInputAmount}
</AnimatedText>
<Animated.View style={[styles.caretContainer, SwapTextStyles.inputCaretStyle]}>
Expand Down Expand Up @@ -105,7 +112,7 @@ export function SwapInputAsset() {

return (
<SwapInput asset={internalSelectedInputAsset} otherInputProgress={outputProgress} progress={inputProgress}>
<Box as={Animated.View} style={AnimatedSwapStyles.inputStyle}>
<Box testID={'swap-asset-input'} as={Animated.View} style={AnimatedSwapStyles.inputStyle}>
<Stack space="16px">
<Columns alignHorizontal="justify" alignVertical="center">
<Column width="content">
Expand Down
Loading
Loading