diff options
Diffstat (limited to 'packages')
67 files changed, 1002 insertions, 1444 deletions
diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index 0deca26cc..5c6983bfc 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -9,7 +9,7 @@ "private": false, "scripts": { "clean": "rimraf dist lib tsconfig.tsbuildinfo", - "test": "pnpm compile && mocha 'dist/**/*.test.js' 'dist/**/test.js'", + "test": "pnpm compile && mocha --require source-map-support/register 'dist/**/*.test.js' 'dist/**/test.js'", "test:coverage": "nyc pnpm test", "compile": "tsc && ./build-fast-with-linaria.mjs", "prepare": "pnpm compile", @@ -80,4 +80,4 @@ "pogen": { "domain": "taler-wallet-webex" } -}
\ No newline at end of file +} diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx index 8fb289aa6..ab36af376 100644 --- a/packages/taler-wallet-webextension/src/NavigationBar.tsx +++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -24,20 +24,20 @@ /** * Imports. */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; +import { JustInDevMode } from "./components/JustInDevMode.js"; import { NavigationHeader, NavigationHeaderHolder, SvgIcon, } from "./components/styled/index.js"; +import { useBackendContext } from "./context/backend.js"; import { useTranslationContext } from "./context/translation.js"; -import settingsIcon from "./svg/settings_black_24dp.svg"; +import { useAsyncAsHook } from "./hooks/useAsyncAsHook.js"; import qrIcon from "./svg/qr_code_24px.svg"; +import settingsIcon from "./svg/settings_black_24dp.svg"; import warningIcon from "./svg/warning_24px.svg"; -import { useAsyncAsHook } from "./hooks/useAsyncAsHook.js"; -import { wxApi } from "./wxApi.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { JustInDevMode } from "./components/JustInDevMode.js"; /** * List of pages used by the wallet @@ -133,13 +133,8 @@ export const Pages = { ), }; -export function PopupNavBar({ - path = "", -}: { - path?: string; -}): // api: typeof wxApi, -VNode { - const api = wxApi; //FIXME: as parameter +export function PopupNavBar({ path = "" }: { path?: string }): VNode { + const api = useBackendContext(); const hook = useAsyncAsHook(async () => { return await api.wallet.call( WalletApiOperation.GetUserAttentionUnreadCount, @@ -194,7 +189,7 @@ VNode { export function WalletNavBar({ path = "" }: { path?: string }): VNode { const { i18n } = useTranslationContext(); - const api = wxApi; //FIXME: as parameter + const api = useBackendContext(); const hook = useAsyncAsHook(async () => { return await api.wallet.call( WalletApiOperation.GetUserAttentionUnreadCount, diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index 5d5dae092..85b43fb4e 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -22,11 +22,11 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, JSX, VNode } from "preact"; import { useEffect } from "preact/hooks"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Avatar } from "../mui/Avatar.js"; import { Typography } from "../mui/Typography.js"; -import { wxApi } from "../wxApi.js"; import Banner from "./Banner.js"; import { Time } from "./Time.js"; @@ -35,12 +35,13 @@ interface Props extends JSX.HTMLAttributes { } export function PendingTransactions({ goToTransaction }: Props): VNode { + const api = useBackendContext(); const state = useAsyncAsHook(() => - wxApi.wallet.call(WalletApiOperation.GetTransactions, {}), + api.wallet.call(WalletApiOperation.GetTransactions, {}), ); useEffect(() => { - return wxApi.listener.onUpdateNotification( + return api.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], state?.retry, ); diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx index 6461f76e3..47c10347c 100644 --- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx +++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -25,11 +25,11 @@ import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; import { Modal } from "../components/Modal.js"; import { Time } from "../components/Time.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../mui/handlers.js"; import { compose, StateViewMap } from "../utils/index.js"; -import { wxApi } from "../wxApi.js"; import { Amount } from "./Amount.js"; import { Link } from "./styled/index.js"; @@ -98,7 +98,8 @@ interface Props { proposalId: string; } -function useComponentState({ proposalId }: Props, api: typeof wxApi): State { +function useComponentState({ proposalId }: Props): State { + const api = useBackendContext(); const [show, setShow] = useState(false); const hook = useAsyncAsHook(async () => { if (!show) return undefined; @@ -139,7 +140,7 @@ const viewMapping: StateViewMap<State> = { export const ShowFullContractTermPopup = compose( "ShowFullContractTermPopup", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts index 4d2c4346e..5d5ad3ba2 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts @@ -18,7 +18,6 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ToggleHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { TermsState } from "./utils.js"; import { @@ -26,7 +25,7 @@ import { LoadingUriView, ShowButtonsAcceptedTosView, ShowButtonsNonAcceptedTosView, - ShowTosContentView, + ShowTosContentView } from "./views.js"; export interface Props { @@ -89,6 +88,6 @@ const viewMapping: StateViewMap<State> = { export const TermsOfService = compose( "TermsOfService", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts index c5be71ef0..5006cefce 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts @@ -16,15 +16,15 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; import { buildTermsOfServiceState } from "./utils.js"; export function useComponentState( { exchangeUrl, onChange }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const readOnly = !onChange; const [showContent, setShowContent] = useState<boolean>(readOnly); const [errorAccepting, setErrorAccepting] = useState<Error | undefined>( diff --git a/packages/taler-wallet-webextension/src/context/backend.ts b/packages/taler-wallet-webextension/src/context/backend.ts new file mode 100644 index 000000000..3e9e1f0ab --- /dev/null +++ b/packages/taler-wallet-webextension/src/context/backend.ts @@ -0,0 +1,53 @@ +/* + 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 { ComponentChildren, createContext, h, VNode } from "preact"; +import { useContext } from "preact/hooks"; +import { wxApi, WxApiType } from "../wxApi.js"; + +type Type = WxApiType + +const initial = wxApi; + +const Context = createContext<Type>(initial); + +type Props = Partial<WxApiType> & { + children: ComponentChildren; +} + +export const BackendProvider = ({ + wallet, + background, + listener, + children, +}: Props): VNode => { + + return h(Context.Provider, { + value: { + wallet: wallet ?? initial.wallet, + background: background ?? initial.background, + listener: listener ?? initial.listener + }, + children, + }); +}; + +export const useBackendContext = (): Type => useContext(Context); diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts index 539709821..9ff3ddd1d 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts @@ -19,7 +19,6 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { 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 } from "./views.js"; @@ -64,6 +63,6 @@ const viewMapping: StateViewMap<State> = { export const DepositPage = compose( "Deposit", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts index 77e918ca9..fbcd107ef 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -16,14 +16,14 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerDepositUri, amountStr, cancel, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const info = useAsyncAsHook(async () => { if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT"); if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT"); diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts index a5bfed4a8..1c8d4708d 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -20,16 +20,18 @@ */ import { Amounts } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; -import { mountHook } from "../../test-utils.js"; import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; +import { tests } from "@gnu-taler/web-util/lib/index.browser"; +import { Props } from "./index.js"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; describe("Deposit CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { handler, mock } = createWalletApiMock(); - const props = { + const { handler, TestingContext } = createWalletApiMock(); + + const props: Props = { talerDepositUri: undefined, amountStr: undefined, cancel: async () => { @@ -39,32 +41,28 @@ describe("Deposit CTA states", () => { null; }, }; - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - { - const { status } = pullLastResultOrThrow(); - expect(status).equals("loading"); - } - - expect(await waitForStateUpdate()).true; - - { - const { status, error } = pullLastResultOrThrow(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equals("loading"); + }, + ({ status, error }) => { + expect(status).equals("loading-uri"); - expect(status).equals("loading-uri"); + if (!error) expect.fail(); + if (!error.hasError) expect.fail(); + if (error.operational) expect.fail(); + expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); + }, + ], TestingContext) - if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); - } - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be ready after loading", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); + handler.addWalletCallResponse( WalletApiOperation.PrepareDeposit, undefined, @@ -73,6 +71,7 @@ describe("Deposit CTA states", () => { totalDepositCost: "EUR:1.2", }, ); + const props = { talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1", @@ -84,28 +83,21 @@ describe("Deposit CTA states", () => { }, }; - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equals("loading"); - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "ready") expect.fail(); - if (state.error) expect.fail(); - expect(state.confirm.onClick).not.undefined; - expect(state.cost).deep.eq(Amounts.parseOrThrow("EUR:1.2")); - expect(state.fee).deep.eq(Amounts.parseOrThrow("EUR:0.2")); - expect(state.effective).deep.eq(Amounts.parseOrThrow("EUR:1")); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equals("loading"); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + if (state.error) expect.fail(); + expect(state.confirm.onClick).not.undefined; + expect(state.cost).deep.eq(Amounts.parseOrThrow("EUR:1.2")); + expect(state.fee).deep.eq(Amounts.parseOrThrow("EUR:0.2")); + expect(state.effective).deep.eq(Amounts.parseOrThrow("EUR:1")); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts index 01dbb6d6d..0569e8e5f 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts @@ -22,7 +22,6 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; @@ -78,6 +77,6 @@ const viewMapping: StateViewMap<State> = { export const InvoiceCreatePage = compose( "InvoiceCreatePage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index 6007b5193..a26167f8e 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -23,17 +23,17 @@ import { import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { isFuture, parse } from "date-fns"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; 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"; export function useComponentState( { amount: amountStr, onClose, onSuccess }: Props, - api: typeof wxApi, ): RecursiveState<State> { const amount = Amounts.parseOrThrow(amountStr); + const api = useBackendContext() const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}), @@ -158,8 +158,8 @@ export function useComponentState( subject === undefined ? undefined : !subject - ? "Can't be empty" - : undefined, + ? "Can't be empty" + : undefined, value: subject ?? "", onInput: async (e) => setSubject(e), }, diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts index 6e16b528c..78f244964 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts @@ -18,13 +18,12 @@ import { AbsoluteTime, AmountJson, PreparePayResult, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { 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 } from "./views.js"; @@ -92,6 +91,6 @@ const viewMapping: StateViewMap<State> = { export const InvoicePayPage = compose( "InvoicePayPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts index c7fb48958..eb50ba748 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts @@ -25,14 +25,14 @@ import { } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerPayPullUri, onClose, goToWalletManualWithdraw, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const hook = useAsyncAsHook(async () => { const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, { talerUri: talerPayPullUri, diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts index 9bca8f74f..45e4a5b88 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts @@ -19,13 +19,12 @@ import { PreparePayResult, PreparePayResultAlreadyConfirmed, PreparePayResultInsufficientBalance, - PreparePayResultPaymentPossible, + PreparePayResultPaymentPossible } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { BaseView, LoadingUriView } from "./views.js"; @@ -96,6 +95,6 @@ const viewMapping: StateViewMap<State> = { export const PaymentPage = compose( "Payment", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts index 970af5b81..7690910e6 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/state.ts @@ -23,16 +23,16 @@ import { } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerPayUri, cancel, goToWalletManualWithdraw, onSuccess }: Props, - api: typeof wxApi, ): State { const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined); + const api = useBackendContext() const hook = useAsyncAsHook(async () => { if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT"); diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index b02ac6274..aba76fcf4 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -30,54 +30,46 @@ import { } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; +import { tests } from "../../../../web-util/src/index.browser.js"; import { mountHook, nullFunction } from "../../test-utils.js"; import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Payment CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: undefined, cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - { - const { status, error } = pullLastResultOrThrow(); - - expect(status).equals("loading-uri"); - if (error === undefined) expect.fail(); - expect(error.hasError).true; - expect(error.operational).false; - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + ({ status, error }) => { + expect(status).equals("loading-uri"); + if (error === undefined) expect.fail(); + expect(error.hasError).true; + expect(error.operational).false; + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); it("should response with no balance", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; handler.addWalletCallResponse( @@ -94,41 +86,34 @@ describe("Payment CTA states", () => { { balances: [] }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "no-balance-for-currency") { - expect(r).eq({}); - return; - } - expect(r.balance).undefined; - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "no-balance-for-currency") { + expect(state).eq({}); + return; + } + expect(state.balance).undefined; + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:10")); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should not be able to pay if there is no enough balance", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; + handler.addWalletCallResponse( WalletApiOperation.PreparePayForUri, undefined, @@ -153,38 +138,31 @@ describe("Payment CTA states", () => { }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "no-enough-balance") expect.fail(); - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:5")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "no-enough-balance") expect.fail(); + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:5")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:10")); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be able to pay (without fee)", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; + handler.addWalletCallResponse( WalletApiOperation.PreparePayForUri, undefined, @@ -209,42 +187,36 @@ describe("Payment CTA states", () => { ], }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") { - expect(r).eq({}); - return; - } - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); - expect(r.payHandler.onClick).not.undefined; - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") { + expect(state).eq({}); + return; + } + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:10")); + expect(state.payHandler.onClick).not.undefined; + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); it("should be able to pay (with fee)", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; + handler.addWalletCallResponse( WalletApiOperation.PreparePayForUri, undefined, @@ -269,39 +241,32 @@ describe("Payment CTA states", () => { ], }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - expect(r.payHandler.onClick).not.undefined; - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); + expect(state.payHandler.onClick).not.undefined; + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should get confirmation done after pay successfully", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; + handler.addWalletCallResponse( WalletApiOperation.PreparePayForUri, undefined, @@ -332,35 +297,30 @@ describe("Payment CTA states", () => { contractTerms: {}, } as ConfirmPayResult); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") { - expect(r).eq({}); - return; - } - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - if (r.payHandler.onClick === undefined) expect.fail(); - r.payHandler.onClick(); - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") { + expect(state).eq({}); + return; + } + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); + if (state.payHandler.onClick === undefined) expect.fail(); + state.payHandler.onClick(); + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); it("should not stay in ready state after pay with error", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, @@ -397,62 +357,50 @@ describe("Payment CTA states", () => { lastError: { code: 1 }, } as ConfirmPayResult); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); - if (r.payHandler.onClick === undefined) expect.fail(); - r.payHandler.onClick(); - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); - expect(r.payHandler.onClick).undefined; - if (r.payHandler.error === undefined) expect.fail(); - //FIXME: error message here is bad - expect(r.payHandler.error.errorDetail.hint).eq( - "could not confirm payment", - ); - expect(r.payHandler.error.errorDetail.payResult).deep.equal({ - type: ConfirmPayResultType.Pending, - lastError: { code: 1 }, - }); - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); + // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); + if (state.payHandler.onClick === undefined) expect.fail(); + state.payHandler.onClick(); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); + // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); + expect(state.payHandler.onClick).undefined; + if (state.payHandler.error === undefined) expect.fail(); + //FIXME: error message here is bad + expect(state.payHandler.error.errorDetail.hint).eq( + "could not confirm payment", + ); + expect(state.payHandler.error.errorDetail.payResult).deep.equal({ + type: ConfirmPayResultType.Pending, + lastError: { code: 1 }, + }); + }, + ], TestingContext) + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); it("should update balance if a coins is withdraw", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerPayUri: "taller://pay", cancel: nullFunction, goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, + onSuccess: nullFunction, }; handler.addWalletCallResponse( @@ -507,46 +455,30 @@ describe("Payment CTA states", () => { }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") { - expect(r).eq({}); - return; - } - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); - expect(r.payHandler.onClick).not.undefined; - - handler.notifyEventFromWallet(NotificationType.CoinWithdrawn); - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") { - expect(r).eq({}); - return; - } - expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); - expect(r.payHandler.onClick).not.undefined; - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") expect.fail() + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:10")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); + // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); + expect(state.payHandler.onClick).not.undefined; + + handler.notifyEventFromWallet(NotificationType.CoinWithdrawn); + }, + (state) => { + if (state.status !== "ready") expect.fail() + expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); + expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); + // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); + expect(state.payHandler.onClick).not.undefined; + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts index 4a65c571b..4a6fc79c9 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts @@ -18,7 +18,6 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { 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 } from "./views.js"; @@ -60,6 +59,6 @@ const viewMapping: StateViewMap<State> = { export const RecoveryPage = compose( "Recovery", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts index 3a5d94e2e..018d61c03 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts @@ -16,13 +16,13 @@ import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { wxApi } from "../../wxApi.js"; +import { useBackendContext } from "../../context/backend.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerRecoveryUri, onCancel, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() if (!talerRecoveryUri) { return { status: "loading-uri", @@ -48,7 +48,7 @@ export function useComponentState( const recovery = info; async function recoverBackup(): Promise<void> { - await wxApi.wallet.call(WalletApiOperation.ImportBackupRecovery, { + await api.wallet.call(WalletApiOperation.ImportBackupRecovery, { recovery, }); onSuccess(); diff --git a/packages/taler-wallet-webextension/src/cta/Refund/index.ts b/packages/taler-wallet-webextension/src/cta/Refund/index.ts index 099f72919..158f5c179 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/index.ts @@ -19,13 +19,12 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { IgnoredView, InProgressView, LoadingUriView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { @@ -90,6 +89,6 @@ const viewMapping: StateViewMap<State> = { export const RefundPage = compose( "Refund", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/Refund/state.ts b/packages/taler-wallet-webextension/src/cta/Refund/state.ts index 94c5567d6..624ab2fb2 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/state.ts @@ -17,14 +17,14 @@ import { Amounts, NotificationType } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerRefundUri, cancel, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const [ignored, setIgnored] = useState(false); const info = useAsyncAsHook(async () => { diff --git a/packages/taler-wallet-webextension/src/cta/Refund/test.ts b/packages/taler-wallet-webextension/src/cta/Refund/test.ts index 927c45981..5fbf3743e 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/test.ts @@ -20,75 +20,50 @@ */ import { - AmountJson, Amounts, NotificationType, - OrderShortInfo, - PrepareRefundResult, + OrderShortInfo } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; -import { mountHook } from "../../test-utils.js"; -import { createWalletApiMock } from "../../test-utils.js"; +import { tests } from "../../../../web-util/src/index.browser.js"; +import { createWalletApiMock, mountHook, nullFunction } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Refund CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { - talerRefundUri: undefined, - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - mock, - // { - // prepareRefund: async () => ({}), - // applyRefund: async () => ({}), - // onUpdateNotification: async () => ({}), - // } as any, - ), - ); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; + const props = { + talerRefundUri: undefined, + cancel: nullFunction, + onSuccess: nullFunction, } - expect(await waitForStateUpdate()).true; - - { - const { status, error } = pullLastResultOrThrow(); - - expect(status).equals("loading-uri"); - if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-REFUND"); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + ({ status, error }) => { + expect(status).equals("loading-uri"); + if (!error) expect.fail(); + if (!error.hasError) expect.fail(); + if (error.operational) expect.fail(); + expect(error.message).eq("ERROR_NO-URI-FOR-REFUND"); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be ready after loading", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, + cancel: nullFunction, + onSuccess: nullFunction, }; handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { @@ -108,61 +83,28 @@ describe("Refund CTA states", () => { } as OrderShortInfo, }); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - props, - mock, - // { - // prepareRefund: async () => - // ({ - // effectivePaid: "EUR:2", - // awaiting: "EUR:2", - // gone: "EUR:0", - // granted: "EUR:0", - // pending: false, - // proposalId: "1", - // info: { - // contractTermsHash: "123", - // merchant: { - // name: "the merchant name", - // }, - // orderId: "orderId1", - // summary: "the summary", - // }, - // } as PrepareRefundResult as any), - // applyRefund: async () => ({}), - // onUpdateNotification: async () => ({}), - // } as any, - ), - ); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "ready") expect.fail(); - if (state.error) expect.fail(); - expect(state.accept.onClick).not.undefined; - expect(state.ignore.onClick).not.undefined; - expect(state.merchantName).eq("the merchant name"); - expect(state.orderId).eq("orderId1"); - expect(state.products).undefined; - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") expect.fail(); + if (state.error) expect.fail(); + expect(state.accept.onClick).not.undefined; + expect(state.ignore.onClick).not.undefined; + expect(state.merchantName).eq("the merchant name"); + expect(state.orderId).eq("orderId1"); + expect(state.products).undefined; + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be ignored after clicking the ignore button", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerRefundUri: "taler://refund/asdasdas", cancel: async () => { @@ -189,102 +131,36 @@ describe("Refund CTA states", () => { summary: "the summary", } as OrderShortInfo, }); - // handler.addWalletCall(WalletApiOperation.ApplyRefund) - // handler.addWalletCall(WalletApiOperation.PrepareRefund, undefined, { - // awaiting: "EUR:1", - // effectivePaid: "EUR:2", - // gone: "EUR:0", - // granted: "EUR:1", - // pending: true, - // proposalId: "1", - // info: { - // contractTermsHash: "123", - // merchant: { - // name: "the merchant name", - // }, - // orderId: "orderId1", - // summary: "the summary", - // } as OrderShortInfo, - // }) - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - props, - mock, - // { - // prepareRefund: async () => - // ({ - // effectivePaid: "EUR:2", - // awaiting: "EUR:2", - // gone: "EUR:0", - // granted: "EUR:0", - // pending: false, - // proposalId: "1", - // info: { - // contractTermsHash: "123", - // merchant: { - // name: "the merchant name", - // }, - // orderId: "orderId1", - // summary: "the summary", - // }, - // } as PrepareRefundResult as any), - // applyRefund: async () => ({}), - // onUpdateNotification: async () => ({}), - // } as any, - ), - ); - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "ready") { - expect(state).eq({}); - return; - } - if (state.error) { - expect(state).eq({}); - return; - } - expect(state.accept.onClick).not.undefined; - expect(state.merchantName).eq("the merchant name"); - expect(state.orderId).eq("orderId1"); - expect(state.products).undefined; - - if (state.ignore.onClick === undefined) expect.fail(); - state.ignore.onClick(); - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "ignored") { - expect(state).eq({}); - return; - } - if (state.error) { - expect(state).eq({}); - return; - } - expect(state.merchantName).eq("the merchant name"); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") expect.fail() + if (state.error) expect.fail() + expect(state.accept.onClick).not.undefined; + expect(state.merchantName).eq("the merchant name"); + expect(state.orderId).eq("orderId1"); + expect(state.products).undefined; + + if (state.ignore.onClick === undefined) expect.fail(); + state.ignore.onClick(); + }, + (state) => { + if (state.status !== "ignored") expect.fail() + if (state.error) expect.fail() + expect(state.merchantName).eq("the merchant name"); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be in progress when doing refresh", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerRefundUri: "taler://refund/asdasdas", cancel: async () => { @@ -344,67 +220,42 @@ describe("Refund CTA states", () => { } as OrderShortInfo, }); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "in-progress") { - expect(state).eq({}); - return; - } - if (state.error) expect.fail(); - expect(state.merchantName).eq("the merchant name"); - expect(state.products).undefined; - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); - // expect(state.progress).closeTo(1 / 3, 0.01) - - handler.notifyEventFromWallet(NotificationType.RefreshMelted); - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "in-progress") { - expect(state).eq({}); - return; - } - if (state.error) expect.fail(); - expect(state.merchantName).eq("the merchant name"); - expect(state.products).undefined; - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); - // expect(state.progress).closeTo(2 / 3, 0.01) - - handler.notifyEventFromWallet(NotificationType.RefreshMelted); - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "in-progress") expect.fail() + if (state.error) expect.fail(); + expect(state.merchantName).eq("the merchant name"); + expect(state.products).undefined; + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); + // expect(state.progress).closeTo(1 / 3, 0.01) + + handler.notifyEventFromWallet(NotificationType.RefreshMelted); + }, + (state) => { + if (state.status !== "in-progress") expect.fail() + if (state.error) expect.fail(); + expect(state.merchantName).eq("the merchant name"); + expect(state.products).undefined; + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); + // expect(state.progress).closeTo(2 / 3, 0.01) + + handler.notifyEventFromWallet(NotificationType.RefreshMelted); + }, + (state) => { + if (state.status !== "ready") expect.fail() + if (state.error) expect.fail(); + expect(state.merchantName).eq("the merchant name"); + expect(state.products).undefined; + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); - if (state.status !== "ready") { - expect(state).eq({}); - return; - } - if (state.error) expect.fail(); - expect(state.merchantName).eq("the merchant name"); - expect(state.products).undefined; - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); - } + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Tip/index.ts b/packages/taler-wallet-webextension/src/cta/Tip/index.ts index ff917008f..a29a3eadb 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/index.ts @@ -19,13 +19,12 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { AcceptedView, IgnoredView, LoadingUriView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { @@ -84,6 +83,6 @@ const viewMapping: StateViewMap<State> = { export const TipPage = compose( "Tip", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/Tip/state.ts b/packages/taler-wallet-webextension/src/cta/Tip/state.ts index ea9ba1b37..0ca213b01 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/state.ts @@ -16,14 +16,14 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerTipUri, onCancel, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const tipInfo = useAsyncAsHook(async () => { if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { diff --git a/packages/taler-wallet-webextension/src/cta/Tip/test.ts b/packages/taler-wallet-webextension/src/cta/Tip/test.ts index e57b9ec4d..21ed95218 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/test.ts @@ -22,54 +22,42 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; -import { mountHook } from "../../test-utils.js"; +import { tests } from "../../../../web-util/src/index.browser.js"; +import { mountHook, nullFunction } from "../../test-utils.js"; import { createWalletApiMock } from "../../test-utils.js"; +import { Props } from "./index.js"; import { useComponentState } from "./state.js"; describe("Tip CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { - talerTipUri: undefined, - onCancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - mock, - ), - ); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; + const props: Props = { + talerTipUri: undefined, + onCancel: nullFunction, + onSuccess: nullFunction, } - expect(await waitForStateUpdate()).true; - - { - const { status, error } = pullLastResultOrThrow(); - - expect(status).equals("loading-uri"); - if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-TIP"); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + ({ status, error }) => { + expect(status).equals("loading-uri"); + if (!error) expect.fail(); + if (!error.hasError) expect.fail(); + if (error.operational) expect.fail(); + expect(error.message).eq("ERROR_NO-URI-FOR-TIP"); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be ready for accepting the tip", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { accepted: false, @@ -83,78 +71,59 @@ describe("Tip CTA states", () => { tipAmountRaw: "", }); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { - talerTipUri: "taler://tip/asd", - onCancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - mock, - ), - ); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; + const props: Props = { + talerTipUri: "taler://tip/asd", + onCancel: nullFunction, + onSuccess: nullFunction, } - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "ready") { - expect(state).eq({ status: "ready" }); - return; - } - if (state.error) expect.fail(); - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); - expect(state.merchantBaseUrl).eq("merchant url"); - expect(state.exchangeBaseUrl).eq("exchange url"); - if (state.accept.onClick === undefined) expect.fail(); - - handler.addWalletCallResponse(WalletApiOperation.AcceptTip); - state.accept.onClick(); - } - - handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { - accepted: true, - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - expirationTimestamp: { - t_s: 1, + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; }, - tipAmountRaw: "", - }); - expect(await waitForStateUpdate()).true; + (state) => { + if (state.status !== "ready") { + expect(state).eq({ status: "ready" }); + return; + } + if (state.error) expect.fail(); + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); + expect(state.merchantBaseUrl).eq("merchant url"); + expect(state.exchangeBaseUrl).eq("exchange url"); + if (state.accept.onClick === undefined) expect.fail(); + + handler.addWalletCallResponse(WalletApiOperation.AcceptTip); + state.accept.onClick(); + + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: true, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1, + }, + tipAmountRaw: "", + }); - { - const state = pullLastResultOrThrow(); + }, + (state) => { + if (state.status !== "accepted") expect.fail() + if (state.error) expect.fail(); + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); + expect(state.merchantBaseUrl).eq("merchant url"); + expect(state.exchangeBaseUrl).eq("exchange url"); + }, + ], TestingContext) - if (state.status !== "accepted") { - expect(state).eq({ status: "accepted" }); - return; - } - if (state.error) expect.fail(); - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); - expect(state.merchantBaseUrl).eq("merchant url"); - expect(state.exchangeBaseUrl).eq("exchange url"); - } - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); - it("should be ignored after clicking the ignore button", async () => { - const { handler, mock } = createWalletApiMock(); + it.skip("should be ignored after clicking the ignore button", async () => { + const { handler, TestingContext } = createWalletApiMock(); handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { exchangeBaseUrl: "exchange url", merchantBaseUrl: "merchant url", @@ -167,46 +136,34 @@ describe("Tip CTA states", () => { tipAmountRaw: "", }); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { - talerTipUri: "taler://tip/asd", - onCancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - mock, - ), - ); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; + const props: Props = { + talerTipUri: "taler://tip/asd", + onCancel: nullFunction, + onSuccess: nullFunction, } - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - - if (state.status !== "ready") expect.fail(); - if (state.error) expect.fail(); - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); - expect(state.merchantBaseUrl).eq("merchant url"); - expect(state.exchangeBaseUrl).eq("exchange url"); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "ready") expect.fail(); + if (state.error) expect.fail(); + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); + expect(state.merchantBaseUrl).eq("merchant url"); + expect(state.exchangeBaseUrl).eq("exchange url"); + + //FIXME: add ignore button + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should render accepted if the tip has been used previously", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { accepted: true, @@ -220,40 +177,28 @@ describe("Tip CTA states", () => { tipAmountRaw: "", }); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => - useComponentState( - { - talerTipUri: "taler://tip/asd", - onCancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - mock, - ), - ); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; + const props: Props = { + talerTipUri: "taler://tip/asd", + onCancel: nullFunction, + onSuccess: nullFunction, } - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + if (state.status !== "accepted") expect.fail(); + if (state.error) expect.fail(); + expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); + expect(state.merchantBaseUrl).eq("merchant url"); + expect(state.exchangeBaseUrl).eq("exchange url"); + }, + ], TestingContext) - if (state.status !== "accepted") expect.fail(); - if (state.error) expect.fail(); - expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); - expect(state.merchantBaseUrl).eq("merchant url"); - expect(state.exchangeBaseUrl).eq("exchange url"); - } - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); }); diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts index 8d51ff3e0..0715bb60e 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts @@ -19,7 +19,6 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; @@ -66,6 +65,6 @@ const viewMapping: StateViewMap<State> = { export const TransferCreatePage = compose( "TransferCreatePage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts index c5e143f42..3536014da 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -17,19 +17,19 @@ import { Amounts, TalerErrorDetail, - TalerProtocolTimestamp, + TalerProtocolTimestamp } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { format, isFuture, parse } from "date-fns"; +import { isFuture, parse } from "date-fns"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { amount: amountStr, onClose, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const amount = Amounts.parseOrThrow(amountStr); const [subject, setSubject] = useState<string | undefined>(); @@ -124,8 +124,8 @@ export function useComponentState( subject === undefined ? undefined : !subject - ? "Can't be empty" - : undefined, + ? "Can't be empty" + : undefined, value: subject ?? "", onInput: async (e) => setSubject(e), }, diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts index 399f1e290..de6ad3b79 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts @@ -17,13 +17,12 @@ import { AbsoluteTime, AmountJson, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { 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 } from "./views.js"; @@ -69,6 +68,6 @@ const viewMapping: StateViewMap<State> = { export const TransferPickupPage = compose( "TransferPickupPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts index e8fb99ab7..45bec28fb 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts @@ -18,18 +18,18 @@ import { AbsoluteTime, Amounts, TalerErrorDetail, - TalerProtocolTimestamp, + TalerProtocolTimestamp } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerPayPushUri, onClose, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const hook = useAsyncAsHook(async () => { return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, { talerUri: talerPayPushUri, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index 68b314c07..9e5943161 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -20,10 +20,9 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentStateFromParams, - useComponentStateFromURI, + useComponentStateFromURI } from "./state.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; @@ -96,11 +95,11 @@ const viewMapping: StateViewMap<State> = { export const WithdrawPageFromURI = compose( "WithdrawPageFromURI", - (p: PropsFromURI) => useComponentStateFromURI(p, wxApi), + (p: PropsFromURI) => useComponentStateFromURI(p), viewMapping, ); export const WithdrawPageFromParams = compose( "WithdrawPageFromParams", - (p: PropsFromParams) => useComponentStateFromParams(p, wxApi), + (p: PropsFromParams) => useComponentStateFromParams(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index 9bb29fbd6..4420221fc 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -19,20 +19,20 @@ import { AmountJson, Amounts, ExchangeListItem, - ExchangeTosStatus, + ExchangeTosStatus } from "@gnu-taler/taler-util"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; 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"; export function useComponentStateFromParams( { amount, cancel, onSuccess }: PropsFromParams, - api: typeof wxApi, ): RecursiveState<State> { + const api = useBackendContext() const uriInfoHook = useAsyncAsHook(async () => { const exchanges = await api.wallet.call( WalletApiOperation.ListExchanges, @@ -84,14 +84,13 @@ export function useComponentStateFromParams( chosenAmount, exchangeList, undefined, - api, ); } export function useComponentStateFromURI( { talerWithdrawUri, cancel, onSuccess }: PropsFromURI, - api: typeof wxApi, ): RecursiveState<State> { + const api = useBackendContext() /** * Ask the wallet about the withdraw URI */ @@ -158,7 +157,6 @@ export function useComponentStateFromURI( chosenAmount, exchangeList, defaultExchange, - api, ); } @@ -176,8 +174,8 @@ function exchangeSelectionState( chosenAmount: AmountJson, exchangeList: ExchangeListItem[], defaultExchange: string | undefined, - api: typeof wxApi, ): RecursiveState<State> { + const api = useBackendContext() const selectedExchange = useSelectedExchange({ currency: chosenAmount.currency, defaultExchange, @@ -278,10 +276,10 @@ function exchangeSelectionState( //TODO: calculate based on exchange info const ageRestriction = ageRestrictionEnabled ? { - list: ageRestrictionOptions, - value: String(ageRestricted), - onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), - } + list: ageRestrictionOptions, + value: String(ageRestricted), + onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), + } : undefined; return { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index 7fd8188ce..084b4368c 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -27,6 +27,7 @@ import { } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; +import { tests } from "../../../../web-util/src/index.browser.js"; import { mountHook } from "../../test-utils.js"; import { createWalletApiMock } from "../../test-utils.js"; import { useComponentStateFromURI } from "./state.js"; @@ -61,53 +62,45 @@ const exchanges: ExchangeListItem[] = [ } as Partial<ExchangeListItem> as ExchangeListItem, ]; +const nullFunction = async (): Promise<void> => { + null; +} + describe("Withdraw CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); + const props = { talerWithdrawUri: undefined, - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, + cancel: nullFunction, + onSuccess: nullFunction, }; - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentStateFromURI(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equals("loading"); - } - - expect(await waitForStateUpdate()).true; - { - const { status, error } = pullLastResultOrThrow(); - - if (status != "uri-error") expect.fail(); - if (!error) expect.fail(); - if (!error.hasError) expect.fail(); - if (error.operational) expect.fail(); - expect(error.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL"); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentStateFromURI, props, [ + ({ status }) => { + expect(status).equals("loading"); + }, + ({ status, error }) => { + if (status != "uri-error") expect.fail(); + if (!error) expect.fail(); + if (!error.hasError) expect.fail(); + if (error.operational) expect.fail(); + expect(error.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL"); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should tell the user that there is not known exchange", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, + cancel: nullFunction, + onSuccess: nullFunction, }; + handler.addWalletCallResponse( WalletApiOperation.GetWithdrawalDetailsForUri, undefined, @@ -117,39 +110,28 @@ describe("Withdraw CTA states", () => { }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentStateFromURI(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equals("loading", "1"); - } - - expect(await waitForStateUpdate()).true; - - { - const { status, error } = pullLastResultOrThrow(); - - expect(status).equals("no-exchange", "3"); - - expect(error).undefined; - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentStateFromURI, props, [ + ({ status }) => { + expect(status).equals("loading"); + }, + ({ status, error }) => { + expect(status).equals("no-exchange"); + expect(error).undefined; + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should be able to withdraw if tos are ok", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, + cancel: nullFunction, + onSuccess: nullFunction, }; + handler.addWalletCallResponse( WalletApiOperation.GetWithdrawalDetailsForUri, undefined, @@ -171,54 +153,38 @@ describe("Withdraw CTA states", () => { }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentStateFromURI(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const { status, error } = pullLastResultOrThrow(); - - expect(status).equals("loading"); - - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - expect(state.status).equals("success"); - if (state.status !== "success") return; + const hookBehavior = await tests.hookBehaveLikeThis(useComponentStateFromURI, props, [ + ({ status }) => { + expect(status).equals("loading"); + }, + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + expect(state.status).equals("success"); + if (state.status !== "success") return; - expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); - expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2")); + expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); + expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); + expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.doWithdrawal.onClick).not.undefined; - } + expect(state.doWithdrawal.onClick).not.undefined; + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should accept the tos before withdraw", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, + cancel: nullFunction, + onSuccess: nullFunction, }; + const exchangeWithNewTos = exchanges.map((e) => ({ ...e, tosStatus: ExchangeTosStatus.New, @@ -255,57 +221,39 @@ describe("Withdraw CTA states", () => { }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentStateFromURI(props, mock)); - - { - const { status, error } = pullLastResultOrThrow(); - expect(status).equals("loading"); - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const { status, error } = pullLastResultOrThrow(); - - expect(status).equals("loading"); - - expect(error).undefined; - } - - expect(await waitForStateUpdate()).true; - - { - const state = pullLastResultOrThrow(); - expect(state.status).equals("success"); - if (state.status !== "success") return; - - expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); - expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2")); - - expect(state.doWithdrawal.onClick).undefined; + const hookBehavior = await tests.hookBehaveLikeThis(useComponentStateFromURI, props, [ + ({ status }) => { + expect(status).equals("loading"); + }, + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + (state) => { + expect(state.status).equals("success"); + if (state.status !== "success") return; - // updateAcceptedVersionToCurrentVersion(); - state.onTosUpdate(); - } + expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); + expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); + expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(await waitForStateUpdate()).true; + expect(state.doWithdrawal.onClick).undefined; - { - const state = pullLastResultOrThrow(); - expect(state.status).equals("success"); - if (state.status !== "success") return; + state.onTosUpdate(); + }, + (state) => { + expect(state.status).equals("success"); + if (state.status !== "success") return; - expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); - expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2")); + expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); + expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); + expect(state.chosenAmount).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.doWithdrawal.onClick).not.undefined; - } + expect(state.doWithdrawal.onClick).not.undefined; + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); }); diff --git a/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts index 3361394a4..1a48cdca7 100644 --- a/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts +++ b/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts @@ -16,22 +16,23 @@ import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { useBackendContext } from "../context/backend.js"; import { ToggleHandler } from "../mui/handlers.js"; import { platform } from "../platform/api.js"; -import { wxApi } from "../wxApi.js"; export function useAutoOpenPermissions(): ToggleHandler { + const api = useBackendContext(); const [enabled, setEnabled] = useState(false); const [error, setError] = useState<TalerError | undefined>(); const toggle = async (): Promise<void> => { - return handleAutoOpenPerm(enabled, setEnabled).catch((e) => { + return handleAutoOpenPerm(enabled, setEnabled, api.background).catch((e) => { setError(TalerError.fromException(e)); }); }; useEffect(() => { async function getValue(): Promise<void> { - const res = await wxApi.background.containsHeaderListener(); + const res = await api.background.containsHeaderListener(); setEnabled(res.newValue); } getValue(); @@ -48,6 +49,7 @@ export function useAutoOpenPermissions(): ToggleHandler { async function handleAutoOpenPerm( isEnabled: boolean, onChange: (value: boolean) => void, + background: ReturnType<typeof useBackendContext>["background"], ): Promise<void> { if (!isEnabled) { // We set permissions here, since apparently FF wants this to be done @@ -59,11 +61,11 @@ async function handleAutoOpenPerm( onChange(false); throw lastError; } - const res = await wxApi.background.toggleHeaderListener(granted); + const res = await background.toggleHeaderListener(granted); onChange(res.newValue); } else { try { - await wxApi.background + await background .toggleHeaderListener(false) .then((r) => onChange(r.newValue)); } catch (e) { diff --git a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts index be81b7d7d..585a643a9 100644 --- a/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts +++ b/packages/taler-wallet-webextension/src/hooks/useBackupDeviceName.ts @@ -16,7 +16,7 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; -import { wxApi } from "../wxApi.js"; +import { useBackendContext } from "../context/backend.js"; export interface BackupDeviceName { name: string; @@ -28,17 +28,18 @@ export function useBackupDeviceName(): BackupDeviceName { name: "", update: () => Promise.resolve(), }); + const api = useBackendContext() useEffect(() => { async function run(): Promise<void> { //create a first list of backup info by currency - const status = await wxApi.wallet.call( + const status = await api.wallet.call( WalletApiOperation.GetBackupInfo, {}, ); async function update(newName: string): Promise<void> { - await wxApi.wallet.call(WalletApiOperation.SetWalletDeviceId, { + await api.wallet.call(WalletApiOperation.SetWalletDeviceId, { walletDeviceId: newName, }); setStatus((old) => ({ ...old, name: newName })); diff --git a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts index 9b7d46ba7..649def599 100644 --- a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts +++ b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts @@ -16,22 +16,24 @@ import { TalerError } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { useBackendContext } from "../context/backend.js"; import { ToggleHandler } from "../mui/handlers.js"; import { platform } from "../platform/api.js"; -import { wxApi } from "../wxApi.js"; export function useClipboardPermissions(): ToggleHandler { const [enabled, setEnabled] = useState(false); const [error, setError] = useState<TalerError | undefined>(); + const api = useBackendContext() + const toggle = async (): Promise<void> => { - return handleClipboardPerm(enabled, setEnabled).catch((e) => { + return handleClipboardPerm(enabled, setEnabled, api.background).catch((e) => { setError(TalerError.fromException(e)); }); }; useEffect(() => { async function getValue(): Promise<void> { - const res = await wxApi.background.containsHeaderListener(); + const res = await api.background.containsHeaderListener(); setEnabled(res.newValue); } getValue(); @@ -49,6 +51,7 @@ export function useClipboardPermissions(): ToggleHandler { async function handleClipboardPerm( isEnabled: boolean, onChange: (value: boolean) => void, + background: ReturnType<typeof useBackendContext>["background"], ): Promise<void> { if (!isEnabled) { // We set permissions here, since apparently FF wants this to be done @@ -62,11 +65,10 @@ async function handleClipboardPerm( onChange(false); throw lastError; } - // const res = await wxApi.toggleHeaderListener(granted); onChange(granted); } else { try { - await wxApi.background + await background .toggleHeaderListener(false) .then((r) => onChange(r.newValue)); } catch (e) { diff --git a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts index a8564fddb..1f36ca6c0 100644 --- a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts +++ b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts @@ -16,10 +16,11 @@ import { WalletDiagnostics } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; -import { wxApi } from "../wxApi.js"; +import { useBackendContext } from "../context/backend.js"; export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { const [timedOut, setTimedOut] = useState(false); + const api = useBackendContext(); const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>( undefined, ); @@ -33,7 +34,7 @@ export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] { } }, 1000); const doFetch = async (): Promise<void> => { - const d = await wxApi.background.getDiagnostics(); + const d = await api.background.getDiagnostics(); gotDiagnostics = true; setDiagnostics(d); }; diff --git a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts index e6473d1ec..ca2054931 100644 --- a/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts +++ b/packages/taler-wallet-webextension/src/hooks/useProviderStatus.ts @@ -16,7 +16,7 @@ import { ProviderInfo, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; -import { wxApi } from "../wxApi.js"; +import { useBackendContext } from "../context/backend.js"; export interface ProviderStatus { info?: ProviderInfo; @@ -26,11 +26,11 @@ export interface ProviderStatus { export function useProviderStatus(url: string): ProviderStatus | undefined { const [status, setStatus] = useState<ProviderStatus | undefined>(undefined); - + const api = useBackendContext(); useEffect(() => { async function run(): Promise<void> { //create a first list of backup info by currency - const status = await wxApi.wallet.call( + const status = await api.wallet.call( WalletApiOperation.GetBackupInfo, {}, ); @@ -42,7 +42,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined { async function sync(): Promise<void> { if (info) { - await wxApi.wallet.call(WalletApiOperation.RunBackupCycle, { + await api.wallet.call(WalletApiOperation.RunBackupCycle, { providers: [info.syncProviderBaseUrl], }); } @@ -50,7 +50,7 @@ export function useProviderStatus(url: string): ProviderStatus | undefined { async function remove(): Promise<void> { if (info) { - await wxApi.wallet.call(WalletApiOperation.RemoveBackupProvider, { + await api.wallet.call(WalletApiOperation.RemoveBackupProvider, { provider: info.syncProviderBaseUrl, }); } diff --git a/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts b/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts index d5743eb2e..6ae55da61 100644 --- a/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts +++ b/packages/taler-wallet-webextension/src/hooks/useWalletDevMode.ts @@ -15,22 +15,24 @@ */ import { useState, useEffect } from "preact/hooks"; -import { wxApi } from "../wxApi.js"; import { ToggleHandler } from "../mui/handlers.js"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useBackendContext } from "../context/backend.js"; export function useWalletDevMode(): ToggleHandler { const [enabled, setEnabled] = useState<undefined | boolean>(undefined); const [error, setError] = useState<TalerError | undefined>(); + const api = useBackendContext(); + const toggle = async (): Promise<void> => { - return handleOpen(enabled, setEnabled).catch((e) => { + return handleOpen(enabled, setEnabled, api).catch((e) => { setError(TalerError.fromException(e)); }); }; useEffect(() => { async function getValue(): Promise<void> { - const res = await wxApi.wallet.call(WalletApiOperation.GetVersion, {}); + const res = await api.wallet.call(WalletApiOperation.GetVersion, {}); setEnabled(res.devMode); } getValue(); @@ -47,9 +49,10 @@ export function useWalletDevMode(): ToggleHandler { async function handleOpen( currentValue: undefined | boolean, onChange: (value: boolean) => void, + api: ReturnType<typeof useBackendContext>, ): Promise<void> { const nextValue = !currentValue; - await wxApi.wallet.call(WalletApiOperation.SetDevMode, { + await api.wallet.call(WalletApiOperation.SetDevMode, { devModeEnabled: nextValue, }); onChange(nextValue); diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx index 98c6d166c..8786b2ff7 100644 --- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx +++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -22,13 +22,13 @@ import { BalanceTable } from "../components/BalanceTable.js"; import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; import { MultiActionButton } from "../components/MultiActionButton.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { ButtonHandler } from "../mui/handlers.js"; import { compose, StateViewMap } from "../utils/index.js"; import { AddNewActionView } from "../wallet/AddNewActionView.js"; -import { wxApi } from "../wxApi.js"; import { NoBalanceHelp } from "./NoBalanceHelp.js"; export interface Props { @@ -67,10 +67,12 @@ export namespace State { } } -function useComponentState( - { goToWalletDeposit, goToWalletHistory, goToWalletManualWithdraw }: Props, - api: typeof wxApi, -): State { +function useComponentState({ + goToWalletDeposit, + goToWalletHistory, + goToWalletManualWithdraw, +}: Props): State { + const api = useBackendContext(); const [addingAction, setAddingAction] = useState(false); const state = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.GetBalances, {}), @@ -128,7 +130,7 @@ const viewMapping: StateViewMap<State> = { export const BalancePage = compose( "BalancePage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/test-utils.ts b/packages/taler-wallet-webextension/src/test-utils.ts index b4983b4c2..791773c42 100644 --- a/packages/taler-wallet-webextension/src/test-utils.ts +++ b/packages/taler-wallet-webextension/src/test-utils.ts @@ -31,6 +31,7 @@ import { VNode, } from "preact"; import { render as renderToString } from "preact-render-to-string"; +import { BackendProvider } from "./context/backend.js"; import { BackgroundApiClient, wxApi } from "./wxApi.js"; // When doing tests we want the requestAnimationFrame to be as fast as possible. @@ -252,7 +253,7 @@ type Subscriptions = { export function createWalletApiMock(): { handler: MockHandler; - mock: typeof wxApi; + TestingContext: FunctionalComponent<{ children: ComponentChildren }> } { const calls = new Array<CallRecord>(); const subscriptions: Subscriptions = {}; @@ -357,5 +358,14 @@ export function createWalletApiMock(): { }, }; - return { handler, mock }; + function TestingContext({ children }: { children: ComponentChildren }): VNode { + return create(BackendProvider, { + wallet: mock.wallet, + background: mock.background, + listener: mock.listener, + children, + }, children) + } + + return { handler, TestingContext }; } diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts index 3205588af..2adcc9f74 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts @@ -15,9 +15,7 @@ */ import { - AmountJson, - BackupBackupProviderTerms, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core"; import { Loading } from "../../components/Loading.js"; @@ -25,15 +23,13 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, TextFieldHandler, - ToggleHandler, + ToggleHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { - LoadingUriView, - SelectProviderView, - ConfirmProviderView, + ConfirmProviderView, LoadingUriView, + SelectProviderView } from "./views.js"; export interface Props { @@ -90,6 +86,6 @@ const viewMapping: StateViewMap<State> = { export const AddBackupProviderPage = compose( "AddBackupProvider", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts index 504ee4678..271a1bf98 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts @@ -17,15 +17,15 @@ import { canonicalizeBaseUrl, Codec, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { codecForSyncTermsOfServiceResponse, - WalletApiOperation, + WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { assertUnreachable } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; type UrlState<T> = UrlOk<T> | UrlError; @@ -106,37 +106,37 @@ function useUrlState<T>( constHref == undefined ? undefined : async () => { - const req = await fetch(constHref).catch((e) => { - return setState({ - status: "network-error", - href: constHref, - }); + const req = await fetch(constHref).catch((e) => { + return setState({ + status: "network-error", + href: constHref, }); - if (!req) return; + }); + if (!req) return; - if (req.status >= 400 && req.status < 500) { - setState({ - status: "client-error", - code: req.status, - }); - return; - } - if (req.status > 500) { - setState({ - status: "server-error", - code: req.status, - }); - return; - } + if (req.status >= 400 && req.status < 500) { + setState({ + status: "client-error", + code: req.status, + }); + return; + } + if (req.status > 500) { + setState({ + status: "server-error", + code: req.status, + }); + return; + } - const json = await req.json(); - try { - const result = codec.decode(json); - setState({ status: "ok", result }); - } catch (e: any) { - setState({ status: "parsing-error", json }); - } - }, + const json = await req.json(); + try { + const result = codec.decode(json); + setState({ status: "ok", result }); + } catch (e: any) { + setState({ status: "parsing-error", json }); + } + }, [host, path], ); @@ -145,8 +145,8 @@ function useUrlState<T>( export function useComponentState( { currency, onBack, onComplete, onPaymentRequired }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const [url, setHost] = useState<string | undefined>(); const [name, setName] = useState<string | undefined>(); const [tos, setTos] = useState(false); @@ -223,8 +223,8 @@ export function useComponentState( !urlState || urlState.status !== "ok" || !name ? undefined : async () => { - setShowConfirm(true); - }, + setShowConfirm(true); + }, }, urlOk: urlState?.status === "ok", url: { diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts index 1143853f8..929e051cb 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts @@ -19,12 +19,10 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; +import { tests } from "../../../../web-util/src/index.browser.js"; import { - createWalletApiMock, - mountHook, - nullFunction, + createWalletApiMock, nullFunction } from "../../test-utils.js"; import { Props } from "./index.js"; import { useComponentState } from "./state.js"; @@ -36,44 +34,21 @@ const props: Props = { onPaymentRequired: nullFunction, }; describe("AddBackupProvider states", () => { - it("should start in 'select-provider' state", async () => { - const { handler, mock } = createWalletApiMock(); - - // handler.addWalletCallResponse( - // WalletApiOperation.ListKnownBankAccounts, - // undefined, - // { - // accounts: [], - // }, - // ); - - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const state = pullLastResultOrThrow(); - expect(state.status).equal("select-provider"); - if (state.status !== "select-provider") return; - expect(state.name.value).eq(""); - expect(state.url.value).eq(""); - } - //FIXME: this should not make an extra update - /** - * this may be due to useUrlState because is using an effect over - * a dependency with a timeout - */ - // NOTE: do not remove this comment, keeping as an example - // await waitForStateUpdate() - // { - // const state = pullLastResultOrThrow(); - // expect(state.status).equal("select-provider"); - // if (state.status !== "select-provider") return; - // expect(state.name.value).eq("") - // expect(state.url.value).eq("") - // } - - await assertNoPendingUpdate(); + it("should start in 'select-provider' state", async () => { + const { handler, TestingContext } = createWalletApiMock(); + + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + (state) => { + expect(state.status).equal("select-provider"); + if (state.status !== "select-provider") return; + expect(state.name.value).eq(""); + expect(state.url.value).eq(""); + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); }); diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx index c9dbfb64d..6e987f965 100644 --- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -42,11 +42,11 @@ import { SmallText, WarningBox, } from "../components/styled/index.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { Pages } from "../NavigationBar.js"; -import { wxApi } from "../wxApi.js"; interface Props { onAddProvider: () => Promise<void>; @@ -107,8 +107,9 @@ export function ShowRecoveryInfo({ export function BackupPage({ onAddProvider }: Props): VNode { const { i18n } = useTranslationContext(); + const api = useBackendContext(); const status = useAsyncAsHook(() => - wxApi.wallet.call(WalletApiOperation.GetBackupInfo, {}), + api.wallet.call(WalletApiOperation.GetBackupInfo, {}), ); const [recoveryInfo, setRecoveryInfo] = useState<string>(""); if (!status) { @@ -124,7 +125,7 @@ export function BackupPage({ onAddProvider }: Props): VNode { } async function getRecoveryInfo(): Promise<void> { - const r = await wxApi.wallet.call( + const r = await api.wallet.call( WalletApiOperation.ExportBackupRecovery, {}, ); @@ -158,7 +159,7 @@ export function BackupPage({ onAddProvider }: Props): VNode { providers={providers} onAddProvider={onAddProvider} onSyncAll={async () => - wxApi.wallet.call(WalletApiOperation.RunBackupCycle, {}).then() + api.wallet.call(WalletApiOperation.RunBackupCycle, {}).then() } onShowInfo={getRecoveryInfo} /> diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts index 3f23515b2..ad4c759bf 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts @@ -20,11 +20,9 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { AmountFieldHandler, ButtonHandler, - SelectFieldHandler, - TextFieldHandler, + SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { ManageAccountPage } from "../ManageAccount/index.js"; import { useComponentState } from "./state.js"; import { @@ -32,7 +30,7 @@ import { LoadingErrorView, NoAccountToDepositView, NoEnoughBalanceView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { @@ -119,6 +117,6 @@ const viewMapping: StateViewMap<State> = { export const DepositPage = compose( "DepositPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts index bbf2c2771..5ad0841dc 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -21,18 +21,18 @@ import { KnownBankAccountsInfo, parsePaytoUri, PaytoUri, - stringifyPaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { amount: amountStr, currency: currencyStr, onCancel, onSuccess }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr); const currency = parsed !== undefined ? parsed.currency : currencyStr; @@ -55,8 +55,8 @@ export function useComponentState( parsed !== undefined ? parsed : currency !== undefined - ? Amounts.zeroOfCurrency(currency) - : undefined; + ? Amounts.zeroOfCurrency(currency) + : undefined; // const [accountIdx, setAccountIdx] = useState<number>(0); const [amount, setAmount] = useState<AmountJson>(initialValue ?? ({} as any)); const [selectedAccount, setSelectedAccount] = useState<PaytoUri>(); @@ -134,7 +134,7 @@ export function useComponentState( const currentAccount = !selectedAccount ? firstAccount : selectedAccount; if (fee === undefined) { - getFeeForAmount(currentAccount, amount, api).then((initialFee) => { + getFeeForAmount(currentAccount, amount, api.wallet).then((initialFee) => { setFee(initialFee); }); return { @@ -149,7 +149,7 @@ export function useComponentState( const uri = !accountStr ? undefined : parsePaytoUri(accountStr); if (uri) { try { - const result = await getFeeForAmount(uri, amount, api); + const result = await getFeeForAmount(uri, amount, api.wallet); setSelectedAccount(uri); setFee(result); } catch (e) { @@ -162,7 +162,7 @@ export function useComponentState( async function updateAmount(newAmount: AmountJson): Promise<void> { // const parsed = Amounts.parse(`${currency}:${numStr}`); try { - const result = await getFeeForAmount(currentAccount, newAmount, api); + const result = await getFeeForAmount(currentAccount, newAmount, api.wallet); setAmount(newAmount); setFee(result); } catch (e) { @@ -185,8 +185,8 @@ export function useComponentState( const amountError = !isDirty ? undefined : Amounts.cmp(balance, amount) === -1 - ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` - : undefined; + ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` + : undefined; const unableToDeposit = Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee @@ -243,11 +243,11 @@ export function useComponentState( async function getFeeForAmount( p: PaytoUri, a: AmountJson, - api: typeof wxApi, + wallet: ReturnType<typeof useBackendContext>["wallet"], ): Promise<DepositGroupFees> { const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`; const amount = Amounts.stringify(a); - return await api.wallet.call(WalletApiOperation.GetFeeForDeposit, { + return await wallet.call(WalletApiOperation.GetFeeForDeposit, { amount, depositPaytoUri, }); diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts index 3f08c678c..90ac020b7 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts @@ -23,14 +23,13 @@ import { Amounts, DepositGroupFees, parsePaytoUri, - stringifyPaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; +import { tests } from "../../../../web-util/src/index.browser.js"; import { - createWalletApiMock, - mountHook, - nullFunction, + createWalletApiMock, nullFunction } from "../../test-utils.js"; import { useComponentState } from "./state.js"; @@ -50,7 +49,7 @@ const withSomeFee = (): DepositGroupFees => ({ describe("DepositPage states", () => { it("should have status 'no-enough-balance' when balance is empty", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { @@ -72,27 +71,21 @@ describe("DepositPage states", () => { }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - - expect(await waitForStateUpdate()).true; - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("no-enough-balance"); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equal("loading"); + }, + ({ status }) => { + expect(status).equal("no-enough-balance"); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should have status 'no-accounts' when balance is not empty and accounts is empty", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { @@ -113,22 +106,17 @@ describe("DepositPage states", () => { accounts: [], }, ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - - expect(await waitForStateUpdate()).true; - { - const r = pullLastResultOrThrow(); - if (r.status !== "no-accounts") expect.fail(); - // expect(r.cancelHandler.onClick).not.undefined; - } - - await assertNoPendingUpdate(); + + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equal("loading"); + }, + ({ status }) => { + expect(status).equal("no-accounts"); + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); @@ -146,7 +134,7 @@ describe("DepositPage states", () => { }; it("should have status 'ready' but unable to deposit ", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { @@ -173,37 +161,29 @@ describe("DepositPage states", () => { withoutFee(), ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - - expect(await waitForStateUpdate()).true; - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); - expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); - expect(r.depositHandler.onClick).undefined; - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equal("loading"); + }, + ({ status }) => { + expect(status).equal("loading"); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.cancelHandler.onClick).not.undefined; + expect(state.currency).eq(currency); + expect(state.account.value).eq(stringifyPaytoUri(ibanPayto.uri)); + expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); + expect(state.depositHandler.onClick).undefined; + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); }); it("should not be able to deposit more than the balance ", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { @@ -235,134 +215,45 @@ describe("DepositPage states", () => { undefined, withoutFee(), ); - handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, - undefined, - withoutFee(), - ); - handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, - undefined, - withoutFee(), - ); - - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - expect(await waitForStateUpdate()).true; - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - - expect(await waitForStateUpdate()).true; const accountSelected = stringifyPaytoUri(ibanPayto.uri); - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); - expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); - expect(r.depositHandler.onClick).undefined; - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.account.onChange).not.undefined; - - r.account.onChange!(accountSelected); - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(accountSelected); - expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.depositHandler.onClick).undefined; - } - - await assertNoPendingUpdate(); - }); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equal("loading"); + }, + ({ status }) => { + expect(status).equal("loading"); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.cancelHandler.onClick).not.undefined; + expect(state.currency).eq(currency); + expect(state.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); + expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); + expect(state.depositHandler.onClick).undefined; + expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); + expect(state.account.onChange).not.undefined; + + state.account.onChange!(accountSelected); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.cancelHandler.onClick).not.undefined; + expect(state.currency).eq(currency); + expect(state.account.value).eq(accountSelected); + expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); + expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); + expect(state.depositHandler.onClick).undefined; + }, + ], TestingContext) - // it("should calculate the fee upon entering amount ", async () => { - // const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - // mountHook(() => - // useComponentState( - // { currency, onCancel: nullFunction, onSuccess: nullFunction }, - // { - // getBalance: async () => - // ({ - // balances: [{ available: `${currency}:1` }], - // } as Partial<BalancesResponse>), - // listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }), - // getFeeForDeposit: withSomeFee, - // } as Partial<typeof wxApi> as any, - // ), - // ); - - // { - // const { status } = getLastResultOrThrow(); - // expect(status).equal("loading"); - // } - - // await waitNextUpdate(); - - // { - // const r = getLastResultOrThrow(); - // if (r.status !== "ready") expect.fail(); - // expect(r.cancelHandler.onClick).not.undefined; - // expect(r.currency).eq(currency); - // expect(r.account.value).eq(accountSelected); - // expect(r.amount.value).eq("0"); - // expect(r.depositHandler.onClick).undefined; - // expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - - // r.amount.onInput("10"); - // } - - // expect(await waitForStateUpdate()).true; - - // { - // const r = pullLastResultOrThrow(); - // if (r.status !== "ready") expect.fail(); - // expect(r.cancelHandler.onClick).not.undefined; - // expect(r.currency).eq(currency); - // expect(r.account.value).eq(accountSelected); - // expect(r.amount.value).eq("10"); - // expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - // expect(r.depositHandler.onClick).undefined; - - // r.amount.onInput("3"); - // } - - // expect(await waitForStateUpdate()).true; - - // { - // const r = pullLastResultOrThrow(); - // if (r.status !== "ready") expect.fail(); - // expect(r.cancelHandler.onClick).not.undefined; - // expect(r.currency).eq(currency); - // expect(r.account.value).eq(accountSelected); - // expect(r.amount.value).eq("3"); - // expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - // expect(r.depositHandler.onClick).not.undefined; - // } - - // await assertNoPendingUpdate(); - // expect(handler.getCallingQueueState()).eq("empty") - // }); + expect(hookBehavior).deep.equal({ result: "ok" }) + expect(handler.getCallingQueueState()).eq("empty"); + }); it("should calculate the fee upon entering amount ", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { currency, onCancel: nullFunction, onSuccess: nullFunction }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { @@ -399,70 +290,54 @@ describe("DepositPage states", () => { withSomeFee(), ); - const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = - mountHook(() => useComponentState(props, mock)); - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - - expect(await waitForStateUpdate()).true; - - { - const { status } = pullLastResultOrThrow(); - expect(status).equal("loading"); - } - - expect(await waitForStateUpdate()).true; const accountSelected = stringifyPaytoUri(ibanPayto.uri); - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); - expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); - expect(r.depositHandler.onClick).undefined; - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); - expect(r.account.onChange).not.undefined; - - r.account.onChange!(accountSelected); - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(accountSelected); - expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); - expect(r.depositHandler.onClick).undefined; - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - - expect(r.amount.onInput).not.undefined; - if (!r.amount.onInput) return; - r.amount.onInput(Amounts.parseOrThrow("EUR:10")); - } - - expect(await waitForStateUpdate()).true; - - { - const r = pullLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); - expect(r.cancelHandler.onClick).not.undefined; - expect(r.currency).eq(currency); - expect(r.account.value).eq(accountSelected); - expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:10")); - expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); - expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); - expect(r.depositHandler.onClick).not.undefined; - } - - await assertNoPendingUpdate(); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equal("loading"); + }, + ({ status }) => { + expect(status).equal("loading"); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.cancelHandler.onClick).not.undefined; + expect(state.currency).eq(currency); + expect(state.account.value).eq(stringifyPaytoUri(talerBankPayto.uri)); + expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); + expect(state.depositHandler.onClick).undefined; + expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`)); + expect(state.account.onChange).not.undefined; + + state.account.onChange!(accountSelected); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.cancelHandler.onClick).not.undefined; + expect(state.currency).eq(currency); + expect(state.account.value).eq(accountSelected); + expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0")); + expect(state.depositHandler.onClick).undefined; + expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); + + expect(state.amount.onInput).not.undefined; + if (!state.amount.onInput) return; + state.amount.onInput(Amounts.parseOrThrow("EUR:10")); + }, + (state) => { + if (state.status !== "ready") expect.fail(); + expect(state.cancelHandler.onClick).not.undefined; + expect(state.currency).eq(currency); + expect(state.account.value).eq(accountSelected); + expect(state.amount.value).deep.eq(Amounts.parseOrThrow("EUR:10")); + expect(state.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); + expect(state.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`)); + expect(state.depositHandler.onClick).not.undefined; + }, + ], TestingContext) + + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); }); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts index 2f066d744..f1e766a18 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts @@ -18,7 +18,6 @@ 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"; @@ -88,6 +87,6 @@ const viewMapping: StateViewMap<State> = { export const DestinationSelectionPage = compose( "DestinationSelectionPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts index a67f926bc..0621d3304 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts @@ -17,15 +17,15 @@ import { Amounts } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; 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"; export function useComponentState( props: Props, - api: typeof wxApi, ): RecursiveState<State> { + const api = useBackendContext() const parsedInitialAmount = !props.amount ? undefined : Amounts.parse(props.amount); diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts index c2aa04849..afba5db35 100644 --- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts @@ -23,11 +23,12 @@ import { Amounts, ExchangeEntryStatus, ExchangeListItem, - ExchangeTosStatus, + 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 { tests } from "../../../../web-util/src/index.browser.js"; +import { createWalletApiMock, nullFunction } from "../../test-utils.js"; import { useComponentState } from "./state.js"; const exchangeArs: ExchangeListItem = { @@ -42,7 +43,7 @@ const exchangeArs: ExchangeListItem = { describe("Destination selection states", () => { it("should select currency if no amount specified", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); handler.addWalletCallResponse( WalletApiOperation.ListExchanges, @@ -54,83 +55,65 @@ describe("Destination selection states", () => { const props = { type: "get" as const, - goToWalletManualWithdraw: () => { - return null; - }, - goToWalletWalletInvoice: () => { - null; - }, + goToWalletManualWithdraw: nullFunction, + goToWalletWalletInvoice: nullFunction, }; - 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); + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status }) => { + expect(status).equal("loading"); + }, + (state) => { + 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!); + }, + (state) => { + 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")); - } + expect(state.amountHandler.value).deep.eq(Amounts.parseOrThrow("ARS:0")); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); it("should be possible to start with an amount specified in request params", async () => { - const { handler, mock } = createWalletApiMock(); + const { handler, TestingContext } = createWalletApiMock(); const props = { type: "get" as const, - goToWalletManualWithdraw: () => { - return null; - }, - goToWalletWalletInvoice: () => { - null; - }, + goToWalletManualWithdraw: nullFunction, + goToWalletWalletInvoice: nullFunction, 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")); - } + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + // ({ status }) => { + // expect(status).equal("loading"); + // }, + (state) => { + 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")); + }, + ], TestingContext) - await assertNoPendingUpdate(); + expect(hookBehavior).deep.equal({ result: "ok" }) expect(handler.getCallingQueueState()).eq("empty"); + }); }); diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index 2333fd3c1..c42798c8f 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -31,12 +31,12 @@ import { useEffect, useRef, useState } from "preact/hooks"; import { Diagnostics } from "../components/Diagnostics.js"; import { NotifyUpdateFadeOut } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useDiagnostics } from "../hooks/useDiagnostics.js"; import { Button } from "../mui/Button.js"; import { Grid } from "../mui/Grid.js"; -import { wxApi } from "../wxApi.js"; export function DeveloperPage(): VNode { const [status, timedOut] = useDiagnostics(); @@ -45,13 +45,15 @@ export function DeveloperPage(): VNode { //FIXME: waiting for retry notification make a always increasing loop of notifications listenAllEvents.includes = (e) => e !== "waiting-for-retry"; // includes every event + const api = useBackendContext(); + const response = useAsyncAsHook(async () => { - const op = await wxApi.wallet.call( + const op = await api.wallet.call( WalletApiOperation.GetPendingOperations, {}, ); - const c = await wxApi.wallet.call(WalletApiOperation.DumpCoins, {}); - const ex = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {}); + const c = await api.wallet.call(WalletApiOperation.DumpCoins, {}); + const ex = await api.wallet.call(WalletApiOperation.ListExchanges, {}); return { operations: op.pendingOperations, coins: c.coins, @@ -60,10 +62,7 @@ export function DeveloperPage(): VNode { }); useEffect(() => { - return wxApi.listener.onUpdateNotification( - listenAllEvents, - response?.retry, - ); + return api.listener.onUpdateNotification(listenAllEvents, response?.retry); }); const nonResponse = { operations: [], coins: [], exchanges: [] }; @@ -82,7 +81,7 @@ export function DeveloperPage(): VNode { coins={coins} exchanges={exchanges} onDownloadDatabase={async () => { - const db = await wxApi.wallet.call(WalletApiOperation.ExportDb, {}); + const db = await api.wallet.call(WalletApiOperation.ExportDb, {}); return JSON.stringify(db); }} /> @@ -135,9 +134,10 @@ export function View({ content, }); } + const api = useBackendContext(); const fileRef = useRef<HTMLInputElement>(null); async function onImportDatabase(str: string): Promise<void> { - return wxApi.wallet.call(WalletApiOperation.ImportDb, { + return api.wallet.call(WalletApiOperation.ImportDb, { dump: JSON.parse(str), }); } @@ -177,7 +177,7 @@ export function View({ onClick={() => confirmReset( i18n.str`Do you want to IRREVOCABLY DESTROY everything inside your wallet and LOSE ALL YOUR COINS?`, - () => wxApi.background.resetDb(), + () => api.background.resetDb(), ) } > @@ -190,7 +190,7 @@ export function View({ onClick={() => confirmReset( i18n.str`TESTING: This may delete all your coin, proceed with caution`, - () => wxApi.background.runGarbageCollector(), + () => api.background.runGarbageCollector(), ) } > diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts index 4b7725264..95badb218 100644 --- a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts @@ -17,7 +17,6 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; @@ -55,6 +54,6 @@ const viewMapping: StateViewMap<State> = { export const ComponentName = compose( "ComponentName", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts index d194b3f97..31a351579 100644 --- a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts @@ -14,10 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; -export function useComponentState({ p }: Props, api: typeof wxApi): State { +export function useComponentState({ p }: Props): State { return { status: "ready", error: undefined, diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx index a0c62787a..d8a7c6090 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx @@ -21,9 +21,9 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../context/backend.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { queryToSlashKeys } from "../utils/index.js"; -import { wxApi } from "../wxApi.js"; import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm.js"; import { ExchangeSetUrlPage } from "./ExchangeSetUrl.js"; @@ -37,8 +37,9 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode { { url: string; config: TalerConfigResponse } | undefined >(undefined); + const api = useBackendContext(); const knownExchangesResponse = useAsyncAsHook(() => - wxApi.wallet.call(WalletApiOperation.ListExchanges, {}), + api.wallet.call(WalletApiOperation.ListExchanges, {}), ); const knownExchanges = !knownExchangesResponse ? [] @@ -75,7 +76,7 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode { url={verifying.url} onCancel={onBack} onConfirm={async () => { - await wxApi.wallet.call(WalletApiOperation.AddExchange, { + await api.wallet.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: canonicalizeBaseUrl(verifying.url), forceUpdate: true, }); diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index a95830f8e..661fa5286 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -18,14 +18,13 @@ import { DenomOperationMap, ExchangeFullDetails, ExchangeListItem, - FeeDescriptionPair, + FeeDescriptionPair } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { ComparingView, @@ -33,7 +32,7 @@ import { NoExchangesView, PrivacyContentView, ReadyView, - TosContentView, + TosContentView } from "./views.js"; export interface Props { @@ -106,6 +105,6 @@ const viewMapping: StateViewMap<State> = { export const ExchangeSelectionPage = compose( "ExchangeSelectionPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts index 63d545b97..585050413 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -17,17 +17,17 @@ import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util"; import { createPairTimeline, - WalletApiOperation, + WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { onCancel, onSelection, list: exchanges, currentExchange }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const initialValue = exchanges.findIndex( (e) => e.exchangeBaseUrl === currentExchange, ); @@ -52,14 +52,14 @@ export function useComponentState( const selected = !selectedExchange ? undefined : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { - exchangeBaseUrl: selectedExchange.exchangeBaseUrl, - }); + exchangeBaseUrl: selectedExchange.exchangeBaseUrl, + }); const original = !initialExchange ? undefined : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { - exchangeBaseUrl: initialExchange.exchangeBaseUrl, - }); + exchangeBaseUrl: initialExchange.exchangeBaseUrl, + }); return { exchanges, diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 4b9c5c711..50f634f52 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -33,13 +33,13 @@ import { } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; import { TransactionItem } from "../components/TransactionItem.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { NoBalanceHelp } from "../popup/NoBalanceHelp.js"; import DownloadIcon from "../svg/download_24px.svg"; import UploadIcon from "../svg/upload_24px.svg"; -import { wxApi } from "../wxApi.js"; interface Props { currency?: string; @@ -52,13 +52,14 @@ export function HistoryPage({ goToWalletDeposit, }: Props): VNode { const { i18n } = useTranslationContext(); + const api = useBackendContext(); const state = useAsyncAsHook(async () => ({ - b: await wxApi.wallet.call(WalletApiOperation.GetBalances, {}), - tx: await wxApi.wallet.call(WalletApiOperation.GetTransactions, {}), + b: await api.wallet.call(WalletApiOperation.GetBalances, {}), + tx: await api.wallet.call(WalletApiOperation.GetTransactions, {}), })); useEffect(() => { - return wxApi.listener.onUpdateNotification( + return api.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], state?.retry, ); diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts index df4e7586f..0ee6472d6 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts @@ -20,10 +20,9 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, SelectFieldHandler, - TextFieldHandler, + TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; @@ -75,6 +74,6 @@ const viewMapping: StateViewMap<State> = { export const ManageAccountPage = compose( "ManageAccountPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts index 1f920f05f..d60ef962b 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts @@ -17,18 +17,18 @@ import { KnownBankAccountsInfo, parsePaytoUri, - stringifyPaytoUri, + stringifyPaytoUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { AccountByType, Props, State } from "./index.js"; export function useComponentState( { currency, onAccountAdded, onCancel }: Props, - api: typeof wxApi, ): State { + const api = useBackendContext() const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }), ); diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts index 253a0e629..3791b8967 100644 --- a/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/Notifications/index.ts @@ -18,11 +18,10 @@ import { UserAttentionUnreadList } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; -export interface Props {} +export type Props = object export type State = State.Loading | State.LoadingUriError | State.Ready; @@ -56,6 +55,6 @@ const viewMapping: StateViewMap<State> = { export const NotificationsPage = compose( "NotificationsPage", - (p: Props) => useComponentState(p, wxApi), + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts index 093722cf0..1042dea9f 100644 --- a/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts @@ -15,11 +15,12 @@ */ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; -export function useComponentState({}: Props, api: typeof wxApi): State { +export function useComponentState(p: Props): State { + const api = useBackendContext() const hook = useAsyncAsHook(async () => { return await api.wallet.call( WalletApiOperation.GetUserAttentionRequests, diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx index d5f072828..eb86c9a3f 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx @@ -31,10 +31,10 @@ import { SubTitle, Title, } from "../components/styled/index.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { Button } from "../mui/Button.js"; import { queryToSlashConfig } from "../utils/index.js"; -import { wxApi } from "../wxApi.js"; interface Props { currency: string; @@ -46,7 +46,7 @@ export function ProviderAddPage({ onBack }: Props): VNode { | { url: string; name: string; provider: BackupBackupProviderTerms } | undefined >(undefined); - + const api = useBackendContext(); if (!verifying) { return ( <SetUrlView @@ -70,7 +70,7 @@ export function ProviderAddPage({ onBack }: Props): VNode { setVerifying(undefined); }} onConfirm={() => { - return wxApi.wallet + return api.wallet .call(WalletApiOperation.AddBackupProvider, { backupProviderBaseUrl: verifying.url, name: verifying.name, diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index 6dde30b39..46d54e871 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -28,10 +28,10 @@ import { Loading } from "../components/Loading.js"; import { LoadingError } from "../components/LoadingError.js"; import { PaymentStatus, SmallLightText } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; -import { wxApi } from "../wxApi.js"; interface Props { pid: string; @@ -47,12 +47,10 @@ export function ProviderDetailPage({ onWithdraw, }: Props): VNode { const { i18n } = useTranslationContext(); + const api = useBackendContext(); async function getProviderInfo(): Promise<ProviderInfo | null> { //create a first list of backup info by currency - const status = await wxApi.wallet.call( - WalletApiOperation.GetBackupInfo, - {}, - ); + const status = await api.wallet.call(WalletApiOperation.GetBackupInfo, {}); const providers = status.providers.filter( (p) => p.syncProviderBaseUrl === providerURL, @@ -103,7 +101,7 @@ export function ProviderDetailPage({ <ProviderView info={info} onSync={async () => - wxApi.wallet + api.wallet .call(WalletApiOperation.RunBackupCycle, { providers: [providerURL], }) @@ -120,7 +118,7 @@ export function ProviderDetailPage({ onWithdraw(info.paymentStatus.amount); }} onDelete={() => - wxApi.wallet + api.wallet .call(WalletApiOperation.RemoveBackupProvider, { provider: providerURL, }) diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index c0268a1ae..f00b242d6 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -34,6 +34,7 @@ import { SuccessText, WarningText, } from "../components/styled/index.js"; +import { useBackendContext } from "../context/backend.js"; import { useDevContext } from "../context/devContext.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; @@ -43,7 +44,6 @@ import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js"; import { ToggleHandler } from "../mui/handlers.js"; import { Pages } from "../NavigationBar.js"; import { platform } from "../platform/api.js"; -import { wxApi } from "../wxApi.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; @@ -53,10 +53,11 @@ export function SettingsPage(): VNode { const { devModeToggle } = useDevContext(); const { name, update } = useBackupDeviceName(); const webex = platform.getWalletWebExVersion(); + const api = useBackendContext(); const exchangesHook = useAsyncAsHook(async () => { - const list = await wxApi.wallet.call(WalletApiOperation.ListExchanges, {}); - const version = await wxApi.wallet.call(WalletApiOperation.GetVersion, {}); + const list = await api.wallet.call(WalletApiOperation.ListExchanges, {}); + const version = await api.wallet.call(WalletApiOperation.GetVersion, {}); return { exchanges: list.exchanges, version }; }); const { exchanges, version } = diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index e70f5fbd1..b7eb4a947 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -60,11 +60,11 @@ import { WarningBox, } from "../components/styled/index.js"; import { Time } from "../components/Time.js"; +import { useBackendContext } from "../context/backend.js"; import { useTranslationContext } from "../context/translation.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { Button } from "../mui/Button.js"; import { Pages } from "../NavigationBar.js"; -import { wxApi } from "../wxApi.js"; interface Props { tid: string; @@ -76,17 +76,17 @@ export function TransactionPage({ goToWalletHistory, }: Props): VNode { const { i18n } = useTranslationContext(); - + const api = useBackendContext(); const state = useAsyncAsHook( () => - wxApi.wallet.call(WalletApiOperation.GetTransactionById, { + api.wallet.call(WalletApiOperation.GetTransactionById, { transactionId, }), [transactionId], ); useEffect(() => - wxApi.listener.onUpdateNotification( + api.listener.onUpdateNotification( [NotificationType.WithdrawGroupFinished], state?.retry, ), @@ -118,19 +118,19 @@ export function TransactionPage({ null; }} onDelete={async () => { - await wxApi.wallet.call(WalletApiOperation.DeleteTransaction, { + await api.wallet.call(WalletApiOperation.DeleteTransaction, { transactionId, }); goToWalletHistory(currency); }} onRetry={async () => { - await wxApi.wallet.call(WalletApiOperation.RetryTransaction, { + await api.wallet.call(WalletApiOperation.RetryTransaction, { transactionId, }); goToWalletHistory(currency); }} onRefund={async (purchaseId) => { - await wxApi.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, { + await api.wallet.call(WalletApiOperation.ApplyRefundFromPurchaseId, { purchaseId, }); }} diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 524eed990..d304a42a5 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -151,6 +151,14 @@ function onUpdateNotification( return platform.listenToWalletBackground(onNewMessage); } +export type WxApiType = { + wallet: WalletCoreApiClient; + background: BackgroundApiClient; + listener: { + onUpdateNotification: typeof onUpdateNotification; + } +} + export const wxApi = { wallet: new WxWalletCoreApiClient(), background: new BackgroundApiClient(), diff --git a/packages/web-util/src/tests/hook.ts b/packages/web-util/src/tests/hook.ts index f5bebbd6d..a4938f3f9 100644 --- a/packages/web-util/src/tests/hook.ts +++ b/packages/web-util/src/tests/hook.ts @@ -246,7 +246,7 @@ interface HookTestResultError { export async function hookBehaveLikeThis<T extends object, PropsType>( hookFunction: (p: PropsType) => RecursiveState<T>, props: PropsType, - checks: Array<(state: T) => void>, + checks: Array<(state: Exclude<T, VoidFunction>) => void>, Context?: ({ children }: { children: any }) => VNode | null, ): Promise<HookTestResult> { const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = |