diff --git a/packages/taler-wallet-webextension/.gitignore b/packages/taler-wallet-webextension/.gitignore index 2897bd5d0..9e7c76524 100644 --- a/packages/taler-wallet-webextension/.gitignore +++ b/packages/taler-wallet-webextension/.gitignore @@ -2,3 +2,4 @@ extension/ /storybook-static/ /.linaria-cache/ /lib +/coverage diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw.test.ts index e26e86445..2a297c4bb 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.test.ts @@ -19,7 +19,8 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ExchangeListItem } from "@gnu-taler/taler-util"; +import { Amounts, ExchangeListItem, GetExchangeTosResult } from "@gnu-taler/taler-util"; +import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../test-utils.js"; import { useComponentState } from "./Withdraw.js"; @@ -93,4 +94,156 @@ describe("Withdraw CTA states", () => { await assertNoPendingUpdate() }); + it("should be able to withdraw if tos are ok", async () => { + const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => + useComponentState('taler-withdraw://', { + listExchanges: async () => ({ exchanges }), + getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ + amount: 'ARS:2', + possibleExchanges: exchanges, + }), + getExchangeWithdrawalInfo: async (): Promise => ({ + withdrawalAmountRaw: 'ARS:5', + withdrawalAmountEffective: 'ARS:5', + } as any), + getExchangeTos: async (): Promise => ({ + contentType: 'text', + content: 'just accept', + acceptedEtag: 'v1', + currentEtag: 'v1' + }) + } as any), + ); + + { + const { status, hook } = getLastResultOrThrow() + expect(status).equals('loading-uri') + expect(hook).undefined; + } + + await waitNextUpdate() + + { + const { status, hook } = getLastResultOrThrow() + + expect(status).equals('loading-info') + + expect(hook).undefined; + } + + await waitNextUpdate() + + { + const state = getLastResultOrThrow() + expect(state.status).equals("success") + if (state.status !== "success") return; + + expect(state.exchange.isDirty).false + expect(state.exchange.value).equal("http://exchange.demo.taler.net") + expect(state.exchange.list).deep.equal({ + "http://exchange.demo.taler.net": "http://exchange.demo.taler.net" + }) + expect(state.showExchangeSelection).false + + 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.disabled).false + expect(state.mustAcceptFirst).false + + } + + await assertNoPendingUpdate() + }); + + it("should be accept the tos before withdraw", async () => { + const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = mountHook(() => + useComponentState('taler-withdraw://', { + listExchanges: async () => ({ exchanges }), + getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ + amount: 'ARS:2', + possibleExchanges: exchanges, + }), + getExchangeWithdrawalInfo: async (): Promise => ({ + withdrawalAmountRaw: 'ARS:5', + withdrawalAmountEffective: 'ARS:5', + } as any), + getExchangeTos: async (): Promise => ({ + contentType: 'text', + content: 'just accept', + acceptedEtag: 'v1', + currentEtag: 'v2' + }), + setExchangeTosAccepted: async () => ({}) + } as any), + ); + + { + const { status, hook } = getLastResultOrThrow() + expect(status).equals('loading-uri') + expect(hook).undefined; + } + + await waitNextUpdate() + + { + const { status, hook } = getLastResultOrThrow() + + expect(status).equals('loading-info') + + expect(hook).undefined; + } + + await waitNextUpdate() + + { + const state = getLastResultOrThrow() + expect(state.status).equals("success") + if (state.status !== "success") return; + + expect(state.exchange.isDirty).false + expect(state.exchange.value).equal("http://exchange.demo.taler.net") + expect(state.exchange.list).deep.equal({ + "http://exchange.demo.taler.net": "http://exchange.demo.taler.net" + }) + expect(state.showExchangeSelection).false + + 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.disabled).true + expect(state.mustAcceptFirst).true + + // accept TOS + state.tosProps?.onAccept(true) + } + + await waitNextUpdate() + + { + const state = getLastResultOrThrow() + expect(state.status).equals("success") + if (state.status !== "success") return; + + expect(state.exchange.isDirty).false + expect(state.exchange.value).equal("http://exchange.demo.taler.net") + expect(state.exchange.list).deep.equal({ + "http://exchange.demo.taler.net": "http://exchange.demo.taler.net" + }) + expect(state.showExchangeSelection).false + + 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.disabled).false + expect(state.mustAcceptFirst).true + + } + + await assertNoPendingUpdate() + }); + }); \ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx index 6eb87a85c..64059f721 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx @@ -195,7 +195,7 @@ export function useComponentState( const [withdrawError, setWithdrawError] = useState( undefined, ); - const [confirmDisabled, setConfirmDisabled] = useState(false); + const [doingWithdraw, setDoingWithdraw] = useState(false); const [showExchangeSelection, setShowExchangeSelection] = useState(false); const [nextExchange, setNextExchange] = useState(); @@ -222,7 +222,7 @@ export function useComponentState( async function doWithdrawAndCheckError(): Promise { try { - setConfirmDisabled(true); + setDoingWithdraw(true); if (!talerWithdrawUri) return; const res = await api.acceptWithdrawal( talerWithdrawUri, @@ -235,8 +235,8 @@ export function useComponentState( if (e instanceof TalerError) { setWithdrawError(e); } - setConfirmDisabled(false); } + setDoingWithdraw(false); } const exchanges = thisCurrencyExchanges.reduce( @@ -259,9 +259,9 @@ export function useComponentState( const exchangeHandler: SelectFieldHandler = { onChange: setNextExchange, - value: nextExchange || thisExchange, + value: nextExchange ?? thisExchange, list: exchanges, - isDirty: nextExchange !== thisExchange, + isDirty: nextExchange !== undefined, }; const editExchange: ButtonHandler = { @@ -278,6 +278,7 @@ export function useComponentState( onClick: async () => { setCustomExchange(exchangeHandler.value); setShowExchangeSelection(false); + setNextExchange(undefined); }, }; @@ -307,6 +308,10 @@ export function useComponentState( } } + const mustAcceptFirst = + termsState !== undefined && + (termsState.status === "changed" || termsState.status === "new"); + return { status: "success", hook: undefined, @@ -321,7 +326,7 @@ export function useComponentState( doWithdrawal: { onClick: doWithdrawAndCheckError, error: withdrawError, - disabled: confirmDisabled, + disabled: doingWithdraw || (mustAcceptFirst && !reviewed), }, tosProps: !termsState ? undefined @@ -332,9 +337,7 @@ export function useComponentState( reviewing: reviewing, terms: termsState, }, - mustAcceptFirst: - termsState !== undefined && - (termsState.status === "changed" || termsState.status === "new"), + mustAcceptFirst, }; }