migrate to preact

This commit is contained in:
Sebastian 2021-05-07 10:38:28 -03:00
parent b414de8533
commit 30f86f8748
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
22 changed files with 10753 additions and 2672 deletions

View File

@ -6,15 +6,8 @@ module.exports = {
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:react/recommended", "preact",
"plugin:react-hooks/recommended",
], ],
settings: {
"react": {
"version": "16.9.6",
},
},
rules: { rules: {
"no-constant-condition": ["error", { "checkLoops": false }], "no-constant-condition": ["error", { "checkLoops": false }],
"prefer-const": ["warn", { destructuring: "all" }], "prefer-const": ["warn", { destructuring: "all" }],

View File

@ -9,34 +9,53 @@
"private": false, "private": false,
"scripts": { "scripts": {
"clean": "rimraf dist lib tsconfig.tsbuildinfo", "clean": "rimraf dist lib tsconfig.tsbuildinfo",
"test": "jest ./tests",
"compile": "tsc && rollup -c" "compile": "tsc && rollup -c"
}, },
"dependencies": { "dependencies": {
"moment": "^2.29.1",
"@gnu-taler/taler-wallet-core": "workspace:*",
"@gnu-taler/taler-util": "workspace:*", "@gnu-taler/taler-util": "workspace:*",
"@gnu-taler/taler-wallet-core": "workspace:*",
"preact": "^10.5.13",
"preact-router": "^3.2.1",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.0",
"@babel/plugin-transform-react-jsx-source": "^7.12.13",
"@babel/preset-typescript": "^7.13.0",
"@rollup/plugin-commonjs": "^17.0.0", "@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.1.0", "@rollup/plugin-node-resolve": "^11.1.0",
"@rollup/plugin-replace": "^2.3.4", "@rollup/plugin-replace": "^2.3.4",
"@testing-library/preact": "^2.0.1",
"@types/chrome": "^0.0.128", "@types/chrome": "^0.0.128",
"@types/enzyme": "^3.10.8", "@types/jest": "^26.0.23",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/node": "^14.14.22", "@types/node": "^14.14.22",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"ava": "3.15.0", "ava": "3.15.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"react": "^17.0.1", "enzyme-adapter-preact-pure": "^3.1.0",
"react-dom": "^17.0.1", "jest": "^26.6.3",
"jest-preset-preact": "^4.0.3",
"preact-cli": "^3.0.5",
"preact-render-to-string": "^5.1.19",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.37.1", "rollup": "^2.37.1",
"rollup-plugin-ignore": "^1.0.9", "rollup-plugin-ignore": "^1.0.9",
"rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"typescript": "^4.1.3" "typescript": "^4.1.3"
},
"jest": {
"preset": "jest-preset-preact",
"setupFiles": [
"<rootDir>/tests/__mocks__/setupTests.ts"
],
"moduleNameMapper": {
"\\.(css|less)$": "identity-obj-proxy"
},
"transform": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$": "<rootDir>/tests/__mocks__/fileTransformer.js"
}
} }
} }

View File

