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

[B2CQA-387] first detox cosmos delegation tests #4974

Merged
merged 3 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export async function scrollToId(

export async function getTextOfElement(id: string, index = 0) {
const attributes = await getElementById(id, index).getAttributes();
return !("elements" in attributes) ? attributes.text : attributes.elements[index].text;
return (!("elements" in attributes) ? attributes.text : attributes.elements[index].text) || "";
}

/**
Expand Down
52 changes: 52 additions & 0 deletions apps/ledger-live-mobile/e2e/models/stake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getTextOfElement, tapById, waitForElementById } from "../helpers";

export default class StakePage {
cosmosDelegationSummaryValidatorId = "cosmos-delegation-summary-validator";
cosmosDelegationSummaryValidator = () => getTextOfElement("cosmos-delegation-summary-validator");
cosmosDelegationSummaryAmountId = "cosmos-delegation-summary-amount";
cosmosDelegationAmountValue = () => getTextOfElement(this.cosmosDelegationSummaryAmountId);
cosmosAssestsRemainingId = "cosmos-assets-remaining";
cosmosDelegatedRatioId = (delegatedPercent: number) => `delegate-ratio-${delegatedPercent}%`;
cosmosAllAssestsUsedText = "cosmos-all-assets-used-text";
summaryContinueButtonId = "cosmos-summary-continue-button";

async selectCurrency(currencyId: string) {
const id = "currency-row-" + currencyId;
await waitForElementById(id);
await tapById(id);
}

async selectAccount(accountId: string) {
const id = "account-card-" + accountId;
await waitForElementById(id);
await tapById(id);
}

async delegationStart() {
await tapById("cosmos-delegation-start-button");
await waitForElementById(this.cosmosDelegationSummaryValidatorId);
}

async setAmount(delegatedPercent: 25 | 50 | 75 | 100) {
await waitForElementById(this.cosmosDelegationSummaryAmountId);
await tapById(this.cosmosDelegationSummaryAmountId);
await tapById(this.cosmosDelegatedRatioId(delegatedPercent));
const max = delegatedPercent == 100;
const id = max ? this.cosmosAllAssestsUsedText : this.cosmosAssestsRemainingId;
await waitForElementById(id);
const assestsRemaining = max ? "0\u00a0ATOM" : (await getTextOfElement(id)).split(": ")[1];
await tapById("cosmos-delegation-amount-continue");
await waitForElementById(this.cosmosDelegationSummaryAmountId);
const assestsDelagated = await this.cosmosDelegationAmountValue();
return [assestsDelagated, assestsRemaining];
}

async summaryContinue() {
await tapById(this.summaryContinueButtonId);
}

async successClose() {
await waitForElementById("success-close-button");
await tapById("success-close-button");
}
}
9 changes: 9 additions & 0 deletions apps/ledger-live-mobile/e2e/models/wallet/portfolioPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
getElementById,
getTextOfElement,
openDeeplink,
scrollToId,
tapByElement,
waitForElementById,
} from "../../helpers";
Expand All @@ -12,10 +13,13 @@ export default class PortfolioPage {
graphCardBalanceId = "graphCard-balance";
assetBalanceId = "asset-balance";
readOnlyPortfolioId = "PortfolioReadOnlyList";
transferScrollListId = "transfer-scroll-list";
stakeMenuButtonId = "transfer-stake-button";
emptyPortfolioComponent = () => getElementById("PortfolioEmptyAccount");
portfolioSettingsButton = () => getElementById("settings-icon");
transferButton = () => getElementById("transfer-button");
swapTransferMenuButton = () => getElementById("swap-transfer-button");
stakeTransferMenuButton = () => getElementById(this.stakeMenuButtonId);
sendTransferMenuButton = () => getElementById("transfer-send-button");
sendMenuButton = () => getElementById("send-button");
marketTabButton = () => getElementById("tab-bar-market");
Expand All @@ -42,6 +46,11 @@ export default class PortfolioPage {
await tapByElement(this.sendTransferMenuButton());
}

async navigateToStakeFromTransferMenu() {
await scrollToId(this.stakeMenuButtonId, this.transferScrollListId);
return tapByElement(this.stakeTransferMenuButton());
}

async openAddAccount() {
const element = getElementById("add-account-button");
await element.tap();
Expand Down
83 changes: 83 additions & 0 deletions apps/ledger-live-mobile/e2e/specs/delegate/cosmos.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { genAccount } from "@ledgerhq/live-common/mock/account";
import {
getCryptoCurrencyById,
setSupportedCurrencies,
} from "@ledgerhq/live-common/currencies/index";
import { loadAccounts, loadBleState, loadConfig } from "../../bridge/server";
import PortfolioPage from "../../models/wallet/portfolioPage";
import StakePage from "../../models/stake";
import DeviceAction from "../../models/DeviceAction";
import { DeviceModelId } from "@ledgerhq/devices";
import { BigNumber } from "bignumber.js";
import { Unit } from "@ledgerhq/types-cryptoassets";
import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index";

let portfolioPage: PortfolioPage;
let stakePage: StakePage;
let deviceAction: DeviceAction;

const knownDevice = {
name: "Nano X de test",
id: "mock_1",
modelId: DeviceModelId.nanoX,
};

const testedCurrency = "cosmos";
const id = "cosmosid";

setSupportedCurrencies([testedCurrency]);
const testedAccount = genAccount(id, {
currency: getCryptoCurrencyById(testedCurrency),
});

const COSMOS_MIN_SAFE = new BigNumber(100000); // 100000 uAtom
const COSMOS_MIN_FEES = new BigNumber(6000);
const formattedAmount = (unit: Unit, amount: BigNumber, showAllDigits = false) =>
// amount formatted with the same unit as what the input should use
formatCurrencyUnit(unit, amount, {
showCode: true,
showAllDigits: showAllDigits,
disableRounding: false,
});

describe("Cosmos delegate flow", () => {
beforeAll(async () => {
loadConfig("onboardingcompleted", true);

loadBleState({ knownDevices: [knownDevice] });
loadAccounts([testedAccount]);

portfolioPage = new PortfolioPage();
deviceAction = new DeviceAction(knownDevice);
stakePage = new StakePage();

await portfolioPage.waitForPortfolioPageToLoad();
});

it("open account stake flow", async () => {
await portfolioPage.openTransferMenu();
await portfolioPage.navigateToStakeFromTransferMenu();
});

it("goes through the delegate flow", async () => {
const delegatedPercent = 50;
const usableAmount = testedAccount.spendableBalance
.minus(COSMOS_MIN_SAFE)
.minus(COSMOS_MIN_FEES);
const delegatedAmount = usableAmount.div(100 / delegatedPercent).integerValue();
const remainingAmount = usableAmount.minus(delegatedAmount);

await stakePage.selectCurrency(testedCurrency);
await stakePage.selectAccount(testedAccount.id);

const [assestsDelagated, assestsRemaining] = await stakePage.setAmount(delegatedPercent);
expect(await stakePage.cosmosDelegationSummaryValidator()).toEqual("Ledger");
expect(assestsRemaining).toEqual(formattedAmount(testedAccount.unit, remainingAmount));
expect(assestsDelagated).toEqual(formattedAmount(testedAccount.unit, delegatedAmount, true));

await stakePage.summaryContinue();
await deviceAction.selectMockDevice();
await deviceAction.openApp();
await stakePage.successClose();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default function DelegationStarted({ navigation, route }: Props) {
title={t("cosmos.delegation.flow.steps.starter.warning.description")}
/>
</View>
<Button onPress={onNext} type="main" mt={6}>
<Button onPress={onNext} type="main" mt={6} testID="cosmos-delegation-start-button">
<Trans i18nKey="cosmos.delegation.flow.steps.starter.cta" />
</Button>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { accountScreenSelector } from "../../../reducers/accounts";
import ValidatorImage from "../shared/ValidatorImage";
import { StackNavigatorProps } from "../../../components/RootNavigator/types/helpers";
import { CosmosDelegationFlowParamList } from "./types";
import Config from "react-native-config";

type Props = StackNavigatorProps<
CosmosDelegationFlowParamList,
Expand Down Expand Up @@ -108,26 +109,28 @@ export default function DelegationSummary({ navigation, route }: Props) {

const [rotateAnim] = useState(() => new Animated.Value(0));
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(rotateAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(rotateAnim, {
toValue: -1,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(rotateAnim, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}),
Animated.delay(1000),
]),
).start();
if (!Config.MOCK) {
Animated.loop(
Animated.sequence([
Animated.timing(rotateAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(rotateAnim, {
toValue: -1,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(rotateAnim, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}),
Animated.delay(1000),
]),
).start();
}
return () => {
rotateAnim.setValue(0);
};
Expand Down Expand Up @@ -251,6 +254,7 @@ export default function DelegationSummary({ navigation, route }: Props) {
onPress={onContinue}
disabled={bridgePending || !!bridgeError || hasErrors}
pending={bridgePending}
testID="cosmos-summary-continue-button"
/>
</View>
</SafeAreaView>
Expand Down Expand Up @@ -364,15 +368,18 @@ function SummaryWords({
<Trans i18nKey={`cosmos.delegation.iDelegate`} />
</Words>
<Touchable onPress={onChangeAmount}>
<Selectable name={formattedAmount} />
<Selectable name={formattedAmount} testID="cosmos-delegation-summary-amount" />
</Touchable>
</Line>
<Line>
<Words>
<Trans i18nKey="delegation.to" />
</Words>
<Touchable onPress={onChangeValidator}>
<Selectable name={validator?.name ?? validator?.validatorAddress ?? "-"} />
<Selectable
name={validator?.name ?? validator?.validatorAddress ?? "-"}
testID="cosmos-delegation-summary-validator"
/>
</Touchable>
</Line>
</>
Expand Down Expand Up @@ -428,7 +435,7 @@ const Words = ({
</Text>
);

const Selectable = ({ name }: { name: string; readOnly?: boolean }) => {
const Selectable = ({ name, testID }: { name: string; readOnly?: boolean; testID?: string }) => {
const { colors } = useTheme();
return (
<View style={[styles.validatorSelection, { backgroundColor: rgba(colors.primary, 0.2) }]}>
Expand All @@ -437,6 +444,7 @@ const Selectable = ({ name }: { name: string; readOnly?: boolean }) => {
numberOfLines={1}
style={styles.validatorSelectionText}
color={colors.primary}
testID={testID}
>
{name}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ function DelegationAmount({ navigation, route }: Props) {
<LText
style={[styles.ratioLabel]}
color={value.eq(v) ? colors.neutral.c100 : colors.neutral.c60}
testID={"delegate-ratio-" + label}
>
{label}
</LText>
Expand Down Expand Up @@ -217,14 +218,18 @@ function DelegationAmount({ navigation, route }: Props) {
{max.isZero() && (
<View style={styles.labelContainer}>
<Check size={16} color={colors.success.c50} />
<LText style={[styles.assetsRemaining]} color={colors.success.c50}>
<LText
style={[styles.assetsRemaining]}
color={colors.success.c50}
testID="cosmos-all-assets-used-text"
>
<Trans i18nKey={`cosmos.${mode}.flow.steps.amount.allAssetsUsed`} />
</LText>
</View>
)}
{max.gt(0) && !isAmountOutOfRange && !isNotEnoughBalance && (
<View style={styles.labelContainer}>
<LText style={styles.assetsRemaining}>
<LText style={styles.assetsRemaining} testID="cosmos-assets-remaining">
<Trans
i18nKey="cosmos.delegation.flow.steps.amount.assetsRemaining"
values={{
Expand Down Expand Up @@ -263,6 +268,7 @@ function DelegationAmount({ navigation, route }: Props) {
onPress={onNext}
title={<Trans i18nKey="cosmos.delegation.flow.steps.amount.cta" />}
type="primary"
testID="cosmos-delegation-amount-continue"
/>
</View>
</View>
Expand Down
32 changes: 27 additions & 5 deletions libs/ledger-live-common/src/families/cosmos/bridge/mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { BigNumber } from "bignumber.js";
import { NotEnoughBalance, RecipientRequired, InvalidAddress, FeeTooHigh } from "@ledgerhq/errors";
import {
AmountRequired,
NotEnoughBalance,
RecipientRequired,
InvalidAddress,
FeeTooHigh,
} from "@ledgerhq/errors";
import type { CosmosAccount, CosmosValidatorItem, StatusErrorMap, Transaction } from "../types";
import {
scanAccounts,
Expand All @@ -14,6 +20,9 @@ import { setCosmosPreloadData, asSafeCosmosPreloadData } from "../preloadedData"
import { getMainAccount } from "../../../account";
import mockPreloadedData from "../preloadedData.mock";
import type { Account, AccountBridge, CurrencyBridge } from "@ledgerhq/types-live";
import { assignFromAccountRaw, assignToAccountRaw } from "../serialization";
import { CosmosValidatorsManager } from "../CosmosValidatorsManager";
import { getCryptoCurrencyById } from "../../../currencies";
const receive = makeAccountBridgeReceive();

const defaultGetFees = (a, t) => (t.fees || new BigNumber(0)).times(t.gas || new BigNumber(0));
Expand Down Expand Up @@ -58,10 +67,16 @@ const getTransactionStatus = (account: Account, t: Transaction) => {
}

// Fill up recipient errors...
if (!t.recipient) {
errors.recipient = new RecipientRequired("");
} else if (isInvalidRecipient(t.recipient)) {
errors.recipient = new InvalidAddress("");
if (t.mode === "send") {
if (!t.recipient) {
errors.recipient = new RecipientRequired("");
} else if (isInvalidRecipient(t.recipient)) {
errors.recipient = new InvalidAddress("");
}
}

if (amount.eq(0)) {
errors.amount = new AmountRequired();
}

return Promise.resolve({
Expand Down Expand Up @@ -99,6 +114,8 @@ const accountBridge: AccountBridge<Transaction> = {
receive,
signOperation,
broadcast,
assignFromAccountRaw,
assignToAccountRaw,
};
const currencyBridge: CurrencyBridge = {
scanAccounts,
Expand All @@ -107,6 +124,11 @@ const currencyBridge: CurrencyBridge = {
return Promise.resolve(mockPreloadedData);
},
hydrate: (data: { validators?: CosmosValidatorItem[] }) => {
if (!data || typeof data !== "object") return;
const { validators } = data;
if (!validators || typeof validators !== "object" || !Array.isArray(validators)) return;
const cosmosValidatorsManager = new CosmosValidatorsManager(getCryptoCurrencyById("cosmos"));
cosmosValidatorsManager.hydrateValidators(validators);
setCosmosPreloadData("cosmos", asSafeCosmosPreloadData(data));
},
};
Expand Down
Loading
Loading