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";
|
2022-05-26 22:42:25 +02:00
|
|
|
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;
|
2022-03-14 09:37:21 +01:00
|
|
|
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
|
|
|
|
2019-05-21 08:32:32 +02:00
|
|
|
export function trim(str: string): string {
|
|
|
|
|
return str.replace(/\s/g, "");
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-26 16:45:25 +01:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Helpers
|
|
|
|
|
// -----------------------------------------------------------------------------
|
2019-05-21 08:32:32 +02:00
|
|
|
|
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;
|
2019-05-21 08:32:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2022-01-27 09:42:33 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2021-11-27 10:08:10 +01:00
|
|
|
const steps: string[] = [];
|
|
|
|
|
|
|
|
|
|
export function logStep(step: string) {
|
|
|
|
|
steps.push(step);
|
|
|
|
|
}
|
2022-04-15 11:41:41 +02:00
|
|
|
export function useLogLifecycle(key?: string, skipAsyncHooks: boolean = false) {
|
2020-11-26 16:45:25 +01:00
|
|
|
const component = useComponent();
|
2022-01-11 15:57:41 +01:00
|
|
|
let name = component.constructor.name;
|
|
|
|
|
if (key) {
|
|
|
|
|
name = `${name} (${key})`;
|
|
|
|
|
}
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:setup`);
|
2020-11-26 16:45:25 +01:00
|
|
|
expect(name + ": " + status(component)).toBe(name + ": " + "new");
|
|
|
|
|
|
2022-04-15 11:41:41 +02:00
|
|
|
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");
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:mounted`);
|
2020-11-26 16:45:25 +01:00
|
|
|
});
|
|
|
|
|
|
2022-04-15 11:41:41 +02:00
|
|
|
if (!skipAsyncHooks) {
|
|
|
|
|
onWillUpdateProps(() => {
|
|
|
|
|
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
|
|
|
|
|
logStep(`${name}:willUpdateProps`);
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-11-26 16:45:25 +01:00
|
|
|
|
2021-11-19 09:43:06 +01:00
|
|
|
onWillRender(() => {
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:willRender`);
|
2021-11-19 09:43:06 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onRendered(() => {
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:rendered`);
|
2020-11-26 16:45:25 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onWillPatch(() => {
|
|
|
|
|
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:willPatch`);
|
2020-11-26 16:45:25 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onPatched(() => {
|
|
|
|
|
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:patched`);
|
2020-11-26 16:45:25 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onWillUnmount(() => {
|
|
|
|
|
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
|
2021-11-27 10:08:10 +01:00
|
|
|
logStep(`${name}:willUnmount`);
|
2020-11-26 16:45:25 +01:00
|
|
|
});
|
|
|
|
|
|
2021-11-29 14:06:53 +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
|
|
|
}
|
2019-06-13 16:27:12 +02:00
|
|
|
|
2020-11-26 16:45:25 +01:00
|
|
|
export function isDirectChildOf(child: Component, parent: Component): boolean {
|
|
|
|
|
return children(parent).includes(child);
|
2019-06-13 16:27:12 +02:00
|
|
|
}
|
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();
|
|
|
|
|
}
|
2021-11-27 10:08:10 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|