replace jest with mocha

This commit is contained in:
Sebastian 2022-01-04 17:06:17 -03:00
parent 2e71117f59
commit 9f8139e09b
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
56 changed files with 1789 additions and 4260 deletions

View File

@ -45,7 +45,6 @@
"@storybook/preact": "^6.3.12", "@storybook/preact": "^6.3.12",
"@storybook/preset-scss": "^1.0.3", "@storybook/preset-scss": "^1.0.3",
"@types/enzyme": "^3.10.10", "@types/enzyme": "^3.10.10",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.3.0", "@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0", "@typescript-eslint/parser": "^5.3.0",
"bulma": "^0.9.3", "bulma": "^0.9.3",
@ -55,20 +54,11 @@
"enzyme-adapter-preact-pure": "^3.2.0", "enzyme-adapter-preact-pure": "^3.2.0",
"eslint": "^8.1.0", "eslint": "^8.1.0",
"eslint-config-preact": "^1.2.0", "eslint-config-preact": "^1.2.0",
"jest": "^27.3.1",
"jest-preset-preact": "^4.0.5",
"jssha": "^3.2.0", "jssha": "^3.2.0",
"preact-cli": "^3.3.1", "preact-cli": "^3.3.1",
"sass": "1.32.13", "sass": "1.32.13",
"sass-loader": "^10", "sass-loader": "^10",
"sirv-cli": "^1.0.14", "sirv-cli": "^1.0.14",
"typescript": "^4.4.4" "typescript": "^4.4.4"
},
"jest": {
"preset": "jest-preset-preact",
"setupFiles": [
"<rootDir>/tests/__mocks__/browserMocks.ts",
"<rootDir>/tests/__mocks__/setupTests.ts"
]
} }
} }

View File

