splitted rollup config for testing and first component state unit test
This commit is contained in:
parent
136c39ba9f
commit
e21c1b3192
3
packages/taler-wallet-webextension/encode.mjs
Normal file
3
packages/taler-wallet-webextension/encode.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
import {encodeCrock, stringToBytes} from "../taler-util/lib/talerCrypto.js";
|
||||
const pepe =process.argv[2]
|
||||
console.log(pepe, encodeCrock(stringToBytes(pepe)));
|
@ -12,6 +12,7 @@
|
||||
"test": "mocha --enable-source-maps 'dist/**/*.test.js'",
|
||||
"test:coverage": "nyc pnpm test",
|
||||
"compile": "rollup -c -m",
|
||||
"compile:test": "rollup -c rollup.config.test.js -m",
|
||||
"prepare": "rollup -c -m",
|
||||
"build-storybook": "build-storybook",
|
||||
"storybook": "start-storybook -s . -p 6006 --no-open",
|
||||
|
@ -10,30 +10,7 @@ import css from 'rollup-plugin-css-only';
|
||||
import ignore from "rollup-plugin-ignore";
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
function fromDir(startPath, regex) {
|
||||
if (!fs.existsSync(startPath)) {
|
||||
return;
|
||||
}
|
||||
const files = fs.readdirSync(startPath);
|
||||
const result = files.flatMap(file => {
|
||||
const filename = path.join(startPath, file);
|
||||
|
||||
const stat = fs.lstatSync(filename);
|
||||
if (stat.isDirectory()) {
|
||||
return fromDir(filename, regex);
|
||||
}
|
||||
else if (regex.test(filename)) {
|
||||
return filename
|
||||
}
|
||||
}).filter(x => !!x)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const makePlugins = () => [
|
||||
export const makePlugins = () => [
|
||||
typescript({
|
||||
outputToFilesystem: false,
|
||||
}),
|
||||
@ -135,26 +112,10 @@ const webExtensionCryptoWorker = {
|
||||
plugins: makePlugins(),
|
||||
};
|
||||
|
||||
const tests = fromDir('./src', /.test.ts$/).map(test => ({
|
||||
input: test,
|
||||
output: {
|
||||
file: test.replace(/^src/, 'dist').replace(/\.ts$/, '.js'),
|
||||
format: "iife",
|
||||
exports: "none",
|
||||
name: test,
|
||||
},
|
||||
plugins: [
|
||||
...makePlugins(),
|
||||
css({
|
||||
output: 'walletEntryPoint.css',
|
||||
}),
|
||||
],
|
||||
}))
|
||||
|
||||
export default [
|
||||
webExtensionPopupEntryPoint,
|
||||
webExtensionWalletEntryPoint,
|
||||
webExtensionBackgroundPageScript,
|
||||
webExtensionCryptoWorker,
|
||||
...tests,
|
||||
];
|
||||
|
47
packages/taler-wallet-webextension/rollup.config.test.js
Normal file
47
packages/taler-wallet-webextension/rollup.config.test.js
Normal file
@ -0,0 +1,47 @@
|
||||
// rollup.config.js
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import css from 'rollup-plugin-css-only';
|
||||
import { makePlugins } from "./rollup.config"
|
||||
|
||||
function fromDir(startPath, regex) {
|
||||
if (!fs.existsSync(startPath)) {
|
||||
return;
|
||||
}
|
||||
const files = fs.readdirSync(startPath);
|
||||
const result = files.flatMap(file => {
|
||||
const filename = path.join(startPath, file);
|
||||
|
||||
const stat = fs.lstatSync(filename);
|
||||
if (stat.isDirectory()) {
|
||||
return fromDir(filename, regex);
|
||||
}
|
||||
else if (regex.test(filename)) {
|
||||
return filename
|
||||
}
|
||||
}).filter(x => !!x)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const tests = fromDir('./src', /.test.ts$/)
|
||||
.filter(t => t === 'src/wallet/CreateManualWithdraw.test.ts')
|
||||
.map(test => ({
|
||||
input: test,
|
||||
output: {
|
||||
file: test.replace(/^src/, 'dist').replace(/\.ts$/, '.js'),
|
||||
format: "iife",
|
||||
exports: "none",
|
||||
name: test,
|
||||
},
|
||||
plugins: [
|
||||
...makePlugins(),
|
||||
css({
|
||||
output: 'walletEntryPoint.css',
|
||||
}),
|
||||
],
|
||||
}))
|
||||
|
||||
export default [
|
||||
...tests,
|
||||
];
|
@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="./static/popup.html" name="popup" width="500" height="400">
|
||||
algo
|
||||
</iframe>
|
||||
<hr />
|
||||
<iframe src="./static/wallet.html" name="wallet" width="800" height="100%">
|
||||
otroe
|
||||
</iframe>
|
||||
</body>
|
||||
</html>
|
@ -14,7 +14,7 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
import { useTalerActionURL } from "./useTalerActionURL"
|
||||
import { justBrowser_it, mountBrowser } from "../test-utils";
|
||||
import { mountHook } from "../test-utils";
|
||||
import { IoCProviderForTesting } from "../context/iocContext";
|
||||
import { h, VNode } from "preact";
|
||||
import { act } from "preact/test-utils";
|
||||
@ -22,7 +22,7 @@ import { act } from "preact/test-utils";
|
||||
describe('useTalerActionURL hook', () => {
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
justBrowser_it('should be set url to undefined when dismiss', async () => {
|
||||
it('should be set url to undefined when dismiss', async () => {
|
||||
|
||||
const ctx = ({ children }: { children: any }): VNode => {
|
||||
return h(IoCProviderForTesting, {
|
||||
@ -32,7 +32,7 @@ describe('useTalerActionURL hook', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const { result, waitNextUpdate } = mountBrowser(useTalerActionURL, ctx)
|
||||
const { result, waitNextUpdate } = mountHook(useTalerActionURL, ctx)
|
||||
|
||||
{
|
||||
const [url] = result.current!
|
||||
|
@ -31,7 +31,6 @@ function testThisStory(st: any): any {
|
||||
const Component = (st as any)[k];
|
||||
if (k === "default" || !Component) return;
|
||||
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it(`example: ${k}`, () => {
|
||||
renderNodeOrBrowser(Component, Component.args);
|
||||
|
@ -62,7 +62,9 @@ interface Mounted<T> {
|
||||
waitNextUpdate: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function mountBrowser<T>(callback: () => T, Context?: ({ children }: { children: any }) => VNode): Mounted<T> {
|
||||
const isNode = typeof window === "undefined"
|
||||
|
||||
export function mountHook<T>(callback: () => T, Context?: ({ children }: { children: any }) => VNode): Mounted<T> {
|
||||
const result: { current: T | null } = {
|
||||
current: null
|
||||
}
|
||||
@ -81,23 +83,6 @@ export function mountBrowser<T>(callback: () => T, Context?: ({ children }: { ch
|
||||
// create the vdom with context if required
|
||||
const vdom = !Context ? create(Component, {}) : create(Context, { children: [create(Component, {})] },);
|
||||
|
||||
// in non-browser environment (server side rendering) just serialize to
|
||||
// string and exit
|
||||
if (typeof window === "undefined") {
|
||||
renderToString(vdom);
|
||||
return { unmount: () => null, result } as any
|
||||
}
|
||||
|
||||
// do the render into the DOM
|
||||
const div = document.createElement("div");
|
||||
document.body.appendChild(div);
|
||||
renderIntoDom(vdom, div);
|
||||
|
||||
// clean up callback
|
||||
function unmount(): any {
|
||||
document.body.removeChild(div);
|
||||
}
|
||||
|
||||
// waiter callback
|
||||
async function waitNextUpdate(): Promise<void> {
|
||||
await new Promise((res, rej) => {
|
||||
@ -112,11 +97,22 @@ export function mountBrowser<T>(callback: () => T, Context?: ({ children }: { ch
|
||||
})
|
||||
}
|
||||
|
||||
const customElement = {} as Element
|
||||
const parentElement = isNode ? customElement : document.createElement("div");
|
||||
if (!isNode) {
|
||||
document.body.appendChild(parentElement);
|
||||
}
|
||||
|
||||
renderIntoDom(vdom, parentElement);
|
||||
|
||||
// clean up callback
|
||||
function unmount() {
|
||||
if (!isNode) {
|
||||
document.body.removeChild(parentElement);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
unmount, result, waitNextUpdate
|
||||
}
|
||||
}
|
||||
|
||||
const nullTestFunction = {} as TestFunction
|
||||
export const justBrowser_it: PendingTestFunction | TestFunction =
|
||||
typeof it === 'undefined' ? nullTestFunction : (typeof window === 'undefined' ? it.skip : it)
|
@ -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,
|
||||
});
|
||||
|
@ -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)
|
||||
}
|
@ -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>
|
||||
|
@ -110,7 +110,7 @@ export function ManualWithdrawPage({ currency, onCancel }: Props): VNode {
|
||||
return (
|
||||
<CreateManualWithdraw
|
||||
error={error}
|
||||
exchangeList={exchangeList}
|
||||
exchangeUrlWithCurrency={exchangeList}
|
||||
onCreate={doCreate}
|
||||
initialCurrency={currency}
|
||||
/>
|
||||
|
@ -104,7 +104,7 @@ async function callBackend(operation: string, payload: any): Promise<any> {
|
||||
}
|
||||
console.log("got response", response);
|
||||
if (response.type === "error") {
|
||||
throw new TalerError.fromUncheckedDetail(response.error);
|
||||
throw TalerError.fromUncheckedDetail(response.error);
|
||||
}
|
||||
return response.result;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user