summaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/test-utils.ts
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/src/test-utils.ts
parentb00635c1404ed3cc6ed36940bd54ff70cb837f0f (diff)
downloadwallet-core-b419db505b8cd5e7aa92043696f42a0d710d9226.tar.gz
wallet-core-b419db505b8cd5e7aa92043696f42a0d710d9226.tar.bz2
wallet-core-b419db505b8cd5e7aa92043696f42a0d710d9226.zip
ui testing
Diffstat (limited to 'packages/anastasis-webui/src/test-utils.ts')
-rw-r--r--packages/anastasis-webui/src/test-utils.ts201
1 files changed, 201 insertions, 0 deletions
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,
+ };
+}