mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da5fa6d45e | |||
| 77ff5ee895 | |||
| 47c6d6cc3c | |||
| 0625b5883a | |||
| 53ab54b1ec | |||
| 4770b91faa |
@@ -82,6 +82,7 @@ Are you new to Owl? This is the place to start!
|
||||
- [Component](doc/reference/component.md)
|
||||
- [Component Lifecycle](doc/reference/component.md#lifecycle)
|
||||
- [Concurrency Model](doc/reference/concurrency_model.md)
|
||||
- [Dev mode](doc/reference/app.md#dev-mode)
|
||||
- [Dynamic sub components](doc/reference/component.md#dynamic-sub-components)
|
||||
- [Environment](doc/reference/environment.md)
|
||||
- [Error Handling](doc/reference/error_handling.md)
|
||||
|
||||
+13
-4
@@ -51,11 +51,10 @@ The `config` object is an object with some of the following keys:
|
||||
|
||||
- **`env (object)`**: if given, this will be the shared `env` given to each component
|
||||
- **`props (object)`**: the props given to the root component
|
||||
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in `dev`
|
||||
mode, which activates some additional checks (in particular, the props validation
|
||||
code is only performed in dev mode)
|
||||
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in
|
||||
[`dev` mode](#dev-mode);
|
||||
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
|
||||
that Owll 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.
|
||||
- **`translatableAttributes (string[])`**: a list of additional attributes that should
|
||||
be translated (see [translations](translations.md))
|
||||
- **`translateFn (function)`**: a function that will be called by owl to translate
|
||||
@@ -110,3 +109,13 @@ const { loadFile, mount } = owl;
|
||||
mount(Root, document.body, { env });
|
||||
})();
|
||||
```
|
||||
|
||||
## Dev mode
|
||||
|
||||
Dev mode activates some additional checks and developer amenities:
|
||||
|
||||
- [Props validation](./props.md#props-validation) is performed
|
||||
- [t-foreach](./templates.md#loops) loops check for key unicity
|
||||
- Lifecycle hooks are wrapped to report their errors in a more developer-friendly way
|
||||
- onWillStart and onWillUpdateProps will emit a warning in the console when they
|
||||
take longer than 3 seconds in an effort to ease debugging the presence of deadlocks
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
|
||||
import { compile, Template } from "../compiler";
|
||||
import { compile, Template, TemplateFunction } from "../compiler";
|
||||
import { markRaw } from "../reactivity";
|
||||
import { Portal } from "../portal";
|
||||
import { component, getCurrent } from "../component/component_node";
|
||||
import { helpers } from "./template_helpers";
|
||||
import { globalTemplates } from "../utils";
|
||||
|
||||
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
||||
|
||||
@@ -48,7 +47,7 @@ function makeHelpers(getTemplate: (name: string) => Template): any {
|
||||
markRaw,
|
||||
getTemplate,
|
||||
call: (owner: any, subTemplate: string, ctx: any, parent: any, key: any) => {
|
||||
const template = getTemplate(subTemplate);
|
||||
const template = typeof subTemplate === "string" ? getTemplate(subTemplate) : subTemplate;
|
||||
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
||||
},
|
||||
});
|
||||
@@ -63,11 +62,12 @@ export interface TemplateSetConfig {
|
||||
|
||||
export class TemplateSet {
|
||||
dev: boolean;
|
||||
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
|
||||
rawTemplates: any = {}; //typeof globalTemplates = Object.create(globalTemplates);
|
||||
templates: { [name: string]: Template } = {};
|
||||
translateFn?: (s: string) => string;
|
||||
translatableAttributes?: string[];
|
||||
helpers: any;
|
||||
bdom: any = bdom;
|
||||
|
||||
constructor(config: TemplateSetConfig = {}) {
|
||||
this.dev = config.dev || false;
|
||||
@@ -102,8 +102,11 @@ export class TemplateSet {
|
||||
}
|
||||
}
|
||||
|
||||
getTemplate(name: string): Template {
|
||||
getTemplate(name: string | any): Template {
|
||||
if (!(name in this.templates)) {
|
||||
if (typeof name === "function") {
|
||||
return name(this);
|
||||
}
|
||||
const rawTemplate = this.rawTemplates[name];
|
||||
if (rawTemplate === undefined) {
|
||||
let extraInfo = "";
|
||||
@@ -126,7 +129,7 @@ export class TemplateSet {
|
||||
return this.templates[name];
|
||||
}
|
||||
|
||||
_compileTemplate(name: string, template: string | Element) {
|
||||
_compileTemplate(name: string, template: string | Element): TemplateFunction {
|
||||
return compile(template, {
|
||||
name,
|
||||
dev: this.dev,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import type { TemplateFunction } from "../compiler/index";
|
||||
import type { App } from "..";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Component Class
|
||||
@@ -20,7 +22,7 @@ export type ComponentConstructor<P extends Props = any, E = any> = (new (
|
||||
StaticComponentProperties;
|
||||
|
||||
export class Component<Props = any, Env = any> {
|
||||
static template: string = "";
|
||||
static template: string | ((app: App) => TemplateFunction) = "";
|
||||
static props?: any;
|
||||
static defaultProps?: any;
|
||||
|
||||
|
||||
@@ -58,13 +58,6 @@ export function useState<T extends object>(state: T): Reactive<T> | NonReactive<
|
||||
batchedRenderFunctions.set(node, render);
|
||||
// manual implementation of onWillDestroy to break cyclic dependency
|
||||
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
||||
if (node.app.dev) {
|
||||
Object.defineProperty(node, "subscriptions", {
|
||||
get() {
|
||||
return getSubscriptions(render);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return reactive(state, render);
|
||||
}
|
||||
@@ -394,4 +387,16 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Some debug helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
get name(): string {
|
||||
return this.component.constructor.name;
|
||||
}
|
||||
|
||||
get subscriptions(): ReturnType<typeof getSubscriptions> {
|
||||
const render = batchedRenderFunctions.get(this);
|
||||
return render ? getSubscriptions(render) : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
import { getCurrent } from "./component_node";
|
||||
import { nodeErrorHandlers } from "./error_handling";
|
||||
|
||||
const TIMEOUT = Symbol("timeout");
|
||||
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
||||
const error = new Error(`The following error occurred in ${hookName}: `) as Error & {
|
||||
cause: any;
|
||||
};
|
||||
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
||||
const node = getCurrent();
|
||||
return (...args: any[]) => {
|
||||
try {
|
||||
const result = fn(...args);
|
||||
if (result instanceof Promise) {
|
||||
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result,
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber) {
|
||||
console.warn(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch((cause) => {
|
||||
error.cause = cause;
|
||||
if (cause instanceof Error) {
|
||||
|
||||
+7
-6
@@ -57,7 +57,6 @@ export function useChildSubEnv(envExtension: Env) {
|
||||
// useEffect
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const NO_OP = () => {};
|
||||
/**
|
||||
* @param {...any} dependencies the dependencies computed by computeDependencies
|
||||
* @returns {void|(()=>void)} a cleanup function that reverses the side
|
||||
@@ -78,11 +77,11 @@ type Effect = (...dependencies: any[]) => void | (() => void);
|
||||
* NaN !== NaN, which will cause the effect to rerun on every patch.
|
||||
*/
|
||||
export function useEffect(effect: Effect, computeDependencies: () => any[] = () => [NaN]) {
|
||||
let cleanup: () => void;
|
||||
let cleanup: (() => void) | void;
|
||||
let dependencies: any[];
|
||||
onMounted(() => {
|
||||
dependencies = computeDependencies();
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
cleanup = effect(...dependencies);
|
||||
});
|
||||
|
||||
onPatched(() => {
|
||||
@@ -90,12 +89,14 @@ export function useEffect(effect: Effect, computeDependencies: () => any[] = ()
|
||||
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
||||
if (shouldReapply) {
|
||||
dependencies = newDeps;
|
||||
cleanup();
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
}
|
||||
cleanup = effect(...dependencies);
|
||||
}
|
||||
});
|
||||
|
||||
onWillUnmount(() => cleanup());
|
||||
onWillUnmount(() => cleanup && cleanup());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -308,6 +308,28 @@ function makeIteratorObserver(
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a forEach function that will delegate to forEach on the underlying
|
||||
* collection while observing key changes, and keys as they're iterated over,
|
||||
* and making the passed keys/values reactive.
|
||||
*
|
||||
* @param target @see reactive
|
||||
* @param callback @see reactive
|
||||
*/
|
||||
function makeForEachObserver(target: any, callback: Callback) {
|
||||
return function forEach(forEachCb: (val: any, key: any, target: any) => void, thisArg: any) {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
target.forEach(function (val: any, key: any, targetObj: any) {
|
||||
observeTargetKey(target, key, callback);
|
||||
forEachCb.call(
|
||||
thisArg,
|
||||
possiblyReactive(val, callback),
|
||||
possiblyReactive(key, callback),
|
||||
possiblyReactive(targetObj, callback)
|
||||
);
|
||||
}, thisArg);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a function that will delegate to an underlying method, and check if
|
||||
* that method has modified the presence or value of a key, and notify the
|
||||
@@ -370,6 +392,7 @@ const rawTypeToFuncHandlers = {
|
||||
values: makeIteratorObserver("values", target, callback),
|
||||
entries: makeIteratorObserver("entries", target, callback),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||
forEach: makeForEachObserver(target, callback),
|
||||
clear: makeClearNotifier(target),
|
||||
get size() {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
@@ -385,6 +408,7 @@ const rawTypeToFuncHandlers = {
|
||||
values: makeIteratorObserver("values", target, callback),
|
||||
entries: makeIteratorObserver("entries", target, callback),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||
forEach: makeForEachObserver(target, callback),
|
||||
clear: makeClearNotifier(target),
|
||||
get size() {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
|
||||
+33
-5
@@ -75,13 +75,41 @@ export function markup(value: any) {
|
||||
// -----------------------------------------------------------------------------
|
||||
// xml tag helper
|
||||
// -----------------------------------------------------------------------------
|
||||
export const globalTemplates: { [key: string]: string | Element } = {};
|
||||
|
||||
export function xml(...args: Parameters<typeof String.raw>) {
|
||||
const name = `__template__${xml.nextId++}`;
|
||||
export function xml(...args: Parameters<typeof String.raw>): any {
|
||||
const subTemplates: any = {};
|
||||
let hasSubTemplates = false;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (typeof arg === "function") {
|
||||
hasSubTemplates = true;
|
||||
const id = `__template__${xml.nextId++}`;
|
||||
subTemplates[id] = arg;
|
||||
args[i] = `{{__owl__.subTemplates.${id}(__owl__.app)}}`;
|
||||
}
|
||||
}
|
||||
const value = String.raw(...args);
|
||||
globalTemplates[name] = value;
|
||||
return name;
|
||||
|
||||
let cache = new WeakMap();
|
||||
|
||||
return (app: any) => {
|
||||
debugger;
|
||||
let renderFn = cache.get(app);
|
||||
if (!renderFn) {
|
||||
let templateFn = app._compileTemplate("", value);
|
||||
renderFn = templateFn(app.bdom, app.helpers);
|
||||
if (hasSubTemplates) {
|
||||
let origRenderFn = renderFn;
|
||||
renderFn = (context: any, vnode: any, key?: any) => {
|
||||
debugger;
|
||||
context.__owl__.subTemplates = subTemplates;
|
||||
return origRenderFn(context, vnode, key);
|
||||
};
|
||||
}
|
||||
cache.set(app, renderFn);
|
||||
}
|
||||
return renderFn;
|
||||
};
|
||||
}
|
||||
|
||||
xml.nextId = 1;
|
||||
|
||||
@@ -169,11 +169,11 @@ exports[`basics can inject values in tagged templates 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { call } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__1(ctx['__owl__'].app));
|
||||
return call(this, template1, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -301,6 +301,19 @@ exports[`hooks useEffect hook effect with empty dependency list never reruns 1`]
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks useEffect hook properly behaves when the effect function throws 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks useExternalListener 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -658,6 +658,43 @@ exports[`lifecycle hooks sub widget (inside sub node): hooks are correctly calle
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillStart emits a warning 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<span/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const props1 = {prop: ctx['state'].prop};
|
||||
helpers.validateProps(\`Child\`, props1, ctx);
|
||||
return component(\`Child\`, props1, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents, in proper order 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -249,11 +249,11 @@ exports[`slots can use t-call in default-content of t-slot 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { callSlot, getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { callSlot, call } = helpers;
|
||||
|
||||
function defaultContent1(ctx, node, key = \\"\\") {
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__3(ctx['__owl__'].app));
|
||||
return call(this, template1, ctx, node, key + \`__1\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
@@ -772,12 +772,12 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { capture, getTemplate, markRaw } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__1002\`);
|
||||
let { capture, call, markRaw } = helpers;
|
||||
|
||||
function slot1(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`[A]\`);
|
||||
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__5(ctx['__owl__'].app));
|
||||
const b3 = call(this, template1, ctx, node, key + \`__1\`);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
|
||||
@@ -790,41 +790,6 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil
|
||||
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 4`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__1001\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
const b2 = text(\`[sub1] \`);
|
||||
setContextValue(ctx, \\"dummy\\", ctx['validate']);
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
setContextValue(ctx, \\"v\\", ctx['props'].number);
|
||||
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 5`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`[sub2\`);
|
||||
const b3 = text(ctx['v']);
|
||||
const b4 = text(\`]\`);
|
||||
return multi([b2, b3, b4]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 6`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
@@ -836,7 +801,7 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 7`] = `
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 5`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
@@ -850,6 +815,41 @@ exports[`slots mix of slots, t-call, t-call with body, and giving own props chil
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 6`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { isBoundary, withDefault, setContextValue, call } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
const b2 = text(\`[sub1] \`);
|
||||
setContextValue(ctx, \\"dummy\\", ctx['validate']);
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1;
|
||||
setContextValue(ctx, \\"v\\", ctx['props'].number);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__4(ctx['__owl__'].app));
|
||||
const b3 = call(this, template1, ctx, node, key + \`__1\`);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots mix of slots, t-call, t-call with body, and giving own props child 7`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`[sub2\`);
|
||||
const b3 = text(ctx['v']);
|
||||
const b4 = text(\`]\`);
|
||||
return multi([b2, b3, b4]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots multiple roots are allowed in a default slot 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
@@ -1486,13 +1486,13 @@ exports[`slots slot and (inline) t-call 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { capture, getTemplate, markRaw } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { capture, call, markRaw } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
function slot1(ctx, node, key = \\"\\") {
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__2(ctx['__owl__'].app));
|
||||
return call(this, template1, ctx, node, key + \`__1\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
@@ -1505,19 +1505,6 @@ exports[`slots slot and (inline) t-call 1`] = `
|
||||
|
||||
exports[`slots slot and (inline) t-call 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<p>sokka</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots slot and (inline) t-call 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
@@ -1531,17 +1518,30 @@ exports[`slots slot and (inline) t-call 3`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots slot and (inline) t-call 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<p>sokka</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots slot and t-call 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { capture, getTemplate, markRaw } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { capture, call, markRaw } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
function slot1(ctx, node, key = \\"\\") {
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__1(ctx['__owl__'].app));
|
||||
return call(this, template1, ctx, node, key + \`__1\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
@@ -1556,11 +1556,13 @@ exports[`slots slot and t-call 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<p>sokka</p>\`);
|
||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
const b2 = callSlot(ctx, node, key, 'default', false, null);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -1569,13 +1571,11 @@ exports[`slots slot and t-call 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||
let block1 = createBlock(\`<p>sokka</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callSlot(ctx, node, key, 'default', false, null);
|
||||
return block1([], [b2]);
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -116,13 +116,13 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { call } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__1(ctx['__owl__'].app));
|
||||
const b2 = call(this, template1, ctx, node, key + \`__1\`);
|
||||
let txt1 = ctx['counter'];
|
||||
return block1([txt1], [b2]);
|
||||
}
|
||||
@@ -147,13 +147,13 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`]
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { call } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__6(ctx['__owl__'].app));
|
||||
const b2 = call(this, template1, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
@@ -178,13 +178,13 @@ exports[`t-call parent is set within t-call 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { call } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__3(ctx['__owl__'].app));
|
||||
const b2 = call(this, template1, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
@@ -218,11 +218,11 @@ exports[`t-call parent is set within t-call with no parentNode 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { call } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__5(ctx['__owl__'].app));
|
||||
return call(this, template1, ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -353,8 +353,7 @@ exports[`t-call t-call in t-foreach and children component 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { prepareList, getTemplate, withKey } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`__template__999\`);
|
||||
let { prepareList, call, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
@@ -368,7 +367,8 @@ exports[`t-call t-call in t-foreach and children component 1`] = `
|
||||
ctx[\`val_index\`] = i1;
|
||||
ctx[\`val_value\`] = k_block2[i1];
|
||||
const key1 = ctx['val'];
|
||||
c_block2[i1] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`), key1);
|
||||
const template1 = (ctx['__owl__'].subTemplates.__template__4(ctx['__owl__'].app));
|
||||
c_block2[i1] = withKey(call(this, template1, ctx, node, key + \`__1__\${key1}\`), key1);
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
|
||||
@@ -444,6 +444,7 @@ describe("hooks", () => {
|
||||
let cleanupRun = 0;
|
||||
let steps = [];
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<div/>`;
|
||||
state = useState({
|
||||
value: 0,
|
||||
});
|
||||
@@ -455,7 +456,6 @@ describe("hooks", () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
MyComponent.template = xml`<div/>`;
|
||||
|
||||
const component = await mount(MyComponent, fixture);
|
||||
|
||||
@@ -512,6 +512,7 @@ describe("hooks", () => {
|
||||
test("dependencies prevent effects from rerunning when unchanged", async () => {
|
||||
let steps = [];
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<div/>`;
|
||||
state = useState({
|
||||
a: 0,
|
||||
b: 0,
|
||||
@@ -540,7 +541,6 @@ describe("hooks", () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = xml`<div/>`;
|
||||
steps.push("before mount");
|
||||
const component = await mount(MyComponent, fixture);
|
||||
steps.push("after mount");
|
||||
@@ -597,6 +597,7 @@ describe("hooks", () => {
|
||||
test("effect with empty dependency list never reruns", async () => {
|
||||
let steps = [];
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<div t-esc="state.value"/>`;
|
||||
state = useState({
|
||||
value: 0,
|
||||
});
|
||||
@@ -610,7 +611,6 @@ describe("hooks", () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
MyComponent.template = xml`<div t-esc="state.value"/>`;
|
||||
|
||||
const component = await mount(MyComponent, fixture);
|
||||
|
||||
@@ -632,5 +632,35 @@ describe("hooks", () => {
|
||||
"cleaning up for 1",
|
||||
]);
|
||||
});
|
||||
|
||||
test("properly behaves when the effect function throws", async () => {
|
||||
let originalconsoleError = console.error;
|
||||
let originalconsoleWarn = console.warn;
|
||||
console.error = jest.fn(() => {});
|
||||
console.warn = jest.fn(() => {});
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<div/>`;
|
||||
setup() {
|
||||
useEffect(
|
||||
() => {
|
||||
throw new Error("Intentional error");
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await mount(MyComponent, fixture);
|
||||
} catch (e: any) {
|
||||
expect(e.message).toBe("Intentional error");
|
||||
}
|
||||
// no console.error because the error has been caught in this test
|
||||
expect(console.error).toHaveBeenCalledTimes(0);
|
||||
console.error = originalconsoleError;
|
||||
// 1 console.warn because app is destroyed
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
console.warn = originalconsoleWarn;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
logStep,
|
||||
makeDeferred,
|
||||
makeTestFixture,
|
||||
nextMicroTick,
|
||||
nextTick,
|
||||
snapshotEverything,
|
||||
useLogLifecycle,
|
||||
@@ -104,6 +105,83 @@ describe("lifecycle hooks", () => {
|
||||
await mount(Test, fixture);
|
||||
});
|
||||
|
||||
test("timeout in onWillStart emits a warning", async () => {
|
||||
const { warn } = console;
|
||||
let warnArgs: any[];
|
||||
console.warn = jest.fn((...args) => (warnArgs = args));
|
||||
const { setTimeout } = window;
|
||||
let timeoutCbs: any = {};
|
||||
let timeoutId = 0;
|
||||
window.setTimeout = ((cb: any) => {
|
||||
timeoutCbs[++timeoutId] = cb;
|
||||
return timeoutId;
|
||||
}) as any;
|
||||
class Test extends Component {
|
||||
static template = xml`<span/>`;
|
||||
setup() {
|
||||
onWillStart(() => new Promise(() => {}));
|
||||
}
|
||||
}
|
||||
mount(Test, fixture, { test: true });
|
||||
nextTick();
|
||||
for (const id in timeoutCbs) {
|
||||
timeoutCbs[id]();
|
||||
delete timeoutCbs[id];
|
||||
}
|
||||
await nextMicroTick();
|
||||
await nextMicroTick();
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(warnArgs![0]!.message).toBe("onWillStart's promise hasn't resolved after 3 seconds");
|
||||
console.warn = warn;
|
||||
window.setTimeout = setTimeout;
|
||||
});
|
||||
|
||||
test("timeout in onWillUpdateProps emits a warning", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml``;
|
||||
setup() {
|
||||
onWillUpdateProps(() => new Promise(() => {}));
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child prop="state.prop"/>`;
|
||||
static components = { Child };
|
||||
state = useState({ prop: 1 });
|
||||
}
|
||||
const parent = await mount(Parent, fixture, { test: true });
|
||||
|
||||
const { warn } = console;
|
||||
let warnArgs: any[];
|
||||
console.warn = jest.fn((...args) => (warnArgs = args));
|
||||
const { setTimeout } = window;
|
||||
let timeoutCbs: any = {};
|
||||
let timeoutId = 0;
|
||||
window.setTimeout = ((cb: any) => {
|
||||
timeoutCbs[++timeoutId] = cb;
|
||||
return timeoutId;
|
||||
}) as any;
|
||||
|
||||
parent.state.prop = 2;
|
||||
let tick = nextTick();
|
||||
for (const id in timeoutCbs) {
|
||||
timeoutCbs[id]();
|
||||
delete timeoutCbs[id];
|
||||
}
|
||||
await tick;
|
||||
tick = nextTick();
|
||||
for (const id in timeoutCbs) {
|
||||
timeoutCbs[id]();
|
||||
delete timeoutCbs[id];
|
||||
}
|
||||
await tick;
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(warnArgs![0]!.message).toBe(
|
||||
"onWillUpdateProps's promise hasn't resolved after 3 seconds"
|
||||
);
|
||||
console.warn = warn;
|
||||
window.setTimeout = setTimeout;
|
||||
});
|
||||
|
||||
test("mounted hook is called if mounted in DOM", async () => {
|
||||
let mounted = false;
|
||||
class Test extends Component {
|
||||
|
||||
+19
-9
@@ -13,13 +13,13 @@ import {
|
||||
onWillUpdateProps,
|
||||
status,
|
||||
useComponent,
|
||||
xml,
|
||||
} from "../src";
|
||||
import { helpers } from "../src/app/template_helpers";
|
||||
import { TemplateSet } from "../src/app/template_set";
|
||||
import { BDom } from "../src/blockdom";
|
||||
import { compile } from "../src/compiler";
|
||||
import { globalTemplates } from "../src/utils";
|
||||
import { Portal } from "../src/portal";
|
||||
// import { globalTemplates } from "../src/utils";
|
||||
|
||||
const mount = blockDom.mount;
|
||||
|
||||
@@ -39,9 +39,6 @@ export function makeTestFixture() {
|
||||
return fixture;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
xml.nextId = 999;
|
||||
});
|
||||
export async function nextTick(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
@@ -49,7 +46,7 @@ export async function nextTick(): Promise<void> {
|
||||
|
||||
interface Deferred extends Promise<any> {
|
||||
resolve(val?: any): void;
|
||||
reject(): void;
|
||||
reject(val?: any): void;
|
||||
}
|
||||
|
||||
export function makeDeferred(): Deferred {
|
||||
@@ -117,16 +114,29 @@ export function snapshotEverything() {
|
||||
// this function has already been called
|
||||
return;
|
||||
}
|
||||
const globalTemplateNames = new Set(Object.keys(globalTemplates));
|
||||
shouldSnapshot = true;
|
||||
beforeEach(() => {
|
||||
snapshottedTemplates.clear();
|
||||
});
|
||||
|
||||
const getTemplate = TemplateSet.prototype.getTemplate;
|
||||
TemplateSet.prototype.getTemplate = function (this: any, name: string) {
|
||||
if (name === Portal.template) {
|
||||
this.skipSnapshot = true;
|
||||
}
|
||||
const result = getTemplate.call(this, name);
|
||||
this.skipSnapshot = false;
|
||||
return result;
|
||||
};
|
||||
|
||||
const originalCompileTemplate = TemplateSet.prototype._compileTemplate;
|
||||
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) {
|
||||
TemplateSet.prototype._compileTemplate = function (
|
||||
this: any,
|
||||
name: string,
|
||||
template: string | Element
|
||||
) {
|
||||
const fn = originalCompileTemplate.call(this, "", template);
|
||||
if (!globalTemplateNames.has(name)) {
|
||||
if (!this.skipSnapshot) {
|
||||
expect(fn.toString()).toMatchSnapshot();
|
||||
}
|
||||
return fn;
|
||||
|
||||
@@ -1245,6 +1245,33 @@ describe("Collections", () => {
|
||||
reactiveObj.a = 1; // setting same value again shouldn't notify
|
||||
expect(observer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("iterating with forEach returns reactives", async () => {
|
||||
const keyObj = { a: 2 };
|
||||
const thisArg = {};
|
||||
const observer = jest.fn();
|
||||
const state = reactive(new Set([keyObj]), observer);
|
||||
let reactiveKeyObj: any, reactiveValObj: any, thisObj: any, mapObj: any;
|
||||
state.forEach(function (this: any, val, key, map) {
|
||||
[reactiveValObj, reactiveKeyObj, mapObj, thisObj] = [val, key, map, this];
|
||||
}, thisArg);
|
||||
expect(reactiveKeyObj).not.toBe(keyObj);
|
||||
expect(reactiveValObj).not.toBe(keyObj);
|
||||
expect(mapObj).toBe(state); // third argument should be the reactive
|
||||
expect(thisObj).toBe(thisArg); // thisArg should not be made reactive
|
||||
expect(toRaw(reactiveKeyObj as any)).toBe(keyObj);
|
||||
expect(toRaw(reactiveValObj as any)).toBe(keyObj);
|
||||
expect(reactiveKeyObj).toBe(reactiveValObj); // reactiveKeyObj and reactiveValObj should be the same object
|
||||
reactiveKeyObj!.a = 0;
|
||||
reactiveValObj!.a = 0;
|
||||
expect(observer).toHaveBeenCalledTimes(0);
|
||||
reactiveKeyObj!.a; // observe key "a" in key sub-reactive;
|
||||
reactiveKeyObj!.a = 1;
|
||||
expect(observer).toHaveBeenCalledTimes(1);
|
||||
reactiveKeyObj!.a = 1; // setting same value again shouldn't notify
|
||||
reactiveValObj!.a = 1;
|
||||
expect(observer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("WeakSet", () => {
|
||||
@@ -1467,6 +1494,36 @@ describe("Collections", () => {
|
||||
reactiveValObj.a = 1;
|
||||
expect(observer).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test("iterating with forEach returns reactives", async () => {
|
||||
const keyObj = { a: 2 };
|
||||
const valObj = { a: 2 };
|
||||
const thisArg = {};
|
||||
const observer = jest.fn();
|
||||
const state = reactive(new Map([[keyObj, valObj]]), observer);
|
||||
let reactiveKeyObj: any, reactiveValObj: any, thisObj: any, mapObj: any;
|
||||
state.forEach(function (this: any, val, key, map) {
|
||||
[reactiveValObj, reactiveKeyObj, mapObj, thisObj] = [val, key, map, this];
|
||||
}, thisArg);
|
||||
expect(reactiveKeyObj).not.toBe(keyObj);
|
||||
expect(reactiveValObj).not.toBe(valObj);
|
||||
expect(mapObj).toBe(state); // third argument should be the reactive
|
||||
expect(thisObj).toBe(thisArg); // thisArg should not be made reactive
|
||||
expect(toRaw(reactiveKeyObj as any)).toBe(keyObj);
|
||||
expect(toRaw(reactiveValObj as any)).toBe(valObj);
|
||||
reactiveKeyObj!.a = 0;
|
||||
reactiveValObj!.a = 0;
|
||||
expect(observer).toHaveBeenCalledTimes(0);
|
||||
reactiveKeyObj!.a; // observe key "a" in key sub-reactive;
|
||||
reactiveKeyObj!.a = 1;
|
||||
expect(observer).toHaveBeenCalledTimes(1);
|
||||
reactiveValObj!.a; // observe key "a" in val sub-reactive;
|
||||
reactiveValObj!.a = 1;
|
||||
expect(observer).toHaveBeenCalledTimes(2);
|
||||
reactiveKeyObj!.a = 1; // setting same value again shouldn't notify
|
||||
reactiveValObj!.a = 1;
|
||||
expect(observer).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("WeakMap", () => {
|
||||
|
||||
Reference in New Issue
Block a user