gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (45bbe7ba1 -> fc2adae6b)


From: gnunet
Subject: [taler-wallet-core] branch master updated (45bbe7ba1 -> fc2adae6b)
Date: Fri, 21 Apr 2023 16:06:57 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 45bbe7ba1 add wallet config on the top
     new b9c24772f better swr mocks
     new c5c00e4da moving libs to web utils, apply new mock api to backoffice
     new 0e9aa3324 test file end with -test.js
     new 821fbb0e2 running all tests in sequential mode, otherwise it will fail 
(maybe ava can't run in parallel?)
     new d61c5808b fix bulid
     new 9fe1c4b5e allow the example to add params to the testing context
     new 3772ff85d render hook and render ui  are not the same function (node 
and browser)
     new 6dcc488a2 use better testing api
     new f470f167e integrate anastasis to the web-utils testing api
     new 7fe5f3767 integrate to the web util testing api
     new dbb3529b4 remove old testing function, use web-utils
     new 1ee962fbd tsc ask to export type from web-util
     new fc2adae6b props types should be exported for tests

The 13 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 package.json                                       |   2 +-
 packages/anastasis-webui/build.mjs                 |  25 +-
 packages/anastasis-webui/package.json              |  11 +-
 .../components/picker/DurationPicker.stories.tsx   |  12 +-
 packages/anastasis-webui/src/index.test.ts         |  74 +++-
 .../src/pages/home/AddingProviderScreen/state.ts   |  39 +-
 .../pages/home/AddingProviderScreen/stories.tsx    |  10 +-
 .../src/pages/home/AddingProviderScreen/test.ts    |  33 +-
 .../pages/home/AttributeEntryScreen.stories.tsx    |  57 +--
 .../home/AuthenticationEditorScreen.stories.tsx    | 116 +++---
 .../pages/home/BackupFinishedScreen.stories.tsx    |  10 +-
 .../pages/home/ChallengeOverviewScreen.stories.tsx | 145 +++----
 .../pages/home/ChallengePayingScreen.stories.tsx   |   6 +-
 .../home/ContinentSelectionScreen.stories.tsx      |  13 +-
 .../src/pages/home/EditPoliciesScreen.stories.tsx  |  11 +-
 .../pages/home/PoliciesPayingScreen.stories.tsx    |   8 +-
 .../pages/home/RecoveryFinishedScreen.stories.tsx  |   8 +-
 .../pages/home/ReviewPoliciesScreen.stories.tsx    | 457 +++++++++++----------
 .../src/pages/home/SecretEditorScreen.stories.tsx  |  17 +-
 .../pages/home/SecretSelectionScreen.stories.tsx   |  33 +-
 .../src/pages/home/SolveScreen.stories.tsx         |   9 +-
 .../src/pages/home/StartScreen.stories.tsx         |   6 +-
 .../src/pages/home/TruthsPayingScreen.stories.tsx  |   8 +-
 .../authMethod/AuthMethodEmailSetup.stories.tsx    |  15 +-
 .../authMethod/AuthMethodEmailSolve.stories.tsx    |  19 +-
 .../authMethod/AuthMethodIbanSetup.stories.tsx     |  15 +-
 .../authMethod/AuthMethodIbanSolve.stories.tsx     |  13 +-
 .../authMethod/AuthMethodPostSetup.stories.tsx     |  15 +-
 .../authMethod/AuthMethodPostSolve.stories.tsx     |  11 +-
 .../authMethod/AuthMethodQuestionSetup.stories.tsx |  15 +-
 .../authMethod/AuthMethodQuestionSolve.stories.tsx | 220 +++++-----
 .../home/authMethod/AuthMethodSmsSetup.stories.tsx |  15 +-
 .../home/authMethod/AuthMethodSmsSolve.stories.tsx |  11 +-
 .../authMethod/AuthMethodTotpSetup.stories.tsx     |  15 +-
 .../authMethod/AuthMethodTotpSolve.stories.tsx     |  13 +-
 .../{index.storiesNo.tsx => index.stories.tsx}     |   2 +
 packages/anastasis-webui/src/stories.tsx           |   2 +-
 packages/anastasis-webui/src/test-utils.ts         | 205 ---------
 packages/anastasis-webui/src/utils/index.tsx       |  57 +--
 packages/demobank-ui/package.json                  |   4 +-
 .../demobank-ui/src/components/Cashouts/test.ts    | 134 +-----
 .../src/components/Transactions/state.ts           |  38 --
 .../src/components/Transactions/test.ts            | 148 ++++---
 packages/demobank-ui/src/context/backend.ts        |  19 +
 packages/demobank-ui/src/endpoints.ts              |  17 +-
 packages/demobank-ui/src/hooks/access.ts           |   4 +-
 packages/demobank-ui/src/hooks/backend.ts          |   5 +-
 packages/demobank-ui/src/hooks/circuit.ts          |   4 +-
 .../src/pages/PaymentOptions.stories.tsx           |  10 +-
 .../src/pages/PaytoWireTransferForm.stories.tsx    |  10 +-
 .../src/pages/QrCodeSection.stories.tsx            |  11 +-
 packages/demobank-ui/src/stories.test.ts           |  31 +-
 packages/merchant-backoffice-ui/package.json       |   2 +-
 .../merchant-backoffice-ui/src/hooks/testing.tsx   |   7 +-
 .../merchant-backoffice-ui/src/stories.test.ts     |   9 +-
 packages/taler-util/package.json                   |   4 +-
 packages/taler-wallet-webextension/package.json    |   4 +-
 .../src/components/ShowFullContractTermPopup.tsx   |   2 +-
 .../src/serviceWorkerCryptoWorkerFactory.ts        |  36 --
 .../taler-wallet-webextension/src/stories.test.ts  |  27 +-
 .../taler-wallet-webextension/src/test-utils.ts    | 190 +--------
 .../src/wallet/QrReader.tsx                        |   2 +-
 .../taler-wallet-webextension/src/wxBackend.ts     |  12 +-
 packages/web-util/src/index.browser.ts             |   2 +
 packages/web-util/src/stories.tsx                  |  14 +-
 packages/web-util/src/tests/hook.ts                | 131 +++---
 packages/web-util/src/tests/mock.ts                |  16 +-
 packages/web-util/src/tests/swr.ts                 | 102 +++--
 .../src/utils/http-impl.browser.ts}                |   0
 .../src/utils/http-impl.sw.ts}                     |   0
 packages/web-util/src/utils/request.ts             |  44 +-
 packages/web-util/tsconfig.json                    |   2 +
 72 files changed, 1216 insertions(+), 1578 deletions(-)
 rename packages/anastasis-webui/src/pages/home/{index.storiesNo.tsx => 
index.stories.tsx} (99%)
 delete mode 100644 packages/anastasis-webui/src/test-utils.ts
 delete mode 100644 
packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts
 rename packages/{taler-wallet-webextension/src/browserHttpLib.ts => 
web-util/src/utils/http-impl.browser.ts} (100%)
 rename packages/{taler-wallet-webextension/src/serviceWorkerHttpLib.ts => 
web-util/src/utils/http-impl.sw.ts} (100%)

diff --git a/package.json b/package.json
index e13d590c0..058d2d7f8 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
     "compile": "pnpm run --filter '@gnu-taler/*' compile",
     "clean": "pnpm run --filter '@gnu-taler/*' clean",
     "pretty": "pnpm run --filter '@gnu-taler/*' pretty",
-    "check": "pnpm run --filter '@gnu-taler/*' --if-present test"
+    "check": "pnpm run --filter '@gnu-taler/*' --if-present --sequential test"
   },
   "devDependencies": {
     "@babel/core": "7.13.16",
diff --git a/packages/anastasis-webui/build.mjs 
b/packages/anastasis-webui/build.mjs
index ebe914541..def8b1050 100755
--- a/packages/anastasis-webui/build.mjs
+++ b/packages/anastasis-webui/build.mjs
@@ -104,8 +104,31 @@ function copyFilesPlugin(options) {
   };
 }
 
+function getFilesInDirectory(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 getFilesInDirectory(filename, regex);
+    }
+    else if (regex.test(filename)) {
+      return filename
+    }
+  }).filter(x => !!x)
+
+  return result
+}
+
+const allTestFiles = getFilesInDirectory(path.join(BASE, 'src'), /test.tsx?$/)
+const entryPoints = ["src/index.ts", "src/stories.tsx", ...allTestFiles];
+
 export const buildConfig = {
-  entryPoints: ['src/index.ts', 'src/stories.tsx'],
+  entryPoints: [...entryPoints],
   bundle: true,
   outdir: 'dist',
   minify: false,
diff --git a/packages/anastasis-webui/package.json 
b/packages/anastasis-webui/package.json
index 631e75369..a390a2fa8 100644
--- a/packages/anastasis-webui/package.json
+++ b/packages/anastasis-webui/package.json
@@ -3,13 +3,14 @@
   "name": "@gnu-taler/anastasis-webui",
   "version": "0.2.99",
   "license": "MIT",
+  "type": "module",
   "scripts": {
-    "build": "./clean_and_build.sh",
-    "compile": "tsc",
-    "dev": "./clean_and_build.sh WATCH",
+    "build": "./build.mjs",
+    "compile": "tsc && ./build.mjs",
+    "dev": "./dev.mjs",
     "prepare": "pnpm compile",
     "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
-    "test": "mocha --enable-source-maps 'dist/**/*test.js'",
+    "test": "mocha --require source-map-support/register --enable-source-maps 
'dist/**/*test.js'",
     "pretty": "prettier --write src"
   },
   "dependencies": {
@@ -47,4 +48,4 @@
     "sass": "1.56.1",
     "typescript": "^4.9.4"
   }
-}
\ No newline at end of file
+}
diff --git 
a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx 
b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
index 0339c9ec0..b4844c706 100644
--- a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
+++ b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx
@@ -22,6 +22,7 @@
 import { h, FunctionalComponent } from "preact";
 import { useState } from "preact/hooks";
 import { DurationPicker as TestedComponent } from "./DurationPicker.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   component: TestedComponent,
@@ -31,16 +32,7 @@ export default {
   },
 };
 
