summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-11-28 09:29:14 -0300
committerSebastian <sebasjm@gmail.com>2022-11-28 09:29:14 -0300
commit3577227cc0ff0f9e0c422ae34c4407d88e98ec21 (patch)
tree837b707dbc7d154c5092e50b0296cb8289e0f1ef
parentdcddc4c53a59b087c5e48b6b20ed740514257bc8 (diff)
downloadwallet-core-3577227cc0ff0f9e0c422ae34c4407d88e98ec21.tar.gz
wallet-core-3577227cc0ff0f9e0c422ae34c4407d88e98ec21.tar.bz2
wallet-core-3577227cc0ff0f9e0c422ae34c4407d88e98ec21.zip
fix #7496 with unit tests
-rw-r--r--packages/taler-wallet-webextension/src/components/TermsOfService/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts3
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/cta/Withdraw/state.ts3
-rw-r--r--packages/taler-wallet-webextension/src/utils/index.ts2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Application.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts93
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts167
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx (renamed from packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx)41
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts136
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx (renamed from packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx)379
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Notifications/test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/index.stories.tsx2
17 files changed, 576 insertions, 290 deletions
diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts
index eae4d4ca2..170e7cad8 100644
--- a/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts
+++ b/packages/taler-wallet-webextension/src/components/TermsOfService/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Term of service states", () => {
+ it.skip("should assert", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
index 4ce81dad8..6007b5193 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
@@ -25,11 +25,10 @@ import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
+import { RecursiveState } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
import { Props, State } from "./index.js";
-type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
-
export function useComponentState(
{ amount: amountStr, onClose, onSuccess }: Props,
api: typeof wxApi,
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts
index eae4d4ca2..3ebedfd5a 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Invoice create state", () => {
+ it.skip("should create some tests", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts
index eae4d4ca2..4a3d08ed0 100644
--- a/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Invoice payment state", () => {
+ it.skip("should create some states", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts
index eae4d4ca2..be753e492 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Transfer create states", () => {
+ it.skip("should assert", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts
index eae4d4ca2..fa5b6979a 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Transfer pickup states", () => {
+ it.skip("should assert", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
index 016027163..9bb29fbd6 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
@@ -25,11 +25,10 @@ import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
+import { RecursiveState } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
import { PropsFromParams, PropsFromURI, State } from "./index.js";
-type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
-
export function useComponentStateFromParams(
{ amount, cancel, onSuccess }: PropsFromParams,
api: typeof wxApi,
diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts
index 23cfc7730..c2d7c10a8 100644
--- a/packages/taler-wallet-webextension/src/utils/index.ts
+++ b/packages/taler-wallet-webextension/src/utils/index.ts
@@ -80,7 +80,7 @@ export type StateViewMap<StateType extends { status: string }> = {
[S in StateType as S["status"]]: StateFunc<S>;
};
-type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
+export type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
export function compose<SType extends { status: string }, PType>(
name: string,
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx
index 6362f1924..d150ebfaf 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -48,17 +48,13 @@ import { BackupPage } from "./BackupPage.js";
import { DepositPage } from "./DepositPage/index.js";
import { ExchangeAddPage } from "./ExchangeAddPage.js";
import { HistoryPage } from "./History.js";
-import { ProviderAddPage } from "./ProviderAddPage.js";
import { ProviderDetailPage } from "./ProviderDetailPage.js";
import { SettingsPage } from "./Settings.js";
import { TransactionPage } from "./Transaction.js";
import { WelcomePage } from "./Welcome.js";
import { QrReaderPage } from "./QrReader.js";
import { platform } from "../platform/api.js";
-import {
- DestinationSelectionGetCash,
- DestinationSelectionSendCash,
-} from "./DestinationSelection.js";
+import { DestinationSelectionPage } from "./DestinationSelection/index.js";
import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
import { TransferCreatePage } from "../cta/TransferCreate/index.js";
import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
@@ -153,7 +149,8 @@ export function Application(): VNode {
<Route path={Pages.exchanges} component={ExchangeSelectionPage} />
<Route
path={Pages.sendCash.pattern}
- component={DestinationSelectionSendCash}
+ type="send"
+ component={DestinationSelectionPage}
goToWalletBankDeposit={(amount: string) =>
redirectTo(Pages.balanceDeposit({ amount }))
}
@@ -163,7 +160,8 @@ export function Application(): VNode {
/>
<Route
path={Pages.receiveCash.pattern}
- component={DestinationSelectionGetCash}
+ type="get"
+ component={DestinationSelectionPage}
goToWalletManualWithdraw={(amount?: string) =>
redirectTo(Pages.ctaWithdrawManual({ amount }))
}
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts
new file mode 100644
index 000000000..492da193b
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts
@@ -0,0 +1,93 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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/>
+ */
+
+import { Loading } from "../../components/Loading.js";
+import { HookError } from "../../hooks/useAsyncAsHook.js";
+import { AmountFieldHandler, ButtonHandler } from "../../mui/handlers.js";
+import { compose, StateViewMap } from "../../utils/index.js";
+import { wxApi } from "../../wxApi.js";
+import { useComponentState } from "./state.js";
+import { LoadingUriView, ReadyView, SelectCurrencyView } from "./views.js";
+
+export type Props = PropsGet | PropsSend;
+
+interface PropsGet {
+ type: "get";
+ amount?: string;
+ goToWalletManualWithdraw: (amount: string) => void;
+ goToWalletWalletInvoice: (amount: string) => void;
+}
+interface PropsSend {
+ type: "send";
+ amount?: string;
+ goToWalletBankDeposit: (amount: string) => void;
+ goToWalletWalletSend: (amount: string) => void;
+}
+
+export type State =
+ | State.Loading
+ | State.LoadingUriError
+ | State.Ready
+ | State.SelectCurrency;
+
+export namespace State {
+ export interface Loading {
+ status: "loading";
+ error: undefined;
+ }
+
+ export interface LoadingUriError {
+ status: "loading-error";
+ error: HookError;
+ }
+
+ export interface SelectCurrency {
+ status: "select-currency";
+ error: undefined;
+ currencies: Record<string, string>;
+ onCurrencySelected: (currency: string) => void;
+ }
+
+ export interface Ready {
+ status: "ready";
+ error: undefined;
+ type: Props["type"];
+ selectCurrency: ButtonHandler;
+ previous: Contact[];
+ goToBank: ButtonHandler;
+ goToWallet: ButtonHandler;
+ amountHandler: AmountFieldHandler;
+ }
+}
+
+export type Contact = {
+ icon: string;
+ name: string;
+ description: string;
+};
+
+const viewMapping: StateViewMap<State> = {
+ loading: Loading,
+ "loading-error": LoadingUriView,
+ "select-currency": SelectCurrencyView,
+ ready: ReadyView,
+};
+
+export const DestinationSelectionPage = compose(
+ "DestinationSelectionPage",
+ (p: Props) => useComponentState(p, wxApi),
+ viewMapping,
+);
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts
new file mode 100644
index 000000000..fe02151de
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts
@@ -0,0 +1,167 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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/>
+ */
+
+import { Amounts } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { useState } from "preact/hooks";
+import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
+import { assertUnreachable, RecursiveState } from "../../utils/index.js";
+import { wxApi } from "../../wxApi.js";
+import { Contact, Props, State } from "./index.js";
+import bankIcon from "../../svg/ri-bank-line.svg";
+
+export function useComponentState(
+ props: Props,
+ api: typeof wxApi,
+): RecursiveState<State> {
+ const parsedInitialAmount = !props.amount
+ ? undefined
+ : Amounts.parse(props.amount);
+
+ // const initialCurrency = parsedInitialAmount?.currency;
+
+ const [amount, setAmount] = useState(
+ !parsedInitialAmount ? undefined : parsedInitialAmount,
+ );
+
+ //FIXME: get this information from wallet
+ // eslint-disable-next-line no-constant-condition
+ const previous: Contact[] = true
+ ? []
+ : [
+ {
+ name: "International Bank",
+ icon: bankIcon, //FIXME: should be decided in the view
+ description: "account ending with 3454",
+ },
+ {
+ name: "Max",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ {
+ name: "Alex",
+ icon: bankIcon,
+ description: "account ending with 3454",
+ },
+ ];
+
+ if (!amount) {
+ return () => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const hook = useAsyncAsHook(() =>
+ api.wallet.call(WalletApiOperation.ListExchanges, {}),
+ );
+
+ if (!hook) {
+ return {
+ status: "loading",
+ error: undefined,
+ };
+ }
+ if (hook.hasError) {
+ return {
+ status: "loading-error",
+ error: hook,
+ };
+ }
+ const currencies: Record<string, string> = {};
+ hook.response.exchanges.forEach((e) => {
+ if (e.currency) {
+ currencies[e.currency] = e.currency;
+ }
+ });
+ currencies[""] = "Select a currency";
+
+ return {
+ status: "select-currency",
+ error: undefined,
+ onCurrencySelected: (c: string) => {
+ setAmount(Amounts.zeroOfCurrency(c));
+ },
+ currencies,
+ };
+ };
+ }
+
+ const currencyAndAmount = Amounts.stringify(amount);
+ const invalid = Amounts.isZero(amount);
+
+ switch (props.type) {
+ case "send":
+ return {
+ status: "ready",
+ error: undefined,
+ previous,
+ selectCurrency: {
+ onClick: async () => {
+ setAmount(undefined);
+ },
+ },
+ goToBank: {
+ onClick: invalid
+ ? undefined
+ : async () => {
+ props.goToWalletBankDeposit(currencyAndAmount);
+ },
+ },
+ goToWallet: {
+ onClick: invalid
+ ? undefined
+ : async () => {
+ props.goToWalletWalletSend(currencyAndAmount);
+ },
+ },
+ amountHandler: {
+ onInput: async (s) => setAmount(s),
+ value: amount,
+ },
+ type: props.type,
+ };
+ case "get":
+ return {
+ status: "ready",
+ error: undefined,
+ previous,
+ selectCurrency: {
+ onClick: async () => {
+ setAmount(undefined);
+ },
+ },
+ goToBank: {
+ onClick: invalid
+ ? undefined
+ : async () => {
+ props.goToWalletManualWithdraw(currencyAndAmount);
+ },
+ },
+ goToWallet: {
+ onClick: invalid
+ ? undefined
+ : async () => {
+ props.goToWalletWalletInvoice(currencyAndAmount);
+ },
+ },
+ amountHandler: {
+ onInput: async (s) => setAmount(s),
+ value: amount,
+ },
+ type: props.type,
+ };
+ default:
+ assertUnreachable(props);
+ }
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx
index 166b2c007..b8d868683 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/stories.tsx
@@ -19,25 +19,44 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { createExample } from "../test-utils.js";
-import {
- DestinationSelectionGetCash,
- DestinationSelectionSendCash,
- SelectCurrencyView,
-} from "./DestinationSelection.js";
+import { createExample } from "../../test-utils.js";
+import { ReadyView, SelectCurrencyView } from "./views.js";
export default {
title: "wallet/destination",
};
-export const GetCash = createExample(DestinationSelectionGetCash, {
- amount: "usd:0",
+export const GetCash = createExample(ReadyView, {
+ amountHandler: {
+ value: {
+ currency: "EUR",
+ fraction: 0,
+ value: 2,
+ },
+ },
+ goToBank: {},
+ goToWallet: {},
+ previous: [],
+ selectCurrency: {},
+ type: "get",
});
-export const SendCash = createExample(DestinationSelectionSendCash, {
- amount: "eur:1",
+export const SendCash = createExample(ReadyView, {
+ amountHandler: {
+ value: {
+ currency: "EUR",
+ fraction: 0,
+ value: 1,
+ },
+ },
+ goToBank: {},
+ goToWallet: {},
+ previous: [],
+ selectCurrency: {},
+ type: "send",
});
+
export const SelectCurrency = createExample(SelectCurrencyView, {
- list: {
+ currencies: {
"": "Select a currency",
USD: "USD",
},
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts
new file mode 100644
index 000000000..c2aa04849
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts
@@ -0,0 +1,136 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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 {
+ Amounts,
+ ExchangeEntryStatus,
+ ExchangeListItem,
+ ExchangeTosStatus,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { expect } from "chai";
+import { createWalletApiMock, mountHook } from "../../test-utils.js";
+import { useComponentState } from "./state.js";
+
+const exchangeArs: ExchangeListItem = {
+ currency: "ARS",
+ exchangeBaseUrl: "http://",
+ tosStatus: ExchangeTosStatus.Accepted,
+ exchangeStatus: ExchangeEntryStatus.Ok,
+ paytoUris: [],
+ permanent: true,
+ ageRestrictionOptions: [],
+};
+
+describe("Destination selection states", () => {
+ it("should select currency if no amount specified", async () => {
+ const { handler, mock } = createWalletApiMock();
+
+ handler.addWalletCallResponse(
+ WalletApiOperation.ListExchanges,
+ {},
+ {
+ exchanges: [exchangeArs],
+ },
+ );
+
+ const props = {
+ type: "get" as const,
+ goToWalletManualWithdraw: () => {
+ return null;
+ },
+ goToWalletWalletInvoice: () => {
+ null;
+ },
+ };
+ const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
+ mountHook(() => useComponentState(props, mock));
+
+ {
+ const state = pullLastResultOrThrow();
+
+ if (state.status !== "loading") expect.fail();
+ if (state.error) expect.fail();
+ }
+
+ expect(await waitForStateUpdate()).true;
+
+ {
+ const state = pullLastResultOrThrow();
+
+ if (state.status !== "select-currency") expect.fail();
+ if (state.error) expect.fail();
+ expect(state.currencies).deep.eq({
+ ARS: "ARS",
+ "": "Select a currency",
+ });
+
+ state.onCurrencySelected(exchangeArs.currency!);
+ }
+
+ expect(await waitForStateUpdate()).true;
+
+ {
+ const state = pullLastResultOrThrow();
+
+ if (state.status !== "ready") expect.fail();
+ if (state.error) expect.fail();
+ expect(state.goToBank.onClick).eq(undefined);
+ expect(state.goToWallet.onClick).eq(undefined);
+
+ expect(state.amountHandler.value).deep.eq(Amounts.parseOrThrow("ARS:0"));
+ }
+
+ await assertNoPendingUpdate();
+ expect(handler.getCallingQueueState()).eq("empty");
+ });
+
+ it("should be possible to start with an amount specified in request params", async () => {
+ const { handler, mock } = createWalletApiMock();
+
+ const props = {
+ type: "get" as const,
+ goToWalletManualWithdraw: () => {
+ return null;
+ },
+ goToWalletWalletInvoice: () => {
+ null;
+ },
+ amount: "ARS:2",
+ };
+ const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
+ mountHook(() => useComponentState(props, mock));
+
+ {
+ const state = pullLastResultOrThrow();
+
+ if (state.status !== "ready") expect.fail();
+ if (state.error) expect.fail();
+ expect(state.goToBank.onClick).not.eq(undefined);
+ expect(state.goToWallet.onClick).not.eq(undefined);
+
+ expect(state.amountHandler.value).deep.eq(Amounts.parseOrThrow("ARS:2"));
+ }
+
+ await assertNoPendingUpdate();
+ expect(handler.getCallingQueueState()).eq("empty");
+ });
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
index 7e4c775e6..ba8d65ffa 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
@@ -14,30 +14,70 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts } from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { AmountField } from "../components/AmountField.js";
-import { Loading } from "../components/Loading.js";
-import { LoadingError } from "../components/LoadingError.js";
-import { SelectList } from "../components/SelectList.js";
+import { LoadingError } from "../../components/LoadingError.js";
+import { SelectList } from "../../components/SelectList.js";
import {
Input,
LightText,
LinkPrimary,
SvgIcon,
-} from "../components/styled/index.js";
-import { useTranslationContext } from "../context/translation.js";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
-import { Button } from "../mui/Button.js";
-import { Grid } from "../mui/Grid.js";
-import { Paper } from "../mui/Paper.js";
-import { Pages } from "../NavigationBar.js";
-import arrowIcon from "../svg/chevron-down.svg";
-import bankIcon from "../svg/ri-bank-line.svg";
-import { wxApi } from "../wxApi.js";
+} from "../../components/styled/index.js";
+import { useTranslationContext } from "../../context/translation.js";
+import { Pages } from "../../NavigationBar.js";
+import { Contact, State } from "./index.js";
+import arrowIcon from "../../svg/chevron-down.svg";
+import { AmountField } from "../../components/AmountField.js";
+import { Grid } from "../../mui/Grid.js";
+import { Paper } from "../../mui/Paper.js";
+import { Button } from "../../mui/Button.js";
+import { assertUnreachable } from "../../utils/index.js";
+
+export function LoadingUriView({ error }: State.LoadingUriError): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <LoadingError
+ title={<i18n.Translate>Could not load</i18n.Translate>}
+ error={error}
+ />
+ );
+}
+
+export function SelectCurrencyView({
+ currencies,
+ onCurrencySelected,
+}: State.SelectCurrency): VNode {
+ const { i18n } = useTranslationContext();
+
+ return (
+ <Fragment>
+ <h2>
+ <i18n.Translate>
+ Choose a currency to proceed or add another exchange
+ </i18n.Translate>
+ </h2>
+
+ <p>
+ <Input>
+ <SelectList
+ label={<i18n.Translate>Known currencies</i18n.Translate>}
+ list={currencies}
+ name="lang"
+ value={""}
+ onChange={(v) => onCurrencySelected(v)}
+ />
+ </Input>
+ </p>
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
+ <div />
+ <LinkPrimary href={Pages.settingsExchangeAdd({})}>
+ <i18n.Translate>Add an exchange</i18n.Translate>
+ </LinkPrimary>
+ </div>
+ </Fragment>
+ );
+}
const Container = styled.div`
display: flex;
@@ -47,23 +87,6 @@ const Container = styled.div`
}
`;
-interface PropsGet {
- amount?: string;
- goToWalletManualWithdraw: (amount: string) => void;
- goToWalletWalletInvoice: (amount: string) => void;
-}
-interface PropsSend {
- amount?: string;
- goToWalletBankDeposit: (amount: string) => void;
- goToWalletWalletSend: (amount: string) => void;
-}
-
-type Contact = {
- icon: string;
- name: string;
- description: string;
-};
-
const ContactTable = styled.table`
width: 100%;
& > tr > td {
@@ -165,156 +188,26 @@ const CircleDiv = styled.div`
border: none;
`;
-export function SelectCurrency({
- onChange,
-}: {
- onChange: (s: string) => void;
-}): VNode {
- const { i18n } = useTranslationContext();
-
- const hook = useAsyncAsHook(() =>
- wxApi.wallet.call(WalletApiOperation.ListExchanges, {}),
- );
-
- if (!hook) {
- return <Loading />;
+export function ReadyView(props: State.Ready): VNode {
+ switch (props.type) {
+ case "get":
+ return ReadyGetView(props);
+ case "send":
+ return ReadySendView(props);
+ default:
+ assertUnreachable(props.type);
}
- if (hook.hasError) {
- return (
- <LoadingError
- error={hook}
- title={<i18n.Translate>Could not load list of exchange</i18n.Translate>}
- />
- );
- }
- const list: Record<string, string> = {};
- hook.response.exchanges.forEach((e) => {
- if (e.currency) {
- list[e.currency] = e.currency;
- }
- });
- list[""] = "Select a currency";
- return <SelectCurrencyView onChange={onChange} list={list} />;
}
-
-export function SelectCurrencyView({
- onChange,
- list,
-}: {
- onChange: (s: string) => void;
- list: Record<string, string>;
-}): VNode {
+export function ReadyGetView({
+ amountHandler,
+ goToBank,
+ goToWallet,
+ selectCurrency,
+ previous,
+}: State.Ready): VNode {
const { i18n } = useTranslationContext();
return (
- <Fragment>
- <h2>
- <i18n.Translate>
- Choose a currency to proceed or add another exchange
- </i18n.Translate>
- </h2>
-
- <p>
- <Input>
- <SelectList
- label={<i18n.Translate>Known currencies</i18n.Translate>}
- list={list}
- name="lang"
- value={""}
- onChange={(v) => onChange(v)}
- />
- </Input>
- </p>
- <div style={{ display: "flex", justifyContent: "space-between" }}>
- <div />
- <LinkPrimary href={Pages.settingsExchangeAdd({})}>
- <i18n.Translate>Add an exchange</i18n.Translate>
- </LinkPrimary>
- </div>
- </Fragment>
- );
-}
-
-function RowExample({
- info,
- disabled,
-}: {
- info: Contact;
- disabled?: boolean;
-}): VNode {
- return (
- <MediaExample data-disabled={disabled}>
- <MediaLeft>
- <CircleDiv>
- <SvgIcon
- title={info.name}
- dangerouslySetInnerHTML={{ __html: info.icon }}
- color="currentColor"
- />
- </CircleDiv>
- </MediaLeft>
- <MediaBody>
- <span>{info.name}</span>
- <LightText>{info.description}</LightText>
- </MediaBody>
- <MediaRight>
- <SvgIcon
- title="Select this contact"
- dangerouslySetInnerHTML={{ __html: arrowIcon }}
- color="currentColor"
- transform="rotate(-90deg)"
- />
- </MediaRight>
- </MediaExample>
- );
-}
-
-export function DestinationSelectionGetCash({
- amount: initialAmount,
- goToWalletManualWithdraw,
- goToWalletWalletInvoice,
-}: PropsGet): VNode {
- const parsedInitialAmount = !initialAmount
- ? undefined
- : Amounts.parse(initialAmount);
-
- const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
-
- const [amount, setAmount] = useState(
- parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
- );
-
- const { i18n } = useTranslationContext();
- const previous1: Contact[] = [];
- const previous2: Contact[] = [
- {
- name: "International Bank",
- icon: bankIcon,
- description: "account ending with 3454",
- },
- {
- name: "Max",
- icon: bankIcon,
- description: "account ending with 3454",
- },
- {
- name: "Alex",
- icon: bankIcon,
- description: "account ending with 3454",
- },
- ];
- const previous = previous1;
-
- if (!currency) {
- return (
- <div>
- <SelectCurrency onChange={(c) => setCurrency(c)} />
- </div>
- );
- }
- const currencyAndAmount = Amounts.stringify(amount);
- const invalid = Amounts.isZero(amount);
- return (
<Container>
<h1>
<i18n.Translate>Specify the amount and the origin</i18n.Translate>
@@ -323,12 +216,9 @@ export function DestinationSelectionGetCash({
<AmountField
label={<i18n.Translate>Amount</i18n.Translate>}
required
- handler={{
- onInput: async (s) => setAmount(s),
- value: amount,
- }}
+ handler={amountHandler}
/>
- <Button onClick={async () => setCurrency(undefined)}>
+ <Button onClick={selectCurrency.onClick}>
<i18n.Translate>Change currency</i18n.Translate>
</Button>
</Grid>
@@ -345,7 +235,10 @@ export function DestinationSelectionGetCash({
{previous.map((info, i) => (
<tr key={i}>
<td>
- <RowExample info={info} disabled={invalid} />
+ <RowExample
+ info={info}
+ disabled={!amountHandler.onInput}
+ />
</td>
</tr>
))}
@@ -375,12 +268,7 @@ export function DestinationSelectionGetCash({
<p>
<i18n.Translate>From my bank account</i18n.Translate>
</p>
- <Button
- disabled={invalid}
- onClick={async () =>
- goToWalletManualWithdraw(currencyAndAmount)
- }
- >
+ <Button onClick={goToBank.onClick}>
<i18n.Translate>Withdraw</i18n.Translate>
</Button>
</Paper>
@@ -390,10 +278,7 @@ export function DestinationSelectionGetCash({
<p>
<i18n.Translate>From another wallet</i18n.Translate>
</p>
- <Button
- disabled={invalid}
- onClick={async () => goToWalletWalletInvoice(currencyAndAmount)}
- >
+ <Button onClick={goToWallet.onClick}>
<i18n.Translate>Invoice</i18n.Translate>
</Button>
</Paper>
@@ -403,52 +288,14 @@ export function DestinationSelectionGetCash({
</Container>
);
}
-
-export function DestinationSelectionSendCash({
- amount: initialAmount,
- goToWalletBankDeposit,
- goToWalletWalletSend,
-}: PropsSend): VNode {
- const parsedInitialAmount = !initialAmount
- ? undefined
- : Amounts.parse(initialAmount);
-
- const currency = parsedInitialAmount?.currency;
-
- const [amount, setAmount] = useState(
- parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
- );
+export function ReadySendView({
+ amountHandler,
+ goToBank,
+ goToWallet,
+ previous,
+}: State.Ready): VNode {
const { i18n } = useTranslationContext();
- const previous1: Contact[] = [];
- const previous2: Contact[] = [
- {
- name: "International Bank",
- icon: bankIcon,
- description: "account ending with 3454",
- },
- {
- name: "Max",
- icon: bankIcon,
- description: "account ending with 3454",
- },
- {
- name: "Alex",
- icon: bankIcon,
- description: "account ending with 3454",
- },
- ];
- const previous = previous1;
- if (!currency) {
- return (
- <div>
- <i18n.Translate>currency not provided</i18n.Translate>
- </div>
- );
- }
- const currencyAndAmount = Amounts.stringify(amount);
- //const parsedAmount = Amounts.parse(currencyAndAmount);
- const invalid = Amounts.isZero(amount);
return (
<Container>
<h1>
@@ -459,10 +306,7 @@ export function DestinationSelectionSendCash({
<AmountField
label={<i18n.Translate>Amount</i18n.Translate>}
required
- handler={{
- onInput: async (s) => setAmount(s),
- value: amount,
- }}
+ handler={amountHandler}
/>
</div>
@@ -478,7 +322,10 @@ export function DestinationSelectionSendCash({
{previous.map((info, i) => (
<tr key={i}>
<td>
- <RowExample info={info} disabled={invalid} />
+ <RowExample
+ info={info}
+ disabled={!amountHandler.onInput}
+ />
</td>
</tr>
))}
@@ -510,10 +357,7 @@ export function DestinationSelectionSendCash({
<p>
<i18n.Translate>To my bank account</i18n.Translate>
</p>
- <Button
- disabled={invalid}
- onClick={async () => goToWalletBankDeposit(currencyAndAmount)}
- >
+ <Button onClick={goToBank.onClick}>
<i18n.Translate>Deposit</i18n.Translate>
</Button>
</Paper>
@@ -523,10 +367,7 @@ export function DestinationSelectionSendCash({
<p>
<i18n.Translate>To another wallet</i18n.Translate>
</p>
- <Button
- disabled={invalid}
- onClick={async () => goToWalletWalletSend(currencyAndAmount)}
- >
+ <Button onClick={goToWallet.onClick}>
<i18n.Translate>Send</i18n.Translate>
</Button>
</Paper>
@@ -536,3 +377,37 @@ export function DestinationSelectionSendCash({
</Container>
);
}
+
+function RowExample({
+ info,
+ disabled,
+}: {
+ info: Contact;
+ disabled?: boolean;
+}): VNode {
+ return (
+ <MediaExample data-disabled={disabled}>
+ <MediaLeft>
+ <CircleDiv>
+ <SvgIcon
+ title={info.name}
+ dangerouslySetInnerHTML={{ __html: info.icon }}
+ color="currentColor"
+ />
+ </CircleDiv>
+ </MediaLeft>
+ <MediaBody>
+ <span>{info.name}</span>
+ <LightText>{info.description}</LightText>
+ </MediaBody>
+ <MediaRight>
+ <SvgIcon
+ title="Select this contact"
+ dangerouslySetInnerHTML={{ __html: arrowIcon }}
+ color="currentColor"
+ transform="rotate(-90deg)"
+ />
+ </MediaRight>
+ </MediaExample>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts
index eae4d4ca2..868269ec0 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Manage Account states", () => {
+ it.skip("should create some tests", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts
index eae4d4ca2..c4ce1efc7 100644
--- a/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/Notifications/test.ts
@@ -21,8 +21,8 @@
import { expect } from "chai";
-describe("test description", () => {
- it("should assert", () => {
+describe("Notifications states", () => {
+ it.skip("should create some tests", () => {
expect([]).deep.equals([]);
});
});
diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
index 20de1a3c3..554527108 100644
--- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
@@ -33,7 +33,7 @@ import * as a14 from "./Welcome.stories.js";
import * as a15 from "./AddNewActionView.stories.js";
import * as a16 from "./DeveloperPage.stories.js";
import * as a17 from "./QrReader.stories.js";
-import * as a18 from "./DestinationSelection.stories.js";
+import * as a18 from "./DestinationSelection/stories.js";
import * as a19 from "./ExchangeSelection/stories.js";
import * as a20 from "./ManageAccount/stories.js";
import * as a21 from "./Notifications/stories.js";