mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1179e84971 | |||
| 24b1ea7604 | |||
| 3e4ebb6378 | |||
| 859748aed9 | |||
| fd13277e1d | |||
| 7fb166bd50 | |||
| decf42c742 | |||
| 989b0d6709 | |||
| 9b93521da4 | |||
| de240b1ebc |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-beta-4",
|
"version": "2.0.0-beta-5",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"browser": "dist/owl.iife.js",
|
"browser": "dist/owl.iife.js",
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ export class App<
|
|||||||
}
|
}
|
||||||
|
|
||||||
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||||
return new ComponentNode(Component, props, this);
|
return new ComponentNode(Component, props, this, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
|
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
|
||||||
|
|||||||
@@ -1115,8 +1115,11 @@ export class CodeGenerator {
|
|||||||
let slotStr: string[] = [];
|
let slotStr: string[] = [];
|
||||||
for (let slotName in ast.slots) {
|
for (let slotName in ast.slots) {
|
||||||
const slotAst = ast.slots[slotName];
|
const slotAst = ast.slots[slotName];
|
||||||
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
const params = [];
|
||||||
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
|
if (slotAst.content) {
|
||||||
|
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
||||||
|
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
||||||
|
}
|
||||||
const scope = ast.slots[slotName].scope;
|
const scope = ast.slots[slotName].scope;
|
||||||
if (scope) {
|
if (scope) {
|
||||||
params.push(`__scope: "${scope}"`);
|
params.push(`__scope: "${scope}"`);
|
||||||
@@ -1231,7 +1234,7 @@ export class CodeGenerator {
|
|||||||
if (dynamic) {
|
if (dynamic) {
|
||||||
let name = this.generateId("slot");
|
let name = this.generateId("slot");
|
||||||
this.define(name, slotName);
|
this.define(name, slotName);
|
||||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
|
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
||||||
} else {
|
} else {
|
||||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-19
@@ -118,7 +118,7 @@ export interface ASTTCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SlotDefinition {
|
interface SlotDefinition {
|
||||||
content: AST;
|
content: AST | null;
|
||||||
scope: string | null;
|
scope: string | null;
|
||||||
on: EventHandlers | null;
|
on: EventHandlers | null;
|
||||||
attrs: Attrs | null;
|
attrs: Attrs | null;
|
||||||
@@ -749,26 +749,24 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
slotNode.removeAttribute("t-set-slot");
|
slotNode.removeAttribute("t-set-slot");
|
||||||
slotNode.remove();
|
slotNode.remove();
|
||||||
const slotAst = parseNode(slotNode, ctx);
|
const slotAst = parseNode(slotNode, ctx);
|
||||||
if (slotAst) {
|
let on: SlotDefinition["on"] = null;
|
||||||
let on: SlotDefinition["on"] = null;
|
let attrs: Attrs | null = null;
|
||||||
let attrs: Attrs | null = null;
|
let scope: string | null = null;
|
||||||
let scope: string | null = null;
|
for (let attributeName of slotNode.getAttributeNames()) {
|
||||||
for (let attributeName of slotNode.getAttributeNames()) {
|
const value = slotNode.getAttribute(attributeName)!;
|
||||||
const value = slotNode.getAttribute(attributeName)!;
|
if (attributeName === "t-slot-scope") {
|
||||||
if (attributeName === "t-slot-scope") {
|
scope = value;
|
||||||
scope = value;
|
continue;
|
||||||
continue;
|
} else if (attributeName.startsWith("t-on-")) {
|
||||||
} else if (attributeName.startsWith("t-on-")) {
|
on = on || {};
|
||||||
on = on || {};
|
on[attributeName.slice(5)] = value;
|
||||||
on[attributeName.slice(5)] = value;
|
} else {
|
||||||
} else {
|
attrs = attrs || {};
|
||||||
attrs = attrs || {};
|
attrs[attributeName] = value;
|
||||||
attrs[attributeName] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
slots = slots || {};
|
|
||||||
slots[name] = { content: slotAst, on, attrs, scope };
|
|
||||||
}
|
}
|
||||||
|
slots = slots || {};
|
||||||
|
slots[name] = { content: slotAst, on, attrs, scope };
|
||||||
}
|
}
|
||||||
|
|
||||||
// default slot
|
// default slot
|
||||||
|
|||||||
@@ -79,13 +79,8 @@ export function component<P extends object>(
|
|||||||
let node: any = ctx.children[key];
|
let node: any = ctx.children[key];
|
||||||
let isDynamic = typeof name !== "string";
|
let isDynamic = typeof name !== "string";
|
||||||
|
|
||||||
if (node) {
|
if (node && node.status === STATUS.DESTROYED) {
|
||||||
if (node.status < STATUS.MOUNTED) {
|
node = undefined;
|
||||||
node.destroy();
|
|
||||||
node = undefined;
|
|
||||||
} else if (node.status === STATUS.DESTROYED) {
|
|
||||||
node = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isDynamic && node && node.component.constructor !== name) {
|
if (isDynamic && node && node.component.constructor !== name) {
|
||||||
node = undefined;
|
node = undefined;
|
||||||
@@ -114,12 +109,11 @@ export function component<P extends object>(
|
|||||||
throw new Error(`Cannot find the definition of component "${name}"`);
|
throw new Error(`Cannot find the definition of component "${name}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node = new ComponentNode(C, props, ctx.app, ctx);
|
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
||||||
ctx.children[key] = node;
|
ctx.children[key] = node;
|
||||||
|
|
||||||
node.initiateRender(new Fiber(node, parentFiber));
|
node.initiateRender(new Fiber(node, parentFiber));
|
||||||
}
|
}
|
||||||
parentFiber.root!.reachedChildren.add(node);
|
parentFiber.childrenMap[key] = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +131,7 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
bdom: BDom | null = null;
|
bdom: BDom | null = null;
|
||||||
status: STATUS = STATUS.NEW;
|
status: STATUS = STATUS.NEW;
|
||||||
forceNextRender: boolean = false;
|
forceNextRender: boolean = false;
|
||||||
|
parentKey: string | null;
|
||||||
|
|
||||||
renderFn: Function;
|
renderFn: Function;
|
||||||
parent: ComponentNode | null;
|
parent: ComponentNode | null;
|
||||||
@@ -153,10 +148,17 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
patched: LifecycleHook[] = [];
|
patched: LifecycleHook[] = [];
|
||||||
willDestroy: LifecycleHook[] = [];
|
willDestroy: LifecycleHook[] = [];
|
||||||
|
|
||||||
constructor(C: ComponentConstructor<P, E>, props: P, app: App, parent?: ComponentNode) {
|
constructor(
|
||||||
|
C: ComponentConstructor<P, E>,
|
||||||
|
props: P,
|
||||||
|
app: App,
|
||||||
|
parent: ComponentNode | null,
|
||||||
|
parentKey: string | null
|
||||||
|
) {
|
||||||
currentNode = this;
|
currentNode = this;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.parent = parent || null;
|
this.parent = parent;
|
||||||
|
this.parentKey = parentKey;
|
||||||
this.level = parent ? parent.level + 1 : 0;
|
this.level = parent ? parent.level + 1 : 0;
|
||||||
applyDefaultProps(props, C);
|
applyDefaultProps(props, C);
|
||||||
const env = (parent && parent.childEnv) || app.env;
|
const env = (parent && parent.childEnv) || app.env;
|
||||||
@@ -193,7 +195,7 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
|
|
||||||
async render(deep: boolean = false) {
|
async render(deep: boolean = false) {
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && current.root!.locked) {
|
if (current && (current.root!.locked || (current as any).bdom === true)) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
// situation may have changed after the microtask tick
|
// situation may have changed after the microtask tick
|
||||||
current = this.fiber;
|
current = this.fiber;
|
||||||
@@ -215,6 +217,7 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
const fiber = makeRootFiber(this);
|
const fiber = makeRootFiber(this);
|
||||||
fiber.deep = deep;
|
fiber.deep = deep;
|
||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
|
|
||||||
this.app.scheduler.addFiber(fiber);
|
this.app.scheduler.addFiber(fiber);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
if (this.status === STATUS.DESTROYED) {
|
if (this.status === STATUS.DESTROYED) {
|
||||||
@@ -254,8 +257,14 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
for (let child of Object.values(this.children)) {
|
for (let child of Object.values(this.children)) {
|
||||||
child._destroy();
|
child._destroy();
|
||||||
}
|
}
|
||||||
for (let cb of this.willDestroy) {
|
if (this.willDestroy.length) {
|
||||||
cb.call(component);
|
try {
|
||||||
|
for (let cb of this.willDestroy) {
|
||||||
|
cb.call(component);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
handleError({ error: e, node: this });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.status = STATUS.DESTROYED;
|
this.status = STATUS.DESTROYED;
|
||||||
}
|
}
|
||||||
@@ -326,6 +335,7 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
bdom.mount(parent, anchor);
|
bdom.mount(parent, anchor);
|
||||||
this.status = STATUS.MOUNTED;
|
this.status = STATUS.MOUNTED;
|
||||||
this.fiber!.appliedToDom = true;
|
this.fiber!.appliedToDom = true;
|
||||||
|
this.children = this.fiber!.childrenMap;
|
||||||
this.fiber = null;
|
this.fiber = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,10 +353,8 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
}
|
}
|
||||||
_patch() {
|
_patch() {
|
||||||
const hasChildren = Object.keys(this.children).length > 0;
|
const hasChildren = Object.keys(this.children).length > 0;
|
||||||
|
this.children = this.fiber!.childrenMap;
|
||||||
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
|
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
|
||||||
if (hasChildren) {
|
|
||||||
this.cleanOutdatedChildren();
|
|
||||||
}
|
|
||||||
this.fiber!.appliedToDom = true;
|
this.fiber!.appliedToDom = true;
|
||||||
this.fiber = null;
|
this.fiber = null;
|
||||||
}
|
}
|
||||||
@@ -359,20 +367,6 @@ export class ComponentNode<P extends object = any, E = any> implements VNode<Com
|
|||||||
this.bdom!.remove();
|
this.bdom!.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanOutdatedChildren() {
|
|
||||||
const children = this.children;
|
|
||||||
for (const key in children) {
|
|
||||||
const node = children[key];
|
|
||||||
const status = node.status;
|
|
||||||
if (status !== STATUS.MOUNTED) {
|
|
||||||
delete children[key];
|
|
||||||
if (status !== STATUS.DESTROYED) {
|
|
||||||
node.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Some debug helpers
|
// Some debug helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { Fiber } from "./fibers";
|
|||||||
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
||||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
||||||
|
|
||||||
function _handleError(node: ComponentNode | null, error: any, isFirstRound = false): boolean {
|
function _handleError(node: ComponentNode | null, error: any): boolean {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -16,23 +16,19 @@ function _handleError(node: ComponentNode | null, error: any, isFirstRound = fal
|
|||||||
|
|
||||||
const errorHandlers = nodeErrorHandlers.get(node);
|
const errorHandlers = nodeErrorHandlers.get(node);
|
||||||
if (errorHandlers) {
|
if (errorHandlers) {
|
||||||
let stopped = false;
|
let handled = false;
|
||||||
// execute in the opposite order
|
// execute in the opposite order
|
||||||
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
||||||
try {
|
try {
|
||||||
errorHandlers[i](error);
|
errorHandlers[i](error);
|
||||||
stopped = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopped) {
|
if (handled) {
|
||||||
if (isFirstRound && fiber && fiber.node.fiber) {
|
|
||||||
const root = fiber.root!;
|
|
||||||
root.setCounter(root.counter - 1);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +51,7 @@ export function handleError(params: ErrorParams) {
|
|||||||
|
|
||||||
fibersInError.set(fiber.root!, error);
|
fibersInError.set(fiber.root!, error);
|
||||||
|
|
||||||
const handled = _handleError(node, error, true);
|
const handled = _handleError(node, error);
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
||||||
try {
|
try {
|
||||||
|
|||||||
+21
-30
@@ -8,10 +8,6 @@ export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
|||||||
if (current) {
|
if (current) {
|
||||||
cancelFibers(current.children);
|
cancelFibers(current.children);
|
||||||
current.root = null;
|
current.root = null;
|
||||||
if (current instanceof RootFiber && current.delayedRenders.length) {
|
|
||||||
let root = parent.root!;
|
|
||||||
root.delayedRenders = root.delayedRenders.concat(current.delayedRenders);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new Fiber(node, parent);
|
return new Fiber(node, parent);
|
||||||
}
|
}
|
||||||
@@ -20,12 +16,15 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
|||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
let root = current.root!;
|
let root = current.root!;
|
||||||
|
// lock root fiber because canceling children fibers may destroy components,
|
||||||
|
// which means any arbitrary code can be run in onWillDestroy, which may
|
||||||
|
// trigger new renderings
|
||||||
|
root.locked = true;
|
||||||
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
||||||
|
root.locked = false;
|
||||||
current.children = [];
|
current.children = [];
|
||||||
|
current.childrenMap = {};
|
||||||
current.bdom = null;
|
current.bdom = null;
|
||||||
if (current === root) {
|
|
||||||
root.reachedChildren = new WeakSet();
|
|
||||||
}
|
|
||||||
if (fibersInError.has(current)) {
|
if (fibersInError.has(current)) {
|
||||||
fibersInError.delete(current);
|
fibersInError.delete(current);
|
||||||
fibersInError.delete(root);
|
fibersInError.delete(root);
|
||||||
@@ -49,7 +48,11 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
|||||||
function cancelFibers(fibers: Fiber[]): number {
|
function cancelFibers(fibers: Fiber[]): number {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
for (let fiber of fibers) {
|
for (let fiber of fibers) {
|
||||||
fiber.node.fiber = null;
|
let node = fiber.node;
|
||||||
|
if (node.status === STATUS.NEW) {
|
||||||
|
node.destroy();
|
||||||
|
}
|
||||||
|
node.fiber = null;
|
||||||
if (fiber.bdom) {
|
if (fiber.bdom) {
|
||||||
// if fiber has been rendered, this means that the component props have
|
// if fiber has been rendered, this means that the component props have
|
||||||
// been updated. however, this fiber will not be patched to the dom, so
|
// been updated. however, this fiber will not be patched to the dom, so
|
||||||
@@ -57,7 +60,7 @@ function cancelFibers(fibers: Fiber[]): number {
|
|||||||
// the same props, and skip the render completely. With the next line,
|
// the same props, and skip the render completely. With the next line,
|
||||||
// we kindly request the component code to force a render, so it works as
|
// we kindly request the component code to force a render, so it works as
|
||||||
// expected.
|
// expected.
|
||||||
fiber.node.forceNextRender = true;
|
node.forceNextRender = true;
|
||||||
} else {
|
} else {
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
@@ -74,6 +77,7 @@ export class Fiber {
|
|||||||
children: Fiber[] = [];
|
children: Fiber[] = [];
|
||||||
appliedToDom = false;
|
appliedToDom = false;
|
||||||
deep: boolean = false;
|
deep: boolean = false;
|
||||||
|
childrenMap: ComponentNode["children"] = {};
|
||||||
|
|
||||||
constructor(node: ComponentNode, parent: Fiber | null) {
|
constructor(node: ComponentNode, parent: Fiber | null) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
@@ -92,20 +96,16 @@ export class Fiber {
|
|||||||
render() {
|
render() {
|
||||||
// if some parent has a fiber => register in followup
|
// if some parent has a fiber => register in followup
|
||||||
let prev = this.root!.node;
|
let prev = this.root!.node;
|
||||||
|
let scheduler = prev.app.scheduler;
|
||||||
let current = prev.parent;
|
let current = prev.parent;
|
||||||
while (current) {
|
while (current) {
|
||||||
if (current.fiber) {
|
if (current.fiber) {
|
||||||
let root = current.fiber.root!;
|
let root = current.fiber.root!;
|
||||||
if (root.counter) {
|
if (root.counter === 0 && prev.parentKey! in current.fiber.childrenMap) {
|
||||||
root.delayedRenders.push(this);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (!root.reachedChildren.has(prev)) {
|
|
||||||
// is dead
|
|
||||||
this.node.app.scheduler.shouldClear = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
current = root.node;
|
current = root.node;
|
||||||
|
} else {
|
||||||
|
scheduler.delayedRenders.push(this);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prev = current;
|
prev = current;
|
||||||
@@ -121,11 +121,12 @@ export class Fiber {
|
|||||||
const root = this.root;
|
const root = this.root;
|
||||||
if (root) {
|
if (root) {
|
||||||
try {
|
try {
|
||||||
|
(this.bdom as any) = true;
|
||||||
this.bdom = node.renderFn();
|
this.bdom = node.renderFn();
|
||||||
root.setCounter(root.counter - 1);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError({ node, error: e });
|
handleError({ node, error: e });
|
||||||
}
|
}
|
||||||
|
root.setCounter(root.counter - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,9 +142,6 @@ export class RootFiber extends Fiber {
|
|||||||
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
// i.e.: render triggered in onWillUnmount or in willPatch will be delayed
|
||||||
locked: boolean = false;
|
locked: boolean = false;
|
||||||
|
|
||||||
delayedRenders: Fiber[] = [];
|
|
||||||
reachedChildren: WeakSet<ComponentNode> = new WeakSet();
|
|
||||||
|
|
||||||
complete() {
|
complete() {
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
this.locked = true;
|
this.locked = true;
|
||||||
@@ -198,14 +196,6 @@ export class RootFiber extends Fiber {
|
|||||||
setCounter(newValue: number) {
|
setCounter(newValue: number) {
|
||||||
this.counter = newValue;
|
this.counter = newValue;
|
||||||
if (newValue === 0) {
|
if (newValue === 0) {
|
||||||
if (this.delayedRenders.length) {
|
|
||||||
for (let f of this.delayedRenders) {
|
|
||||||
if (f.root) {
|
|
||||||
f.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.delayedRenders = [];
|
|
||||||
}
|
|
||||||
this.node.app.scheduler.flush();
|
this.node.app.scheduler.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +220,7 @@ export class MountFiber extends RootFiber {
|
|||||||
let current: Fiber | undefined = this;
|
let current: Fiber | undefined = this;
|
||||||
try {
|
try {
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
|
node.children = this.childrenMap;
|
||||||
(node.app.constructor as any).validateTarget(this.target);
|
(node.app.constructor as any).validateTarget(this.target);
|
||||||
if (node.bdom) {
|
if (node.bdom) {
|
||||||
// this is a complicated situation: if we mount a fiber with an existing
|
// this is a complicated situation: if we mount a fiber with an existing
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class Scheduler {
|
|||||||
tasks: Set<RootFiber> = new Set();
|
tasks: Set<RootFiber> = new Set();
|
||||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||||
frame: number = 0;
|
frame: number = 0;
|
||||||
shouldClear: boolean = false;
|
delayedRenders: Fiber[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||||
@@ -28,16 +28,23 @@ export class Scheduler {
|
|||||||
* Other tasks are left unchanged.
|
* Other tasks are left unchanged.
|
||||||
*/
|
*/
|
||||||
flush() {
|
flush() {
|
||||||
|
if (this.delayedRenders.length) {
|
||||||
|
let renders = this.delayedRenders;
|
||||||
|
this.delayedRenders = [];
|
||||||
|
for (let f of renders) {
|
||||||
|
if (f.root && f.node.status !== STATUS.DESTROYED) {
|
||||||
|
f.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.frame === 0) {
|
if (this.frame === 0) {
|
||||||
this.frame = this.requestAnimationFrame(() => {
|
this.frame = this.requestAnimationFrame(() => {
|
||||||
this.frame = 0;
|
this.frame = 0;
|
||||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||||
if (this.shouldClear) {
|
for (let task of this.tasks) {
|
||||||
this.shouldClear = false;
|
if (task.node.status === STATUS.DESTROYED) {
|
||||||
for (let task of this.tasks) {
|
this.tasks.delete(task);
|
||||||
if (task.node.status === STATUS.DESTROYED) {
|
|
||||||
this.tasks.delete(task);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1370,6 +1370,25 @@ describe("qweb parser", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a component with an empty named slot", async () => {
|
||||||
|
expect(parse(`<MyComponent><t t-set-slot="mySlot"></t></MyComponent>`)).toEqual({
|
||||||
|
dynamicProps: null,
|
||||||
|
isDynamic: false,
|
||||||
|
name: "MyComponent",
|
||||||
|
on: null,
|
||||||
|
props: null,
|
||||||
|
slots: {
|
||||||
|
mySlot: {
|
||||||
|
attrs: null,
|
||||||
|
content: null,
|
||||||
|
on: null,
|
||||||
|
scope: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 11,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("a component with a named slot", async () => {
|
test("a component with a named slot", async () => {
|
||||||
expect(parse(`<MyComponent><t t-set-slot="name">foo</t></MyComponent>`)).toEqual({
|
expect(parse(`<MyComponent><t t-set-slot="name">foo</t></MyComponent>`)).toEqual({
|
||||||
type: ASTType.TComponent,
|
type: ASTType.TComponent,
|
||||||
|
|||||||
@@ -52,6 +52,50 @@ exports[`Cascading renders after microtaskTick 3`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`another scenario with delayed rendering 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(\`A\`);
|
||||||
|
if (ctx['state'].value<15) {
|
||||||
|
b3 = component(\`B\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`another scenario with delayed rendering 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(ctx['props'].value);
|
||||||
|
const b3 = component(\`C\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`another scenario with delayed rendering 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let hdlr1 = [ctx['increment'], ctx];
|
||||||
|
let txt1 = ctx['state'].val;
|
||||||
|
return block1([hdlr1, txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`async rendering destroying a widget before start is over 1`] = `
|
exports[`async rendering destroying a widget before start is over 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1120,6 +1164,69 @@ exports[`delayed rendering, but then initial rendering is cancelled by yet anoth
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`delayed rendering, destruction, stuff happens 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`A\`);
|
||||||
|
const b3 = component(\`B\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`delayed rendering, destruction, stuff happens 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(\`B\`);
|
||||||
|
if (ctx['state'].hasChild) {
|
||||||
|
b3 = component(\`C\`, {value: ctx['state'].someValue+ctx['props'].value}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`delayed rendering, destruction, stuff happens 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block4 = createBlock(\`<p><block-text-0/></p>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`C\`);
|
||||||
|
const b3 = component(\`D\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
let txt1 = ctx['props'].value;
|
||||||
|
const b4 = block4([txt1]);
|
||||||
|
return multi([b2, b3, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`delayed rendering, destruction, stuff happens 4`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block3 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`D\`);
|
||||||
|
let hdlr1 = [ctx['increment'], ctx];
|
||||||
|
let txt1 = ctx['state'].val;
|
||||||
|
const b3 = block3([hdlr1, txt1]);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`delayed rendering, reusing fiber and stuff 1`] = `
|
exports[`delayed rendering, reusing fiber and stuff 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1245,6 +1352,46 @@ exports[`delayed rendering, then component is destroyed and stuff 3`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(\` A \`);
|
||||||
|
if (ctx['state'].flag) {
|
||||||
|
const b4 = component(\`B\`, {value: ctx['state'].valueB}, key + \`__1\`, node, ctx);
|
||||||
|
const b5 = component(\`C\`, {value: ctx['state'].valueC}, key + \`__2\`, node, ctx);
|
||||||
|
b3 = multi([b4, b5]);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['props'].value);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`destroyed component causes other soon to be destroyed component to rerender, weird stuff happens 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(ctx['state'].val+ctx['props'].value);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`destroying/recreating a subcomponent, other scenario 1`] = `
|
exports[`destroying/recreating a subcomponent, other scenario 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1422,6 +1569,70 @@ exports[`rendering parent twice, with different props on child and stuff 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`renderings, destruction, patch, stuff, ... yet another variation 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`A\`);
|
||||||
|
const b3 = component(\`B\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
|
const b4 = component(\`D\`, {}, key + \`__2\`, node, ctx);
|
||||||
|
return multi([b2, b3, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renderings, destruction, patch, stuff, ... yet another variation 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(\`B\`);
|
||||||
|
if (ctx['props'].value===33) {
|
||||||
|
b3 = component(\`C\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renderings, destruction, patch, stuff, ... yet another variation 3`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`D\`);
|
||||||
|
let hdlr1 = [ctx['increment'], ctx];
|
||||||
|
let txt1 = ctx['state'].val;
|
||||||
|
const b3 = block3([hdlr1, txt1]);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renderings, destruction, patch, stuff, ... yet another variation 4`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = text(\`C\`);
|
||||||
|
let hdlr1 = [ctx['increment'], ctx];
|
||||||
|
let txt1 = ctx['state'].val;
|
||||||
|
const b3 = block3([hdlr1, txt1]);
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-foreach with dynamic async component 1`] = `
|
exports[`t-foreach with dynamic async component 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -85,6 +85,64 @@ exports[`basics simple catchError 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors an error in onWillDestroy 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(ctx['state'].value);
|
||||||
|
if (ctx['state'].hasChild) {
|
||||||
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors an error in onWillDestroy 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>abc</div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors an error in onWillDestroy, variation 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
b2 = text(ctx['state'].value);
|
||||||
|
if (ctx['state'].hasChild) {
|
||||||
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`can catch errors an error in onWillDestroy, variation 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div>abc</div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return block1();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`can catch errors calling a hook outside setup should crash 1`] = `
|
exports[`can catch errors calling a hook outside setup should crash 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -626,7 +626,7 @@ exports[`slots dynamic t-slot call 2`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let hdlr1 = [ctx['toggle'], ctx];
|
let hdlr1 = [ctx['toggle'], ctx];
|
||||||
const slot1 = (ctx['current'].slot);
|
const slot1 = (ctx['current'].slot);
|
||||||
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1), true, null);
|
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, null));
|
||||||
return block1([hdlr1], [b2]);
|
return block1([hdlr1], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -1444,6 +1444,110 @@ exports[`slots simple default slot, variation 2`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`slots simple dynamic slot with slot scope 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
let b2,b3;
|
||||||
|
if (ctx['slotScope'].bool) {
|
||||||
|
b2 = text(\`some text\`);
|
||||||
|
} else {
|
||||||
|
b3 = text(\`other text\`);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return component(\`Child\`, {slots: markRaw({'slotName': {__render: slot1, __ctx: ctx1, __scope: \\"slotScope\\"}})}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots simple dynamic slot with slot scope 2`] = `
|
||||||
|
"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>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const slot1 = ('slotName');
|
||||||
|
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {bool: ctx['state'].bool}));
|
||||||
|
return block1([], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots simple named and empty slot -- 2 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return component(\`Child\`, {slots: markRaw({'myEmptySlot': {myProp: 'myProp text'}})}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots simple named and empty slot -- 2 2`] = `
|
||||||
|
"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>\`);
|
||||||
|
|
||||||
|
function defaultContent1(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`default empty\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b3 = callSlot(ctx, node, key, 'myEmptySlot', false, null, defaultContent1);
|
||||||
|
return block1([], [b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots simple named and empty slot 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { capture, markRaw } = helpers;
|
||||||
|
|
||||||
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
|
return text(\`some text\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const ctx1 = capture(ctx);
|
||||||
|
return component(\`Child\`, {slots: markRaw({'myEmptySlot': {}, 'default': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`slots simple named and empty slot 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { callSlot } = helpers;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<span><block-child-0/><block-child-1/></span>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const b2 = callSlot(ctx, node, key, 'default', false, null);
|
||||||
|
const b3 = callSlot(ctx, node, key, 'myEmptySlot', false, null);
|
||||||
|
return block1([], [b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`slots simple slot with slot scope 1`] = `
|
exports[`slots simple slot with slot scope 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1689,7 +1793,7 @@ exports[`slots slot content has different key from other content -- dynamic slot
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const b2 = component(\`Child\`, {parent: 'SlotDisplay'}, key + \`__1\`, node, ctx);
|
const b2 = component(\`Child\`, {parent: 'SlotDisplay'}, key + \`__1\`, node, ctx);
|
||||||
const slot1 = (ctx['slotName']);
|
const slot1 = (ctx['slotName']);
|
||||||
const b3 = toggler(slot1, callSlot(ctx, node, key, slot1), true, null);
|
const b3 = toggler(slot1, callSlot(ctx, node, key, slot1, true, null));
|
||||||
return multi([b2, b3]);
|
return multi([b2, b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
mount,
|
mount,
|
||||||
onMounted,
|
onMounted,
|
||||||
onRendered,
|
onRendered,
|
||||||
|
onWillDestroy,
|
||||||
onWillStart,
|
onWillStart,
|
||||||
onWillUnmount,
|
onWillUnmount,
|
||||||
onWillUpdateProps,
|
onWillUpdateProps,
|
||||||
@@ -114,8 +115,8 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
|||||||
expect(n).toBe(2);
|
expect(n).toBe(2);
|
||||||
|
|
||||||
expect([
|
expect([
|
||||||
"W:willRender",
|
|
||||||
"Child:willDestroy",
|
"Child:willDestroy",
|
||||||
|
"W:willRender",
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"W:rendered",
|
"W:rendered",
|
||||||
@@ -172,6 +173,11 @@ test("destroying/recreating a subcomponent, other scenario", async () => {
|
|||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect([
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:setup",
|
||||||
|
"Child:willStart",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willDestroy",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
@@ -244,8 +250,8 @@ test("creating two async components, scenario 1", async () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
expect([
|
expect([
|
||||||
"Parent:willRender",
|
|
||||||
"ChildA:willDestroy",
|
"ChildA:willDestroy",
|
||||||
|
"Parent:willRender",
|
||||||
"ChildA:setup",
|
"ChildA:setup",
|
||||||
"ChildA:willStart",
|
"ChildA:willStart",
|
||||||
"ChildB:setup",
|
"ChildB:setup",
|
||||||
@@ -696,8 +702,8 @@ test("rendering component again in next microtick", async () => {
|
|||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
"Parent:willRender",
|
|
||||||
"Child:willDestroy",
|
"Child:willDestroy",
|
||||||
|
"Parent:willRender",
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
@@ -1725,6 +1731,7 @@ 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",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
@@ -1735,7 +1742,6 @@ test("concurrent renderings scenario 10", async () => {
|
|||||||
expect(rendered).toBe(1);
|
expect(rendered).toBe(1);
|
||||||
expect([
|
expect([
|
||||||
"ComponentB:willRender",
|
"ComponentB:willRender",
|
||||||
"ComponentC:willDestroy",
|
|
||||||
"ComponentC:setup",
|
"ComponentC:setup",
|
||||||
"ComponentC:willStart",
|
"ComponentC:willStart",
|
||||||
"ComponentB:rendered",
|
"ComponentB:rendered",
|
||||||
@@ -2275,11 +2281,11 @@ 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",
|
||||||
"C:willRender",
|
"C:willRender",
|
||||||
"D:willDestroy",
|
|
||||||
"D:setup",
|
"D:setup",
|
||||||
"D:willStart",
|
"D:willStart",
|
||||||
"C:rendered",
|
"C:rendered",
|
||||||
@@ -2990,13 +2996,13 @@ 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 (1):willUnmount",
|
"Child (1):willUnmount",
|
||||||
"Child (1):willDestroy",
|
"Child (1):willDestroy",
|
||||||
"Child (2):willDestroy",
|
|
||||||
"Child (3):mounted",
|
"Child (3):mounted",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
@@ -3048,13 +3054,13 @@ 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 (1):willUnmount",
|
"Child (1):willUnmount",
|
||||||
"Child (1):willDestroy",
|
"Child (1):willDestroy",
|
||||||
"Child (2):willDestroy",
|
|
||||||
"Child (3):mounted",
|
"Child (3):mounted",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
@@ -3107,13 +3113,13 @@ 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 (1):willUnmount",
|
"Child (1):willUnmount",
|
||||||
"Child (1):willDestroy",
|
"Child (1):willDestroy",
|
||||||
"Child (2):willDestroy",
|
|
||||||
"Child (3):mounted",
|
"Child (3):mounted",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
@@ -3591,6 +3597,393 @@ test("delayed rendering, reusing fiber then component is destroyed and stuff",
|
|||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("another scenario with delayed rendering", async () => {
|
||||||
|
let prom1 = makeDeferred();
|
||||||
|
let onSecondRenderA = makeDeferred();
|
||||||
|
|
||||||
|
class C extends Component {
|
||||||
|
static template = xml`<button t-on-click="increment"><t t-esc="state.val"/></button>`;
|
||||||
|
state = useState({ val: 1 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
increment() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`<t t-esc="props.value"/><C />`;
|
||||||
|
static components = { C };
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onWillUpdateProps(() => prom1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class A extends Component {
|
||||||
|
static template = xml`A<t t-if="state.value lt 15"><B value="state.value"/></t>`;
|
||||||
|
static components = { B };
|
||||||
|
state = useState({ value: 3 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
let n = 0;
|
||||||
|
onRendered(() => {
|
||||||
|
n++;
|
||||||
|
if (n === 2) {
|
||||||
|
onSecondRenderA.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(A, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("A3<button>1</button>");
|
||||||
|
expect([
|
||||||
|
"A:setup",
|
||||||
|
"A:willStart",
|
||||||
|
"A:willRender",
|
||||||
|
"B:setup",
|
||||||
|
"B:willStart",
|
||||||
|
"A:rendered",
|
||||||
|
"B:willRender",
|
||||||
|
"C:setup",
|
||||||
|
"C:willStart",
|
||||||
|
"B:rendered",
|
||||||
|
"C:willRender",
|
||||||
|
"C:rendered",
|
||||||
|
"C:mounted",
|
||||||
|
"B:mounted",
|
||||||
|
"A:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
// initiate a render in A, but is blocked in B
|
||||||
|
parent.state.value = 5;
|
||||||
|
await nextTick();
|
||||||
|
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
|
||||||
|
|
||||||
|
// initiate a render in C (will be delayed because of render in A)
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect([]).toBeLogged();
|
||||||
|
|
||||||
|
// initiate a render in A, that will destroy B
|
||||||
|
parent.state.value = 23;
|
||||||
|
await onSecondRenderA;
|
||||||
|
await nextMicroTick();
|
||||||
|
expect(["A:willRender", "A:rendered"]).toBeLogged();
|
||||||
|
|
||||||
|
// rerender A, but without destroying B
|
||||||
|
parent.state.value = 7;
|
||||||
|
await nextTick();
|
||||||
|
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
|
||||||
|
|
||||||
|
prom1.resolve();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("A7<button>2</button>");
|
||||||
|
|
||||||
|
expect([
|
||||||
|
"B:willRender",
|
||||||
|
"B:rendered",
|
||||||
|
"C:willRender",
|
||||||
|
"C:rendered",
|
||||||
|
"A:willPatch",
|
||||||
|
"B:willPatch",
|
||||||
|
"B:patched",
|
||||||
|
"A:patched",
|
||||||
|
"C:willPatch",
|
||||||
|
"C:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destroyed component causes other soon to be destroyed component to rerender, weird stuff happens", async () => {
|
||||||
|
let def = makeDeferred();
|
||||||
|
let c: any = null;
|
||||||
|
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`<t t-esc="props.value"/>`;
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onRendered(() => {
|
||||||
|
def.resolve();
|
||||||
|
});
|
||||||
|
onWillDestroy(() => {
|
||||||
|
c.state.val++;
|
||||||
|
c.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class C extends Component {
|
||||||
|
static template = xml`<t t-esc="state.val + props.value"/>`;
|
||||||
|
state = useState({ val: 0 });
|
||||||
|
setup() {
|
||||||
|
c = this;
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class A extends Component {
|
||||||
|
static template = xml`
|
||||||
|
A
|
||||||
|
<t t-if="state.flag">
|
||||||
|
<B value="state.valueB"/>
|
||||||
|
<C value="state.valueC"/>
|
||||||
|
</t>`;
|
||||||
|
static components = { B, C };
|
||||||
|
state = useState({ flag: false, valueB: 1, valueC: 2 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(A, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe(" A ");
|
||||||
|
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged();
|
||||||
|
|
||||||
|
// initiate a render in A, but is blocked in B
|
||||||
|
parent.state.flag = true;
|
||||||
|
|
||||||
|
await def;
|
||||||
|
await nextMicroTick();
|
||||||
|
expect([
|
||||||
|
"A:willRender",
|
||||||
|
"B:setup",
|
||||||
|
"B:willStart",
|
||||||
|
"C:setup",
|
||||||
|
"C:willStart",
|
||||||
|
"A:rendered",
|
||||||
|
"B:willRender",
|
||||||
|
"B:rendered",
|
||||||
|
"C:willRender",
|
||||||
|
"C:rendered",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
// initiate render in A => will cancel renders in B/C and restarts
|
||||||
|
parent.state.valueB = 2;
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"B:willDestroy",
|
||||||
|
"C:willDestroy",
|
||||||
|
"A:willRender",
|
||||||
|
"B:setup",
|
||||||
|
"B:willStart",
|
||||||
|
"C:setup",
|
||||||
|
"C:willStart",
|
||||||
|
"A:rendered",
|
||||||
|
"B:willRender",
|
||||||
|
"B:rendered",
|
||||||
|
"C:willRender",
|
||||||
|
"C:rendered",
|
||||||
|
"A:willPatch",
|
||||||
|
"C:mounted",
|
||||||
|
"B:mounted",
|
||||||
|
"A:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(" A 22");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delayed rendering, destruction, stuff happens", async () => {
|
||||||
|
const promC = makeDeferred();
|
||||||
|
let stateB: any = null;
|
||||||
|
|
||||||
|
class D extends Component {
|
||||||
|
static template = xml`D<button t-on-click="increment"><t t-esc="state.val"/></button>`;
|
||||||
|
state = useState({ val: 1 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
increment() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C extends Component {
|
||||||
|
static template = xml`C<D/><p><t t-esc="props.value"/></p>`;
|
||||||
|
static components = { D };
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onWillUpdateProps(() => promC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`B<t t-if="state.hasChild"><C value="state.someValue + props.value"/></t>`;
|
||||||
|
static components = { C };
|
||||||
|
state = useState({ someValue: 3, hasChild: true });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
stateB = this.state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class A extends Component {
|
||||||
|
static template = xml`A<B value="state.value"/>`;
|
||||||
|
static components = { B };
|
||||||
|
state = useState({ value: 33 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(A, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("ABCD<button>1</button><p>36</p>");
|
||||||
|
expect([
|
||||||
|
"A:setup",
|
||||||
|
"A:willStart",
|
||||||
|
"A:willRender",
|
||||||
|
"B:setup",
|
||||||
|
"B:willStart",
|
||||||
|
"A:rendered",
|
||||||
|
"B:willRender",
|
||||||
|
"C:setup",
|
||||||
|
"C:willStart",
|
||||||
|
"B:rendered",
|
||||||
|
"C:willRender",
|
||||||
|
"D:setup",
|
||||||
|
"D:willStart",
|
||||||
|
"C:rendered",
|
||||||
|
"D:willRender",
|
||||||
|
"D:rendered",
|
||||||
|
"D:mounted",
|
||||||
|
"C:mounted",
|
||||||
|
"B:mounted",
|
||||||
|
"A:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
// render in A, it updates B and C, but render is blocked in C
|
||||||
|
parent.state.value = 50;
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"A:willRender",
|
||||||
|
"B:willUpdateProps",
|
||||||
|
"A:rendered",
|
||||||
|
"B:willRender",
|
||||||
|
"C:willUpdateProps",
|
||||||
|
"B:rendered",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
// update B => removes child C
|
||||||
|
stateB.hasChild = false;
|
||||||
|
// update D => render should be delayed, because AB is currently rendering
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"B:willRender",
|
||||||
|
"B:rendered",
|
||||||
|
"A:willPatch",
|
||||||
|
"B:willPatch",
|
||||||
|
"C:willUnmount",
|
||||||
|
"D:willUnmount",
|
||||||
|
"D:willDestroy",
|
||||||
|
"C:willDestroy",
|
||||||
|
"B:patched",
|
||||||
|
"A:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("AB");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renderings, destruction, patch, stuff, ... yet another variation", async () => {
|
||||||
|
const promB = makeDeferred();
|
||||||
|
|
||||||
|
class D extends Component {
|
||||||
|
static template = xml`D<p t-on-click="increment"><t t-esc="state.val"/></p>`;
|
||||||
|
state = useState({ val: 1 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
increment() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// almost the same as D
|
||||||
|
class C extends Component {
|
||||||
|
static template = xml`C<span t-on-click="increment"><t t-esc="state.val"/></span>`;
|
||||||
|
state = useState({ val: 1 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
increment() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`B<t t-if="props.value === 33"><C/></t>`;
|
||||||
|
static components = { C };
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onWillUpdateProps(() => promB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class A extends Component {
|
||||||
|
static template = xml`A<B value="state.value"/><D/>`;
|
||||||
|
static components = { B, D };
|
||||||
|
state = useState({ value: 33 });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(A, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("ABC<span>1</span>D<p>1</p>");
|
||||||
|
expect([
|
||||||
|
"A:setup",
|
||||||
|
"A:willStart",
|
||||||
|
"A:willRender",
|
||||||
|
"B:setup",
|
||||||
|
"B:willStart",
|
||||||
|
"D:setup",
|
||||||
|
"D:willStart",
|
||||||
|
"A:rendered",
|
||||||
|
"B:willRender",
|
||||||
|
"C:setup",
|
||||||
|
"C:willStart",
|
||||||
|
"B:rendered",
|
||||||
|
"D:willRender",
|
||||||
|
"D:rendered",
|
||||||
|
"C:willRender",
|
||||||
|
"C:rendered",
|
||||||
|
"C:mounted",
|
||||||
|
"D:mounted",
|
||||||
|
"B:mounted",
|
||||||
|
"A:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
// render in A, it updates B, will remove C, stopped in B
|
||||||
|
parent.state.value = 50;
|
||||||
|
await nextTick();
|
||||||
|
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
|
||||||
|
|
||||||
|
// update C => render should be delayed, because AB is currently rendering
|
||||||
|
fixture.querySelector("span")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect([]).toBeLogged();
|
||||||
|
|
||||||
|
// resolve prom B => render is done, component C is destroyed
|
||||||
|
promB.resolve();
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"B:willRender",
|
||||||
|
"B:rendered",
|
||||||
|
"A:willPatch",
|
||||||
|
"B:willPatch",
|
||||||
|
"C:willUnmount",
|
||||||
|
"C:willDestroy",
|
||||||
|
"B:patched",
|
||||||
|
"A:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("ABD<p>1</p>");
|
||||||
|
|
||||||
|
// update D => should just render completely independently
|
||||||
|
fixture.querySelector("p")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(["D:willRender", "D:rendered", "D:willPatch", "D:patched"]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("ABD<p>2</p>");
|
||||||
|
});
|
||||||
|
|
||||||
// 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 };
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, mount } from "../../src";
|
import { Component, mount, onWillDestroy } from "../../src";
|
||||||
import {
|
import {
|
||||||
onError,
|
onError,
|
||||||
onMounted,
|
onMounted,
|
||||||
@@ -1197,4 +1197,126 @@ describe("can catch errors", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div>Child 2</div>");
|
expect(fixture.innerHTML).toBe("<div>Child 2</div>");
|
||||||
expect(steps).toEqual(["Error Component"]);
|
expect(steps).toEqual(["Error Component"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("an error in onWillDestroy", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<div>abc</div>`;
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onWillDestroy(() => {
|
||||||
|
throw new Error("boom");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-esc="state.value"/>
|
||||||
|
<t t-if="state.hasChild"><Child/></t>`;
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
state = useState({ value: 1, hasChild: true });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onError(() => {
|
||||||
|
this.state.value++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("1<div>abc</div>");
|
||||||
|
expect([
|
||||||
|
"Parent:setup",
|
||||||
|
"Parent:willStart",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:setup",
|
||||||
|
"Child:willStart",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willRender",
|
||||||
|
"Child:rendered",
|
||||||
|
"Child:mounted",
|
||||||
|
"Parent:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
parent.state.hasChild = false;
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Child:willUnmount",
|
||||||
|
"Child:willDestroy",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Parent:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("an error in onWillDestroy, variation", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<div>abc</div>`;
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onWillDestroy(() => {
|
||||||
|
throw new Error("boom");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<t t-esc="state.value"/>
|
||||||
|
<t t-if="state.hasChild"><Child/></t>`;
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
state = useState({ value: 1, hasChild: false });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
onError(() => {
|
||||||
|
this.state.value++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("1");
|
||||||
|
|
||||||
|
expect([
|
||||||
|
"Parent:setup",
|
||||||
|
"Parent:willStart",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
parent.state.hasChild = true;
|
||||||
|
await nextMicroTick();
|
||||||
|
await nextMicroTick();
|
||||||
|
await nextMicroTick();
|
||||||
|
await nextMicroTick();
|
||||||
|
await nextMicroTick();
|
||||||
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:setup",
|
||||||
|
"Child:willStart",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willRender",
|
||||||
|
"Child:rendered",
|
||||||
|
]).toBeLogged();
|
||||||
|
parent.state.hasChild = false;
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"Child:willDestroy",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Parent:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("2");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,6 +74,72 @@ describe("slots", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>other text</span>");
|
expect(fixture.innerHTML).toBe("<span>other text</span>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("simple dynamic slot with slot scope", async () => {
|
||||||
|
let child: any;
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<span><t t-slot="{{ 'slotName' }}" bool="state.bool"/></span>`;
|
||||||
|
state = useState({ bool: true });
|
||||||
|
setup() {
|
||||||
|
child = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<Child>
|
||||||
|
<t t-set-slot="slotName" t-slot-scope="slotScope">
|
||||||
|
<t t-if="slotScope.bool">some text</t>
|
||||||
|
<t t-else="slotScope.bool">other text</t>
|
||||||
|
</t>
|
||||||
|
</Child>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<span>some text</span>");
|
||||||
|
|
||||||
|
child.state.bool = false;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<span>other text</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simple named and empty slot", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<span><t t-slot="default" /><t t-slot="myEmptySlot"/></span>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
expect(this.props.slots["myEmptySlot"]).toBeTruthy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<Child>some text<t t-set-slot="myEmptySlot" /></Child>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<span>some text</span>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simple named and empty slot -- 2", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<span><t t-slot="myEmptySlot">default empty</t></span>`;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
expect(this.props.slots["myEmptySlot"]).toBeTruthy();
|
||||||
|
expect(this.props.slots["myEmptySlot"].myProp).toBe("myProp text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<Child><t t-set-slot="myEmptySlot" myProp="'myProp text'" /></Child>`;
|
||||||
|
static components = { Child };
|
||||||
|
}
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<span>default empty</span>");
|
||||||
|
});
|
||||||
|
|
||||||
test("default slot with slot scope: shorthand syntax", async () => {
|
test("default slot with slot scope: shorthand syntax", async () => {
|
||||||
let child: any;
|
let child: any;
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
|
|||||||
Reference in New Issue
Block a user