commit 3106f9c0ffed981bbd3501943e3508659643bd81 parent 73cd938013310a6e607df8368205e935140e5a8b Author: Florian Dold <dold@taler.net> Date: Mon, 24 Aug 2026 02:30:39 +0200 bank web UI: fix examples and add a coverage command Diffstat:
14 files changed, 210 insertions(+), 27 deletions(-)
diff --git a/packages/libeufin-bank-webui/package.json b/packages/libeufin-bank-webui/package.json @@ -10,6 +10,8 @@ "build": "tsc && ./build.mjs", "build:with-deps": "pnpm --filter \"{.}...\" run build", "test": "./test.mjs && node --test 'dist/test/**/*.test.js' 'dist/test/**/test.js'", + "coverage": "./test.mjs && node --experimental-test-coverage --test 'dist/test/**/*.test.js' 'dist/test/**/test.js'", + "i18n:check": "pogen check", "lint": "../qa-tooling/bin/eslint.mjs .", "typedoc": "pnpm dlx typedoc --out dist/typedoc ./src/", "i18n:source2po": "pogen extract && pogen merge", diff --git a/packages/libeufin-bank-webui/src/components/Cashouts/stories.tsx b/packages/libeufin-bank-webui/src/components/Cashouts/stories.tsx @@ -21,9 +21,14 @@ import * as tests from "@gnu-taler/web-util/testing"; import { ReadyView } from "./views.js"; +import { urlPattern } from "@gnu-taler/web-util/browser"; export default { - title: "transaction list", + title: "cashout list", }; -export const Ready = tests.createExample(ReadyView, {}); +export const Ready = tests.createExample(ReadyView, { + cashouts: [], + failures: [], + routeCashoutDetails: urlPattern<{ cid: string }>(/.*/, () => "#"), +}); diff --git a/packages/libeufin-bank-webui/src/components/Transactions/stories.tsx b/packages/libeufin-bank-webui/src/components/Transactions/stories.tsx @@ -30,6 +30,7 @@ export default { export const Ready = tests.createExample(ReadyView, { transactions: [ { + id: 1, amount: { currency: "USD", fraction: 0, diff --git a/packages/libeufin-bank-webui/src/components/index.examples.ts b/packages/libeufin-bank-webui/src/components/index.examples.ts @@ -15,3 +15,4 @@ */ export * as tx from "./Transactions/stories.js"; +export * as cashouts from "./Cashouts/stories.js"; diff --git a/packages/libeufin-bank-webui/src/pages/AccountPage/AccountPage.test.ts b/packages/libeufin-bank-webui/src/pages/AccountPage/AccountPage.test.ts @@ -19,15 +19,56 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import assert from "node:assert"; import { describe, it } from "node:test"; +import { AmountString, PaytoString } from "@gnu-taler/taler-util"; +import { TanChannel } from "../../utils.js"; +import { buildAccountReconfiguration } from "../admin/AccountForm.js"; -// import * as tests from "@gnu-taler/web-util/testing"; -// import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; -// import { expect } from "chai"; -// import { CASHOUT_API_EXAMPLE } from "../../endpoints.js"; -// import { Props } from "./index.js"; -// import { useComponentState } from "./state.js"; +describe("account reconfiguration payload", () => { + it("keeps an unrelated edit sparse", () => { + assert.deepEqual( + buildAccountReconfiguration({ name: "Alice Example" }, null, undefined), + { name: "Alice Example" }, + ); + }); -describe("Account states", () => { - it("should do some tests", async () => {}); + it("distinguishes contact removal and TAN disablement", () => { + assert.deepEqual( + buildAccountReconfiguration( + { email: "", phone: " +41790000000 ", tan_channels: [] }, + null, + undefined, + ), + { + contact_data: { email: null, phone: "+41790000000" }, + tan_channels: [], + }, + ); + }); + + it("includes cashout removal only when explicitly changed", () => { + assert.deepEqual( + buildAccountReconfiguration( + { cashout_payto_uri: "" }, + null, + "EUR:20" as AmountString, + ), + { cashout_payto_uri: null }, + ); + assert.deepEqual( + buildAccountReconfiguration( + { + cashout_payto_uri: "DE89370400440532013000", + tan_channels: [TanChannel.EMAIL, TanChannel.SMS], + }, + "payto://iban/DE89370400440532013000" as PaytoString, + undefined, + ), + { + cashout_payto_uri: "payto://iban/DE89370400440532013000", + tan_channels: [TanChannel.EMAIL, TanChannel.SMS], + }, + ); + }); }); diff --git a/packages/libeufin-bank-webui/src/pages/AccountPage/stories.tsx b/packages/libeufin-bank-webui/src/pages/AccountPage/stories.tsx @@ -21,9 +21,32 @@ import * as tests from "@gnu-taler/web-util/testing"; import { ReadyView } from "./views.js"; +import { urlPattern } from "@gnu-taler/web-util/browser"; export default { title: "account page", }; -export const Ready = tests.createExample(ReadyView, {}); +const route = urlPattern<any>(/.*/, () => "#"); + +export const Ready = tests.createExample(ReadyView, { + account: "alice", + tab: undefined, + limit: { + currency: "ASR", + value: 10, + fraction: 0, + negative: false, + saturated: false, + }, + balance: { currency: "ASR", value: 10, fraction: 0 }, + onOperationCreated: () => undefined, + onClose: () => undefined, + routeClose: route, + routeCashout: route, + routeChargeWallet: route, + routePublicAccounts: route, + routeWireTransfer: route, + routeCreateWireTransfer: route, + routeOperationDetails: route, +}); diff --git a/packages/libeufin-bank-webui/src/pages/OperationState/OperationState.test.ts b/packages/libeufin-bank-webui/src/pages/OperationState/OperationState.test.ts @@ -19,15 +19,44 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import assert from "node:assert"; import { describe, it } from "node:test"; +import { isWithdrawalWithinLimit } from "../WalletWithdrawForm.js"; -// import * as tests from "@gnu-taler/web-util/testing"; -// import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; -// import { expect } from "chai"; -// import { CASHOUT_API_EXAMPLE } from "../../endpoints.js"; -// import { Props } from "./index.js"; -// import { useComponentState } from "./state.js"; +describe("withdrawal limits", () => { + const amount = { currency: "EUR", value: 5, fraction: 0 }; -describe("Withdrawal operation states", () => { - it("should do some tests", async () => {}); + it("accepts values inside a positive limit", () => { + assert.equal( + isWithdrawalWithinLimit( + { ...amount, value: 10, negative: false, saturated: false }, + amount, + ), + true, + ); + }); + + it("rejects zero, negative and saturated limits", () => { + assert.equal( + isWithdrawalWithinLimit( + { ...amount, value: 0, negative: false, saturated: false }, + amount, + ), + false, + ); + assert.equal( + isWithdrawalWithinLimit( + { ...amount, negative: true, saturated: false }, + amount, + ), + false, + ); + assert.equal( + isWithdrawalWithinLimit( + { ...amount, negative: false, saturated: true }, + amount, + ), + false, + ); + }); }); diff --git a/packages/libeufin-bank-webui/src/pages/OperationState/stories.tsx b/packages/libeufin-bank-webui/src/pages/OperationState/stories.tsx @@ -21,9 +21,20 @@ import * as tests from "@gnu-taler/web-util/testing"; import { ReadyView } from "./views.js"; +import { HostPortPath, TalerUriAction } from "@gnu-taler/taler-util"; +import { urlPattern } from "@gnu-taler/web-util/browser"; export default { title: "operation status page", }; -export const Ready = tests.createExample(ReadyView, {}); +export const Ready = tests.createExample(ReadyView, { + uri: { + type: TalerUriAction.Withdraw, + bankIntegrationApiBaseUrl: "http://bank.example/" as HostPortPath, + withdrawalOperationId: "story-withdrawal", + }, + operationId: "story-withdrawal", + routeClose: urlPattern(/.*/, () => "#"), + onAbort: () => undefined, +}); diff --git a/packages/libeufin-bank-webui/src/pages/PaymentOptions.stories.tsx b/packages/libeufin-bank-webui/src/pages/PaymentOptions.stories.tsx @@ -21,11 +21,14 @@ import * as tests from "@gnu-taler/web-util/testing"; import { PaymentOptions } from "./PaymentOptions.js"; +import { urlPattern } from "@gnu-taler/web-util/browser"; export default { title: "PaymentOptions", }; +const route = urlPattern<any>(/.*/, () => "#"); + export const USD = tests.createExample(PaymentOptions, { limit: { currency: "USD", @@ -34,4 +37,12 @@ export const USD = tests.createExample(PaymentOptions, { negative: false, saturated: false, }, + balance: { currency: "USD", fraction: 0, value: 1 }, + routeClose: route, + routeCashout: route, + routeChargeWallet: route, + routeWireTransfer: route, + routeOperationDetails: route, + onOperationCreated: () => undefined, + onClose: () => undefined, }); diff --git a/packages/libeufin-bank-webui/src/pages/PaytoWireTransferForm.stories.tsx b/packages/libeufin-bank-webui/src/pages/PaytoWireTransferForm.stories.tsx @@ -28,10 +28,11 @@ export default { export const USD = tests.createExample(PaytoWireTransferForm, { limit: { - currency: "USD", + currency: "ASR", fraction: 0, value: 1, negative: false, saturated: false, }, + balance: { currency: "ASR", fraction: 0, value: 1 }, }); diff --git a/packages/libeufin-bank-webui/src/pages/QrCodeSection.stories.tsx b/packages/libeufin-bank-webui/src/pages/QrCodeSection.stories.tsx @@ -35,4 +35,5 @@ export const SimpleExample = tests.createExample(QrCodeSection, { withdrawalOperationId: "123", externalConfirmation: false, }, + onAborted: () => undefined, }); diff --git a/packages/libeufin-bank-webui/src/pages/index.stories.tsx b/packages/libeufin-bank-webui/src/pages/index.stories.tsx @@ -18,3 +18,5 @@ export * as qr from "./QrCodeSection.stories.js"; export * as po from "./PaymentOptions.stories.js"; export * as ptf from "./PaytoWireTransferForm.stories.js"; export * as frame from "./BankFrame.stories.js"; +export * as account from "./AccountPage/stories.js"; +export * as operation from "./OperationState/stories.js"; diff --git a/packages/libeufin-bank-webui/src/stories.test.ts b/packages/libeufin-bank-webui/src/stories.test.ts @@ -19,13 +19,18 @@ * @author Sebastian Javier Marchano (sebasjm) */ import { describe, it } from "node:test"; +import assert from "node:assert"; import { AmountString, setupI18n, + TalerBankConversionHttpClient, + TalerCoreBankHttpClient, TalerCorebankApi, } from "@gnu-taler/taler-util"; import { BankApiProviderTesting, + NotificationProvider, + TalerWalletIntegrationTestingProvider, parseGroupImport, } from "@gnu-taler/web-util/browser"; import * as tests from "@gnu-taler/web-util/testing"; @@ -38,6 +43,15 @@ import { ComponentChildren, h as create, VNode } from "preact"; setupI18n("en", { en: {} }); describe("All the examples:", () => { + it("forwards the selected story through the provider", () => { + const marker = tests.createExample(() => { + throw new Error("story-render-marker"); + }, {}); + assert.throws( + () => tests.renderUI(marker, DefaultTestingContext), + /story-render-marker/, + ); + }); const cms = parseGroupImport({ pages, components }); cms.forEach((group) => { describe(`Example for group "${group.title}:"`, () => { @@ -54,7 +68,11 @@ describe("All the examples:", () => { }); }); -function DefaultTestingContext(_props: { children: ComponentChildren }): VNode { +function DefaultTestingContext({ + children, +}: { + children: ComponentChildren; +}): VNode { const cfg: TalerCorebankApi.TalerCorebankConfigResponse = { name: "libeufin-bank", allow_deletions: true, @@ -76,9 +94,45 @@ function DefaultTestingContext(_props: { children: ComponentChildren }): VNode { default_debit_threshold: "ARS:10" as AmountString, version: "1:0:0", }; + const baseUrl = new URL("http://bank.example/"); + const http = { + fetch: async () => { + throw new Error("story made an unexpected HTTP request"); + }, + } as any; + const bank = new TalerCoreBankHttpClient(baseUrl.href, http); + const conversion = new TalerBankConversionHttpClient( + bank.getConversionInfoAPI().href, + http, + ); const ctx2 = create(BankApiProviderTesting, { - children: [], - value: cfg as any, + children, + value: { + url: baseUrl, + config: cfg, + lib: { + bank, + conversion, + conversionForUser: (username: string) => + new TalerBankConversionHttpClient( + bank.getConversionInfoAPIForUser(username).href, + http, + ), + conversionForClass: (classId: number) => + new TalerBankConversionHttpClient( + bank.getConversionInfoAPIForClass(classId).href, + http, + ), + }, + hints: [], + onActivity: () => () => undefined, + cancelRequest: () => undefined, + }, + }); + return create(NotificationProvider, { + children: create(TalerWalletIntegrationTestingProvider, { + value: { publishTalerAction: () => undefined }, + children: ctx2, + }), }); - return ctx2; } diff --git a/packages/libeufin-bank-webui/test.mjs b/packages/libeufin-bank-webui/test.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /* This file is part of GNU Taler - (C) 2022-2024 Taler Systems S.A. + (C) 2022-2024, 2026 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 @@ -17,15 +17,16 @@ import { build } from "@gnu-taler/web-util/build"; import { getFilesInDirectory } from "@gnu-taler/web-util/build"; +import { rmSync } from "node:fs"; const allTestFiles = getFilesInDirectory("src", /\.test\.tsx?$/); +rmSync("./dist/test", { recursive: true, force: true }); await build({ type: "test", source: { js: allTestFiles.files, assets: [{ base: "src", files: ["src/index.html"] }], - }, destination: "./dist/test", css: "sass",