diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
4 files changed, 260 insertions, 23 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx index e4955e376..407d4ef34 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx @@ -29,30 +29,30 @@ export default { }; // , -const exchangeList = { +const exchangeUrlWithCurrency = { "http://exchange.taler:8081": "COL", "http://exchange.tal": "EUR", }; export const WithoutAnyExchangeKnown = createExample(TestedComponent, { - exchangeList: {}, + exchangeUrlWithCurrency: {}, }); export const InitialState = createExample(TestedComponent, { - exchangeList, + exchangeUrlWithCurrency, }); export const WithAmountInitialized = createExample(TestedComponent, { initialAmount: "10", - exchangeList, + exchangeUrlWithCurrency, }); export const WithExchangeError = createExample(TestedComponent, { error: "The exchange url seems invalid", - exchangeList, + exchangeUrlWithCurrency, }); export const WithAmountError = createExample(TestedComponent, { initialAmount: "e", - exchangeList, + exchangeUrlWithCurrency, }); diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts new file mode 100644 index 000000000..a5174bef9 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts @@ -0,0 +1,212 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { SelectFieldHandler, TextFieldHandler, useComponentState } from "./CreateManualWithdraw"; +import { expect } from "chai"; +import { mountHook } from "../test-utils"; + + +const exchangeListWithARSandUSD = { + "url1": "USD", + "url2": "ARS", + "url3": "ARS", +}; + +const exchangeListEmpty = { +}; + +describe("CreateManualWithdraw states", () => { + it("should set noExchangeFound when exchange list is empty", () => { + const { result } = mountHook(() => + useComponentState(exchangeListEmpty, undefined, undefined), + ); + + if (!result.current) { + expect(result.current).not.to.be.undefined; + return; + } + + expect(result.current.noExchangeFound).equal(true) + }); + + it("should set noExchangeFound when exchange list doesn't include selected currency", () => { + const { result } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "COL"), + ); + + if (!result.current) { + expect(result.current).not.to.be.undefined; + return; + } + + expect(result.current.noExchangeFound).equal(true) + }); + + + it("should select the first exchange from the list", () => { + const { result } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, undefined), + ); + + if (!result.current) { + expect(result.current).not.to.be.undefined; + return; + } + + expect(result.current.exchange.value).equal("url1") + }); + + it("should select the first exchange with the selected currency", () => { + const { result } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect(result.current).not.to.be.undefined; + return; + } + + expect(result.current.exchange.value).equal("url2") + }); + + it("should change the exchange when currency change", async () => { + const { result, waitNextUpdate } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect.fail("hook didn't render"); + } + + expect(result.current.exchange.value).equal("url2") + + result.current.currency.onChange("USD") + + await waitNextUpdate() + + expect(result.current.exchange.value).equal("url1") + + }); + + it("should change the currency when exchange change", async () => { + const { result, waitNextUpdate } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect.fail("hook didn't render"); + } + + expect(result.current.exchange.value).equal("url2") + expect(result.current.currency.value).equal("ARS") + + result.current.exchange.onChange("url1") + + await waitNextUpdate() + + expect(result.current.exchange.value).equal("url1") + expect(result.current.currency.value).equal("USD") + }); + + it("should update parsed amount when amount change", async () => { + const { result, waitNextUpdate } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect.fail("hook didn't render"); + } + + expect(result.current.parsedAmount).equal(undefined) + + result.current.amount.onInput("12") + + await waitNextUpdate() + + expect(result.current.parsedAmount).deep.equals({ + value: 12, fraction: 0, currency: "ARS" + }) + }); + + it("should have an amount field", async () => { + const { result, waitNextUpdate } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect.fail("hook didn't render"); + } + + await defaultTestForInputText(waitNextUpdate, () => result.current!.amount) + }) + + it("should have an exchange selector ", async () => { + const { result, waitNextUpdate } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect.fail("hook didn't render"); + } + + await defaultTestForInputSelect(waitNextUpdate, () => result.current!.exchange) + }) + + it("should have a currency selector ", async () => { + const { result, waitNextUpdate } = mountHook(() => + useComponentState(exchangeListWithARSandUSD, undefined, "ARS"), + ); + + if (!result.current) { + expect.fail("hook didn't render"); + } + + await defaultTestForInputSelect(waitNextUpdate, () => result.current!.currency) + }) + +}); + + +async function defaultTestForInputText(awaiter: () => Promise<void>, getField: () => TextFieldHandler) { + const initialValue = getField().value; + const otherValue = `${initialValue} something else` + getField().onInput(otherValue) + + await awaiter() + + expect(getField().value).equal(otherValue) +} + + +async function defaultTestForInputSelect(awaiter: () => Promise<void>, getField: () => SelectFieldHandler) { + const initialValue = getField().value; + const keys = Object.keys(getField().list) + const nextIdx = keys.indexOf(initialValue) + 1 + if (keys.length < nextIdx) { + throw new Error('no enough values') + } + const nextValue = keys[nextIdx] + getField().onChange(nextValue) + + await awaiter() + + expect(getField().value).equal(nextValue) +} diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx index 068135ae0..2d5129a3d 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx @@ -39,20 +39,39 @@ import { Pages } from "../NavigationBar"; export interface Props { error: string | undefined; initialAmount?: string; - exchangeList: Record<string, string>; + exchangeUrlWithCurrency: Record<string, string>; onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>; initialCurrency?: string; } +export interface State { + noExchangeFound: boolean; + parsedAmount: AmountJson | undefined; + amount: TextFieldHandler; + currency: SelectFieldHandler; + exchange: SelectFieldHandler; +} + +export interface TextFieldHandler { + onInput: (value: string) => void; + value: string; +} + +export interface SelectFieldHandler { + onChange: (value: string) => void; + value: string; + list: Record<string, string>; +} + export function useComponentState( - exchangeList: Record<string, string>, + exchangeUrlWithCurrency: Record<string, string>, initialAmount: string | undefined, initialCurrency: string | undefined, -) { - const exchangeSelectList = Object.keys(exchangeList); - const currencySelectList = Object.values(exchangeList); +): State { + const exchangeSelectList = Object.keys(exchangeUrlWithCurrency); + const currencySelectList = Object.values(exchangeUrlWithCurrency); const exchangeMap = exchangeSelectList.reduce( - (p, c) => ({ ...p, [c]: `${c} (${exchangeList[c]})` }), + (p, c) => ({ ...p, [c]: `${c} (${exchangeUrlWithCurrency[c]})` }), {} as Record<string, string>, ); const currencyMap = currencySelectList.reduce( @@ -61,7 +80,7 @@ export function useComponentState( ); const foundExchangeForCurrency = exchangeSelectList.findIndex( - (e) => exchangeList[e] === initialCurrency, + (e) => exchangeUrlWithCurrency[e] === initialCurrency, ); const initialExchange = @@ -73,7 +92,7 @@ export function useComponentState( const [exchange, setExchange] = useState(initialExchange || ""); const [currency, setCurrency] = useState( - initialExchange ? exchangeList[initialExchange] : "", + initialExchange ? exchangeUrlWithCurrency[initialExchange] : "", ); const [amount, setAmount] = useState(initialAmount || ""); @@ -81,12 +100,14 @@ export function useComponentState( function changeExchange(exchange: string): void { setExchange(exchange); - setCurrency(exchangeList[exchange]); + setCurrency(exchangeUrlWithCurrency[exchange]); } function changeCurrency(currency: string): void { setCurrency(currency); - const found = Object.entries(exchangeList).find((e) => e[1] === currency); + const found = Object.entries(exchangeUrlWithCurrency).find( + (e) => e[1] === currency, + ); if (found) { setExchange(found[0]); @@ -95,7 +116,7 @@ export function useComponentState( } } return { - initialExchange, + noExchangeFound: initialExchange === undefined, currency: { list: currencyMap, value: currency, @@ -114,12 +135,12 @@ export function useComponentState( }; } -interface InputHandler { +export interface InputHandler { value: string; onInput: (s: string) => void; } -interface SelectInputHandler { +export interface SelectInputHandler { list: Record<string, string>; value: string; onChange: (s: string) => void; @@ -127,16 +148,20 @@ interface SelectInputHandler { export function CreateManualWithdraw({ initialAmount, - exchangeList, + exchangeUrlWithCurrency, error, initialCurrency, onCreate, }: Props): VNode { const { i18n } = useTranslationContext(); - const state = useComponentState(exchangeList, initialAmount, initialCurrency); + const state = useComponentState( + exchangeUrlWithCurrency, + initialAmount, + initialCurrency, + ); - if (!state.initialExchange) { + if (state.noExchangeFound) { if (initialCurrency !== undefined) { return ( <section> diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx index b7e63bbf8..05c518508 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx @@ -110,7 +110,7 @@ export function ManualWithdrawPage({ currency, onCancel }: Props): VNode { return ( <CreateManualWithdraw error={error} - exchangeList={exchangeList} + exchangeUrlWithCurrency={exchangeList} onCreate={doCreate} initialCurrency={currency} /> |