@ -24,7 +24,6 @@ import {
HttpRequestOptions, HttpRequestOptions,
HttpResponse, HttpResponse,
Headers, Headers,
bytesToString,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { TalerErrorCode } from "@gnu-taler/taler-util"; import { TalerErrorCode } from "@gnu-taler/taler-util";

View File

@ -21,9 +21,9 @@
/** /**
* Imports * Imports
*/ */
import React from "react";
import * as i18nCore from "@gnu-taler/taler-wallet-core"; import * as i18nCore from "@gnu-taler/taler-wallet-core";
import { Component, ComponentChildren, h, JSX, toChildArray, VNode } from "preact";
/** /**
* Convert template strings to a msgid * Convert template strings to a msgid
*/ */
@ -50,7 +50,7 @@ interface TranslateSwitchProps {
function stringifyChildren(children: any): string { function stringifyChildren(children: any): string {
let n = 1; let n = 1;
const ss = React.Children.map(children, (c) => { const ss = toChildArray(children).map((c) => {
if (typeof c === "string") { if (typeof c === "string") {
return c; return c;
} }
@ -76,10 +76,10 @@ interface TranslateProps {
function getTranslatedChildren( function getTranslatedChildren(
translation: string, translation: string,
children: React.ReactNode, children: ComponentChildren,
): React.ReactNode[] { ): ComponentChildren {
const tr = translation.split(/%(\d+)\$s/); const tr = translation.split(/%(\d+)\$s/);
const childArray = React.Children.toArray(children); const childArray = toChildArray(children);
// Merge consecutive string children. // Merge consecutive string children.
const placeholderChildren = []; const placeholderChildren = [];
for (let i = 0; i < childArray.length; i++) { for (let i = 0; i < childArray.length; i++) {
@ -117,15 +117,15 @@ function getTranslatedChildren(
* </Translate> * </Translate>
* ``` * ```
*/ */
export class Translate extends React.Component<TranslateProps, {}> { export class Translate extends Component<TranslateProps, any> {
render(): JSX.Element { render() {
const s = stringifyChildren(this.props.children); const s = stringifyChildren(this.props.children);
const translation: string = i18nCore.jed.ngettext(s, s, 1); const translation: string = i18nCore.jed.ngettext(s, s, 1);
const result = getTranslatedChildren(translation, this.props.children); const result = getTranslatedChildren(translation, this.props.children);
if (!this.props.wrap) { if (!this.props.wrap) {
return <div>{result}</div>; return <div>{result}</div>;
} }
return React.createElement(this.props.wrap, this.props.wrapProps, result); return h(this.props.wrap, this.props.wrapProps, result);
} }
} }
@ -141,16 +141,16 @@ export class Translate extends React.Component<TranslateProps, {}> {
* </TranslateSwitch> * </TranslateSwitch>
* ``` * ```
*/ */
export class TranslateSwitch extends React.Component< export class TranslateSwitch extends Component<
TranslateSwitchProps, TranslateSwitchProps,
void void
> { > {
render(): JSX.Element { render(): JSX.Element {
let singular: React.ReactElement<TranslationPluralProps> | undefined; let singular: VNode<TranslationPluralProps> | undefined;
let plural: React.ReactElement<TranslationPluralProps> | undefined; let plural: VNode<TranslationPluralProps> | undefined;
const children = this.props.children; const children = this.props.children;
if (children) { if (children) {
React.Children.forEach(children, (child: any) => { toChildArray(children).forEach((child: any) => {
if (child.type === TranslatePlural) { if (child.type === TranslatePlural) {
plural = child; plural = child;
} }
@ -161,7 +161,7 @@ export class TranslateSwitch extends React.Component<
} }
if (!singular || !plural) { if (!singular || !plural) {
console.error("translation not found"); console.error("translation not found");
return React.createElement("span", {}, ["translation not found"]); return h("span", {}, ["translation not found"]);
} }
singular.props.target = this.props.target; singular.props.target = this.props.target;
plural.props.target = this.props.target; plural.props.target = this.props.target;
@ -178,7 +178,7 @@ interface TranslationPluralProps {
/** /**
* See [[TranslateSwitch]]. * See [[TranslateSwitch]].
*/ */
export class TranslatePlural extends React.Component< export class TranslatePlural extends Component<
TranslationPluralProps, TranslationPluralProps,
void void
> { > {
@ -193,7 +193,7 @@ export class TranslatePlural extends React.Component<
/** /**
* See [[TranslateSwitch]]. * See [[TranslateSwitch]].
*/ */
export class TranslateSingular extends React.Component< export class TranslateSingular extends Component<
TranslationPluralProps, TranslationPluralProps,
void void
> { > {

View File

@ -20,7 +20,7 @@
* @author Florian Dold <dold@taler.net> * @author Florian Dold <dold@taler.net>
*/ */
import ReactDOM from "react-dom"; import {render} from "preact";
import { createPopup } from "./pages/popup"; import { createPopup } from "./pages/popup";
import { createWithdrawPage } from "./pages/withdraw"; import { createWithdrawPage } from "./pages/withdraw";
import { createWelcomePage } from "./pages/welcome"; import { createWelcomePage } from "./pages/welcome";
@ -63,7 +63,7 @@ function main(): void {
if (!container) { if (!container) {
throw Error("container not found, can't mount page contents"); throw Error("container not found, can't mount page contents");
} }
ReactDOM.render(mainElement, container); render(mainElement, container);
} catch (e) { } catch (e) {
console.error("got error", e); console.error("got error", e);
document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`; document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;

View File

@ -27,7 +27,7 @@ import * as i18n from "../i18n";
import { renderAmount, ProgressButton } from "../renderHtml"; import { renderAmount, ProgressButton } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "preact/hooks";
import { getJsonI18n } from "@gnu-taler/taler-wallet-core"; import { getJsonI18n } from "@gnu-taler/taler-wallet-core";
import { import {
@ -39,10 +39,11 @@ import {
ContractTerms, ContractTerms,
ConfirmPayResultType, ConfirmPayResultType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { JSX, VNode } from "preact";
function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element { function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(); const [payStatus, setPayStatus] = useState<PreparePayResult | undefined>(undefined);
const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(); const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(undefined);
const [payErrMsg, setPayErrMsg] = useState<string | undefined>(""); const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
const [numTries, setNumTries] = useState(0); const [numTries, setNumTries] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -66,8 +67,8 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
} }
if (payStatus.status === PreparePayResultType.PaymentPossible) { if (payStatus.status === PreparePayResultType.PaymentPossible) {
let amountRaw = Amounts.parseOrThrow(payStatus.amountRaw); const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
let amountEffective: AmountJson = Amounts.parseOrThrow( const amountEffective: AmountJson = Amounts.parseOrThrow(
payStatus.amountEffective, payStatus.amountEffective,
); );
totalFees = Amounts.sub(amountEffective, amountRaw).amount; totalFees = Amounts.sub(amountEffective, amountRaw).amount;
@ -95,7 +96,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
} }
} }
let contractTerms: ContractTerms = payStatus.contractTerms; const contractTerms: ContractTerms = payStatus.contractTerms;
if (!contractTerms) { if (!contractTerms) {
return ( return (
@ -105,7 +106,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
); );
} }
let merchantName: React.ReactElement; let merchantName: VNode;
if (contractTerms.merchant && contractTerms.merchant.name) { if (contractTerms.merchant && contractTerms.merchant.name) {
merchantName = <strong>{contractTerms.merchant.name}</strong>; merchantName = <strong>{contractTerms.merchant.name}</strong>;
} else { } else {
@ -200,7 +201,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
) : ( ) : (
<div> <div>
<ProgressButton <ProgressButton
loading={loading} isLoading={loading}
disabled={insufficientBalance} disabled={insufficientBalance}
onClick={() => doPayment()} onClick={() => doPayment()}
> >

View File

@ -14,6 +14,8 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { JSX } from "preact/jsx-runtime";
/** /**
* View and edit auditors. * View and edit auditors.
* *
@ -23,7 +25,6 @@
/** /**
* Imports. * Imports.
*/ */
import * as React from "react";
export function makePaybackPage(): JSX.Element { export function makePaybackPage(): JSX.Element {
return <div>not implemented</div>; return <div>not implemented</div>;

View File

@ -41,18 +41,18 @@ import {
amountFractionalBase, amountFractionalBase,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { abbrev, renderAmount, PageLink } from "../renderHtml"; import { renderAmount, PageLink } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import React, { Fragment, useState, useEffect } from "react"; import { useState, useEffect } from "preact/hooks";
import moment from "moment";
import { PermissionsCheckbox } from "./welcome"; import { PermissionsCheckbox } from "./welcome";
import { JSXInternal } from "preact/src/jsx";
import { Component, ComponentChild, ComponentChildren, JSX, toChildArray, VNode } from "preact";
// FIXME: move to newer react functions // FIXME: move to newer react functions
/* eslint-disable react/no-deprecated */
class Router extends React.Component<any, any> { class Router extends Component<any, any> {
static setRoute(s: string): void { static setRoute(s: string): void {
window.location.hash = s; window.location.hash = s;
} }
@ -85,21 +85,21 @@ class Router extends React.Component<any, any> {
render(): JSX.Element { render(): JSX.Element {
const route = window.location.hash.substring(1); const route = window.location.hash.substring(1);
console.log("rendering route", route); console.log("rendering route", route);
let defaultChild: React.ReactChild | null = null; let defaultChild: ComponentChild | null = null;
let foundChild: React.ReactChild | null = null; let foundChild: ComponentChild | null = null;
React.Children.forEach(this.props.children, (child) => { toChildArray(this.props.children).forEach((child) => {
const childProps: any = (child as any).props; const childProps: any = (child as any).props;
if (!childProps) { if (!childProps) {
return; return;
} }
if (childProps.default) { if (childProps.default) {
defaultChild = child as React.ReactChild; defaultChild = child;
} }
if (childProps.route === route) { if (childProps.route === route) {
foundChild = child as React.ReactChild; foundChild = child;
} }
}); });
const c: React.ReactChild | null = foundChild || defaultChild; const c: ComponentChild | null = foundChild || defaultChild;
if (!c) { if (!c) {
throw Error("unknown route"); throw Error("unknown route");
} }
@ -110,7 +110,7 @@ class Router extends React.Component<any, any> {
interface TabProps { interface TabProps {
target: string; target: string;
children?: React.ReactNode; children?: ComponentChildren;
} }
function Tab(props: TabProps): JSX.Element { function Tab(props: TabProps): JSX.Element {
@ -118,7 +118,7 @@ function Tab(props: TabProps): JSX.Element {
if (props.target === Router.getRoute()) { if (props.target === Router.getRoute()) {
cssClass = "active"; cssClass = "active";
} }
const onClick = (e: React.MouseEvent<HTMLAnchorElement>): void => { const onClick = (e: JSXInternal.TargetedMouseEvent<HTMLAnchorElement>): void => {
Router.setRoute(props.target); Router.setRoute(props.target);
e.preventDefault(); e.preventDefault();
}; };
@ -129,7 +129,7 @@ function Tab(props: TabProps): JSX.Element {
); );
} }
class WalletNavBar extends React.Component<any, any> { class WalletNavBar extends Component<any, any> {
private cancelSubscription: any; private cancelSubscription: any;
componentWillMount(): void { componentWillMount(): void {
@ -179,7 +179,7 @@ function EmptyBalanceView(): JSX.Element {
); );
} }
class WalletBalanceView extends React.Component<any, any> { class WalletBalanceView extends Component<any, any> {
private balance?: BalancesResponse; private balance?: BalancesResponse;
private gotError = false; private gotError = false;
private canceler: (() => void) | undefined = undefined; private canceler: (() => void) | undefined = undefined;
@ -323,7 +323,7 @@ function TransactionAmount(props: TransactionAmountProps): JSX.Element {
case "unknown": case "unknown":
sign = ""; sign = "";
} }
const style: React.CSSProperties = { const style: JSX.AllCSSProperties = {
marginLeft: "auto", marginLeft: "auto",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
@ -476,7 +476,7 @@ function TransactionItem(props: { tx: Transaction }): JSX.Element {
function WalletHistory(props: any): JSX.Element { function WalletHistory(props: any): JSX.Element {
const [transactions, setTransactions] = useState< const [transactions, setTransactions] = useState<
TransactionsResponse | undefined TransactionsResponse | undefined
>(); >(undefined);
useEffect(() => { useEffect(() => {
const fetchData = async (): Promise<void> => { const fetchData = async (): Promise<void> => {
@ -484,7 +484,6 @@ function WalletHistory(props: any): JSX.Element {
setTransactions(res); setTransactions(res);
}; };
fetchData(); fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
if (!transactions) { if (!transactions) {
@ -495,14 +494,14 @@ function WalletHistory(props: any): JSX.Element {
return ( return (
<div> <div>
{txs.map((tx) => ( {txs.map((tx,i) => (
<TransactionItem tx={tx} /> <TransactionItem key={i} tx={tx} />
))} ))}
</div> </div>
); );
} }
class WalletSettings extends React.Component<any, any> { class WalletSettings extends Component<any, any> {
render(): JSX.Element { render(): JSX.Element {
return ( return (
<div> <div>
@ -522,14 +521,14 @@ function reload(): void {
} }
} }
function confirmReset(): void { async function confirmReset(): Promise<void> {
if ( if (
confirm( confirm(
"Do you want to IRREVOCABLY DESTROY everything inside your" + "Do you want to IRREVOCABLY DESTROY everything inside your" +
" wallet and LOSE ALL YOUR COINS?", " wallet and LOSE ALL YOUR COINS?",
) )
) { ) {
wxApi.resetDb(); await wxApi.resetDb();
window.close(); window.close();
} }
} }
@ -554,14 +553,14 @@ function openExtensionPage(page: string) {
}; };
} }
function openTab(page: string) { // function openTab(page: string) {
return (evt: React.SyntheticEvent<any>) => { // return (evt: React.SyntheticEvent<any>) => {
evt.preventDefault(); // evt.preventDefault();
chrome.tabs.create({ // chrome.tabs.create({
url: page, // url: page,
}); // });
}; // };
} // }
function makeExtensionUrlWithParams( function makeExtensionUrlWithParams(
url: string, url: string,

View File

@ -20,17 +20,18 @@
* @author Florian Dold * @author Florian Dold
*/ */
import React, { useEffect, useState } from "react";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { AmountView } from "../renderHtml"; import { AmountView } from "../renderHtml";
import { import {
PurchaseDetails,
ApplyRefundResponse, ApplyRefundResponse,
Amounts, Amounts,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
// import { h } from 'preact';
import { useEffect, useState } from "preact/hooks";
import { JSX } from "preact/jsx-runtime";
function RefundStatusView(props: { talerRefundUri: string }): JSX.Element { function RefundStatusView(props: { talerRefundUri: string }): JSX.Element {
const [applyResult, setApplyResult] = useState<ApplyRefundResponse>(); const [applyResult, setApplyResult] = useState<ApplyRefundResponse|undefined>(undefined);
const [errMsg, setErrMsg] = useState<string | undefined>(undefined); const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
useEffect(() => { useEffect(() => {

View File

@ -20,8 +20,7 @@
* @author Florian Dold * @author Florian Dold
*/ */
import * as React from "react"; import { Component, JSX } from "preact";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
interface State { interface State {
@ -36,7 +35,7 @@ interface State {
resetRequired: boolean; resetRequired: boolean;
} }
class ResetNotification extends React.Component<any, State> { class ResetNotification extends Component<any, State> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { checked: false, resetRequired: true }; this.state = { checked: false, resetRequired: true };
@ -50,7 +49,7 @@ class ResetNotification extends React.Component<any, State> {
if (this.state.resetRequired) { if (this.state.resetRequired) {
return ( return (
<div> <div>
<h1>Manual Reset Reqired</h1> <h1>Manual Reset Required</h1>
<p> <p>
The wallet&apos;s database in your browser is incompatible with the{" "} The wallet&apos;s database in your browser is incompatible with the{" "}
currently installed wallet. Please reset manually. currently installed wallet. Please reset manually.
@ -63,7 +62,9 @@ class ResetNotification extends React.Component<any, State> {
id="check" id="check"
type="checkbox" type="checkbox"
checked={this.state.checked} checked={this.state.checked}
onChange={(e) => this.setState({ checked: e.target.checked })} onChange={() => {
this.setState(prev => ({ checked: prev.checked }))
}}
/>{" "} />{" "}
<label htmlFor="check"> <label htmlFor="check">
I understand that I will lose all my data I understand that I will lose all my data

View File

@ -14,6 +14,8 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { JSX } from "preact/jsx-runtime";
/** /**
* Return coins to own bank account. * Return coins to own bank account.
* *
@ -23,8 +25,6 @@
/** /**
* Imports. * Imports.
*/ */
import * as React from "react";
export function createReturnCoinsPage(): JSX.Element { export function createReturnCoinsPage(): JSX.Element {
return <span>Not implemented yet.</span>; return <span>Not implemented yet.</span>;
} }

View File

@ -20,17 +20,17 @@
* @author Florian Dold <dold@taler.net> * @author Florian Dold <dold@taler.net>
*/ */
import * as React from "react"; import { useEffect, useState } from "preact/hooks";
import { useEffect, useState } from "react";
import { PrepareTipResult } from "@gnu-taler/taler-util"; import { PrepareTipResult } from "@gnu-taler/taler-util";
import { AmountView } from "../renderHtml"; import { AmountView } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { JSX } from "preact/jsx-runtime";
function TalerTipDialog({ talerTipUri }: { talerTipUri: string }): JSX.Element { function TalerTipDialog({ talerTipUri }: { talerTipUri: string }): JSX.Element {
const [updateCounter, setUpdateCounter] = useState<number>(0); const [updateCounter, setUpdateCounter] = useState<number>(0);
const [prepareTipResult, setPrepareTipResult] = useState< const [prepareTipResult, setPrepareTipResult] = useState<
PrepareTipResult | undefined PrepareTipResult | undefined
>(); >(undefined);
const [tipIgnored, setTipIgnored] = useState(false); const [tipIgnored, setTipIgnored] = useState(false);

View File

@ -20,13 +20,14 @@
* @author Florian Dold * @author Florian Dold
*/ */
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "preact/hooks";
import { getDiagnostics } from "../wxApi"; import { getDiagnostics } from "../wxApi";
import { PageLink } from "../renderHtml"; import { PageLink } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { getPermissionsApi } from "../compat"; import { getPermissionsApi } from "../compat";
import { extendedPermissions } from "../permissions"; import { extendedPermissions } from "../permissions";
import { WalletDiagnostics } from "@gnu-taler/taler-util"; import { WalletDiagnostics } from "@gnu-taler/taler-util";
import { JSX } from "preact/jsx-runtime";
function Diagnostics(): JSX.Element | null { function Diagnostics(): JSX.Element | null {
const [timedOut, setTimedOut] = useState(false); const [timedOut, setTimedOut] = useState(false);
@ -102,9 +103,9 @@ export function PermissionsCheckbox(): JSX.Element {
const [extendedPermissionsEnabled, setExtendedPermissionsEnabled] = useState( const [extendedPermissionsEnabled, setExtendedPermissionsEnabled] = useState(
false, false,
); );
async function handleExtendedPerm(requestedVal: boolean): Promise<void> { async function handleExtendedPerm(): Promise<void> {
let nextVal: boolean | undefined; let nextVal: boolean | undefined;
if (requestedVal) { if (extendedPermissionsEnabled) {
const granted = await new Promise<boolean>((resolve, reject) => { const granted = await new Promise<boolean>((resolve, reject) => {
// We set permissions here, since apparently FF wants this to be done // We set permissions here, since apparently FF wants this to be done
// as the result of an input event ... // as the result of an input event ...
@ -141,7 +142,7 @@ export function PermissionsCheckbox(): JSX.Element {
<div> <div>
<input <input
checked={extendedPermissionsEnabled} checked={extendedPermissionsEnabled}
onChange={(x) => handleExtendedPerm(x.target.checked)} onChange={() => handleExtendedPerm()}
type="checkbox" type="checkbox"
id="checkbox-perm" id="checkbox-perm"
style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}

View File

@ -25,19 +25,20 @@ import * as i18n from "../i18n";
import { renderAmount } from "../renderHtml"; import { renderAmount } from "../renderHtml";
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "preact/hooks";
import { import {
acceptWithdrawal, acceptWithdrawal,
onUpdateNotification, onUpdateNotification,
getWithdrawalDetailsForUri, getWithdrawalDetailsForUri,
} from "../wxApi"; } from "../wxApi";
import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util"; import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
import { JSX } from "preact/jsx-runtime";
function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element { function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(); const [details, setDetails] = useState<WithdrawUriInfoResponse | undefined>(undefined);
const [selectedExchange, setSelectedExchange] = useState< const [selectedExchange, setSelectedExchange] = useState<
string | undefined string | undefined
>(); >(undefined);
const talerWithdrawUri = props.talerWithdrawUri; const talerWithdrawUri = props.talerWithdrawUri;
const [cancelled, setCancelled] = useState(false); const [cancelled, setCancelled] = useState(false);
const [selecting, setSelecting] = useState(false); const [selecting, setSelecting] = useState(false);
@ -48,7 +49,6 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
return onUpdateNotification(() => { return onUpdateNotification(() => {
setUpdateCounter(updateCounter + 1); setUpdateCounter(updateCounter + 1);
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -23,12 +23,13 @@
/** /**
* Imports. * Imports.
*/ */
import React from "react";
import { import {
AmountJson, AmountJson,
Amounts, Amounts,
amountFractionalBase, amountFractionalBase,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { Component, ComponentChildren, JSX } from "preact";
import { JSXInternal } from "preact/src/jsx";
/** /**
* Render amount as HTML, which non-breaking space between * Render amount as HTML, which non-breaking space between
@ -87,7 +88,7 @@ interface CollapsibleProps {
* Component that shows/hides its children when clicking * Component that shows/hides its children when clicking
* a heading. * a heading.
*/ */
export class Collapsible extends React.Component< export class Collapsible extends Component<
CollapsibleProps, CollapsibleProps,
CollapsibleState CollapsibleState
> { > {
@ -139,24 +140,18 @@ export function ExpanderText({ text }: ExpanderTextProps): JSX.Element {
return <span>{text}</span>; return <span>{text}</span>;
} }
export interface LoadingButtonProps { export interface LoadingButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> {
loading: boolean; isLoading: boolean;
} }
export function ProgressButton( export function ProgressButton({isLoading, ...rest}: LoadingButtonProps): JSX.Element {
props: React.PropsWithChildren<LoadingButtonProps> &
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>,
): JSX.Element {
return ( return (
<button <button
className="pure-button pure-button-primary" className="pure-button pure-button-primary"
type="button" type="button"
{...props} {...rest}
> >
{props.loading ? ( {isLoading ? (
<span> <span>
<object <object
className="svg-icon svg-baseline" className="svg-icon svg-baseline"
@ -164,13 +159,13 @@ export function ProgressButton(
/> />
</span> </span>
) : null}{" "} ) : null}{" "}
{props.children} {rest.children}
</button> </button>
); );
} }
export function PageLink( export function PageLink(
props: React.PropsWithChildren<{ pageName: string }>, props: { pageName: string, children?: ComponentChildren },
): JSX.Element { ): JSX.Element {
const url = chrome.extension.getURL(`/${props.pageName}`); const url = chrome.extension.getURL(`/${props.pageName}`);
return ( return (

View File

@ -146,7 +146,7 @@ async function dispatch(
} }
break; break;
} }
default: default: {
const w = currentWallet; const w = currentWallet;
if (!w) { if (!w) {
r = { r = {
@ -163,6 +163,7 @@ async function dispatch(
} }
r = await w.handleCoreApiRequest(req.operation, req.id, req.payload); r = await w.handleCoreApiRequest(req.operation, req.id, req.payload);
break; break;
}
} }
try { try {

View File

@ -0,0 +1,24 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
// This fixed an error related to the CSS and loading gif breaking my Jest test
// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets
export default 'test-file-stub';

View File

@ -0,0 +1,31 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
// fileTransformer.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
module.exports = {
process(src, filename, config, options) {
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
},
};

View File

@ -0,0 +1,33 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import 'regenerator-runtime/runtime'
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-preact-pure';
configure({
adapter: new Adapter()
});
// Polyfill for encoding which isn't present globally in jsdom
import { TextEncoder, TextDecoder } from 'util'
global.TextEncoder = TextEncoder
global.TextDecoder = TextDecoder

View File

@ -14,12 +14,11 @@
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 test from "ava"; // import * as test from "ava";
import { internalSetStrings, str, Translate, strings } from "./i18n"; import { internalSetStrings, str, Translate } from "../src/i18n";
import React from "react"; import { render, configure } from "enzyme";
import { render } from "enzyme"; import Adapter from 'enzyme-adapter-preact-pure';
import { configure } from "enzyme"; import { h } from "preact";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });
@ -39,30 +38,31 @@ const testStrings = {
}, },
}; };
test("str translation", (t) => { test("str translation", (done) => {
// Alias, so we nly use the function for lookups, not for string extranction. // Alias, so we nly use the function for lookups, not for string extranction.
const strAlias = str; const strAlias = str;
const TranslateAlias = Translate; const TranslateAlias = Translate;
internalSetStrings(testStrings); internalSetStrings(testStrings);
t.is(strAlias`str1`, "foo1"); expect(strAlias`str1`).toEqual("foo1");
t.is(strAlias`str2`, "str2"); expect(strAlias`str2`).toEqual("str2");
const a = "a"; const a = "a";
const b = "b"; const b = "b";
t.is(strAlias`str3 ${a} / ${b}`, "foo3 b ; a"); expect(strAlias`str3 ${a} / ${b}`).toEqual("foo3 b ; a");
const r = render(<TranslateAlias>str1</TranslateAlias>); const r = render(<TranslateAlias>str1</TranslateAlias>);
t.is(r.text(), "foo1"); expect(r.text()).toEqual("foo1");
const r2 = render( const r2 = render(
<TranslateAlias> <TranslateAlias>
str3 <span>{a}</span> / <span>{b}</span> str3 <span>{a}</span> / <span>{b}</span>
</TranslateAlias>, </TranslateAlias>,
); );
t.is(r2.text(), "foo3 b ; a"); expect(r2.text()).toEqual("foo3 b ; a");
t.pass(); done();
}); });
test("existing str translation", (t) => { // test.default("existing str translation", (t) => {
internalSetStrings(strings); // internalSetStrings(strings);
t.pass(); // t.pass();
}); // });

View File

@ -2,9 +2,9 @@
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"lib": ["es6", "DOM"], "lib": ["es6", "DOM"],
"jsx": "react", "jsx": "react-jsx",
"jsxImportSource": "preact",
"moduleResolution": "Node", "moduleResolution": "Node",
"reactNamespace": "React",
"module": "ESNext", "module": "ESNext",
"target": "ES6", "target": "ES6",
"noImplicitAny": true, "noImplicitAny": true,

File diff suppressed because it is too large Load Diff