@ -48,14 +48,14 @@ module.exports = {
webpackFinal: (config) => { webpackFinal: (config) => {
// should be removed after storybook 6.3 // should be removed after storybook 6.3
// https://github.com/storybookjs/storybook/issues/12853#issuecomment-821576113 // https://github.com/storybookjs/storybook/issues/12853#issuecomment-821576113
// removing workaround since this creates another problem // removing workaround since this creates another problem
// https://github.com/storybookjs/storybook/issues/16623 // https://github.com/storybookjs/storybook/issues/16623
// https://github.com/nodejs/node/issues/33460 // https://github.com/nodejs/node/issues/33460
// FIXME: remove this comments in 2022 if no problem arise // FIXME: remove this comments in 2022 if no problem arise
// config.resolve.alias = { // config.resolve.alias = {
// react: "preact/compat", // react: "preact/compat",
// "react-dom": "preact/compat", // "react-dom": "preact/compat",
// }; // };
// we need to add @linaria loader AFTER the babel-loader // we need to add @linaria loader AFTER the babel-loader
// https://github.com/callstack/linaria/blob/master/docs/BUNDLERS_INTEGRATION.md#webpack // https://github.com/callstack/linaria/blob/master/docs/BUNDLERS_INTEGRATION.md#webpack
@ -65,7 +65,7 @@ module.exports = {
use: [ use: [
{ {
...(config.module.rules[0].use[0]), ...(config.module.rules[0].use[0]),
loader: 'babel-loader', loader: 'babel-loader',
}, },
{ {
loader: '@linaria/webpack-loader', loader: '@linaria/webpack-loader',
@ -74,7 +74,7 @@ module.exports = {
babelOptions: { babelOptions: {
presets: config.module.rules[0].use[0].options.presets, presets: config.module.rules[0].use[0].options.presets,
} }
// Pass the current babel options to linaria's babel instance // Pass the current babel options to linaria's babel instance
} }
} }
] ]

View File

@ -21,7 +21,6 @@ import { TranslationProvider } from '../src/context/translation'
import { PopupBox, WalletBox } from '../src/components/styled' import { PopupBox, WalletBox } from '../src/components/styled'
export const parameters = { export const parameters = {
controls: { expanded: true }, controls: { expanded: true },
actions: { argTypesRegex: "^on[A-Z].*" },
} }
export const globalTypes = { export const globalTypes = {

View File

@ -2,6 +2,7 @@
# This file is in the public domain. # This file is in the public domain.
set -e set -e
mv node_modules{,_saved}
rm -rf dist lib tsconfig.tsbuildinfo rm -rf dist lib tsconfig.tsbuildinfo
(cd ../.. && rm -rf build/web && ./contrib/build-fast-web.sh) (cd ../.. && rm -rf build/web && ./contrib/build-fast-web.sh)
rm -rf extension/ rm -rf extension/
@ -9,3 +10,4 @@ rm -rf extension/
(cd extension/v2 && unzip taler*.zip) (cd extension/v2 && unzip taler*.zip)
(cd extension/v3 && unzip taler*.zip) (cd extension/v3 && unzip taler*.zip)
mv node_modules{_saved,}

View File

@ -9,8 +9,9 @@
"private": false, "private": false,
"scripts": { "scripts": {
"clean": "rimraf dist lib tsconfig.tsbuildinfo", "clean": "rimraf dist lib tsconfig.tsbuildinfo",
"test": "jest ./tests", "test": "mocha --enable-source-maps 'dist/**/*.test.js'",
"compile": "tsc && rollup -c", "test:coverage": "nyc pnpm test",
"compile": "rollup -c -m",
"build-storybook": "build-storybook", "build-storybook": "build-storybook",
"storybook": "start-storybook -s . -p 6006", "storybook": "start-storybook -s . -p 6006",
"pretty": "prettier --write src", "pretty": "prettier --write src",
@ -41,22 +42,21 @@
"@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",
"@rollup/plugin-typescript": "^8.3.0",
"@storybook/addon-a11y": "^6.2.9", "@storybook/addon-a11y": "^6.2.9",
"@storybook/addon-essentials": "^6.2.9", "@storybook/addon-essentials": "^6.2.9",
"@storybook/preact": "^6.2.9", "@storybook/preact": "6.4.9",
"@testing-library/preact": "^2.0.1", "@testing-library/preact": "^2.0.1",
"@testing-library/preact-hooks": "^1.1.0",
"@types/chrome": "^0.0.128", "@types/chrome": "^0.0.128",
"@types/enzyme": "^3.10.10",
"@types/history": "^4.7.8", "@types/history": "^4.7.8",
"@types/jest": "^26.0.23", "@types/mocha": "^9.0.0",
"@types/node": "^14.14.22", "@types/node": "^14.14.22",
"ava": "3.15.0", "ava": "3.15.0",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"babel-plugin-transform-react-jsx": "^6.24.1", "babel-plugin-transform-react-jsx": "^6.24.1",
"enzyme": "^3.11.0", "mocha": "^9.1.3",
"enzyme-adapter-preact-pure": "^3.1.0", "nyc": "^15.1.0",
"jest": "^26.6.3",
"jest-preset-preact": "^4.0.2",
"preact-cli": "^3.0.5", "preact-cli": "^3.0.5",
"preact-render-to-string": "^5.1.19", "preact-render-to-string": "^5.1.19",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@ -68,17 +68,10 @@
"storybook-dark-mode": "^1.0.8", "storybook-dark-mode": "^1.0.8",
"typescript": "^4.1.3" "typescript": "^4.1.3"
}, },
"jest": { "nyc": {
"preset": "jest-preset-preact", "include": [
"setupFiles": [ "**"
"<rootDir>/tests/__mocks__/setupTests.ts"
], ],
"moduleNameMapper": { "exclude": []
"\\.(css|less)$": "identity-obj-proxy",
"@linaria/react": "<rootDir>/tests/__mocks__/linaria.ts"
},
"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

@ -8,48 +8,77 @@ import nodeResolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace"; import replace from "@rollup/plugin-replace";
import css from 'rollup-plugin-css-only'; import css from 'rollup-plugin-css-only';
import ignore from "rollup-plugin-ignore"; import ignore from "rollup-plugin-ignore";
import typescript from '@rollup/plugin-typescript';
import path from 'path';
import fs from 'fs';
function fromDir(startPath, regex) {
if (!fs.existsSync(startPath)) {
return;
}
const files = fs.readdirSync(startPath);
const result = files.flatMap(file => {
const filename = path.join(startPath, file);
const stat = fs.lstatSync(filename);
if (stat.isDirectory()) {
return fromDir(filename, regex);
}
else if (regex.test(filename)) {
return filename
}
}).filter(x => !!x)
return result
}
const makePlugins = () => [ const makePlugins = () => [
alias({ typescript({
entries: [ outputToFilesystem: false,
{ find: 'react', replacement: 'preact/compat' }, }),
{ find: 'react-dom', replacement: 'preact/compat' }
]
}),
ignore(["module", "os"]), alias({
nodeResolve({ entries: [
browser: true, { find: 'react', replacement: 'preact/compat' },
preferBuiltins: true, { find: 'react-dom', replacement: 'preact/compat' }
}), ]
}),
//terser(), ignore(["module", "os"]),
nodeResolve({
browser: true,
preferBuiltins: true,
}),
replace({ //terser(),
"process.env.NODE_ENV": JSON.stringify("production"),
"__filename": "'__webextension__'",
}),
commonjs({
include: [/node_modules/, /dist/],
extensions: [".js"],
ignoreGlobal: true,
sourceMap: true,
}),
json(), replace({
image(), "process.env.NODE_ENV": JSON.stringify("production"),
// "__filename": "'__webextension__'",
preventAssignment: true
}),
commonjs({
include: [/node_modules/, /dist/],
extensions: [".js"],
ignoreGlobal: true,
sourceMap: true,
}),
json(),
image(),
linaria({
sourceMap: process.env.NODE_ENV !== 'production',
}),
linaria({
sourceMap: process.env.NODE_ENV !== 'production',
}),
]; ];
const webExtensionWalletEntryPoint = { const webExtensionWalletEntryPoint = {
input: "lib/walletEntryPoint.js", input: "src/walletEntryPoint.tsx",
output: { output: {
file: "dist/walletEntryPoint.js", file: "dist/walletEntryPoint.js",
format: "iife", format: "iife",
@ -60,12 +89,12 @@ const webExtensionWalletEntryPoint = {
...makePlugins(), ...makePlugins(),
css({ css({
output: 'walletEntryPoint.css', output: 'walletEntryPoint.css',
}), }),
], ],
}; };
const webExtensionPopupEntryPoint = { const webExtensionPopupEntryPoint = {
input: "lib/popupEntryPoint.js", input: "src/popupEntryPoint.tsx",
output: { output: {
file: "dist/popupEntryPoint.js", file: "dist/popupEntryPoint.js",
format: "iife", format: "iife",
@ -76,12 +105,12 @@ const webExtensionPopupEntryPoint = {
...makePlugins(), ...makePlugins(),
css({ css({
output: 'popupEntryPoint.css', output: 'popupEntryPoint.css',
}), }),
], ],
}; };
const webExtensionBackgroundPageScript = { const webExtensionBackgroundPageScript = {
input: "lib/background.js", input: "src/background.ts",
output: { output: {
file: "dist/background.js", file: "dist/background.js",
format: "iife", format: "iife",
@ -92,7 +121,7 @@ const webExtensionBackgroundPageScript = {
}; };
const webExtensionCryptoWorker = { const webExtensionCryptoWorker = {
input: "lib/browserWorkerEntry.js", input: "src/browserWorkerEntry.ts",
output: { output: {
file: "dist/browserWorkerEntry.js", file: "dist/browserWorkerEntry.js",
format: "iife", format: "iife",
@ -102,9 +131,26 @@ const webExtensionCryptoWorker = {
plugins: makePlugins(), plugins: makePlugins(),
}; };
const tests = fromDir('./src', /.test.ts$/).map(test => ({
input: test,
output: {
file: test.replace(/^src/, 'dist').replace(/\.ts$/, '.js'),
format: "iife",
exports: "none",
name: test,
},
plugins: [
...makePlugins(),
css({
output: 'walletEntryPoint.css',
}),
],
}))
export default [ export default [
webExtensionPopupEntryPoint, webExtensionPopupEntryPoint,
webExtensionWalletEntryPoint, webExtensionWalletEntryPoint,
webExtensionBackgroundPageScript, webExtensionBackgroundPageScript,
webExtensionCryptoWorker, webExtensionCryptoWorker,
...tests,
]; ];

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha Tests</title>
<link rel="stylesheet" href="node_modules/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<!-- load code you want to test here -->
<!-- script src="dist/stories.test.js"></script -->
<script src="dist/hooks/useTalerActionURL.test.js"></script>
<!-- load your test files here -->
<script>
mocha.run();
</script>
</body>
</html>

View File

@ -37,7 +37,7 @@ export enum Pages {
deposit = "/deposit/:currency", deposit = "/deposit/:currency",
settings = "/settings", settings = "/settings",
dev = "/dev", dev = "/dev",
cta = "/cta", cta = "/cta/:action",
backup = "/backup", backup = "/backup",
history = "/history", history = "/history",
transaction = "/transaction/:tid", transaction = "/transaction/:tid",

View File

@ -0,0 +1,24 @@
export async function findTalerUriInActiveTab(): Promise<string | undefined> {
return new Promise((resolve, reject) => {
chrome.tabs.executeScript(
{
code: `
(() => {
let x = document.querySelector("a[href^='taler://'") || document.querySelector("a[href^='taler+http://'");
return x ? x.href.toString() : null;
})();
`,
allFrames: false,
},
(result) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
resolve(undefined);
return;
}
resolve(result[0]);
},
);
});
}

View File

@ -1,64 +0,0 @@
"use strict";
/*
This file is part of TALER
(C) 2017 INRIA
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.
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
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPermissionsApi = exports.isNode = exports.isFirefox = void 0;
/**
* Compatibility helpers needed for browsers that don't implement
* WebExtension APIs consistently.
*/
function isFirefox() {
const rt = chrome.runtime;
if (typeof rt.getBrowserInfo === "function") {
return true;
}
return false;
}
exports.isFirefox = isFirefox;
/**
* Check if we are running under nodejs.
*/
function isNode() {
return typeof process !== "undefined" && process.release.name === "node";
}
exports.isNode = isNode;
function getPermissionsApi() {
const myBrowser = globalThis.browser;
if (
typeof myBrowser === "object" &&
typeof myBrowser.permissions === "object"
) {
return {
addPermissionsListener: () => {
// Not supported yet.
},
contains: myBrowser.permissions.contains,
request: myBrowser.permissions.request,
remove: myBrowser.permissions.remove,
};
} else {
return {
addPermissionsListener: chrome.permissions.onAdded.addListener.bind(
chrome.permissions.onAdded,
),
contains: chrome.permissions.contains,
request: chrome.permissions.request,
remove: chrome.permissions.remove,
};
}
}
exports.getPermissionsApi = getPermissionsApi;
//# sourceMappingURL=compat.js.map

View File

@ -19,7 +19,7 @@ import { h, VNode } from "preact";
import { import {
ButtonPrimary, ButtonPrimary,
TableWithRoundRows as TableWithRoundedRows, TableWithRoundRows as TableWithRoundedRows,
} from "./styled/index"; } from "./styled";
export function BalanceTable({ export function BalanceTable({
balances, balances,

View File

@ -14,7 +14,7 @@
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 { Outlined, StyledCheckboxLabel } from "./styled/index"; import { Outlined, StyledCheckboxLabel } from "./styled";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
interface Props { interface Props {

View File

@ -29,10 +29,11 @@ export function ErrorTalerOperation({
}): VNode | null { }): VNode | null {
const { devMode } = useDevContext(); const { devMode } = useDevContext();
const [showErrorDetail, setShowErrorDetail] = useState(false); const [showErrorDetail, setShowErrorDetail] = useState(false);
if (!title || !error) return null; if (!title || !error) return null;
// const errorCode: number | undefined = (error.details as any)?.errorResponse?.code // const errorCode: number | undefined = (error.details as any)?.errorResponse?.code
const errorHint: string | undefined = (error.details as any)?.errorResponse?.hint const errorHint: string | undefined = (error.details as any)?.errorResponse
?.hint;
return ( return (
<ErrorBox style={{ paddingTop: 0, paddingBottom: 0 }}> <ErrorBox style={{ paddingTop: 0, paddingBottom: 0 }}>
@ -53,11 +54,11 @@ export function ErrorTalerOperation({
<div style={{ padding: 5, textAlign: "left" }}> <div style={{ padding: 5, textAlign: "left" }}>
<div>{error.message}</div> <div>{error.message}</div>
</div> </div>
{errorHint && {errorHint && (
<div style={{ padding: 5, textAlign: "left" }}> <div style={{ padding: 5, textAlign: "left" }}>
<div>{errorHint}</div> <div>{errorHint}</div>
</div> </div>
} )}
{devMode && ( {devMode && (
<div style={{ textAlign: "left", overflowX: "auto" }}> <div style={{ textAlign: "left", overflowX: "auto" }}>
<pre>{JSON.stringify(error, undefined, 2)}</pre> <pre>{JSON.stringify(error, undefined, 2)}</pre>

View File

@ -15,7 +15,7 @@
*/ */
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { NiceSelect } from "./styled/index"; import { NiceSelect } from "./styled";
interface Props { interface Props {
value?: string; value?: string;

View File

@ -34,7 +34,7 @@ import {
SmallLightText, SmallLightText,
LargeText, LargeText,
LightText, LightText,
} from "./styled/index"; } from "./styled";
import { Time } from "./Time"; import { Time } from "./Time";
export function TransactionItem(props: { export function TransactionItem(props: {

View File

@ -0,0 +1,49 @@
/*
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 { createContext, h, VNode } from "preact";
import { useContext } from "preact/hooks";
import { findTalerUriInActiveTab } from "../api/browser";
interface Type {
findTalerUriInActiveTab: () => Promise<string | undefined>;
}
const Context = createContext<Type>({
findTalerUriInActiveTab: async () => undefined,
});
/**
* Inversion of control Context
*
* This context act as a proxy between API that need to be replaced in
* different environments
*
* @returns
*/
export const useIocContext = (): Type => useContext(Context);
export const IoCProviderForTesting = ({ value, children }: { value: Type, children: any }): VNode => {
return h(Context.Provider, { value, children });
};
export const IoCProviderForRuntime = ({ children }: { children: any }): VNode => {
return h(Context.Provider, { value: { findTalerUriInActiveTab }, children });
};

View File

@ -34,13 +34,13 @@ export const NoBalance = createExample(TestedComponent, {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
noncePriv: "", noncePriv: "",
proposalId: "proposal1234", proposalId: "proposal1234",
contractTerms: ({ contractTerms: {
merchant: { merchant: {
name: "someone", name: "someone",
}, },
summary: "some beers", summary: "some beers",
amount: "USD:10", amount: "USD:10",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
amountRaw: "USD:10", amountRaw: "USD:10",
}, },
}); });
@ -50,13 +50,13 @@ export const NoEnoughBalance = createExample(TestedComponent, {
status: PreparePayResultType.InsufficientBalance, status: PreparePayResultType.InsufficientBalance,
noncePriv: "", noncePriv: "",
proposalId: "proposal1234", proposalId: "proposal1234",
contractTerms: ({ contractTerms: {
merchant: { merchant: {
name: "someone", name: "someone",
}, },
summary: "some beers", summary: "some beers",
amount: "USD:10", amount: "USD:10",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
amountRaw: "USD:10", amountRaw: "USD:10",
}, },
balance: { balance: {
@ -67,42 +67,40 @@ export const NoEnoughBalance = createExample(TestedComponent, {
}); });
export const PaymentPossible = createExample(TestedComponent, { export const PaymentPossible = createExample(TestedComponent, {
uri: uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
"taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
payStatus: { payStatus: {
status: PreparePayResultType.PaymentPossible, status: PreparePayResultType.PaymentPossible,
amountEffective: "USD:10", amountEffective: "USD:10",
amountRaw: "USD:10", amountRaw: "USD:10",
noncePriv: "", noncePriv: "",
contractTerms: ({ contractTerms: {
nonce: "123213123", nonce: "123213123",
merchant: { merchant: {
name: "someone", name: "someone",
}, },
amount: "USD:10", amount: "USD:10",
summary: "some beers", summary: "some beers",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
contractTermsHash: "123456", contractTermsHash: "123456",
proposalId: "proposal1234", proposalId: "proposal1234",
}, },
}); });
export const PaymentPossibleWithFee = createExample(TestedComponent, { export const PaymentPossibleWithFee = createExample(TestedComponent, {
uri: uri: "taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
"taler://pay/merchant-backend.taler/2021.242-01G2X4275RBWG/?c=66BE594PDZR24744J6EQK52XM0",
payStatus: { payStatus: {
status: PreparePayResultType.PaymentPossible, status: PreparePayResultType.PaymentPossible,
amountEffective: "USD:10.20", amountEffective: "USD:10.20",
amountRaw: "USD:10", amountRaw: "USD:10",
noncePriv: "", noncePriv: "",
contractTerms: ({ contractTerms: {
nonce: "123213123", nonce: "123213123",
merchant: { merchant: {
name: "someone", name: "someone",
}, },
amount: "USD:10", amount: "USD:10",
summary: "some beers", summary: "some beers",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
contractTermsHash: "123456", contractTermsHash: "123456",
proposalId: "proposal1234", proposalId: "proposal1234",
}, },
@ -113,7 +111,7 @@ export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
status: PreparePayResultType.AlreadyConfirmed, status: PreparePayResultType.AlreadyConfirmed,
amountEffective: "USD:10", amountEffective: "USD:10",
amountRaw: "USD:10", amountRaw: "USD:10",
contractTerms: ({ contractTerms: {
merchant: { merchant: {
name: "someone", name: "someone",
}, },
@ -121,7 +119,7 @@ export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
"congratulations! you are looking at the fulfillment message! ", "congratulations! you are looking at the fulfillment message! ",
summary: "some beers", summary: "some beers",
amount: "USD:10", amount: "USD:10",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
contractTermsHash: "123456", contractTermsHash: "123456",
proposalId: "proposal1234", proposalId: "proposal1234",
paid: false, paid: false,
@ -135,13 +133,13 @@ export const AlreadyConfirmedWithoutFullfilment = createExample(
status: PreparePayResultType.AlreadyConfirmed, status: PreparePayResultType.AlreadyConfirmed,
amountEffective: "USD:10", amountEffective: "USD:10",
amountRaw: "USD:10", amountRaw: "USD:10",
contractTerms: ({ contractTerms: {
merchant: { merchant: {
name: "someone", name: "someone",
}, },
summary: "some beers", summary: "some beers",
amount: "USD:10", amount: "USD:10",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
contractTermsHash: "123456", contractTermsHash: "123456",
proposalId: "proposal1234", proposalId: "proposal1234",
paid: false, paid: false,
@ -154,7 +152,7 @@ export const AlreadyPaid = createExample(TestedComponent, {
status: PreparePayResultType.AlreadyConfirmed, status: PreparePayResultType.AlreadyConfirmed,
amountEffective: "USD:10", amountEffective: "USD:10",
amountRaw: "USD:10", amountRaw: "USD:10",
contractTerms: ({ contractTerms: {
merchant: { merchant: {
name: "someone", name: "someone",
}, },
@ -162,7 +160,7 @@ export const AlreadyPaid = createExample(TestedComponent, {
"congratulations! you are looking at the fulfillment message! ", "congratulations! you are looking at the fulfillment message! ",
summary: "some beers", summary: "some beers",
amount: "USD:10", amount: "USD:10",
} as Partial<ContractTerms>) as any, } as Partial<ContractTerms> as any,
contractTermsHash: "123456", contractTermsHash: "123456",
proposalId: "proposal1234", proposalId: "proposal1234",
paid: true, paid: true,

View File

@ -135,7 +135,9 @@ export function PayPage({
? Amounts.parseOrThrow(foundBalance.available) ? Amounts.parseOrThrow(foundBalance.available)
: undefined; : undefined;
// We use a string here so that dependency tracking for useEffect works properly // We use a string here so that dependency tracking for useEffect works properly
const foundAmountStr = foundAmount ? Amounts.stringify(foundAmount) : undefined; const foundAmountStr = foundAmount
? Amounts.stringify(foundAmount)
: undefined;
useEffect(() => { useEffect(() => {
if (!talerPayUri) return; if (!talerPayUri) return;

View File

@ -35,10 +35,10 @@ export const Complete = createExample(TestedComponent, {
amountRefundGone: "USD:0", amountRefundGone: "USD:0",
amountRefundGranted: "USD:2", amountRefundGranted: "USD:2",
contractTermsHash: "QWEASDZXC", contractTermsHash: "QWEASDZXC",
info: ({ info: {
summary: "tasty cold beer", summary: "tasty cold beer",
contractTermsHash: "QWEASDZXC", contractTermsHash: "QWEASDZXC",
} as Partial<OrderShortInfo>) as any, } as Partial<OrderShortInfo> as any,
pendingAtExchange: false, pendingAtExchange: false,
proposalId: "proposal123", proposalId: "proposal123",
}, },
@ -50,10 +50,10 @@ export const Partial = createExample(TestedComponent, {
amountRefundGone: "USD:1", amountRefundGone: "USD:1",
amountRefundGranted: "USD:2", amountRefundGranted: "USD:2",
contractTermsHash: "QWEASDZXC", contractTermsHash: "QWEASDZXC",
info: ({ info: {
summary: "tasty cold beer", summary: "tasty cold beer",
contractTermsHash: "QWEASDZXC", contractTermsHash: "QWEASDZXC",
} as Partial<OrderShortInfo>) as any, } as Partial<OrderShortInfo> as any,
pendingAtExchange: false, pendingAtExchange: false,
proposalId: "proposal123", proposalId: "proposal123",
}, },
@ -65,10 +65,10 @@ export const InProgress = createExample(TestedComponent, {
amountRefundGone: "USD:1", amountRefundGone: "USD:1",
amountRefundGranted: "USD:2", amountRefundGranted: "USD:2",
contractTermsHash: "QWEASDZXC", contractTermsHash: "QWEASDZXC",
info: ({ info: {
summary: "tasty cold beer", summary: "tasty cold beer",
contractTermsHash: "QWEASDZXC", contractTermsHash: "QWEASDZXC",
} as Partial<OrderShortInfo>) as any, } as Partial<OrderShortInfo> as any,
pendingAtExchange: true, pendingAtExchange: true,
proposalId: "proposal123", proposalId: "proposal123",
}, },

View File

@ -10,7 +10,7 @@ import {
WarningBox, WarningBox,
WarningText, WarningText,
} from "../components/styled"; } from "../components/styled";
import { TermsState } from "../utils"; import { TermsState } from "../utils/index";
interface Props { interface Props {
reviewing: boolean; reviewing: boolean;

View File

@ -24,12 +24,16 @@ import { createExample } from "../test-utils";
import { termsHtml, termsPdf, termsPlain, termsXml } from "./termsExample"; import { termsHtml, termsPdf, termsPlain, termsXml } from "./termsExample";
import { View as TestedComponent } from "./Withdraw"; import { View as TestedComponent } from "./Withdraw";
function parseFromString(s: string): Document {
if (typeof window === "undefined") {
return {} as Document;
}
return new window.DOMParser().parseFromString(s, "text/xml");
}
export default { export default {
title: "cta/withdraw", title: "cta/withdraw",
component: TestedComponent, component: TestedComponent,
argTypes: {
onSwitchExchange: { action: "onRetry" },
},
}; };
const exchangeList: ExchangeListItem[] = [ const exchangeList: ExchangeListItem[] = [
@ -77,7 +81,7 @@ export const NewTerms = createExample(TestedComponent, {
terms: { terms: {
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
status: "new", status: "new",
version: "", version: "",
@ -192,7 +196,7 @@ export const TermsReviewingXML = createExample(TestedComponent, {
terms: { terms: {
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
status: "new", status: "new",
version: "", version: "",
@ -219,7 +223,7 @@ export const NewTermsAccepted = createExample(TestedComponent, {
terms: { terms: {
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
status: "new", status: "new",
version: "", version: "",
@ -247,7 +251,7 @@ export const TermsShowAgainXML = createExample(TestedComponent, {
terms: { terms: {
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
version: "", version: "",
status: "new", status: "new",
@ -276,7 +280,7 @@ export const TermsChanged = createExample(TestedComponent, {
terms: { terms: {
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
version: "", version: "",
status: "changed", status: "changed",
@ -351,7 +355,7 @@ export const WithoutFee = createExample(TestedComponent, {
terms: { terms: {
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
status: "accepted", status: "accepted",
version: "", version: "",

View File

@ -40,7 +40,11 @@ import {
WalletAction, WalletAction,
} from "../components/styled"; } from "../components/styled";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { amountToString, buildTermsOfServiceState, TermsState } from "../utils"; import {
amountToString,
buildTermsOfServiceState,
TermsState,
} from "../utils/index";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { TermsOfServiceSection } from "./TermsOfServiceSection"; import { TermsOfServiceSection } from "./TermsOfServiceSection";

View File

@ -43,8 +43,8 @@ function getStatusPaidOrder(a: ProviderPaymentPaid, b: ProviderPaymentPaid) {
return a.paidUntil.t_ms === "never" return a.paidUntil.t_ms === "never"
? -1 ? -1
: b.paidUntil.t_ms === "never" : b.paidUntil.t_ms === "never"
? 1 ? 1
: a.paidUntil.t_ms - b.paidUntil.t_ms; : a.paidUntil.t_ms - b.paidUntil.t_ms;
} }
export function useBackupStatus(): BackupStatus | undefined { export function useBackupStatus(): BackupStatus | undefined {

View File

@ -0,0 +1,60 @@
/*
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/>
*/
import { useTalerActionURL } from "./useTalerActionURL"
import { justBrowser_it, mountBrowser } from "../test-utils";
import { IoCProviderForTesting } from "../context/iocContext";
import { h, VNode } from "preact";
import { act } from "preact/test-utils";
describe('useTalerActionURL hook', () => {
// eslint-disable-next-line jest/expect-expect
justBrowser_it('should be set url to undefined when dismiss', async () => {
const ctx = ({ children }: { children: any }): VNode => {
return h(IoCProviderForTesting, {
value: {
findTalerUriInActiveTab: async () => "asd",
}, children
})
}
const { result, waitNextUpdate } = mountBrowser(useTalerActionURL, ctx)
{
const [url] = result.current!
if (url !== undefined) throw Error('invalid')
}
await waitNextUpdate()
{
const [url] = result.current!
if (url !== "asd") throw Error(`invalid: ${url}`)
}
await act(() => {
const [, setDismissed] = result.current!
setDismissed(true)
})
{
const [url] = result.current!
if (url !== undefined) throw Error('invalid')
}
})
})

View File

@ -14,8 +14,8 @@
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 { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useIocContext } from "../context/iocContext";
export function useTalerActionURL(): [ export function useTalerActionURL(): [
string | undefined, string | undefined,
@ -25,6 +25,8 @@ export function useTalerActionURL(): [
undefined, undefined,
); );
const [dismissed, setDismissed] = useState(false); const [dismissed, setDismissed] = useState(false);
const { findTalerUriInActiveTab } = useIocContext()
useEffect(() => { useEffect(() => {
async function check(): Promise<void> { async function check(): Promise<void> {
const talerUri = await findTalerUriInActiveTab(); const talerUri = await findTalerUriInActiveTab();
@ -35,28 +37,3 @@ export function useTalerActionURL(): [
const url = dismissed ? undefined : talerActionUrl; const url = dismissed ? undefined : talerActionUrl;
return [url, setDismissed]; return [url, setDismissed];
} }
async function findTalerUriInActiveTab(): Promise<string | undefined> {
return new Promise((resolve, reject) => {
chrome.tabs.executeScript(
{
code: `
(() => {
let x = document.querySelector("a[href^='taler://'") || document.querySelector("a[href^='taler+http://'");
return x ? x.href.toString() : null;
})();
`,
allFrames: false,
},
(result) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
resolve(undefined);
return;
}
console.log("got result", result);
resolve(result[0]);
},
);
});
}

View File

@ -1,11 +1,7 @@
import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; import { classifyTalerUri, TalerUriType } 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 { import { Button, ButtonSuccess, InputWithLabel } from "../components/styled";
Button,
ButtonSuccess,
InputWithLabel,
} from "../components/styled/index";
import { actionForTalerUri } from "../utils/index"; import { actionForTalerUri } from "../utils/index";
export interface Props { export interface Props {

View File

@ -17,7 +17,7 @@
import { BalancesResponse, i18n } from "@gnu-taler/taler-util"; import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { BalanceTable } from "../components/BalanceTable"; import { BalanceTable } from "../components/BalanceTable";
import { ButtonPrimary, ErrorBox } from "../components/styled/index"; import { ButtonPrimary, ErrorBox } from "../components/styled";
import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { PageLink } from "../renderHtml"; import { PageLink } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";

View File

@ -32,7 +32,7 @@ export default {
}; };
export const AllOff = createExample(TestedComponent, { export const AllOff = createExample(TestedComponent, {
onDownloadDatabase: async () => "this is the content of the database", onDownloadDatabase: async () => "this is the content of the database",
operations: [ operations: [
{ {
type: PendingTaskType.ExchangeUpdate, type: PendingTaskType.ExchangeUpdate,

View File

@ -20,7 +20,7 @@ import { format } from "date-fns";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Diagnostics } from "../components/Diagnostics"; import { Diagnostics } from "../components/Diagnostics";
import { NotifyUpdateFadeOut } from "../components/styled/index"; import { NotifyUpdateFadeOut } from "../components/styled";
import { Time } from "../components/Time"; import { Time } from "../components/Time";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { useDiagnostics } from "../hooks/useDiagnostics"; import { useDiagnostics } from "../hooks/useDiagnostics";

View File

@ -23,7 +23,7 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
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 { ButtonPrimary } from "../components/styled/index"; import { ButtonPrimary } from "../components/styled";
import { TransactionItem } from "../components/TransactionItem"; import { TransactionItem } from "../components/TransactionItem";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
@ -133,7 +133,7 @@ export function HistoryView({
style={{ color: "darkgreen", textDecoration: "none" }} style={{ color: "darkgreen", textDecoration: "none" }}
href={ href={
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
chrome.extension typeof chrome !== "undefined" && chrome.extension
? // eslint-disable-next-line no-undef ? // eslint-disable-next-line no-undef
chrome.extension.getURL(`/static/wallet.html#/history`) chrome.extension.getURL(`/static/wallet.html#/history`)
: "#" : "#"

View File

@ -21,7 +21,7 @@
import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
import { Fragment, h } from "preact"; import { Fragment, h } from "preact";
import { ButtonPrimary, ButtonSuccess } from "../components/styled/index"; import { ButtonPrimary, ButtonSuccess } from "../components/styled";
import { actionForTalerUri } from "../utils/index"; import { actionForTalerUri } from "../utils/index";
export interface Props { export interface Props {

View File

@ -14,11 +14,16 @@
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/>
*/ */
/** /**
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
// This fixed an error related to the CSS and loading gif breaking my Jest test import * as a1 from "./AddNewActionView.stories";
// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets import * as a2 from "./Balance.stories";
export default 'test-file-stub'; import * as a3 from "./DeveloperPage.stories";
import * as a4 from "./History.stories";
import * as a5 from "./Popup.stories";
import * as a6 from "./TalerActionFound.stories";
export default [a1, a2, a3, a4, a5, a6];

View File

@ -22,7 +22,7 @@
import { setupI18n } from "@gnu-taler/taler-util"; import { setupI18n } from "@gnu-taler/taler-util";
import { createHashHistory } from "history"; import { createHashHistory } from "history";
import { render, h } from "preact"; import { render, h, VNode, Fragment } from "preact";
import Router, { route, Route } from "preact-router"; import Router, { route, Route } from "preact-router";
import { useEffect } from "preact/hooks"; import { useEffect } from "preact/hooks";
import { PopupBox } from "./components/styled"; import { PopupBox } from "./components/styled";
@ -39,6 +39,7 @@ import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
import { SettingsPage } from "./popup/Settings"; import { SettingsPage } from "./popup/Settings";
import { TalerActionFound } from "./popup/TalerActionFound"; import { TalerActionFound } from "./popup/TalerActionFound";
import { ExchangeAddPage } from "./wallet/ExchangeAddPage"; import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
import { IoCProviderForRuntime } from "./context/iocContext";
function main(): void { function main(): void {
try { try {
@ -63,87 +64,99 @@ if (document.readyState === "loading") {
main(); main();
} }
function Application() { function CheckTalerActionComponent(): VNode {
const [talerActionUrl, setDismissed] = useTalerActionURL(); const [talerActionUrl] = useTalerActionURL();
useEffect(() => { useEffect(() => {
if (talerActionUrl) route(Pages.cta); if (talerActionUrl)
route(Pages.cta.replace(":action", encodeURIComponent(talerActionUrl)));
}, [talerActionUrl]); }, [talerActionUrl]);
return <Fragment />;
}
function Application() {
return ( return (
<div> <div>
<DevContextProvider> <DevContextProvider>
<WalletNavBar /> <IoCProviderForRuntime>
<PopupBox> <WalletNavBar />
<Router history={createHashHistory()}> <CheckTalerActionComponent />
<Route path={Pages.dev} component={DeveloperPage} /> <PopupBox>
<Router history={createHashHistory()}>
<Route path={Pages.dev} component={DeveloperPage} />
<Route <Route
path={Pages.balance} path={Pages.balance}
component={BalancePage} component={BalancePage}
goToWalletManualWithdraw={() => goToWalletManualWithdraw={() =>
goToWalletPage(Pages.manual_withdraw) goToWalletPage(Pages.manual_withdraw)
} }
goToWalletDeposit={(currency: string) => goToWalletDeposit={(currency: string) =>
goToWalletPage(Pages.deposit.replace(":currency", currency)) goToWalletPage(Pages.deposit.replace(":currency", currency))
} }
/> />
<Route path={Pages.settings} component={SettingsPage} /> <Route path={Pages.settings} component={SettingsPage} />
<Route <Route
path={Pages.cta} path={Pages.cta}
component={() => ( component={function Action({ action }: { action: string }) {
<TalerActionFound const [, setDismissed] = useTalerActionURL();
url={talerActionUrl!}
onDismiss={() => {
setDismissed(true);
route(Pages.balance);
}}
/>
)}
/>
<Route return (
path={Pages.transaction} <TalerActionFound
component={({ tid }: { tid: string }) => url={decodeURIComponent(action)}
goToWalletPage(Pages.transaction.replace(":tid", tid)) onDismiss={() => {
} setDismissed(true);
/> route(Pages.balance);
}}
/>
);
}}
/>
<Route path={Pages.history} component={HistoryPage} /> <Route
path={Pages.transaction}
component={({ tid }: { tid: string }) =>
goToWalletPage(Pages.transaction.replace(":tid", tid))
}
/>
<Route <Route path={Pages.history} component={HistoryPage} />
path={Pages.backup}
component={BackupPage}
onAddProvider={() => {
route(Pages.provider_add);
}}
/>
<Route
path={Pages.provider_detail}
component={ProviderDetailPage}
onBack={() => {
route(Pages.backup);
}}
/>
<Route
path={Pages.provider_add}
component={ProviderAddPage}
onBack={() => {
route(Pages.backup);
}}
/>
<Route <Route
path={Pages.exchange_add} path={Pages.backup}
component={ExchangeAddPage} component={BackupPage}
onBack={() => { onAddProvider={() => {
route(Pages.balance); route(Pages.provider_add);
}} }}
/> />
<Route
path={Pages.provider_detail}
component={ProviderDetailPage}
onBack={() => {
route(Pages.backup);
}}
/>
<Route
path={Pages.provider_add}
component={ProviderAddPage}
onBack={() => {
route(Pages.backup);
}}
/>
<Route default component={Redirect} to={Pages.balance} /> <Route
</Router> path={Pages.exchange_add}
</PopupBox> component={ExchangeAddPage}
onBack={() => {
route(Pages.balance);
}}
/>
<Route default component={Redirect} to={Pages.balance} />
</Router>
</PopupBox>
</IoCProviderForRuntime>
</DevContextProvider> </DevContextProvider>
</div> </div>
); );

View File

@ -0,0 +1,51 @@
/*
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 * as popup from "./popup/index.stories";
import * as wallet from "./wallet/index.stories";
import { setupI18n } from "@gnu-taler/taler-util";
import { renderNodeOrBrowser } from "./test-utils";
setupI18n("en", { en: {} });
function testThisStory(st: any): any {
describe(`render examples for ${(st as any).default.title}`, () => {
Object.keys(st).forEach((k) => {
const Component = (st as any)[k];
if (k === "default" || !Component) return;
// eslint-disable-next-line jest/expect-expect
it(`example: ${k}`, () => {
renderNodeOrBrowser(Component, Component.args);
});
});
});
}
describe("render every storybook example", () => {
[popup, wallet].forEach(function testAll(st: any) {
if (Array.isArray(st.default)) {
st.default.forEach(testAll)
} else {
testThisStory(st)
}
});
});

View File

@ -14,13 +14,15 @@
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 { ComponentChildren, FunctionalComponent, h as render, VNode } from "preact"; import { PendingTestFunction, TestFunction } from "mocha";
import { ComponentChildren, Fragment, FunctionalComponent, h as create, render as renderIntoDom, VNode } from "preact";
import { render as renderToString } from "preact-render-to-string";
export function createExample<Props>( export function createExample<Props>(
Component: FunctionalComponent<Props>, Component: FunctionalComponent<Props>,
props: Partial<Props>, props: Partial<Props>,
): ComponentChildren { ): ComponentChildren {
const Render = (args: any) => render(Component, args); const Render = (args: any): VNode => create(Component, args);
Render.args = props; Render.args = props;
return Render; return Render;
} }
@ -31,12 +33,89 @@ export function createExampleWithCustomContext<Props, ContextProps>(
ContextProvider: FunctionalComponent<ContextProps>, ContextProvider: FunctionalComponent<ContextProps>,
contextProps: Partial<ContextProps>, contextProps: Partial<ContextProps>,
): ComponentChildren { ): ComponentChildren {
const Render = (args: any): VNode => render(Component, args); const Render = (args: any): VNode => create(Component, args);
const WithContext = (args: any): VNode => render(ContextProvider, { ...contextProps, children: [Render(args)] } as any); const WithContext = (args: any): VNode => create(ContextProvider, { ...contextProps, children: [Render(args)] } as any);
WithContext.args = props WithContext.args = props
return WithContext return WithContext
} }
export function NullLink({ children }: { children?: ComponentChildren }) { export function NullLink({ children }: { children?: ComponentChildren }): VNode {
return render("a", { children, href: "javascript:void(0);" }); return create("a", { children, href: "javascript:void(0);" });
} }
export function renderNodeOrBrowser(Component: any, args: any): void {
const vdom = create(Component, args);
if (typeof window === "undefined") {
renderToString(vdom);
} else {
const div = document.createElement("div");
document.body.appendChild(div);
renderIntoDom(vdom, div);
renderIntoDom(null, div);
document.body.removeChild(div);
}
}
interface Mounted<T> {
unmount: () => void;
result: { current: T | null };
waitNextUpdate: () => Promise<void>;
}
export function mountBrowser<T>(callback: () => T, Context?: ({ children }: { children: any }) => VNode): Mounted<T> {
const result: { current: T | null } = {
current: null
}
const listener: Array<() => void> = []
// component that's going to hold the hook
function Component(): VNode {
const hookResult = callback()
// save the hook result
result.current = hookResult
// notify to everyone waiting for an update and clean the queue
listener.splice(0, listener.length).forEach(cb => cb())
return create(Fragment, {})
}
// create the vdom with context if required
const vdom = !Context ? create(Component, {}) : create(Context, { children: [create(Component, {})] },);
// in non-browser environment (server side rendering) just serialize to
// string and exit
if (typeof window === "undefined") {
renderToString(vdom);
return { unmount: () => null, result } as any
}
// do the render into the DOM
const div = document.createElement("div");
document.body.appendChild(div);
renderIntoDom(vdom, div);
// clean up callback
function unmount(): any {
document.body.removeChild(div);
}
// waiter callback
async function waitNextUpdate(): Promise<void> {
await new Promise((res, rej) => {
const tid = setTimeout(() => {
rej(Error("waiting for an update but the hook didn't make one"))
}, 100)
listener.push(() => {
clearTimeout(tid)
res(undefined)
})
})
}
return {
unmount, result, waitNextUpdate
}
}
export const justBrowser_it: PendingTestFunction | TestFunction =
typeof window === 'undefined' ? it.skip : it

View File

@ -17,7 +17,7 @@
import { BalancesResponse, i18n } from "@gnu-taler/taler-util"; import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { BalanceTable } from "../components/BalanceTable"; import { BalanceTable } from "../components/BalanceTable";
import { ButtonPrimary, Centered, ErrorBox } from "../components/styled/index"; import { ButtonPrimary, Centered, ErrorBox } from "../components/styled";
import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { PageLink } from "../renderHtml"; import { PageLink } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { AmountJson, Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { View as TestedComponent } from "./DepositPage"; import { View as TestedComponent } from "./DepositPage";

View File

@ -108,8 +108,13 @@ export function View({
const currency = balance.currency; const currency = balance.currency;
const amountStr: AmountString = `${currency}:${amount}`; const amountStr: AmountString = `${currency}:${amount}`;
const account = knownBankAccounts[accountIdx]; const account = knownBankAccounts.length
const accountURI = `payto://${account.targetType}/${account.targetPath}`; ? knownBankAccounts[accountIdx]
: undefined;
const accountURI = !account
? ""
: `payto://${account.targetType}/${account.targetPath}`;
useEffect(() => { useEffect(() => {
if (amount === undefined) return; if (amount === undefined) return;
onCalculateFee(accountURI, amountStr).then((result) => { onCalculateFee(accountURI, amountStr).then((result) => {

View File

@ -23,6 +23,17 @@ import { termsXml } from "../cta/termsExample";
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { View as TestedComponent } from "./ExchangeAddConfirm"; import { View as TestedComponent } from "./ExchangeAddConfirm";
function parseFromString(s: string): Document {
if (typeof window === "undefined") {
return {
querySelector: () => ({
children: [],
}),
} as any;
}
return new window.DOMParser().parseFromString(s, "text/xml");
}
export default { export default {
title: "wallet/exchange add/confirm", title: "wallet/exchange add/confirm",
component: TestedComponent, component: TestedComponent,
@ -60,7 +71,7 @@ export const TermsChanged = createExample(TestedComponent, {
version: "1", version: "1",
content: { content: {
type: "xml", type: "xml",
document: new DOMParser().parseFromString(termsXml, "text/xml"), document: parseFromString(termsXml),
}, },
}, },
onAccept: async () => undefined, onAccept: async () => undefined,

View File

@ -1,14 +1,10 @@
import { i18n } from "@gnu-taler/taler-util"; import { i18n } 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 { import { Button, ButtonSuccess, ButtonWarning } from "../components/styled";
Button,
ButtonSuccess,
ButtonWarning,
} from "../components/styled/index";
import { TermsOfServiceSection } from "../cta/TermsOfServiceSection"; import { TermsOfServiceSection } from "../cta/TermsOfServiceSection";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { buildTermsOfServiceState, TermsState } from "../utils"; import { buildTermsOfServiceState, TermsState } from "../utils/index";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
export interface Props { export interface Props {

View File

@ -21,7 +21,7 @@ import {
import { h, VNode } from "preact"; import { h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
import { queryToSlashKeys } from "../utils"; import { queryToSlashKeys } from "../utils/index";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm"; import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm";
import { ExchangeSetUrlPage } from "./ExchangeSetUrl"; import { ExchangeSetUrlPage } from "./ExchangeSetUrl";

View File

@ -20,7 +20,7 @@
*/ */
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { queryToSlashKeys } from "../utils"; import { queryToSlashKeys } from "../utils/index";
import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl"; import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl";
export default { export default {

View File

@ -7,12 +7,7 @@ import {
import { Fragment, h } from "preact"; import { Fragment, h } from "preact";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { ErrorMessage } from "../components/ErrorMessage"; import { ErrorMessage } from "../components/ErrorMessage";
import { import { Button, ButtonPrimary, Input, WarningBox } from "../components/styled";
Button,
ButtonPrimary,
Input,
WarningBox,
} from "../components/styled/index";
export interface Props { export interface Props {
initialValue?: string; initialValue?: string;

View File

@ -30,8 +30,8 @@ import {
Input, Input,
LightText, LightText,
SmallLightText, SmallLightText,
} from "../components/styled/index"; } from "../components/styled";
import { queryToSlashConfig } from "../utils"; import { queryToSlashConfig } from "../utils/index";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
interface Props { interface Props {

View File

@ -3,7 +3,7 @@ import { Fragment, h, VNode } from "preact";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
import { QR } from "../components/QR"; import { QR } from "../components/QR";
import { ButtonDestructive, WarningBox } from "../components/styled"; import { ButtonDestructive, WarningBox } from "../components/styled";
import { amountToString } from "../utils"; import { amountToString } from "../utils/index";
export interface Props { export interface Props {
reservePub: string; reservePub: string;
payto: string; payto: string;

View File

@ -29,7 +29,7 @@ import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
import { useLang } from "../hooks/useLang"; import { useLang } from "../hooks/useLang";
import { Pages } from "../NavigationBar"; import { Pages } from "../NavigationBar";
import { buildTermsOfServiceStatus } from "../utils"; import { buildTermsOfServiceStatus } from "../utils/index";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
export function SettingsPage(): VNode { export function SettingsPage(): VNode {

View File

@ -125,12 +125,13 @@ const exampleData = {
const transactionError = { const transactionError = {
code: 7005, code: 7005,
details: { details: {
requestUrl: "http://merchant-backend.taler:9966/orders/2021.340-02AD5XCC97MQM/pay", requestUrl:
"http://merchant-backend.taler:9966/orders/2021.340-02AD5XCC97MQM/pay",
httpStatusCode: 410, httpStatusCode: 410,
errorResponse: { errorResponse: {
code: 2161, code: 2161,
hint: "The payment is too late, the offer has expired." hint: "The payment is too late, the offer has expired.",
} },
}, },
hint: "Error: WALLET_UNEXPECTED_REQUEST_ERROR", hint: "Error: WALLET_UNEXPECTED_REQUEST_ERROR",
message: "Unexpected error code in response", message: "Unexpected error code in response",

View File

@ -0,0 +1,37 @@
/*
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 * as a1 from "./Backup.stories";
import * as a2 from "./Balance.stories";
import * as a3 from "./CreateManualWithdraw.stories";
import * as a4 from "./DepositPage.stories";
import * as a5 from "./ExchangeAddConfirm.stories";
import * as a6 from "./ExchangeAddSetUrl.stories";
import * as a7 from "./History.stories";
import * as a8 from "./ProviderAddConfirmProvider.stories";
import * as a9 from "./ProviderAddSetUrl.stories";
import * as a10 from "./ProviderDetail.stories";
import * as a11 from "./ReserveCreated.stories";
import * as a12 from "./Settings.stories";
import * as a13 from "./Transaction.stories";
import * as a14 from "./Welcome.stories";
export default [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14];

View File

@ -46,6 +46,7 @@ import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
import { ProviderAddPage } from "./wallet/ProviderAddPage"; import { ProviderAddPage } from "./wallet/ProviderAddPage";
import { ExchangeAddPage } from "./wallet/ExchangeAddPage"; import { ExchangeAddPage } from "./wallet/ExchangeAddPage";
import { DepositPage } from "./wallet/DepositPage"; import { DepositPage } from "./wallet/DepositPage";
import { IoCProviderForRuntime } from "./context/iocContext";
function main(): void { function main(): void {
try { try {
@ -88,103 +89,105 @@ function Application(): VNode {
return ( return (
<div> <div>
<DevContextProvider> <DevContextProvider>
<Router history={createHashHistory()}> <IoCProviderForRuntime>
<Route <Router history={createHashHistory()}>
path={Pages.welcome} <Route
component={withLogoAndNavBar(WelcomePage)} path={Pages.welcome}
/> component={withLogoAndNavBar(WelcomePage)}
/>
<Route <Route
path={Pages.history} path={Pages.history}
component={withLogoAndNavBar(HistoryPage)} component={withLogoAndNavBar(HistoryPage)}
/> />
<Route <Route
path={Pages.transaction} path={Pages.transaction}
component={withLogoAndNavBar(TransactionPage)} component={withLogoAndNavBar(TransactionPage)}
/> />
<Route <Route
path={Pages.balance} path={Pages.balance}
component={withLogoAndNavBar(BalancePage)} component={withLogoAndNavBar(BalancePage)}
goToWalletManualWithdraw={() => route(Pages.manual_withdraw)} goToWalletManualWithdraw={() => route(Pages.manual_withdraw)}
goToWalletDeposit={(currency: string) => goToWalletDeposit={(currency: string) =>
route(Pages.deposit.replace(":currency", currency)) route(Pages.deposit.replace(":currency", currency))
} }
/> />
<Route <Route
path={Pages.settings} path={Pages.settings}
component={withLogoAndNavBar(SettingsPage)} component={withLogoAndNavBar(SettingsPage)}
/> />
<Route <Route
path={Pages.backup} path={Pages.backup}
component={withLogoAndNavBar(BackupPage)} component={withLogoAndNavBar(BackupPage)}
onAddProvider={() => { onAddProvider={() => {
route(Pages.provider_add); route(Pages.provider_add);
}} }}
/> />
<Route <Route
path={Pages.provider_detail} path={Pages.provider_detail}
component={withLogoAndNavBar(ProviderDetailPage)} component={withLogoAndNavBar(ProviderDetailPage)}
onBack={() => { onBack={() => {
route(Pages.backup); route(Pages.backup);
}} }}
/> />
<Route <Route
path={Pages.provider_add} path={Pages.provider_add}
component={withLogoAndNavBar(ProviderAddPage)} component={withLogoAndNavBar(ProviderAddPage)}
onBack={() => { onBack={() => {
route(Pages.backup); route(Pages.backup);
}} }}
/> />
<Route <Route
path={Pages.exchange_add} path={Pages.exchange_add}
component={withLogoAndNavBar(ExchangeAddPage)} component={withLogoAndNavBar(ExchangeAddPage)}
onBack={() => { onBack={() => {
route(Pages.balance); route(Pages.balance);
}} }}
/> />
<Route <Route
path={Pages.manual_withdraw} path={Pages.manual_withdraw}
component={withLogoAndNavBar(ManualWithdrawPage)} component={withLogoAndNavBar(ManualWithdrawPage)}
/> />
<Route <Route
path={Pages.deposit} path={Pages.deposit}
component={withLogoAndNavBar(DepositPage)} component={withLogoAndNavBar(DepositPage)}
/> />
<Route <Route
path={Pages.reset_required} path={Pages.reset_required}
component={() => <div>no yet implemented</div>} component={() => <div>no yet implemented</div>}
/> />
<Route <Route
path={Pages.payback} path={Pages.payback}
component={() => <div>no yet implemented</div>} component={() => <div>no yet implemented</div>}
/> />
<Route <Route
path={Pages.return_coins} path={Pages.return_coins}
component={() => <div>no yet implemented</div>} component={() => <div>no yet implemented</div>}
/> />
<Route <Route
path={Pages.dev} path={Pages.dev}
component={withLogoAndNavBar(DeveloperPage)} component={withLogoAndNavBar(DeveloperPage)}
/> />
{/** call to action */} {/** call to action */}
<Route <Route
path={Pages.pay} path={Pages.pay}
component={PayPage} component={PayPage}
goToWalletManualWithdraw={() => goToWalletManualWithdraw={() =>
goToWalletPage(Pages.manual_withdraw) goToWalletPage(Pages.manual_withdraw)
} }
/> />
<Route path={Pages.refund} component={RefundPage} /> <Route path={Pages.refund} component={RefundPage} />
<Route path={Pages.tips} component={TipPage} /> <Route path={Pages.tips} component={TipPage} />
<Route path={Pages.withdraw} component={WithdrawPage} /> <Route path={Pages.withdraw} component={WithdrawPage} />
<Route default component={Redirect} to={Pages.history} /> <Route default component={Redirect} to={Pages.history} />
</Router> </Router>
</IoCProviderForRuntime>
</DevContextProvider> </DevContextProvider>
</div> </div>
); );

View File

@ -39,7 +39,7 @@ import {
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw";
import { MessageFromBackend } from "./wxBackend.js"; import { MessageFromBackend } from "./wxBackend";
export interface ExtendedPermissionsResponse { export interface ExtendedPermissionsResponse {
newValue: boolean; newValue: boolean;
@ -75,7 +75,7 @@ async function callBackend(operation: string, payload: any): Promise<any> {
console.log("Error calling backend"); console.log("Error calling backend");
reject( reject(
new Error( new Error(
`Error contacting backend: chrome.runtime.lastError.message`, `Error contacting backend: ${chrome.runtime.lastError.message}`,
), ),
); );
} }

View File

@ -1,31 +0,0 @@
/*
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

@ -1,33 +0,0 @@
/*
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)
*/
/**
* Here we are mocking the linaria runtime since it should not be used in
* runtime.
*/
export const styled = new Proxy(function (tag: any) {
return jest.fn(() => `mock-styled.${tag}`);
}, {
get(o, prop) {
return o(prop);
},
})

View File

@ -1,34 +0,0 @@
/*
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;
(global as any).chrome = {};

View File

@ -1,68 +0,0 @@
/*
This file is part of GNU Taler
(C) 2020 Taler Systems SA
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/>
*/
// import * as test from "ava";
import { internalSetStrings, i18n, Translate } from "@gnu-taler/taler-util";
import { render, configure } from "enzyme";
import { h } from 'preact';
import Adapter from 'enzyme-adapter-preact-pure';
configure({ adapter: new Adapter() });
const testStrings = {
domain: "messages",
locale_data: {
messages: {
str1: ["foo1"],
str2: [""],
"str3 %1$s / %2$s": ["foo3 %2$s ; %1$s"],
"": {
domain: "messages",
plural_forms: "nplurals=2; plural=(n != 1);",
lang: "",
},
},
},
};
test("str translation", (done) => {
// Alias, so we nly use the function for lookups, not for string extranction.
const strAlias = i18n.str;
const TranslateAlias = Translate;
internalSetStrings(testStrings);
expect(strAlias`str1`).toEqual("foo1");
expect(strAlias`str2`).toEqual("str2");
const a = "a";
const b = "b";
expect(strAlias`str3 ${a} / ${b}`).toEqual("foo3 b ; a");
const r = render(<Translate>str1</Translate>);
expect(r.text()).toEqual("foo1");
const r2 = render(
<TranslateAlias>
str3 <span>{a}</span> / <span>{b}</span>
</TranslateAlias>,
);
expect(r2.text()).toEqual("foo3 b ; a");
done();
});
// test.default("existing str translation", (t) => {
// internalSetStrings(strings);
// t.pass();
// });

View File

@ -1,70 +0,0 @@
/*
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 { mount } from 'enzyme';
import { h } from 'preact';
import fs from 'fs';
function getFiles(dir: string, files_: string[] = []) {
const files = fs.readdirSync(dir);
for (const i in files) {
const name = dir + '/' + files[i];
if (fs.statSync(name).isDirectory()) {
getFiles(name, files_);
} else {
files_.push(name);
}
}
return files_;
}
const re = RegExp('.*\.stories.tsx')
import { setupI18n } from '@gnu-taler/taler-util';
setupI18n('en',{'en':{}})
it('render every story', () => {
// jest.spyOn(i18n, 'useTranslationContext').mockImplementation(() => ({ changeLanguage: () => null, lang: 'en' }));
getFiles('./src').filter(f => re.test(f)).map(f => {
// const f = "./src/paths/instance/transfers/list/List.stories.tsx";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const s = require(`../${f}`)
delete s.default
Object.keys(s).forEach(k => {
const Component = s[k];
expect(() => {
try {
let p = mount(<Component {...Component.args} /> as any)
p.mount()
p.unmount()
p.mount();
} catch (e) {
console.log(e)
throw e
}
}).not.toThrow() //`problem rendering ${f} example ${k}`
})
})
});

File diff suppressed because it is too large Load Diff