aboutsummaryrefslogtreecommitdiff
path: root/packages/anastasis-webui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-06-08 15:18:41 -0300
committerSebastian <sebasjm@gmail.com>2022-06-08 15:19:26 -0300
commitb419db505b8cd5e7aa92043696f42a0d710d9226 (patch)
treec18eb877999de68c5be6821710a1c0ba7ace4a1b /packages/anastasis-webui
parentb00635c1404ed3cc6ed36940bd54ff70cb837f0f (diff)
downloadwallet-core-b419db505b8cd5e7aa92043696f42a0d710d9226.tar.xz
ui testing
Diffstat (limited to 'packages/anastasis-webui')
-rwxr-xr-xpackages/anastasis-webui/clean_and_build.sh53
-rwxr-xr-xpackages/anastasis-webui/dev.mjs25
-rw-r--r--packages/anastasis-webui/html/stories.html (renamed from packages/anastasis-webui/stories.html)0
-rw-r--r--packages/anastasis-webui/html/ui-dev.html50
-rw-r--r--packages/anastasis-webui/html/ui.html (renamed from packages/anastasis-webui/ui.html)0
-rw-r--r--packages/anastasis-webui/package.json4
-rw-r--r--packages/anastasis-webui/src/main.test.ts48
-rw-r--r--packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx2
-rw-r--r--packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx16
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx1
-rw-r--r--packages/anastasis-webui/src/stories.tsx21
-rw-r--r--packages/anastasis-webui/src/test-utils.ts201
-rw-r--r--packages/anastasis-webui/src/utils/index.tsx14
-rwxr-xr-xpackages/anastasis-webui/watch/reply.sh17
-rwxr-xr-xpackages/anastasis-webui/watch/send_reload.sh7
-rwxr-xr-xpackages/anastasis-webui/watch/serve.sh4
-rw-r--r--packages/anastasis-webui/watch/web_socket_client.request6
-rw-r--r--packages/anastasis-webui/watch/web_socket_server.reply5
18 files changed, 435 insertions, 39 deletions
diff --git a/packages/anastasis-webui/clean_and_build.sh b/packages/anastasis-webui/clean_and_build.sh
index 300ed8930..2fa3ec777 100755
--- a/packages/anastasis-webui/clean_and_build.sh
+++ b/packages/anastasis-webui/clean_and_build.sh
@@ -10,16 +10,51 @@ cp \
src/scss/fonts/materialdesignicons-webfont-4.9.95.woff2 \
dist/fonts
-echo css
-pnpm exec sass -I . ./src/scss/main.scss dist/main.css &
-echo js
-pnpm exec esbuild --log-level=error --bundle src/main.ts --outdir=dist --target=es6 --loader:.svg=dataurl --format=iife --sourcemap --jsx-factory=h --jsx-fragment=Fragment --platform=browser &
+function build_css() {
+ pnpm exec sass -I . ./src/scss/main.scss dist/main.css
+}
+function build_js() {
+ pnpm exec esbuild --log-level=error --bundle $1 --outdir=dist --target=es6 --loader:.svg=dataurl --format=iife --sourcemap --jsx-factory=h --jsx-fragment=Fragment --platform=browser
+}
+
+function bundle() {
+ cat html/$1.html \
+ | sed -e '/ANASTASIS_SCRIPT_CONTENT/ {' -e 'r dist/main.js' -e 'd' -e '}' \
+ | sed -e '/ANASTASIS_STYLE_CONTENT/ {' -e 'r dist/main.css' -e 'd' -e '}' \
+ >dist/$1.html
+}
+
+function cleanup {
+ trap - SIGHUP SIGINT SIGTERM SIGQUIT
+ echo -n "Cleaning up... "
+ kill -- -$$
+ exit 1
+}
+trap cleanup SIGHUP SIGINT SIGTERM SIGQUIT
+
+
+echo compile
+build_css &
+build_js src/main.ts &
+build_js src/main.test.ts &
wait -n
wait -n
+wait -n
+pnpm run --silent test -- -R dot
echo html
-cat ui.html \
- | sed -e '/ANASTASIS_SCRIPT_CONTENT/ {' -e 'r dist/main.js' -e 'd' -e '}' \
- | sed -e '/ANASTASIS_STYLE_CONTENT/ {' -e 'r dist/main.css' -e 'd' -e '}' \
- >dist/index.html
-echo done
+bundle ui
+bundle ui-dev
+
+
+if [ "WATCH" == "$1" ]; then
+
+ echo watch mode
+ inotifywait -e close_write -r src -q -m | while read line; do
+ DATE=$(date)
+ echo $DATE $line
+ build_js src/main.ts
+ bundle ui-dev
+ ./watch/send_reload.sh
+ done;
+fi
diff --git a/packages/anastasis-webui/dev.mjs b/packages/anastasis-webui/dev.mjs
index d6b6bf10d..3f4915ffc 100755
--- a/packages/anastasis-webui/dev.mjs
+++ b/packages/anastasis-webui/dev.mjs
@@ -1,4 +1,19 @@
#!/usr/bin/env node
+/*
+ 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/>
+ */
/* eslint-disable no-undef */
import esbuild from 'esbuild'
import fs from 'fs';
@@ -41,14 +56,17 @@ const watcher = chokidar
broadcast(file, { type: "RELOAD" });
});
-
-fs.writeFileSync("dist/stories.html", fs.readFileSync("stories.html"))
+/**
+ * Just bundling UI Stories.
+ * FIXME: add linaria CSS after implementing Material so CSS will be bundled
+ */
+fs.writeFileSync("dist/index.html", fs.readFileSync("html/stories.html"))
fs.writeFileSync("dist/mocha.css", fs.readFileSync("node_modules/mocha/mocha.css"))
fs.writeFileSync("dist/mocha.js", fs.readFileSync("node_modules/mocha/mocha.js"))
fs.writeFileSync("dist/mocha.js.map", fs.readFileSync("node_modules/mocha/mocha.js.map"))
export const buildConfig = {
- entryPoints: ['src/stories.tsx'],
+ entryPoints: ['src/main.ts', 'src/stories.tsx'],
bundle: true,
outdir: 'dist',
minify: false,
@@ -75,7 +93,6 @@ const server = await esbuild
});
console.log(`Dev server is ready at http://localhost:${server.port}/.
-http://localhost:${server.port}/stories.html for the components stories.
The server is running a using websocket at ${devServerPort} to notify code change and live reload.
`);
diff --git a/packages/anastasis-webui/stories.html b/packages/anastasis-webui/html/stories.html
index 9f41fdeaf..9f41fdeaf 100644
--- a/packages/anastasis-webui/stories.html
+++ b/packages/anastasis-webui/html/stories.html
diff --git a/packages/anastasis-webui/html/ui-dev.html b/packages/anastasis-webui/html/ui-dev.html
new file mode 100644
index 000000000..4cc36268e
--- /dev/null
+++ b/packages/anastasis-webui/html/ui-dev.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html
+ lang="en"
+ class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
+>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <meta name="mobile-web-app-capable" content="yes" />
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+
+ <link
+ rel="icon"
+ href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+ />
+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
+ <style type="text/css">
+ /* <![CDATA[ */
+ ANASTASIS_STYLE_CONTENT
+ /* <![CDATA[ */
+ </style>
+ </head>
+
+ <body>
+ <div id="container" class="anastasis-container"></div>
+ <script type="application/javascript">
+ ANASTASIS_SCRIPT_CONTENT;
+ </script>
+ <script type="application/javascript">
+ function setupLiveReload(port) {
+ const socketPath = `ws://localhost:${port}/socket`;
+
+ const ws = new WebSocket(socketPath);
+ ws.onmessage = (message) => {
+ const event = JSON.parse(message.data);
+ if (event.type === "LOG") {
+ console.log(event.message);
+ }
+ if (event.type === "RELOAD") {
+ window.location.reload();
+ }
+ };
+ ws.onerror = (error) => {
+ console.error(error);
+ };
+ }
+ setupLiveReload(8003);
+ </script>
+ </body>
+</html>
diff --git a/packages/anastasis-webui/ui.html b/packages/anastasis-webui/html/ui.html
index 17c48e904..17c48e904 100644
--- a/packages/anastasis-webui/ui.html
+++ b/packages/anastasis-webui/html/ui.html
diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json
index 95b85585d..7c57160ce 100644
--- a/packages/anastasis-webui/package.json
+++ b/packages/anastasis-webui/package.json
@@ -9,7 +9,7 @@
"dev": "./dev.mjs",
"prepare": "pnpm compile",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
- "test": "echo no tests",
+ "test": "mocha --enable-source-maps 'dist/**/*.test.js'",
"pretty": "prettier --write src"
},
"dependencies": {
@@ -18,6 +18,7 @@
"date-fns": "2.28.0",
"jed": "1.1.1",
"preact": "^10.5.15",
+ "preact-render-to-string": "^5.1.19",
"preact-router": "^3.2.1",
"qrcode-generator": "^1.4.4"
},
@@ -34,6 +35,7 @@
},
"devDependencies": {
"@creativebulma/bulma-tooltip": "^1.2.0",
+ "@types/mocha": "^9.0.0",
"bulma": "^0.9.3",
"bulma-checkbox": "^1.1.1",
"bulma-radio": "^1.1.1",
diff --git a/packages/anastasis-webui/src/main.test.ts b/packages/anastasis-webui/src/main.test.ts
new file mode 100644
index 000000000..e65cca454
--- /dev/null
+++ b/packages/anastasis-webui/src/main.test.ts
@@ -0,0 +1,48 @@
+/*
+ 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/>
+ */
+
+/**
+ *
+ * @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";
+
+setupI18n("en", { en: {} });
+
+function testThisStory(st: any): any {
+ describe(`render examples for ${(st as any).default.title}`, () => {
+ Object.keys(st).forEach((k) => {
+ const Component = (st as any)[k];
+ if (k === "default" || !Component) return;
+
+ it(`example: ${k}`, () => {
+ renderNodeOrBrowser(Component, Component.args);
+ });
+ });
+ });
+}
+
+describe("render every storybook example", () => {
+ [pages].forEach(function testAll(st: any) {
+ if (Array.isArray(st.default)) {
+ st.default.forEach(testAll);
+ } else {
+ testThisStory(st);
+ }
+ });
+});
diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx
index feeac274d..d4675f9da 100644
--- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx
@@ -223,7 +223,7 @@ function TableRow({
onDelete: (s: string) => void;
url: string;
info: AuthenticationProviderStatusOk;
-}) {
+}): VNode {
const [status, setStatus] = useState("checking");
useEffect(function () {
testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url)
diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
index 43d865b48..534f9136d 100644
--- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
@@ -125,13 +125,6 @@ export function ContinentSelectionScreen(): VNode {
</div>
</div>
</div>
-
- {/* {theCountry && <div class="field">
- <label class="label">Available currencies:</label>
- <div class="control">
- <input class="input is-small" type="text" readonly value={theCountry.currency} />
- </div>
- </div>} */}
</div>
<div class="column is-two-third">
<p>
@@ -151,18 +144,11 @@ export function ContinentSelectionScreen(): VNode {
}}
>
<p>
- If you just want to try out Anastasis, we recomment that you
+ If you just want to try out Anastasis, we recommend that you
choose <b>Testcontinent</b> with <b>Demoland</b>. For this special
country, you will be asked for a simple number and not real,
personal identifiable information.
</p>
- {/*
- <p>
- Because of the diversity of personally identifying information in
- different countries and cultures, we do not support all countries
- yet. If you want to improve the supported countries,{" "}
- <a href="mailto:contact@anastasis.lu">contact us</a>.
- </p> */}
</div>
</div>
</div>
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx
index f9b292d94..0285c87e1 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx
@@ -36,6 +36,7 @@ export function AuthMethodTotpSetup({
const [test, setTest] = useState("");
const secretKey = useMemo(() => {
const array = new Uint8Array(32);
+ if (typeof window === "undefined") return array;
return window.crypto.getRandomValues(array);
}, []);
diff --git a/packages/anastasis-webui/src/stories.tsx b/packages/anastasis-webui/src/stories.tsx
index a51dfb20f..351d6f37b 100644
--- a/packages/anastasis-webui/src/stories.tsx
+++ b/packages/anastasis-webui/src/stories.tsx
@@ -143,21 +143,24 @@ function ExampleList({
{k.examples.map((r) => {
const e = encodeURIComponent;
const eId = `${e(r.group)}-${e(r.component)}-${e(r.name)}`;
+ function doSelection(e: any): void {
+ e.preventDefault();
+ location.hash = `#${eId}`;
+ onSelectStory(r, eId);
+ }
const isSelected =
selected &&
selected.component === r.component &&
selected.group === r.group &&
selected.name === r.name;
return (
- <dd id={eId} key={r.name} data-selected={isSelected}>
- <a
- href={`#${eId}`}
- onClick={(e) => {
- e.preventDefault();
- location.hash = `#${eId}`;
- onSelectStory(r, eId);
- }}
- >
+ <dd
+ id={eId}
+ key={r.name}
+ data-selected={isSelected}
+ onClick={doSelection}
+ >
+ <a href={`#${eId}`} onClick={doSelection}>
{r.name}
</a>
</dd>
diff --git a/packages/anastasis-webui/src/test-utils.ts b/packages/anastasis-webui/src/test-utils.ts
new file mode 100644
index 000000000..1fcc753ee
--- /dev/null
+++ b/packages/anastasis-webui/src/test-utils.ts
@@ -0,0 +1,201 @@
+/*
+ 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);
+ Render.args = evaluatedProps;
+ return Render;
+}
+
+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);
+ WithContext.args = evaluatedProps;
+ return WithContext;
+}
+
+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 ce21071f7..204c48d18 100644
--- a/packages/anastasis-webui/src/utils/index.tsx
+++ b/packages/anastasis-webui/src/utils/index.tsx
@@ -209,10 +209,12 @@ export const reducerStatesExample = {
initial: undefined,
recoverySelectCountry: {
...base,
+ reducer_type: "recovery",
recovery_state: RecoveryStates.CountrySelecting,
} as ReducerState,
recoverySelectContinent: {
...base,
+ reducer_type: "recovery",
recovery_state: RecoveryStates.ContinentSelecting,
} as ReducerState,
secretSelection: {
@@ -222,10 +224,12 @@ export const reducerStatesExample = {
} as ReducerState,
recoveryFinished: {
...base,
+ reducer_type: "recovery",
recovery_state: RecoveryStates.RecoveryFinished,
} as ReducerState,
challengeSelecting: {
...base,
+ reducer_type: "recovery",
recovery_state: RecoveryStates.ChallengeSelecting,
} as ReducerState,
challengeSolving: {
@@ -235,34 +239,42 @@ export const reducerStatesExample = {
} as ReducerStateRecovery,
challengePaying: {
...base,
+ reducer_type: "recovery",
recovery_state: RecoveryStates.ChallengePaying,
} as ReducerState,
recoveryAttributeEditing: {
...base,
+ reducer_type: "recovery",
recovery_state: RecoveryStates.UserAttributesCollecting,
} as ReducerState,
backupSelectCountry: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.CountrySelecting,
} as ReducerState,
backupSelectContinent: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.ContinentSelecting,
} as ReducerState,
secretEdition: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.SecretEditing,
} as ReducerState,
policyReview: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.PoliciesReviewing,
} as ReducerState,
policyPay: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.PoliciesPaying,
} as ReducerState,
backupFinished: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.BackupFinished,
} as ReducerState,
authEditing: {
@@ -272,10 +284,12 @@ export const reducerStatesExample = {
} as ReducerState,
backupAttributeEditing: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.UserAttributesCollecting,
} as ReducerState,
truthsPaying: {
...base,
+ reducer_type: "backup",
backup_state: BackupStates.TruthsPaying,
} as ReducerState,
};
diff --git a/packages/anastasis-webui/watch/reply.sh b/packages/anastasis-webui/watch/reply.sh
new file mode 100755
index 000000000..1b42aa4e3
--- /dev/null
+++ b/packages/anastasis-webui/watch/reply.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+SERVER_KEY=258EAFA5-E914-47DA-95CA-C5AB0DC85B11
+
+while read line; do
+ LINE=$(echo $line | tr -d '\r')
+ case $LINE in
+ Sec-WebSocket-Key:*)
+ CLIENT_KEY="${LINE:19}"
+ export WS_ACCEPT=$( echo -n $CLIENT_KEY$SERVER_KEY | sha1sum | xxd -r -p | base64 )
+ ;;
+ "") break ;;
+ esac
+done
+
+cat watch/web_socket_server.reply | sed 's/$'"/`echo \\\r`/" | envsubst '$WS_ACCEPT'
+
+socat UNIX-RECV:./send_signal STDOUT
diff --git a/packages/anastasis-webui/watch/send_reload.sh b/packages/anastasis-webui/watch/send_reload.sh
new file mode 100755
index 000000000..87eef7d54
--- /dev/null
+++ b/packages/anastasis-webui/watch/send_reload.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+COMMAND='{"type":"RELOAD"}'
+LEN=$(printf '%x\n' ${#COMMAND})
+OPCODE=81
+cat <(echo -n $OPCODE$LEN | xxd -r -p) <(echo -n $COMMAND) | socat - UNIX-SEND:./send_signal
+
diff --git a/packages/anastasis-webui/watch/serve.sh b/packages/anastasis-webui/watch/serve.sh
new file mode 100755
index 000000000..cf2737416
--- /dev/null
+++ b/packages/anastasis-webui/watch/serve.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+socat TCP-LISTEN:8003,fork EXEC:"./watch/reply.sh"
+
diff --git a/packages/anastasis-webui/watch/web_socket_client.request b/packages/anastasis-webui/watch/web_socket_client.request
new file mode 100644
index 000000000..e7077b0cb
--- /dev/null
+++ b/packages/anastasis-webui/watch/web_socket_client.request
@@ -0,0 +1,6 @@
+GET /socket HTTP/1.1
+Connection: Upgrade
+Upgrade: websocket
+Sec-WebSocket-Version: 13
+Sec-WebSocket-Key: aaaaaaaaaaaaaaaaaaaaaa==
+
diff --git a/packages/anastasis-webui/watch/web_socket_server.reply b/packages/anastasis-webui/watch/web_socket_server.reply
new file mode 100644
index 000000000..b4e0db001
--- /dev/null
+++ b/packages/anastasis-webui/watch/web_socket_server.reply
@@ -0,0 +1,5 @@
+HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: $WS_ACCEPT
+