render hook and render ui are not the same function (node and browser)
This commit is contained in:
parent
9fe1c4b5ec
commit
3772ff85db
@ -15,14 +15,16 @@
|
||||
*/
|
||||
|
||||
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";
|
||||
import { ExampleItem, ExampleItemSetup } from "../stories.js";
|
||||
|
||||
// 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,51 +33,83 @@ options.requestAnimationFrame = (fn: () => void) => {
|
||||
return fn();
|
||||
};
|
||||
|
||||
export function createExample<Props>(
|
||||
/**
|
||||
*
|
||||
* @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,
|
||||
export function renderUI(example: ExampleItemSetup<any>, Context?: any): void {
|
||||
const vdom = !Context
|
||||
? create(example.component, example.props)
|
||||
: create(Context, {
|
||||
...example.contextProps,
|
||||
children: [create(example.component, example.props)],
|
||||
});
|
||||
|
||||
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, args)
|
||||
: create(Context, { children: [create(Component, args)] });
|
||||
? create(Component, {})
|
||||
: create(Context, { children: [create(Component, {})] });
|
||||
|
||||
const customElement = {} as Element;
|
||||
const parentElement = isNode ? customElement : document.createElement("div");
|
||||
if (!isNode) {
|
||||
document.body.appendChild(parentElement);
|
||||
//use normal mounting API since we expect
|
||||
//useEffect to be called ( and similar APIs )
|
||||
renderIntoDom(vdom, {} as Element);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
type RecursiveState<S> = S | (() => RecursiveState<S>);
|
||||
|
||||
interface Mounted<T> {
|
||||
@ -95,8 +129,7 @@ interface Mounted<T> {
|
||||
*
|
||||
* @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 +141,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 +165,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 +203,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 +219,6 @@ export function mountHook<T extends object>(
|
||||
}
|
||||
|
||||
return {
|
||||
// unmount,
|
||||
pullLastResultOrThrow,
|
||||
waitForStateUpdate,
|
||||
assertNoPendingUpdate,
|
||||
@ -228,7 +264,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 +272,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 +293,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++;
|
||||
|
Loading…
Reference in New Issue
Block a user