mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cb74d619b | |||
| a93f015795 | |||
| 02a187d80b | |||
| d3b0d1971e | |||
| b90aa0e23a | |||
| 163366997c |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-beta-15",
|
"version": "2.0.0-beta-17",
|
||||||
"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",
|
||||||
|
|||||||
@@ -584,8 +584,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") {
|
||||||
|
|||||||
@@ -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!;
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
error.cause = cause;
|
||||||
if (cause instanceof Error) {
|
if (cause instanceof Error) {
|
||||||
error.cause = cause;
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-5
@@ -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) {
|
||||||
if (!document.body.contains(target)) {
|
const HTMLElement = document.defaultView!.HTMLElement;
|
||||||
throw new OwlError("Cannot mount a component on a detached dom node");
|
if (target instanceof HTMLElement) {
|
||||||
|
if (!document.body.contains(target)) {
|
||||||
|
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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -511,7 +511,7 @@ describe("can catch errors", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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"/>`;
|
||||||
@@ -574,7 +574,9 @@ describe("can catch errors", () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
e = error as OwlError;
|
e = error as OwlError;
|
||||||
}
|
}
|
||||||
expect(e!.message).toBe("An error occured in the owl lifecycle");
|
expect(e!.message).toBe(
|
||||||
|
`An error occured in the owl lifecycle (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
expect(e!.cause).toBe(err);
|
expect(e!.cause).toBe(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -597,10 +599,58 @@ describe("can catch errors", () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
e = error as OwlError;
|
e = error as OwlError;
|
||||||
}
|
}
|
||||||
expect(e!.message).toBe("An error occured in the owl lifecycle");
|
expect(e!.message).toBe(
|
||||||
|
`An error occured in the owl lifecycle (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
expect(e!.cause).toBe(err);
|
expect(e!.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";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let e: OwlError;
|
||||||
|
try {
|
||||||
|
await mount(Root, fixture, { test: true });
|
||||||
|
} catch (error) {
|
||||||
|
e = error as OwlError;
|
||||||
|
}
|
||||||
|
expect(e!.message).toBe(
|
||||||
|
`Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
|
expect(e!.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";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let e: OwlError;
|
||||||
|
try {
|
||||||
|
await mount(Root, fixture);
|
||||||
|
} catch (error) {
|
||||||
|
e = error as OwlError;
|
||||||
|
}
|
||||||
|
expect(e!.message).toBe(
|
||||||
|
`An error occured in the owl lifecycle (see this Error's "cause" property)`
|
||||||
|
);
|
||||||
|
expect(e!.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 () => {
|
||||||
class ErrorComponent extends Component {
|
class ErrorComponent extends Component {
|
||||||
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
|
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user