mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b51756f356 | |||
| cfdf7caa50 | |||
| a5a6a592c1 | |||
| d0d7482b0f | |||
| 8fe4c0c76e | |||
| c1afaeb92a | |||
| 3883cec079 | |||
| 9cb74d619b | |||
| a93f015795 | |||
| 02a187d80b | |||
| d3b0d1971e | |||
| b90aa0e23a | |||
| 163366997c |
@@ -770,10 +770,11 @@ For reference, here is the final code:
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>OWL Todo App</title>
|
<title>OWL Todo App</title>
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<script src="owl.js"></script>
|
<script src="owl.js"></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</head>
|
</body>
|
||||||
<body></body>
|
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-beta-15",
|
"version": "2.0.0-beta-20",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"module": "dist/owl.es.js",
|
"module": "dist/owl.es.js",
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export class CodeGenerator {
|
|||||||
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
|
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
|
||||||
ast: AST;
|
ast: AST;
|
||||||
staticDefs: { id: string; expr: string }[] = [];
|
staticDefs: { id: string; expr: string }[] = [];
|
||||||
|
slotNames: Set<String> = new Set();
|
||||||
helpers: Set<string> = new Set();
|
helpers: Set<string> = new Set();
|
||||||
|
|
||||||
constructor(ast: AST, options: CodeGenOptions) {
|
constructor(ast: AST, options: CodeGenOptions) {
|
||||||
@@ -584,8 +585,12 @@ export class CodeGenerator {
|
|||||||
expr = compileExpr(ast.attrs[key]);
|
expr = compileExpr(ast.attrs[key]);
|
||||||
if (attrName && isProp(ast.tag, attrName)) {
|
if (attrName && isProp(ast.tag, attrName)) {
|
||||||
// we force a new string or new boolean to bypass the equality check in blockdom when patching same value
|
// we force a new string or new boolean to bypass the equality check in blockdom when patching same value
|
||||||
const C = attrName === "value" ? "String" : "Boolean";
|
if (attrName === "value") {
|
||||||
expr = `new ${C}(${expr})`;
|
// When the expression is falsy, fall back to an empty string
|
||||||
|
expr = `new String((${expr}) || "")`;
|
||||||
|
} else {
|
||||||
|
expr = `new Boolean(${expr})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const idx = block!.insertData(expr, "attr");
|
const idx = block!.insertData(expr, "attr");
|
||||||
if (key === "t-att") {
|
if (key === "t-att") {
|
||||||
@@ -1241,28 +1246,37 @@ export class CodeGenerator {
|
|||||||
let blockString: string;
|
let blockString: string;
|
||||||
let slotName;
|
let slotName;
|
||||||
let dynamic = false;
|
let dynamic = false;
|
||||||
|
let isMultiple = false;
|
||||||
if (ast.name.match(INTERP_REGEXP)) {
|
if (ast.name.match(INTERP_REGEXP)) {
|
||||||
dynamic = true;
|
dynamic = true;
|
||||||
|
isMultiple = true;
|
||||||
slotName = interpolate(ast.name);
|
slotName = interpolate(ast.name);
|
||||||
} else {
|
} else {
|
||||||
slotName = "'" + ast.name + "'";
|
slotName = "'" + ast.name + "'";
|
||||||
|
isMultiple = isMultiple || this.slotNames.has(ast.name);
|
||||||
|
this.slotNames.add(ast.name);
|
||||||
}
|
}
|
||||||
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
|
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
|
||||||
if (ast.attrs) {
|
if (ast.attrs) {
|
||||||
delete ast.attrs["t-props"];
|
delete ast.attrs["t-props"];
|
||||||
}
|
}
|
||||||
|
let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
|
||||||
|
if (isMultiple) {
|
||||||
|
key = `${key} + \`${this.generateComponentKey()}\``;
|
||||||
|
}
|
||||||
|
|
||||||
const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
|
const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
|
||||||
const scope = this.getPropString(props, dynProps);
|
const scope = this.getPropString(props, dynProps);
|
||||||
if (ast.defaultContent) {
|
if (ast.defaultContent) {
|
||||||
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
|
||||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
||||||
} else {
|
} else {
|
||||||
if (dynamic) {
|
if (dynamic) {
|
||||||
let name = generateId("slot");
|
let name = generateId("slot");
|
||||||
this.define(name, slotName);
|
this.define(name, slotName);
|
||||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;
|
||||||
} else {
|
} else {
|
||||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// event handling
|
// event handling
|
||||||
|
|||||||
+6
-3
@@ -6,6 +6,7 @@ import { Scheduler } from "./scheduler";
|
|||||||
import { validateProps } from "./template_helpers";
|
import { validateProps } from "./template_helpers";
|
||||||
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||||
import { validateTarget } from "./utils";
|
import { validateTarget } from "./utils";
|
||||||
|
import { handleError } from "./error_handling";
|
||||||
|
|
||||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||||
|
|
||||||
@@ -94,9 +95,7 @@ export class App<
|
|||||||
nodeErrorHandlers.set(node, handlers);
|
nodeErrorHandlers.set(node, handlers);
|
||||||
}
|
}
|
||||||
handlers.unshift((e) => {
|
handlers.unshift((e) => {
|
||||||
if (isResolved) {
|
if (!isResolved) {
|
||||||
console.error(e);
|
|
||||||
} else {
|
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
@@ -169,6 +168,10 @@ export class App<
|
|||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleError(...args: Parameters<typeof handleError>) {
|
||||||
|
return handleError(...args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mount<
|
export async function mount<
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listener(ev: Event) {
|
function listener(ev: Event) {
|
||||||
const currentTarget = ev.currentTarget;
|
const currentTarget = ev.currentTarget as HTMLElement;
|
||||||
if (!currentTarget || !document.contains(currentTarget as HTMLElement)) return;
|
if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) return;
|
||||||
const data = (currentTarget as any)[eventKey];
|
const data = (currentTarget as any)[eventKey];
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
config.mainEventHandler(data, ev, currentTarget);
|
config.mainEventHandler(data, ev, currentTarget);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { App, Env } from "./app";
|
import type { App, Env } from "./app";
|
||||||
import { BDom, VNode } from "./blockdom";
|
import { BDom, VNode } from "./blockdom";
|
||||||
import { Component, ComponentConstructor, Props } from "./component";
|
import { Component, ComponentConstructor, Props } from "./component";
|
||||||
import { fibersInError, handleError, OwlError } from "./error_handling";
|
import { fibersInError, OwlError } from "./error_handling";
|
||||||
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
||||||
import {
|
import {
|
||||||
clearReactivesForCallback,
|
clearReactivesForCallback,
|
||||||
@@ -141,7 +141,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
try {
|
try {
|
||||||
await Promise.all(this.willStart.map((f) => f.call(component)));
|
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ node: this, error: e });
|
this.app.handleError({ node: this, error: e });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
||||||
@@ -219,7 +219,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
cb.call(component);
|
cb.call(component);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ error: e, node: this });
|
this.app.handleError({ error: e, node: this });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.status = STATUS.DESTROYED;
|
this.status = STATUS.DESTROYED;
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ export function handleError(params: ErrorParams) {
|
|||||||
let { error } = params;
|
let { error } = params;
|
||||||
// Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)
|
// Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode)
|
||||||
if (!(error instanceof OwlError)) {
|
if (!(error instanceof OwlError)) {
|
||||||
error = Object.assign(new OwlError("An error occured in the owl lifecycle"), { cause: error });
|
error = Object.assign(
|
||||||
|
new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`),
|
||||||
|
{ cause: error }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const node = "node" in params ? params.node : params.fiber.node;
|
const node = "node" in params ? params.node : params.fiber.node;
|
||||||
const fiber = "fiber" in params ? params.fiber : node.fiber!;
|
const fiber = "fiber" in params ? params.fiber : node.fiber!;
|
||||||
@@ -68,5 +71,6 @@ export function handleError(params: ErrorParams) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BDom, mount } from "./blockdom";
|
import { BDom, mount } from "./blockdom";
|
||||||
import type { ComponentNode } from "./component_node";
|
import type { ComponentNode } from "./component_node";
|
||||||
import { fibersInError, handleError, OwlError } from "./error_handling";
|
import { fibersInError, OwlError } from "./error_handling";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
|
|
||||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||||
@@ -130,7 +130,7 @@ export class Fiber {
|
|||||||
(this.bdom as any) = true;
|
(this.bdom as any) = true;
|
||||||
this.bdom = node.renderFn();
|
this.bdom = node.renderFn();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ node, error: e });
|
node.app.handleError({ node, error: e });
|
||||||
}
|
}
|
||||||
root.setCounter(root.counter - 1);
|
root.setCounter(root.counter - 1);
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ export class RootFiber extends Fiber {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
handleError({ fiber: current || this, error: e });
|
node.app.handleError({ fiber: current || this, error: e });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ export class MountFiber extends RootFiber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ fiber: current as Fiber, error: e });
|
this.node.app.handleError({ fiber: current as Fiber, error: e });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const blockDom = {
|
|||||||
export { App, mount } from "./app";
|
export { App, mount } from "./app";
|
||||||
export { xml } from "./template_set";
|
export { xml } from "./template_set";
|
||||||
export { Component } from "./component";
|
export { Component } from "./component";
|
||||||
|
export type { ComponentConstructor } from "./component";
|
||||||
export { useComponent, useState } from "./component_node";
|
export { useComponent, useState } from "./component_node";
|
||||||
export { status } from "./status";
|
export { status } from "./status";
|
||||||
export { reactive, markRaw, toRaw } from "./reactivity";
|
export { reactive, markRaw, toRaw } from "./reactivity";
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
|||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
return (...args: any[]) => {
|
return (...args: any[]) => {
|
||||||
const onError = (cause: any) => {
|
const onError = (cause: any) => {
|
||||||
if (cause instanceof Error) {
|
|
||||||
error.cause = cause;
|
error.cause = cause;
|
||||||
|
if (cause instanceof Error) {
|
||||||
error.message += `"${cause.message}"`;
|
error.message += `"${cause.message}"`;
|
||||||
|
} else {
|
||||||
|
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,12 +28,18 @@ export function batched(callback: Callback): Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateTarget(target: HTMLElement) {
|
export function validateTarget(target: HTMLElement) {
|
||||||
if (!(target instanceof HTMLElement)) {
|
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes
|
||||||
throw new OwlError("Cannot mount component: the target is not a valid DOM element");
|
const document = target && target.ownerDocument;
|
||||||
}
|
if (document) {
|
||||||
|
const HTMLElement = document.defaultView!.HTMLElement;
|
||||||
|
if (target instanceof HTMLElement) {
|
||||||
if (!document.body.contains(target)) {
|
if (!document.body.contains(target)) {
|
||||||
throw new OwlError("Cannot mount a component on a detached dom node");
|
throw new OwlError("Cannot mount a component on a detached dom node");
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new OwlError("Cannot mount component: the target is not a valid DOM element");
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventBus extends EventTarget {
|
export class EventBus extends EventTarget {
|
||||||
|
|||||||
@@ -29,6 +29,19 @@ exports[`app can configure an app with props 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app can mount app in an iframe 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div class=\\"my-div\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app destroy remove the widget from the DOM 1`] = `
|
exports[`app destroy remove the widget from the DOM 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -76,4 +76,22 @@ describe("app", () => {
|
|||||||
"Component 'Root' does not have a static props description"
|
"Component 'Root' does not have a static props description"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can mount app in an iframe", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`<div class="my-div"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iframe = document.createElement("iframe");
|
||||||
|
fixture.appendChild(iframe);
|
||||||
|
const app = new App(SomeComponent);
|
||||||
|
const iframeDoc = iframe.contentDocument!;
|
||||||
|
const comp = await app.mount(iframeDoc.body);
|
||||||
|
const div = iframeDoc.querySelector(".my-div");
|
||||||
|
expect(div).not.toBe(null);
|
||||||
|
expect(iframeDoc.contains(div)).toBe(true);
|
||||||
|
app.destroy();
|
||||||
|
expect(iframeDoc.contains(div)).toBe(false);
|
||||||
|
expect(status(comp)).toBe("destroyed");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -708,6 +708,20 @@ exports[`attributes updating classes (with obj notation) 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`attributes updating property with falsy value 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let attr1 = new String((ctx['v']) || \\"\\");
|
||||||
|
return block1([attr1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`attributes various escapes 1`] = `
|
exports[`attributes various escapes 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -773,7 +787,7 @@ exports[`special cases for some specific html attributes/properties input with t
|
|||||||
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
|
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let attr1 = new String(ctx['v']);
|
let attr1 = new String((ctx['v']) || \\"\\");
|
||||||
return block1([attr1]);
|
return block1([attr1]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -787,7 +801,7 @@ exports[`special cases for some specific html attributes/properties input with t
|
|||||||
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
|
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let attr1 = new String(ctx['v']);
|
let attr1 = new String((ctx['v']) || \\"\\");
|
||||||
return block1([attr1]);
|
return block1([attr1]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -815,7 +829,7 @@ exports[`special cases for some specific html attributes/properties select with
|
|||||||
let block1 = createBlock(\`<select block-attribute-0=\\"value\\"><option value=\\"potato\\">Potato</option><option value=\\"tomato\\">Tomato</option><option value=\\"onion\\">Onion</option></select>\`);
|
let block1 = createBlock(\`<select block-attribute-0=\\"value\\"><option value=\\"potato\\">Potato</option><option value=\\"tomato\\">Tomato</option><option value=\\"onion\\">Onion</option></select>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let attr1 = new String(ctx['value']);
|
let attr1 = new String((ctx['value']) || \\"\\");
|
||||||
return block1([attr1]);
|
return block1([attr1]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -829,7 +843,7 @@ exports[`special cases for some specific html attributes/properties textarea wit
|
|||||||
let block1 = createBlock(\`<textarea block-attribute-0=\\"value\\"/>\`);
|
let block1 = createBlock(\`<textarea block-attribute-0=\\"value\\"/>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let attr1 = new String(ctx['v']);
|
let attr1 = new String((ctx['v']) || \\"\\");
|
||||||
return block1([attr1]);
|
return block1([attr1]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ exports[`misc other complex template 1`] = `
|
|||||||
const b15 = list(c_block15);
|
const b15 = list(c_block15);
|
||||||
b14 = block14([], [b15]);
|
b14 = block14([], [b15]);
|
||||||
}
|
}
|
||||||
let attr8 = new String(ctx['search'].value);
|
let attr8 = new String((ctx['search'].value) || \\"\\");
|
||||||
let hdlr4 = [ctx['updateFilter'], ctx];
|
let hdlr4 = [ctx['updateFilter'], ctx];
|
||||||
let hdlr5 = [ctx['updateFilter'], ctx];
|
let hdlr5 = [ctx['updateFilter'], ctx];
|
||||||
let hdlr6 = [ctx['clearSearch'], ctx];
|
let hdlr6 = [ctx['clearSearch'], ctx];
|
||||||
|
|||||||
@@ -329,6 +329,35 @@ describe("attributes", () => {
|
|||||||
expect(fixture.innerHTML).toBe('<div value=""></div>');
|
expect(fixture.innerHTML).toBe('<div value=""></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("updating property with falsy value", async () => {
|
||||||
|
// render input with initial value
|
||||||
|
const template = `<input t-att-value="v"></input>`;
|
||||||
|
const bnode1 = renderToBdom(template, { v: false });
|
||||||
|
const fixture = makeTestFixture();
|
||||||
|
mount(bnode1, fixture);
|
||||||
|
|
||||||
|
const input = fixture.querySelector("input")!;
|
||||||
|
expect(input.value).toBe("");
|
||||||
|
|
||||||
|
patch(bnode1, renderToBdom(template, { v: "owl" }));
|
||||||
|
expect(input.value).toBe("owl");
|
||||||
|
|
||||||
|
patch(bnode1, renderToBdom(template, { v: false }));
|
||||||
|
expect(input.value).toBe("");
|
||||||
|
|
||||||
|
patch(bnode1, renderToBdom(template, { v: "owl" }));
|
||||||
|
expect(input.value).toBe("owl");
|
||||||
|
|
||||||
|
patch(bnode1, renderToBdom(template, { v: undefined }));
|
||||||
|
expect(input.value).toBe("");
|
||||||
|
|
||||||
|
patch(bnode1, renderToBdom(template, { v: "owl" }));
|
||||||
|
expect(input.value).toBe("owl");
|
||||||
|
|
||||||
|
patch(bnode1, renderToBdom(template, { v: null }));
|
||||||
|
expect(input.value).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
test("changing a class with t-att-class", () => {
|
test("changing a class with t-att-class", () => {
|
||||||
// render input with initial value
|
// render input with initial value
|
||||||
const template = `<div t-att-class="v"/>`;
|
const template = `<div t-att-class="v"/>`;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ exports[`basics simple catchError 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`can catch errors Errors in owl lifecycle are wrapped in dev mode: async hook 1`] = `
|
exports[`can catch errors Errors have the right cause 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
@@ -112,7 +112,7 @@ exports[`can catch errors Errors in owl lifecycle are wrapped in dev mode: async
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`can catch errors Errors in owl lifecycle are wrapped in dev mode: sync hook 1`] = `
|
exports[`can catch errors Errors in owl lifecycle are wrapped in dev mode: async hook 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
@@ -145,6 +145,28 @@ exports[`can catch errors Errors in owl lifecycle are wrapped outside dev mode:
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors Thrown values that are not errors are wrapped in dev mode 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['state'].value);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors Thrown values that are not errors are wrapped outside dev mode 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['state'].value);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`can catch errors an error in onWillDestroy 1`] = `
|
exports[`can catch errors an error in onWillDestroy 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -58,6 +58,20 @@ exports[`event handling handler receive the event as argument 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`event handling handler works when app is mounted in an iframe 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<span block-handler-0=\\"click\\">click me</span>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let hdlr1 = [ctx['inc'], ctx];
|
||||||
|
return block1([hdlr1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`event handling input blur event is not called if component is destroyed 1`] = `
|
exports[`event handling input blur event is not called if component is destroyed 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -604,6 +604,64 @@ exports[`slots default slot work with text nodes 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`slots dynamic slot in multiple locations 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
|
||||||
|
const comp2 = app.createComponent(\`Slotter\`, true, true, false, false);
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`hello \`);
|
||||||
|
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return comp2({location: ctx['state'].location,slots: markRaw({'coffee': {__render: slot1, __ctx: ctx1}})}, key + \`__2\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots dynamic slot in multiple locations 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { callSlot } = helpers;
|
||||||
|
|
||||||
|
let block2 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b4;
|
||||||
|
if (ctx['props'].location===1) {
|
||||||
|
const slot1 = ('coffee');
|
||||||
|
const b3 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {}));
|
||||||
|
b2 = block2([], [b3]);
|
||||||
|
}
|
||||||
|
if (ctx['props'].location===2) {
|
||||||
|
const slot2 = ('coffee');
|
||||||
|
b4 = toggler(slot2, callSlot(ctx, node, key + \`__2\`, slot2, true, {}));
|
||||||
|
}
|
||||||
|
return multi([b2, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots dynamic slot in multiple locations 3`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>child</div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`slots dynamic t-slot call 1`] = `
|
exports[`slots dynamic t-slot call 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -645,7 +703,7 @@ exports[`slots dynamic t-slot call 2`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let hdlr1 = [ctx['toggle'], ctx];
|
let hdlr1 = [ctx['toggle'], ctx];
|
||||||
const slot1 = (ctx['current'].slot);
|
const slot1 = (ctx['current'].slot);
|
||||||
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {}));
|
const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {}));
|
||||||
return block1([hdlr1], [b2]);
|
return block1([hdlr1], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -695,7 +753,7 @@ exports[`slots dynamic t-slot call with default 2`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let hdlr1 = [ctx['toggle'], ctx];
|
let hdlr1 = [ctx['toggle'], ctx];
|
||||||
const b3 = callSlot(ctx, node, key, (ctx['current'].slot), true, {}, defaultContent1);
|
const b3 = callSlot(ctx, node, key + \`__1\`, (ctx['current'].slot), true, {}, defaultContent1);
|
||||||
return block1([hdlr1], [b3]);
|
return block1([hdlr1], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -726,7 +784,7 @@ exports[`slots fun: two calls to the same slot 2`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const b2 = callSlot(ctx, node, key, 'default', false, {});
|
const b2 = callSlot(ctx, node, key, 'default', false, {});
|
||||||
const b3 = callSlot(ctx, node, key, 'default', false, {});
|
const b3 = callSlot(ctx, node, key + \`__1\`, 'default', false, {});
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1527,7 +1585,7 @@ exports[`slots simple dynamic slot with slot scope 2`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const slot1 = ('slotName');
|
const slot1 = ('slotName');
|
||||||
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {bool: ctx['state'].bool}));
|
const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {bool: ctx['state'].bool}));
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1854,7 +1912,7 @@ exports[`slots slot content has different key from other content -- dynamic slot
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const b2 = comp1({parent: 'SlotDisplay'}, key + \`__1\`, node, this, null);
|
const b2 = comp1({parent: 'SlotDisplay'}, key + \`__1\`, node, this, null);
|
||||||
const slot1 = (ctx['slotName']);
|
const slot1 = (ctx['slotName']);
|
||||||
const b3 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {}));
|
const b3 = toggler(slot1, callSlot(ctx, node, key + \`__2\`, slot1, true, {}));
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1995,6 +2053,118 @@ exports[`slots slot content is bound to caller 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`slots slot in multiple locations 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
|
||||||
|
const comp2 = app.createComponent(\`Slotter\`, true, true, false, false);
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\` hello \`);
|
||||||
|
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return comp2({location: ctx['state'].location,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots slot in multiple locations 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { callSlot } = helpers;
|
||||||
|
|
||||||
|
let block2 = createBlock(\`<p><block-child-0/></p>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b4;
|
||||||
|
if (ctx['props'].location===1) {
|
||||||
|
const b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||||
|
b2 = block2([], [b3]);
|
||||||
|
}
|
||||||
|
if (ctx['props'].location===2) {
|
||||||
|
b4 = callSlot(ctx, node, key + \`__1\`, 'default', false, {});
|
||||||
|
}
|
||||||
|
return multi([b2, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots slot in multiple locations 3`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>child</div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots slot in t-foreach locations 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
|
||||||
|
const comp2 = app.createComponent(\`Slotter\`, true, true, false, false);
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\` hello \`);
|
||||||
|
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return comp2({list: ctx['state'].list,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, this, null);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots slot in t-foreach locations 2`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, callSlot, withKey } = helpers;
|
||||||
|
|
||||||
|
let block2 = createBlock(\`<p><block-text-0/><block-child-0/></p>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['props'].list);;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`elem\`] = v_block1[i1];
|
||||||
|
ctx[\`elem_index\`] = i1;
|
||||||
|
const key1 = ctx['elem_index'];
|
||||||
|
let txt1 = ctx['elem'];
|
||||||
|
const b3 = callSlot(ctx, node, key1, 'default', false, {});
|
||||||
|
c_block1[i1] = withKey(block2([txt1], [b3]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots slot in t-foreach locations 3`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>child</div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`slots slot preserves properly parented relationship 1`] = `
|
exports[`slots slot preserves properly parented relationship 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { App, Component, mount, status, toRaw, useState, xml } from "../../src";
|
import { App, Component, mount, status, toRaw, useState, xml } from "../../src";
|
||||||
import { elem, makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
|
import {
|
||||||
|
elem,
|
||||||
|
makeTestFixture,
|
||||||
|
nextAppError,
|
||||||
|
nextTick,
|
||||||
|
snapshotEverything,
|
||||||
|
useLogLifecycle,
|
||||||
|
} from "../helpers";
|
||||||
import { markup } from "../../src/runtime/utils";
|
import { markup } from "../../src/runtime/utils";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
@@ -208,14 +215,14 @@ describe("basics", () => {
|
|||||||
static template = xml`<div/>`;
|
static template = xml`<div/>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
const prom = mount(Test, fixture);
|
const app = new App(Test);
|
||||||
|
const prom = app.mount(fixture);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
fixture.remove();
|
fixture.remove();
|
||||||
try {
|
prom.catch((e: Error) => (error = e));
|
||||||
await prom;
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
} catch (e) {
|
"Cannot mount a component on a detached dom node"
|
||||||
error = e as Error;
|
);
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
||||||
expect(console.warn).toBeCalledTimes(1);
|
expect(console.warn).toBeCalledTimes(1);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, mount, onWillDestroy } from "../../src";
|
import { App, Component, mount, onWillDestroy } from "../../src";
|
||||||
import {
|
import {
|
||||||
onError,
|
onError,
|
||||||
onMounted,
|
onMounted,
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
nextMicroTick,
|
nextMicroTick,
|
||||||
snapshotEverything,
|
snapshotEverything,
|
||||||
useLogLifecycle,
|
useLogLifecycle,
|
||||||
|
nextAppError,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { OwlError } from "../../src/runtime/error_handling";
|
import { OwlError } from "../../src/runtime/error_handling";
|
||||||
|
|
||||||
@@ -59,9 +60,10 @@ describe("basics", () => {
|
|||||||
parent.state.flag = true;
|
parent.state.flag = true;
|
||||||
|
|
||||||
parent.render();
|
parent.render();
|
||||||
await nextTick();
|
await expect(nextAppError(parent.__owl__.app)).resolves.toThrow(
|
||||||
|
"An error occured in the owl lifecycle"
|
||||||
|
);
|
||||||
expect(fixture.innerHTML).toBe("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
expect(mockConsoleError).toBeCalledTimes(1);
|
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,12 +73,13 @@ describe("basics", () => {
|
|||||||
static template = xml`<SomeMispelledComponent />`;
|
static template = xml`<SomeMispelledComponent />`;
|
||||||
static components = { SomeComponent };
|
static components = { SomeComponent };
|
||||||
}
|
}
|
||||||
|
const app = new App(Parent);
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(Parent, fixture);
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
} catch (e) {
|
'Cannot find the definition of component "SomeMispelledComponent"'
|
||||||
error = e as Error;
|
);
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
||||||
expect(console.error).toBeCalledTimes(0);
|
expect(console.error).toBeCalledTimes(0);
|
||||||
@@ -90,12 +93,13 @@ describe("basics", () => {
|
|||||||
static template = xml`<SomeMispelledComponent />`;
|
static template = xml`<SomeMispelledComponent />`;
|
||||||
static components = { SomeComponent };
|
static components = { SomeComponent };
|
||||||
}
|
}
|
||||||
|
const app = new App(Parent, { test: true });
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(Parent, fixture, { test: true });
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
} catch (e) {
|
'Cannot find the definition of component "SomeMispelledComponent"'
|
||||||
error = e as Error;
|
);
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
||||||
expect(console.error).toBeCalledTimes(0);
|
expect(console.error).toBeCalledTimes(0);
|
||||||
@@ -109,13 +113,13 @@ describe("basics", () => {
|
|||||||
static template = xml`<SomeComponent />`;
|
static template = xml`<SomeComponent />`;
|
||||||
static components = { SomeComponent: notAComponentConstructor };
|
static components = { SomeComponent: notAComponentConstructor };
|
||||||
}
|
}
|
||||||
|
const app = new App(Parent as typeof Component);
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
// @ts-expect-error
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
await mount(Parent, fixture);
|
'"SomeComponent" is not a Component. It must inherit from the Component class'
|
||||||
} catch (e) {
|
);
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
'"SomeComponent" is not a Component. It must inherit from the Component class'
|
'"SomeComponent" is not a Component. It must inherit from the Component class'
|
||||||
@@ -156,16 +160,15 @@ describe("basics", () => {
|
|||||||
describe("errors and promises", () => {
|
describe("errors and promises", () => {
|
||||||
test("a rendering error will reject the mount promise", async () => {
|
test("a rendering error will reject the mount promise", async () => {
|
||||||
// we do not catch error in willPatch anymore
|
// we do not catch error in willPatch anymore
|
||||||
class App extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
|
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const app = new App(Root);
|
||||||
let error: OwlError;
|
let error: OwlError;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(App, fixture);
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
} catch (e) {
|
await mountProm;
|
||||||
error = e as OwlError;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.cause).toBeDefined();
|
expect(error!.cause).toBeDefined();
|
||||||
const regexp =
|
const regexp =
|
||||||
@@ -176,7 +179,7 @@ describe("errors and promises", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("an error in mounted call will reject the mount promise", async () => {
|
test("an error in mounted call will reject the mount promise", async () => {
|
||||||
class App extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<div>abc</div>`;
|
static template = xml`<div>abc</div>`;
|
||||||
setup() {
|
setup() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -185,12 +188,11 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const app = new App(Root);
|
||||||
let error: OwlError;
|
let error: OwlError;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(App, fixture);
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
} catch (e) {
|
await mountProm;
|
||||||
error = e as OwlError;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.cause).toBeDefined();
|
expect(error!.cause).toBeDefined();
|
||||||
expect(error!.cause.message).toBe("boom");
|
expect(error!.cause.message).toBe("boom");
|
||||||
@@ -200,7 +202,7 @@ describe("errors and promises", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
|
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
|
||||||
class App extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<div>abc</div>`;
|
static template = xml`<div>abc</div>`;
|
||||||
setup() {
|
setup() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -209,14 +211,13 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let error: Error;
|
const app = new App(Root, { test: true });
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(App, fixture, { test: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.stack).toContain("App.setup");
|
expect(error!.stack).toContain("Root.setup");
|
||||||
expect(error!.stack).toContain("error_handling.test.ts");
|
expect(error!.stack).toContain("error_handling.test.ts");
|
||||||
expect(fixture.innerHTML).toBe("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
expect(mockConsoleError).toBeCalledTimes(0);
|
expect(mockConsoleError).toBeCalledTimes(0);
|
||||||
@@ -224,7 +225,7 @@ describe("errors and promises", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
|
test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
|
||||||
class App extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<div>abc</div>`;
|
static template = xml`<div>abc</div>`;
|
||||||
setup() {
|
setup() {
|
||||||
onWillRender(() => {
|
onWillRender(() => {
|
||||||
@@ -236,12 +237,11 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let error: Error;
|
const app = new App(Root, { test: true });
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(App, fixture, { test: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillRender");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
`The following error occurred in onWillRender: "boom in onWillRender"`
|
`The following error occurred in onWillRender: "boom in onWillRender"`
|
||||||
@@ -278,12 +278,11 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let error: any;
|
const app = new App(Root, { test: true });
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(Root, fixture, { test: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
|
||||||
error = e;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
`The following error occurred in onWillStart: "boom in onWillStart"`
|
`The following error occurred in onWillStart: "boom in onWillStart"`
|
||||||
@@ -342,17 +341,16 @@ describe("errors and promises", () => {
|
|||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
|
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
|
||||||
}
|
}
|
||||||
class App extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><Child/></div>`;
|
static template = xml`<div><Child/></div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const app = new App(Parent);
|
||||||
let error: OwlError;
|
let error: OwlError;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(App, fixture);
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
} catch (e) {
|
await mountProm;
|
||||||
error = e as OwlError;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.cause).toBeDefined();
|
expect(error!.cause).toBeDefined();
|
||||||
const regexp =
|
const regexp =
|
||||||
@@ -394,12 +392,11 @@ describe("errors and promises", () => {
|
|||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const app = new App(Parent);
|
||||||
let error: OwlError;
|
let error: OwlError;
|
||||||
try {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(Parent, fixture);
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
} catch (e) {
|
await mountProm;
|
||||||
error = e as OwlError;
|
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.cause).toBeDefined();
|
expect(error!.cause).toBeDefined();
|
||||||
const regexp =
|
const regexp =
|
||||||
@@ -425,13 +422,12 @@ describe("errors and promises", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const app = new App(Example, { test: true });
|
||||||
await mount(Example, fixture, { test: true });
|
let error: OwlError;
|
||||||
} catch (e) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
expect((e as Error).message).toBe(
|
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
|
||||||
`The following error occurred in onMounted: "Error in mounted"`
|
await mountProm;
|
||||||
);
|
expect(error!.message).toBe(`The following error occurred in onMounted: "Error in mounted"`);
|
||||||
}
|
|
||||||
// 1 additional error is logged because the destruction of the app causes
|
// 1 additional error is logged because the destruction of the app causes
|
||||||
// the onWillUnmount hook to be called and to fail
|
// the onWillUnmount hook to be called and to fail
|
||||||
expect(mockConsoleError).toBeCalledTimes(1);
|
expect(mockConsoleError).toBeCalledTimes(1);
|
||||||
@@ -448,9 +444,10 @@ describe("errors and promises", () => {
|
|||||||
|
|
||||||
root.state = "boom";
|
root.state = "boom";
|
||||||
root.render();
|
root.render();
|
||||||
await nextTick();
|
await expect(nextAppError(root.__owl__.app)).resolves.toThrow(
|
||||||
|
"error occured in the owl lifecycle"
|
||||||
|
);
|
||||||
expect(fixture.innerHTML).toBe("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
expect(mockConsoleError).toBeCalledTimes(1);
|
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -500,18 +497,17 @@ describe("can catch errors", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let e: Error;
|
const app = new App(Root, { test: true });
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(Root, fixture, { test: true });
|
const crashProm = expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
|
||||||
} catch (error) {
|
await app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
e = error as Error;
|
await crashProm;
|
||||||
}
|
expect(error!.message).toBe(
|
||||||
expect(e!.message).toBe(
|
|
||||||
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
|
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Errors in owl lifecycle are wrapped in dev mode: sync hook", async () => {
|
test("Errors have the right cause", async () => {
|
||||||
const err = new Error("test error");
|
const err = new Error("test error");
|
||||||
class Root extends Component {
|
class Root extends Component {
|
||||||
static template = xml`<t t-esc="state.value"/>`;
|
static template = xml`<t t-esc="state.value"/>`;
|
||||||
@@ -523,14 +519,13 @@ describe("can catch errors", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let e: OwlError;
|
const app = new App(Root, { test: true });
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(Root, fixture, { test: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (error) {
|
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
|
||||||
e = error as OwlError;
|
await mountProm;
|
||||||
}
|
expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`);
|
||||||
expect(e!.message).toBe(`The following error occurred in onMounted: "test error"`);
|
expect(error!.cause).toBe(err);
|
||||||
expect(e!.cause).toBe(err);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => {
|
test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => {
|
||||||
@@ -546,14 +541,13 @@ describe("can catch errors", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let e: OwlError;
|
const app = new App(Root, { test: true });
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(Root, fixture, { test: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (error) {
|
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
|
||||||
e = error as OwlError;
|
await mountProm;
|
||||||
}
|
expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`);
|
||||||
expect(e!.message).toBe(`The following error occurred in onWillStart: "test error"`);
|
expect(error!.cause).toBe(err);
|
||||||
expect(e!.cause).toBe(err);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => {
|
test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => {
|
||||||
@@ -568,14 +562,15 @@ describe("can catch errors", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let e: OwlError;
|
const app = new App(Root);
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(Root, fixture);
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (error) {
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
e = error as OwlError;
|
await mountProm;
|
||||||
}
|
expect(error!.message).toBe(
|
||||||
expect(e!.message).toBe("An error occured in the owl lifecycle");
|
`An error occured in the owl lifecycle (see this Error's "cause" property)`
|
||||||
expect(e!.cause).toBe(err);
|
);
|
||||||
|
expect(error!.cause).toBe(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => {
|
test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => {
|
||||||
@@ -591,14 +586,59 @@ describe("can catch errors", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let e: OwlError;
|
const app = new App(Root);
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(Root, fixture);
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (error) {
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
e = error as OwlError;
|
await mountProm;
|
||||||
|
expect(error!.message).toBe(
|
||||||
|
`An error occured in the owl lifecycle (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
|
expect(error!.cause).toBe(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Thrown values that are not errors are wrapped in dev mode", async () => {
|
||||||
|
class Root extends Component {
|
||||||
|
static template = xml`<t t-esc="state.value"/>`;
|
||||||
|
state = useState({ value: 1 });
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
throw "This is not an error";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
expect(e!.message).toBe("An error occured in the owl lifecycle");
|
}
|
||||||
expect(e!.cause).toBe(err);
|
const app = new App(Root, { test: true });
|
||||||
|
let error: OwlError;
|
||||||
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
|
await expect(nextAppError(app)).resolves.toThrow("not an Error was thrown in onMounted");
|
||||||
|
await mountProm;
|
||||||
|
expect(error!.message).toBe(
|
||||||
|
`Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
|
expect(error!.cause).toBe("This is not an error");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Thrown values that are not errors are wrapped outside dev mode", async () => {
|
||||||
|
class Root extends Component {
|
||||||
|
static template = xml`<t t-esc="state.value"/>`;
|
||||||
|
state = useState({ value: 1 });
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
throw "This is not an error";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const app = new App(Root);
|
||||||
|
let error: OwlError;
|
||||||
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
|
await mountProm;
|
||||||
|
expect(error!.message).toBe(
|
||||||
|
`An error occured in the owl lifecycle (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
|
expect(error!.cause).toBe("This is not an error");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
|
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
|
||||||
|
|||||||
@@ -171,4 +171,22 @@ describe("event handling", () => {
|
|||||||
// input is removed when component is destroyed => nothing should happen
|
// input is removed when component is destroyed => nothing should happen
|
||||||
expect([]).toBeLogged();
|
expect([]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("handler works when app is mounted in an iframe", async () => {
|
||||||
|
let clickCount = 0;
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<span t-on-click="inc">click me</span>`;
|
||||||
|
inc() {
|
||||||
|
clickCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const iframe = document.createElement("iframe");
|
||||||
|
fixture.appendChild(iframe);
|
||||||
|
const iframeDoc = iframe.contentDocument!;
|
||||||
|
await mount(Parent, iframeDoc.body);
|
||||||
|
expect(clickCount).toBe(0);
|
||||||
|
iframeDoc.querySelector("span")!.click();
|
||||||
|
expect(clickCount).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,8 +17,16 @@ import {
|
|||||||
useChildSubEnv,
|
useChildSubEnv,
|
||||||
useSubEnv,
|
useSubEnv,
|
||||||
xml,
|
xml,
|
||||||
|
OwlError,
|
||||||
} from "../../src/index";
|
} from "../../src/index";
|
||||||
import { elem, logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import {
|
||||||
|
elem,
|
||||||
|
logStep,
|
||||||
|
makeTestFixture,
|
||||||
|
nextAppError,
|
||||||
|
nextTick,
|
||||||
|
snapshotEverything,
|
||||||
|
} from "../helpers";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
|
|
||||||
@@ -650,11 +658,12 @@ describe("hooks", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
let error: OwlError;
|
||||||
await mount(MyComponent, fixture);
|
const app = new App(MyComponent);
|
||||||
} catch (e: any) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
expect(e.cause.message).toBe("Intentional error");
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
}
|
await mountProm;
|
||||||
|
expect(error!.cause.message).toBe("Intentional error");
|
||||||
// no console.error because the error has been caught in this test
|
// no console.error because the error has been caught in this test
|
||||||
expect(console.error).toHaveBeenCalledTimes(0);
|
expect(console.error).toHaveBeenCalledTimes(0);
|
||||||
console.error = originalconsoleError;
|
console.error = originalconsoleError;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
|
||||||
import { Component, onError, xml, mount } from "../../src";
|
import { Component, onError, xml, mount, OwlError } from "../../src";
|
||||||
import { DEV_MSG } from "../../src/runtime/app";
|
import { App, DEV_MSG } from "../../src/runtime/app";
|
||||||
import { validateProps } from "../../src/runtime/template_helpers";
|
import { validateProps } from "../../src/runtime/template_helpers";
|
||||||
import { Schema } from "../../src/runtime/validation";
|
import { Schema } from "../../src/runtime/validation";
|
||||||
|
|
||||||
@@ -48,13 +48,14 @@ describe("props validation", () => {
|
|||||||
static components = { SubComp };
|
static components = { SubComp };
|
||||||
static template = xml`<div><SubComp /></div>`;
|
static template = xml`<div><SubComp /></div>`;
|
||||||
}
|
}
|
||||||
let error: Error | undefined;
|
|
||||||
|
|
||||||
try {
|
const app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
let error: OwlError | undefined;
|
||||||
} catch (e) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
}
|
"Invalid props for component 'SubComp': 'message' is missing"
|
||||||
|
);
|
||||||
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
|
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
|
||||||
error = undefined;
|
error = undefined;
|
||||||
@@ -77,12 +78,13 @@ describe("props validation", () => {
|
|||||||
static template = xml`<div><SubComp /></div>`;
|
static template = xml`<div><SubComp /></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let error: Error;
|
const app = new App(Parent, { test: true });
|
||||||
try {
|
let error: OwlError | undefined;
|
||||||
await mount(Parent, fixture, { dev: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
error = e as Error;
|
"Invalid props for component 'SubComp': 'message' is missing"
|
||||||
}
|
);
|
||||||
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
|
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
|
||||||
});
|
});
|
||||||
@@ -126,14 +128,12 @@ describe("props validation", () => {
|
|||||||
};
|
};
|
||||||
(Parent as any).components = { SubComp };
|
(Parent as any).components = { SubComp };
|
||||||
|
|
||||||
let error: Error | undefined;
|
|
||||||
props = {};
|
props = {};
|
||||||
|
let app = new App(Parent, { test: true });
|
||||||
try {
|
let error: OwlError | undefined;
|
||||||
await mount(Parent, fixture, { dev: true });
|
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
|
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
|
||||||
@@ -147,11 +147,10 @@ describe("props validation", () => {
|
|||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
props = { p: test.ko };
|
props = { p: test.ko };
|
||||||
try {
|
app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
|
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
|
||||||
@@ -181,13 +180,12 @@ describe("props validation", () => {
|
|||||||
static template = xml`<div>hey</div>`;
|
static template = xml`<div>hey</div>`;
|
||||||
};
|
};
|
||||||
(Parent as any).components = { SubComp };
|
(Parent as any).components = { SubComp };
|
||||||
let error: Error | undefined;
|
|
||||||
props = {};
|
props = {};
|
||||||
try {
|
let app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
let error: OwlError | undefined;
|
||||||
} catch (e) {
|
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
|
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
|
||||||
@@ -201,11 +199,10 @@ describe("props validation", () => {
|
|||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
props = { p: test.ko };
|
props = { p: test.ko };
|
||||||
try {
|
app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
|
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
|
||||||
@@ -227,26 +224,25 @@ describe("props validation", () => {
|
|||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
let props: { p?: any };
|
let props: { p?: any };
|
||||||
try {
|
|
||||||
props = { p: "string" };
|
props = { p: "string" };
|
||||||
|
try {
|
||||||
await mount(Parent, fixture, { dev: true });
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: true };
|
props = { p: true };
|
||||||
|
try {
|
||||||
await mount(Parent, fixture, { dev: true });
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: 1 };
|
props = { p: 1 };
|
||||||
await mount(Parent, fixture, { dev: true });
|
const app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'SubComp': 'p' is not a string or boolean"
|
"Invalid props for component 'SubComp': 'p' is not a string or boolean"
|
||||||
@@ -267,26 +263,25 @@ describe("props validation", () => {
|
|||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
let props: { p?: any };
|
let props: { p?: any };
|
||||||
try {
|
|
||||||
props = { p: "key" };
|
props = { p: "key" };
|
||||||
|
try {
|
||||||
await mount(Parent, fixture, { dev: true });
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = {};
|
props = {};
|
||||||
|
try {
|
||||||
await mount(Parent, fixture, { dev: true });
|
await mount(Parent, fixture, { dev: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: 1 };
|
props = { p: 1 };
|
||||||
await mount(Parent, fixture, { dev: true });
|
const app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string");
|
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string");
|
||||||
});
|
});
|
||||||
@@ -319,20 +314,18 @@ describe("props validation", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: [1] };
|
props = { p: [1] };
|
||||||
await mount(Parent, fixture, { dev: true });
|
let app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
error = undefined;
|
error = undefined;
|
||||||
try {
|
app = new App(Parent, { test: true });
|
||||||
props = { p: ["string", 1] };
|
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
await mount(Parent, fixture, { dev: true });
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
} catch (e) {
|
await mountProm;
|
||||||
error = e as Error;
|
expect(error!).toBeDefined();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can validate an array with multiple sub element types", async () => {
|
test("can validate an array with multiple sub element types", async () => {
|
||||||
@@ -370,12 +363,11 @@ describe("props validation", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: [true, 1] };
|
props = { p: [true, 1] };
|
||||||
await mount(Parent, fixture, { dev: true });
|
const app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'SubComp': 'p[1]' is not a string or boolean"
|
"Invalid props for component 'SubComp': 'p[1]' is not a string or boolean"
|
||||||
@@ -405,33 +397,30 @@ describe("props validation", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: { id: 1, url: "url", extra: true } };
|
props = { p: { id: 1, url: "url", extra: true } };
|
||||||
await mount(Parent, fixture, { dev: true });
|
let app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'SubComp': 'p' has not the correct shape (unknown key 'extra')"
|
"Invalid props for component 'SubComp': 'p' has not the correct shape (unknown key 'extra')"
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
props = { p: { id: "1", url: "url" } };
|
props = { p: { id: "1", url: "url" } };
|
||||||
await mount(Parent, fixture, { dev: true });
|
app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'SubComp': 'p' has not the correct shape ('id' is not a number)"
|
"Invalid props for component 'SubComp': 'p' has not the correct shape ('id' is not a number)"
|
||||||
);
|
);
|
||||||
error = undefined;
|
error = undefined;
|
||||||
try {
|
|
||||||
props = { p: { id: 1 } };
|
props = { p: { id: 1 } };
|
||||||
await mount(Parent, fixture, { dev: true });
|
app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is missing (should be a string))"
|
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is missing (should be a string))"
|
||||||
@@ -474,12 +463,11 @@ describe("props validation", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeUndefined();
|
expect(error!).toBeUndefined();
|
||||||
try {
|
|
||||||
props = { p: { id: 1, url: [12, true] } };
|
props = { p: { id: 1, url: [12, true] } };
|
||||||
await mount(Parent, fixture, { dev: true });
|
const app = new App(Parent, { test: true });
|
||||||
} catch (e) {
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
error = e as Error;
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
}
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is not a boolean or list of numbers)"
|
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is not a boolean or list of numbers)"
|
||||||
@@ -686,11 +674,10 @@ describe("props validation", () => {
|
|||||||
static components = { SubComp };
|
static components = { SubComp };
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
|
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
|
||||||
});
|
});
|
||||||
@@ -754,11 +741,10 @@ describe("props validation", () => {
|
|||||||
static template = xml`<div><Child/></div>`;
|
static template = xml`<div><Child/></div>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'Child'");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"Invalid props for component 'Child': 'mandatory' is missing (should be a number)"
|
"Invalid props for component 'Child': 'mandatory' is missing (should be a number)"
|
||||||
@@ -859,11 +845,12 @@ describe("default props", () => {
|
|||||||
static template = xml`<Child/>`;
|
static template = xml`<Child/>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const app = new App(Parent, { test: true });
|
||||||
await mount(Parent, fixture, { dev: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
error = e as Error;
|
"default value cannot be defined for a mandatory prop"
|
||||||
}
|
);
|
||||||
|
await mountProm;
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(
|
expect(error!.message).toBe(
|
||||||
"A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)"
|
"A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, mount, onMounted, useRef, useState } from "../../src/index";
|
import { App, Component, mount, onMounted, useRef, useState } from "../../src/index";
|
||||||
import { logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { logStep, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
|
||||||
import { xml } from "../../src/index";
|
import { xml } from "../../src/index";
|
||||||
|
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
@@ -94,9 +94,14 @@ describe("refs", () => {
|
|||||||
ref = useRef("coucou");
|
ref = useRef("coucou");
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(async () => {
|
const app = new App(Test, { test: true });
|
||||||
await mount(Test, fixture);
|
const mountProm = expect(app.mount(fixture)).rejects.toThrowError(
|
||||||
}).rejects.toThrowError("Cannot have 2 elements with same ref name at the same time");
|
"Cannot have 2 elements with same ref name at the same time"
|
||||||
|
);
|
||||||
|
await expect(nextAppError(app)).resolves.toThrow(
|
||||||
|
"Cannot have 2 elements with same ref name at the same time"
|
||||||
|
);
|
||||||
|
await mountProm;
|
||||||
expect(console.warn).toBeCalledTimes(1);
|
expect(console.warn).toBeCalledTimes(1);
|
||||||
console.warn = consoleWarn;
|
console.warn = consoleWarn;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
|
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
|
||||||
import { children, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { children, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
let originalconsoleWarn = console.warn;
|
let originalconsoleWarn = console.warn;
|
||||||
@@ -204,13 +204,12 @@ describe("slots", () => {
|
|||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = null;
|
let error: Error;
|
||||||
try {
|
const app = new App(Parent);
|
||||||
await mount(Parent, fixture);
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
error = e;
|
await mountProm;
|
||||||
}
|
expect(error!).not.toBeNull();
|
||||||
expect(error).not.toBeNull();
|
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1819,4 +1818,102 @@ describe("slots", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2334]");
|
expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2334]");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("slot in multiple locations", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<div>child</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Slotter extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`
|
||||||
|
<t t-if="props.location === 1">
|
||||||
|
<p><t t-slot="default"/></p>
|
||||||
|
</t>
|
||||||
|
<t t-if="props.location === 2">
|
||||||
|
<t t-slot="default"/>
|
||||||
|
</t>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child, Slotter };
|
||||||
|
static template = xml`
|
||||||
|
<Slotter location="state.location">
|
||||||
|
hello <Child/>
|
||||||
|
</Slotter>`;
|
||||||
|
state = useState({ location: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<p> hello <div>child</div></p>");
|
||||||
|
parent.state.location = 2;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(" hello <div>child</div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("dynamic slot in multiple locations", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<div>child</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Slotter extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`
|
||||||
|
<t t-if="props.location === 1">
|
||||||
|
<p><t t-slot="{{'coffee'}}"/></p>
|
||||||
|
</t>
|
||||||
|
<t t-if="props.location === 2">
|
||||||
|
<t t-slot="{{'coffee'}}"/>
|
||||||
|
</t>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child, Slotter };
|
||||||
|
static template = xml`
|
||||||
|
<Slotter location="state.location">
|
||||||
|
<t t-set-slot="coffee">hello <Child/></t>
|
||||||
|
</Slotter>`;
|
||||||
|
state = useState({ location: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<p>hello <div>child</div></p>");
|
||||||
|
parent.state.location = 2;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("hello <div>child</div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slot in t-foreach locations", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<div>child</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Slotter extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`
|
||||||
|
<t t-foreach="props.list" t-as="elem" t-key="elem_index">
|
||||||
|
<p><t t-esc="elem"/><t t-slot="default"/></p>
|
||||||
|
</t>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child, Slotter };
|
||||||
|
static template = xml`
|
||||||
|
<Slotter list="state.list">
|
||||||
|
hello <Child/>
|
||||||
|
</Slotter>`;
|
||||||
|
state = useState({ list: [1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<p>1 hello <div>child</div></p>");
|
||||||
|
parent.state.list.push(2);
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<p>1 hello <div>child</div></p><p>2 hello <div>child</div></p>"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { OwlError } from "../../src/runtime/error_handling";
|
import { OwlError } from "../../src/runtime/error_handling";
|
||||||
import { Component, mount, onMounted, useState, xml } from "../../src";
|
import { App, Component, mount, onMounted, useState, xml } from "../../src";
|
||||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
@@ -343,16 +343,15 @@ describe("style and class handling", () => {
|
|||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div t-att-class="props.class" t-esc="this.will.crash"/>`;
|
static template = xml`<div t-att-class="props.class" t-esc="this.will.crash"/>`;
|
||||||
}
|
}
|
||||||
class ParentWidget extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<Child class="'a'"/>`;
|
static template = xml`<Child class="'a'"/>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
let error: OwlError;
|
let error: OwlError;
|
||||||
try {
|
const app = new App(Parent);
|
||||||
await mount(ParentWidget, fixture);
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
error = e as OwlError;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.cause).toBeDefined();
|
expect(error!.cause).toBeDefined();
|
||||||
const regexp =
|
const regexp =
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Component, mount, onMounted, useState, xml } from "../../src/index";
|
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
|
||||||
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
|
import {
|
||||||
|
makeTestFixture,
|
||||||
|
nextAppError,
|
||||||
|
nextTick,
|
||||||
|
snapshotEverything,
|
||||||
|
useLogLifecycle,
|
||||||
|
} from "../helpers";
|
||||||
|
|
||||||
snapshotEverything();
|
snapshotEverything();
|
||||||
|
|
||||||
@@ -315,9 +321,13 @@ describe("list of components", () => {
|
|||||||
`;
|
`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
await expect(async () => {
|
|
||||||
await mount(Parent, fixture, { dev: true });
|
const app = new App(Parent, { test: true });
|
||||||
}).rejects.toThrowError("Got duplicate key in t-foreach: child");
|
const mountProm = expect(app.mount(fixture)).rejects.toThrow(
|
||||||
|
"Got duplicate key in t-foreach: child"
|
||||||
|
);
|
||||||
|
await expect(nextAppError(app)).resolves.toThrow("Got duplicate key in t-foreach: child");
|
||||||
|
await mountProm;
|
||||||
console.info = consoleInfo;
|
console.info = consoleInfo;
|
||||||
expect(mockConsoleWarn).toBeCalledTimes(1);
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -261,6 +261,20 @@ expect.extend({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function nextAppError(app: any) {
|
||||||
|
const { handleError } = app;
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
app.handleError = (...args: Parameters<typeof handleError>) => {
|
||||||
|
try {
|
||||||
|
handleError.call(app, ...args);
|
||||||
|
} catch (e: any) {
|
||||||
|
app.handleError = handleError;
|
||||||
|
resolve(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace jest {
|
namespace jest {
|
||||||
interface Matchers<R> {
|
interface Matchers<R> {
|
||||||
|
|||||||
+13
-16
@@ -12,7 +12,7 @@ import {
|
|||||||
} from "../../src";
|
} from "../../src";
|
||||||
import { xml } from "../../src/";
|
import { xml } from "../../src/";
|
||||||
import { DEV_MSG } from "../../src/runtime/app";
|
import { DEV_MSG } from "../../src/runtime/app";
|
||||||
import { elem, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
import { elem, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
let fixture: HTMLElement;
|
||||||
let originalconsoleWarn = console.warn;
|
let originalconsoleWarn = console.warn;
|
||||||
@@ -269,11 +269,10 @@ describe("Portal", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const app = new App(Parent);
|
||||||
await mount(Parent, fixture);
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("invalid portal target");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe("invalid portal target");
|
expect(error!.message).toBe("invalid portal target");
|
||||||
@@ -960,11 +959,10 @@ describe("Portal: Props validation", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
let error: OwlError;
|
let error: OwlError;
|
||||||
try {
|
const app = new App(Parent);
|
||||||
await mount(Parent, fixture, { dev: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
||||||
error = e as OwlError;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.cause).toBeDefined();
|
expect(error!.cause).toBeDefined();
|
||||||
expect(error!.cause.message).toBe(`' ' is not a valid selector`);
|
expect(error!.cause.message).toBe(`' ' is not a valid selector`);
|
||||||
@@ -980,11 +978,10 @@ describe("Portal: Props validation", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
let error: Error;
|
let error: Error;
|
||||||
try {
|
const app = new App(Parent);
|
||||||
await mount(Parent, fixture, { dev: true });
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
||||||
} catch (e) {
|
await expect(nextAppError(app)).resolves.toThrow("invalid portal target");
|
||||||
error = e as Error;
|
await mountProm;
|
||||||
}
|
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe(`invalid portal target`);
|
expect(error!.message).toBe(`invalid portal target`);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user