gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: some DepositPage unit test


From: gnunet
Subject: [taler-wallet-core] branch master updated: some DepositPage unit test
Date: Wed, 23 Mar 2022 21:50:54 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new cc18751e some DepositPage unit test
cc18751e is described below

commit cc18751e72435544297de4f5b5a6b318fbba9cd1
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Mar 23 17:50:06 2022 -0300

    some DepositPage unit test
---
 .../rollup.config.test.js                          |   2 +-
 .../src/wallet/CreateManualWithdraw.test.ts        |  12 +-
 .../src/wallet/CreateManualWithdraw.tsx            |   2 +
 .../src/wallet/DepositPage.stories.tsx             |  18 +-
 .../src/wallet/DepositPage.test.ts                 |  63 +++++
 .../src/wallet/DepositPage.tsx                     | 258 +++++++++++++--------
 6 files changed, 250 insertions(+), 105 deletions(-)

diff --git a/packages/taler-wallet-webextension/rollup.config.test.js 
b/packages/taler-wallet-webextension/rollup.config.test.js
index 387e176b..9a706fc6 100644
--- a/packages/taler-wallet-webextension/rollup.config.test.js
+++ b/packages/taler-wallet-webextension/rollup.config.test.js
@@ -25,7 +25,7 @@ function fromDir(startPath, regex) {
 }
 
 const tests = fromDir('./src', /.test.ts$/)
