mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba5365e9d9 | |||
| 163366997c |
@@ -55,6 +55,9 @@ The `config` object is an object with some of the following keys:
|
|||||||
[`dev` mode](#dev-mode);
|
[`dev` mode](#dev-mode);
|
||||||
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
|
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
|
||||||
that Owl will not log a message to warn that Owl is in `dev` mode.
|
that Owl will not log a message to warn that Owl is in `dev` mode.
|
||||||
|
- **`shareTemplates (boolean, default=false)`**: if `true`, each compiled template
|
||||||
|
will be shared between instances of `App`. Useful for speeding test suites, because
|
||||||
|
it prevent recompiling the same templates again and again.
|
||||||
- **`translatableAttributes (string[])`**: a list of additional attributes that should
|
- **`translatableAttributes (string[])`**: a list of additional attributes that should
|
||||||
be translated (see [translations](translations.md))
|
be translated (see [translations](translations.md))
|
||||||
- **`translateFn (function)`**: a function that will be called by owl to translate
|
- **`translateFn (function)`**: a function that will be called by owl to translate
|
||||||
|
|||||||
@@ -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!;
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
+35
-10
@@ -37,11 +37,17 @@ function parseXML(xml: string): Document {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sharedTemplates: Map<
|
||||||
|
Function | undefined,
|
||||||
|
{ [key: string]: { [name: string]: TemplateFunction } }
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
export interface TemplateSetConfig {
|
export interface TemplateSetConfig {
|
||||||
dev?: boolean;
|
dev?: boolean;
|
||||||
translatableAttributes?: string[];
|
translatableAttributes?: string[];
|
||||||
translateFn?: (s: string) => string;
|
translateFn?: (s: string) => string;
|
||||||
templates?: string | Document;
|
templates?: string | Document;
|
||||||
|
shareTemplates?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TemplateSet {
|
export class TemplateSet {
|
||||||
@@ -51,12 +57,27 @@ export class TemplateSet {
|
|||||||
dev: boolean;
|
dev: boolean;
|
||||||
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
||||||
templates: { [name: string]: Template } = {};
|
templates: { [name: string]: Template } = {};
|
||||||
|
templateFunctions: { [name: string]: TemplateFunction } = {};
|
||||||
translateFn?: (s: string) => string;
|
translateFn?: (s: string) => string;
|
||||||
translatableAttributes?: string[];
|
translatableAttributes?: string[];
|
||||||
Portal = Portal;
|
Portal = Portal;
|
||||||
|
|
||||||
constructor(config: TemplateSetConfig = {}) {
|
constructor(config: TemplateSetConfig = {}) {
|
||||||
this.dev = config.dev || false;
|
this.dev = config.dev || false;
|
||||||
|
if (config.shareTemplates) {
|
||||||
|
let cache = sharedTemplates.get(this.translateFn);
|
||||||
|
if (!cache) {
|
||||||
|
cache = {};
|
||||||
|
sharedTemplates.set(this.translateFn, cache);
|
||||||
|
}
|
||||||
|
let key = `${this.dev ? "d" : "p"}${(this.translatableAttributes || []).toString()}`;
|
||||||
|
let templates = cache[key];
|
||||||
|
if (!templates) {
|
||||||
|
cache[key] = {};
|
||||||
|
templates = cache[key];
|
||||||
|
}
|
||||||
|
this.templateFunctions = templates;
|
||||||
|
}
|
||||||
this.translateFn = config.translateFn;
|
this.translateFn = config.translateFn;
|
||||||
this.translatableAttributes = config.translatableAttributes;
|
this.translatableAttributes = config.translatableAttributes;
|
||||||
if (config.templates) {
|
if (config.templates) {
|
||||||
@@ -96,17 +117,21 @@ export class TemplateSet {
|
|||||||
|
|
||||||
getTemplate(name: string): Template {
|
getTemplate(name: string): Template {
|
||||||
if (!(name in this.templates)) {
|
if (!(name in this.templates)) {
|
||||||
const rawTemplate = this.rawTemplates[name];
|
let templateFn = this.templateFunctions[name];
|
||||||
if (rawTemplate === undefined) {
|
if (!templateFn) {
|
||||||
let extraInfo = "";
|
const rawTemplate = this.rawTemplates[name];
|
||||||
try {
|
if (rawTemplate === undefined) {
|
||||||
const componentName = getCurrent().component.constructor.name;
|
let extraInfo = "";
|
||||||
extraInfo = ` (for component "${componentName}")`;
|
try {
|
||||||
} catch {}
|
const componentName = getCurrent().component.constructor.name;
|
||||||
throw new OwlError(`Missing template: "${name}"${extraInfo}`);
|
extraInfo = ` (for component "${componentName}")`;
|
||||||
|
} catch {}
|
||||||
|
throw new OwlError(`Missing template: "${name}"${extraInfo}`);
|
||||||
|
}
|
||||||
|
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
|
||||||
|
templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
|
||||||
|
this.templateFunctions[name] = templateFn;
|
||||||
}
|
}
|
||||||
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
|
|
||||||
const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
|
|
||||||
// first add a function to lazily get the template, in case there is a
|
// first add a function to lazily get the template, in case there is a
|
||||||
// recursive call to the template name
|
// recursive call to the template name
|
||||||
const templates = this.templates;
|
const templates = this.templates;
|
||||||
|
|||||||
@@ -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