fix #7153: more error handling

if handler do not trap error then fail at compile time,
all safe handlers push alert on error
errors are typed so they render good information
This commit is contained in:
Sebastian 2023-01-09 20:20:09 -03:00
parent 8a70edb2f8
commit 4a781bd0dd
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
91 changed files with 1003 additions and 1008 deletions

View File

@ -20,12 +20,10 @@
*/ */
import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { Grid } from "../mui/Grid.js"; import { AmountFieldHandler, nullFunction, withSafe } from "../mui/handlers.js";
import { AmountFieldHandler, TextFieldHandler } from "../mui/handlers.js";
import { AmountField } from "./AmountField.js"; import { AmountField } from "./AmountField.js";
export default { export default {
@ -39,9 +37,9 @@ function RenderAmount(): VNode {
const handler: AmountFieldHandler = { const handler: AmountFieldHandler = {
value: value ?? Amounts.zeroOfCurrency("USD"), value: value ?? Amounts.zeroOfCurrency("USD"),
onInput: async (e) => { onInput: withSafe(async (e) => {
setValue(e); setValue(e);
}, }, nullFunction),
error, error,
}; };
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();

View File

@ -18,7 +18,6 @@ import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useTranslationContext } from "../../../web-util/src/index.browser.js"; import { useTranslationContext } from "../../../web-util/src/index.browser.js";
import { import {
ErrorAlert,
Alert as AlertNotification, Alert as AlertNotification,
useAlertContext, useAlertContext,
} from "../context/alert.js"; } from "../context/alert.js";
@ -37,41 +36,78 @@ function AlertContext({
context: undefined | object; context: undefined | object;
}): VNode { }): VNode {
const [more, setMore] = useState(false); const [more, setMore] = useState(false);
const [wrap, setWrap] = useState(false);
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
if (!more) { if (!more) {
return ( return (
<div style={{ display: "flex", justifyContent: "right" }}> <div style={{ display: "flex", justifyContent: "right" }}>
<a onClick={() => setMore(true)}> <a
onClick={() => setMore(true)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>more info</i18n.Translate> <i18n.Translate>more info</i18n.Translate>
</a> </a>
</div> </div>
); );
} }
return ( const errorInfo = JSON.stringify(
<pre style={{ overflow: "overlay" }}>
{JSON.stringify(
context === undefined ? { cause } : { context, cause }, context === undefined ? { cause } : { context, cause },
undefined, undefined,
2, 2,
)} );
return (
<Fragment>
<div style={{ display: "flex", justifyContent: "right" }}>
<a
onClick={() => setWrap(!wrap)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>wrap text</i18n.Translate>
</a>
&nbsp;&nbsp;
<a
onClick={() => navigator.clipboard.writeText(errorInfo)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>copy content</i18n.Translate>
</a>
&nbsp;&nbsp;
<a
onClick={() => setMore(false)}
style={{ cursor: "pointer", textDecoration: "underline" }}
>
<i18n.Translate>less info</i18n.Translate>
</a>
</div>
<pre
style={
wrap
? {
whiteSpace: "pre-wrap",
overflowWrap: "anywhere",
}
: {
overflow: "overlay",
}
}
>
{errorInfo}
</pre> </pre>
</Fragment>
); );
} }
export function ErrorAlertView({ export function ErrorAlertView({
error: alert, error,
onClose, onClose,
}: { }: {
error: ErrorAlert; error: AlertNotification;
onClose?: () => Promise<void>; onClose?: () => Promise<void>;
}): VNode { }): VNode {
return ( return (
<Alert title={alert.message} severity={alert.type} onClose={onClose}> <Wrapper>
<div style={{ display: "flex", flexDirection: "column" }}> <AlertView alert={error} onClose={onClose} />
<div>{alert.description}</div> </Wrapper>
<AlertContext context={alert.context} cause={alert.cause} />
</div>
</Alert>
); );
} }
@ -86,6 +122,9 @@ export function AlertView({
<Alert title={alert.message} severity={alert.type} onClose={onClose}> <Alert title={alert.message} severity={alert.type} onClose={onClose}>
<div style={{ display: "flex", flexDirection: "column" }}> <div style={{ display: "flex", flexDirection: "column" }}>
<div>{alert.description}</div> <div>{alert.description}</div>
{alert.type === "error" ? (
<AlertContext context={alert.context} cause={alert.cause} />
) : undefined}
</div> </div>
</Alert> </Alert>
); );
@ -104,5 +143,5 @@ export function CurrentAlerts(): VNode {
} }
function Wrapper({ children }: { children: ComponentChildren }): VNode { function Wrapper({ children }: { children: ComponentChildren }): VNode {
return <div style={{ margin: "2em" }}>{children}</div>; return <div style={{ margin: "1em" }}>{children}</div>;
} }

View File

@ -24,7 +24,7 @@ import {
Transaction, Transaction,
TransactionType, TransactionType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { PendingTransactionsView as TestedComponent } from "./PendingTransactions.js"; import { PendingTransactionsView as TestedComponent } from "./PendingTransactions.js";
export default { export default {
@ -32,7 +32,7 @@ export default {
component: TestedComponent, component: TestedComponent,
}; };
export const OnePendingTransaction = createExample(TestedComponent, { export const OnePendingTransaction = tests.createExample(TestedComponent, {
transactions: [ transactions: [
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
@ -42,7 +42,7 @@ export const OnePendingTransaction = createExample(TestedComponent, {
], ],
}); });
export const ThreePendingTransactions = createExample(TestedComponent, { export const ThreePendingTransactions = tests.createExample(TestedComponent, {
transactions: [ transactions: [
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
@ -62,7 +62,7 @@ export const ThreePendingTransactions = createExample(TestedComponent, {
], ],
}); });
export const TenPendingTransactions = createExample(TestedComponent, { export const TenPendingTransactions = tests.createExample(TestedComponent, {
transactions: [ transactions: [
{ {
amountEffective: "USD:10", amountEffective: "USD:10",

View File

@ -19,13 +19,13 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { QR } from "./QR.js"; import { QR } from "./QR.js";
export default { export default {
title: "qr", title: "qr",
}; };
export const Restore = createExample(QR, { export const Restore = tests.createExample(QR, {
text: "taler://restore/6J0RZTJC6AV21WXK87BTE67WTHE9P2QSHF2BZXTP7PDZY2ARYBPG@sync1.demo.taler.net,sync2.demo.taler.net,sync1.demo.taler.net,sync3.demo.taler.net", text: "taler://restore/6J0RZTJC6AV21WXK87BTE67WTHE9P2QSHF2BZXTP7PDZY2ARYBPG@sync1.demo.taler.net,sync2.demo.taler.net,sync1.demo.taler.net,sync3.demo.taler.net",
}); });

View File

@ -20,7 +20,7 @@
*/ */
import { WalletContractData } from "@gnu-taler/taler-wallet-core"; import { WalletContractData } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { import {
ErrorView, ErrorView,
HiddenView, HiddenView,
@ -86,10 +86,10 @@ const cd: WalletContractData = {
deliveryLocation: undefined, deliveryLocation: undefined,
}; };
export const ShowingSimpleOrder = createExample(ShowView, { export const ShowingSimpleOrder = tests.createExample(ShowView, {
contractTerms: cd, contractTerms: cd,
}); });
export const Error = createExample(ErrorView, { export const Error = tests.createExample(ErrorView, {
proposalId: "asd", proposalId: "asd",
error: { error: {
hasError: true, hasError: true,
@ -103,5 +103,5 @@ export const Error = createExample(ErrorView, {
// }, // },
}, },
}); });
export const Loading = createExample(LoadingView, {}); export const Loading = tests.createExample(LoadingView, {});
export const Hidden = createExample(HiddenView, {}); export const Hidden = tests.createExample(HiddenView, {});

View File

@ -24,14 +24,14 @@ import { useState } from "preact/hooks";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { Modal } from "../components/Modal.js"; import { Modal } from "../components/Modal.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { alertFromError } from "../context/alert.js"; import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { HookError, useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../mui/handlers.js"; import { ButtonHandler } from "../mui/handlers.js";
import { compose, StateViewMap } from "../utils/index.js"; import { compose, StateViewMap } from "../utils/index.js";
import { Amount } from "./Amount.js"; import { Amount } from "./Amount.js";
import { AlertView } from "./CurrentAlerts.js"; import { ErrorAlertView } from "./CurrentAlerts.js";
import { Link } from "./styled/index.js"; import { Link } from "./styled/index.js";
const ContractTermsTable = styled.table` const ContractTermsTable = styled.table`
@ -102,6 +102,7 @@ interface Props {
function useComponentState({ proposalId }: Props): State { function useComponentState({ proposalId }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const { pushAlertOnError } = useAlertContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
if (!show) return undefined; if (!show) return undefined;
return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, { return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, {
@ -110,10 +111,10 @@ function useComponentState({ proposalId }: Props): State {
}, [show]); }, [show]);
const hideHandler = { const hideHandler = {
onClick: async () => setShow(false), onClick: pushAlertOnError(async () => setShow(false)),
}; };
const showHandler = { const showHandler = {
onClick: async () => setShow(true), onClick: pushAlertOnError(async () => setShow(true)),
}; };
if (!show) { if (!show) {
return { return {
@ -161,8 +162,8 @@ export function ErrorView({
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (
<Modal title="Full detail" onClose={hideHandler}> <Modal title="Full detail" onClose={hideHandler}>
<AlertView <ErrorAlertView
alert={alertFromError( error={alertFromError(
i18n.str`Could not load purchase proposal details`, i18n.str`Could not load purchase proposal details`,
error, error,
{ proposalId }, { proposalId },

View File

@ -28,7 +28,7 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
const readOnly = !onChange; const readOnly = !onChange;
const [showContent, setShowContent] = useState<boolean>(readOnly); const [showContent, setShowContent] = useState<boolean>(readOnly);
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlert } = useAlertContext(); const { pushAlertOnError } = useAlertContext();
/** /**
* For the exchange selected, bring the status of the terms of service * For the exchange selected, bring the status of the terms of service
@ -67,7 +67,6 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
async function onUpdate(accepted: boolean): Promise<void> { async function onUpdate(accepted: boolean): Promise<void> {
if (!state) return; if (!state) return;
try {
if (accepted) { if (accepted) {
await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, { await api.wallet.call(WalletApiOperation.SetExchangeTosAccepted, {
exchangeBaseUrl: exchangeUrl, exchangeBaseUrl: exchangeUrl,
@ -82,9 +81,6 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
} }
// setAccepted(accepted); // setAccepted(accepted);
if (!readOnly) onChange(accepted); //external update if (!readOnly) onChange(accepted); //external update
} catch (e) {
pushAlert(alertFromError(i18n.str`Could not accept terms of service`, e));
}
} }
const accepted = state.status === "accepted"; const accepted = state.status === "accepted";
@ -94,20 +90,20 @@ export function useComponentState({ exchangeUrl, onChange }: Props): State {
showingTermsOfService: { showingTermsOfService: {
value: showContent, value: showContent,
button: { button: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setShowContent(!showContent); setShowContent(!showContent);
}, }),
}, },
}, },
terms: state, terms: state,
termsAccepted: { termsAccepted: {
value: accepted, value: accepted,
button: { button: {
onClick: async () => { onClick: pushAlertOnError(async () => {
const newValue = !accepted; //toggle const newValue = !accepted; //toggle
onUpdate(newValue); await onUpdate(newValue);
setShowContent(false); setShowContent(false);
}, }),
}, },
}, },
}; };

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
// import { ReadyView } from "./views.js"; // import { ReadyView } from "./views.js";
export default { export default {
title: "TermsOfService", title: "TermsOfService",
}; };
// export const Ready = createExample(ReadyView, {}); // export const Ready = tests.createExample(ReadyView, {});

View File

@ -19,19 +19,26 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { TranslatedString } from "@gnu-taler/taler-util"; import { TalerErrorDetail, TranslatedString } from "@gnu-taler/taler-util";
import { ComponentChildren, createContext, h, VNode } from "preact"; import { ComponentChildren, createContext, h, VNode } from "preact";
import { useContext, useState } from "preact/hooks"; import { useContext, useState } from "preact/hooks";
import { HookError } from "../hooks/useAsyncAsHook.js";
import { SafeHandler, withSafe } from "../mui/handlers.js";
import { BackgroundError } from "../wxApi.js";
export type AlertType = "info" | "warning" | "error" | "success"; export type AlertType = "info" | "warning" | "error" | "success";
export interface Alert { export interface InfoAlert {
message: TranslatedString; message: TranslatedString;
description: TranslatedString | VNode; description: TranslatedString | VNode;
type: AlertType; type: "info" | "warning" | "success";
} }
export interface ErrorAlert extends Alert { export type Alert = InfoAlert | ErrorAlert;
export interface ErrorAlert {
message: TranslatedString;
description: TranslatedString | VNode;
type: "error"; type: "error";
context: object; context: object;
cause: any; cause: any;
@ -41,10 +48,14 @@ type Type = {
alerts: Alert[]; alerts: Alert[];
pushAlert: (n: Alert) => void; pushAlert: (n: Alert) => void;
removeAlert: (n: Alert) => void; removeAlert: (n: Alert) => void;
pushAlertOnError: <T>(h: (p: T) => Promise<void>) => SafeHandler<T>;
}; };
const initial: Type = { const initial: Type = {
alerts: [], alerts: [],
pushAlertOnError: () => {
throw Error("alert context not initialized");
},
pushAlert: () => { pushAlert: () => {
null; null;
}, },
@ -80,8 +91,17 @@ export const AlertProvider = ({ children }: Props): VNode => {
setAlerts((ns: AlertWithDate[]) => ns.filter((n) => n !== alert)); setAlerts((ns: AlertWithDate[]) => ns.filter((n) => n !== alert));
}; };
function pushAlertOnError<T>(
handler: (p: T) => Promise<void>,
): SafeHandler<T> {
return withSafe(handler, (e) => {
const a = alertFromError(e.message as TranslatedString, e);
pushAlert(a);
});
}
return h(Context.Provider, { return h(Context.Provider, {
value: { alerts, pushAlert, removeAlert }, value: { alerts, pushAlert, removeAlert, pushAlertOnError },
children, children,
}); });
}; };
@ -90,29 +110,71 @@ export const useAlertContext = (): Type => useContext(Context);
export function alertFromError( export function alertFromError(
message: TranslatedString, message: TranslatedString,
error: unknown, error: HookError,
...context: any[]
): ErrorAlert;
export function alertFromError(
message: TranslatedString,
error: Error,
...context: any[]
): ErrorAlert;
export function alertFromError(
message: TranslatedString,
error: TalerErrorDetail,
...context: any[]
): ErrorAlert;
export function alertFromError(
message: TranslatedString,
error: HookError | TalerErrorDetail | Error,
...context: any[] ...context: any[]
): ErrorAlert { ): ErrorAlert {
let description = "" as TranslatedString; let description: TranslatedString;
let cause: any;
const isObject = typeof error === "object" && if (typeof error === "object" && error !== null) {
error !== null; if ("code" in error) {
const hasMessage = //TalerErrorDetail
isObject && description = (error.hint ??
"message" in error && `Error code: ${error.code}`) as TranslatedString;
typeof error.message === "string"; cause = {
details: error,
if (hasMessage) { };
} else if ("hasError" in error) {
//HookError
description = error.message as TranslatedString; description = error.message as TranslatedString;
if (error.type === "taler") {
cause = {
details: error.details,
};
}
} else { } else {
description = `Unknown error: ${String(error)}` as TranslatedString; if (error instanceof BackgroundError) {
description = (error.errorDetail.hint ??
`Error code: ${error.errorDetail.code}`) as TranslatedString;
cause = {
details: error.errorDetail,
stack: error.stack,
};
} else {
description = error.message as TranslatedString;
cause = {
stack: error.stack,
};
}
}
} else {
description = "" as TranslatedString;
cause = error;
} }
return { return {
type: "error", type: "error",
message, message,
description, description,
cause: error, cause,
context, context,
}; };
} }

View File

@ -22,16 +22,15 @@
import { createContext, h, VNode } from "preact"; import { createContext, h, VNode } from "preact";
import { useContext } from "preact/hooks"; import { useContext } from "preact/hooks";
import { useWalletDevMode } from "../hooks/useWalletDevMode.js"; import { useWalletDevMode } from "../hooks/useWalletDevMode.js";
import { ToggleHandler } from "../mui/handlers.js";
interface Type { interface Type {
devMode: boolean; devMode: boolean;
devModeToggle: ToggleHandler; toggle: () => Promise<void>;
} }
const Context = createContext<Type>({ const Context = createContext<Type>({
devMode: false, devMode: false,
devModeToggle: { toggle: async () => {
button: {}, null;
}, },
}); });
@ -47,9 +46,8 @@ export const DevContextProviderForTesting = ({
return h(Context.Provider, { return h(Context.Provider, {
value: { value: {
devMode: !!value, devMode: !!value,
devModeToggle: { toggle: async () => {
value, null;
button: {},
}, },
}, },
children, children,
@ -58,7 +56,10 @@ export const DevContextProviderForTesting = ({
export const DevContextProvider = ({ children }: { children: any }): VNode => { export const DevContextProvider = ({ children }: { children: any }): VNode => {
const devModeToggle = useWalletDevMode(); const devModeToggle = useWalletDevMode();
const value: Type = { devMode: !!devModeToggle.value, devModeToggle }; const value: Type = {
devMode: !!devModeToggle.value,
toggle: devModeToggle.toggle,
};
//support for function as children, useful for getting the value right away //support for function as children, useful for getting the value right away
children = children =
children.length === 1 && typeof children === "function" children.length === 1 && typeof children === "function"

View File

@ -16,7 +16,7 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -29,6 +29,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const info = useAsyncAsHook(async () => { const info = useAsyncAsHook(async () => {
if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT"); if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT");
if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT"); if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT");
@ -66,7 +67,7 @@ export function useComponentState({
status: "ready", status: "ready",
error: undefined, error: undefined,
confirm: { confirm: {
onClick: doDeposit, onClick: pushAlertOnError(doDeposit),
}, },
fee: Amounts.sub(deposit.totalDepositCost, deposit.effectiveDepositAmount) fee: Amounts.sub(deposit.totalDepositCost, deposit.effectiveDepositAmount)
.amount, .amount,

View File

@ -20,14 +20,14 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "deposit", title: "deposit",
}; };
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
status: "ready", status: "ready",
confirm: {}, confirm: {},
cost: Amounts.parseOrThrow("EUR:1.2"), cost: Amounts.parseOrThrow("EUR:1.2"),

View File

@ -55,7 +55,7 @@ describe("Deposit CTA states", () => {
if (!error) expect.fail(); if (!error) expect.fail();
// if (!error.hasError) expect.fail(); // if (!error.hasError) expect.fail();
// if (error.operational) expect.fail(); // if (error.operational) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); expect(error.description).eq("ERROR_NO-URI-FOR-DEPOSIT");
}, },
], ],
TestingContext, TestingContext,

View File

@ -15,15 +15,11 @@
*/ */
/* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable react-hooks/rules-of-hooks */
import { import { Amounts, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
Amounts,
TalerErrorDetail,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { isFuture, parse } from "date-fns"; import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -71,6 +67,7 @@ export function useComponentState({
return () => { return () => {
const [subject, setSubject] = useState<string | undefined>(); const [subject, setSubject] = useState<string | undefined>();
const [timestamp, setTimestamp] = useState<string | undefined>(); const [timestamp, setTimestamp] = useState<string | undefined>();
const { pushAlertOnError } = useAlertContext();
const selectedExchange = useSelectedExchange({ const selectedExchange = useSelectedExchange({
currency: amount.currency, currency: amount.currency,
@ -144,7 +141,7 @@ export function useComponentState({
async function accept(): Promise<void> { async function accept(): Promise<void> {
if (!subject || !purse_expiration) return; if (!subject || !purse_expiration) return;
try {
const resp = await api.wallet.call( const resp = await api.wallet.call(
WalletApiOperation.InitiatePeerPullPayment, WalletApiOperation.InitiatePeerPullPayment,
{ {
@ -158,13 +155,6 @@ export function useComponentState({
); );
onSuccess(resp.transactionId); onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
// setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
} }
const unableToCreate = const unableToCreate =
!subject || Amounts.isZero(amount) || !purse_expiration; !subject || Amounts.isZero(amount) || !purse_expiration;
@ -179,22 +169,22 @@ export function useComponentState({
? "Can't be empty" ? "Can't be empty"
: undefined, : undefined,
value: subject ?? "", value: subject ?? "",
onInput: async (e) => setSubject(e), onInput: pushAlertOnError(async (e) => setSubject(e)),
}, },
expiration: { expiration: {
error: timestampError, error: timestampError,
value: timestamp === undefined ? "" : timestamp, value: timestamp === undefined ? "" : timestamp,
onInput: async (e) => { onInput: pushAlertOnError(async (e) => {
setTimestamp(e); setTimestamp(e);
}, }),
}, },
doSelectExchange: selectedExchange.doSelect, doSelectExchange: selectedExchange.doSelect,
exchangeUrl: exchange.exchangeBaseUrl, exchangeUrl: exchange.exchangeBaseUrl,
create: { create: {
onClick: unableToCreate ? undefined : accept, onClick: unableToCreate ? undefined : pushAlertOnError(accept),
}, },
cancel: { cancel: {
onClick: onClose, onClick: pushAlertOnError(onClose),
}, },
requestAmount, requestAmount,
toBeReceived, toBeReceived,

View File

@ -19,14 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "invoice create", title: "invoice create",
}; };
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
requestAmount: { requestAmount: {
currency: "ARS", currency: "ARS",
value: 1, value: 1,
@ -45,9 +46,7 @@ export const Ready = createExample(ReadyView, {
exchangeUrl: "https://exchange.taler.ar", exchangeUrl: "https://exchange.taler.ar",
subject: { subject: {
value: "some subject", value: "some subject",
onInput: async () => { onInput: nullFunction,
null;
},
}, },
create: {}, create: {},
}); });

View File

@ -61,7 +61,6 @@ export namespace State {
goToWalletManualWithdraw: (currency: string) => Promise<void>; goToWalletManualWithdraw: (currency: string) => Promise<void>;
summary: string | undefined; summary: string | undefined;
expiration: AbsoluteTime | undefined; expiration: AbsoluteTime | undefined;
operationError?: TalerErrorDetail;
payStatus: PreparePayResult; payStatus: PreparePayResult;
} }

View File

@ -25,10 +25,11 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { withSafe } from "../../mui/handlers.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
export function useComponentState({ export function useComponentState({
@ -39,6 +40,7 @@ export function useComponentState({
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, { const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, {
talerUri: talerPayPullUri, talerUri: talerPayPullUri,
@ -54,10 +56,6 @@ export function useComponentState({
), ),
); );
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
if (!hook) { if (!hook) {
return { return {
status: "loading", status: "loading",
@ -109,18 +107,17 @@ export function useComponentState({
contractTerms: {} as any, contractTerms: {} as any,
amountRaw: hook.response.p2p.amount, amountRaw: hook.response.p2p.amount,
noncePriv: "", noncePriv: "",
}; } as any; //FIXME: check this interface with new values
const baseResult = { const baseResult = {
uri: talerPayPullUri, uri: talerPayPullUri,
cancel: { cancel: {
onClick: onClose, onClick: pushAlertOnError(onClose),
}, },
amount, amount,
goToWalletManualWithdraw, goToWalletManualWithdraw,
summary, summary,
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined, expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
operationError,
}; };
if (!foundBalance) { if (!foundBalance) {
@ -148,7 +145,6 @@ export function useComponentState({
} }
async function accept(): Promise<void> { async function accept(): Promise<void> {
try {
const resp = await api.wallet.call( const resp = await api.wallet.call(
WalletApiOperation.AcceptPeerPullPayment, WalletApiOperation.AcceptPeerPullPayment,
{ {
@ -156,13 +152,6 @@ export function useComponentState({
}, },
); );
onSuccess(resp.transactionId); onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
} }
return { return {
@ -172,7 +161,7 @@ export function useComponentState({
payStatus: paymentPossible, payStatus: paymentPossible,
balance: foundAmount, balance: foundAmount,
accept: { accept: {
onClick: accept, onClick: pushAlertOnError(accept),
}, },
}; };
} }

View File

@ -20,14 +20,14 @@
*/ */
import { PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util"; import { PreparePayResult, PreparePayResultType } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "invoice payment", title: "invoice payment",
}; };
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
amount: { amount: {
currency: "ARS", currency: "ARS",
value: 1, value: 1,

View File

@ -16,11 +16,10 @@
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { LogoHeader } from "../../components/LogoHeader.js"; import { LogoHeader } from "../../components/LogoHeader.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { PaymentButtons } from "../../components/PaymentButtons.js"; import { PaymentButtons } from "../../components/PaymentButtons.js";
import { Link, SubTitle, WalletAction } from "../../components/styled/index.js"; import { SubTitle, WalletAction } from "../../components/styled/index.js";
import { Time } from "../../components/Time.js"; import { Time } from "../../components/Time.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { State } from "./index.js"; import { State } from "./index.js";
@ -29,29 +28,14 @@ export function ReadyView(
state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance, state: State.Ready | State.NoBalanceForCurrency | State.NoEnoughBalance,
): VNode { ): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { const { summary, amount, expiration, uri, status, balance, payStatus } =
operationError, state;
summary,
amount,
expiration,
uri,
status,
balance,
payStatus,
cancel,
} = state;
return ( return (
<WalletAction> <WalletAction>
<LogoHeader /> <LogoHeader />
<SubTitle> <SubTitle>
<i18n.Translate>Digital invoice</i18n.Translate> <i18n.Translate>Digital invoice</i18n.Translate>
</SubTitle> </SubTitle>
{operationError && (
<ErrorTalerOperation
title={i18n.str`Could not finish the payment operation`}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part title={i18n.str`Subject`} text={<div>{summary}</div>} /> <Part title={i18n.str`Subject`} text={<div>{summary}</div>} />
<Part title={i18n.str`Amount`} text={<Amount value={amount} />} /> <Part title={i18n.str`Amount`} text={<Amount value={amount} />} />

View File

@ -19,11 +19,10 @@ import {
ConfirmPayResultType, ConfirmPayResultType,
NotificationType, NotificationType,
PreparePayResultType, PreparePayResultType,
TalerErrorCode,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -36,7 +35,7 @@ export function useComponentState({
goToWalletManualWithdraw, goToWalletManualWithdraw,
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const [payErrMsg, setPayErrMsg] = useState<TalerError | undefined>(undefined); const { pushAlertOnError } = useAlertContext();
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -142,23 +141,27 @@ export function useComponentState({
} }
async function doPayment(): Promise<void> { async function doPayment(): Promise<void> {
try { // if (payStatus.status !== "payment-possible") {
if (payStatus.status !== "payment-possible") { // throw TalerError.fromUncheckedDetail({
throw TalerError.fromUncheckedDetail({ // code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR, // when: new Date().toISOString(),
hint: `payment is not possible: ${payStatus.status}`, // hint: `payment is not possible: ${payStatus.status}`,
}); // });
} // }
const res = await api.wallet.call(WalletApiOperation.ConfirmPay, { const res = await api.wallet.call(WalletApiOperation.ConfirmPay, {
proposalId: payStatus.proposalId, proposalId: payStatus.proposalId,
}); });
// handle confirm pay // handle confirm pay
if (res.type !== ConfirmPayResultType.Done) { if (res.type !== ConfirmPayResultType.Done) {
throw TalerError.fromUncheckedDetail({ // throw new BackgroundError("Could not confirm payment", res.lastError)
code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR, // // throw TalerError.fromUncheckedDetail({
hint: `could not confirm payment`, // // code: TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
payResult: res, // // when: new Date().toISOString(),
}); // // hint: `could not confirm payment`,
// // payResult: res,
// // });
onSuccess(res.transactionId);
return;
} }
const fu = res.contractTerms.fulfillment_url; const fu = res.contractTerms.fulfillment_url;
if (fu) { if (fu) {
@ -169,16 +172,10 @@ export function useComponentState({
} }
} }
onSuccess(res.transactionId); onSuccess(res.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setPayErrMsg(e);
}
}
} }
const payHandler: ButtonHandler = { const payHandler: ButtonHandler = {
onClick: payErrMsg ? undefined : doPayment, onClick: pushAlertOnError(doPayment),
error: payErrMsg,
}; };
// (payStatus.status === PreparePayResultType.PaymentPossible) // (payStatus.status === PreparePayResultType.PaymentPossible)

View File

@ -24,10 +24,11 @@ import {
MerchantContractTerms as ContractTerms, MerchantContractTerms as ContractTerms,
PreparePayResultType, PreparePayResultType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import merchantIcon from "../../../static-dev/merchant-icon.jpeg"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { createExample } from "../../test-utils.js";
import { BaseView } from "./views.js";
import beer from "../../../static-dev/beer.png"; import beer from "../../../static-dev/beer.png";
import merchantIcon from "../../../static-dev/merchant-icon.jpeg";
import { nullFunction } from "../../mui/handlers.js";
import { BaseView } from "./views.js";
export default { export default {
title: "payment", title: "payment",
@ -35,7 +36,7 @@ export default {
argTypes: {}, argTypes: {},
}; };
export const NoBalance = createExample(BaseView, { export const NoBalance = tests.createExample(BaseView, {
status: "no-balance-for-currency", status: "no-balance-for-currency",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -44,6 +45,7 @@ export const NoBalance = createExample(BaseView, {
uri: "", uri: "",
payStatus: { payStatus: {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
balanceDetails: {} as any,
talerUri: "taler://pay/..", talerUri: "taler://pay/..",
noncePriv: "", noncePriv: "",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
@ -61,7 +63,7 @@ export const NoBalance = createExample(BaseView, {
}, },
}); });
export const NoEnoughBalance = createExample(BaseView, { export const NoEnoughBalance = tests.createExample(BaseView, {
status: "no-enough-balance", status: "no-enough-balance",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -74,6 +76,7 @@ export const NoEnoughBalance = createExample(BaseView, {
uri: "", uri: "",
payStatus: { payStatus: {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
balanceDetails: {} as any,
talerUri: "taler://pay/..", talerUri: "taler://pay/..",
noncePriv: "", noncePriv: "",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
@ -91,7 +94,7 @@ export const NoEnoughBalance = createExample(BaseView, {
}, },
}); });
export const EnoughBalanceButRestricted = createExample(BaseView, { export const EnoughBalanceButRestricted = tests.createExample(BaseView, {
status: "no-enough-balance", status: "no-enough-balance",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -104,6 +107,7 @@ export const EnoughBalanceButRestricted = createExample(BaseView, {
uri: "", uri: "",
payStatus: { payStatus: {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
balanceDetails: {} as any,
talerUri: "taler://pay/..", talerUri: "taler://pay/..",
noncePriv: "", noncePriv: "",
proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0", proposalId: "96YY92RQZGF3V7TJSPN4SF9549QX7BRF88Q5PYFCSBNQ0YK4RPK0",
@ -121,7 +125,7 @@ export const EnoughBalanceButRestricted = createExample(BaseView, {
}, },
}); });
export const PaymentPossible = createExample(BaseView, { export const PaymentPossible = tests.createExample(BaseView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -131,9 +135,7 @@ export const PaymentPossible = createExample(BaseView, {
value: 11, value: 11,
}, },
payHandler: { payHandler: {
onClick: async () => { onClick: nullFunction,
null;
},
}, },
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0", uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -162,7 +164,7 @@ export const PaymentPossible = createExample(BaseView, {
}, },
}); });
export const PaymentPossibleWithFee = createExample(BaseView, { export const PaymentPossibleWithFee = tests.createExample(BaseView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -172,9 +174,7 @@ export const PaymentPossibleWithFee = createExample(BaseView, {
value: 11, value: 11,
}, },
payHandler: { payHandler: {
onClick: async () => { onClick: nullFunction,
null;
},
}, },
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0", uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -200,7 +200,7 @@ export const PaymentPossibleWithFee = createExample(BaseView, {
}, },
}); });
export const TicketWithAProductList = createExample(BaseView, { export const TicketWithAProductList = tests.createExample(BaseView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -210,9 +210,7 @@ export const TicketWithAProductList = createExample(BaseView, {
value: 11, value: 11,
}, },
payHandler: { payHandler: {
onClick: async () => { onClick: nullFunction,
null;
},
}, },
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0", uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -257,7 +255,7 @@ export const TicketWithAProductList = createExample(BaseView, {
}, },
}); });
export const TicketWithShipping = createExample(BaseView, { export const TicketWithShipping = tests.createExample(BaseView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),
@ -267,9 +265,7 @@ export const TicketWithShipping = createExample(BaseView, {
value: 11, value: 11,
}, },
payHandler: { payHandler: {
onClick: async () => { onClick: nullFunction,
null;
},
}, },
uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0", uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
@ -309,7 +305,7 @@ export const TicketWithShipping = createExample(BaseView, {
}, },
}); });
export const AlreadyConfirmedByOther = createExample(BaseView, { export const AlreadyConfirmedByOther = tests.createExample(BaseView, {
status: "confirmed", status: "confirmed",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:10"), amount: Amounts.parseOrThrow("USD:10"),

View File

@ -31,7 +31,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { mountHook, nullFunction } from "../../test-utils.js"; import { ErrorAlert, useAlertContext } from "../../context/alert.js";
import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js"; import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
@ -385,8 +386,12 @@ describe("Payment CTA states", () => {
} as ConfirmPayResult); } as ConfirmPayResult);
const hookBehavior = await tests.hookBehaveLikeThis( const hookBehavior = await tests.hookBehaveLikeThis(
useComponentState, () => {
props, const state = useComponentState(props);
// const { alerts } = useAlertContext();
return { ...state, alerts: {} };
},
{},
[ [
({ status, error }) => { ({ status, error }) => {
expect(status).equals("loading"); expect(status).equals("loading");
@ -400,22 +405,21 @@ describe("Payment CTA states", () => {
if (state.payHandler.onClick === undefined) expect.fail(); if (state.payHandler.onClick === undefined) expect.fail();
state.payHandler.onClick(); state.payHandler.onClick();
}, },
(state) => { // (state) => {
if (state.status !== "ready") expect.fail(); // if (state.status !== "ready") expect.fail();
expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15")); // expect(state.balance).deep.equal(Amounts.parseOrThrow("USD:15"));
expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9")); // expect(state.amount).deep.equal(Amounts.parseOrThrow("USD:9"));
// expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1"));
expect(state.payHandler.onClick).undefined; // // FIXME: check that the error is pushed to the alertContext
if (state.payHandler.error === undefined) expect.fail(); // // expect(state.alerts.length).eq(1);
//FIXME: error message here is bad // // const alert = state.alerts[0]
expect(state.payHandler.error.errorDetail.hint).eq( // // if (alert.type !== "error") expect.fail();
"could not confirm payment",
); // // expect(alert.cause.errorDetail.payResult).deep.equal({
expect(state.payHandler.error.errorDetail.payResult).deep.equal({ // // type: ConfirmPayResultType.Pending,
type: ConfirmPayResultType.Pending, // // lastError: { code: 1 },
lastError: { code: 1 }, // // });
}); // },
},
], ],
TestingContext, TestingContext,
); );

View File

@ -16,7 +16,7 @@
import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { parseRecoveryUri } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Alert } from "../../context/alert.js"; import { useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -27,6 +27,7 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
if (!talerRecoveryUri) { if (!talerRecoveryUri) {
return { return {
@ -67,10 +68,10 @@ export function useComponentState({
status: "ready", status: "ready",
accept: { accept: {
onClick: recoverBackup, onClick: pushAlertOnError(recoverBackup),
}, },
cancel: { cancel: {
onClick: onCancel, onClick: pushAlertOnError(onCancel),
}, },
error: undefined, error: undefined,
}; };

View File

@ -20,7 +20,7 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {

View File

@ -17,7 +17,7 @@
import { Amounts, NotificationType } from "@gnu-taler/taler-util"; import { Amounts, NotificationType } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -31,6 +31,7 @@ export function useComponentState({
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const [ignored, setIgnored] = useState(false); const [ignored, setIgnored] = useState(false);
const { pushAlertOnError } = useAlertContext();
const info = useAsyncAsHook(async () => { const info = useAsyncAsHook(async () => {
if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND"); if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND");
@ -108,10 +109,10 @@ export function useComponentState({
...baseInfo, ...baseInfo,
orderId: info.response.refund.info.orderId, orderId: info.response.refund.info.orderId,
accept: { accept: {
onClick: doAccept, onClick: pushAlertOnError(doAccept),
}, },
ignore: { ignore: {
onClick: doIgnore, onClick: pushAlertOnError(doIgnore),
}, },
cancel, cancel,
}; };

View File

@ -21,13 +21,13 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import beer from "../../../static-dev/beer.png"; import beer from "../../../static-dev/beer.png";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { IgnoredView, InProgressView, ReadyView } from "./views.js"; import { IgnoredView, InProgressView, ReadyView } from "./views.js";
export default { export default {
title: "refund", title: "refund",
}; };
export const InProgress = createExample(InProgressView, { export const InProgress = tests.createExample(InProgressView, {
status: "in-progress", status: "in-progress",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("USD:1"), amount: Amounts.parseOrThrow("USD:1"),
@ -37,7 +37,7 @@ export const InProgress = createExample(InProgressView, {
products: undefined, products: undefined,
}); });
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
accept: {}, accept: {},
@ -51,7 +51,7 @@ export const Ready = createExample(ReadyView, {
orderId: "abcdef", orderId: "abcdef",
}); });
export const WithAProductList = createExample(ReadyView, { export const WithAProductList = tests.createExample(ReadyView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
accept: {}, accept: {},
@ -75,7 +75,7 @@ export const WithAProductList = createExample(ReadyView, {
orderId: "abcdef", orderId: "abcdef",
}); });
export const Ignored = createExample(IgnoredView, { export const Ignored = tests.createExample(IgnoredView, {
status: "ignored", status: "ignored",
error: undefined, error: undefined,
merchantName: "the merchant", merchantName: "the merchant",

View File

@ -27,11 +27,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { import { nullFunction } from "../../mui/handlers.js";
createWalletApiMock, import { createWalletApiMock } from "../../test-utils.js";
mountHook,
nullFunction,
} from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
describe("Refund CTA states", () => { describe("Refund CTA states", () => {
@ -57,7 +54,7 @@ describe("Refund CTA states", () => {
if (!error) expect.fail(); if (!error) expect.fail();
// if (!error.hasError) expect.fail(); // if (!error.hasError) expect.fail();
// if (error.operational) expect.fail(); // if (error.operational) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-REFUND"); expect(error.description).eq("ERROR_NO-URI-FOR-REFUND");
}, },
], ],
TestingContext, TestingContext,

View File

@ -16,7 +16,7 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -29,6 +29,7 @@ export function useComponentState({
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const tipInfo = useAsyncAsHook(async () => { const tipInfo = useAsyncAsHook(async () => {
if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP");
const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { const tip = await api.wallet.call(WalletApiOperation.PrepareTip, {
@ -77,7 +78,7 @@ export function useComponentState({
amount: Amounts.parseOrThrow(tip.tipAmountEffective), amount: Amounts.parseOrThrow(tip.tipAmountEffective),
error: undefined, error: undefined,
cancel: { cancel: {
onClick: onCancel, onClick: pushAlertOnError(onCancel),
}, },
}; };
@ -92,7 +93,7 @@ export function useComponentState({
status: "ready", status: "ready",
...baseInfo, ...baseInfo,
accept: { accept: {
onClick: doAccept, onClick: pushAlertOnError(doAccept),
}, },
}; };
} }

View File

@ -20,14 +20,14 @@
*/ */
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { AcceptedView, ReadyView } from "./views.js"; import { AcceptedView, ReadyView } from "./views.js";
export default { export default {
title: "tip", title: "tip",
}; };
export const Accepted = createExample(AcceptedView, { export const Accepted = tests.createExample(AcceptedView, {
status: "accepted", status: "accepted",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("EUR:1"), amount: Amounts.parseOrThrow("EUR:1"),
@ -35,7 +35,7 @@ export const Accepted = createExample(AcceptedView, {
merchantBaseUrl: "", merchantBaseUrl: "",
}); });
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
status: "ready", status: "ready",
error: undefined, error: undefined,
amount: Amounts.parseOrThrow("EUR:1"), amount: Amounts.parseOrThrow("EUR:1"),

View File

@ -23,7 +23,8 @@ import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js"; import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { Props } from "./index.js"; import { Props } from "./index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
@ -48,7 +49,7 @@ describe("Tip CTA states", () => {
({ status, error }) => { ({ status, error }) => {
expect(status).equals("error"); expect(status).equals("error");
if (!error) expect.fail(); if (!error) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-TIP"); expect(error.description).eq("ERROR_NO-URI-FOR-TIP");
}, },
], ],
TestingContext, TestingContext,

View File

@ -54,7 +54,6 @@ export namespace State {
subject: TextFieldHandler; subject: TextFieldHandler;
expiration: TextFieldHandler; expiration: TextFieldHandler;
error: undefined; error: undefined;
operationError?: TalerErrorDetail;
} }
} }

View File

@ -22,7 +22,7 @@ import {
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { isFuture, parse } from "date-fns"; import { isFuture, parse } from "date-fns";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -34,16 +34,13 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const amount = Amounts.parseOrThrow(amountStr); const amount = Amounts.parseOrThrow(amountStr);
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const [subject, setSubject] = useState<string | undefined>(); const [subject, setSubject] = useState<string | undefined>();
const [timestamp, setTimestamp] = useState<string | undefined>(); const [timestamp, setTimestamp] = useState<string | undefined>();
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
const resp = await api.wallet.call( const resp = await api.wallet.call(
WalletApiOperation.PreparePeerPushPayment, WalletApiOperation.PreparePeerPushPayment,
@ -104,7 +101,6 @@ export function useComponentState({
async function accept(): Promise<void> { async function accept(): Promise<void> {
if (!subject || !purse_expiration) return; if (!subject || !purse_expiration) return;
try {
const resp = await api.wallet.call( const resp = await api.wallet.call(
WalletApiOperation.InitiatePeerPushPayment, WalletApiOperation.InitiatePeerPushPayment,
{ {
@ -116,13 +112,6 @@ export function useComponentState({
}, },
); );
onSuccess(resp.transactionId); onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
} }
const unableToCreate = const unableToCreate =
@ -131,7 +120,7 @@ export function useComponentState({
return { return {
status: "ready", status: "ready",
cancel: { cancel: {
onClick: onClose, onClick: pushAlertOnError(onClose),
}, },
subject: { subject: {
error: error:
@ -141,21 +130,20 @@ export function useComponentState({
? "Can't be empty" ? "Can't be empty"
: undefined, : undefined,
value: subject ?? "", value: subject ?? "",
onInput: async (e) => setSubject(e), onInput: pushAlertOnError(async (e) => setSubject(e)),
}, },
expiration: { expiration: {
error: timestampError, error: timestampError,
value: timestamp === undefined ? "" : timestamp, value: timestamp === undefined ? "" : timestamp,
onInput: async (e) => { onInput: pushAlertOnError(async (e) => {
setTimestamp(e); setTimestamp(e);
}, }),
}, },
create: { create: {
onClick: unableToCreate ? undefined : accept, onClick: unableToCreate ? undefined : pushAlertOnError(accept),
}, },
debitAmount, debitAmount,
toBeReceived, toBeReceived,
error: undefined, error: undefined,
operationError,
}; };
} }

View File

@ -19,14 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "transfer create", title: "transfer create",
}; };
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
debitAmount: { debitAmount: {
currency: "ARS", currency: "ARS",
value: 1, value: 1,
@ -44,8 +45,6 @@ export const Ready = createExample(ReadyView, {
}, },
subject: { subject: {
value: "the subject", value: "the subject",
onInput: async () => { onInput: nullFunction,
null;
},
}, },
}); });

View File

@ -32,8 +32,6 @@ export function ReadyView({
toBeReceived, toBeReceived,
debitAmount, debitAmount,
create, create,
operationError,
cancel,
}: State.Ready): VNode { }: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -65,12 +63,6 @@ export function ReadyView({
<SubTitle> <SubTitle>
<i18n.Translate>Digital cash transfer</i18n.Translate> <i18n.Translate>Digital cash transfer</i18n.Translate>
</SubTitle> </SubTitle>
{operationError && (
<ErrorTalerOperation
title={i18n.str`Could not finish the transfer creation`}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<p> <p>
<TextField <TextField

View File

@ -57,7 +57,6 @@ export namespace State {
expiration: AbsoluteTime | undefined; expiration: AbsoluteTime | undefined;
error: undefined; error: undefined;
accept: ButtonHandler; accept: ButtonHandler;
operationError?: TalerErrorDetail;
} }
} }

View File

@ -22,7 +22,7 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -34,15 +34,13 @@ export function useComponentState({
onSuccess, onSuccess,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(async () => { const hook = useAsyncAsHook(async () => {
return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, { return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, {
talerUri: talerPayPushUri, talerUri: talerPayPushUri,
}); });
}, []); }, []);
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>(undefined);
if (!hook) { if (!hook) {
return { return {
@ -74,7 +72,6 @@ export function useComponentState({
contractTerms?.purse_expiration; contractTerms?.purse_expiration;
async function accept(): Promise<void> { async function accept(): Promise<void> {
try {
const resp = await api.wallet.call( const resp = await api.wallet.call(
WalletApiOperation.AcceptPeerPushPayment, WalletApiOperation.AcceptPeerPushPayment,
{ {
@ -82,26 +79,18 @@ export function useComponentState({
}, },
); );
onSuccess(resp.transactionId); onSuccess(resp.transactionId);
} catch (e) {
if (e instanceof TalerError) {
setOperationError(e.errorDetail);
}
console.error(e);
throw Error("error trying to accept");
}
} }
return { return {
status: "ready", status: "ready",
amount: Amounts.parseOrThrow(amount), amount: Amounts.parseOrThrow(amount),
error: undefined, error: undefined,
accept: { accept: {
onClick: accept, onClick: pushAlertOnError(accept),
}, },
summary, summary,
expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined, expiration: expiration ? AbsoluteTime.fromTimestamp(expiration) : undefined,
cancel: { cancel: {
onClick: onClose, onClick: pushAlertOnError(onClose),
}, },
operationError,
}; };
} }

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "transfer pickup", title: "transfer pickup",
}; };
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
amount: { amount: {
currency: "ARS", currency: "ARS",
value: 1, value: 1,

View File

@ -30,8 +30,6 @@ export function ReadyView({
summary, summary,
expiration, expiration,
amount, amount,
cancel,
operationError,
}: State.Ready): VNode { }: State.Ready): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (
@ -40,12 +38,6 @@ export function ReadyView({
<SubTitle> <SubTitle>
<i18n.Translate>Digital cash transfer</i18n.Translate> <i18n.Translate>Digital cash transfer</i18n.Translate>
</SubTitle> </SubTitle>
{operationError && (
<ErrorTalerOperation
title={i18n.str`Could not finish the pickup operation`}
error={operationError}
/>
)}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part title={i18n.str`Subject`} text={<div>{summary}</div>} /> <Part title={i18n.str`Subject`} text={<div>{summary}</div>} />
<Part title={i18n.str`Amount`} text={<Amount value={amount} />} /> <Part title={i18n.str`Amount`} text={<Amount value={amount} />} />

View File

@ -23,7 +23,7 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -205,6 +205,7 @@ function exchangeSelectionState(
return () => { return () => {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const [ageRestricted, setAgeRestricted] = useState(0); const [ageRestricted, setAgeRestricted] = useState(0);
const currentExchange = selectedExchange.selected; const currentExchange = selectedExchange.selected;
const tosNeedToBeAccepted = const tosNeedToBeAccepted =
@ -299,7 +300,9 @@ function exchangeSelectionState(
? { ? {
list: ageRestrictionOptions, list: ageRestrictionOptions,
value: String(ageRestricted), value: String(ageRestricted),
onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), onChange: pushAlertOnError(async (v: string) =>
setAgeRestricted(parseInt(v, 10)),
),
} }
: undefined; : undefined;
@ -317,7 +320,7 @@ function exchangeSelectionState(
onClick: onClick:
doingWithdraw || tosNeedToBeAccepted doingWithdraw || tosNeedToBeAccepted
? undefined ? undefined
: doWithdrawAndCheckError, : pushAlertOnError(doWithdrawAndCheckError),
error: withdrawError, error: withdrawError,
}, },
onTosUpdate, onTosUpdate,

View File

@ -20,7 +20,8 @@
*/ */
import { ExchangeListItem } from "@gnu-taler/taler-util"; import { ExchangeListItem } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
// import { TermsState } from "../../utils/index.js"; // import { TermsState } from "../../utils/index.js";
import { SuccessView } from "./views.js"; import { SuccessView } from "./views.js";
@ -28,28 +29,6 @@ export default {
title: "withdraw", title: "withdraw",
}; };
const exchangeList = {
"exchange.demo.taler.net": "http://exchange.demo.taler.net (USD)",
"exchange.test.taler.net": "http://exchange.test.taler.net (KUDOS)",
};
const nullHandler = {
onClick: async (): Promise<void> => {
null;
},
};
// const normalTosState = {
// terms: {
// status: "accepted",
// version: "",
// } as TermsState,
// onAccept: () => null,
// onReview: () => null,
// reviewed: false,
// reviewing: false,
// };
const ageRestrictionOptions: Record<string, string> = "6:12:18" const ageRestrictionOptions: Record<string, string> = "6:12:18"
.split(":") .split(":")
.reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {}); .reduce((p, c) => ({ ...p, [c]: `under ${c}` }), {});
@ -61,7 +40,7 @@ const ageRestrictionSelectField = {
value: "0", value: "0",
}; };
export const TermsOfServiceNotYetLoaded = createExample(SuccessView, { export const TermsOfServiceNotYetLoaded = tests.createExample(SuccessView, {
error: undefined, error: undefined,
status: "success", status: "success",
chosenAmount: { chosenAmount: {
@ -69,7 +48,7 @@ export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
value: 2, value: 2,
fraction: 10000000, fraction: 10000000,
}, },
doWithdrawal: nullHandler, doWithdrawal: { onClick: nullFunction },
currentExchange: { currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {}, tos: {},
@ -87,7 +66,7 @@ export const TermsOfServiceNotYetLoaded = createExample(SuccessView, {
}, },
}); });
export const WithSomeFee = createExample(SuccessView, { export const WithSomeFee = tests.createExample(SuccessView, {
error: undefined, error: undefined,
status: "success", status: "success",
chosenAmount: { chosenAmount: {
@ -95,7 +74,7 @@ export const WithSomeFee = createExample(SuccessView, {
value: 2, value: 2,
fraction: 10000000, fraction: 10000000,
}, },
doWithdrawal: nullHandler, doWithdrawal: { onClick: nullFunction },
currentExchange: { currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {}, tos: {},
@ -113,7 +92,7 @@ export const WithSomeFee = createExample(SuccessView, {
doSelectExchange: {}, doSelectExchange: {},
}); });
export const WithoutFee = createExample(SuccessView, { export const WithoutFee = tests.createExample(SuccessView, {
error: undefined, error: undefined,
status: "success", status: "success",
chosenAmount: { chosenAmount: {
@ -121,7 +100,7 @@ export const WithoutFee = createExample(SuccessView, {
value: 2, value: 2,
fraction: 0, fraction: 0,
}, },
doWithdrawal: nullHandler, doWithdrawal: { onClick: nullFunction },
currentExchange: { currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {}, tos: {},
@ -139,7 +118,7 @@ export const WithoutFee = createExample(SuccessView, {
}, },
}); });
export const EditExchangeUntouched = createExample(SuccessView, { export const EditExchangeUntouched = tests.createExample(SuccessView, {
error: undefined, error: undefined,
status: "success", status: "success",
chosenAmount: { chosenAmount: {
@ -147,7 +126,7 @@ export const EditExchangeUntouched = createExample(SuccessView, {
value: 2, value: 2,
fraction: 10000000, fraction: 10000000,
}, },
doWithdrawal: nullHandler, doWithdrawal: { onClick: nullFunction },
currentExchange: { currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {}, tos: {},
@ -165,7 +144,7 @@ export const EditExchangeUntouched = createExample(SuccessView, {
}, },
}); });
export const EditExchangeModified = createExample(SuccessView, { export const EditExchangeModified = tests.createExample(SuccessView, {
error: undefined, error: undefined,
status: "success", status: "success",
chosenAmount: { chosenAmount: {
@ -173,7 +152,7 @@ export const EditExchangeModified = createExample(SuccessView, {
value: 2, value: 2,
fraction: 10000000, fraction: 10000000,
}, },
doWithdrawal: nullHandler, doWithdrawal: { onClick: nullFunction },
currentExchange: { currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {}, tos: {},
@ -191,7 +170,7 @@ export const EditExchangeModified = createExample(SuccessView, {
}, },
}); });
export const WithAgeRestriction = createExample(SuccessView, { export const WithAgeRestriction = tests.createExample(SuccessView, {
error: undefined, error: undefined,
status: "success", status: "success",
ageRestriction: ageRestrictionSelectField, ageRestriction: ageRestrictionSelectField,
@ -201,7 +180,7 @@ export const WithAgeRestriction = createExample(SuccessView, {
fraction: 10000000, fraction: 10000000,
}, },
doSelectExchange: {}, doSelectExchange: {},
doWithdrawal: nullHandler, doWithdrawal: { onClick: nullFunction },
currentExchange: { currentExchange: {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
tos: {}, tos: {},

View File

@ -28,7 +28,6 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { mountHook } from "../../test-utils.js";
import { createWalletApiMock } from "../../test-utils.js"; import { createWalletApiMock } from "../../test-utils.js";
import { useComponentStateFromURI } from "./state.js"; import { useComponentStateFromURI } from "./state.js";
@ -88,7 +87,7 @@ describe("Withdraw CTA states", () => {
if (!error) expect.fail(); if (!error) expect.fail();
// if (!error.hasError) expect.fail(); // if (!error.hasError) expect.fail();
// if (error.operational) expect.fail(); // if (error.operational) expect.fail();
expect(error.cause?.message).eq("ERROR_NO-URI-FOR-WITHDRAWAL"); expect(error.description).eq("ERROR_NO-URI-FOR-WITHDRAWAL");
}, },
], ],
TestingContext, TestingContext,

View File

@ -18,7 +18,6 @@ import { ExchangeTosStatus } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { ErrorTalerOperation } from "../../components/ErrorTalerOperation.js";
import { Part } from "../../components/Part.js"; import { Part } from "../../components/Part.js";
import { QR } from "../../components/QR.js"; import { QR } from "../../components/QR.js";
import { SelectList } from "../../components/SelectList.js"; import { SelectList } from "../../components/SelectList.js";
@ -36,13 +35,6 @@ export function SuccessView(state: State.Success): VNode {
state.currentExchange.tosStatus === ExchangeTosStatus.Accepted; state.currentExchange.tosStatus === ExchangeTosStatus.Accepted;
return ( return (
<Fragment> <Fragment>
{state.doWithdrawal.error && (
<ErrorTalerOperation
title={i18n.str`Could not finish the withdrawal operation`}
error={state.doWithdrawal.error.errorDetail}
/>
)}
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>
<Part <Part
title={ title={

View File

@ -16,7 +16,7 @@
import { TalerErrorDetail } from "@gnu-taler/taler-util"; import { TalerErrorDetail } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core"; import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useMemo, useState } from "preact/hooks"; import { useEffect, useMemo, useState } from "preact/hooks";
import { WalletError } from "../wxApi.js"; import { BackgroundError } from "../wxApi.js";
export interface HookOk<T> { export interface HookOk<T> {
hasError: false; hasError: false;
@ -74,12 +74,12 @@ export function useAsyncAsHook<T>(
message: e.message, message: e.message,
details: e.errorDetail, details: e.errorDetail,
}); });
} else if (e instanceof WalletError) { } else if (e instanceof BackgroundError) {
setHookResponse({ setHookResponse({
hasError: true, hasError: true,
type: "taler", type: "taler",
message: e.message, message: e.message,
details: e.errorDetail.errorDetail, details: e.errorDetail,
}); });
} else if (e instanceof Error) { } else if (e instanceof Error) {
setHookResponse({ setHookResponse({

View File

@ -14,23 +14,41 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { ToggleHandler } from "../mui/handlers.js"; import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/foreground.js"; import { platform } from "../platform/foreground.js";
export function useAutoOpenPermissions(): ToggleHandler { export function useAutoOpenPermissions(): ToggleHandler {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(false);
const [error, setError] = useState<TalerError | undefined>();
const toggle = async (): Promise<void> => { async function handleAutoOpenPerm(): Promise<void> {
return handleAutoOpenPerm(enabled, setEnabled, api.background).catch( if (!enabled) {
(e) => { // We set permissions here, since apparently FF wants this to be done
setError(TalerError.fromException(e)); // as the result of an input event ...
}, let granted: boolean;
); try {
}; granted = await platform.getPermissionsApi().requestHostPermissions();
} catch (lastError) {
setEnabled(false);
throw lastError;
}
const res = await api.background.call("toggleHeaderListener", granted);
setEnabled(res.newValue);
} else {
try {
await api.background
.call("toggleHeaderListener", false)
.then((r) => setEnabled(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}
useEffect(() => { useEffect(() => {
async function getValue(): Promise<void> { async function getValue(): Promise<void> {
@ -42,40 +60,11 @@ export function useAutoOpenPermissions(): ToggleHandler {
} }
getValue(); getValue();
}, []); }, []);
return { return {
value: enabled, value: enabled,
button: { button: {
onClick: toggle, onClick: pushAlertOnError(handleAutoOpenPerm),
error,
}, },
}; };
} }
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
// as the result of an input event ...
let granted: boolean;
try {
granted = await platform.getPermissionsApi().requestHostPermissions();
} catch (lastError) {
onChange(false);
throw lastError;
}
const res = await background.call("toggleHeaderListener", granted);
onChange(res.newValue);
} else {
try {
await background
.call("toggleHeaderListener", false)
.then((r) => onChange(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}

View File

@ -14,24 +14,42 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { ToggleHandler } from "../mui/handlers.js"; import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/foreground.js"; import { platform } from "../platform/foreground.js";
export function useClipboardPermissions(): ToggleHandler { export function useClipboardPermissions(): ToggleHandler {
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(false);
const [error, setError] = useState<TalerError | undefined>();
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const toggle = async (): Promise<void> => { async function handleClipboardPerm(): Promise<void> {
return handleClipboardPerm(enabled, setEnabled, api.background).catch( if (!enabled) {
(e) => { // We set permissions here, since apparently FF wants this to be done
setError(TalerError.fromException(e)); // as the result of an input event ...
}, let granted: boolean;
); try {
}; granted = await platform
.getPermissionsApi()
.requestClipboardPermissions();
} catch (lastError) {
setEnabled(false);
throw lastError;
}
setEnabled(granted);
} else {
try {
await api.background
.call("toggleHeaderListener", false)
.then((r) => setEnabled(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}
useEffect(() => { useEffect(() => {
async function getValue(): Promise<void> { async function getValue(): Promise<void> {
@ -47,38 +65,7 @@ export function useClipboardPermissions(): ToggleHandler {
return { return {
value: enabled, value: enabled,
button: { button: {
onClick: toggle, onClick: pushAlertOnError(handleClipboardPerm),
error,
}, },
}; };
} }
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
// as the result of an input event ...
let granted: boolean;
try {
granted = await platform
.getPermissionsApi()
.requestClipboardPermissions();
} catch (lastError) {
onChange(false);
throw lastError;
}
onChange(granted);
} else {
try {
await background
.call("toggleHeaderListener", false)
.then((r) => onChange(r.newValue));
} catch (e) {
console.log(e);
}
}
return;
}

View File

@ -16,6 +16,7 @@
import { ExchangeListItem } from "@gnu-taler/taler-util"; import { ExchangeListItem } from "@gnu-taler/taler-util";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAlertContext } from "../context/alert.js";
import { ButtonHandler } from "../mui/handlers.js"; import { ButtonHandler } from "../mui/handlers.js";
type State = State.Ready | State.NoExchange | State.Selecting; type State = State.Ready | State.NoExchange | State.Selecting;
@ -59,6 +60,7 @@ export function useSelectedExchange({
const [selectedExchange, setSelectedExchange] = useState<string | undefined>( const [selectedExchange, setSelectedExchange] = useState<string | undefined>(
undefined, undefined,
); );
const { pushAlertOnError } = useAlertContext();
if (!list.length) { if (!list.length) {
return { return {
@ -105,7 +107,7 @@ export function useSelectedExchange({
return { return {
status: "ready", status: "ready",
doSelect: { doSelect: {
onClick: async () => setIsSelecting(true), onClick: pushAlertOnError(async () => setIsSelecting(true)),
}, },
selected: found, selected: found,
}; };
@ -118,7 +120,7 @@ export function useSelectedExchange({
return { return {
status: "ready", status: "ready",
doSelect: { doSelect: {
onClick: async () => setIsSelecting(true), onClick: pushAlertOnError(async () => setIsSelecting(true)),
}, },
selected: found, selected: found,
}; };
@ -127,7 +129,7 @@ export function useSelectedExchange({
return { return {
status: "ready", status: "ready",
doSelect: { doSelect: {
onClick: async () => setIsSelecting(true), onClick: pushAlertOnError(async () => setIsSelecting(true)),
}, },
selected: listCurrency[0], selected: listCurrency[0],
}; };

View File

@ -13,11 +13,11 @@
You should have received a copy of the GNU General Public License along with 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/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useTalerActionURL } from "./useTalerActionURL.js";
import { mountHook } from "../test-utils.js";
import { IoCProviderForTesting } from "../context/iocContext.js";
import { h, VNode } from "preact";
import { expect } from "chai"; import { expect } from "chai";
import { h, VNode } from "preact";
import { IoCProviderForTesting } from "../context/iocContext.js";
import { useTalerActionURL } from "./useTalerActionURL.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
describe("useTalerActionURL hook", () => { describe("useTalerActionURL hook", () => {
it("should be set url to undefined when dismiss", async () => { it("should be set url to undefined when dismiss", async () => {
@ -31,32 +31,28 @@ describe("useTalerActionURL hook", () => {
}); });
}; };
const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = const hookBehavior = await tests.hookBehaveLikeThis(
mountHook(useTalerActionURL, ctx); useTalerActionURL,
{},
{ [
const [url] = pullLastResultOrThrow(); ([url]) => {
expect(url).undefined; expect(url).undefined;
} },
([url, setDismissed]) => {
expect(await waitForStateUpdate()).true;
{
const [url, setDismissed] = pullLastResultOrThrow();
expect(url).deep.equals({ expect(url).deep.equals({
location: "clipboard", location: "clipboard",
uri: "qwe", uri: "qwe",
}); });
setDismissed(true); setDismissed(true);
} },
([url]) => {
expect(await waitForStateUpdate()).true;
{
const [url] = pullLastResultOrThrow();
if (url !== undefined) throw Error("invalid"); if (url !== undefined) throw Error("invalid");
expect(url).undefined; expect(url).undefined;
} },
await assertNoPendingUpdate(); ],
ctx,
);
expect(hookBehavior).deep.equal({ result: "ok" });
}); });
}); });

View File

@ -14,22 +14,29 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { useState, useEffect } from "preact/hooks"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { ToggleHandler } from "../mui/handlers.js"; import { useEffect, useState } from "preact/hooks";
import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
export function useWalletDevMode(): ToggleHandler { type Result = {
const [enabled, setEnabled] = useState<undefined | boolean>(undefined); value: boolean | undefined;
const [error, setError] = useState<TalerError | undefined>(); toggle: () => Promise<void>;
const api = useBackendContext();
const toggle = async (): Promise<void> => {
return handleOpen(enabled, setEnabled, api).catch((e) => {
setError(TalerError.fromException(e));
});
}; };
export function useWalletDevMode(): Result {
const [enabled, setEnabled] = useState<undefined | boolean>(undefined);
const api = useBackendContext();
// const { pushAlertOnError } = useAlertContext();
async function handleOpen(): Promise<void> {
const nextValue = !enabled;
await api.wallet.call(WalletApiOperation.SetDevMode, {
devModeEnabled: nextValue,
});
setEnabled(nextValue);
return;
}
useEffect(() => { useEffect(() => {
async function getValue(): Promise<void> { async function getValue(): Promise<void> {
const res = await api.wallet.call(WalletApiOperation.GetVersion, {}); const res = await api.wallet.call(WalletApiOperation.GetVersion, {});
@ -37,24 +44,9 @@ export function useWalletDevMode(): ToggleHandler {
} }
getValue(); getValue();
}, []); }, []);
return { return {
value: enabled, value: enabled,
button: { toggle: handleOpen,
onClick: enabled === undefined ? undefined : toggle,
error,
},
}; };
} }
async function handleOpen(
currentValue: undefined | boolean,
onChange: (value: boolean) => void,
api: ReturnType<typeof useBackendContext>,
): Promise<void> {
const nextValue = !currentValue;
await api.wallet.call(WalletApiOperation.SetDevMode, {
devModeEnabled: nextValue,
});
onChange(nextValue);
return;
}

View File

@ -14,23 +14,51 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AmountJson } from "@gnu-taler/taler-util"; import { AmountJson } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
export interface TextFieldHandler { export interface TextFieldHandler {
onInput?: (value: string) => Promise<void>; onInput?: SafeHandler<string>;
value: string; value: string;
error?: string; error?: string;
} }
export interface AmountFieldHandler { export interface AmountFieldHandler {
onInput?: (value: AmountJson) => Promise<void>; onInput?: SafeHandler<AmountJson>;
value: AmountJson; value: AmountJson;
error?: string; error?: string;
} }
declare const __safe_handler: unique symbol;
export type SafeHandler<T> = {
<Req extends T>(req: Req): Promise<void>;
(): Promise<void>;
[__safe_handler]: true;
};
export function withSafe<T>(
handler: (p: T) => Promise<void>,
onError: (e: Error) => void,
): SafeHandler<T> {
const sh = async function (p: T): Promise<void> {
try {
await handler(p);
} catch (e) {
if (e instanceof Error) {
onError(e);
} else {
onError(new Error(String(e)));
}
}
};
return sh as SafeHandler<T>;
}
export const nullFunction = async function (): Promise<void> {
//do nothing
} as SafeHandler<void>;
export interface ButtonHandler { export interface ButtonHandler {
onClick?: () => Promise<void>; onClick?: SafeHandler<void>;
error?: TalerError; // error?: TalerError;
} }
export interface ToggleHandler { export interface ToggleHandler {
@ -39,7 +67,7 @@ export interface ToggleHandler {
} }
export interface SelectFieldHandler { export interface SelectFieldHandler {
onChange?: (value: string) => Promise<void>; onChange?: SafeHandler<string>;
error?: string; error?: string;
value: string; value: string;
isDirty?: boolean; isDirty?: boolean;

View File

@ -23,10 +23,10 @@
import { createHashHistory } from "history"; import { createHashHistory } from "history";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import Router, { route, Route } from "preact-router"; import Router, { route, Route } from "preact-router";
import { Match } from "preact-router/match";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import PendingTransactions from "../components/PendingTransactions.js"; import PendingTransactions from "../components/PendingTransactions.js";
import { PopupBox } from "../components/styled/index.js"; import { PopupBox } from "../components/styled/index.js";
import { AlertProvider } from "../context/alert.js";
import { DevContextProvider } from "../context/devContext.js"; import { DevContextProvider } from "../context/devContext.js";
import { IoCProviderForRuntime } from "../context/iocContext.js"; import { IoCProviderForRuntime } from "../context/iocContext.js";
import { import {
@ -34,7 +34,7 @@ import {
useTranslationContext, useTranslationContext,
} from "../context/translation.js"; } from "../context/translation.js";
import { useTalerActionURL } from "../hooks/useTalerActionURL.js"; import { useTalerActionURL } from "../hooks/useTalerActionURL.js";
import { PopupNavBarOptions, Pages, PopupNavBar } from "../NavigationBar.js"; import { Pages, PopupNavBar, PopupNavBarOptions } from "../NavigationBar.js";
import { platform } from "../platform/foreground.js"; import { platform } from "../platform/foreground.js";
import { BackupPage } from "../wallet/BackupPage.js"; import { BackupPage } from "../wallet/BackupPage.js";
import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js"; import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
@ -219,7 +219,9 @@ function PopupTemplate({
<PendingTransactions goToTransaction={goToTransaction} /> <PendingTransactions goToTransaction={goToTransaction} />
) : undefined} ) : undefined}
<PopupNavBar path={path} /> <PopupNavBar path={path} />
<PopupBox>{children}</PopupBox> <PopupBox>
<AlertProvider>{children}</AlertProvider>
</PopupBox>
</Fragment> </Fragment>
); );
} }

View File

@ -19,19 +19,19 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { BalanceView as TestedComponent } from "./BalancePage.js"; import { BalanceView as TestedComponent } from "./BalancePage.js";
export default { export default {
title: "balance", title: "balance",
}; };
export const EmptyBalance = createExample(TestedComponent, { export const EmptyBalance = tests.createExample(TestedComponent, {
balances: [], balances: [],
goToWalletManualWithdraw: {}, goToWalletManualWithdraw: {},
}); });
export const SomeCoins = createExample(TestedComponent, { export const SomeCoins = tests.createExample(TestedComponent, {
balances: [ balances: [
{ {
available: "USD:10.5", available: "USD:10.5",
@ -45,7 +45,7 @@ export const SomeCoins = createExample(TestedComponent, {
goToWalletManualWithdraw: {}, goToWalletManualWithdraw: {},
}); });
export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, { export const SomeCoinsInTreeCurrencies = tests.createExample(TestedComponent, {
balances: [ balances: [
{ {
available: "EUR:1", available: "EUR:1",
@ -73,7 +73,7 @@ export const SomeCoinsInTreeCurrencies = createExample(TestedComponent, {
addAction: {}, addAction: {},
}); });
export const NoCoinsInTreeCurrencies = createExample(TestedComponent, { export const NoCoinsInTreeCurrencies = tests.createExample(TestedComponent, {
balances: [ balances: [
{ {
available: "EUR:3", available: "EUR:3",
@ -101,7 +101,7 @@ export const NoCoinsInTreeCurrencies = createExample(TestedComponent, {
addAction: {}, addAction: {},
}); });
export const SomeCoinsInFiveCurrencies = createExample(TestedComponent, { export const SomeCoinsInFiveCurrencies = tests.createExample(TestedComponent, {
balances: [ balances: [
{ {
available: "USD:0", available: "USD:0",

View File

@ -22,7 +22,11 @@ import { BalanceTable } from "../components/BalanceTable.js";
import { ErrorAlertView } from "../components/CurrentAlerts.js"; import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { MultiActionButton } from "../components/MultiActionButton.js"; import { MultiActionButton } from "../components/MultiActionButton.js";
import { alertFromError, ErrorAlert } from "../context/alert.js"; import {
alertFromError,
ErrorAlert,
useAlertContext,
} from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -75,6 +79,7 @@ function useComponentState({
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const [addingAction, setAddingAction] = useState(false); const [addingAction, setAddingAction] = useState(false);
const state = useAsyncAsHook(() => const state = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.GetBalances, {}), api.wallet.call(WalletApiOperation.GetBalances, {}),
@ -104,7 +109,7 @@ function useComponentState({
status: "action", status: "action",
error: undefined, error: undefined,
cancel: { cancel: {
onClick: async () => setAddingAction(false), onClick: pushAlertOnError(async () => setAddingAction(false)),
}, },
}; };
} }
@ -113,10 +118,10 @@ function useComponentState({
error: undefined, error: undefined,
balances: state.response.balances, balances: state.response.balances,
addAction: { addAction: {
onClick: async () => setAddingAction(true), onClick: pushAlertOnError(async () => setAddingAction(true)),
}, },
goToWalletManualWithdraw: { goToWalletManualWithdraw: {
onClick: goToWalletManualWithdraw, onClick: pushAlertOnError(goToWalletManualWithdraw),
}, },
goToWalletDeposit, goToWalletDeposit,
goToWalletHistory, goToWalletHistory,

View File

@ -19,33 +19,33 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { TalerActionFound as TestedComponent } from "./TalerActionFound.js"; import { TalerActionFound as TestedComponent } from "./TalerActionFound.js";
export default { export default {
title: "TalerActionFound", title: "TalerActionFound",
}; };
export const PayAction = createExample(TestedComponent, { export const PayAction = tests.createExample(TestedComponent, {
url: "taler://pay/something", url: "taler://pay/something",
}); });
export const WithdrawalAction = createExample(TestedComponent, { export const WithdrawalAction = tests.createExample(TestedComponent, {
url: "taler://withdraw/something", url: "taler://withdraw/something",
}); });
export const TipAction = createExample(TestedComponent, { export const TipAction = tests.createExample(TestedComponent, {
url: "taler://tip/something", url: "taler://tip/something",
}); });
export const NotifyAction = createExample(TestedComponent, { export const NotifyAction = tests.createExample(TestedComponent, {
url: "taler://notify-reserve/something", url: "taler://notify-reserve/something",
}); });
export const RefundAction = createExample(TestedComponent, { export const RefundAction = tests.createExample(TestedComponent, {
url: "taler://refund/something", url: "taler://refund/something",
}); });
export const InvalidAction = createExample(TestedComponent, { export const InvalidAction = tests.createExample(TestedComponent, {
url: "taler://something/asd", url: "taler://something/asd",
}); });

View File

@ -20,15 +20,17 @@
*/ */
import { setupI18n } from "@gnu-taler/taler-util"; import { setupI18n } from "@gnu-taler/taler-util";
import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser"; import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
import { setupPlatform } from "./platform/foreground.js";
import chromeAPI from "./platform/chrome.js"; import chromeAPI from "./platform/chrome.js";
import { renderNodeOrBrowser } from "./test-utils.js"; import { setupPlatform } from "./platform/foreground.js";
import * as components from "./components/index.stories.js"; import * as components from "./components/index.stories.js";
import * as cta from "./cta/index.stories.js"; import * as cta from "./cta/index.stories.js";
import * as mui from "./mui/index.stories.js"; import * as mui from "./mui/index.stories.js";
import * as popup from "./popup/index.stories.js"; import * as popup from "./popup/index.stories.js";
import * as wallet from "./wallet/index.stories.js"; import * as wallet from "./wallet/index.stories.js";
import { renderNodeOrBrowser } from "./test-utils.js";
import { h, VNode } from "preact";
import { AlertProvider } from "./context/alert.js";
setupI18n("en", { en: {} }); setupI18n("en", { en: {} });
setupPlatform(chromeAPI); setupPlatform(chromeAPI);
@ -41,10 +43,15 @@ describe("All the examples:", () => {
describe(`Component ${component.name}:`, () => { describe(`Component ${component.name}:`, () => {
component.examples.forEach((example) => { component.examples.forEach((example) => {
it(`should render example: ${example.name}`, () => { it(`should render example: ${example.name}`, () => {
renderNodeOrBrowser( function C(): VNode {
example.render.component, const B = h(example.render.component, example.render.props);
example.render.props, //FIXME:
); //some components push the alter in the UI function
//that's not correct, should be moved into the sate function
// until then, we ran the tests with the alert provider
return h(AlertProvider, { children: B }, B);
}
renderNodeOrBrowser(C, {});
}); });
}); });
}); });

View File

@ -31,8 +31,10 @@ import {
VNode, VNode,
} from "preact"; } from "preact";
import { render as renderToString } from "preact-render-to-string"; import { render as renderToString } from "preact-render-to-string";
import { AlertProvider } from "./context/alert.js";
import { BackendProvider } from "./context/backend.js"; import { BackendProvider } from "./context/backend.js";
import { TranslationProvider } from "./context/translation.js"; import { TranslationProvider } from "./context/translation.js";
import { nullFunction } from "./mui/handlers.js";
import { BackgroundApiClient, wxApi } from "./wxApi.js"; import { BackgroundApiClient, wxApi } from "./wxApi.js";
// When doing tests we want the requestAnimationFrame to be as fast as possible. // When doing tests we want the requestAnimationFrame to be as fast as possible.
@ -218,7 +220,7 @@ export function mountHook<T extends object>(
}; };
} }
export const nullFunction: any = () => null; // export const nullFunction: any = () => null;
interface MockHandler { interface MockHandler {
addWalletCallResponse<Op extends WalletCoreOpKeys>( addWalletCallResponse<Op extends WalletCoreOpKeys>(
@ -365,6 +367,7 @@ export function createWalletApiMock(): {
children: ComponentChildren; children: ComponentChildren;
}): VNode { }): VNode {
let children = _cs; let children = _cs;
children = create(AlertProvider, { children }, children);
children = create(TranslationProvider, { children }, children); children = create(TranslationProvider, { children }, children);
return create( return create(
BackendProvider, BackendProvider,

View File

@ -53,7 +53,7 @@ export namespace State {
export interface ConfirmProvider { export interface ConfirmProvider {
status: "confirm-provider"; status: "confirm-provider";
error: undefined | TalerErrorDetail; error: undefined;
url: string; url: string;
provider: SyncTermsOfServiceResponse; provider: SyncTermsOfServiceResponse;
tos: ToggleHandler; tos: ToggleHandler;

View File

@ -14,16 +14,13 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { import { canonicalizeBaseUrl, Codec } from "@gnu-taler/taler-util";
canonicalizeBaseUrl,
Codec,
TalerErrorDetail,
} from "@gnu-taler/taler-util";
import { import {
codecForSyncTermsOfServiceResponse, codecForSyncTermsOfServiceResponse,
WalletApiOperation, WalletApiOperation,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { assertUnreachable } from "../../utils/index.js"; import { assertUnreachable } from "../../utils/index.js";
import { Props, State } from "./index.js"; import { Props, State } from "./index.js";
@ -152,17 +149,15 @@ export function useComponentState({
const [url, setHost] = useState<string | undefined>(); const [url, setHost] = useState<string | undefined>();
const [name, setName] = useState<string | undefined>(); const [name, setName] = useState<string | undefined>();
const [tos, setTos] = useState(false); const [tos, setTos] = useState(false);
const { pushAlertOnError } = useAlertContext();
const urlState = useUrlState( const urlState = useUrlState(
url, url,
"config", "config",
codecForSyncTermsOfServiceResponse(), codecForSyncTermsOfServiceResponse(),
); );
const [operationError, setOperationError] = useState<
TalerErrorDetail | undefined
>();
const [showConfirm, setShowConfirm] = useState(false); const [showConfirm, setShowConfirm] = useState(false);
async function addBackupProvider() { async function addBackupProvider(): Promise<void> {
if (!url || !name) return; if (!url || !name) return;
const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, { const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, {
@ -178,8 +173,6 @@ export function useComponentState({
} else { } else {
return onComplete(url); return onComplete(url);
} }
case "error":
return setOperationError(resp.error);
case "ok": case "ok":
return onComplete(url); return onComplete(url);
default: default:
@ -190,18 +183,18 @@ export function useComponentState({
if (showConfirm && urlState && urlState.status === "ok") { if (showConfirm && urlState && urlState.status === "ok") {
return { return {
status: "confirm-provider", status: "confirm-provider",
error: operationError, error: undefined,
onAccept: { onAccept: {
onClick: !tos ? undefined : addBackupProvider, onClick: !tos ? undefined : pushAlertOnError(addBackupProvider),
}, },
onCancel: { onCancel: {
onClick: onBack, onClick: pushAlertOnError(onBack),
}, },
provider: urlState.result, provider: urlState.result,
tos: { tos: {
value: tos, value: tos,
button: { button: {
onClick: async () => setTos(!tos), onClick: pushAlertOnError(async () => setTos(!tos)),
}, },
}, },
url: url ?? "", url: url ?? "",
@ -213,25 +206,25 @@ export function useComponentState({
error: undefined, error: undefined,
name: { name: {
value: name || "", value: name || "",
onInput: async (e) => setName(e), onInput: pushAlertOnError(async (e) => setName(e)),
error: error:
name === undefined ? undefined : !name ? "Can't be empty" : undefined, name === undefined ? undefined : !name ? "Can't be empty" : undefined,
}, },
onCancel: { onCancel: {
onClick: onBack, onClick: pushAlertOnError(onBack),
}, },
onConfirm: { onConfirm: {
onClick: onClick:
!urlState || urlState.status !== "ok" || !name !urlState || urlState.status !== "ok" || !name
? undefined ? undefined
: async () => { : pushAlertOnError(async () => {
setShowConfirm(true); setShowConfirm(true);
}, }),
}, },
urlOk: urlState?.status === "ok", urlOk: urlState?.status === "ok",
url: { url: {
value: url || "", value: url || "",
onInput: async (e) => setHost(e), onInput: pushAlertOnError(async (e) => setHost(e)),
error: errorString(urlState), error: errorString(urlState),
}, },
}; };

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ConfirmProviderView, SelectProviderView } from "./views.js"; import { ConfirmProviderView, SelectProviderView } from "./views.js";
export default { export default {
title: "add backup provider", title: "add backup provider",
}; };
export const DemoService = createExample(ConfirmProviderView, { export const DemoService = tests.createExample(ConfirmProviderView, {
url: "https://sync.demo.taler.net/", url: "https://sync.demo.taler.net/",
provider: { provider: {
annual_fee: "KUDOS:0.1", annual_fee: "KUDOS:0.1",
@ -40,7 +40,7 @@ export const DemoService = createExample(ConfirmProviderView, {
onCancel: {}, onCancel: {},
}); });
export const FreeService = createExample(ConfirmProviderView, { export const FreeService = tests.createExample(ConfirmProviderView, {
url: "https://sync.taler:9667/", url: "https://sync.taler:9667/",
provider: { provider: {
annual_fee: "ARS:0", annual_fee: "ARS:0",
@ -54,14 +54,14 @@ export const FreeService = createExample(ConfirmProviderView, {
onCancel: {}, onCancel: {},
}); });
export const Initial = createExample(SelectProviderView, { export const Initial = tests.createExample(SelectProviderView, {
url: { value: "" }, url: { value: "" },
name: { value: "" }, name: { value: "" },
onCancel: {}, onCancel: {},
onConfirm: {}, onConfirm: {},
}); });
export const WithValue = createExample(SelectProviderView, { export const WithValue = tests.createExample(SelectProviderView, {
url: { url: {
value: "sync.demo.taler.net", value: "sync.demo.taler.net",
}, },
@ -72,7 +72,7 @@ export const WithValue = createExample(SelectProviderView, {
onConfirm: {}, onConfirm: {},
}); });
export const WithConnectionError = createExample(SelectProviderView, { export const WithConnectionError = tests.createExample(SelectProviderView, {
url: { url: {
value: "sync.demo.taler.net", value: "sync.demo.taler.net",
error: "Network error", error: "Network error",
@ -84,7 +84,7 @@ export const WithConnectionError = createExample(SelectProviderView, {
onConfirm: {}, onConfirm: {},
}); });
export const WithClientError = createExample(SelectProviderView, { export const WithClientError = tests.createExample(SelectProviderView, {
url: { url: {
value: "sync.demo.taler.net", value: "sync.demo.taler.net",
error: "URL may not be right: (404) Not Found", error: "URL may not be right: (404) Not Found",
@ -96,7 +96,7 @@ export const WithClientError = createExample(SelectProviderView, {
onConfirm: {}, onConfirm: {},
}); });
export const WithServerError = createExample(SelectProviderView, { export const WithServerError = tests.createExample(SelectProviderView, {
url: { url: {
value: "sync.demo.taler.net", value: "sync.demo.taler.net",
error: "Try another server: (500) Internal Server Error", error: "Try another server: (500) Internal Server Error",

View File

@ -21,7 +21,8 @@
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js"; import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { Props } from "./index.js"; import { Props } from "./index.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { AddNewActionView as TestedComponent } from "./AddNewActionView.js"; import { AddNewActionView as TestedComponent } from "./AddNewActionView.js";
export default { export default {
@ -30,4 +30,4 @@ export default {
}, },
}; };
export const Initial = createExample(TestedComponent, {}); export const Initial = tests.createExample(TestedComponent, {});

View File

@ -25,14 +25,14 @@ import {
BackupView as TestedComponent, BackupView as TestedComponent,
ShowRecoveryInfo, ShowRecoveryInfo,
} from "./BackupPage.js"; } from "./BackupPage.js";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util"; import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
export default { export default {
title: "backup", title: "backup",
}; };
export const LotOfProviders = createExample(TestedComponent, { export const LotOfProviders = tests.createExample(TestedComponent, {
providers: [ providers: [
{ {
active: true, active: true,
@ -164,7 +164,7 @@ export const LotOfProviders = createExample(TestedComponent, {
], ],
}); });
export const OneProvider = createExample(TestedComponent, { export const OneProvider = tests.createExample(TestedComponent, {
providers: [ providers: [
{ {
active: true, active: true,
@ -190,10 +190,10 @@ export const OneProvider = createExample(TestedComponent, {
], ],
}); });
export const Empty = createExample(TestedComponent, { export const Empty = tests.createExample(TestedComponent, {
providers: [], providers: [],
}); });
export const Recovery = createExample(ShowRecoveryInfo, { export const Recovery = tests.createExample(ShowRecoveryInfo, {
info: "taler://recovery/ASLDKJASLKDJASD", info: "taler://recovery/ASLDKJASLKDJASD",
}); });

View File

@ -29,7 +29,7 @@ import {
} from "date-fns"; } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { AlertView } from "../components/CurrentAlerts.js"; import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { QR } from "../components/QR.js"; import { QR } from "../components/QR.js";
import { import {
@ -118,8 +118,8 @@ export function BackupPage({ onAddProvider }: Props): VNode {
} }
if (status.hasError) { if (status.hasError) {
return ( return (
<AlertView <ErrorAlertView
alert={alertFromError( error={alertFromError(
i18n.str`Could not load backup providers`, i18n.str`Could not load backup providers`,
status, status,
)} )}

View File

@ -25,7 +25,7 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -39,6 +39,7 @@ export function useComponentState({
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const { pushAlertOnError } = useAlertContext();
const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr); const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr);
const currency = parsed !== undefined ? parsed.currency : currencyStr; const currency = parsed !== undefined ? parsed.currency : currencyStr;
@ -130,9 +131,9 @@ export function useComponentState({
error: undefined, error: undefined,
currency, currency,
onAddAccount: { onAddAccount: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setAddingAccount(true); setAddingAccount(true);
}, }),
}, },
}; };
} }
@ -221,27 +222,27 @@ export function useComponentState({
currency, currency,
amount: { amount: {
value: amount, value: amount,
onInput: updateAmount, onInput: pushAlertOnError(updateAmount),
error: amountError, error: amountError,
}, },
onAddAccount: { onAddAccount: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setAddingAccount(true); setAddingAccount(true);
}, }),
}, },
account: { account: {
list: accountMap, list: accountMap,
value: stringifyPaytoUri(currentAccount), value: stringifyPaytoUri(currentAccount),
onChange: updateAccountFromList, onChange: pushAlertOnError(updateAccountFromList),
}, },
currentAccount, currentAccount,
cancelHandler: { cancelHandler: {
onClick: async () => { onClick: pushAlertOnError(async () => {
onCancel(currency); onCancel(currency);
}, }),
}, },
depositHandler: { depositHandler: {
onClick: unableToDeposit ? undefined : doSend, onClick: unableToDeposit ? undefined : pushAlertOnError(doSend),
}, },
totalFee, totalFee,
totalToDeposit, totalToDeposit,
@ -263,7 +264,7 @@ async function getFeeForAmount(
}); });
} }
export function labelForAccountType(id: string) { export function labelForAccountType(id: string): string {
switch (id) { switch (id) {
case "": case "":
return "Choose one"; return "Choose one";

View File

@ -19,26 +19,21 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { Amounts, DepositGroupFees } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { labelForAccountType } from "./state.js"; import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "deposit", title: "deposit",
}; };
// const ac = parsePaytoUri("payto://iban/ES8877998399652238")!; export const WithNoAccountForIBAN = tests.createExample(ReadyView, {
// const accountMap = createLabelsForBankAccount([ac]);
export const WithNoAccountForIBAN = createExample(ReadyView, {
status: "ready", status: "ready",
account: { account: {
list: {}, list: {},
value: "", value: "",
onChange: async () => { onChange: nullFunction,
null;
},
}, },
currentAccount: { currentAccount: {
isKnown: true, isKnown: true,
@ -49,31 +44,25 @@ export const WithNoAccountForIBAN = createExample(ReadyView, {
}, },
currency: "USD", currency: "USD",
amount: { amount: {
onInput: async () => { onInput: nullFunction,
null;
},
value: Amounts.parseOrThrow("USD:10"), value: Amounts.parseOrThrow("USD:10"),
}, },
onAddAccount: {}, onAddAccount: {},
cancelHandler: {}, cancelHandler: {},
depositHandler: { depositHandler: {
onClick: async () => { onClick: nullFunction,
return;
},
}, },
totalFee: Amounts.zeroOfCurrency("USD"), totalFee: Amounts.zeroOfCurrency("USD"),
totalToDeposit: Amounts.parseOrThrow("USD:10"), totalToDeposit: Amounts.parseOrThrow("USD:10"),
// onCalculateFee: alwaysReturnFeeToOne, // onCalculateFee: alwaysReturnFeeToOne,
}); });
export const WithIBANAccountTypeSelected = createExample(ReadyView, { export const WithIBANAccountTypeSelected = tests.createExample(ReadyView, {
status: "ready", status: "ready",
account: { account: {
list: { asdlkajsdlk: "asdlkajsdlk", qwerqwer: "qwerqwer" }, list: { asdlkajsdlk: "asdlkajsdlk", qwerqwer: "qwerqwer" },
value: "asdlkajsdlk", value: "asdlkajsdlk",
onChange: async () => { onChange: nullFunction,
null;
},
}, },
currentAccount: { currentAccount: {
isKnown: true, isKnown: true,
@ -84,31 +73,25 @@ export const WithIBANAccountTypeSelected = createExample(ReadyView, {
}, },
currency: "USD", currency: "USD",
amount: { amount: {
onInput: async () => { onInput: nullFunction,
null;
},
value: Amounts.parseOrThrow("USD:10"), value: Amounts.parseOrThrow("USD:10"),
}, },
onAddAccount: {}, onAddAccount: {},
cancelHandler: {}, cancelHandler: {},
depositHandler: { depositHandler: {
onClick: async () => { onClick: nullFunction,
return;
},
}, },
totalFee: Amounts.zeroOfCurrency("USD"), totalFee: Amounts.zeroOfCurrency("USD"),
totalToDeposit: Amounts.parseOrThrow("USD:10"), totalToDeposit: Amounts.parseOrThrow("USD:10"),
// onCalculateFee: alwaysReturnFeeToOne, // onCalculateFee: alwaysReturnFeeToOne,
}); });
export const NewBitcoinAccountTypeSelected = createExample(ReadyView, { export const NewBitcoinAccountTypeSelected = tests.createExample(ReadyView, {
status: "ready", status: "ready",
account: { account: {
list: {}, list: {},
value: "asdlkajsdlk", value: "asdlkajsdlk",
onChange: async () => { onChange: nullFunction,
null;
},
}, },
currentAccount: { currentAccount: {
isKnown: true, isKnown: true,
@ -120,16 +103,12 @@ export const NewBitcoinAccountTypeSelected = createExample(ReadyView, {
onAddAccount: {}, onAddAccount: {},
currency: "USD", currency: "USD",
amount: { amount: {
onInput: async () => { onInput: nullFunction,
null;
},
value: Amounts.parseOrThrow("USD:10"), value: Amounts.parseOrThrow("USD:10"),
}, },
cancelHandler: {}, cancelHandler: {},
depositHandler: { depositHandler: {
onClick: async () => { onClick: nullFunction,
return;
},
}, },
totalFee: Amounts.zeroOfCurrency("USD"), totalFee: Amounts.zeroOfCurrency("USD"),
totalToDeposit: Amounts.parseOrThrow("USD:10"), totalToDeposit: Amounts.parseOrThrow("USD:10"),

View File

@ -28,7 +28,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js"; import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";

View File

@ -17,7 +17,7 @@
import { Amounts } from "@gnu-taler/taler-util"; import { Amounts } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -26,6 +26,7 @@ import { Contact, Props, State } from "./index.js";
export function useComponentState(props: Props): RecursiveState<State> { export function useComponentState(props: Props): RecursiveState<State> {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const parsedInitialAmount = !props.amount const parsedInitialAmount = !props.amount
? undefined ? undefined
: Amounts.parse(props.amount); : Amounts.parse(props.amount);
@ -108,26 +109,26 @@ export function useComponentState(props: Props): RecursiveState<State> {
error: undefined, error: undefined,
previous, previous,
selectCurrency: { selectCurrency: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setAmount(undefined); setAmount(undefined);
}, }),
}, },
goToBank: { goToBank: {
onClick: invalid onClick: invalid
? undefined ? undefined
: async () => { : pushAlertOnError(async () => {
props.goToWalletBankDeposit(currencyAndAmount); props.goToWalletBankDeposit(currencyAndAmount);
}, }),
}, },
goToWallet: { goToWallet: {
onClick: invalid onClick: invalid
? undefined ? undefined
: async () => { : pushAlertOnError(async () => {
props.goToWalletWalletSend(currencyAndAmount); props.goToWalletWalletSend(currencyAndAmount);
}, }),
}, },
amountHandler: { amountHandler: {
onInput: async (s) => setAmount(s), onInput: pushAlertOnError(async (s) => setAmount(s)),
value: amount, value: amount,
}, },
type: props.type, type: props.type,
@ -138,26 +139,26 @@ export function useComponentState(props: Props): RecursiveState<State> {
error: undefined, error: undefined,
previous, previous,
selectCurrency: { selectCurrency: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setAmount(undefined); setAmount(undefined);
}, }),
}, },
goToBank: { goToBank: {
onClick: invalid onClick: invalid
? undefined ? undefined
: async () => { : pushAlertOnError(async () => {
props.goToWalletManualWithdraw(currencyAndAmount); props.goToWalletManualWithdraw(currencyAndAmount);
}, }),
}, },
goToWallet: { goToWallet: {
onClick: invalid onClick: invalid
? undefined ? undefined
: async () => { : pushAlertOnError(async () => {
props.goToWalletWalletInvoice(currencyAndAmount); props.goToWalletWalletInvoice(currencyAndAmount);
}, }),
}, },
amountHandler: { amountHandler: {
onInput: async (s) => setAmount(s), onInput: pushAlertOnError(async (s) => setAmount(s)),
value: amount, value: amount,
}, },
type: props.type, type: props.type,

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView, SelectCurrencyView } from "./views.js"; import { ReadyView, SelectCurrencyView } from "./views.js";
export default { export default {
title: "destination", title: "destination",
}; };
export const GetCash = createExample(ReadyView, { export const GetCash = tests.createExample(ReadyView, {
amountHandler: { amountHandler: {
value: { value: {
currency: "EUR", currency: "EUR",
@ -40,7 +40,7 @@ export const GetCash = createExample(ReadyView, {
selectCurrency: {}, selectCurrency: {},
type: "get", type: "get",
}); });
export const SendCash = createExample(ReadyView, { export const SendCash = tests.createExample(ReadyView, {
amountHandler: { amountHandler: {
value: { value: {
currency: "EUR", currency: "EUR",
@ -55,7 +55,7 @@ export const SendCash = createExample(ReadyView, {
type: "send", type: "send",
}); });
export const SelectCurrency = createExample(SelectCurrencyView, { export const SelectCurrency = tests.createExample(SelectCurrencyView, {
currencies: { currencies: {
"": "Select a currency", "": "Select a currency",
USD: "USD", USD: "USD",

View File

@ -28,7 +28,8 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai"; import { expect } from "chai";
import { tests } from "../../../../web-util/src/index.browser.js"; import { tests } from "../../../../web-util/src/index.browser.js";
import { createWalletApiMock, nullFunction } from "../../test-utils.js"; import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js"; import { useComponentState } from "./state.js";
const exchangeArs: ExchangeListItem = { const exchangeArs: ExchangeListItem = {

View File

@ -20,7 +20,7 @@
*/ */
import { PendingTaskType } from "@gnu-taler/taler-wallet-core"; import { PendingTaskType } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { View as TestedComponent } from "./DeveloperPage.js"; import { View as TestedComponent } from "./DeveloperPage.js";
export default { export default {
@ -31,7 +31,7 @@ export default {
}, },
}; };
export const AllOff = createExample(TestedComponent, { export const AllOff = tests.createExample(TestedComponent, {
onDownloadDatabase: async () => "this is the content of the database", onDownloadDatabase: async () => "this is the content of the database",
operations: [ operations: [
{ {

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "example", title: "example",
}; };
export const Ready = createExample(ReadyView, {}); export const Ready = tests.createExample(ReadyView, {});

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ExchangeAddConfirmPage as TestedComponent } from "./ExchangeAddConfirm.js"; import { ExchangeAddConfirmPage as TestedComponent } from "./ExchangeAddConfirm.js";
export default { export default {
@ -32,14 +32,14 @@ export default {
}, },
}; };
export const TermsNotFound = createExample(TestedComponent, { export const TermsNotFound = tests.createExample(TestedComponent, {
url: "https://exchange.demo.taler.net/", url: "https://exchange.demo.taler.net/",
}); });
export const NewTerms = createExample(TestedComponent, { export const NewTerms = tests.createExample(TestedComponent, {
url: "https://exchange.demo.taler.net/", url: "https://exchange.demo.taler.net/",
}); });
export const TermsChanged = createExample(TestedComponent, { export const TermsChanged = tests.createExample(TestedComponent, {
url: "https://exchange.demo.taler.net/", url: "https://exchange.demo.taler.net/",
}); });

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { queryToSlashKeys } from "../utils/index.js"; import { queryToSlashKeys } from "../utils/index.js";
import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl.js"; import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl.js";
@ -27,17 +27,17 @@ export default {
title: "exchange add set url", title: "exchange add set url",
}; };
export const ExpectedUSD = createExample(TestedComponent, { export const ExpectedUSD = tests.createExample(TestedComponent, {
expectedCurrency: "USD", expectedCurrency: "USD",
onVerify: queryToSlashKeys, onVerify: queryToSlashKeys,
}); });
export const ExpectedKUDOS = createExample(TestedComponent, { export const ExpectedKUDOS = tests.createExample(TestedComponent, {
expectedCurrency: "KUDOS", expectedCurrency: "KUDOS",
onVerify: queryToSlashKeys, onVerify: queryToSlashKeys,
}); });
export const InitialState = createExample(TestedComponent, { export const InitialState = tests.createExample(TestedComponent, {
onVerify: queryToSlashKeys, onVerify: queryToSlashKeys,
}); });
@ -55,7 +55,7 @@ const knownExchanges = [
}, },
]; ];
export const WithDemoAsKnownExchange = createExample(TestedComponent, { export const WithDemoAsKnownExchange = tests.createExample(TestedComponent, {
onVerify: async (url) => { onVerify: async (url) => {
const found = const found =
knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1; knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;

View File

@ -20,7 +20,7 @@ import {
WalletApiOperation, WalletApiOperation,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -33,6 +33,7 @@ export function useComponentState({
currentExchange, currentExchange,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const initialValue = exchanges.findIndex( const initialValue = exchanges.findIndex(
(e) => e.exchangeBaseUrl === currentExchange, (e) => e.exchangeBaseUrl === currentExchange,
@ -115,7 +116,7 @@ export function useComponentState({
status: "showing-privacy", status: "showing-privacy",
error: undefined, error: undefined,
onClose: { onClose: {
onClick: async () => setShowingPrivacy(undefined), onClick: pushAlertOnError(async () => setShowingPrivacy(undefined)),
}, },
exchangeUrl: showingPrivacy, exchangeUrl: showingPrivacy,
}; };
@ -125,7 +126,7 @@ export function useComponentState({
status: "showing-tos", status: "showing-tos",
error: undefined, error: undefined,
onClose: { onClose: {
onClick: async () => setShowingTos(undefined), onClick: pushAlertOnError(async () => setShowingTos(undefined)),
}, },
exchangeUrl: showingTos, exchangeUrl: showingTos,
}; };
@ -138,24 +139,24 @@ export function useComponentState({
exchanges: { exchanges: {
list: exchangeMap, list: exchangeMap,
value: value, value: value,
onChange: async (v) => { onChange: pushAlertOnError(async (v) => {
setValue(v); setValue(v);
}, }),
}, },
error: undefined, error: undefined,
onClose: { onClose: {
onClick: onCancel, onClick: pushAlertOnError(onCancel),
}, },
selected, selected,
onShowPrivacy: { onShowPrivacy: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setShowingPrivacy(selected.exchangeBaseUrl); setShowingPrivacy(selected.exchangeBaseUrl);
}, }),
}, },
onShowTerms: { onShowTerms: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setShowingTos(selected.exchangeBaseUrl); setShowingTos(selected.exchangeBaseUrl);
}, }),
}, },
}; };
} }
@ -215,30 +216,30 @@ export function useComponentState({
exchanges: { exchanges: {
list: exchangeMap, list: exchangeMap,
value: value, value: value,
onChange: async (v) => { onChange: pushAlertOnError(async (v) => {
setValue(v); setValue(v);
}, }),
}, },
error: undefined, error: undefined,
onReset: { onReset: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setValue(String(initialValue)); setValue(String(initialValue));
}, }),
}, },
onSelect: { onSelect: {
onClick: async () => { onClick: pushAlertOnError(async () => {
onSelection(selected.exchangeBaseUrl); onSelection(selected.exchangeBaseUrl);
}, }),
}, },
onShowPrivacy: { onShowPrivacy: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setShowingPrivacy(selected.exchangeBaseUrl); setShowingPrivacy(selected.exchangeBaseUrl);
}, }),
}, },
onShowTerms: { onShowTerms: {
onClick: async () => { onClick: pushAlertOnError(async () => {
setShowingTos(selected.exchangeBaseUrl); setShowingTos(selected.exchangeBaseUrl);
}, }),
}, },
selected, selected,
coinOperationTimeline, coinOperationTimeline,

View File

@ -19,14 +19,14 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ComparingView, ReadyView } from "./views.js"; import { ComparingView, ReadyView } from "./views.js";
export default { export default {
title: "select exchange", title: "select exchange",
}; };
export const Bitcoin1 = createExample(ReadyView, { export const Bitcoin1 = tests.createExample(ReadyView, {
exchanges: { exchanges: {
list: { "0": "https://exchange.taler.ar" }, list: { "0": "https://exchange.taler.ar" },
value: "0", value: "0",
@ -43,7 +43,7 @@ export const Bitcoin1 = createExample(ReadyView, {
onShowTerms: {}, onShowTerms: {},
onClose: {}, onClose: {},
}); });
export const Bitcoin2 = createExample(ReadyView, { export const Bitcoin2 = tests.createExample(ReadyView, {
exchanges: { exchanges: {
list: { list: {
"https://exchange.taler.ar": "https://exchange.taler.ar", "https://exchange.taler.ar": "https://exchange.taler.ar",
@ -64,7 +64,7 @@ export const Bitcoin2 = createExample(ReadyView, {
onClose: {}, onClose: {},
}); });
export const Kudos1 = createExample(ReadyView, { export const Kudos1 = tests.createExample(ReadyView, {
exchanges: { exchanges: {
list: { list: {
"https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar", "https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar",
@ -83,7 +83,7 @@ export const Kudos1 = createExample(ReadyView, {
onShowTerms: {}, onShowTerms: {},
onClose: {}, onClose: {},
}); });
export const Kudos2 = createExample(ReadyView, { export const Kudos2 = tests.createExample(ReadyView, {
exchanges: { exchanges: {
list: { list: {
"https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar", "https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar",
@ -103,7 +103,7 @@ export const Kudos2 = createExample(ReadyView, {
onShowTerms: {}, onShowTerms: {},
onClose: {}, onClose: {},
}); });
export const ComparingBitcoin = createExample(ComparingView, { export const ComparingBitcoin = tests.createExample(ComparingView, {
exchanges: { exchanges: {
list: { "http://exchange": "http://exchange" }, list: { "http://exchange": "http://exchange" },
value: "http://exchange", value: "http://exchange",
@ -131,7 +131,7 @@ export const ComparingBitcoin = createExample(ComparingView, {
missingWireTYpe: [], missingWireTYpe: [],
wireFeeTimeline: {}, wireFeeTimeline: {},
}); });
export const ComparingKudos = createExample(ComparingView, { export const ComparingKudos = tests.createExample(ComparingView, {
exchanges: { exchanges: {
list: { "http://exchange": "http://exchange" }, list: { "http://exchange": "http://exchange" },
value: "http://exchange", value: "http://exchange",

View File

@ -37,7 +37,7 @@ import {
WithdrawalType, WithdrawalType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { HistoryView as TestedComponent } from "./History.js"; import { HistoryView as TestedComponent } from "./History.js";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
export default { export default {
title: "balance", title: "balance",
@ -160,12 +160,14 @@ const exampleData = {
} as TransactionPeerPullDebit, } as TransactionPeerPullDebit,
}; };
export const NoBalance = createExample(TestedComponent, { export const NoBalance = tests.createExample(TestedComponent, {
transactions: [], transactions: [],
balances: [], balances: [],
}); });
export const SomeBalanceWithNoTransactions = createExample(TestedComponent, { export const SomeBalanceWithNoTransactions = tests.createExample(
TestedComponent,
{
transactions: [], transactions: [],
balances: [ balances: [
{ {
@ -176,9 +178,10 @@ export const SomeBalanceWithNoTransactions = createExample(TestedComponent, {
requiresUserInput: false, requiresUserInput: false,
}, },
], ],
}); },
);
export const OneSimpleTransaction = createExample(TestedComponent, { export const OneSimpleTransaction = tests.createExample(TestedComponent, {
transactions: [exampleData.withdraw], transactions: [exampleData.withdraw],
balances: [ balances: [
{ {
@ -191,7 +194,9 @@ export const OneSimpleTransaction = createExample(TestedComponent, {
], ],
}); });
export const TwoTransactionsAndZeroBalance = createExample(TestedComponent, { export const TwoTransactionsAndZeroBalance = tests.createExample(
TestedComponent,
{
transactions: [exampleData.withdraw, exampleData.deposit], transactions: [exampleData.withdraw, exampleData.deposit],
balances: [ balances: [
{ {
@ -202,9 +207,10 @@ export const TwoTransactionsAndZeroBalance = createExample(TestedComponent, {
requiresUserInput: false, requiresUserInput: false,
}, },
], ],
}); },
);
export const OneTransactionPending = createExample(TestedComponent, { export const OneTransactionPending = tests.createExample(TestedComponent, {
transactions: [ transactions: [
{ {
...exampleData.withdraw, ...exampleData.withdraw,
@ -222,7 +228,7 @@ export const OneTransactionPending = createExample(TestedComponent, {
], ],
}); });
export const SomeTransactions = createExample(TestedComponent, { export const SomeTransactions = tests.createExample(TestedComponent, {
transactions: [ transactions: [
exampleData.withdraw, exampleData.withdraw,
exampleData.payment, exampleData.payment,
@ -251,7 +257,7 @@ export const SomeTransactions = createExample(TestedComponent, {
], ],
}); });
export const SomeTransactionsWithTwoCurrencies = createExample( export const SomeTransactionsWithTwoCurrencies = tests.createExample(
TestedComponent, TestedComponent,
{ {
transactions: [ transactions: [
@ -283,7 +289,7 @@ export const SomeTransactionsWithTwoCurrencies = createExample(
}, },
); );
export const FiveOfficialCurrencies = createExample(TestedComponent, { export const FiveOfficialCurrencies = tests.createExample(TestedComponent, {
transactions: [exampleData.withdraw], transactions: [exampleData.withdraw],
balances: [ balances: [
{ {
@ -324,7 +330,7 @@ export const FiveOfficialCurrencies = createExample(TestedComponent, {
], ],
}); });
export const FiveOfficialCurrenciesWithHighValue = createExample( export const FiveOfficialCurrenciesWithHighValue = tests.createExample(
TestedComponent, TestedComponent,
{ {
transactions: [exampleData.withdraw], transactions: [exampleData.withdraw],
@ -368,7 +374,7 @@ export const FiveOfficialCurrenciesWithHighValue = createExample(
}, },
); );
export const PeerToPeer = createExample(TestedComponent, { export const PeerToPeer = tests.createExample(TestedComponent, {
transactions: [ transactions: [
exampleData.pull_credit, exampleData.pull_credit,
exampleData.pull_debit, exampleData.pull_debit,

View File

@ -23,7 +23,7 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { AlertView } from "../components/CurrentAlerts.js"; import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { import {
CenteredBoldText, CenteredBoldText,
@ -33,7 +33,7 @@ import {
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { TransactionItem } from "../components/TransactionItem.js"; import { TransactionItem } from "../components/TransactionItem.js";
import { alertFromError } from "../context/alert.js"; import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
@ -72,8 +72,8 @@ export function HistoryPage({
if (state.hasError) { if (state.hasError) {
return ( return (
<AlertView <ErrorAlertView
alert={alertFromError( error={alertFromError(
i18n.str`Could not load the list of transactions`, i18n.str`Could not load the list of transactions`,
state, state,
)} )}
@ -112,6 +112,7 @@ export function HistoryView({
}): VNode { }): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const currencies = balances.map((b) => b.available.split(":")[0]); const currencies = balances.map((b) => b.available.split(":")[0]);
const { pushAlertOnError } = useAlertContext();
const defaultCurrencyIndex = currencies.findIndex( const defaultCurrencyIndex = currencies.findIndex(
(c) => c === defaultCurrency, (c) => c === defaultCurrency,
@ -145,7 +146,7 @@ export function HistoryView({
return ( return (
<NoBalanceHelp <NoBalanceHelp
goToWalletManualWithdraw={{ goToWalletManualWithdraw={{
onClick: goToWalletManualWithdraw, onClick: pushAlertOnError(goToWalletManualWithdraw),
}} }}
/> />
); );

View File

@ -21,7 +21,7 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js"; import { useTranslationContext } from "../../context/translation.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -33,6 +33,7 @@ export function useComponentState({
onCancel, onCancel,
}: Props): State { }: Props): State {
const api = useBackendContext(); const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(() => const hook = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }), api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }),
@ -109,30 +110,30 @@ export function useComponentState({
accountType: { accountType: {
list: accountType, list: accountType,
value: type, value: type,
onChange: async (v) => { onChange: pushAlertOnError(async (v) => {
setType(v); setType(v);
}, }),
}, },
alias: { alias: {
value: alias, value: alias,
onInput: async (v) => { onInput: pushAlertOnError(async (v) => {
setAlias(v); setAlias(v);
}, }),
}, },
uri: { uri: {
value: payto, value: payto,
error: paytoUriError, error: paytoUriError,
onInput: async (v) => { onInput: pushAlertOnError(async (v) => {
setPayto(v); setPayto(v);
}, }),
}, },
accountByType, accountByType,
deleteAccount, deleteAccount,
onAccountAdded: { onAccountAdded: {
onClick: unableToAdd ? undefined : addAccount, onClick: unableToAdd ? undefined : pushAlertOnError(addAccount),
}, },
onCancel: { onCancel: {
onClick: async () => onCancel(), onClick: pushAlertOnError(async () => onCancel()),
}, },
}; };
} }

View File

@ -19,18 +19,15 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { nullFunction } from "../../mui/handlers.js";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "manage account", title: "manage account",
}; };
const nullFunction = async () => { export const JustTwoBitcoinAccounts = tests.createExample(ReadyView, {
null;
};
export const JustTwoBitcoinAccounts = createExample(ReadyView, {
status: "ready", status: "ready",
currency: "ARS", currency: "ARS",
accountType: { accountType: {
@ -84,7 +81,7 @@ export const JustTwoBitcoinAccounts = createExample(ReadyView, {
onCancel: {}, onCancel: {},
}); });
export const WithAllTypeOfAccounts = createExample(ReadyView, { export const WithAllTypeOfAccounts = tests.createExample(ReadyView, {
status: "ready", status: "ready",
currency: "ARS", currency: "ARS",
accountType: { accountType: {
@ -165,7 +162,7 @@ export const WithAllTypeOfAccounts = createExample(ReadyView, {
onCancel: {}, onCancel: {},
}); });
export const AddingIbanAccount = createExample(ReadyView, { export const AddingIbanAccount = tests.createExample(ReadyView, {
status: "ready", status: "ready",
currency: "ARS", currency: "ARS",
accountType: { accountType: {

View File

@ -20,14 +20,14 @@
*/ */
import { AbsoluteTime, AttentionType } from "@gnu-taler/taler-util"; import { AbsoluteTime, AttentionType } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReadyView } from "./views.js"; import { ReadyView } from "./views.js";
export default { export default {
title: "notifications", title: "notifications",
}; };
export const Ready = createExample(ReadyView, { export const Ready = tests.createExample(ReadyView, {
list: [ list: [
{ {
when: AbsoluteTime.now(), when: AbsoluteTime.now(),

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage.js"; import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage.js";
export default { export default {
@ -32,7 +32,7 @@ export default {
}, },
}; };
export const DemoService = createExample(TestedComponent, { export const DemoService = tests.createExample(TestedComponent, {
url: "https://sync.demo.taler.net/", url: "https://sync.demo.taler.net/",
provider: { provider: {
annual_fee: "KUDOS:0.1", annual_fee: "KUDOS:0.1",
@ -41,7 +41,7 @@ export const DemoService = createExample(TestedComponent, {
}, },
}); });
export const FreeService = createExample(TestedComponent, { export const FreeService = tests.createExample(TestedComponent, {
url: "https://sync.taler:9667/", url: "https://sync.taler:9667/",
provider: { provider: {
annual_fee: "ARS:0", annual_fee: "ARS:0",

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { SetUrlView as TestedComponent } from "./ProviderAddPage.js"; import { SetUrlView as TestedComponent } from "./ProviderAddPage.js";
export default { export default {
@ -32,20 +32,20 @@ export default {
}, },
}; };
export const Initial = createExample(TestedComponent, {}); export const Initial = tests.createExample(TestedComponent, {});
export const WithValue = createExample(TestedComponent, { export const WithValue = tests.createExample(TestedComponent, {
initialValue: "sync.demo.taler.net", initialValue: "sync.demo.taler.net",
}); });
export const WithConnectionError = createExample(TestedComponent, { export const WithConnectionError = tests.createExample(TestedComponent, {
withError: "Network error", withError: "Network error",
}); });
export const WithClientError = createExample(TestedComponent, { export const WithClientError = tests.createExample(TestedComponent, {
withError: "URL may not be right: (404) Not Found", withError: "URL may not be right: (404) Not Found",
}); });
export const WithServerError = createExample(TestedComponent, { export const WithServerError = tests.createExample(TestedComponent, {
withError: "Try another server: (500) Internal Server Error", withError: "Try another server: (500) Internal Server Error",
}); });

View File

@ -21,7 +21,7 @@
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util"; import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ProviderView as TestedComponent } from "./ProviderDetailPage.js"; import { ProviderView as TestedComponent } from "./ProviderDetailPage.js";
export default { export default {
@ -34,7 +34,7 @@ export default {
}, },
}; };
export const Active = createExample(TestedComponent, { export const Active = tests.createExample(TestedComponent, {
info: { info: {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
@ -58,7 +58,7 @@ export const Active = createExample(TestedComponent, {
}, },
}); });
export const ActiveErrorSync = createExample(TestedComponent, { export const ActiveErrorSync = tests.createExample(TestedComponent, {
info: { info: {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
@ -79,6 +79,7 @@ export const ActiveErrorSync = createExample(TestedComponent, {
lastError: { lastError: {
code: 2002, code: 2002,
details: "details", details: "details",
when: new Date().toISOString(),
hint: "error hint from the server", hint: "error hint from the server",
message: "message", message: "message",
}, },
@ -90,7 +91,9 @@ export const ActiveErrorSync = createExample(TestedComponent, {
}, },
}); });
export const ActiveBackupProblemUnreadable = createExample(TestedComponent, { export const ActiveBackupProblemUnreadable = tests.createExample(
TestedComponent,
{
info: { info: {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
@ -115,9 +118,10 @@ export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
supportedProtocolVersion: "0.0", supportedProtocolVersion: "0.0",
}, },
}, },
}); },
);
export const ActiveBackupProblemDevice = createExample(TestedComponent, { export const ActiveBackupProblemDevice = tests.createExample(TestedComponent, {
info: { info: {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
@ -149,7 +153,7 @@ export const ActiveBackupProblemDevice = createExample(TestedComponent, {
}, },
}); });
export const InactiveUnpaid = createExample(TestedComponent, { export const InactiveUnpaid = tests.createExample(TestedComponent, {
info: { info: {
active: false, active: false,
name: "sync.demo", name: "sync.demo",
@ -166,7 +170,9 @@ export const InactiveUnpaid = createExample(TestedComponent, {
}, },
}); });
export const InactiveInsufficientBalance = createExample(TestedComponent, { export const InactiveInsufficientBalance = tests.createExample(
TestedComponent,
{
info: { info: {
active: false, active: false,
name: "sync.demo", name: "sync.demo",
@ -182,9 +188,10 @@ export const InactiveInsufficientBalance = createExample(TestedComponent, {
supportedProtocolVersion: "0.0", supportedProtocolVersion: "0.0",
}, },
}, },
}); },
);
export const InactivePending = createExample(TestedComponent, { export const InactivePending = tests.createExample(TestedComponent, {
info: { info: {
active: false, active: false,
name: "sync.demo", name: "sync.demo",
@ -202,7 +209,7 @@ export const InactivePending = createExample(TestedComponent, {
}, },
}); });
export const ActiveTermsChanged = createExample(TestedComponent, { export const ActiveTermsChanged = tests.createExample(TestedComponent, {
info: { info: {
active: true, active: true,
name: "sync.demo", name: "sync.demo",

View File

@ -23,7 +23,7 @@ import {
WalletApiOperation, WalletApiOperation,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { AlertView } from "../components/CurrentAlerts.js"; import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { ErrorMessage } from "../components/ErrorMessage.js"; import { ErrorMessage } from "../components/ErrorMessage.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { PaymentStatus, SmallLightText } from "../components/styled/index.js"; import { PaymentStatus, SmallLightText } from "../components/styled/index.js";
@ -66,8 +66,8 @@ export function ProviderDetailPage({
} }
if (state.hasError) { if (state.hasError) {
return ( return (
<AlertView <ErrorAlertView
alert={alertFromError( error={alertFromError(
i18n.str`There was an error loading the provider detail for &quot;${providerURL}&quot;`, i18n.str`There was an error loading the provider detail for &quot;${providerURL}&quot;`,
state, state,
)} )}

View File

@ -19,11 +19,11 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { QrReaderPage } from "./QrReader.js"; import { QrReaderPage } from "./QrReader.js";
export default { export default {
title: "qr reader", title: "qr reader",
}; };
export const Reading = createExample(QrReaderPage, {}); export const Reading = tests.createExample(QrReaderPage, {});

View File

@ -20,7 +20,7 @@
*/ */
import { parsePaytoUri } from "@gnu-taler/taler-util"; import { parsePaytoUri } from "@gnu-taler/taler-util";
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { ReserveCreated as TestedComponent } from "./ReserveCreated.js"; import { ReserveCreated as TestedComponent } from "./ReserveCreated.js";
export default { export default {
@ -29,7 +29,7 @@ export default {
argTypes: {}, argTypes: {},
}; };
export const TalerBank = createExample(TestedComponent, { export const TalerBank = tests.createExample(TestedComponent, {
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
paytoURI: parsePaytoUri( paytoURI: parsePaytoUri(
"payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", "payto://x-taler-bank/bank.taler:5882/exchangeminator?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
@ -42,7 +42,7 @@ export const TalerBank = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
}); });
export const IBAN = createExample(TestedComponent, { export const IBAN = tests.createExample(TestedComponent, {
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
paytoURI: parsePaytoUri( paytoURI: parsePaytoUri(
"payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", "payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
@ -55,7 +55,7 @@ export const IBAN = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
}); });
export const WithReceiverName = createExample(TestedComponent, { export const WithReceiverName = tests.createExample(TestedComponent, {
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG", reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
paytoURI: parsePaytoUri( paytoURI: parsePaytoUri(
"payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG&receiver=Sebastian", "payto://iban/ES8877998399652238?amount=COL%3A1&message=Taler+Withdrawal+A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG&receiver=Sebastian",
@ -68,7 +68,7 @@ export const WithReceiverName = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
}); });
export const Bitcoin = createExample(TestedComponent, { export const Bitcoin = tests.createExample(TestedComponent, {
reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
paytoURI: parsePaytoUri( paytoURI: parsePaytoUri(
"payto://bitcoin/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", "payto://bitcoin/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
@ -81,7 +81,7 @@ export const Bitcoin = createExample(TestedComponent, {
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
}); });
export const BitcoinRegTest = createExample(TestedComponent, { export const BitcoinRegTest = tests.createExample(TestedComponent, {
reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
paytoURI: parsePaytoUri( paytoURI: parsePaytoUri(
"payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", "payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
@ -93,7 +93,7 @@ export const BitcoinRegTest = createExample(TestedComponent, {
}, },
exchangeBaseUrl: "https://exchange.demo.taler.net", exchangeBaseUrl: "https://exchange.demo.taler.net",
}); });
export const BitcoinTest = createExample(TestedComponent, { export const BitcoinTest = tests.createExample(TestedComponent, {
reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
paytoURI: parsePaytoUri( paytoURI: parsePaytoUri(
"payto://bitcoin/tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", "payto://bitcoin/tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { SettingsView as TestedComponent } from "./Settings.js"; import { SettingsView as TestedComponent } from "./Settings.js";
export default { export default {
@ -45,7 +45,7 @@ const version = {
}, },
}; };
export const AllOff = createExample(TestedComponent, { export const AllOff = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
@ -54,7 +54,7 @@ export const AllOff = createExample(TestedComponent, {
...version, ...version,
}); });
export const OneChecked = createExample(TestedComponent, { export const OneChecked = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
@ -63,7 +63,7 @@ export const OneChecked = createExample(TestedComponent, {
...version, ...version,
}); });
export const WithOneExchange = createExample(TestedComponent, { export const WithOneExchange = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
@ -85,7 +85,9 @@ export const WithOneExchange = createExample(TestedComponent, {
...version, ...version,
}); });
export const WithExchangeInDifferentState = createExample(TestedComponent, { export const WithExchangeInDifferentState = tests.createExample(
TestedComponent,
{
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, devModeToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
@ -101,7 +103,9 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
content: "content of tos", content: "content of tos",
contentType: "text/plain", contentType: "text/plain",
}, },
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"], paytoUris: [
"payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator",
],
}, },
{ {
currency: "USD", currency: "USD",
@ -112,7 +116,9 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
content: "content of tos", content: "content of tos",
contentType: "text/plain", contentType: "text/plain",
}, },
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"], paytoUris: [
"payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator",
],
} as any, //TODO: complete with auditors, wireInfo and denominations } as any, //TODO: complete with auditors, wireInfo and denominations
{ {
currency: "USD", currency: "USD",
@ -122,8 +128,11 @@ export const WithExchangeInDifferentState = createExample(TestedComponent, {
content: "content of tos", content: "content of tos",
contentType: "text/plain", contentType: "text/plain",
}, },
paytoUris: ["payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator"], paytoUris: [
"payto://x-taler-bank/bank.rpi.sebasjm.com/exchangeminator",
],
}, },
], ],
...version, ...version,
}); },
);

View File

@ -22,7 +22,6 @@ import {
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../components/Checkbox.js"; import { Checkbox } from "../components/Checkbox.js";
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
import { JustInDevMode } from "../components/JustInDevMode.js"; import { JustInDevMode } from "../components/JustInDevMode.js";
import { Part } from "../components/Part.js"; import { Part } from "../components/Part.js";
import { SelectList } from "../components/SelectList.js"; import { SelectList } from "../components/SelectList.js";
@ -34,6 +33,7 @@ import {
SuccessText, SuccessText,
WarningText, WarningText,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useDevContext } from "../context/devContext.js"; import { useDevContext } from "../context/devContext.js";
import { useTranslationContext } from "../context/translation.js"; import { useTranslationContext } from "../context/translation.js";
@ -50,8 +50,9 @@ const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
export function SettingsPage(): VNode { export function SettingsPage(): VNode {
const autoOpenToggle = useAutoOpenPermissions(); const autoOpenToggle = useAutoOpenPermissions();
const clipboardToggle = useClipboardPermissions(); const clipboardToggle = useClipboardPermissions();
const { devModeToggle } = useDevContext(); const { devMode, toggle } = useDevContext();
const { name, update } = useBackupDeviceName(); const { name, update } = useBackupDeviceName();
const { pushAlertOnError } = useAlertContext();
const webex = platform.getWalletWebExVersion(); const webex = platform.getWalletWebExVersion();
const api = useBackendContext(); const api = useBackendContext();
@ -72,7 +73,12 @@ export function SettingsPage(): VNode {
setDeviceName={update} setDeviceName={update}
autoOpenToggle={autoOpenToggle} autoOpenToggle={autoOpenToggle}
clipboardToggle={clipboardToggle} clipboardToggle={clipboardToggle}
devModeToggle={devModeToggle} devModeToggle={{
value: devMode,
button: {
onClick: pushAlertOnError(toggle),
},
}}
webexVersion={{ webexVersion={{
version: webex.version, version: webex.version,
hash: GIT_HASH, hash: GIT_HASH,
@ -109,18 +115,6 @@ export function SettingsView({
return ( return (
<Fragment> <Fragment>
<section> <section>
{autoOpenToggle.button.error && (
<ErrorTalerOperation
title={i18n.str`Could not toggle auto-open`}
error={autoOpenToggle.button.error.errorDetail}
/>
)}
{/* {clipboardToggle.button.error && (
<ErrorTalerOperation
title={i18n.str`Could not toggle clipboard`}
error={clipboardToggle.button.error.errorDetail}
/>
)} */}
<SubTitle> <SubTitle>
<i18n.Translate>Navigator</i18n.Translate> <i18n.Translate>Navigator</i18n.Translate>
</SubTitle> </SubTitle>

View File

@ -39,11 +39,13 @@ import {
WithdrawalType, WithdrawalType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { DevContextProviderForTesting } from "../context/devContext.js"; import { DevContextProviderForTesting } from "../context/devContext.js";
import { // import {
createExample, // createExample,
createExampleWithCustomContext as createExampleInCustomContext, // createExampleWithCustomContext as createExampleInCustomContext,
} from "../test-utils.js"; // } from "../test-utils.js";
import { TransactionView as TestedComponent } from "./Transaction.js"; import { TransactionView as TestedComponent } from "./Transaction.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import beer from "../../static-dev/beer.png";
export default { export default {
title: "transaction details", title: "transaction details",
@ -214,24 +216,28 @@ const transactionError = {
hint: "The payment is too late, the offer has expired.", hint: "The payment is too late, the offer has expired.",
}, },
}, },
when: new Date().toISOString(),
hint: "Error: WALLET_UNEXPECTED_REQUEST_ERROR", hint: "Error: WALLET_UNEXPECTED_REQUEST_ERROR",
message: "Unexpected error code in response", message: "Unexpected error code in response",
}; };
export const Withdraw = createExample(TestedComponent, { export const Withdraw = tests.createExample(TestedComponent, {
transaction: exampleData.withdraw, transaction: exampleData.withdraw,
}); });
export const WithdrawFiveMinutesAgo = createExample(TestedComponent, () => ({ export const WithdrawFiveMinutesAgo = tests.createExample(
TestedComponent,
() => ({
transaction: { transaction: {
...exampleData.withdraw, ...exampleData.withdraw,
timestamp: TalerProtocolTimestamp.fromSeconds( timestamp: TalerProtocolTimestamp.fromSeconds(
new Date().getTime() / 1000 - 60 * 5, new Date().getTime() / 1000 - 60 * 5,
), ),
}, },
})); }),
);
export const WithdrawFiveMinutesAgoAndPending = createExample( export const WithdrawFiveMinutesAgoAndPending = tests.createExample(
TestedComponent, TestedComponent,
() => ({ () => ({
transaction: { transaction: {
@ -244,26 +250,28 @@ export const WithdrawFiveMinutesAgoAndPending = createExample(
}), }),
); );
export const WithdrawError = createExample(TestedComponent, { export const WithdrawError = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.withdraw, ...exampleData.withdraw,
error: transactionError, error: transactionError,
}, },
}); });
export const WithdrawErrorInDevMode = createExampleInCustomContext( // export const WithdrawErrorInDevMode = tests.createExampleInCustomContext(
TestedComponent, // TestedComponent,
{ // {
transaction: { // transaction: {
...exampleData.withdraw, // ...exampleData.withdraw,
error: transactionError, // error: transactionError,
}, // },
}, // },
DevContextProviderForTesting, // DevContextProviderForTesting,
{ value: true }, // { value: true },
); // );
export const WithdrawPendingManual = createExample(TestedComponent, () => ({ export const WithdrawPendingManual = tests.createExample(
TestedComponent,
() => ({
transaction: { transaction: {
...exampleData.withdraw, ...exampleData.withdraw,
withdrawalDetails: { withdrawalDetails: {
@ -273,9 +281,10 @@ export const WithdrawPendingManual = createExample(TestedComponent, () => ({
} as WithdrawalDetails, } as WithdrawalDetails,
pending: true, pending: true,
}, },
})); }),
);
export const WithdrawPendingTalerBankUnconfirmed = createExample( export const WithdrawPendingTalerBankUnconfirmed = tests.createExample(
TestedComponent, TestedComponent,
{ {
transaction: { transaction: {
@ -291,7 +300,7 @@ export const WithdrawPendingTalerBankUnconfirmed = createExample(
}, },
); );
export const WithdrawPendingTalerBankConfirmed = createExample( export const WithdrawPendingTalerBankConfirmed = tests.createExample(
TestedComponent, TestedComponent,
{ {
transaction: { transaction: {
@ -306,18 +315,18 @@ export const WithdrawPendingTalerBankConfirmed = createExample(
}, },
); );
export const Payment = createExample(TestedComponent, { export const Payment = tests.createExample(TestedComponent, {
transaction: exampleData.payment, transaction: exampleData.payment,
}); });
export const PaymentError = createExample(TestedComponent, { export const PaymentError = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
error: transactionError, error: transactionError,
}, },
}); });
export const PaymentWithRefund = createExample(TestedComponent, { export const PaymentWithRefund = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:12", amountRaw: "KUDOS:12",
@ -334,7 +343,7 @@ export const PaymentWithRefund = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithDeliveryDate = createExample(TestedComponent, { export const PaymentWithDeliveryDate = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:12", amountRaw: "KUDOS:12",
@ -347,7 +356,7 @@ export const PaymentWithDeliveryDate = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithDeliveryAddr = createExample(TestedComponent, { export const PaymentWithDeliveryAddr = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:12", amountRaw: "KUDOS:12",
@ -363,7 +372,7 @@ export const PaymentWithDeliveryAddr = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithDeliveryFull = createExample(TestedComponent, { export const PaymentWithDeliveryFull = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:12", amountRaw: "KUDOS:12",
@ -382,7 +391,7 @@ export const PaymentWithDeliveryFull = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithRefundPending = createExample(TestedComponent, { export const PaymentWithRefundPending = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:12", amountRaw: "KUDOS:12",
@ -392,7 +401,7 @@ export const PaymentWithRefundPending = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithFeeAndRefund = createExample(TestedComponent, { export const PaymentWithFeeAndRefund = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:11", amountRaw: "KUDOS:11",
@ -401,7 +410,7 @@ export const PaymentWithFeeAndRefund = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithFeeAndRefundFee = createExample(TestedComponent, { export const PaymentWithFeeAndRefundFee = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:11", amountRaw: "KUDOS:11",
@ -410,20 +419,18 @@ export const PaymentWithFeeAndRefundFee = createExample(TestedComponent, {
}, },
}); });
export const PaymentWithoutFee = createExample(TestedComponent, { export const PaymentWithoutFee = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
amountRaw: "KUDOS:12", amountRaw: "KUDOS:12",
}, },
}); });
export const PaymentPending = createExample(TestedComponent, { export const PaymentPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.payment, pending: true }, transaction: { ...exampleData.payment, pending: true },
}); });
import beer from "../../static-dev/beer.png"; export const PaymentWithProducts = tests.createExample(TestedComponent, {
export const PaymentWithProducts = createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
info: { info: {
@ -460,7 +467,7 @@ export const PaymentWithProducts = createExample(TestedComponent, {
} as TransactionPayment, } as TransactionPayment,
}); });
export const PaymentWithLongSummary = createExample(TestedComponent, { export const PaymentWithLongSummary = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.payment, ...exampleData.payment,
info: { info: {
@ -484,16 +491,16 @@ export const PaymentWithLongSummary = createExample(TestedComponent, {
} as TransactionPayment, } as TransactionPayment,
}); });
export const Deposit = createExample(TestedComponent, { export const Deposit = tests.createExample(TestedComponent, {
transaction: exampleData.deposit, transaction: exampleData.deposit,
}); });
export const DepositTalerBank = createExample(TestedComponent, { export const DepositTalerBank = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.deposit, ...exampleData.deposit,
targetPaytoUri: "payto://x-taler-bank/bank.demo.taler.net/Exchange", targetPaytoUri: "payto://x-taler-bank/bank.demo.taler.net/Exchange",
}, },
}); });
export const DepositBitcoin = createExample(TestedComponent, { export const DepositBitcoin = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.deposit, ...exampleData.deposit,
amountRaw: "BITCOINBTC:0.0000011", amountRaw: "BITCOINBTC:0.0000011",
@ -502,88 +509,88 @@ export const DepositBitcoin = createExample(TestedComponent, {
"payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", "payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
}, },
}); });
export const DepositIBAN = createExample(TestedComponent, { export const DepositIBAN = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.deposit, ...exampleData.deposit,
targetPaytoUri: "payto://iban/ES8877998399652238", targetPaytoUri: "payto://iban/ES8877998399652238",
}, },
}); });
export const DepositError = createExample(TestedComponent, { export const DepositError = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.deposit, ...exampleData.deposit,
error: transactionError, error: transactionError,
}, },
}); });
export const DepositPending = createExample(TestedComponent, { export const DepositPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.deposit, pending: true }, transaction: { ...exampleData.deposit, pending: true },
}); });
export const Refresh = createExample(TestedComponent, { export const Refresh = tests.createExample(TestedComponent, {
transaction: exampleData.refresh, transaction: exampleData.refresh,
}); });
export const RefreshError = createExample(TestedComponent, { export const RefreshError = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.refresh, ...exampleData.refresh,
error: transactionError, error: transactionError,
}, },
}); });
export const Tip = createExample(TestedComponent, { export const Tip = tests.createExample(TestedComponent, {
transaction: exampleData.tip, transaction: exampleData.tip,
}); });
export const TipError = createExample(TestedComponent, { export const TipError = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.tip, ...exampleData.tip,
error: transactionError, error: transactionError,
}, },
}); });
export const TipPending = createExample(TestedComponent, { export const TipPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.tip, pending: true }, transaction: { ...exampleData.tip, pending: true },
}); });
export const Refund = createExample(TestedComponent, { export const Refund = tests.createExample(TestedComponent, {
transaction: exampleData.refund, transaction: exampleData.refund,
}); });
export const RefundError = createExample(TestedComponent, { export const RefundError = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.refund, ...exampleData.refund,
error: transactionError, error: transactionError,
}, },
}); });
export const RefundPending = createExample(TestedComponent, { export const RefundPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.refund, pending: true }, transaction: { ...exampleData.refund, pending: true },
}); });
export const InvoiceCreditComplete = createExample(TestedComponent, { export const InvoiceCreditComplete = tests.createExample(TestedComponent, {
transaction: { ...exampleData.pull_credit }, transaction: { ...exampleData.pull_credit },
}); });
export const InvoiceCreditIncomplete = createExample(TestedComponent, { export const InvoiceCreditIncomplete = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.pull_credit, ...exampleData.pull_credit,
pending: true, pending: true,
}, },
}); });
export const InvoiceDebit = createExample(TestedComponent, { export const InvoiceDebit = tests.createExample(TestedComponent, {
transaction: { ...exampleData.pull_debit }, transaction: { ...exampleData.pull_debit },
}); });
export const TransferCredit = createExample(TestedComponent, { export const TransferCredit = tests.createExample(TestedComponent, {
transaction: { ...exampleData.push_credit }, transaction: { ...exampleData.push_credit },
}); });
export const TransferDebitComplete = createExample(TestedComponent, { export const TransferDebitComplete = tests.createExample(TestedComponent, {
transaction: { ...exampleData.push_debit }, transaction: { ...exampleData.push_debit },
}); });
export const TransferDebitIncomplete = createExample(TestedComponent, { export const TransferDebitIncomplete = tests.createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.push_debit, ...exampleData.push_debit,
pending: true, pending: true,

View File

@ -44,7 +44,7 @@ import emptyImg from "../../static/img/empty.png";
import { Amount } from "../components/Amount.js"; import { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { CopyButton } from "../components/CopyButton.js"; import { CopyButton } from "../components/CopyButton.js";
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js"; import { ErrorAlertView } from "../components/CurrentAlerts.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js"; import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js"; import { QR } from "../components/QR.js";
@ -99,8 +99,8 @@ export function TransactionPage({
if (state.hasError) { if (state.hasError) {
return ( return (
<AlertView <ErrorAlertView
alert={alertFromError( error={alertFromError(
i18n.str`Could not load transaction information`, i18n.str`Could not load transaction information`,
state, state,
)} )}

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { createExample } from "../test-utils.js"; import { tests } from "@gnu-taler/web-util/lib/index.browser";
import { View as TestedComponent } from "./Welcome.js"; import { View as TestedComponent } from "./Welcome.js";
export default { export default {
@ -27,7 +27,7 @@ export default {
component: TestedComponent, component: TestedComponent,
}; };
export const Normal = createExample(TestedComponent, { export const Normal = tests.createExample(TestedComponent, {
permissionToggle: { value: true, button: {} }, permissionToggle: { value: true, button: {} },
diagnostics: { diagnostics: {
errors: [], errors: [],
@ -38,11 +38,11 @@ export const Normal = createExample(TestedComponent, {
}, },
}); });
export const TimedoutDiagnostics = createExample(TestedComponent, { export const TimedoutDiagnostics = tests.createExample(TestedComponent, {
timedOut: true, timedOut: true,
permissionToggle: { value: true, button: {} }, permissionToggle: { value: true, button: {} },
}); });
export const RunningDiagnostics = createExample(TestedComponent, { export const RunningDiagnostics = tests.createExample(TestedComponent, {
permissionToggle: { value: true, button: {} }, permissionToggle: { value: true, button: {} },
}); });

View File

@ -93,22 +93,12 @@ export interface BackgroundApiClient {
): Promise<BackgroundOperations[Op]["response"]>; ): Promise<BackgroundOperations[Op]["response"]>;
} }
export class WalletError extends Error {
public errorDetail: TalerError;
constructor(op: string, e: TalerError) {
super(`Wallet operation "${op}" failed: ${e.message}`);
this.errorDetail = e;
// Object.setPrototypeOf(this, WalletError.prototype);
}
}
export class BackgroundError extends Error { export class BackgroundError extends Error {
public errorDetail: TalerErrorDetail; public errorDetail: TalerErrorDetail;
constructor(op: string, e: TalerErrorDetail) { constructor(title: string, e: TalerErrorDetail) {
super(`Background operation "${op}" failed: ${e.message}`); super(title);
this.errorDetail = e; this.errorDetail = e;
// Object.setPrototypeOf(this, BackgroundError.prototype);
} }
} }
@ -135,13 +125,17 @@ class BackgroundApiClientImpl implements BackgroundApiClient {
if (error instanceof Error) { if (error instanceof Error) {
throw new BackgroundError(operation, { throw new BackgroundError(operation, {
code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
when: new Date().toISOString(),
}); });
} }
throw error; throw error;
} }
logger.info("got response", response); logger.info("got response", response);
if (response.type === "error") { if (response.type === "error") {
throw new BackgroundError(operation, response.error); throw new BackgroundError(
`Background operation "${operation}" failed`,
response.error,
);
} }
return response.result as any; return response.result as any;
} }
@ -169,8 +163,10 @@ class WalletApiClientImpl implements WalletCoreApiClient {
} }
logger.info("got response", response); logger.info("got response", response);
if (response.type === "error") { if (response.type === "error") {
const error = TalerError.fromUncheckedDetail(response.error); throw new BackgroundError(
throw new WalletError(operation, error); `Wallet operation "${operation}" failed`,
response.error,
);
} }
return response.result as any; return response.result as any;
} }