-  .filter(t => t === 'src/wallet/CreateManualWithdraw.test.ts')
+  // .filter(t => t === 'src/wallet/DepositPage.test.ts')
   .map(test => ({
     input: test,
     output: {
diff --git 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts
index a5174bef..0fb12514 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts
@@ -40,8 +40,7 @@ describe("CreateManualWithdraw states", () => {
     );
 
     if (!result.current) {
-      expect(result.current).not.to.be.undefined;
-      return;
+      expect.fail("hook didn't render");
     }
 
     expect(result.current.noExchangeFound).equal(true)
@@ -53,8 +52,7 @@ describe("CreateManualWithdraw states", () => {
     );
 
     if (!result.current) {
-      expect(result.current).not.to.be.undefined;
-      return;
+      expect.fail("hook didn't render");
     }
 
     expect(result.current.noExchangeFound).equal(true)
@@ -67,8 +65,7 @@ describe("CreateManualWithdraw states", () => {
     );
 
     if (!result.current) {
-      expect(result.current).not.to.be.undefined;
-      return;
+      expect.fail("hook didn't render");
     }
 
     expect(result.current.exchange.value).equal("url1")
@@ -80,8 +77,7 @@ describe("CreateManualWithdraw states", () => {
     );
 
     if (!result.current) {
-      expect(result.current).not.to.be.undefined;
-      return;
+      expect.fail("hook didn't render");
     }
 
     expect(result.current.exchange.value).equal("url2")
diff --git 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
index 2d5129a3..bc4b0357 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
@@ -55,10 +55,12 @@ export interface State {
 export interface TextFieldHandler {
   onInput: (value: string) => void;
   value: string;
+  error?: string;
 }
 
 export interface SelectFieldHandler {
   onChange: (value: string) => void;
+  error?: string;
   value: string;
   list: Record<string, string>;
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/DepositPage.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/DepositPage.stories.tsx
index 2e2d4cb3..ddd4cdc9 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage.stories.tsx
@@ -19,7 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, Balance, parsePaytoUri } from "@gnu-taler/taler-util";
 import { DepositFee } from 
"@gnu-taler/taler-wallet-core/src/operations/deposits";
 import { createExample } from "../test-utils";
 import { View as TestedComponent } from "./DepositPage";
@@ -40,13 +40,21 @@ async function alwaysReturnFeeToOne(): Promise<DepositFee> {
 }
 
 export const WithEmptyAccountList = createExample(TestedComponent, {
-  knownBankAccounts: [],
-  balance: Amounts.parseOrThrow("USD:10"),
+  accounts: [],
+  balances: [
+    {
+      available: "USD:10",
+    } as Balance,
+  ],
   onCalculateFee: alwaysReturnFeeToOne,
 });
 
 export const WithSomeBankAccounts = createExample(TestedComponent, {
-  knownBankAccounts: [parsePaytoUri("payto://iban/ES8877998399652238")!],
-  balance: Amounts.parseOrThrow("EUR:10"),
+  accounts: [parsePaytoUri("payto://iban/ES8877998399652238")!],
+  balances: [
+    {
+      available: "USD:10",
+    } as Balance,
+  ],
   onCalculateFee: alwaysReturnFeeToOne,
 });
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage.test.ts 
b/packages/taler-wallet-webextension/src/wallet/DepositPage.test.ts
new file mode 100644
index 00000000..8ff95fdc
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage.test.ts
@@ -0,0 +1,63 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { useComponentState } from "./DepositPage";
+import { expect } from "chai";
+import { mountHook } from "../test-utils";
+import { Amounts, Balance } from "@gnu-taler/taler-util";
+
+
+const currency = "EUR"
+const feeCalculator = async () => ({
+  coin: Amounts.parseOrThrow(`${currency}:1`),
+  wire: Amounts.parseOrThrow(`${currency}:1`),
+  refresh: Amounts.parseOrThrow(`${currency}:1`)
+})
+
+const someBalance = [{
+  available: 'EUR:10'
+} as Balance]
+
+describe("DepositPage states", () => {
+  it("should have status 'no-balance' when balance is empty", () => {
+    const { result } = mountHook(() =>
+      useComponentState(currency, [], [], feeCalculator),
+    );
+
+    if (!result.current) {
+      expect.fail("hook didn't render");
+    }
+
+    expect(result.current.status).equal("no-balance")
+  });
+
+  it("should have status 'no-accounts' when balance is not empty and accounts 
is empty", () => {
+    const { result } = mountHook(() =>
+      useComponentState(currency, [], someBalance, feeCalculator),
+    );
+
+    if (!result.current) {
+      expect.fail("hook didn't render");
+    }
+
+    expect(result.current.status).equal("no-accounts")
+  });
+});
\ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx
index 85541ab2..b420c7eb 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<void> {
+  async function doSend(p: PaytoUri, a: AmountJson): Promise<void> {
+    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<DepositFee> {
+    const account = `payto://${p.targetType}/${p.targetPath}`;
+    const amount = Amounts.stringify(a);
     return await wxApi.getFeeForDeposit(account, amount);
   }
 
-  if (accounts.length === 0) return <Loading />;
+  if (state === undefined) return <Loading />;
+
+  if (state.hasError) {
+    return (
+      <LoadingError
+        title={<i18n.Translate>Could not load deposit balance</i18n.Translate>}
+        error={state}
+      />
+    );
+  }
 
   return (
     <View
-      onCancel={onCancel}
-      knownBankAccounts={accounts}
-      balance={currencyBalance}
+      onCancel={() => 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<PaytoUri>;
-  balance: AmountJson;
-  onCancel: (currency: string) => void;
-  onSend: (account: string, amount: AmountString) => Promise<void>;
+  accounts: Array<PaytoUri>;
+  currency: string;
+  balances: Balance[];
+  onCancel: () => void;
+  onSend: (account: PaytoUri, amount: AmountJson) => Promise<void>;
   onCalculateFee: (
-    account: string,
-    amount: AmountString,
+    account: PaytoUri,
+    amount: AmountJson,
   ) => Promise<DepositFee>;
 }
 
-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<DepositFee>,
+): State {
+  const accountMap = createLabelsForBankAccount(accounts);
   const [accountIdx, setAccountIdx] = useState(0);
   const [amount, setAmount] = useState<number | undefined>(undefined);
   const [fee, setFee] = useState<DepositFee | undefined>(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 (
       <div>
         <i18n.Translate>no balance</i18n.Translate>
       </div>
     );
   }
-  if (!knownBankAccounts || !knownBankAccounts.length) {
+  if (state.status === "no-accounts") {
     return (
       <Fragment>
         <WarningBox>
@@ -159,30 +258,13 @@ export function View({
           </ButtonBoxWarning>
         </WarningBox>
         <footer>
-          <Button onClick={() => onCancel(currency)}>
+          <Button onClick={onCancel}>
             <i18n.Translate>Cancel</i18n.Translate>
           </Button>
         </footer>
       </Fragment>
     );
   }
-  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 (
     <Fragment>
@@ -193,13 +275,13 @@ export function View({
         <Input>
           <SelectList
             label={<i18n.Translate>Bank account IBAN number</i18n.Translate>}
-            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}
           />
         </Input>
-        <InputWithLabel invalid={!!error}>
+        <InputWithLabel invalid={!!state.amount.error}>
           <label>
             <i18n.Translate>Amount</i18n.Translate>
           </label>
@@ -207,19 +289,11 @@ export function View({
             <span>{currency}</span>
             <input
               type="number"
-              value={amount}
-              onInput={(e) => {
-                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)}
             />
           </div>
-          {error && <ErrorText>{error}</ErrorText>}
+          {state.amount.error && <ErrorText>{state.amount.error}</ErrorText>}
         </InputWithLabel>
         {
           <Fragment>
@@ -232,7 +306,7 @@ export function View({
                 <input
                   type="number"
                   disabled
-                  value={Amounts.stringifyValue(feeSum)}
+                  value={Amounts.stringifyValue(state.totalFee)}
                 />
               </div>
             </InputWithLabel>
@@ -246,7 +320,7 @@ export function View({
                 <input
                   type="number"
                   disabled
-                  value={Amounts.stringifyValue(totalToDeposit)}
+                  value={Amounts.stringifyValue(state.totalToDeposit)}
                 />
               </div>
             </InputWithLabel>
@@ -254,17 +328,19 @@ export function View({
         }
       </section>
       <footer>
-        <Button onClick={() => onCancel(currency)}>
+        <Button onClick={onCancel}>
           <i18n.Translate>Cancel</i18n.Translate>
         </Button>
-        {unableToDeposit ? (
+        {state.unableToDeposit ? (
           <ButtonPrimary disabled>
             <i18n.Translate>Deposit</i18n.Translate>
           </ButtonPrimary>
         ) : (
-          <ButtonPrimary onClick={() => onSend(accountURI, amountStr)}>
+          <ButtonPrimary
+            onClick={() => onSend(state.selectedAccount, state.parsedAmount!)}
+          >
             <i18n.Translate>
-              Deposit {Amounts.stringifyValue(totalToDeposit)} {currency}
+              Deposit {Amounts.stringifyValue(state.totalToDeposit)} {currency}
             </i18n.Translate>
           </ButtonPrimary>
         )}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]