Files
owl/tests/helpers.ts
T

266 lines
6.9 KiB
TypeScript
Raw Normal View History

2021-12-20 11:19:59 +01:00
import { diff } from "jest-diff";
2020-11-26 16:45:25 +01:00
import {
2021-12-20 11:19:59 +01:00
blockDom,
2021-10-13 16:49:34 +02:00
Component,
2020-11-26 16:45:25 +01:00
onMounted,
onPatched,
2021-12-20 11:19:59 +01:00
onRendered,
onWillDestroy,
2021-10-13 16:49:34 +02:00
onWillPatch,
2021-12-20 11:19:59 +01:00
onWillRender,
2020-11-26 16:45:25 +01:00
onWillStart,
2021-10-13 16:49:34 +02:00
onWillUnmount,
2020-11-26 16:45:25 +01:00
onWillUpdateProps,
status,
2021-10-13 16:49:34 +02:00
useComponent,
2021-12-20 13:54:52 +01:00
xml,
2020-11-26 16:45:25 +01:00
} from "../src";
2022-03-02 10:04:54 +01:00
import { helpers } from "../src/app/template_helpers";
import { TemplateSet, globalTemplates } from "../src/app/template_set";
2021-12-20 11:19:59 +01:00
import { BDom } from "../src/blockdom";
import { compile } from "../src/compiler";
2020-11-26 16:45:25 +01:00
const mount = blockDom.mount;
2019-03-04 22:47:20 +01:00
export function nextMicroTick(): Promise<void> {
return Promise.resolve();
}
2020-11-26 16:45:25 +01:00
let lastFixture: any = null;
2019-03-04 22:47:20 +01:00
export function makeTestFixture() {
let fixture = document.createElement("div");
document.body.appendChild(fixture);
2020-11-26 16:45:25 +01:00
if (lastFixture) {
lastFixture.remove();
}
lastFixture = fixture;
2019-03-04 22:47:20 +01:00
return fixture;
}
2021-12-20 13:54:52 +01:00
beforeEach(() => {
xml.nextId = 999;
});
2020-11-26 16:45:25 +01:00
export async function nextTick(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
2019-03-04 22:47:20 +01:00
}
interface Deferred extends Promise<any> {
resolve(val?: any): void;
reject(val?: any): void;
2019-03-04 22:47:20 +01:00
}
export function makeDeferred(): Deferred {
let resolve, reject;
let def = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
2020-11-26 16:45:25 +01:00
(def as any).resolve = resolve;
(def as any).reject = reject;
2019-03-04 22:47:20 +01:00
return <Deferred>def;
}
2019-03-14 11:50:39 +01:00
export function trim(str: string): string {
return str.replace(/\s/g, "");
}
2020-11-26 16:45:25 +01:00
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
2021-12-20 11:19:59 +01:00
let shouldSnapshot = false;
let snapshottedTemplates: Set<string> = new Set();
export function snapshotTemplate(template: string) {
const fn = compile(template);
expect(fn.toString()).toMatchSnapshot();
2020-11-26 16:45:25 +01:00
}
2021-12-22 15:06:28 +01:00
export function renderToBdom(template: string, context: any = {}, node?: any): BDom {
if (!node) {
if (!context.__owl__) {
context.__owl__ = { component: context };
} else {
context.__owl__.component = context;
}
}
2021-12-20 11:19:59 +01:00
const fn = compile(template);
if (shouldSnapshot && !snapshottedTemplates.has(template)) {
snapshottedTemplates.add(template);
expect(fn.toString()).toMatchSnapshot();
}
2022-05-26 15:09:09 +02:00
return fn(null as any, blockDom, helpers)(context, node);
2020-11-26 16:45:25 +01:00
}
2021-12-22 15:06:28 +01:00
export function renderToString(template: string, context: any = {}, node?: any): string {
2020-11-26 16:45:25 +01:00
const fixture = makeTestFixture();
2021-12-22 15:06:28 +01:00
const bdom = renderToBdom(template, context, node);
2020-11-26 16:45:25 +01:00
mount(bdom, fixture);
return fixture.innerHTML;
}
export class TestContext extends TemplateSet {
renderToString(name: string, context: any = {}): string {
const renderFn = this.getTemplate(name);
const bdom = renderFn(context, {});
const fixture = makeTestFixture();
mount(bdom, fixture);
return fixture.innerHTML;
}
}
2020-11-26 16:45:25 +01:00
export function snapshotEverything() {
2021-12-20 11:19:59 +01:00
if (shouldSnapshot) {
// this function has already been called
return;
}
const globalTemplateNames = new Set(Object.keys(globalTemplates));
shouldSnapshot = true;
2020-11-26 16:45:25 +01:00
beforeEach(() => {
2021-12-20 11:19:59 +01:00
snapshottedTemplates.clear();
2020-11-26 16:45:25 +01:00
});
2021-12-20 11:19:59 +01:00
const originalCompileTemplate = TemplateSet.prototype._compileTemplate;
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) {
2021-12-20 11:19:59 +01:00
const fn = originalCompileTemplate.call(this, "", template);
if (!globalTemplateNames.has(name)) {
expect(fn.toString()).toMatchSnapshot();
2020-11-26 16:45:25 +01:00
}
2021-12-20 11:19:59 +01:00
return fn;
};
2020-11-26 16:45:25 +01:00
}
const steps: string[] = [];
export function logStep(step: string) {
steps.push(step);
}
export function useLogLifecycle(key?: string, skipAsyncHooks: boolean = false) {
2020-11-26 16:45:25 +01:00
const component = useComponent();
let name = component.constructor.name;
if (key) {
name = `${name} (${key})`;
}
logStep(`${name}:setup`);
2020-11-26 16:45:25 +01:00
expect(name + ": " + status(component)).toBe(name + ": " + "new");
if (!skipAsyncHooks) {
onWillStart(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "new");
logStep(`${name}:willStart`);
});
}
2020-11-26 16:45:25 +01:00
onMounted(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
logStep(`${name}:mounted`);
2020-11-26 16:45:25 +01:00
});
if (!skipAsyncHooks) {
onWillUpdateProps(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
logStep(`${name}:willUpdateProps`);
});
}
2020-11-26 16:45:25 +01:00
onWillRender(() => {
logStep(`${name}:willRender`);
});
onRendered(() => {
logStep(`${name}:rendered`);
2020-11-26 16:45:25 +01:00
});
onWillPatch(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
logStep(`${name}:willPatch`);
2020-11-26 16:45:25 +01:00
});
onPatched(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
logStep(`${name}:patched`);
2020-11-26 16:45:25 +01:00
});
onWillUnmount(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
logStep(`${name}:willUnmount`);
2020-11-26 16:45:25 +01:00
});
onWillDestroy(() => {
expect(status(component)).not.toBe("destroyed");
logStep(`${name}:willDestroy`);
2020-11-26 16:45:25 +01:00
});
2019-05-10 17:46:32 +02:00
}
2020-11-26 16:45:25 +01:00
export function children(w: Component): Component[] {
const childrenMap = w.__owl__.children;
return Object.keys(childrenMap).map((id) => childrenMap[id].component);
2019-05-10 17:46:32 +02:00
}
2020-11-26 16:45:25 +01:00
export function isDirectChildOf(child: Component, parent: Component): boolean {
return children(parent).includes(child);
}
2021-10-15 10:58:29 +02:00
2021-12-20 13:54:52 +01:00
export function elem(component: Component): any {
return component.__owl__.firstNode();
}
2021-10-15 10:58:29 +02:00
export async function editInput(input: HTMLInputElement | HTMLTextAreaElement, value: string) {
input.value = value;
input.dispatchEvent(new Event("input"));
input.dispatchEvent(new Event("change"));
return nextTick();
}
afterEach(() => {
if (steps.length) {
steps.splice(0);
throw new Error("Remaining steps! Should be checked by a .toBeLogged() assertion!");
}
});
expect.extend({
toBeLogged(expected) {
const options = {
comment: "steps equality",
isNot: this.isNot,
promise: this.promise,
};
const currentSteps = steps.splice(0);
const pass = this.equals(currentSteps, expected);
const message = pass
? () =>
this.utils.matcherHint("toEqual", undefined, undefined, options) +
"\n\n" +
`Expected: not ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(currentSteps)}`
: () => {
const diffString = diff(expected, currentSteps, {
expand: this.expand,
});
return (
this.utils.matcherHint("toBe", undefined, undefined, options) +
"\n\n" +
(diffString && diffString.includes("- Expect")
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(currentSteps)}`)
);
};
return { actual: currentSteps, message, pass };
},
});
declare global {
namespace jest {
interface Matchers<R> {
toBeLogged(): R;
}
}
}