-function createExample<Props>(
-  Component: FunctionalComponent<Props>,
-  props: Partial<Props>,
-) {
-  const r = (args: any) => <Component {...args} />;
-  r.args = props;
-  return r;
-}
-
-export const Example = createExample(TestedComponent, {
+export const Example = tests.createExample(TestedComponent, {
   days: true,
   minutes: true,
   hours: true,
diff --git a/packages/anastasis-webui/src/index.test.ts 
b/packages/anastasis-webui/src/index.test.ts
index 1a87e3857..f5873f540 100644
--- a/packages/anastasis-webui/src/index.test.ts
+++ b/packages/anastasis-webui/src/index.test.ts
@@ -19,31 +19,63 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { setupI18n } from "@gnu-taler/taler-util";
-import { renderNodeOrBrowser } from "./test-utils.js";
-import * as pages from "./pages/home/index.storiesNo.js";
+import { parseGroupImport, tests } from 
"@gnu-taler/web-util/lib/index.browser";
+import * as pages from "./pages/home/index.stories.js";
+import { ComponentChildren, VNode, h as create } from "preact";
+import { AnastasisProvider } from "./context/anastasis.js";
+import { AnastasisReducerApi } from "./hooks/use-anastasis-reducer.js";
+import { ReducerState } from "@gnu-taler/anastasis-core";
 
 setupI18n("en", { en: {} });
 
-function testThisStory(key: string, st: any): any {
-  describe(`render examples for ${key}`, () => {
-    Object.keys(st).forEach((k) => {
-      const Component = (st as any)[k];
-      if (k === "default" || !Component) return;
-
-      it(`example: ${k}`, () => {
-        renderNodeOrBrowser(Component, Component.args);
+describe("All the examples:", () => {
+  const cms = parseGroupImport({ pages });
+  cms.forEach((group) => {
+    describe(`Example for group "${group.title}":`, () => {
+      group.list.forEach((component) => {
+        describe(`Component ${component.name}:`, () => {
+          component.examples.forEach((example) => {
+            it(`should render example: ${example.name}`, () => {
+              tests.renderUI(example.render, DefaultTestingContext);
+            });
+          });
+        });
       });
     });
   });
-}
-
-describe("render every storybook example", () => {
-  Object.entries(pages).forEach(function testAll([key, value]) {
-    const st: any = value;
-    if (Array.isArray(st.default)) {
-      st.default.forEach(testAll);
-    } else {
-      testThisStory(key, st);
-    }
-  });
 });
+
+const noop = async (): Promise<void> => {
+  return;
+};
+
+function DefaultTestingContext({
+  children,
+  ...rest
+}: {
+  children: ComponentChildren;
+}): VNode {
+  //some UI example can specify the state of the reducer
+  const currentReducerState = rest as ReducerState;
+  const value: AnastasisReducerApi = {
+    currentReducerState,
+    discoverMore: noop,
+    discoverStart: noop,
+    discoveryState: {
+      state: "finished",
+    },
+    currentError: undefined,
+    back: noop,
+    dismissError: noop,
+    reset: noop,
+    runTransaction: noop,
+    startBackup: noop,
+    startRecover: noop,
+    transition: noop,
+    exportState: () => {
+      return "{}";
+    },
+    importState: noop,
+  };
+  return create(AnastasisProvider, { value, children });
+}
diff --git 
a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts 
b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts
index 009ab20a2..f80f1c464 100644
--- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts
+++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts
@@ -25,7 +25,11 @@ interface Props {
   notifications?: Notification[];
 }
 
-export default function useComponentState({ providerType, onCancel, 
notifications = [] }: Props): State {
+export default function useComponentState({
+  providerType,
+  onCancel,
+  notifications = [],
+}: Props): State {
   const reducer = useAnastasisContext();
 
   const [providerURL, setProviderURL] = useState("");
@@ -39,9 +43,9 @@ export default function useComponentState({ providerType, 
onCancel, notification
 
   const allAuthProviders =
     !reducer ||
-      !reducer.currentReducerState ||
-      reducer.currentReducerState.reducer_type === "error" ||
-      !reducer.currentReducerState.authentication_providers
+    !reducer.currentReducerState ||
+    reducer.currentReducerState.reducer_type === "error" ||
+    !reducer.currentReducerState.authentication_providers
       ? {}
       : reducer.currentReducerState.authentication_providers;
 
@@ -58,7 +62,12 @@ export default function useComponentState({ providerType, 
onCancel, notification
       prev[p.status].push({ ...p, url });
       return prev;
     },
-    { "not-contacted": [], disabled: [], error: [], ok: [] } as 
AuthProvByStatusMap,
+    {
+      "not-contacted": [],
+      disabled: [],
+      error: [],
+      ok: [],
+    } as AuthProvByStatusMap,
   );
   const authProviders = authProvidersByStatus["ok"].map((p) => p.url);
 
@@ -98,10 +107,10 @@ export default function useComponentState({ providerType, 
onCancel, notification
   const addProvider = async (provider_url: string): Promise<void> => {
     await reducer.transition("add_provider", { provider_url });
     onCancel();
-  }
+  };
   const deleteProvider = async (provider_url: string): Promise<void> => {
     reducer.transition("delete_provider", { provider_url });
-  }
+  };
 
   let errors = !providerURL ? "Add provider URL" : undefined;
   let url: string | undefined;
@@ -110,7 +119,7 @@ export default function useComponentState({ providerType, 
onCancel, notification
   } catch {
     errors = "Check the URL";
   }
-  const _url = url
+  const _url = url;
 
   if (!!error && !errors) {
     errors = error;
@@ -130,21 +139,19 @@ export default function useComponentState({ providerType, 
onCancel, notification
     setProviderURL: async (s: string) => setProviderURL(s),
     errors,
     error,
-    notifications
-  }
+    notifications,
+  };
 
   if (!providerLabel) {
     return {
       status: "without-type",
-      ...commonState
-    }
+      ...commonState,
+    };
   } else {
     return {
       status: "with-type",
       providerLabel,
-      ...commonState
-    }
+      ...commonState,
+    };
   }
-
 }
-
diff --git 
a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx 
b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx
index 268189ed8..6ce22e56c 100644
--- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx
@@ -20,7 +20,7 @@
  */
 
 import { AuthenticationProviderStatusOk } from "@gnu-taler/anastasis-core";
-import { createExampleWithoutAnastasis } from "../../../utils/index.jsx";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 import { WithoutProviderType, WithProviderType } from "./views.jsx";
 
 export default {
@@ -34,7 +34,7 @@ export default {
   },
 };
 
-export const NewProvider = createExampleWithoutAnastasis(WithoutProviderType, {
+export const NewProvider = tests.createExample(WithoutProviderType, {
   authProvidersByStatus: {
     ok: [
       {
@@ -57,7 +57,7 @@ export const NewProvider = 
createExampleWithoutAnastasis(WithoutProviderType, {
   notifications: [],
 });
 
-export const NewProviderWithoutProviderList = createExampleWithoutAnastasis(
+export const NewProviderWithoutProviderList = tests.createExample(
   WithoutProviderType,
   {
     authProvidersByStatus: {
@@ -70,7 +70,7 @@ export const NewProviderWithoutProviderList = 
createExampleWithoutAnastasis(
   },
 );
 
-export const NewSmsProvider = createExampleWithoutAnastasis(WithProviderType, {
+export const NewSmsProvider = tests.createExample(WithProviderType, {
   authProvidersByStatus: {
     ok: [],
     "not-contacted": [],
@@ -81,7 +81,7 @@ export const NewSmsProvider = 
createExampleWithoutAnastasis(WithProviderType, {
   notifications: [],
 });
 
-export const NewIBANProvider = createExampleWithoutAnastasis(WithProviderType, 
{
+export const NewIBANProvider = tests.createExample(WithProviderType, {
   authProvidersByStatus: {
     ok: [],
     "not-contacted": [],
diff --git 
a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/test.ts 
b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/test.ts
index d051d7c0b..8d0e69111 100644
--- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/test.ts
+++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/test.ts
@@ -20,23 +20,26 @@
  */
 
 import { expect } from "chai";
-import { mountHook } from "../../../test-utils.js";
 import useComponentState from "./state.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 describe("AddingProviderScreen states", () => {
-  it("should have status 'no-balance' when balance is empty", async () => {
-    const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
-      mountHook(() =>
-        useComponentState({ onCancel: async () => { null } }),
-      );
-
-    {
-      const { status } = getLastResultOrThrow();
-      expect(status).equal("no-reducer");
-    }
-
-    await assertNoPendingUpdate();
-
+  it("should not load more if has reach the end", async () => {
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      () => {
+        return useComponentState({
+          providerType: "email",
+          async onCancel() {},
+        });
+      },
+      {},
+      [
+        ({ status }) => {
+          expect(status).eq("no-reducer");
+        },
+      ],
+    );
+
+    expect(hookBehavior).deep.eq({ result: "ok" });
   });
-
 });
diff --git 
a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
index 38fc1b56b..bc62961a5 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
@@ -20,7 +20,8 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../utils/index.js";
 import { AttributeEntryScreen as TestedComponent } from 
"./AttributeEntryScreen.js";
 
 export default {
@@ -35,7 +36,7 @@ export default {
   },
 };
 
-export const Backup = createExample(TestedComponent, {
+export const Backup = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.backupAttributeEditing,
   required_attributes: [
     {
@@ -62,7 +63,7 @@ export const Backup = createExample(TestedComponent, {
   ],
 } as ReducerState);
 
-export const Recovery = createExample(TestedComponent, {
+export const Recovery = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.recoveryAttributeEditing,
   required_attributes: [
     {
@@ -89,10 +90,14 @@ export const Recovery = createExample(TestedComponent, {
   ],
 } as ReducerState);
 
-export const WithNoRequiredAttribute = createExample(TestedComponent, {
-  ...reducerStatesExample.backupAttributeEditing,
-  required_attributes: undefined,
-} as ReducerState);
+export const WithNoRequiredAttribute = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.backupAttributeEditing,
+    required_attributes: undefined,
+  } as ReducerState,
+);
 
 const allWidgets = [
   "anastasis_gtk_ia_aadhar_in",
@@ -123,7 +128,7 @@ function typeForWidget(name: string): string {
   return "string";
 }
 
-export const WithAllPosibleWidget = createExample(TestedComponent, {
+export const WithAllPosibleWidget = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.backupAttributeEditing,
   required_attributes: allWidgets.map((w) => ({
     name: w,
@@ -134,19 +139,23 @@ export const WithAllPosibleWidget = 
createExample(TestedComponent, {
   })),
 } as ReducerState);
 
-export const WithAutocompleteFeature = createExample(TestedComponent, {
-  ...reducerStatesExample.backupAttributeEditing,
-  required_attributes: [
-    {
-      name: "ahv_number",
-      label: "AHV Number",
-      type: "string",
-      uuid: "asdasdsa1",
-      widget: "wid",
-      "validation-regex":
-        "^(756)\\.[0-9]{4}\\.[0-9]{4}\\.[0-9]{2}|(756)[0-9]{10}$",
-      "validation-logic": "CH_AHV_check",
-      autocomplete: "???.????.????.??",
-    },
-  ],
-} as ReducerState);
+export const WithAutocompleteFeature = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.backupAttributeEditing,
+    required_attributes: [
+      {
+        name: "ahv_number",
+        label: "AHV Number",
+        type: "string",
+        uuid: "asdasdsa1",
+        widget: "wid",
+        "validation-regex":
+          "^(756)\\.[0-9]{4}\\.[0-9]{4}\\.[0-9]{2}|(756)[0-9]{10}$",
+        "validation-logic": "CH_AHV_check",
+        autocomplete: "???.????.????.??",
+      },
+    ],
+  } as ReducerState,
+);
diff --git 
a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
index ba48e2d5c..23212c184 100644
--- 
a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
@@ -20,7 +20,8 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../utils/index.js";
 import { AuthenticationEditorScreen as TestedComponent } from 
"./AuthenticationEditorScreen.js";
 
 export default {
@@ -35,63 +36,72 @@ export default {
   },
 };
 
-export const InitialState = createExample(
+export const InitialState = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.authEditing,
 );
-export const OneAuthMethodConfigured = createExample(TestedComponent, {
-  ...reducerStatesExample.authEditing,
-  authentication_methods: [
-    {
-      type: "question",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-  ],
-} as ReducerState);
+export const OneAuthMethodConfigured = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.authEditing,
+    authentication_methods: [
+      {
+        type: "question",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+    ],
+  } as ReducerState,
+);
 
-export const SomeMoreAuthMethodConfigured = createExample(TestedComponent, {
-  ...reducerStatesExample.authEditing,
-  authentication_methods: [
-    {
-      type: "question",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-    {
-      type: "question",
-      instructions: "what time is it?",
-      challenge: "qwe",
-    },
-    {
-      type: "sms",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-    {
-      type: "email",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-    {
-      type: "email",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-    {
-      type: "email",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-    {
-      type: "email",
-      instructions: "what time is it?",
-      challenge: "asd",
-    },
-  ],
-} as ReducerState);
+export const SomeMoreAuthMethodConfigured = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.authEditing,
+    authentication_methods: [
+      {
+        type: "question",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+      {
+        type: "question",
+        instructions: "what time is it?",
+        challenge: "qwe",
+      },
+      {
+        type: "sms",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+      {
+        type: "email",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+      {
+        type: "email",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+      {
+        type: "email",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+      {
+        type: "email",
+        instructions: "what time is it?",
+        challenge: "asd",
+      },
+    ],
+  } as ReducerState,
+);
 
-export const NoAuthMethodProvided = createExample(TestedComponent, {
+export const NoAuthMethodProvided = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.authEditing,
   authentication_providers: {},
   authentication_methods: [],
diff --git 
a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
index 8aeaec25c..fce823bec 100644
--- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { BackupFinishedScreen as TestedComponent } from 
"./BackupFinishedScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Backup finish",
@@ -35,17 +36,18 @@ export default {
   },
 };
 
-export const WithoutName = createExample(
+export const WithoutName = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.backupFinished,
 );
 
-export const WithName = createExample(TestedComponent, {
+export const WithName = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.backupFinished,
   secret_name: "super_secret",
 } as ReducerState);
 
-export const WithDetails = createExample(TestedComponent, {
+export const WithDetails = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.backupFinished,
   secret_name: "super_secret",
   success_details: {
diff --git 
a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
index d2471755a..f41f894e8 100644
--- 
a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
@@ -24,8 +24,9 @@ import {
   RecoveryStates,
   ReducerState,
 } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { ChallengeOverviewScreen as TestedComponent } from 
"./ChallengeOverviewScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Challenge overview",
@@ -39,7 +40,7 @@ export default {
   },
 };
 
-export const OneUnsolvedPolicy = createExample(TestedComponent, {
+export const OneUnsolvedPolicy = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.challengeSelecting,
   recovery_information: {
     policies: [[{ uuid: "1" }]],
@@ -53,7 +54,7 @@ export const OneUnsolvedPolicy = 
createExample(TestedComponent, {
   },
 } as ReducerState);
 
-export const SomePoliciesOneSolved = createExample(TestedComponent, {
+export const SomePoliciesOneSolved = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.challengeSelecting,
   recovery_information: {
     policies: [[{ uuid: "1" }, { uuid: "2" }], [{ uuid: "uuid-3" }]],
@@ -82,7 +83,7 @@ export const SomePoliciesOneSolved = 
createExample(TestedComponent, {
   },
 } as ReducerState);
 
-export const OneBadConfiguredPolicy = createExample(TestedComponent, {
+export const OneBadConfiguredPolicy = tests.createExample(TestedComponent, {}, 
{
   ...reducerStatesExample.challengeSelecting,
   recovery_information: {
     policies: [[{ uuid: "1" }, { uuid: "2" }]],
@@ -96,71 +97,75 @@ export const OneBadConfiguredPolicy = 
createExample(TestedComponent, {
   },
 } as ReducerState);
 
-export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, {
-  ...reducerStatesExample.challengeSelecting,
-  recovery_information: {
-    policies: [
-      [
-        { uuid: "1" },
-        { uuid: "2" },
-        { uuid: "3" },
-        { uuid: "4" },
-        { uuid: "5" },
-        { uuid: "6" },
-        { uuid: "7" },
-        { uuid: "8" },
-      ],
-    ],
-    challenges: [
-      {
-        instructions: "Does P equals NP?",
-        type: "question",
-        uuid: "1",
-      },
-      {
-        instructions: "SMS to 555-555",
-        type: "sms",
-        uuid: "2",
-      },
-      {
-        instructions: "Email to qwe@asd.com",
-        type: "email",
-        uuid: "3",
-      },
-      {
-        instructions: 'Enter 8 digits code for "Anastasis"',
-        type: "totp",
-        uuid: "4",
-      },
-      {
-        //
-        instructions: "Wire transfer from ASDXCVQWE123123 with holder Florian",
-        type: "iban",
-        uuid: "5",
-      },
-      {
-        instructions: "Join a video call",
-        type: "video", //Enter 8 digits code for "Anastasis"
-        uuid: "7",
-      },
-      {},
-      {
-        instructions: "Letter to address in postal code DE123123",
-        type: "post", //Enter 8 digits code for "Anastasis"
-        uuid: "8",
-      },
-      {
-        instructions: "instruction for an unknown type of challenge",
-        type: "new-type-of-challenge",
-        uuid: "6",
-      },
-    ],
-  },
-} as ReducerState);
-
-export const OnePolicyWithAllTheChallengesInDifferentState = createExample(
+export const OnePolicyWithAllTheChallenges = tests.createExample(
   TestedComponent,
+  {},
   {
+    ...reducerStatesExample.challengeSelecting,
+    recovery_information: {
+      policies: [
+        [
+          { uuid: "1" },
+          { uuid: "2" },
+          { uuid: "3" },
+          { uuid: "4" },
+          { uuid: "5" },
+          { uuid: "6" },
+          { uuid: "7" },
+          { uuid: "8" },
+        ],
+      ],
+      challenges: [
+        {
+          instructions: "Does P equals NP?",
+          type: "question",
+          uuid: "1",
+        },
+        {
+          instructions: "SMS to 555-555",
+          type: "sms",
+          uuid: "2",
+        },
+        {
+          instructions: "Email to qwe@asd.com",
+          type: "email",
+          uuid: "3",
+        },
+        {
+          instructions: 'Enter 8 digits code for "Anastasis"',
+          type: "totp",
+          uuid: "4",
+        },
+        {
+          //
+          instructions:
+            "Wire transfer from ASDXCVQWE123123 with holder Florian",
+          type: "iban",
+          uuid: "5",
+        },
+        {
+          instructions: "Join a video call",
+          type: "video", //Enter 8 digits code for "Anastasis"
+          uuid: "7",
+        },
+        {},
+        {
+          instructions: "Letter to address in postal code DE123123",
+          type: "post", //Enter 8 digits code for "Anastasis"
+          uuid: "8",
+        },
+        {
+          instructions: "instruction for an unknown type of challenge",
+          type: "new-type-of-challenge",
+          uuid: "6",
+        },
+      ],
+    },
+  } as ReducerState,
+);
+
+export const OnePolicyWithAllTheChallengesInDifferentState =
+  tests.createExample(TestedComponent, {}, {
     ...reducerStatesExample.challengeSelecting,
     recovery_state: RecoveryStates.ChallengeSelecting,
     recovery_information: {
@@ -258,9 +263,9 @@ export const OnePolicyWithAllTheChallengesInDifferentState 
= createExample(
       },
       "uuid-10": { state: ChallengeFeedbackStatus.IncorrectAnswer.toString() },
     },
-  } as ReducerState,
-);
-export const NoPolicies = createExample(
+  } as ReducerState);
+export const NoPolicies = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.challengeSelecting,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
index cd41fe03a..086647791 100644
--- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../utils/index.js";
 import { ChallengePayingScreen as TestedComponent } from 
"./ChallengePayingScreen.js";
 
 export default {
@@ -34,7 +35,8 @@ export default {
   },
 };
 
-export const Example = createExample(
+export const Example = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.challengePaying,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
index 12a79c56c..524b673a9 100644
--- 
a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { ContinentSelectionScreen as TestedComponent } from 
"./ContinentSelectionScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Continent selection",
@@ -35,22 +36,24 @@ export default {
   },
 };
 
-export const BackupSelectContinent = createExample(
+export const BackupSelectContinent = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.backupSelectContinent,
 );
 
-export const BackupSelectCountry = createExample(TestedComponent, {
+export const BackupSelectCountry = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.backupSelectContinent,
   selected_continent: "Testcontinent",
 } as ReducerState);
 
-export const RecoverySelectContinent = createExample(
+export const RecoverySelectContinent = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.recoverySelectContinent,
 );
 
-export const RecoverySelectCountry = createExample(TestedComponent, {
+export const RecoverySelectCountry = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.recoverySelectContinent,
   selected_continent: "Testcontinent",
 } as ReducerState);
diff --git 
a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
index 1e3650300..e43996222 100644
--- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
@@ -20,7 +20,8 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../utils/index.js";
 import { EditPoliciesScreen as TestedComponent } from 
"./EditPoliciesScreen.js";
 
 export default {
@@ -35,8 +36,9 @@ export default {
   },
 };
 
-export const EditingAPolicy = createExample(
+export const EditingAPolicy = tests.createExample(
   TestedComponent,
+  { index: 0 },
   {
     ...reducerStatesExample.policyReview,
     policies: [
@@ -84,11 +86,11 @@ export const EditingAPolicy = createExample(
       },
     ],
   } as ReducerState,
-  { index: 0 },
 );
 
-export const CreatingAPolicy = createExample(
+export const CreatingAPolicy = tests.createExample(
   TestedComponent,
+  { index: 3 },
   {
     ...reducerStatesExample.policyReview,
     policies: [
@@ -136,5 +138,4 @@ export const CreatingAPolicy = createExample(
       },
     ],
   } as ReducerState,
-  { index: 3 },
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
index 56c224d34..aaaae25c9 100644
--- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { PoliciesPayingScreen as TestedComponent } from 
"./PoliciesPayingScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Policies paying",
@@ -35,11 +36,12 @@ export default {
   },
 };
 
-export const Example = createExample(
+export const Example = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.policyPay,
 );
-export const WithSomePaymentRequest = createExample(TestedComponent, {
+export const WithSomePaymentRequest = tests.createExample(TestedComponent, {}, 
{
   ...reducerStatesExample.policyPay,
   policy_payment_requests: [
     {
diff --git 
a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
index 1eb2ae50c..1a5c41f94 100644
--- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
@@ -21,8 +21,9 @@
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
 import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { RecoveryFinishedScreen as TestedComponent } from 
"./RecoveryFinishedScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Recovery Finished",
@@ -36,7 +37,7 @@ export default {
   },
 };
 
-export const GoodEnding = createExample(TestedComponent, {
+export const GoodEnding = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.recoveryFinished,
   recovery_document: {
     secret_name: "the_name_of_the_secret",
@@ -49,7 +50,8 @@ export const GoodEnding = createExample(TestedComponent, {
   },
 } as ReducerState);
 
-export const BadEnding = createExample(
+export const BadEnding = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.recoveryFinished,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
index c5003d6a0..7cde84e0f 100644
--- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { ReviewPoliciesScreen as TestedComponent } from 
"./ReviewPoliciesScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Reviewing Policies",
@@ -35,227 +36,235 @@ export default {
   },
 };
 
-export const HasPoliciesButMethodListIsEmpty = createExample(TestedComponent, {
-  ...reducerStatesExample.policyReview,
-  policies: [
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "asd",
-        },
-        {
-          authentication_method: 1,
-          provider: "asd",
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 1,
-          provider: "asd",
-        },
-      ],
-    },
-  ],
-  authentication_methods: [],
-} as ReducerState);
+export const HasPoliciesButMethodListIsEmpty = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.policyReview,
+    policies: [
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "asd",
+          },
+          {
+            authentication_method: 1,
+            provider: "asd",
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 1,
+            provider: "asd",
+          },
+        ],
+      },
+    ],
+    authentication_methods: [],
+  } as ReducerState,
+);
 
-export const SomePoliciesWithMethods = createExample(TestedComponent, {
-  ...reducerStatesExample.policyReview,
-  policies: [
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 1,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 2,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 1,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 3,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 1,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 4,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 2,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 3,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 2,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 4,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 0,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 3,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-        {
-          authentication_method: 4,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 1,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 2,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 3,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 1,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 2,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 4,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 1,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 3,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-        {
-          authentication_method: 4,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-    {
-      methods: [
-        {
-          authentication_method: 2,
-          provider: "https://kudos.demo.anastasis.lu/";,
-        },
-        {
-          authentication_method: 3,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-        {
-          authentication_method: 4,
-          provider: "https://anastasis.demo.taler.net/";,
-        },
-      ],
-    },
-  ],
-  authentication_methods: [
-    {
-      type: "email",
-      instructions: "Email to qwe@asd.com",
-      challenge: "E5VPA",
-    },
-    {
-      type: "sms",
-      instructions: "SMS to 555-555",
-      challenge: "",
-    },
-    {
-      type: "question",
-      instructions: "Does P equal NP?",
-      challenge: "C5SP8",
-    },
-    {
-      type: "totp",
-      instructions: "Response code for 'Anastasis'",
-      challenge: "E5VPA",
-    },
-    {
-      type: "sms",
-      instructions: "SMS to 6666-6666",
-      challenge: "",
-    },
-    {
-      type: "question",
-      instructions: "How did the chicken cross the road?",
-      challenge: "C5SP8",
-    },
-  ],
-} as ReducerState);
+export const SomePoliciesWithMethods = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.policyReview,
+    policies: [
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 1,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 2,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 1,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 3,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 1,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 4,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 2,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 3,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 2,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 4,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 0,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 3,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+          {
+            authentication_method: 4,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 1,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 2,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 3,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 1,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 2,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 4,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 1,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 3,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+          {
+            authentication_method: 4,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+      {
+        methods: [
+          {
+            authentication_method: 2,
+            provider: "https://kudos.demo.anastasis.lu/";,
+          },
+          {
+            authentication_method: 3,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+          {
+            authentication_method: 4,
+            provider: "https://anastasis.demo.taler.net/";,
+          },
+        ],
+      },
+    ],
+    authentication_methods: [
+      {
+        type: "email",
+        instructions: "Email to qwe@asd.com",
+        challenge: "E5VPA",
+      },
+      {
+        type: "sms",
+        instructions: "SMS to 555-555",
+        challenge: "",
+      },
+      {
+        type: "question",
+        instructions: "Does P equal NP?",
+        challenge: "C5SP8",
+      },
+      {
+        type: "totp",
+        instructions: "Response code for 'Anastasis'",
+        challenge: "E5VPA",
+      },
+      {
+        type: "sms",
+        instructions: "SMS to 6666-6666",
+        challenge: "",
+      },
+      {
+        type: "question",
+        instructions: "How did the chicken cross the road?",
+        challenge: "C5SP8",
+      },
+    ],
+  } as ReducerState,
+);
diff --git 
a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
index dbf8bf128..b0e32258b 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { SecretEditorScreen as TestedComponent } from 
"./SecretEditorScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Secret editor",
@@ -35,11 +36,15 @@ export default {
   },
 };
 
-export const WithSecretNamePreselected = createExample(TestedComponent, {
-  ...reducerStatesExample.secretEdition,
-  secret_name: "someSecretName",
-} as ReducerState);
+export const WithSecretNamePreselected = tests.createExample(
+  TestedComponent,
+  {},
+  {
+    ...reducerStatesExample.secretEdition,
+    secret_name: "someSecretName",
+  } as ReducerState,
+);
 
-export const WithoutName = createExample(TestedComponent, {
+export const WithoutName = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.secretEdition,
 } as ReducerState);
diff --git 
a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
index 7669668ee..2da80a8bb 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
@@ -20,7 +20,8 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../utils/index.js";
 import {
   SecretSelectionScreen,
   SecretSelectionScreenFound,
@@ -34,16 +35,8 @@ export default {
   },
 };
 
-export const Example = createExample(
+export const Example = tests.createExample(
   SecretSelectionScreenFound,
-  {
-    ...reducerStatesExample.secretSelection,
-    recovery_document: {
-      provider_url: "https://kudos.demo.anastasis.lu/";,
-      secret_name: "secretName",
-      version: 1,
-    },
-  } as ReducerState,
   {
     policies: [
       {
@@ -70,9 +63,21 @@ export const Example = createExample(
       },
     ],
   },
+  {
+    ...reducerStatesExample.secretSelection,
+    recovery_document: {
+      provider_url: "https://kudos.demo.anastasis.lu/";,
+      secret_name: "secretName",
+      version: 1,
+    },
+  } as ReducerState,
 );
 
-export const NoRecoveryDocumentFound = createExample(SecretSelectionScreen, {
-  ...reducerStatesExample.secretSelection,
-  recovery_document: undefined,
-} as ReducerState);
+export const NoRecoveryDocumentFound = tests.createExample(
+  SecretSelectionScreen,
+  {},
+  {
+    ...reducerStatesExample.secretSelection,
+    recovery_document: undefined,
+  } as ReducerState,
+);
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
index 1058ae126..b6d891fdd 100644
--- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { SolveScreen as TestedComponent } from "./SolveScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Solve Screen",
@@ -35,12 +36,12 @@ export default {
   },
 };
 
-export const NoInformation = createExample(
+export const NoInformation = tests.createExample(
   TestedComponent,
   reducerStatesExample.challengeSolving,
 );
 
-export const NotSupportedChallenge = createExample(TestedComponent, {
+export const NotSupportedChallenge = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.challengeSolving,
   recovery_information: {
     challenges: [
@@ -55,7 +56,7 @@ export const NotSupportedChallenge = 
createExample(TestedComponent, {
   selected_challenge_uuid: "ASDASDSAD!1",
 } as ReducerState);
 
-export const MismatchedChallengeId = createExample(TestedComponent, {
+export const MismatchedChallengeId = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.challengeSolving,
   recovery_information: {
     challenges: [
diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
index 960426098..3745c834b 100644
--- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../utils/index.js";
 import { StartScreen as TestedComponent } from "./StartScreen.js";
 
 export default {
@@ -34,7 +35,8 @@ export default {
   },
 };
 
-export const InitialState = createExample(
+export const InitialState = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.initial,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
index 40ed5117c..dafb4bc58 100644
--- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../utils/index.js";
+import { reducerStatesExample } from "../../utils/index.js";
 import { TruthsPayingScreen as TestedComponent } from 
"./TruthsPayingScreen.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Truths Paying",
@@ -35,11 +36,12 @@ export default {
   },
 };
 
-export const Example = createExample(
+export const Example = tests.createExample(
   TestedComponent,
+  {},
   reducerStatesExample.truthsPaying,
 );
-export const WithPaytoList = createExample(TestedComponent, {
+export const WithPaytoList = tests.createExample(TestedComponent, {}, {
   ...reducerStatesExample.truthsPaying,
   payments: ["payto://x-taler-bank/bank/account"],
 } as ReducerState);
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
index 4a2d76ca3..98119700a 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
 
 export default {
@@ -36,17 +37,16 @@ export default {
 
 const type: KnownAuthMethods = "email";
 
-export const Empty = createExample(
+export const Empty = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithOneExample = createExample(
+export const WithOneExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -57,11 +57,11 @@ export const WithOneExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithMoreExamples = createExample(
+export const WithMoreExamples = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -78,4 +78,5 @@ export const WithMoreExamples = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
index cc378d8f6..6bf6c2e30 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
@@ -23,8 +23,9 @@ import {
   ChallengeFeedbackStatus,
   ReducerState,
 } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Auth method: Email solve",
@@ -40,8 +41,11 @@ export default {
 
 const type: KnownAuthMethods = "email";
 
-export const WithoutFeedback = createExample(
+export const WithoutFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "uuid-1",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -56,13 +60,13 @@ export const WithoutFeedback = createExample(
     },
     selected_challenge_uuid: "uuid-1",
   } as ReducerState,
-  {
-    id: "uuid-1",
-  },
 );
 
-export const PaymentFeedback = createExample(
+export const PaymentFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "uuid-1",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -85,7 +89,4 @@ export const PaymentFeedback = createExample(
       },
     },
   } as ReducerState,
-  {
-    id: "uuid-1",
-  },
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
index dfe3850f1..8dd141b61 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
 
 export default {
@@ -36,17 +37,16 @@ export default {
 
 const type: KnownAuthMethods = "iban";
 
-export const Empty = createExample(
+export const Empty = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithOneExample = createExample(
+export const WithOneExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -57,10 +57,10 @@ export const WithOneExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
-export const WithMoreExamples = createExample(
+export const WithMoreExamples = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -77,4 +77,5 @@ export const WithMoreExamples = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
index 8a9a3f7a0..1aa257259 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
-import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
+import { KnownAuthMethods, authMethods as TestedComponent } from "./index.js";
 
 export default {
   title: "Auth method: IBAN Solve",
@@ -37,8 +38,11 @@ export default {
 
 const type: KnownAuthMethods = "iban";
 
-export const WithoutFeedback = createExample(
+export const WithoutFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "uuid-1",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -53,7 +57,4 @@ export const WithoutFeedback = createExample(
     },
     selected_challenge_uuid: "uuid-1",
   } as ReducerState,
-  {
-    id: "uuid-1",
-  },
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
index 8a32c45c1..9a92ae109 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
 
 export default {
@@ -36,17 +37,16 @@ export default {
 
 const type: KnownAuthMethods = "post";
 
-export const Empty = createExample(
+export const Empty = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithOneExample = createExample(
+export const WithOneExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -57,11 +57,11 @@ export const WithOneExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithMoreExamples = createExample(
+export const WithMoreExamples = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -78,4 +78,5 @@ export const WithMoreExamples = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
index 702ba2810..d28680801 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Auth method: Post solve",
@@ -37,8 +38,11 @@ export default {
 
 const type: KnownAuthMethods = "post";
 
-export const WithoutFeedback = createExample(
+export const WithoutFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "uuid-1",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -53,7 +57,4 @@ export const WithoutFeedback = createExample(
     },
     selected_challenge_uuid: "uuid-1",
   } as ReducerState,
-  {
-    id: "uuid-1",
-  },
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
index 2e108b4e6..c365a7e06 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
 
 export default {
@@ -36,17 +37,16 @@ export default {
 
 const type: KnownAuthMethods = "question";
 
-export const Empty = createExample(
+export const Empty = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithOneExample = createExample(
+export const WithOneExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -58,11 +58,11 @@ export const WithOneExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithMoreExamples = createExample(
+export const WithMoreExamples = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -80,4 +80,5 @@ export const WithMoreExamples = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
index f7116bf6f..b9dff6363 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
@@ -23,8 +23,9 @@ import {
   ChallengeFeedbackStatus,
   ReducerState,
 } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Auth method: Question solve",
@@ -40,8 +41,11 @@ export default {
 
 const type: KnownAuthMethods = "question";
 
-export const WithoutFeedback = createExample(
+export const WithoutFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "uuid-1",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -56,9 +60,6 @@ export const WithoutFeedback = createExample(
     },
     selected_challenge_uuid: "uuid-1",
   } as ReducerState,
-  {
-    id: "uuid-1",
-  },
 );
 
 const recovery_information = {
@@ -72,45 +73,58 @@ const recovery_information = {
   policies: [],
 };
 
-export const CodeInFileFeedback = createExample(TestedComponent[type].solve, {
-  ...reducerStatesExample.challengeSolving,
-  recovery_information,
-  selected_challenge_uuid: "ASDASDSAD!1",
-  challenge_feedback: {
-    "ASDASDSAD!1": {
-      state: ChallengeFeedbackStatus.CodeInFile,
-      filename: "asd",
-      display_hint: "hint",
+export const CodeInFileFeedback = tests.createExample(
+  TestedComponent[type].solve,
+  {},
+  {
+    ...reducerStatesExample.challengeSolving,
+    recovery_information,
+    selected_challenge_uuid: "ASDASDSAD!1",
+    challenge_feedback: {
+      "ASDASDSAD!1": {
+        state: ChallengeFeedbackStatus.CodeInFile,
+        filename: "asd",
+        display_hint: "hint",
+      },
     },
-  },
-} as ReducerState);
-
-export const CodeSentFeedback = createExample(TestedComponent[type].solve, {
-  ...reducerStatesExample.challengeSolving,
-  recovery_information,
-  selected_challenge_uuid: "ASDASDSAD!1",
-  challenge_feedback: {
-    "ASDASDSAD!1": {
-      state: ChallengeFeedbackStatus.CodeSent,
-      address_hint: "asdasd",
-      display_hint: "qweqweqw",
+  } as ReducerState,
+);
+
+export const CodeSentFeedback = tests.createExample(
+  TestedComponent[type].solve,
+  {},
+  {
+    ...reducerStatesExample.challengeSolving,
+    recovery_information,
+    selected_challenge_uuid: "ASDASDSAD!1",
+    challenge_feedback: {
+      "ASDASDSAD!1": {
+        state: ChallengeFeedbackStatus.CodeSent,
+        address_hint: "asdasd",
+        display_hint: "qweqweqw",
+      },
     },
-  },
-} as ReducerState);
-
-export const SolvedFeedback = createExample(TestedComponent[type].solve, {
-  ...reducerStatesExample.challengeSolving,
-  recovery_information,
-  selected_challenge_uuid: "ASDASDSAD!1",
-  challenge_feedback: {
-    "ASDASDSAD!1": {
-      state: ChallengeFeedbackStatus.Solved,
+  } as ReducerState,
+);
+
+export const SolvedFeedback = tests.createExample(
+  TestedComponent[type].solve,
+  {},
+  {
+    ...reducerStatesExample.challengeSolving,
+    recovery_information,
+    selected_challenge_uuid: "ASDASDSAD!1",
+    challenge_feedback: {
+      "ASDASDSAD!1": {
+        state: ChallengeFeedbackStatus.Solved,
+      },
     },
-  },
-} as ReducerState);
+  } as ReducerState,
+);
 
-export const ServerFailureFeedback = createExample(
+export const ServerFailureFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {},
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information,
@@ -124,45 +138,58 @@ export const ServerFailureFeedback = createExample(
   } as ReducerState,
 );
 
-export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, 
{
-  ...reducerStatesExample.challengeSolving,
-  recovery_information,
-  selected_challenge_uuid: "ASDASDSAD!1",
-  challenge_feedback: {
-    "ASDASDSAD!1": {
-      state: ChallengeFeedbackStatus.TruthUnknown,
+export const TruthUnknownFeedback = tests.createExample(
+  TestedComponent[type].solve,
+  {},
+  {
+    ...reducerStatesExample.challengeSolving,
+    recovery_information,
+    selected_challenge_uuid: "ASDASDSAD!1",
+    challenge_feedback: {
+      "ASDASDSAD!1": {
+        state: ChallengeFeedbackStatus.TruthUnknown,
+      },
     },
-  },
-} as ReducerState);
-
-export const TalerPaymentFeedback = createExample(TestedComponent[type].solve, 
{
-  ...reducerStatesExample.challengeSolving,
-  recovery_information,
-  selected_challenge_uuid: "ASDASDSAD!1",
-  challenge_feedback: {
-    "ASDASDSAD!1": {
-      state: ChallengeFeedbackStatus.TalerPayment,
-      payment_secret: "secret",
-      provider: "asdasdas",
-      taler_pay_uri: "taler://pay/...",
+  } as ReducerState,
+);
+
+export const TalerPaymentFeedback = tests.createExample(
+  TestedComponent[type].solve,
+  {},
+  {
+    ...reducerStatesExample.challengeSolving,
+    recovery_information,
+    selected_challenge_uuid: "ASDASDSAD!1",
+    challenge_feedback: {
+      "ASDASDSAD!1": {
+        state: ChallengeFeedbackStatus.TalerPayment,
+        payment_secret: "secret",
+        provider: "asdasdas",
+        taler_pay_uri: "taler://pay/...",
+      },
     },
-  },
-} as ReducerState);
-
-export const UnsupportedFeedback = createExample(TestedComponent[type].solve, {
-  ...reducerStatesExample.challengeSolving,
-  recovery_information,
-  selected_challenge_uuid: "ASDASDSAD!1",
-  challenge_feedback: {
-    "ASDASDSAD!1": {
-      state: ChallengeFeedbackStatus.Unsupported,
-      unsupported_method: "method",
+  } as ReducerState,
+);
+
+export const UnsupportedFeedback = tests.createExample(
+  TestedComponent[type].solve,
+  {},
+  {
+    ...reducerStatesExample.challengeSolving,
+    recovery_information,
+    selected_challenge_uuid: "ASDASDSAD!1",
+    challenge_feedback: {
+      "ASDASDSAD!1": {
+        state: ChallengeFeedbackStatus.Unsupported,
+        unsupported_method: "method",
+      },
     },
-  },
-} as ReducerState);
+  } as ReducerState,
+);
 
-export const RateLimitExceededFeedback = createExample(
+export const RateLimitExceededFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {},
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information,
@@ -175,8 +202,9 @@ export const RateLimitExceededFeedback = createExample(
   } as ReducerState,
 );
 
-export const IbanInstructionsFeedback = createExample(
+export const IbanInstructionsFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {},
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information,
@@ -194,8 +222,9 @@ export const IbanInstructionsFeedback = createExample(
   } as ReducerState,
 );
 
-export const IncorrectAnswerFeedback = createExample(
+export const IncorrectAnswerFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {},
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information,
@@ -207,44 +236,3 @@ export const IncorrectAnswerFeedback = createExample(
     },
   } as ReducerState,
 );
-
-// export const AuthIbanFeedback = createExample(TestedComponent[type].solve, {
-//   ...reducerStatesExample.challengeSolving,
-//   recovery_information: {
-//     challenges: [
-//       {
-//         instructions: "does P equals NP?",
-//         type: "question",
-//         uuid: "ASDASDSAD!1",
-//       },
-//     ],
-//     policies: [],
-//   },
-//   selected_challenge_uuid: "ASDASDSAD!1",
-//   challenge_feedback: {
-//     "ASDASDSAD!1": ibanFeedback,
-//   },
-// } as ReducerState);
-
-// export const PaymentFeedback = createExample(TestedComponent[type].solve, {
-//   ...reducerStatesExample.challengeSolving,
-//   recovery_information: {
-//     challenges: [
-//       {
-//         instructions: "does P equals NP?",
-//         type: "question",
-//         uuid: "ASDASDSAD!1",
-//       },
-//     ],
-//     policies: [],
-//   },
-//   selected_challenge_uuid: "ASDASDSAD!1",
-//   challenge_feedback: {
-//     "ASDASDSAD!1": {
-//       state: ChallengeFeedbackStatus.TalerPayment,
-//       taler_pay_uri: "taler://pay/...",
-//       provider: "https://localhost:8080/";,
-//       payment_secret: 
"3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG",
-//     },
-//   },
-// } as ReducerState);
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
index b2c6cb61d..d98a7ec89 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
 
 export default {
@@ -36,17 +37,16 @@ export default {
 
 const type: KnownAuthMethods = "sms";
 
-export const Empty = createExample(
+export const Empty = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithOneExample = createExample(
+export const WithOneExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -57,11 +57,11 @@ export const WithOneExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
 
-export const WithMoreExamples = createExample(
+export const WithMoreExamples = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -78,4 +78,5 @@ export const WithMoreExamples = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
index 2064f12ff..1fb20b6d2 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 
 export default {
   title: "Auth method: SMS solve",
@@ -37,8 +38,11 @@ export default {
 
 const type: KnownAuthMethods = "sms";
 
-export const WithoutFeedback = createExample(
+export const WithoutFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "AHCC4ZJ3Z1AF8TWBKGVGEKCQ3R7HXHJ51MJ45NHNZMHYZTKJ9NW0",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -54,7 +58,4 @@ export const WithoutFeedback = createExample(
     selected_challenge_uuid:
       "AHCC4ZJ3Z1AF8TWBKGVGEKCQ3R7HXHJ51MJ45NHNZMHYZTKJ9NW0",
   } as ReducerState,
-  {
-    id: "AHCC4ZJ3Z1AF8TWBKGVGEKCQ3R7HXHJ51MJ45NHNZMHYZTKJ9NW0",
-  },
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
index 5582590f7..bb2fa1aeb 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
 import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
 
 export default {
@@ -36,16 +37,15 @@ export default {
 
 const type: KnownAuthMethods = "totp";
 
-export const Empty = createExample(
+export const Empty = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [],
   },
+  reducerStatesExample.authEditing,
 );
-export const WithOneExample = createExample(
+export const WithOneExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -56,10 +56,10 @@ export const WithOneExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
-export const WithMoreExample = createExample(
+export const WithMoreExample = tests.createExample(
   TestedComponent[type].setup,
-  reducerStatesExample.authEditing,
   {
     configured: [
       {
@@ -76,4 +76,5 @@ export const WithMoreExample = createExample(
       },
     ],
   },
+  reducerStatesExample.authEditing,
 );
diff --git 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
index 20cd7e3c9..5f1022247 100644
--- 
a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
+++ 
b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
@@ -20,8 +20,9 @@
  */
 
 import { ReducerState } from "@gnu-taler/anastasis-core";
-import { createExample, reducerStatesExample } from "../../../utils/index.js";
-import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { reducerStatesExample } from "../../../utils/index.js";
+import { KnownAuthMethods, authMethods as TestedComponent } from "./index.js";
 
 export default {
   title: "Auth method: Totp solve",
@@ -37,8 +38,11 @@ export default {
 
 const type: KnownAuthMethods = "totp";
 
-export const WithoutFeedback = createExample(
+export const WithoutFeedback = tests.createExample(
   TestedComponent[type].solve,
+  {
+    id: "uuid-1",
+  },
   {
     ...reducerStatesExample.challengeSolving,
     recovery_information: {
@@ -53,7 +57,4 @@ export const WithoutFeedback = createExample(
     },
     selected_challenge_uuid: "uuid-1",
   } as ReducerState,
-  {
-    id: "uuid-1",
-  },
 );
diff --git a/packages/anastasis-webui/src/pages/home/index.storiesNo.tsx 
b/packages/anastasis-webui/src/pages/home/index.stories.tsx
similarity index 99%
rename from packages/anastasis-webui/src/pages/home/index.storiesNo.tsx
rename to packages/anastasis-webui/src/pages/home/index.stories.tsx
index 0dad73724..b4525b423 100644
--- a/packages/anastasis-webui/src/pages/home/index.storiesNo.tsx
+++ b/packages/anastasis-webui/src/pages/home/index.stories.tsx
@@ -35,6 +35,7 @@ export * as authMethod_AuthMethodSmsSetup from 
"./authMethod/AuthMethodSmsSetup.
 export * as authMethod_AuthMethodSmsSolve from 
"./authMethod/AuthMethodSmsSolve.stories.js";
 export * as authMethod_AuthMethodTotpSetup from 
"./authMethod/AuthMethodTotpSetup.stories.js";
 export * as authMethod_AuthMethodTotpSolve from 
"./authMethod/AuthMethodTotpSolve.stories.js";
+
 export * as BackupFinishedScreen from "./BackupFinishedScreen.stories.js";
 export * as ChallengeOverviewScreen from 
"./ChallengeOverviewScreen.stories.js";
 export * as ChallengePayingScreen from "./ChallengePayingScreen.stories.js";
@@ -42,6 +43,7 @@ export * as ContinentSelectionScreen from 
"./ContinentSelectionScreen.stories.js
 export * as EditPoliciesScreen from "./EditPoliciesScreen.stories.js";
 export * as PoliciesPayingScreen from "./PoliciesPayingScreen.stories.js";
 export * as RecoveryFinishedScreen from "./RecoveryFinishedScreen.stories.js";
+
 export * as ReviewPoliciesScreen from "./ReviewPoliciesScreen.stories.js";
 export * as SecretEditorScreen from "./SecretEditorScreen.stories.js";
 export * as SecretSelectionScreen from "./SecretSelectionScreen.stories.js";
diff --git a/packages/anastasis-webui/src/stories.tsx 
b/packages/anastasis-webui/src/stories.tsx
index f345f082d..52d42577d 100644
--- a/packages/anastasis-webui/src/stories.tsx
+++ b/packages/anastasis-webui/src/stories.tsx
@@ -20,7 +20,7 @@
  */
 import { strings } from "./i18n/strings.js";
 
-import * as pages from "./pages/home/index.storiesNo.js";
+import * as pages from "./pages/home/index.stories.js";
 
 import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
 
diff --git a/packages/anastasis-webui/src/test-utils.ts 
b/packages/anastasis-webui/src/test-utils.ts
deleted file mode 100644
index f220540f1..000000000
--- a/packages/anastasis-webui/src/test-utils.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- This file is part of GNU Anastasis
- (C) 2021-2022 Anastasis SARL
-
- GNU Anastasis is free software; you can redistribute it and/or modify it 
under the
- terms of the GNU Affero General Public License as published by the Free 
Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Anastasis 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 Affero General Public License for more 
details.
-
- You should have received a copy of the GNU Affero General Public License 
along with
- GNU Anastasis; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
- */
-
-import {
-  ComponentChildren,
-  Fragment,
-  FunctionalComponent,
-  h as create,
-  options,
-  render as renderIntoDom,
-  VNode,
-} from "preact";
-import { render as renderToString } from "preact-render-to-string";
-
-// When doing tests we want the requestAnimationFrame to be as fast as 
possible.
-// without this option the RAF will timeout after 100ms making the tests slower
-options.requestAnimationFrame = (fn: () => void) => {
-  // console.log("RAF called")
-  return fn();
-};
-
-export function createExample<Props>(
-  Component: FunctionalComponent<Props>,
-  props: Partial<Props> | (() => Partial<Props>),
-): ComponentChildren {
-  //FIXME: props are evaluated on build time
-  // in some cases we want to evaluated the props on render time so we can get 
some relative timestamp
-  // check how we can build evaluatedProps in render time
-  const evaluatedProps = typeof props === "function" ? props() : props;
-  const Render = (args: any): VNode => create(Component, args);
-  return {
-    component: Render,
-    props: evaluatedProps
-  };
-}
-
-export function createExampleWithCustomContext<Props, ContextProps>(
-  Component: FunctionalComponent<Props>,
-  props: Partial<Props> | (() => Partial<Props>),
-  ContextProvider: FunctionalComponent<ContextProps>,
-  contextProps: Partial<ContextProps>,
-): ComponentChildren {
-  const evaluatedProps = typeof props === "function" ? props() : props;
-  const Render = (args: any): VNode => create(Component, args);
-  const WithContext = (args: any): VNode =>
-    create(ContextProvider, {
-      ...contextProps,
-      children: [Render(args)],
-    } as any);
-  return {
-    component: WithContext,
-    props: evaluatedProps
-  };
-}
-
-export function NullLink({
-  children,
-}: {
-  children?: ComponentChildren;
-}): VNode {
-  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;
-  getLastResultOrThrow: () => T;
-  assertNoPendingUpdate: () => void;
-  waitNextUpdate: (s?: string) => Promise<void>;
-}
-
-const isNode = typeof window === "undefined";
-
-export function mountHook<T>(
-  callback: () => T,
-  Context?: ({ children }: { children: any }) => VNode,
-): Mounted<T> {
-  // const result: { current: T | null } = {
-  //   current: null
-  // }
-  let lastResult: T | Error | null = null;
-
-  const listener: Array<() => void> = [];
-
-  // component that's going to hold the hook
-  function Component(): VNode {
-    try {
-      lastResult = callback();
-    } catch (e) {
-      if (e instanceof Error) {
-        lastResult = e;
-      } else {
-        lastResult = new Error(`mounting the hook throw an exception: ${e}`);
-      }
-    }
-
-    // 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, {})] });
-
-  // waiter callback
-  async function waitNextUpdate(_label = ""): Promise<void> {
-    if (_label) _label = `. label: "${_label}"`;
-    await new Promise((res, rej) => {
-      const tid = setTimeout(() => {
-        rej(
-          Error(`waiting for an update but the hook didn't make one${_label}`),
-        );
-      }, 100);
-
-      listener.push(() => {
-        clearTimeout(tid);
-        res(undefined);
-      });
-    });
-  }
-
-  const customElement = {} as Element;
-  const parentElement = isNode ? customElement : document.createElement("div");
-  if (!isNode) {
-    document.body.appendChild(parentElement);
-  }
-
-  renderIntoDom(vdom, parentElement);
-
-  // clean up callback
-  function unmount(): void {
-    if (!isNode) {
-      document.body.removeChild(parentElement);
-    }
-  }
-
-  function getLastResult(): T | Error | null {
-    const copy = lastResult;
-    lastResult = null;
-    return copy;
-  }
-
-  function getLastResultOrThrow(): T {
-    const r = getLastResult();
-    if (r instanceof Error) throw r;
-    if (!r) throw Error("there was no last result");
-    return r;
-  }
-
-  async function assertNoPendingUpdate(): Promise<void> {
-    await new Promise((res, rej) => {
-      const tid = setTimeout(() => {
-        res(undefined);
-      }, 10);
-
-      listener.push(() => {
-        clearTimeout(tid);
-        rej(
-          Error(`Expecting no pending result but the hook got updated. 
-        If the update was not intended you need to check the hook dependencies 
-        (or dependencies of the internal state) but otherwise make 
-        sure to consume the result before ending the test.`),
-        );
-      });
-    });
-
-    const r = getLastResult();
-    if (r)
-      throw Error(`There are still pending results.
-    This may happen because the hook did a new update but the test didn't 
consume the result using getLastResult`);
-  }
-  return {
-    unmount,
-    getLastResultOrThrow,
-    waitNextUpdate,
-    assertNoPendingUpdate,
-  };
-}
diff --git a/packages/anastasis-webui/src/utils/index.tsx 
b/packages/anastasis-webui/src/utils/index.tsx
index 4cf839473..88bcac551 100644
--- a/packages/anastasis-webui/src/utils/index.tsx
+++ b/packages/anastasis-webui/src/utils/index.tsx
@@ -21,67 +21,12 @@ import {
   ReducerState,
   ReducerStateRecovery,
 } from "@gnu-taler/anastasis-core";
-import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
-import { AnastasisProvider } from "../context/anastasis.js";
+import { VNode } from "preact";
 
 const noop = async (): Promise<void> => {
   return;
 };
 
-export function createExampleWithoutAnastasis<Props>(
-  Component: FunctionalComponent<Props>,
-  props: Partial<Props> | (() => Partial<Props>),
-): ComponentChildren {
-  //FIXME: props are evaluated on build time
-  // in some cases we want to evaluated the props on render time so we can get 
some relative timestamp
-  // check how we can build evaluatedProps in render time
-  const evaluatedProps = typeof props === "function" ? props() : props;
-  const Render = (args: any): VNode => h(Component, args);
-  return {
-    component: Render,
-    props: evaluatedProps,
-  };
-}
-
-export function createExample<Props>(
-  Component: FunctionalComponent<Props>,
-  currentReducerState?: ReducerState,
-  props?: Partial<Props>,
-): ComponentChildren {
-  const Render = (args: Props): VNode => {
-    return (
-      <AnastasisProvider
-        value={{
-          currentReducerState,
-          discoverMore: noop,
-          discoverStart: noop,
-          discoveryState: {
-            state: "finished",
-          },
-          currentError: undefined,
-          back: noop,
-          dismissError: noop,
-          reset: noop,
-          runTransaction: noop,
-          startBackup: noop,
-          startRecover: noop,
-          transition: noop,
-          exportState: () => {
-            return "{}";
-          },
-          importState: noop,
-        }}
-      >
-        <Component {...(args as any)} />
-      </AnastasisProvider>
-    );
-  };
-  return {
-    component: Render,
-    props: props,
-  };
-}
-
 const base = {
   continents: [
     {
diff --git a/packages/demobank-ui/package.json 
b/packages/demobank-ui/package.json
index 41849afa7..586beae05 100644
--- a/packages/demobank-ui/package.json
+++ b/packages/demobank-ui/package.json
@@ -8,7 +8,7 @@
     "build": "./build.mjs",
     "check": "tsc",
     "compile": "tsc && ./build.mjs",
-    "test": "pnpm compile && mocha --require source-map-support/register 
'dist/**/*.test.js' 'dist/**/test.js'",
+    "test": "mocha --require source-map-support/register 'dist/**/*.test.js' 
'dist/**/test.js'",
     "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
     "i18n:extract": "pogen extract",
     "i18n:merge": "pogen merge",
@@ -66,4 +66,4 @@
   "pogen": {
     "domain": "bank"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/demobank-ui/src/components/Cashouts/test.ts 
b/packages/demobank-ui/src/components/Cashouts/test.ts
index 6d61b0af4..c0a9aba00 100644
--- a/packages/demobank-ui/src/components/Cashouts/test.ts
+++ b/packages/demobank-ui/src/components/Cashouts/test.ts
@@ -22,11 +22,11 @@
 import { tests } from "@gnu-taler/web-util/lib/index.browser";
 import { SwrMockEnvironment } from "@gnu-taler/web-util/lib/tests/swr";
 import { expect } from "chai";
-import { TRANSACTION_API_EXAMPLE } from "../../endpoints.js";
+import { CASHOUT_API_EXAMPLE } from "../../endpoints.js";
 import { Props } from "./index.js";
 import { useComponentState } from "./state.js";
 
-describe("Transaction states", () => {
+describe("Cashout states", () => {
   it("should query backend and render transactions", async () => {
     const env = new SwrMockEnvironment();
 
@@ -37,62 +37,16 @@ describe("Transaction states", () => {
       },
     };
 
-    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, {
+    env.addRequestExpectation(CASHOUT_API_EXAMPLE.LIST_FIRST_PAGE, {
       response: {
-        transactions: [
-          {
-            creditorIban: "DE159593",
-            creditorBic: "SANDBOXX",
-            creditorName: "exchange company",
-            debtorIban: "DE118695",
-            debtorBic: "SANDBOXX",
-            debtorName: "Name unknown",
-            amount: "1",
-            currency: "KUDOS",
-            subject:
-              "Taler Withdrawal 
N588V8XE9TR49HKAXFQ20P0EQ0EYW2AC9NNANV8ZP5P59N6N0410",
-            date: "2022-12-12Z",
-            uid: "8PPFR9EM",
-            direction: "DBIT",
-            pmtInfId: null,
-            msgId: null,
-          },
-          {
-            creditorIban: "DE159593",
-            creditorBic: "SANDBOXX",
-            creditorName: "exchange company",
-            debtorIban: "DE118695",
-            debtorBic: "SANDBOXX",
-            debtorName: "Name unknown",
-            amount: "5.00",
-            currency: "KUDOS",
-            subject: "HNEWWT679TQC5P1BVXJS48FX9NW18FWM6PTK2N80Z8GVT0ACGNK0",
-            date: "2022-12-07Z",
-            uid: "7FZJC3RJ",
-            direction: "DBIT",
-            pmtInfId: null,
-            msgId: null,
-          },
-          {
-            creditorIban: "DE118695",
-            creditorBic: "SANDBOXX",
-            creditorName: "Name unknown",
-            debtorIban: "DE579516",
-            debtorBic: "SANDBOXX",
-            debtorName: "The Bank",
-            amount: "100",
-            currency: "KUDOS",
-            subject: "Sign-up bonus",
-            date: "2022-12-07Z",
-            uid: "I31A06J8",
-            direction: "CRDT",
-            pmtInfId: null,
-            msgId: null,
-          },
-        ],
+        cashouts: [],
       },
     });
 
+    env.addRequestExpectation(CASHOUT_API_EXAMPLE.MULTI_GET_EMPTY_FIRST_PAGE, {
+      response: [],
+    });
+
     const hookBehavior = await tests.hookBehaveLikeThis(
       useComponentState,
       props,
@@ -113,76 +67,4 @@ describe("Transaction states", () => {
 
     expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
   });
-
-  it("should show error message on not found", async () => {
-    const env = new SwrMockEnvironment();
-
-    const props: Props = {
-      account: "123",
-      onSelected: () => {
-        null;
-      },
-    };
-
-    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_NOT_FOUND, {});
-
-    const hookBehavior = await tests.hookBehaveLikeThis(
-      useComponentState,
-      props,
-      [
-        ({ status, error }) => {
-          expect(status).equals("loading");
-          expect(error).undefined;
-        },
-        ({ status, error }) => {
-          expect(status).equals("loading-error");
-          expect(error).deep.eq({
-            hasError: true,
-            operational: false,
-            message: "Transactions page 0 was not found.",
-          });
-        },
-      ],
-      env.buildTestingContext(),
-    );
-
-    expect(hookBehavior).deep.eq({ result: "ok" });
-    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
-  });
-
-  it("should show error message on server error", async () => {
-    const env = new SwrMockEnvironment(false);
-
-    const props: Props = {
-      account: "123",
-      onSelected: () => {
-        null;
-      },
-    };
-
-    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_ERROR, {});
-
-    const hookBehavior = await tests.hookBehaveLikeThis(
-      useComponentState,
-      props,
-      [
-        ({ status, error }) => {
-          expect(status).equals("loading");
-          expect(error).undefined;
-        },
-        ({ status, error }) => {
-          expect(status).equals("loading-error");
-          expect(error).deep.equal({
-            hasError: true,
-            operational: false,
-            message: "Transaction page 0 could not be retrieved.",
-          });
-        },
-      ],
-      env.buildTestingContext(),
-    );
-
-    expect(hookBehavior).deep.eq({ result: "ok" });
-    expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" });
-  });
 });
diff --git a/packages/demobank-ui/src/components/Transactions/state.ts 
b/packages/demobank-ui/src/components/Transactions/state.ts
index 198ef6c5f..4f99ed678 100644
--- a/packages/demobank-ui/src/components/Transactions/state.ts
+++ b/packages/demobank-ui/src/components/Transactions/state.ts
@@ -32,44 +32,6 @@ export function useComponentState({ account }: Props): State 
{
       error: result,
     };
   }
-  // if (error) {
-  //   switch (error.status) {
-  //     case 404:
-  //       return {
-  //         status: "loading-error",
-  //         error: {
-  //           hasError: true,
-  //           operational: false,
-  //           message: `Transactions page ${pageNumber} was not found.`,
-  //         },
-  //       };
-  //     case 401:
-  //       return {
-  //         status: "loading-error",
-  //         error: {
-  //           hasError: true,
-  //           operational: false,
-  //           message: "Wrong credentials given.",
-  //         },
-  //       };
-  //     default:
-  //       return {
-  //         status: "loading-error",
-  //         error: {
-  //           hasError: true,
-  //           operational: false,
-  //           message: `Transaction page ${pageNumber} could not be 
retrieved.`,
-  //         } as any,
-  //       };
-  //   }
-  // }
-
-  // if (!data) {
-  //   return {
-  //     status: "loading",
-  //     error: undefined,
-  //   };
-  // }
 
   const transactions = result.data.transactions
     .map((item: unknown) => {
diff --git a/packages/demobank-ui/src/components/Transactions/test.ts 
b/packages/demobank-ui/src/components/Transactions/test.ts
index 3f2d5fb68..b13767f7c 100644
--- a/packages/demobank-ui/src/components/Transactions/test.ts
+++ b/packages/demobank-ui/src/components/Transactions/test.ts
@@ -19,12 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { tests } from "@gnu-taler/web-util/lib/index.browser";
+import { ErrorType, tests } from "@gnu-taler/web-util/lib/index.browser";
 import { SwrMockEnvironment } from "@gnu-taler/web-util/lib/tests/swr";
 import { expect } from "chai";
 import { TRANSACTION_API_EXAMPLE } from "../../endpoints.js";
 import { Props } from "./index.js";
 import { useComponentState } from "./state.js";
+import { HttpStatusCode } from "@gnu-taler/taler-util";
 
 describe("Transaction states", () => {
   it("should query backend and render transactions", async () => {
@@ -34,59 +35,62 @@ describe("Transaction states", () => {
       account: "myAccount",
     };
 
+    //@ts-ignore
     env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, {
       response: {
-        transactions: [
-          {
-            creditorIban: "DE159593",
-            creditorBic: "SANDBOXX",
-            creditorName: "exchange company",
-            debtorIban: "DE118695",
-            debtorBic: "SANDBOXX",
-            debtorName: "Name unknown",
-            amount: "1",
-            currency: "KUDOS",
-            subject:
-              "Taler Withdrawal 
N588V8XE9TR49HKAXFQ20P0EQ0EYW2AC9NNANV8ZP5P59N6N0410",
-            date: "2022-12-12Z",
-            uid: "8PPFR9EM",
-            direction: "DBIT",
-            pmtInfId: null,
-            msgId: null,
-          },
-          {
-            creditorIban: "DE159593",
-            creditorBic: "SANDBOXX",
-            creditorName: "exchange company",
-            debtorIban: "DE118695",
-            debtorBic: "SANDBOXX",
-            debtorName: "Name unknown",
-            amount: "5.00",
-            currency: "KUDOS",
-            subject: "HNEWWT679TQC5P1BVXJS48FX9NW18FWM6PTK2N80Z8GVT0ACGNK0",
-            date: "2022-12-07Z",
-            uid: "7FZJC3RJ",
-            direction: "DBIT",
-            pmtInfId: null,
-            msgId: null,
-          },
-          {
-            creditorIban: "DE118695",
-            creditorBic: "SANDBOXX",
-            creditorName: "Name unknown",
-            debtorIban: "DE579516",
-            debtorBic: "SANDBOXX",
-            debtorName: "The Bank",
-            amount: "100",
-            currency: "KUDOS",
-            subject: "Sign-up bonus",
-            date: "2022-12-07Z",
-            uid: "I31A06J8",
-            direction: "CRDT",
-            pmtInfId: null,
-            msgId: null,
-          },
-        ],
+        data: {
+          transactions: [
+            {
+              creditorIban: "DE159593",
+              creditorBic: "SANDBOXX",
+              creditorName: "exchange company",
+              debtorIban: "DE118695",
+              debtorBic: "SANDBOXX",
+              debtorName: "Name unknown",
+              amount: "1",
+              currency: "KUDOS",
+              subject:
+                "Taler Withdrawal 
N588V8XE9TR49HKAXFQ20P0EQ0EYW2AC9NNANV8ZP5P59N6N0410",
+              date: "2022-12-12Z",
+              uid: "8PPFR9EM",
+              direction: "DBIT",
+              pmtInfId: null,
+              msgId: null,
+            },
+            {
+              creditorIban: "DE159593",
+              creditorBic: "SANDBOXX",
+              creditorName: "exchange company",
+              debtorIban: "DE118695",
+              debtorBic: "SANDBOXX",
+              debtorName: "Name unknown",
+              amount: "5.00",
+              currency: "KUDOS",
+              subject: "HNEWWT679TQC5P1BVXJS48FX9NW18FWM6PTK2N80Z8GVT0ACGNK0",
+              date: "2022-12-07Z",
+              uid: "7FZJC3RJ",
+              direction: "DBIT",
+              pmtInfId: null,
+              msgId: null,
+            },
+            {
+              creditorIban: "DE118695",
+              creditorBic: "SANDBOXX",
+              creditorName: "Name unknown",
+              debtorIban: "DE579516",
+              debtorBic: "SANDBOXX",
+              debtorName: "The Bank",
+              amount: "100",
+              currency: "KUDOS",
+              subject: "Sign-up bonus",
+              date: "2022-12-07Z",
+              uid: "I31A06J8",
+              direction: "CRDT",
+              pmtInfId: null,
+              msgId: null,
+            },
+          ],
+        },
       },
     });
 
@@ -118,7 +122,13 @@ describe("Transaction states", () => {
       account: "myAccount",
     };
 
-    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_NOT_FOUND, {});
+    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_NOT_FOUND, {
+      response: {
+        error: {
+          description: "Transaction page 0 could not be retrieved.",
+        },
+      },
+    });
 
     const hookBehavior = await tests.hookBehaveLikeThis(
       useComponentState,
@@ -130,10 +140,13 @@ describe("Transaction states", () => {
         },
         ({ status, error }) => {
           expect(status).equals("loading-error");
-          expect(error).deep.eq({
-            hasError: true,
-            operational: false,
-            message: "Transactions page 0 was not found.",
+          if (error === undefined || error.type !== ErrorType.CLIENT) {
+            throw Error("not the expected error");
+          }
+          expect(error.payload).deep.equal({
+            error: {
+              description: "Transaction page 0 could not be retrieved.",
+            },
           });
         },
       ],
@@ -145,13 +158,19 @@ describe("Transaction states", () => {
   });
 
   it("should show error message on server error", async () => {
-    const env = new SwrMockEnvironment(false);
+    const env = new SwrMockEnvironment();
 
     const props: Props = {
       account: "myAccount",
     };
 
-    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_ERROR, {});
+    env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_ERROR, {
+      response: {
+        error: {
+          description: "Transaction page 0 could not be retrieved.",
+        },
+      },
+    });
 
     const hookBehavior = await tests.hookBehaveLikeThis(
       useComponentState,
@@ -163,10 +182,13 @@ describe("Transaction states", () => {
         },
         ({ status, error }) => {
           expect(status).equals("loading-error");
-          expect(error).deep.equal({
-            hasError: true,
-            operational: false,
-            message: "Transaction page 0 could not be retrieved.",
+          if (error === undefined || error.type !== ErrorType.SERVER) {
+            throw Error("not the expected error");
+          }
+          expect(error.payload).deep.equal({
+            error: {
+              description: "Transaction page 0 could not be retrieved.",
+            },
           });
         },
       ],
diff --git a/packages/demobank-ui/src/context/backend.ts 
b/packages/demobank-ui/src/context/backend.ts
index b462d20e3..b311ddbb0 100644
--- a/packages/demobank-ui/src/context/backend.ts
+++ b/packages/demobank-ui/src/context/backend.ts
@@ -54,3 +54,22 @@ export const BackendStateProvider = ({
     children,
   });
 };
+
+export const BackendStateProviderTesting = ({
+  children,
+  state,
+}: {
+  children: ComponentChildren;
+  state: typeof defaultState;
+}): VNode => {
+  const value: BackendStateHandler = {
+    state,
+    logIn: () => {},
+    logOut: () => {},
+  };
+
+  return h(Context.Provider, {
+    value,
+    children,
+  });
+};
diff --git a/packages/demobank-ui/src/endpoints.ts 
b/packages/demobank-ui/src/endpoints.ts
index e20525ae2..b28c76613 100644
--- a/packages/demobank-ui/src/endpoints.ts
+++ b/packages/demobank-ui/src/endpoints.ts
@@ -22,16 +22,27 @@
 export const TRANSACTION_API_EXAMPLE = {
   LIST_FIRST_PAGE: {
     method: "get" as const,
-    url: "access-api/accounts/myAccount/transactions?page=0",
+    url: '["access-api/accounts/myAccount/transactions",null,20]',
   },
   LIST_ERROR: {
     method: "get" as const,
-    url: "access-api/accounts/myAccount/transactions?page=0",
+    url: '["access-api/accounts/myAccount/transactions",null,20]',
     code: 500,
   },
   LIST_NOT_FOUND: {
     method: "get" as const,
-    url: "access-api/accounts/myAccount/transactions?page=0",
+    url: '["access-api/accounts/myAccount/transactions",null,20]',
     code: 404,
   },
 };
+
+export const CASHOUT_API_EXAMPLE = {
+  LIST_FIRST_PAGE: {
+    method: "get" as const,
+    url: '["circuit-api/cashouts","123"]',
+  },
+  MULTI_GET_EMPTY_FIRST_PAGE: {
+    method: "get" as const,
+    url: "[[]]",
+  },
+};
diff --git a/packages/demobank-ui/src/hooks/access.ts 
b/packages/demobank-ui/src/hooks/access.ts
index 546d59a84..5eddec033 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -378,7 +378,9 @@ export function useTransactions(
     if (afterData) setLastAfter(afterData);
   }, [afterData]);
 
-  if (afterError) return afterError.info;
+  if (afterError) {
+    return afterError.cause;
+  }
 
   // if the query returns less that we ask, then we have reach the end or 
beginning
   const isReachingEnd =
diff --git a/packages/demobank-ui/src/hooks/backend.ts 
b/packages/demobank-ui/src/hooks/backend.ts
index 9fe0f5b14..c67d1cdeb 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -52,7 +52,10 @@ interface LoggedOut {
 }
 
 export function getInitialBackendBaseURL(): string {
-  const overrideUrl = localStorage.getItem("bank-base-url");
+  const overrideUrl =
+    typeof localStorage !== "undefined"
+      ? localStorage.getItem("bank-base-url")
+      : undefined;
   if (!overrideUrl) {
     //normal path
     if (!bankUiSettings.backendBaseURL) {
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index f90469e24..8169947d3 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -525,8 +525,8 @@ export function useCashouts(
     refreshWhenOffline: false,
   });
 
-  if (listError) return listError.info;
-  if (productError) return productError.info;
+  if (listError) return listError.cause;
+  if (productError) return productError.cause;
 
   if (cashouts) {
     const dataWithId = cashouts.map((d) => {
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
index aced5eea9..fa94af75a 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
@@ -19,15 +19,17 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 import { PaymentOptions } from "./PaymentOptions.js";
 
 export default {
   title: "PaymentOptions",
 };
 
-export const USD = {
-  component: PaymentOptions,
-  props: {
+export const USD = tests.createExample(PaymentOptions, {
+  limit: {
     currency: "USD",
+    fraction: 0,
+    value: 1,
   },
-};
+});
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
index 141b9addf..d5f2d941f 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
@@ -19,15 +19,17 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
 
 export default {
   title: "PaytoWireTransferForm",
 };
 
-export const USD = {
-  component: PaytoWireTransferForm,
-  props: {
+export const USD = tests.createExample(PaytoWireTransferForm, {
+  limit: {
     currency: "USD",
+    fraction: 0,
+    value: 1,
   },
-};
+});
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
index 521d4255e..11c21cc09 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
@@ -19,15 +19,14 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 import { QrCodeSection } from "./QrCodeSection.js";
+import { parseWithdrawUri } from "@gnu-taler/taler-util";
 
 export default {
   title: "Qr Code Selection",
 };
 
-export const SimpleExample = {
-  component: QrCodeSection,
-  props: {
-    talerWithdrawUri: "taler://withdraw/asdasdasd",
-  },
-};
+export const SimpleExample = tests.createExample(QrCodeSection, {
+  withdrawUri: parseWithdrawUri("taler://withdraw/bank.com/operationId"),
+});
diff --git a/packages/demobank-ui/src/stories.test.ts 
b/packages/demobank-ui/src/stories.test.ts
index a9e5ec5c0..19b16d52c 100644
--- a/packages/demobank-ui/src/stories.test.ts
+++ b/packages/demobank-ui/src/stories.test.ts
@@ -19,13 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { setupI18n } from "@gnu-taler/taler-util";
-import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
+import { parseGroupImport, tests } from 
"@gnu-taler/web-util/lib/index.browser";
 
-import * as pages from "./pages/index.stories.js";
 import * as components from "./components/index.examples.js";
+import * as pages from "./pages/index.stories.js";
 
-import { h as create } from "preact";
-import { render as renderToString } from "preact-render-to-string";
+import { ComponentChildren, VNode, h as create } from "preact";
+import { BackendStateProviderTesting } from "./context/backend.js";
 
 setupI18n("en", { en: {} });
 
@@ -37,12 +37,7 @@ describe("All the examples:", () => {
         describe(`Component ${component.name}:`, () => {
           component.examples.forEach((example) => {
             it(`should render example: ${example.name}`, () => {
-              const vdom = create(
-                example.render.component,
-                example.render.props,
-              );
-              const html = renderToString(vdom);
-              // console.log(html)
+              tests.renderUI(example.render, DefaultTestingContext);
             });
           });
         });
@@ -50,3 +45,19 @@ describe("All the examples:", () => {
     });
   });
 });
+
+function DefaultTestingContext({
+  children,
+}: {
+  children: ComponentChildren;
+}): VNode {
+  return create(BackendStateProviderTesting, {
+    children,
+    state: {
+      status: "loggedIn",
+      username: "test",
+      password: "pwd",
+      isUserAdministrator: false,
+    },
+  });
+}
diff --git a/packages/merchant-backoffice-ui/package.json 
b/packages/merchant-backoffice-ui/package.json
index ad7d2eb02..dc5668933 100644
--- a/packages/merchant-backoffice-ui/package.json
+++ b/packages/merchant-backoffice-ui/package.json
@@ -9,7 +9,7 @@
     "check": "tsc",
     "compile": "tsc && ./build.mjs",
     "dev": "preact watch --port ${PORT:=8080} --no-sw --no-esm",
-    "test": "pnpm compile && mocha --require source-map-support/register 
'dist/**/*.test.js' 'dist/**/test.js'",
+    "test": "mocha --require source-map-support/register 'dist/**/*.test.js' 
'dist/**/test.js'",
     "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
     "i18n:extract": "pogen extract",
     "i18n:merge": "pogen merge",
diff --git a/packages/merchant-backoffice-ui/src/hooks/testing.tsx 
b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
index 67831f8a4..132c5f166 100644
--- a/packages/merchant-backoffice-ui/src/hooks/testing.tsx
+++ b/packages/merchant-backoffice-ui/src/hooks/testing.tsx
@@ -71,16 +71,19 @@ export class ApiMockEnvironment extends MockEnvironment {
             request: options.data,
           },
         );
+        const status = mocked.expectedQuery?.query.code ?? 200;
+        const requestPayload = mocked.expectedQuery?.params?.request;
+        const responsePayload = mocked.expectedQuery?.params?.response;
 
         return {
           ok: true,
-          data: (!mocked ? undefined : mocked.payload) as T,
+          data: responsePayload as T,
           loading: false,
           clientError: false,
           serverError: false,
           info: {
             hasToken: !!options.token,
-            status: !mocked ? 200 : mocked.status,
+            status,
             url: _url.href,
             payload: options.data,
             options: {},
diff --git a/packages/merchant-backoffice-ui/src/stories.test.ts 
b/packages/merchant-backoffice-ui/src/stories.test.ts
index e85be31b7..df732895f 100644
--- a/packages/merchant-backoffice-ui/src/stories.test.ts
+++ b/packages/merchant-backoffice-ui/src/stories.test.ts
@@ -19,7 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { setupI18n } from "@gnu-taler/taler-util";
-import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
+import { parseGroupImport, tests } from 
"@gnu-taler/web-util/lib/index.browser";
 import { h as create } from "preact";
 import { render as renderToString } from "preact-render-to-string";
 import * as admin from "./paths/admin/index.stories.js";
@@ -35,12 +35,7 @@ describe("All the examples:", () => {
         describe(`Component: ${component.name}`, () => {
           component.examples.forEach((example) => {
             it(`should render example: ${example.name}`, () => {
-              const vdom = create(
-                example.render.component,
-                example.render.props,
-              );
-              const html = renderToString(vdom);
-              // console.log(html)
+              tests.renderUI(example.render);
             });
           });
         });
diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json
index 4001d30a6..e7df2889f 100644
--- a/packages/taler-util/package.json
+++ b/packages/taler-util/package.json
@@ -74,7 +74,7 @@
   },
   "ava": {
     "files": [
-      "lib/*test*"
+      "lib/*test.js"
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/packages/taler-wallet-webextension/package.json 
b/packages/taler-wallet-webextension/package.json
index 41940e10c..98ee28016 100644
--- a/packages/taler-wallet-webextension/package.json
+++ b/packages/taler-wallet-webextension/package.json
@@ -10,7 +10,7 @@
   "private": false,
   "scripts": {
     "clean": "rimraf dist lib tsconfig.tsbuildinfo",
-    "test": "pnpm compile && mocha --require source-map-support/register 
'dist/**/*.test.js' 'dist/**/test.js'",
+    "test": "mocha --require source-map-support/register 'dist/**/*.test.js' 
'dist/**/test.js'",
     "test:coverage": "nyc pnpm test",
     "compile": "tsc && ./build-fast-with-linaria.mjs",
     "prepare": "tsc",
@@ -82,4 +82,4 @@
   "pogen": {
     "domain": "taler-wallet-webex"
   }
-}
\ No newline at end of file
+}
diff --git 
a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
 
b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
index 317d0d1c7..8e799902f 100644
--- 
a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
+++ 
b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
@@ -74,7 +74,7 @@ function locationAsText(l: Location | undefined): VNode {
 
 type State = States.Loading | States.Error | States.Hidden | States.Show;
 
-namespace States {
+export namespace States {
   export interface Loading {
     status: "loading";
     hideHandler: ButtonHandler;
diff --git 
a/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts 
b/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts
deleted file mode 100644
index 4ee572435..000000000
--- a/packages/taler-wallet-webextension/src/serviceWorkerCryptoWorkerFactory.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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/>
- */
-
-/**
- * API to access the Taler crypto worker thread.
- * @author Florian Dold
- */
-
-import {
-  CryptoWorker,
-  CryptoWorkerFactory,
-  SynchronousCryptoWorkerPlain,
-} from "@gnu-taler/taler-wallet-core";
-
-export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
-  startWorker(): CryptoWorker {
-    return new SynchronousCryptoWorkerPlain();
-  }
-
-  getConcurrency(): number {
-    return 1;
-  }
-}
diff --git a/packages/taler-wallet-webextension/src/stories.test.ts 
b/packages/taler-wallet-webextension/src/stories.test.ts
index 76e3db5f4..bf6ef2afe 100644
--- a/packages/taler-wallet-webextension/src/stories.test.ts
+++ b/packages/taler-wallet-webextension/src/stories.test.ts
@@ -19,7 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { setupI18n } from "@gnu-taler/taler-util";
-import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
+import { parseGroupImport, tests } from 
"@gnu-taler/web-util/lib/index.browser";
 import chromeAPI from "./platform/chrome.js";
 import { setupPlatform } from "./platform/foreground.js";
 
@@ -28,8 +28,8 @@ import * as cta from "./cta/index.stories.js";
 import * as mui from "./mui/index.stories.js";
 import * as popup from "./popup/index.stories.js";
 import * as wallet from "./wallet/index.stories.js";
-import { renderNodeOrBrowser } from "./test-utils.js";
-import { h, VNode } from "preact";
+// import { renderNodeOrBrowser } from "./test-utils.js";
+import { h, VNode, ComponentChildren } from "preact";
 import { AlertProvider } from "./context/alert.js";
 
 setupI18n("en", { en: {} });
@@ -43,15 +43,7 @@ describe("All the examples:", () => {
         describe(`Component ${component.name}:`, () => {
           component.examples.forEach((example) => {
             it(`should render example: ${example.name}`, () => {
-              function C(): VNode {
-                const B = h(example.render.component, example.render.props);
-                //FIXME:
-                //some components push the alter in the UI function
-                //that's not correct, should be moved into the state function
-                // until then, we ran the tests with the alert provider
-                return h(AlertProvider, { children: B }, B);
-              }
-              renderNodeOrBrowser(C, {});
+              tests.renderUI(example.render, DefaultTestingContext);
             });
           });
         });
@@ -59,3 +51,14 @@ describe("All the examples:", () => {
     });
   });
 });
+function DefaultTestingContext({
+  children,
+}: {
+  children: ComponentChildren;
+}): VNode {
+  //FIXME:
+  //some components push the alter in the UI function
+  //that's not correct, should be moved into the state function
+  // until then, we ran the tests with the alert provider
+  return h(AlertProvider, { children });
+}
diff --git a/packages/taler-wallet-webextension/src/test-utils.ts 
b/packages/taler-wallet-webextension/src/test-utils.ts
index 1d2538703..9585da346 100644
--- a/packages/taler-wallet-webextension/src/test-utils.ts
+++ b/packages/taler-wallet-webextension/src/test-utils.ts
@@ -21,204 +21,18 @@ import {
   WalletCoreRequestType,
   WalletCoreResponseType,
 } from "@gnu-taler/taler-wallet-core";
+import { TranslationProvider } from "@gnu-taler/web-util/lib/index.browser";
 import {
   ComponentChildren,
-  Fragment,
   FunctionalComponent,
   VNode,
   h as create,
-  options,
-  render as renderIntoDom,
 } from "preact";
-import { render as renderToString } from "preact-render-to-string";
 import { AlertProvider } from "./context/alert.js";
 import { BackendProvider } from "./context/backend.js";
+import { strings } from "./i18n/strings.js";
 import { nullFunction } from "./mui/handlers.js";
 import { BackgroundApiClient, wxApi } from "./wxApi.js";
-import { TranslationProvider } from "@gnu-taler/web-util/lib/index.browser";
-import { strings } from "./i18n/strings.js";
-
-// When doing tests we want the requestAnimationFrame to be as fast as 
possible.
-// without this option the RAF will timeout after 100ms making the tests slower
-options.requestAnimationFrame = (fn: () => void) => {
-  return fn();
-};
-
-export function createExample<Props>(
-  Component: FunctionalComponent<Props>,
-  props: Partial<Props> | (() => Partial<Props>),
-): ComponentChildren {
-  //FIXME: props are evaluated on build time
-  // in some cases we want to evaluated the props on render time so we can get 
some relative timestamp
-  // check how we can build evaluatedProps in render time
-  const evaluatedProps = typeof props === "function" ? props() : props;
-  const Render = (args: any): VNode => create(Component, args);
-  // Render.args = evaluatedProps;
-
-  return {
-    component: Render,
-    props: evaluatedProps,
-  };
-}
-
-export function createExampleWithCustomContext<Props, ContextProps>(
-  Component: FunctionalComponent<Props>,
-  props: Partial<Props> | (() => Partial<Props>),
-  ContextProvider: FunctionalComponent<ContextProps>,
-  contextProps: Partial<ContextProps>,
-): ComponentChildren {
-  const evaluatedProps = typeof props === "function" ? props() : props;
-  const Render = (args: any): VNode => create(Component, args);
-  const WithContext = (args: any): VNode =>
-    create(ContextProvider, {
-      ...contextProps,
-      children: [Render(args)],
-    } as any);
-
-  return {
-    component: WithContext,
-    props: evaluatedProps,
-  };
-}
-
-export function NullLink({
-  children,
-}: {
-  children?: ComponentChildren;
-}): VNode {
-  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);
-  }
-}
-type RecursiveState<S> = S | (() => RecursiveState<S>);
-
-interface Mounted<T> {
-  unmount: () => void;
-  pullLastResultOrThrow: () => Exclude<T, VoidFunction>;
-  assertNoPendingUpdate: () => void;
-  // waitNextUpdate: (s?: string) => Promise<void>;
-  waitForStateUpdate: () => Promise<boolean>;
-}
-
-const isNode = typeof window === "undefined";
-
-export function mountHook<T extends object>(
-  callback: () => RecursiveState<T>,
-  Context?: ({ children }: { children: any }) => VNode,
-): Mounted<T> {
-  let lastResult: Exclude<T, VoidFunction> | Error | null = null;
-
-  const listener: Array<() => void> = [];
-
-  // component that's going to hold the hook
-  function Component(): VNode {
-    try {
-      let componentOrResult = callback();
-      while (typeof componentOrResult === "function") {
-        componentOrResult = componentOrResult();
-      }
-      //typecheck fails here
-      const l: Exclude<T, () => void> = componentOrResult as any;
-      lastResult = l;
-    } catch (e) {
-      if (e instanceof Error) {
-        lastResult = e;
-      } else {
-        lastResult = new Error(`mounting the hook throw an exception: ${e}`);
-      }
-    }
-
-    // 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, {})] });
-
-  const customElement = {} as Element;
-  const parentElement = isNode ? customElement : document.createElement("div");
-  if (!isNode) {
-    document.body.appendChild(parentElement);
-  }
-
-  renderIntoDom(vdom, parentElement);
-
-  // clean up callback
-  function unmount(): void {
-    if (!isNode) {
-      document.body.removeChild(parentElement);
-    }
-  }
-
-  function pullLastResult(): Exclude<T | Error | null, VoidFunction> {
-    const copy: Exclude<T | Error | null, VoidFunction> = lastResult;
-    lastResult = null;
-    return copy;
-  }
-
-  function pullLastResultOrThrow(): Exclude<T, VoidFunction> {
-    const r = pullLastResult();
-    if (r instanceof Error) throw r;
-    if (!r) throw Error("there was no last result");
-    return r;
-  }
-
-  async function assertNoPendingUpdate(): Promise<void> {
-    await new Promise((res, rej) => {
-      const tid = setTimeout(() => {
-        res(undefined);
-      }, 10);
-
-      listener.push(() => {
-        clearTimeout(tid);
-        rej(
-          Error(`Expecting no pending result but the hook got updated. 
-        If the update was not intended you need to check the hook dependencies 
-        (or dependencies of the internal state) but otherwise make 
-        sure to consume the result before ending the test.`),
-        );
-      });
-    });
-
-    const r = pullLastResult();
-    if (r)
-      throw Error(`There are still pending results.
-    This may happen because the hook did a new update but the test didn't 
consume the result using pullLastResult`);
-  }
-  async function waitForStateUpdate(): Promise<boolean> {
-    return await new Promise((res, rej) => {
-      const tid = setTimeout(() => {
-        res(false);
-      }, 10);
-
-      listener.push(() => {
-        clearTimeout(tid);
-        res(true);
-      });
-    });
-  }
-
-  return {
-    unmount,
-    pullLastResultOrThrow,
-    waitForStateUpdate,
-    assertNoPendingUpdate,
-  };
-}
 
 // export const nullFunction: any = () => null;
 
diff --git a/packages/taler-wallet-webextension/src/wallet/QrReader.tsx 
b/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
index 99c42edec..10916496c 100644
--- a/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/QrReader.tsx
@@ -50,7 +50,7 @@ const Container = styled.div`
   }
 `;
 
-interface Props {
+export interface Props {
   onDetected: (url: string) => void;
 }
 
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index 8acc41247..cc72fc9c9 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -33,10 +33,15 @@ import {
   setGlobalLogLevelFromString,
   setLogLevelFromString,
 } from "@gnu-taler/taler-util";
+import {
+  ServiceWorkerHttpLib,
+  BrowserHttpLib,
+} from "@gnu-taler/web-util/lib/index.browser";
 import {
   DbAccess,
   OpenedPromise,
   SetTimeoutTimerAPI,
+  SynchronousCryptoWorkerFactoryPlain,
   Wallet,
   WalletOperations,
   WalletStoresV1,
@@ -46,15 +51,12 @@ import {
   openPromise,
   openTalerDatabase,
 } from "@gnu-taler/taler-wallet-core";
-import { BrowserHttpLib } from "./browserHttpLib.js";
 import {
   MessageFromBackend,
   MessageFromFrontend,
   MessageResponse,
 } from "./platform/api.js";
 import { platform } from "./platform/background.js";
-import { SynchronousCryptoWorkerFactory } from 
"./serviceWorkerCryptoWorkerFactory.js";
-import { ServiceWorkerHttpLib } from "./serviceWorkerHttpLib.js";
 import { ExtensionOperations } from "./taler-wallet-interaction-loader.js";
 import { BackgroundOperations } from "./wxApi.js";
 
@@ -308,14 +310,14 @@ async function reinitWallet(): Promise<void> {
 
   if (platform.useServiceWorkerAsBackgroundProcess()) {
     httpLib = new ServiceWorkerHttpLib();
-    cryptoWorker = new SynchronousCryptoWorkerFactory();
+    cryptoWorker = new SynchronousCryptoWorkerFactoryPlain();
     timer = new SetTimeoutTimerAPI();
   } else {
     httpLib = new BrowserHttpLib();
     // We could (should?) use the BrowserCryptoWorkerFactory here,
     // but right now we don't, to have less platform differences.
     // cryptoWorker = new BrowserCryptoWorkerFactory();
-    cryptoWorker = new SynchronousCryptoWorkerFactory();
+    cryptoWorker = new SynchronousCryptoWorkerFactoryPlain();
     timer = new SetTimeoutTimerAPI();
   }
 
diff --git a/packages/web-util/src/index.browser.ts 
b/packages/web-util/src/index.browser.ts
index b1df2f96e..c7ba8435f 100644
--- a/packages/web-util/src/index.browser.ts
+++ b/packages/web-util/src/index.browser.ts
@@ -1,5 +1,7 @@
 export * from "./hooks/index.js";
 export * from "./utils/request.js";
+export * from "./utils/http-impl.browser.js";
+export * from "./utils/http-impl.sw.js";
 export * from "./utils/observable.js";
 export * from "./context/index.js";
 export * from "./components/index.js";
diff --git a/packages/web-util/src/stories.tsx 
b/packages/web-util/src/stories.tsx
index a8a9fdf77..4edbf83b5 100644
--- a/packages/web-util/src/stories.tsx
+++ b/packages/web-util/src/stories.tsx
@@ -19,7 +19,6 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { setupI18n } from "@gnu-taler/taler-util";
-import e from "express";
 import {
   ComponentChild,
   ComponentChildren,
@@ -32,6 +31,7 @@ import {
   VNode,
 } from "preact";
 import { useEffect, useErrorBoundary, useState } from "preact/hooks";
+import { ExampleItemSetup } from "./tests/hook.js";
 
 const Page: FunctionalComponent = ({ children }): VNode => {
   return (
@@ -323,6 +323,7 @@ function parseExampleImport(
           render: {
             component: exampleValue as FunctionComponent,
             props: {},
+            contextProps: {},
           },
         };
       }
@@ -367,19 +368,16 @@ export interface Group {
   list: ComponentItem[];
 }
 
-export interface ComponentItem {
+export interface ComponentItem<Props extends object = {}> {
   name: string;
-  examples: ExampleItem[];
+  examples: ExampleItem<Props>[];
 }
 
-export interface ExampleItem {
+export interface ExampleItem<Props extends object = {}> {
   group: string;
   component: string;
   name: string;
-  render: {
-    component: FunctionalComponent;
-    props: object;
-  };
+  render: ExampleItemSetup<Props>;
 }
 
 type ComponentOrFolder = MaybeComponent | MaybeFolder;
diff --git a/packages/web-util/src/tests/hook.ts 
b/packages/web-util/src/tests/hook.ts
index fb9f979e5..59f17ba8d 100644
--- a/packages/web-util/src/tests/hook.ts
+++ b/packages/web-util/src/tests/hook.ts
@@ -15,14 +15,15 @@
  */
 
 import {
-  ComponentChildren,
   Fragment,
+  FunctionComponent,
   FunctionalComponent,
+  VNode,
   h as create,
   options,
   render as renderIntoDom,
-  VNode
 } from "preact";
+import { render as renderToString } from "preact-render-to-string";
 
 // This library is expected to be included in testing environment only
 // When doing tests we want the requestAnimationFrame to be as fast as 
possible.
@@ -31,72 +32,107 @@ options.requestAnimationFrame = (fn: () => void) => {
   return fn();
 };
 
-export function createExample<Props>(
+export type ExampleItemSetup<Props extends object = {}> = {
+  component: FunctionalComponent<Props>;
+  props: Props;
+  contextProps: object;
+};
+
+/**
+ *
+ * @param Component component to be tested
+ * @param props allow partial props for easier example setup
+ * @param contextProps if the context requires params for this example
+ * @returns
+ */
+export function createExample<T extends object, Props extends object>(
   Component: FunctionalComponent<Props>,
   props: Partial<Props> | (() => Partial<Props>),
-): ComponentChildren {
+  contextProps?: T | (() => T),
+): ExampleItemSetup<Props> {
   const evaluatedProps = typeof props === "function" ? props() : props;
   const Render = (args: any): VNode => create(Component, args);
-
+  const evaluatedContextProps =
+    typeof contextProps === "function" ? contextProps() : contextProps;
   return {
     component: Render,
-    props: evaluatedProps,
+    props: evaluatedProps as Props,
+    contextProps: !evaluatedContextProps ? {} : evaluatedContextProps,
   };
 }
 
-const isNode = typeof window === "undefined";
-
 /**
- * To be used on automated unit test.
- * So test will run under node or browser
+ * Should render HTML on node and browser
+ * Browser: mount update and unmount
+ * Node: render to string
+ *
  * @param Component
  * @param args
  */
-export function renderNodeOrBrowser(
-  Component: any,
-  args: any,
-  Context?: any,
-): void {
+export function renderUI(example: ExampleItemSetup<any>, Context?: any): void {
   const vdom = !Context
-    ? create(Component, args)
-    : create(Context, { children: [create(Component, args)] });
+    ? create(example.component, example.props)
+    : create(Context, {
+        ...example.contextProps,
+        children: [create(example.component, example.props)],
+      });
 
-  const customElement = {} as Element;
-  const parentElement = isNode ? customElement : document.createElement("div");
-  if (!isNode) {
-    document.body.appendChild(parentElement);
-  }
-  // renderIntoDom works also in nodejs
-  // if the VirtualDOM is composed only by functional components
-  // then no called is going to be made to the DOM api.
-  // vdom should not have any 'div' or other html component
-  renderIntoDom(vdom, parentElement);
-
-  if (!isNode) {
-    document.body.removeChild(parentElement);
+  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);
   }
 }
+
+/**
+ * No need to render.
+ * Should mount, update and run effects.
+ *
+ * Browser: mount update and unmount
+ * Node: mount on a mock virtual dom
+ *
+ * Mounting hook doesn't use DOM api so is
+ * safe to use normal mounting api in node
+ *
+ * @param Component
+ * @param props
+ * @param Context
+ */
+function renderHook(
+  Component: FunctionComponent,
+  Context?: ({ children }: { children: any }) => VNode | null,
+): void {
+  const vdom = !Context
+    ? create(Component, {})
+    : create(Context, { children: [create(Component, {})] });
+
+  //use normal mounting API since we expect
+  //useEffect to be called ( and similar APIs )
+  renderIntoDom(vdom, {} as Element);
+}
+
 type RecursiveState<S> = S | (() => RecursiveState<S>);
 
 interface Mounted<T> {
-  // unmount: () => void;
   pullLastResultOrThrow: () => Exclude<T, VoidFunction>;
   assertNoPendingUpdate: () => Promise<boolean>;
-  // waitNextUpdate: (s?: string) => Promise<void>;
   waitForStateUpdate: () => Promise<boolean>;
 }
 
 /**
  * Manual API mount the hook and return testing API
  * Consider using hookBehaveLikeThis() function
- * 
+ *
  * @param hookToBeTested
  * @param Context
- * 
+ *
  * @returns testing API
  */
-// eslint-disable-next-line @typescript-eslint/ban-types
-export function mountHook<T extends object>(
+function mountHook<T extends object>(
   hookToBeTested: () => RecursiveState<T>,
   Context?: ({ children }: { children: any }) => VNode | null,
 ): Mounted<T> {
@@ -108,6 +144,11 @@ export function mountHook<T extends object>(
   function Component(): VNode {
     try {
       let componentOrResult = hookToBeTested();
+
+      // special loop
+      // since Taler use a special type of hook that can return
+      // a function and it will be treated as a composed component
+      // then tests should be aware of it and reproduce the same behavior
       while (typeof componentOrResult === "function") {
         componentOrResult = componentOrResult();
       }
@@ -127,7 +168,7 @@ export function mountHook<T extends object>(
     return create(Fragment, {});
   }
 
-  renderNodeOrBrowser(Component, {}, Context);
+  renderHook(Component, Context);
 
   function pullLastResult(): Exclude<T | Error | null, VoidFunction> {
     const copy: Exclude<T | Error | null, VoidFunction> = lastResult;
@@ -165,7 +206,6 @@ export function mountHook<T extends object>(
       return Promise.resolve(false);
     }
     return Promise.resolve(true);
-    // throw Error(`There are still pending results.
     //  This may happen because the hook did a new update but the test didn't 
consume the result using pullLastResult`);
   }
   async function waitForStateUpdate(): Promise<boolean> {
@@ -182,7 +222,6 @@ export function mountHook<T extends object>(
   }
 
   return {
-    // unmount,
     pullLastResultOrThrow,
     waitForStateUpdate,
     assertNoPendingUpdate,
@@ -209,13 +248,13 @@ interface HookTestResultError {
 
 /**
  * Main testing driver.
- * It will assert that there are no more and no less hook updates than 
expected. 
- * 
+ * It will assert that there are no more and no less hook updates than 
expected.
+ *
  * @param hookFunction hook function to be tested
  * @param props initial props for the hook
  * @param checks step by step state validation
  * @param Context additional testing context for overrides
- * 
+ *
  * @returns testing result, should also be checked to be "ok"
  */
 // eslint-disable-next-line @typescript-eslint/ban-types
@@ -228,7 +267,7 @@ export async function hookBehaveLikeThis<T extends object, 
PropsType>(
   const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
     mountHook<T>(() => hookFunction(props), Context);
 
-  const [firstCheck, ...resultOfTheChecks] = checks;
+  const [firstCheck, ...restOfTheChecks] = checks;
   {
     const state = pullLastResultOrThrow();
     const checkError = firstCheck(state);
@@ -236,13 +275,13 @@ export async function hookBehaveLikeThis<T extends 
object, PropsType>(
       return {
         result: "fail",
         index: 0,
-        error: `Check return not undefined error: ${checkError}`,
+        error: `First check returned with error: ${checkError}`,
       };
     }
   }
 
   let index = 1;
-  for (const check of resultOfTheChecks) {
+  for (const check of restOfTheChecks) {
     const hasNext = await waitForStateUpdate();
     if (!hasNext) {
       return {
@@ -257,7 +296,7 @@ export async function hookBehaveLikeThis<T extends object, 
PropsType>(
       return {
         result: "fail",
         index,
-        error: `Check return not undefined error: ${checkError}`,
+        error: `Check returned with error: ${checkError}`,
       };
     }
     index++;
diff --git a/packages/web-util/src/tests/mock.ts 
b/packages/web-util/src/tests/mock.ts
index c01e66849..f4eb0e7aa 100644
--- a/packages/web-util/src/tests/mock.ts
+++ b/packages/web-util/src/tests/mock.ts
@@ -15,6 +15,7 @@
  */
 
 import { Logger } from "@gnu-taler/taler-util";
+import { deprecate } from "util";
 
 type HttpMethod =
   | "get"
@@ -63,6 +64,11 @@ type TestValues = {
 
 const logger = new Logger("testing/mock.ts");
 
+type MockedResponse = {
+  queryMade: ExpectationValues;
+  expectedQuery?: ExpectationValues;
+};
+
 export abstract class MockEnvironment {
   expectations: Array<ExpectationValues> = [];
   queriesMade: Array<ExpectationValues> = [];
@@ -108,7 +114,7 @@ export abstract class MockEnvironment {
       qparam?: any;
       response?: ResponseType;
     },
-  ): { status: number; payload: ResponseType } | undefined {
+  ): MockedResponse {
     const queryMade = { query, params, auth: params.auth };
     this.queriesMade.push(queryMade);
     const expectedQuery = this.expectations[this.index];
@@ -116,11 +122,9 @@ export abstract class MockEnvironment {
       if (this.debug) {
         logger.info("unexpected query made", queryMade);
       }
-      return undefined;
+      return { queryMade };
     }
-    const responseCode = this.expectations[this.index].query.code ?? 200;
-    const mockedResponse = this.expectations[this.index].params
-      ?.response as ResponseType;
+
     if (this.debug) {
       logger.info("tracking query made", {
         queryMade,
@@ -128,7 +132,7 @@ export abstract class MockEnvironment {
       });
     }
     this.index++;
-    return { status: responseCode, payload: mockedResponse };
+    return { queryMade, expectedQuery };
   }
 
   public assertJustExpectedRequestWereMade(): AssertStatus {
diff --git a/packages/web-util/src/tests/swr.ts 
b/packages/web-util/src/tests/swr.ts
index 62a35f83d..903cd48d8 100644
--- a/packages/web-util/src/tests/swr.ts
+++ b/packages/web-util/src/tests/swr.ts
@@ -17,12 +17,17 @@
 import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
 import { MockEnvironment } from "./mock.js";
 import { SWRConfig } from "swr";
+import * as swr__internal from "swr/_internal";
+import { Logger } from "@gnu-taler/taler-util";
+import { buildRequestFailed, RequestError } from "../index.browser.js";
+
+const logger = new Logger("tests/swr.ts");
 
 /**
  * Helper for hook that use SWR inside.
- * 
+ *
  * buildTestingContext() will return a testing context
- * 
+ *
  */
 export class SwrMockEnvironment extends MockEnvironment {
   constructor(debug = false) {
@@ -32,47 +37,68 @@ export class SwrMockEnvironment extends MockEnvironment {
   public buildTestingContext(): FunctionalComponent<{
     children: ComponentChildren;
   }> {
-    const __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE = 
this.saveRequestAndGetMockedResponse.bind(this);
+    const __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE =
+      this.saveRequestAndGetMockedResponse.bind(this);
+
+    function testingFetcher(params: any): any {
+      const url = JSON.stringify(params);
+      const mocked = __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE<any, any>(
+        {
+          method: "get",
+          url,
+        },
+        {},
+      );
+
+      //unexpected query
+      if (!mocked.expectedQuery) return undefined;
+      const status = mocked.expectedQuery.query.code ?? 200;
+      const requestPayload = mocked.expectedQuery.params?.request;
+      const responsePayload = mocked.expectedQuery.params?.response;
+      //simulated error
+      if (status >= 400) {
+        const error = buildRequestFailed(
+          url,
+          JSON.stringify(responsePayload),
+          status,
+          requestPayload,
+        );
+        //example error handling from 
https://swr.vercel.app/docs/error-handling
+        throw new RequestError(error);
+      }
+      return responsePayload;
+    }
+
+    const value: Partial<swr__internal.PublicConfiguration> & {
+      provider: () => Map<any, any>;
+    } = {
+      use: [
+        (useSWRNext) => {
+          return (key, fetcher, config) => {
+            //prevent the request
+            //use the testing fetcher instead
+            return useSWRNext(key, testingFetcher, config);
+          };
+        },
+      ],
+      fetcher: testingFetcher,
+      //These options are set for ending the test faster
+      //otherwise SWR will create timeouts that will live after the test 
finished
+      loadingTimeout: 0,
+      dedupingInterval: 0,
+      shouldRetryOnError: false,
+      errorRetryInterval: 0,
+      errorRetryCount: 0,
+      //clean cache for every test
+      provider: () => new Map(),
+    };
+
     return function TestingContext({
       children,
     }: {
       children: ComponentChildren;
     }): VNode {
-      return h(
-        SWRConfig,
-        {
-          value: {
-            // eslint-disable-next-line @typescript-eslint/ban-types
-            fetcher: (url: string, options: object) => {
-              const mocked = __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE(
-                {
-                  method: "get",
-                  url,
-                },
-                {},
-              );
-              if (!mocked) return undefined;
-              if (mocked.status > 400) {
-                const e: any = Error("simulated error for testing");
-                //example error handling from 
https://swr.vercel.app/docs/error-handling
-                e.status = mocked.status;
-                throw e;
-              }
-              return mocked.payload;
-            },
-            //These options are set for ending the test faster
-            //otherwise SWR will create timeouts that will live after the test 
finished
-            loadingTimeout: 0,
-            dedupingInterval: 0,
-            shouldRetryOnError: false,
-            errorRetryInterval: 0,
-            errorRetryCount: 0,
-            //clean cache for every test
-            provider: () => new Map(),
-          },
-        },
-        children,
-      );
+      return h(SWRConfig, { value }, children);
     };
   }
 }
diff --git a/packages/taler-wallet-webextension/src/browserHttpLib.ts 
b/packages/web-util/src/utils/http-impl.browser.ts
similarity index 100%
rename from packages/taler-wallet-webextension/src/browserHttpLib.ts
rename to packages/web-util/src/utils/http-impl.browser.ts
diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts 
b/packages/web-util/src/utils/http-impl.sw.ts
similarity index 100%
rename from packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
rename to packages/web-util/src/utils/http-impl.sw.ts
diff --git a/packages/web-util/src/utils/request.ts 
b/packages/web-util/src/utils/request.ts
index 8c77814f7..7f7063a23 100644
--- a/packages/web-util/src/utils/request.ts
+++ b/packages/web-util/src/utils/request.ts
@@ -126,11 +126,12 @@ export async function defaultRequestHandler<T>(
     );
     return result;
   } else {
-    const error = await buildRequestFailed(
-      response,
+    const dataTxt = await response.text();
+    const error = buildRequestFailed(
       _url.href,
+      dataTxt,
+      response.status,
       payload,
-      !!options.token,
       options,
     );
     throw new RequestError(error);
@@ -292,47 +293,58 @@ async function buildRequestOk<T>(
   };
 }
 
-async function buildRequestFailed<ErrorDetail>(
-  response: Response,
+export function buildRequestFailed<ErrorDetail>(
   url: string,
+  dataTxt: string,
+  status: number,
   payload: any,
-  hasToken: boolean,
-  options: RequestOptions,
-): Promise<
+  maybeOptions?: RequestOptions,
+):
   | HttpResponseClientError<ErrorDetail>
   | HttpResponseServerError<ErrorDetail>
   | HttpResponseUnreadableError
-  | HttpResponseUnexpectedError
-> {
-  const status = response?.status;
-
+  | HttpResponseUnexpectedError {
+  const options = maybeOptions ?? {};
   const info: RequestInfo = {
     payload,
     url,
-    hasToken,
+    hasToken: !!options.token,
     options,
     status: status || 0,
   };
 
-  const dataTxt = await response.text();
+  // const dataTxt = await response.text();
   try {
     const data = dataTxt ? JSON.parse(dataTxt) : undefined;
+    const errorCode = !data || !data.code ? "" : `(code: ${data.code})`;
+    const errorHint =
+      !data || !data.hint ? "Not hint." : `${data.hint} ${errorCode}`;
+
     if (status && status >= 400 && status < 500) {
+      const message =
+        data === undefined
+          ? `Client error (${status}) without data.`
+          : errorHint;
+
       const error: HttpResponseClientError<ErrorDetail> = {
         type: ErrorType.CLIENT,
         status,
         info,
-        message: data?.hint,
+        message,
         payload: data,
       };
       return error;
     }
     if (status && status >= 500 && status < 600) {
+      const message =
+        data === undefined
+          ? `Server error (${status}) without data.`
+          : errorHint;
       const error: HttpResponseServerError<ErrorDetail> = {
         type: ErrorType.SERVER,
         status,
         info,
-        message: `${data?.hint} (code ${data?.code})`,
+        message,
         payload: data,
       };
       return error;
diff --git a/packages/web-util/tsconfig.json b/packages/web-util/tsconfig.json
index e2ac248aa..fc558e59b 100644
--- a/packages/web-util/tsconfig.json
+++ b/packages/web-util/tsconfig.json
@@ -1,6 +1,8 @@
 {
   "compilerOptions": {
     "composite": true,
+    "declaration": true,
+    "declarationMap": false,
     "target": "ES6",
     "module": "ESNext",
     "jsx": "react",

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]