From cc18751e72435544297de4f5b5a6b318fbba9cd1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 23 Mar 2022 17:50:06 -0300 Subject: some DepositPage unit test --- .../src/wallet/DepositPage.tsx | 258 +++++++++++++-------- 1 file changed, 167 insertions(+), 91 deletions(-) (limited to 'packages/taler-wallet-webextension/src/wallet/DepositPage.tsx') diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx index 85541ab23..b420c7ebb 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx @@ -18,12 +18,15 @@ import { AmountJson, Amounts, AmountString, + Balance, PaytoUri, } from "@gnu-taler/taler-util"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; +import { saturate } from "polished"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Loading } from "../components/Loading"; +import { LoadingError } from "../components/LoadingError"; import { SelectList } from "../components/SelectList"; import { Button, @@ -37,6 +40,7 @@ import { import { useTranslationContext } from "../context/translation"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import * as wxApi from "../wxApi"; +import { SelectFieldHandler, TextFieldHandler } from "./CreateManualWithdraw"; interface Props { currency: string; @@ -45,45 +49,46 @@ interface Props { } export function DepositPage({ currency, onCancel, onSuccess }: Props): VNode { const state = useAsyncAsHook(async () => { - const balance = await wxApi.getBalance(); - const bs = balance.balances.filter((b) => b.available.startsWith(currency)); - const currencyBalance = - bs.length === 0 - ? Amounts.getZero(currency) - : Amounts.parseOrThrow(bs[0].available); - const knownAccounts = await wxApi.listKnownBankAccounts(currency); - return { accounts: knownAccounts.accounts, currencyBalance }; + const { balances } = await wxApi.getBalance(); + const { accounts } = await wxApi.listKnownBankAccounts(currency); + return { accounts, balances }; }); - const accounts = - state === undefined ? [] : state.hasError ? [] : state.response.accounts; - - const currencyBalance = - state === undefined - ? Amounts.getZero(currency) - : state.hasError - ? Amounts.getZero(currency) - : state.response.currencyBalance; + const { i18n } = useTranslationContext(); - async function doSend(account: string, amount: AmountString): Promise { + async function doSend(p: PaytoUri, a: AmountJson): Promise { + const account = `payto://${p.targetType}/${p.targetPath}`; + const amount = Amounts.stringify(a); await wxApi.createDepositGroup(account, amount); onSuccess(currency); } async function getFeeForAmount( - account: string, - amount: AmountString, + p: PaytoUri, + a: AmountJson, ): Promise { + const account = `payto://${p.targetType}/${p.targetPath}`; + const amount = Amounts.stringify(a); return await wxApi.getFeeForDeposit(account, amount); } - if (accounts.length === 0) return ; + if (state === undefined) return ; + + if (state.hasError) { + return ( + Could not load deposit balance} + error={state} + /> + ); + } return ( onCancel(currency)} + currency={currency} + accounts={state.response.accounts} + balances={state.response.balances} onSend={doSend} onCalculateFee={getFeeForAmount} /> @@ -91,25 +96,46 @@ export function DepositPage({ currency, onCancel, onSuccess }: Props): VNode { } interface ViewProps { - knownBankAccounts: Array; - balance: AmountJson; - onCancel: (currency: string) => void; - onSend: (account: string, amount: AmountString) => Promise; + accounts: Array; + currency: string; + balances: Balance[]; + onCancel: () => void; + onSend: (account: PaytoUri, amount: AmountJson) => Promise; onCalculateFee: ( - account: string, - amount: AmountString, + account: PaytoUri, + amount: AmountJson, ) => Promise; } -export function View({ - onCancel, - knownBankAccounts, - balance, - onSend, - onCalculateFee, -}: ViewProps): VNode { - const { i18n } = useTranslationContext(); - const accountMap = createLabelsForBankAccount(knownBankAccounts); +type State = NoBalanceState | NoAccountsState | DepositState; + +interface NoBalanceState { + status: "no-balance"; +} +interface NoAccountsState { + status: "no-accounts"; +} +interface DepositState { + status: "deposit"; + amount: TextFieldHandler; + account: SelectFieldHandler; + totalFee: AmountJson; + totalToDeposit: AmountJson; + unableToDeposit: boolean; + selectedAccount: PaytoUri; + parsedAmount: AmountJson | undefined; +} + +export function useComponentState( + currency: string, + accounts: PaytoUri[], + balances: Balance[], + onCalculateFee: ( + account: PaytoUri, + amount: AmountJson, + ) => Promise, +): State { + const accountMap = createLabelsForBankAccount(accounts); const [accountIdx, setAccountIdx] = useState(0); const [amount, setAmount] = useState(undefined); const [fee, setFee] = useState(undefined); @@ -117,35 +143,108 @@ export function View({ setAmount(num); setFee(undefined); } - const currency = balance.currency; - const amountStr: AmountString = `${currency}:${amount}`; - const feeSum = + + const selectedAmountSTR: AmountString = `${currency}:${amount}`; + const totalFee = fee !== undefined ? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount : Amounts.getZero(currency); - const account = knownBankAccounts.length - ? knownBankAccounts[accountIdx] - : undefined; - const accountURI = !account - ? "" - : `payto://${account.targetType}/${account.targetPath}`; + const selectedAccount = accounts.length ? accounts[accountIdx] : undefined; + + const parsedAmount = + amount === undefined ? undefined : Amounts.parse(selectedAmountSTR); useEffect(() => { - if (amount === undefined) return; - onCalculateFee(accountURI, amountStr).then((result) => { + if (selectedAccount === undefined || parsedAmount === undefined) return; + onCalculateFee(selectedAccount, parsedAmount).then((result) => { setFee(result); }); }, [amount]); - if (!balance) { + const bs = balances.filter((b) => b.available.startsWith(currency)); + const balance = + bs.length > 0 + ? Amounts.parseOrThrow(bs[0].available) + : Amounts.getZero(currency); + + const isDirty = amount !== 0; + const amountError = !isDirty + ? undefined + : !parsedAmount + ? "Invalid amount" + : Amounts.cmp(balance, parsedAmount) === -1 + ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` + : undefined; + + const totalToDeposit = parsedAmount + ? Amounts.sub(parsedAmount, totalFee).amount + : Amounts.getZero(currency); + + const unableToDeposit = + Amounts.isZero(totalToDeposit) || + fee === undefined || + amountError !== undefined; + + if (Amounts.isZero(balance)) { + return { + status: "no-balance", + }; + } + + if (!accounts || !accounts.length || !selectedAccount) { + return { + status: "no-accounts", + }; + } + + return { + status: "deposit", + amount: { + value: String(amount), + onInput: (e) => { + const num = parseFloat(e); + if (!Number.isNaN(num)) { + updateAmount(num); + } else { + updateAmount(undefined); + setFee(undefined); + } + }, + error: amountError, + }, + account: { + list: accountMap, + value: String(accountIdx), + onChange: (s) => setAccountIdx(parseInt(s, 10)), + }, + totalFee, + totalToDeposit, + unableToDeposit, + selectedAccount, + parsedAmount, + }; +} + +export function View({ + onCancel, + currency, + accounts, + balances, + onSend, + onCalculateFee, +}: ViewProps): VNode { + const { i18n } = useTranslationContext(); + const state = useComponentState(currency, accounts, balances, onCalculateFee); + + if (state.status === "no-balance") { return (
no balance
); } - if (!knownBankAccounts || !knownBankAccounts.length) { + if (state.status === "no-accounts") { return ( @@ -159,30 +258,13 @@ export function View({
-
); } - const parsedAmount = - amount === undefined ? undefined : Amounts.parse(amountStr); - const isDirty = amount !== 0; - const error = !isDirty - ? undefined - : !parsedAmount - ? "Invalid amount" - : Amounts.cmp(balance, parsedAmount) === -1 - ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` - : undefined; - - const totalToDeposit = parsedAmount - ? Amounts.sub(parsedAmount, feeSum).amount - : Amounts.getZero(currency); - - const unableToDeposit = - Amounts.isZero(totalToDeposit) || fee === undefined || error !== undefined; return ( @@ -193,13 +275,13 @@ export function View({ Bank account IBAN number} - list={accountMap} + list={state.account.list} name="account" - value={String(accountIdx)} - onChange={(s) => setAccountIdx(parseInt(s, 10))} + value={state.account.value} + onChange={state.account.onChange} /> - + @@ -207,19 +289,11 @@ export function View({ {currency} { - const num = parseFloat(e.currentTarget.value); - if (!Number.isNaN(num)) { - updateAmount(num); - } else { - updateAmount(undefined); - setFee(undefined); - } - }} + value={state.amount.value} + onInput={(e) => state.amount.onInput(e.currentTarget.value)} /> - {error && {error}} + {state.amount.error && {state.amount.error}} { @@ -232,7 +306,7 @@ export function View({ @@ -246,7 +320,7 @@ export function View({ @@ -254,17 +328,19 @@ export function View({ }