mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b11a440625 |
@@ -451,6 +451,5 @@ console.log(status(component));
|
|||||||
// logs either:
|
// logs either:
|
||||||
// - 'new', if the component is new and has not been mounted yet
|
// - 'new', if the component is new and has not been mounted yet
|
||||||
// - 'mounted', if the component is currently mounted
|
// - 'mounted', if the component is currently mounted
|
||||||
// - 'cancelled', if the component has not been mounted yet but will be destroyed soon
|
|
||||||
// - 'destroyed' if the component is currently destroyed
|
// - 'destroyed' if the component is currently destroyed
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -365,8 +365,10 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
const typeAttr = node.getAttribute("type");
|
const typeAttr = node.getAttribute("type");
|
||||||
const isInput = tagName === "input";
|
const isInput = tagName === "input";
|
||||||
const isSelect = tagName === "select";
|
const isSelect = tagName === "select";
|
||||||
|
const isTextarea = tagName === "textarea";
|
||||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||||
const isRadioInput = isInput && typeAttr === "radio";
|
const isRadioInput = isInput && typeAttr === "radio";
|
||||||
|
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
||||||
const hasLazyMod = attr.includes(".lazy");
|
const hasLazyMod = attr.includes(".lazy");
|
||||||
const hasNumberMod = attr.includes(".number");
|
const hasNumberMod = attr.includes(".number");
|
||||||
const hasTrimMod = attr.includes(".trim");
|
const hasTrimMod = attr.includes(".trim");
|
||||||
@@ -379,8 +381,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
||||||
eventType,
|
eventType,
|
||||||
hasDynamicChildren: false,
|
hasDynamicChildren: false,
|
||||||
shouldTrim: hasTrimMod,
|
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||||
shouldNumberize: hasNumberMod,
|
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||||
};
|
};
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
// don't pollute the original ctx
|
// don't pollute the original ctx
|
||||||
|
|||||||
@@ -93,17 +93,6 @@ function normalizeNode(node: HTMLElement | Text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode 2 numbers and 1 boolean in a number, using 31 bits:
|
|
||||||
* n1 => encoded in 16 most significant bits
|
|
||||||
* n2 => encoded in 15 next bits
|
|
||||||
* boolean => encoded in last significant bit.
|
|
||||||
* This code assumes that n1 and n2 are small enough to fit in that number of bits
|
|
||||||
*/
|
|
||||||
function encodeValue(n1: number, n2: number, b: boolean): number {
|
|
||||||
return (((n1 << 15) | n2) << 1) | (b ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// building a intermediate tree
|
// building a intermediate tree
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -317,7 +306,7 @@ interface IndexedLocation extends Location {
|
|||||||
|
|
||||||
interface Child {
|
interface Child {
|
||||||
parentRefIdx: number;
|
parentRefIdx: number;
|
||||||
afterRefIdx: number;
|
afterRefIdx?: number;
|
||||||
isOnlyChild?: boolean;
|
isOnlyChild?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +374,6 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
|
|||||||
// tree is the parentnode here
|
// tree is the parentnode here
|
||||||
ctx.children[info.idx] = {
|
ctx.children[info.idx] = {
|
||||||
parentRefIdx: info.refIdx!,
|
parentRefIdx: info.refIdx!,
|
||||||
afterRefIdx: 0,
|
|
||||||
isOnlyChild: true,
|
isOnlyChild: true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -513,6 +501,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
}));
|
}));
|
||||||
const locN = locations.length;
|
const locN = locations.length;
|
||||||
const childN = children.length;
|
const childN = children.length;
|
||||||
|
const childrenLocs = children;
|
||||||
const isDynamic = refN > 0;
|
const isDynamic = refN > 0;
|
||||||
|
|
||||||
// these values are defined here to make them faster to lookup in the class
|
// these values are defined here to make them faster to lookup in the class
|
||||||
@@ -567,19 +556,6 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDynamic) {
|
if (isDynamic) {
|
||||||
const nextSibling = nodeGetNextSibling;
|
|
||||||
const firstChild = nodeGetFirstChild;
|
|
||||||
const bitPackedCollectors = new Uint32Array(
|
|
||||||
collectors.map((c) => {
|
|
||||||
return encodeValue(c.idx, c.prevIdx, c.getVal === nextSibling);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const childrenLocs = new Uint32Array(
|
|
||||||
children.map((c) => {
|
|
||||||
return encodeValue(c.afterRefIdx, c.parentRefIdx, Boolean(c.isOnlyChild));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
|
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
|
||||||
const el = nodeCloneNode.call(template, true);
|
const el = nodeCloneNode.call(template, true);
|
||||||
// collecting references
|
// collecting references
|
||||||
@@ -587,17 +563,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
this.refs = refs;
|
this.refs = refs;
|
||||||
refs[0] = el;
|
refs[0] = el;
|
||||||
for (let i = 0; i < colN; i++) {
|
for (let i = 0; i < colN; i++) {
|
||||||
let info = bitPackedCollectors[i];
|
const w = collectors[i];
|
||||||
// decode info
|
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
||||||
const fn = (info & 1) === 1 ? nextSibling : firstChild;
|
|
||||||
info = info >> 1;
|
|
||||||
const prevIdx = info & 0b111111111111111;
|
|
||||||
const idx = info >> 15;
|
|
||||||
refs[idx] = fn.call(refs[prevIdx]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// applying data to all update points
|
// applying data to all update points
|
||||||
if (locN !== 0) {
|
if (locN) {
|
||||||
const data = this.data!;
|
const data = this.data!;
|
||||||
for (let i = 0; i < locN; i++) {
|
for (let i = 0; i < locN; i++) {
|
||||||
const loc = locations[i];
|
const loc = locations[i];
|
||||||
@@ -608,21 +579,15 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
nodeInsertBefore.call(parent, el, afterNode);
|
nodeInsertBefore.call(parent, el, afterNode);
|
||||||
|
|
||||||
// preparing all children
|
// preparing all children
|
||||||
if (childN !== 0) {
|
if (childN) {
|
||||||
const children = this.children;
|
const children = this.children;
|
||||||
for (let i = 0; i < childN; i++) {
|
for (let i = 0; i < childN; i++) {
|
||||||
const child = children![i];
|
const child = children![i];
|
||||||
if (child !== undefined) {
|
if (child) {
|
||||||
let info = childrenLocs[i];
|
const loc = childrenLocs[i];
|
||||||
// decode info
|
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
||||||
const isOnlyChild = info & 1;
|
child.isOnlyChild = loc.isOnlyChild;
|
||||||
info = info >> 1;
|
child.mount(refs[loc.parentRefIdx] as any, afterNode);
|
||||||
const parentRefIdx = info & 0b111111111111111;
|
|
||||||
const afterRefIdx = info >> 15;
|
|
||||||
|
|
||||||
const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null;
|
|
||||||
child.isOnlyChild = isOnlyChild as any;
|
|
||||||
child.mount(refs[parentRefIdx] as any, afterNode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -636,7 +601,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
}
|
}
|
||||||
const refs = this.refs!;
|
const refs = this.refs!;
|
||||||
// update texts/attributes/
|
// update texts/attributes/
|
||||||
if (locN !== 0) {
|
if (locN) {
|
||||||
const data1 = this.data!;
|
const data1 = this.data!;
|
||||||
const data2 = other.data!;
|
const data2 = other.data!;
|
||||||
for (let i = 0; i < locN; i++) {
|
for (let i = 0; i < locN; i++) {
|
||||||
@@ -651,14 +616,14 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update children
|
// update children
|
||||||
if (childN !== 0) {
|
if (childN) {
|
||||||
let children1 = this.children;
|
let children1 = this.children;
|
||||||
const children2 = other.children;
|
const children2 = other.children;
|
||||||
for (let i = 0; i < childN; i++) {
|
for (let i = 0; i < childN; i++) {
|
||||||
const child1 = children1![i];
|
const child1 = children1![i];
|
||||||
const child2 = children2![i];
|
const child2 = children2![i];
|
||||||
if (child1 !== undefined) {
|
if (child1) {
|
||||||
if (child2 !== undefined) {
|
if (child2) {
|
||||||
child1.patch(child2, withBeforeRemove);
|
child1.patch(child2, withBeforeRemove);
|
||||||
} else {
|
} else {
|
||||||
if (withBeforeRemove) {
|
if (withBeforeRemove) {
|
||||||
@@ -667,15 +632,10 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
child1.remove();
|
child1.remove();
|
||||||
children1![i] = undefined;
|
children1![i] = undefined;
|
||||||
}
|
}
|
||||||
} else if (child2 !== undefined) {
|
} else if (child2) {
|
||||||
let info = childrenLocs[i];
|
const loc = childrenLocs[i];
|
||||||
// decode info
|
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
|
||||||
info = info >> 1;
|
child2.mount(refs[loc.parentRefIdx] as any, afterNode);
|
||||||
const parentRefIdx = info & 0b111111111111111;
|
|
||||||
const afterRefIdx = info >> 15;
|
|
||||||
|
|
||||||
const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null;
|
|
||||||
child2.mount(refs[parentRefIdx] as any, afterNode);
|
|
||||||
children1![i] = child2;
|
children1![i] = child2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
|
|||||||
}
|
}
|
||||||
|
|
||||||
function remove(this: HTMLElement) {
|
function remove(this: HTMLElement) {
|
||||||
(this as any)[eventKey] = false;
|
delete (this as any)[eventKey];
|
||||||
this.removeEventListener(evName, listener, { capture });
|
this.removeEventListener(evName, listener, { capture });
|
||||||
}
|
}
|
||||||
function update(this: HTMLElement, data: any) {
|
function update(this: HTMLElement, data: any) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class VList {
|
|||||||
this.anchor = _anchor;
|
this.anchor = _anchor;
|
||||||
nodeInsertBefore.call(parent, _anchor, afterNode);
|
nodeInsertBefore.call(parent, _anchor, afterNode);
|
||||||
const l = children.length;
|
const l = children.length;
|
||||||
if (l !== 0) {
|
if (l) {
|
||||||
const mount = children[0].mount;
|
const mount = children[0].mount;
|
||||||
for (let i = 0; i < l; i++) {
|
for (let i = 0; i < l; i++) {
|
||||||
mount.call(children[i], parent, _anchor);
|
mount.call(children[i], parent, _anchor);
|
||||||
@@ -186,7 +186,7 @@ class VList {
|
|||||||
} else {
|
} else {
|
||||||
for (let i = startIdx1; i <= endIdx1; i++) {
|
for (let i = startIdx1; i <= endIdx1; i++) {
|
||||||
let ch = ch1[i];
|
let ch = ch1[i];
|
||||||
if (ch !== null) {
|
if (ch) {
|
||||||
if (withBeforeRemove) {
|
if (withBeforeRemove) {
|
||||||
beforeRemove.call(ch);
|
beforeRemove.call(ch);
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ class VList {
|
|||||||
beforeRemove() {
|
beforeRemove() {
|
||||||
const children = this.children;
|
const children = this.children;
|
||||||
const l = children.length;
|
const l = children.length;
|
||||||
if (l !== 0) {
|
if (l) {
|
||||||
const beforeRemove = children[0].beforeRemove;
|
const beforeRemove = children[0].beforeRemove;
|
||||||
for (let i = 0; i < l; i++) {
|
for (let i = 0; i < l; i++) {
|
||||||
beforeRemove.call(children[i]);
|
beforeRemove.call(children[i]);
|
||||||
@@ -215,7 +215,7 @@ class VList {
|
|||||||
} else {
|
} else {
|
||||||
const children = this.children;
|
const children = this.children;
|
||||||
const l = children.length;
|
const l = children.length;
|
||||||
if (l !== 0) {
|
if (l) {
|
||||||
const remove = children[0].remove;
|
const remove = children[0].remove;
|
||||||
for (let i = 0; i < l; i++) {
|
for (let i = 0; i < l; i++) {
|
||||||
remove.call(children[i]);
|
remove.call(children[i]);
|
||||||
@@ -240,7 +240,7 @@ export function list(children: VNode[]): VNode<VList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
|
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
|
||||||
const mapping: any = {};
|
let mapping: any = {};
|
||||||
for (let i = startIdx1; i <= endIdx2; i++) {
|
for (let i = startIdx1; i <= endIdx2; i++) {
|
||||||
mapping[ch1[i].key] = i;
|
mapping[ch1[i].key] = i;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class VMulti {
|
|||||||
const anchors = new Array(l);
|
const anchors = new Array(l);
|
||||||
for (let i = 0; i < l; i++) {
|
for (let i = 0; i < l; i++) {
|
||||||
let child = children[i];
|
let child = children[i];
|
||||||
if (child !== undefined) {
|
if (child) {
|
||||||
child.mount(parent, afterNode);
|
child.mount(parent, afterNode);
|
||||||
} else {
|
} else {
|
||||||
const childAnchor = document.createTextNode("");
|
const childAnchor = document.createTextNode("");
|
||||||
@@ -44,7 +44,7 @@ export class VMulti {
|
|||||||
const anchors = this.anchors;
|
const anchors = this.anchors;
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
let child = children[i];
|
let child = children[i];
|
||||||
if (child !== undefined) {
|
if (child) {
|
||||||
child.moveBeforeDOMNode(node, parent);
|
child.moveBeforeDOMNode(node, parent);
|
||||||
} else {
|
} else {
|
||||||
const anchor = anchors![i];
|
const anchor = anchors![i];
|
||||||
@@ -56,14 +56,14 @@ export class VMulti {
|
|||||||
moveBeforeVNode(other: VMulti | null, afterNode: Node | null) {
|
moveBeforeVNode(other: VMulti | null, afterNode: Node | null) {
|
||||||
if (other) {
|
if (other) {
|
||||||
const next = other!.children[0];
|
const next = other!.children[0];
|
||||||
afterNode = (next !== undefined ? next.firstNode() : other!.anchors![0]) || null;
|
afterNode = (next ? next.firstNode() : other!.anchors![0]) || null;
|
||||||
}
|
}
|
||||||
const children = this.children;
|
const children = this.children;
|
||||||
const parent = this.parentEl;
|
const parent = this.parentEl;
|
||||||
const anchors = this.anchors;
|
const anchors = this.anchors;
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
let child = children[i];
|
let child = children[i];
|
||||||
if (child !== undefined) {
|
if (child) {
|
||||||
child.moveBeforeVNode(null, afterNode);
|
child.moveBeforeVNode(null, afterNode);
|
||||||
} else {
|
} else {
|
||||||
const anchor = anchors![i];
|
const anchor = anchors![i];
|
||||||
@@ -83,8 +83,8 @@ export class VMulti {
|
|||||||
for (let i = 0, l = children1.length; i < l; i++) {
|
for (let i = 0, l = children1.length; i < l; i++) {
|
||||||
const vn1 = children1[i];
|
const vn1 = children1[i];
|
||||||
const vn2 = children2[i];
|
const vn2 = children2[i];
|
||||||
if (vn1 !== undefined) {
|
if (vn1) {
|
||||||
if (vn2 !== undefined) {
|
if (vn2) {
|
||||||
vn1.patch(vn2, withBeforeRemove);
|
vn1.patch(vn2, withBeforeRemove);
|
||||||
} else {
|
} else {
|
||||||
const afterNode = vn1.firstNode()!;
|
const afterNode = vn1.firstNode()!;
|
||||||
@@ -97,7 +97,7 @@ export class VMulti {
|
|||||||
vn1.remove();
|
vn1.remove();
|
||||||
children1[i] = undefined;
|
children1[i] = undefined;
|
||||||
}
|
}
|
||||||
} else if (vn2 !== undefined) {
|
} else if (vn2) {
|
||||||
children1[i] = vn2;
|
children1[i] = vn2;
|
||||||
const anchor = anchors[i];
|
const anchor = anchors[i];
|
||||||
vn2.mount(parentEl, anchor);
|
vn2.mount(parentEl, anchor);
|
||||||
@@ -110,7 +110,7 @@ export class VMulti {
|
|||||||
const children = this.children;
|
const children = this.children;
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
const child = children[i];
|
const child = children[i];
|
||||||
if (child !== undefined) {
|
if (child) {
|
||||||
child.beforeRemove();
|
child.beforeRemove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ export class VMulti {
|
|||||||
const anchors = this.anchors;
|
const anchors = this.anchors;
|
||||||
for (let i = 0, l = children.length; i < l; i++) {
|
for (let i = 0, l = children.length; i < l; i++) {
|
||||||
const child = children[i];
|
const child = children[i];
|
||||||
if (child !== undefined) {
|
if (child) {
|
||||||
child.remove();
|
child.remove();
|
||||||
} else {
|
} else {
|
||||||
nodeRemoveChild.call(parentEl, anchors![i]);
|
nodeRemoveChild.call(parentEl, anchors![i]);
|
||||||
@@ -136,7 +136,7 @@ export class VMulti {
|
|||||||
|
|
||||||
firstNode(): Node | undefined {
|
firstNode(): Node | undefined {
|
||||||
const child = this.children[0];
|
const child = this.children[0];
|
||||||
return child !== undefined ? child.firstNode() : this.anchors![0];
|
return child ? child.firstNode() : this.anchors![0];
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
}
|
}
|
||||||
|
|
||||||
async render(deep: boolean) {
|
async render(deep: boolean) {
|
||||||
if (this.status >= STATUS.CANCELLED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && (current.root!.locked || (current as any).bdom === true)) {
|
if (current && (current.root!.locked || (current as any).bdom === true)) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -174,7 +171,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
|
|
||||||
this.app.scheduler.addFiber(fiber);
|
this.app.scheduler.addFiber(fiber);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
if (this.status >= STATUS.CANCELLED) {
|
if (this.status === STATUS.DESTROYED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We only want to actually render the component if the following two
|
// We only want to actually render the component if the following two
|
||||||
@@ -193,20 +190,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
|
||||||
this._cancel();
|
|
||||||
delete this.parent!.children[this.parentKey!];
|
|
||||||
this.app.scheduler.scheduleDestroy(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_cancel() {
|
|
||||||
this.status = STATUS.CANCELLED;
|
|
||||||
const children = this.children;
|
|
||||||
for (let childKey in children) {
|
|
||||||
children[childKey]._cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
let shouldRemove = this.status === STATUS.MOUNTED;
|
let shouldRemove = this.status === STATUS.MOUNTED;
|
||||||
this._destroy();
|
this._destroy();
|
||||||
|
|||||||
@@ -51,19 +51,17 @@ export function handleError(params: ErrorParams) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
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!;
|
||||||
|
|
||||||
if (fiber) {
|
// resets the fibers on components if possible. This is important so that
|
||||||
// resets the fibers on components if possible. This is important so that
|
// new renderings can be properly included in the initial one, if any.
|
||||||
// new renderings can be properly included in the initial one, if any.
|
let current: Fiber | null = fiber;
|
||||||
let current: Fiber | null = fiber;
|
do {
|
||||||
do {
|
current.node.fiber = current;
|
||||||
current.node.fiber = current;
|
current = current.parent;
|
||||||
current = current.parent;
|
} while (current);
|
||||||
} while (current);
|
|
||||||
|
|
||||||
fibersInError.set(fiber.root!, error);
|
fibersInError.set(fiber.root!, error);
|
||||||
}
|
|
||||||
|
|
||||||
const handled = _handleError(node, error);
|
const handled = _handleError(node, error);
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ function cancelFibers(fibers: Fiber[]): number {
|
|||||||
let node = fiber.node;
|
let node = fiber.node;
|
||||||
fiber.render = throwOnRender;
|
fiber.render = throwOnRender;
|
||||||
if (node.status === STATUS.NEW) {
|
if (node.status === STATUS.NEW) {
|
||||||
node.cancel();
|
node.destroy();
|
||||||
|
delete node.parent!.children[node.parentKey!];
|
||||||
}
|
}
|
||||||
node.fiber = null;
|
node.fiber = null;
|
||||||
if (fiber.bdom) {
|
if (fiber.bdom) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { ComponentNode } from "./component_node";
|
|
||||||
import { fibersInError } from "./error_handling";
|
import { fibersInError } from "./error_handling";
|
||||||
import { Fiber, RootFiber } from "./fibers";
|
import { Fiber, RootFiber } from "./fibers";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
@@ -15,7 +14,6 @@ export class Scheduler {
|
|||||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||||
frame: number = 0;
|
frame: number = 0;
|
||||||
delayedRenders: Fiber[] = [];
|
delayedRenders: Fiber[] = [];
|
||||||
cancelledNodes: Set<ComponentNode> = new Set();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||||
@@ -25,13 +23,6 @@ export class Scheduler {
|
|||||||
this.tasks.add(fiber.root!);
|
this.tasks.add(fiber.root!);
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleDestroy(node: ComponentNode) {
|
|
||||||
this.cancelledNodes.add(node);
|
|
||||||
if (this.frame === 0) {
|
|
||||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process all current tasks. This only applies to the fibers that are ready.
|
* Process all current tasks. This only applies to the fibers that are ready.
|
||||||
* Other tasks are left unchanged.
|
* Other tasks are left unchanged.
|
||||||
@@ -48,23 +39,15 @@ export class Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.frame === 0) {
|
if (this.frame === 0) {
|
||||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
this.frame = this.requestAnimationFrame(() => {
|
||||||
}
|
this.frame = 0;
|
||||||
}
|
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||||
|
for (let task of this.tasks) {
|
||||||
processTasks() {
|
if (task.node.status === STATUS.DESTROYED) {
|
||||||
this.frame = 0;
|
this.tasks.delete(task);
|
||||||
for (let node of this.cancelledNodes) {
|
}
|
||||||
node._destroy();
|
}
|
||||||
}
|
});
|
||||||
this.cancelledNodes.clear();
|
|
||||||
for (let task of this.tasks) {
|
|
||||||
this.processFiber(task);
|
|
||||||
}
|
|
||||||
for (let task of this.tasks) {
|
|
||||||
if (task.node.status === STATUS.DESTROYED) {
|
|
||||||
this.tasks.delete(task);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,15 @@ import type { Component } from "./component";
|
|||||||
export const enum STATUS {
|
export const enum STATUS {
|
||||||
NEW,
|
NEW,
|
||||||
MOUNTED, // is ready, and in DOM. It has a valid el
|
MOUNTED, // is ready, and in DOM. It has a valid el
|
||||||
// component has been created, but has been replaced by a newer component before being mounted
|
|
||||||
// it is cancelled until the next animation frame where it will be destroyed
|
|
||||||
CANCELLED,
|
|
||||||
DESTROYED,
|
DESTROYED,
|
||||||
}
|
}
|
||||||
|
|
||||||
type STATUS_DESCR = "new" | "mounted" | "cancelled" | "destroyed";
|
type STATUS_DESCR = "new" | "mounted" | "destroyed";
|
||||||
|
|
||||||
export function status(component: Component): STATUS_DESCR {
|
export function status(component: Component): STATUS_DESCR {
|
||||||
switch (component.__owl__.status) {
|
switch (component.__owl__.status) {
|
||||||
case STATUS.NEW:
|
case STATUS.NEW:
|
||||||
return "new";
|
return "new";
|
||||||
case STATUS.CANCELLED:
|
|
||||||
return "cancelled";
|
|
||||||
case STATUS.MOUNTED:
|
case STATUS.MOUNTED:
|
||||||
return "mounted";
|
return "mounted";
|
||||||
case STATUS.DESTROYED:
|
case STATUS.DESTROYED:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function callSlot(
|
|||||||
const slots = ctx.props.slots || {};
|
const slots = ctx.props.slots || {};
|
||||||
const { __render, __ctx, __scope } = slots[name] || {};
|
const { __render, __ctx, __scope } = slots[name] || {};
|
||||||
const slotScope = ObjectCreate(__ctx || {});
|
const slotScope = ObjectCreate(__ctx || {});
|
||||||
if (__scope !== undefined) {
|
if (__scope) {
|
||||||
slotScope[__scope] = extra;
|
slotScope[__scope] = extra;
|
||||||
}
|
}
|
||||||
const slotBDom = __render ? __render(slotScope, parent, key) : null;
|
const slotBDom = __render ? __render(slotScope, parent, key) : null;
|
||||||
|
|||||||
+14
-7
@@ -9,13 +9,20 @@ export type Callback = () => void;
|
|||||||
* @returns a batched version of the original callback
|
* @returns a batched version of the original callback
|
||||||
*/
|
*/
|
||||||
export function batched(callback: Callback): Callback {
|
export function batched(callback: Callback): Callback {
|
||||||
let scheduled = false;
|
let called = false;
|
||||||
return async (...args) => {
|
return async () => {
|
||||||
if (scheduled === false) {
|
// This await blocks all calls to the callback here, then releases them sequentially
|
||||||
scheduled = true;
|
// in the next microtick. This line decides the granularity of the batch.
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
scheduled = false;
|
if (!called) {
|
||||||
callback(...args);
|
called = true;
|
||||||
|
// wait for all calls in this microtick to fall through before resetting "called"
|
||||||
|
// so that only the first call to the batched function calls the original callback.
|
||||||
|
// Schedule this before calling the callback so that calls to the batched function
|
||||||
|
// within the callback will proceed only after resetting called to false, and have
|
||||||
|
// a chance to execute the callback again
|
||||||
|
Promise.resolve().then(() => (called = false));
|
||||||
|
callback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1991,8 +1991,8 @@ describe("qweb parser", () => {
|
|||||||
baseExpr: "state",
|
baseExpr: "state",
|
||||||
expr: "'stuff'",
|
expr: "'stuff'",
|
||||||
eventType: "click",
|
eventType: "click",
|
||||||
shouldNumberize: true,
|
shouldNumberize: false,
|
||||||
shouldTrim: true,
|
shouldTrim: false,
|
||||||
targetAttr: "value",
|
targetAttr: "value",
|
||||||
hasDynamicChildren: false,
|
hasDynamicChildren: false,
|
||||||
specialInitTargetAttr: "checked",
|
specialInitTargetAttr: "checked",
|
||||||
|
|||||||
@@ -212,73 +212,6 @@ exports[`changing state before first render does not trigger a render 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`component destroyed just after render 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return comp1({}, key + \`__1\`, node, this, null);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`component destroyed just after render 2`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`B\`);
|
|
||||||
const b3 = text(ctx['state'].value);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components are not destroyed between animation frame 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2,b3;
|
|
||||||
b2 = text(\`A\`);
|
|
||||||
if (ctx['state'].flag) {
|
|
||||||
b3 = comp1({}, key + \`__1\`, node, this, null);
|
|
||||||
}
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components are not destroyed between animation frame 2`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
const comp1 = app.createComponent(\`C\`, true, false, false, []);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const b2 = text(\`B\`);
|
|
||||||
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
|
||||||
return multi([b2, b3]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components are not destroyed between animation frame 3`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return text(\`C\`);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`concurrent renderings scenario 1 1`] = `
|
exports[`concurrent renderings scenario 1 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -468,36 +468,6 @@ exports[`t-model directive t-model on select with static options 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`t-model directive t-model with dynamic number values on select options in foreach 1`] = `
|
|
||||||
"function anonymous(app, bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
|
||||||
let { toNumber, prepareList, withKey } = helpers;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<select block-handler-0=\\"change\\"><block-child-0/></select>\`);
|
|
||||||
let block3 = createBlock(\`<option block-attribute-0=\\"value\\" block-attribute-1=\\"selected\\"><block-text-2/></option>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
const bExpr1 = ctx['state'];
|
|
||||||
const expr1 = 'value';
|
|
||||||
const bValue1 = bExpr1[expr1];
|
|
||||||
let hdlr1 = [(ev) => { bExpr1[expr1] = toNumber(ev.target.value); }];
|
|
||||||
ctx = Object.create(ctx);
|
|
||||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['state'].options);;
|
|
||||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
|
||||||
ctx[\`o\`] = v_block2[i1];
|
|
||||||
const key1 = ctx['o'].value;
|
|
||||||
let attr1 = ctx['o'].value;
|
|
||||||
let attr2 = bValue1 === ctx['o'].value;
|
|
||||||
let txt1 = ctx['o'].value;
|
|
||||||
c_block2[i1] = withKey(block3([attr1, attr2, txt1]), key1);
|
|
||||||
}
|
|
||||||
const b2 = list(c_block2);
|
|
||||||
return block1([hdlr1], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`t-model directive t-model with dynamic values on select options -- 2 1`] = `
|
exports[`t-model directive t-model with dynamic values on select options -- 2 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -115,7 +115,13 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
|||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(n).toBe(2);
|
expect(n).toBe(2);
|
||||||
|
|
||||||
expect(["W:willRender", "Child:setup", "Child:willStart", "W:rendered"]).toBeLogged();
|
expect([
|
||||||
|
"Child:willDestroy",
|
||||||
|
"W:willRender",
|
||||||
|
"Child:setup",
|
||||||
|
"Child:willStart",
|
||||||
|
"W:rendered",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
def.resolve();
|
def.resolve();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -124,7 +130,6 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
|||||||
expect([
|
expect([
|
||||||
"Child:willRender",
|
"Child:willRender",
|
||||||
"Child:rendered",
|
"Child:rendered",
|
||||||
"Child:willDestroy",
|
|
||||||
"W:willPatch",
|
"W:willPatch",
|
||||||
"Child:mounted",
|
"Child:mounted",
|
||||||
"W:patched",
|
"W:patched",
|
||||||
@@ -173,13 +178,13 @@ test("destroying/recreating a subcomponent, other scenario", async () => {
|
|||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
|
"Child:willDestroy",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
"Child:willRender",
|
"Child:willRender",
|
||||||
"Child:rendered",
|
"Child:rendered",
|
||||||
"Child:willDestroy",
|
|
||||||
"Parent:willPatch",
|
"Parent:willPatch",
|
||||||
"Child:mounted",
|
"Child:mounted",
|
||||||
"Parent:patched",
|
"Parent:patched",
|
||||||
@@ -246,13 +251,13 @@ test("creating two async components, scenario 1", async () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
expect([
|
expect([
|
||||||
|
"ChildA:willDestroy",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"ChildA:setup",
|
"ChildA:setup",
|
||||||
"ChildA:willStart",
|
"ChildA:willStart",
|
||||||
"ChildB:setup",
|
"ChildB:setup",
|
||||||
"ChildB:willStart",
|
"ChildB:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
"ChildA:willDestroy",
|
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
defB.resolve();
|
defB.resolve();
|
||||||
@@ -499,6 +504,48 @@ test("update a sub-component twice in the same frame", async () => {
|
|||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.only("abcde", async () => {
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<span t-esc="state.x"/>`;
|
||||||
|
state = useState({ x: 1 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _doIt() {
|
||||||
|
await Promise.resolve();
|
||||||
|
this.state.x++;
|
||||||
|
}
|
||||||
|
|
||||||
|
async doIt() {
|
||||||
|
await this._doIt();
|
||||||
|
this.state.x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect([
|
||||||
|
"Parent:setup",
|
||||||
|
"Parent:willStart",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("<span>1</span>");
|
||||||
|
|
||||||
|
parent.doIt();
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Parent:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("<span>3</span>");
|
||||||
|
});
|
||||||
|
|
||||||
test("update a sub-component twice in the same frame, 2", async () => {
|
test("update a sub-component twice in the same frame, 2", async () => {
|
||||||
class ChildA extends Component {
|
class ChildA extends Component {
|
||||||
static template = xml`<span><t t-esc="val()"/></span>`;
|
static template = xml`<span><t t-esc="val()"/></span>`;
|
||||||
@@ -698,13 +745,13 @@ test("rendering component again in next microtick", async () => {
|
|||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
|
"Child:willDestroy",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
"Child:willRender",
|
"Child:willRender",
|
||||||
"Child:rendered",
|
"Child:rendered",
|
||||||
"Child:willDestroy",
|
|
||||||
"Parent:willPatch",
|
"Parent:willPatch",
|
||||||
"Child:mounted",
|
"Child:mounted",
|
||||||
"Parent:patched",
|
"Parent:patched",
|
||||||
@@ -1727,9 +1774,9 @@ test("concurrent renderings scenario 10", async () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><p></p></div>");
|
expect(fixture.innerHTML).toBe("<div><p></p></div>");
|
||||||
expect([
|
expect([
|
||||||
"ComponentA:willRender",
|
"ComponentA:willRender",
|
||||||
|
"ComponentC:willDestroy",
|
||||||
"ComponentB:willUpdateProps",
|
"ComponentB:willUpdateProps",
|
||||||
"ComponentA:rendered",
|
"ComponentA:rendered",
|
||||||
"ComponentC:willDestroy",
|
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
defB.resolve();
|
defB.resolve();
|
||||||
@@ -2277,6 +2324,7 @@ test("concurrent renderings scenario 16", async () => {
|
|||||||
"D:setup",
|
"D:setup",
|
||||||
"D:willStart",
|
"D:willStart",
|
||||||
"C:rendered",
|
"C:rendered",
|
||||||
|
"D:willDestroy",
|
||||||
"B:willRender",
|
"B:willRender",
|
||||||
"C:willUpdateProps",
|
"C:willUpdateProps",
|
||||||
"B:rendered",
|
"B:rendered",
|
||||||
@@ -2284,7 +2332,6 @@ test("concurrent renderings scenario 16", async () => {
|
|||||||
"D:setup",
|
"D:setup",
|
||||||
"D:willStart",
|
"D:willStart",
|
||||||
"C:rendered",
|
"C:rendered",
|
||||||
"D:willDestroy",
|
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
// at this point, C rendering is still pending, and nothing should have been
|
// at this point, C rendering is still pending, and nothing should have been
|
||||||
@@ -2992,11 +3039,11 @@ test("t-key on dom node having a component", async () => {
|
|||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||||
expect([
|
expect([
|
||||||
|
"Child (2):willDestroy",
|
||||||
"Child (3):setup",
|
"Child (3):setup",
|
||||||
"Child (3):willStart",
|
"Child (3):willStart",
|
||||||
"Child (3):willRender",
|
"Child (3):willRender",
|
||||||
"Child (3):rendered",
|
"Child (3):rendered",
|
||||||
"Child (2):willDestroy",
|
|
||||||
"Child (1):willUnmount",
|
"Child (1):willUnmount",
|
||||||
"Child (1):willDestroy",
|
"Child (1):willDestroy",
|
||||||
"Child (3):mounted",
|
"Child (3):mounted",
|
||||||
@@ -3050,11 +3097,11 @@ test("t-key on dynamic async component (toggler is never patched)", async () =>
|
|||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||||
expect([
|
expect([
|
||||||
|
"Child (2):willDestroy",
|
||||||
"Child (3):setup",
|
"Child (3):setup",
|
||||||
"Child (3):willStart",
|
"Child (3):willStart",
|
||||||
"Child (3):willRender",
|
"Child (3):willRender",
|
||||||
"Child (3):rendered",
|
"Child (3):rendered",
|
||||||
"Child (2):willDestroy",
|
|
||||||
"Child (1):willUnmount",
|
"Child (1):willUnmount",
|
||||||
"Child (1):willDestroy",
|
"Child (1):willDestroy",
|
||||||
"Child (3):mounted",
|
"Child (3):mounted",
|
||||||
@@ -3109,11 +3156,11 @@ test("t-foreach with dynamic async component", async () => {
|
|||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||||
expect([
|
expect([
|
||||||
|
"Child (2):willDestroy",
|
||||||
"Child (3):setup",
|
"Child (3):setup",
|
||||||
"Child (3):willStart",
|
"Child (3):willStart",
|
||||||
"Child (3):willRender",
|
"Child (3):willRender",
|
||||||
"Child (3):rendered",
|
"Child (3):rendered",
|
||||||
"Child (2):willDestroy",
|
|
||||||
"Child (1):willUnmount",
|
"Child (1):willUnmount",
|
||||||
"Child (1):willDestroy",
|
"Child (1):willDestroy",
|
||||||
"Child (3):mounted",
|
"Child (3):mounted",
|
||||||
@@ -3796,7 +3843,7 @@ test("destroyed component causes other soon to be destroyed component to rerende
|
|||||||
static template = xml`<t t-esc="state.val + props.value"/>`;
|
static template = xml`<t t-esc="state.val + props.value"/>`;
|
||||||
state = useState({ val: 0 });
|
state = useState({ val: 0 });
|
||||||
setup() {
|
setup() {
|
||||||
c = c || this;
|
c = this;
|
||||||
useLogLifecycle();
|
useLogLifecycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3841,6 +3888,8 @@ test("destroyed component causes other soon to be destroyed component to rerende
|
|||||||
parent.state.valueB = 2;
|
parent.state.valueB = 2;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect([
|
expect([
|
||||||
|
"B:willDestroy",
|
||||||
|
"C:willDestroy",
|
||||||
"A:willRender",
|
"A:willRender",
|
||||||
"B:setup",
|
"B:setup",
|
||||||
"B:willStart",
|
"B:willStart",
|
||||||
@@ -3851,8 +3900,6 @@ test("destroyed component causes other soon to be destroyed component to rerende
|
|||||||
"B:rendered",
|
"B:rendered",
|
||||||
"C:willRender",
|
"C:willRender",
|
||||||
"C:rendered",
|
"C:rendered",
|
||||||
"B:willDestroy",
|
|
||||||
"C:willDestroy",
|
|
||||||
"A:willPatch",
|
"A:willPatch",
|
||||||
"C:mounted",
|
"C:mounted",
|
||||||
"B:mounted",
|
"B:mounted",
|
||||||
@@ -4195,116 +4242,6 @@ test("delayed render is not cancelled by upcoming render", async () => {
|
|||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("components are not destroyed between animation frame", async () => {
|
|
||||||
const def = makeDeferred();
|
|
||||||
class C extends Component {
|
|
||||||
static template = xml`C`;
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class B extends Component {
|
|
||||||
static template = xml`B<C/>`;
|
|
||||||
static components = { C };
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
onWillStart(() => {
|
|
||||||
return def;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class A extends Component {
|
|
||||||
static template = xml`A<B t-if="state.flag"/>`;
|
|
||||||
static components = { B };
|
|
||||||
|
|
||||||
state = useState({ flag: false });
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const a = await mount(A, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("A");
|
|
||||||
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged();
|
|
||||||
|
|
||||||
// turn the flag on, this will render A and stops at B because of def
|
|
||||||
a.state.flag = true;
|
|
||||||
await nextTick();
|
|
||||||
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged();
|
|
||||||
|
|
||||||
// force a render of A
|
|
||||||
// => owl will need to create a new B component
|
|
||||||
// => initial B component will be cancelled
|
|
||||||
a.render();
|
|
||||||
await nextMicroTick();
|
|
||||||
expect([
|
|
||||||
// note that B is not destroyed here. It is cancelled instead
|
|
||||||
"A:willRender",
|
|
||||||
"B:setup",
|
|
||||||
"B:willStart",
|
|
||||||
"A:rendered",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
// resolve def, so B render is unblocked
|
|
||||||
def.resolve();
|
|
||||||
await nextTick();
|
|
||||||
expect([
|
|
||||||
"B:willRender",
|
|
||||||
"C:setup",
|
|
||||||
"C:willStart",
|
|
||||||
"B:rendered",
|
|
||||||
"C:willRender",
|
|
||||||
"C:rendered",
|
|
||||||
// animation frame callback starts here
|
|
||||||
"B:willDestroy", // B is destroyed here
|
|
||||||
"A:willPatch",
|
|
||||||
"C:mounted",
|
|
||||||
"B:mounted",
|
|
||||||
"A:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("component destroyed just after render", async () => {
|
|
||||||
let stateB: any;
|
|
||||||
|
|
||||||
class B extends Component {
|
|
||||||
static template = xml`B<t t-esc="state.value"/>`;
|
|
||||||
state = useState({ value: 1 });
|
|
||||||
setup() {
|
|
||||||
stateB = this.state;
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class A extends Component {
|
|
||||||
static template = xml`<B/>`;
|
|
||||||
static components = { B };
|
|
||||||
setup() {
|
|
||||||
useLogLifecycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const a = await mount(A, fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("B1");
|
|
||||||
expect([
|
|
||||||
"A:setup",
|
|
||||||
"A:willStart",
|
|
||||||
"A:willRender",
|
|
||||||
"B:setup",
|
|
||||||
"B:willStart",
|
|
||||||
"A:rendered",
|
|
||||||
"B:willRender",
|
|
||||||
"B:rendered",
|
|
||||||
"B:mounted",
|
|
||||||
"A:mounted",
|
|
||||||
]).toBeLogged();
|
|
||||||
|
|
||||||
stateB!.value++; // force a render of B
|
|
||||||
await nextMicroTick(); // wait for B render to actually start
|
|
||||||
a.__owl__.app.destroy();
|
|
||||||
expect(["A:willUnmount", "B:willUnmount", "B:willDestroy", "A:willDestroy"]).toBeLogged();
|
|
||||||
await nextTick();
|
|
||||||
// check that B was not rendered after being destroyed
|
|
||||||
expect([]).toBeLogged();
|
|
||||||
});
|
|
||||||
|
|
||||||
// test.skip("components with shouldUpdate=false", async () => {
|
// test.skip("components with shouldUpdate=false", async () => {
|
||||||
// const state = { p: 1, cc: 10 };
|
// const state = { p: 1, cc: 10 };
|
||||||
|
|
||||||
|
|||||||
@@ -1444,7 +1444,6 @@ describe("can catch errors", () => {
|
|||||||
"Parent:willPatch",
|
"Parent:willPatch",
|
||||||
"Child:willUnmount",
|
"Child:willUnmount",
|
||||||
"Child:willDestroy",
|
"Child:willDestroy",
|
||||||
"Parent:patched",
|
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
"Parent:willPatch",
|
"Parent:willPatch",
|
||||||
@@ -1507,15 +1506,12 @@ describe("can catch errors", () => {
|
|||||||
parent.state.hasChild = false;
|
parent.state.hasChild = false;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect([
|
expect([
|
||||||
"Parent:willRender",
|
|
||||||
"Parent:rendered",
|
|
||||||
"Child:willDestroy",
|
"Child:willDestroy",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Parent:patched",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
expect(fixture.innerHTML).toBe("1");
|
|
||||||
await nextTick();
|
|
||||||
expect(["Parent:willPatch", "Parent:patched"]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("2");
|
expect(fixture.innerHTML).toBe("2");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -626,39 +626,6 @@ describe("t-model directive", () => {
|
|||||||
expect(fixture.querySelector("select")!.value).toEqual("b");
|
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-model with dynamic number values on select options in foreach", async () => {
|
|
||||||
class Test extends Component {
|
|
||||||
static template = xml`
|
|
||||||
<select t-model.number="state.value">
|
|
||||||
<t t-foreach="state.options" t-as="o" t-key="o.value">
|
|
||||||
<option t-att-value="o.value" t-esc="o.value"/>
|
|
||||||
</t>
|
|
||||||
</select>
|
|
||||||
`;
|
|
||||||
state: any;
|
|
||||||
setup() {
|
|
||||||
this.state = useState({
|
|
||||||
value: 2,
|
|
||||||
options: [{ value: 1 }, { value: 2 }, { value: 3 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const comp = await mount(Test, fixture);
|
|
||||||
// check that we have a value of 2 selected
|
|
||||||
expect(fixture.querySelector("select")!.value).toEqual("2");
|
|
||||||
expect(comp.state.value).toBe(2);
|
|
||||||
|
|
||||||
// emulate a click on the option=3 element
|
|
||||||
fixture.querySelectorAll("option")[2].selected = true;
|
|
||||||
fixture.querySelector("select")!.dispatchEvent(new Event("change"));
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
// check that we have now selected the number 3 (and not the string)
|
|
||||||
expect(fixture.querySelector("select")!.value).toEqual("3");
|
|
||||||
expect(comp.state.value).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("t-model is applied before t-on-input", async () => {
|
test("t-model is applied before t-on-input", async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
|
|||||||
@@ -1519,9 +1519,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If nothing was found, return the path of the first root component found in the apps
|
// If nothing was found, return the first app's root component path
|
||||||
const appIndex = [...this.apps].findIndex((app) => app.root);
|
return ["0", "root"];
|
||||||
return [appIndex.toString(), "root"];
|
|
||||||
}
|
}
|
||||||
// Returns the tree of components of the inspected page in a parsed format
|
// Returns the tree of components of the inspected page in a parsed format
|
||||||
// Use inspectedPath to specify the path of the selected component
|
// Use inspectedPath to specify the path of the selected component
|
||||||
|
|||||||
Reference in New Issue
Block a user