mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a56cc6402 | |||
| 1cadae86fd |
@@ -2,8 +2,6 @@ import { BDom, multi, text, toggler } from "../blockdom";
|
||||
import { validateProps } from "../component/props_validation";
|
||||
import { Markup } from "../utils";
|
||||
import { html } from "../blockdom/index";
|
||||
import { TARGET } from "../reactivity";
|
||||
|
||||
/**
|
||||
* This file contains utility functions that will be injected in each template,
|
||||
* to perform various useful tasks in the compiled code.
|
||||
@@ -23,8 +21,7 @@ function callSlot(
|
||||
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
||||
): BDom {
|
||||
key = key + "__slot_" + name;
|
||||
const nonReactiveProps = ctx.props && ctx.props[TARGET];
|
||||
const slots = nonReactiveProps ? nonReactiveProps.slots || {} : {};
|
||||
const slots = (ctx.props && ctx.props.slots) || {};
|
||||
const { __render, __ctx, __scope } = slots[name] || {};
|
||||
const slotScope = Object.create(__ctx || {});
|
||||
if (__scope) {
|
||||
|
||||
@@ -55,7 +55,7 @@ export class TemplateSet {
|
||||
const template = this.getTemplate(subTemplate);
|
||||
return toggler(subTemplate, template.call(owner, ctx, parent, key));
|
||||
},
|
||||
getTemplate: (name: string) => this.getTemplate(name),
|
||||
getTemplate: (name: string, nameSpace?: string) => this.getTemplate(name, nameSpace),
|
||||
});
|
||||
|
||||
constructor(config: TemplateSetConfig = {}) {
|
||||
@@ -87,13 +87,15 @@ export class TemplateSet {
|
||||
}
|
||||
}
|
||||
|
||||
getTemplate(name: string): Template {
|
||||
getTemplate(name: string, nameSpace?: string): Template {
|
||||
|
||||
if (!(name in this.templates)) {
|
||||
const rawTemplate = this.rawTemplates[name];
|
||||
if (rawTemplate === undefined) {
|
||||
throw new Error(`Missing template: "${name}"`);
|
||||
}
|
||||
const templateFn = this._compileTemplate(name, rawTemplate);
|
||||
console.log("getTemplate", nameSpace);
|
||||
const templateFn = this._compileTemplate(name, rawTemplate, nameSpace);
|
||||
// first add a function to lazily get the template, in case there is a
|
||||
// recursive call to the template name
|
||||
const templates = this.templates;
|
||||
@@ -106,12 +108,14 @@ export class TemplateSet {
|
||||
return this.templates[name];
|
||||
}
|
||||
|
||||
_compileTemplate(name: string, template: string | Node) {
|
||||
_compileTemplate(name: string, template: string | Node, nameSpace?: string) {
|
||||
console.log("CompileTpm", nameSpace)
|
||||
return compile(template, {
|
||||
name,
|
||||
dev: this.dev,
|
||||
translateFn: this.translateFn,
|
||||
translatableAttributes: this.translatableAttributes,
|
||||
nameSpace: nameSpace,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface Config {
|
||||
export interface CodeGenOptions extends Config {
|
||||
hasSafeContext?: boolean;
|
||||
name?: string;
|
||||
nameSpace?: string;
|
||||
}
|
||||
|
||||
// using a non-html document so that <inner/outer>HTML serializes as XML instead
|
||||
@@ -214,8 +215,9 @@ export class CodeGenerator {
|
||||
translateFn: (s: string) => string;
|
||||
translatableAttributes: string[];
|
||||
ast: AST;
|
||||
staticCalls: { id: string; template: string }[] = [];
|
||||
staticCalls: { id: string; template: string, nameSpace?: string }[] = [];
|
||||
helpers: Set<string> = new Set();
|
||||
nameSpace?: string;
|
||||
|
||||
constructor(ast: AST, options: CodeGenOptions) {
|
||||
this.translateFn = options.translateFn || ((s: string) => s);
|
||||
@@ -224,6 +226,8 @@ export class CodeGenerator {
|
||||
this.dev = options.dev || false;
|
||||
this.ast = ast;
|
||||
this.templateName = options.name;
|
||||
this.nameSpace = options.nameSpace;
|
||||
console.log('ThisNS', this.nameSpace)
|
||||
}
|
||||
|
||||
generateCode(): string {
|
||||
@@ -238,6 +242,7 @@ export class CodeGenerator {
|
||||
isLast: true,
|
||||
translate: true,
|
||||
tKeyExpr: null,
|
||||
nameSpace: this.nameSpace,
|
||||
});
|
||||
// define blocks and utility functions
|
||||
let mainCode = [
|
||||
@@ -250,8 +255,12 @@ export class CodeGenerator {
|
||||
mainCode.push(`// Template name: "${this.templateName}"`);
|
||||
}
|
||||
|
||||
for (let { id, template } of this.staticCalls) {
|
||||
mainCode.push(`const ${id} = getTemplate(${template});`);
|
||||
for (let { id, template, nameSpace } of this.staticCalls) {
|
||||
let ns;
|
||||
if (nameSpace) {
|
||||
ns = `, "${nameSpace}"`;
|
||||
}
|
||||
mainCode.push(`const ${id} = getTemplate(${template}${ns});`);
|
||||
}
|
||||
|
||||
// define all blocks
|
||||
@@ -539,6 +548,7 @@ export class CodeGenerator {
|
||||
// attributes
|
||||
const attrs: { [key: string]: string } = {};
|
||||
const nameSpace = ast.ns || ctx.nameSpace;
|
||||
console.log(nameSpace)
|
||||
if (nameSpace && isNewBlock) {
|
||||
// specific namespace uri
|
||||
attrs["block-ns"] = nameSpace;
|
||||
@@ -960,7 +970,7 @@ export class CodeGenerator {
|
||||
} else {
|
||||
const id = this.generateId(`callTemplate_`);
|
||||
this.helpers.add("getTemplate");
|
||||
this.staticCalls.push({ id, template: subTemplate });
|
||||
this.staticCalls.push({ id, template: subTemplate, nameSpace: ctx.nameSpace });
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block!, {
|
||||
...ctx,
|
||||
@@ -1081,9 +1091,11 @@ export class CodeGenerator {
|
||||
|
||||
let propString = propStr;
|
||||
if (ast.dynamicProps) {
|
||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${
|
||||
props.length ? ", " + propStr : ""
|
||||
})`;
|
||||
if (!props.length) {
|
||||
propString = `${compileExpr(ast.dynamicProps)}`;
|
||||
} else {
|
||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
||||
}
|
||||
}
|
||||
|
||||
let propVar: string;
|
||||
|
||||
@@ -8,6 +8,7 @@ export type TemplateFunction = (blocks: any, utils: any) => Template;
|
||||
|
||||
interface CompileOptions extends Config {
|
||||
name?: string;
|
||||
nameSpace?: string,
|
||||
}
|
||||
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction {
|
||||
// parsing
|
||||
@@ -20,8 +21,10 @@ export function compile(template: string | Node, options: CompileOptions = {}):
|
||||
: !template.includes("t-set") && !template.includes("t-call");
|
||||
|
||||
// code generation
|
||||
console.log("compile", options)
|
||||
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
|
||||
const code = codeGenerator.generateCode();
|
||||
console.log(code)
|
||||
// template function
|
||||
return new Function("bdom, helpers", code) as TemplateFunction;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class Component<Props = any, Env = any> {
|
||||
|
||||
setup() {}
|
||||
|
||||
render(force: boolean = false) {
|
||||
this.__owl__.render(force);
|
||||
render() {
|
||||
this.__owl__.render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { App, Env } from "../app/app";
|
||||
import { BDom, VNode } from "../blockdom";
|
||||
import { clearReactivesForCallback, Reactive, reactive, TARGET } from "../reactivity";
|
||||
import { batched, Callback } from "../utils";
|
||||
import { Component } from "./component";
|
||||
import { fibersInError, handleError } from "./error_handling";
|
||||
import {
|
||||
Fiber,
|
||||
makeChildFiber,
|
||||
@@ -12,6 +9,7 @@ import {
|
||||
MountOptions,
|
||||
RootFiber,
|
||||
} from "./fibers";
|
||||
import { handleError, fibersInError } from "./error_handling";
|
||||
import { applyDefaultProps } from "./props_validation";
|
||||
import { STATUS } from "./status";
|
||||
|
||||
@@ -25,46 +23,6 @@ export function useComponent(): Component {
|
||||
return currentNode!.component;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Integration with reactivity system (useState)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||
/**
|
||||
* Creates a reactive object that will be observed by the current component.
|
||||
* Reading data from the returned object (eg during rendering) will cause the
|
||||
* component to subscribe to that data and be rerendered when it changes.
|
||||
*
|
||||
* @param state the state to observe
|
||||
* @returns a reactive object that will cause the component to re-render on
|
||||
* relevant changes
|
||||
* @see reactive
|
||||
*/
|
||||
export function useState<T extends object>(state: T): Reactive<T> {
|
||||
const node = currentNode!;
|
||||
let render = batchedRenderFunctions.get(node)!;
|
||||
if (!render) {
|
||||
render = batched(node.render.bind(node));
|
||||
batchedRenderFunctions.set(node, render);
|
||||
// manual implementation of onWillUnmount to break cyclic dependency
|
||||
node.willUnmount.unshift(clearReactivesForCallback.bind(null, render));
|
||||
}
|
||||
return reactive(state, render);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// component function (used in compiled template code)
|
||||
// -----------------------------------------------------------------------------
|
||||
type Props = { [key: string]: any };
|
||||
|
||||
function arePropsDifferent(props1: Props, props2: Props): boolean {
|
||||
for (let k in props1) {
|
||||
if (props1[k] !== props2[k]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function component(
|
||||
name: string | typeof Component,
|
||||
props: any,
|
||||
@@ -89,10 +47,7 @@ export function component(
|
||||
|
||||
const parentFiber = ctx.fiber!;
|
||||
if (node) {
|
||||
const currentProps = node.component.props[TARGET];
|
||||
if (parentFiber.force || arePropsDifferent(currentProps, props)) {
|
||||
node.updateAndRender(props, parentFiber);
|
||||
}
|
||||
node.updateAndRender(props, parentFiber);
|
||||
} else {
|
||||
// new component
|
||||
let C;
|
||||
@@ -114,7 +69,7 @@ export function component(
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Component VNode class
|
||||
// Component VNode
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type LifecycleHook = Function;
|
||||
@@ -152,7 +107,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
applyDefaultProps(props, C);
|
||||
const env = (parent && parent.childEnv) || app.env;
|
||||
this.childEnv = env;
|
||||
props = useState(props);
|
||||
this.component = new C(props, env, this) as any;
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
||||
this.component.setup();
|
||||
@@ -181,7 +135,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
}
|
||||
}
|
||||
|
||||
async render(force: boolean = false) {
|
||||
async render() {
|
||||
let current = this.fiber;
|
||||
if (current && current.root!.locked) {
|
||||
await Promise.resolve();
|
||||
@@ -189,15 +143,13 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
current = this.fiber;
|
||||
}
|
||||
if (current && !current.bdom && !fibersInError.has(current)) {
|
||||
if (current.force || force === false) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!this.bdom && !current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fiber = makeRootFiber(this, force);
|
||||
const fiber = makeRootFiber(this);
|
||||
this.fiber = fiber;
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
@@ -259,9 +211,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
this.fiber = fiber;
|
||||
const component = this.component;
|
||||
applyDefaultProps(props, component.constructor as any);
|
||||
|
||||
currentNode = this;
|
||||
props = useState(props);
|
||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||
await prom;
|
||||
if (fiber !== this.fiber) {
|
||||
@@ -326,14 +275,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
}
|
||||
|
||||
patch() {
|
||||
if (this.fiber && this.fiber.parent) {
|
||||
// we only patch here renderings coming from above. renderings initiated
|
||||
// by the component will be patched independently in the appropriate
|
||||
// fiber.complete
|
||||
this._patch();
|
||||
}
|
||||
}
|
||||
_patch() {
|
||||
const hasChildren = Object.keys(this.children).length > 0;
|
||||
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
|
||||
if (hasChildren) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||
return new Fiber(node, parent);
|
||||
}
|
||||
|
||||
export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
|
||||
export function makeRootFiber(node: ComponentNode): Fiber {
|
||||
let current = node.fiber;
|
||||
if (current) {
|
||||
let root = current.root!;
|
||||
@@ -21,7 +21,6 @@ export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
|
||||
current.children = [];
|
||||
root.counter++;
|
||||
current.bdom = null;
|
||||
current.force = force;
|
||||
if (fibersInError.has(current)) {
|
||||
fibersInError.delete(current);
|
||||
fibersInError.delete(root);
|
||||
@@ -36,7 +35,7 @@ export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
|
||||
if (node.patched.length) {
|
||||
fiber.patched.push(fiber);
|
||||
}
|
||||
fiber.force = force;
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
@@ -63,13 +62,11 @@ export class Fiber {
|
||||
parent: Fiber | null;
|
||||
children: Fiber[] = [];
|
||||
appliedToDom = false;
|
||||
force: boolean = false;
|
||||
|
||||
constructor(node: ComponentNode, parent: Fiber | null) {
|
||||
this.node = node;
|
||||
this.parent = parent;
|
||||
if (parent) {
|
||||
this.force = parent.force;
|
||||
const root = parent.root!;
|
||||
root.counter++;
|
||||
this.root = root;
|
||||
@@ -112,7 +109,7 @@ export class RootFiber extends Fiber {
|
||||
current = undefined;
|
||||
|
||||
// Step 2: patching the dom
|
||||
node._patch();
|
||||
node.patch();
|
||||
this.locked = false;
|
||||
|
||||
// Step 4: calling all mounted lifecycle hooks
|
||||
|
||||
+1
-2
@@ -42,8 +42,7 @@ export { useComponent } from "./component/component_node";
|
||||
export { status } from "./component/status";
|
||||
export { Memo } from "./memo";
|
||||
export { xml } from "./app/template_set";
|
||||
export { reactive } from "./reactivity";
|
||||
export { useState } from "./component/component_node";
|
||||
export { useState, reactive } from "./reactivity";
|
||||
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||
export {
|
||||
|
||||
+30
-3
@@ -1,7 +1,9 @@
|
||||
import { Callback } from "./utils";
|
||||
import { onWillUnmount } from "./component/lifecycle_hooks";
|
||||
import { ComponentNode, getCurrent } from "./component/component_node";
|
||||
import { batched, Callback } from "./utils";
|
||||
|
||||
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||
export const TARGET = Symbol("Target");
|
||||
const TARGET = Symbol("Target");
|
||||
// Special key to subscribe to, to be notified of key creation/deletion
|
||||
const KEYCHANGES = Symbol("Key changes");
|
||||
|
||||
@@ -83,7 +85,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
|
||||
*
|
||||
* @param callback the callback for which the reactives need to be cleared
|
||||
*/
|
||||
export function clearReactivesForCallback(callback: Callback): void {
|
||||
function clearReactivesForCallback(callback: Callback): void {
|
||||
const targetsToClear = callbacksToTargets.get(callback);
|
||||
if (!targetsToClear) {
|
||||
return;
|
||||
@@ -188,3 +190,28 @@ export function reactive<T extends Target>(target: T, callback: Callback = () =>
|
||||
}
|
||||
return reactivesForTarget.get(callback) as Reactive<T>;
|
||||
}
|
||||
|
||||
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
|
||||
/**
|
||||
* Creates a reactive object that will be observed by the current component.
|
||||
* Reading data from the returned object (eg during rendering) will cause the
|
||||
* component to subscribe to that data and be rerendered when it changes.
|
||||
*
|
||||
* @param state the state to observe
|
||||
* @returns a reactive object that will cause the component to re-render on
|
||||
* relevant changes
|
||||
* @see reactive
|
||||
*/
|
||||
export function useState<T extends object>(state: T): Reactive<T> {
|
||||
const node = getCurrent()!;
|
||||
if (!batchedRenderFunctions.has(node)) {
|
||||
batchedRenderFunctions.set(
|
||||
node,
|
||||
batched(() => node.render())
|
||||
);
|
||||
onWillUnmount(() => clearReactivesForCallback(render));
|
||||
}
|
||||
const render = batchedRenderFunctions.get(node)!;
|
||||
const reactiveState = reactive(state, render);
|
||||
return reactiveState;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,35 @@ exports[`properly support svg namespace to svg tags added even if already in svg
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg svg namespace added to sub-blocks (t-call) 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { getTemplate } = helpers;
|
||||
const callTemplate_1 = getTemplate(\`path\`, \\"http://www.w3.org/2000/svg\\");
|
||||
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg svg namespace added to sub-blocks (t-call) 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<path/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg svg namespace added to sub-blocks 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers";
|
||||
import { mount } from "../../src/blockdom";
|
||||
import { mount as mountComponent, Component } from "../../src/index"
|
||||
|
||||
// NB: check the snapshots to see where the SVG namespaces are added
|
||||
snapshotEverything();
|
||||
|
||||
@@ -52,4 +54,24 @@ describe("properly support svg", () => {
|
||||
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||
}
|
||||
});
|
||||
|
||||
test.only("svg namespace added to sub-blocks (t-call)", async () => {
|
||||
const templates = `<t>
|
||||
<t t-name="svg"><svg><t t-call="path" /></svg></t>
|
||||
<t t-name="path"><path /></t>
|
||||
</t>
|
||||
`
|
||||
const fixture = makeTestFixture();
|
||||
|
||||
class Svg extends Component {
|
||||
static template = "svg";
|
||||
}
|
||||
|
||||
await mountComponent(Svg, fixture, {templates})
|
||||
const elems = fixture.querySelectorAll("svg, path");
|
||||
expect(elems.length).toEqual(2);
|
||||
for (const el of elems) {
|
||||
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -968,7 +968,7 @@ exports[`basics update props of component without concrete own node 1`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const tKey_1 = ctx['childProps'].key;
|
||||
let b2 = toggler(tKey_1, component(\`Child\`, Object.assign({}, ctx['childProps']), tKey_1 + key + \`__1\`, node, ctx));
|
||||
let b2 = toggler(tKey_1, component(\`Child\`, ctx['childProps'], tKey_1 + key + \`__1\`, node, ctx));
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -1145,7 +1145,7 @@ exports[`properly behave when destroyed/unmounted while rendering 2`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`SubChild\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx);
|
||||
let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -499,7 +499,7 @@ exports[`lifecycle hooks onWillRender 1`] = `
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`force render in case of existing render 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`B\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`force render in case of existing render 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`C\`, {}, key + \`__1\`, node, ctx);
|
||||
let b3 = text(ctx['props'].val);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`force render in case of existing render 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`C\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can force a render to update sub tree 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].value);
|
||||
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can force a render to update sub tree 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`child\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can render a parent without rendering child 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].value);
|
||||
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics can render a parent without rendering child 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`child\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics props are reactive (nested prop) 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics props are reactive (nested prop) 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'].a.b.c);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics props are reactive 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics props are reactive 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'].a.b);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics rendering is atomic (for one subtree) 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].obj.val);
|
||||
let b3 = component(\`B\`, {obj: ctx['state'].obj}, key + \`__1\`, node, ctx);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics rendering is atomic (for one subtree) 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`C\`, {obj: ctx['props'].obj}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`rendering semantics rendering is atomic (for one subtree) 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(ctx['props'].obj.val);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -8,7 +8,7 @@ exports[`t-props basic use 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`Child\`, Object.assign({}, ctx['some'].obj), key + \`__1\`, node, ctx);
|
||||
let b2 = component(\`Child\`, ctx['some'].obj, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
@@ -65,7 +65,7 @@ exports[`t-props t-props only 1`] = `
|
||||
let block1 = createBlock(\`<div><div><block-child-0/></div></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`Comp\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx);
|
||||
let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -121,7 +121,7 @@ describe("basics", () => {
|
||||
class Test extends Component {
|
||||
static template = xml`<span>simple vnode</span>`;
|
||||
setup() {
|
||||
expect(this.props).not.toBe(p);
|
||||
expect(this.props).toBe(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ Scheduler.prototype.addFiber = function (fiber: Fiber) {
|
||||
|
||||
afterEach(() => {
|
||||
if (lastScheduler && lastScheduler.tasks.size > 0) {
|
||||
// we still clear the scheduler to prevent additional noise
|
||||
lastScheduler.tasks.clear();
|
||||
throw new Error("we got a memory leak...");
|
||||
}
|
||||
});
|
||||
@@ -523,7 +521,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
|
||||
}
|
||||
|
||||
class Child extends Component {
|
||||
static template = xml`<div><SubChild val="props.val"/></div>`;
|
||||
static template = xml`<div><SubChild /></div>`;
|
||||
static components = { SubChild };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
@@ -1909,13 +1907,18 @@ test("concurrent renderings scenario 13", async () => {
|
||||
await nextTick(); // wait for this change to be applied
|
||||
expect([
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:mounted",
|
||||
"Child:patched",
|
||||
"Parent:patched",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
@@ -2469,9 +2472,9 @@ test("two renderings initiated between willPatch and patched", async () => {
|
||||
useLogLifecycle();
|
||||
onMounted(() => {
|
||||
this.mounted = "Mounted";
|
||||
parent.render(true);
|
||||
parent.render();
|
||||
});
|
||||
onWillUnmount(() => parent.render(true));
|
||||
onWillUnmount(() => parent.render());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2504,11 +2507,15 @@ test("two renderings initiated between willPatch and patched", async () => {
|
||||
"Parent:rendered",
|
||||
]).toBeLogged();
|
||||
|
||||
await nextMicroTick();
|
||||
expect(["Panel:willRender", "Panel:rendered"]).toBeLogged();
|
||||
|
||||
await nextTick();
|
||||
expect(["Parent:willPatch", "Panel:willPatch", "Panel:patched", "Parent:patched"]).toBeLogged();
|
||||
expect([
|
||||
"Panel:willRender",
|
||||
"Panel:rendered",
|
||||
"Parent:willPatch",
|
||||
"Panel:willPatch",
|
||||
"Panel:patched",
|
||||
"Parent:patched",
|
||||
]).toBeLogged();
|
||||
expect(fixture.innerHTML).toBe("<div><abc>Panel1Mounted</abc></div>");
|
||||
|
||||
parent.state.panel = "Panel2";
|
||||
@@ -2746,20 +2753,12 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
||||
static template = xml`<Parent state="state"/>`;
|
||||
static components = { Parent };
|
||||
state = { value: 0 };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(GrandParent, fixture);
|
||||
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
||||
expect([
|
||||
"GrandParent:setup",
|
||||
"GrandParent:willStart",
|
||||
"GrandParent:willRender",
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"GrandParent:rendered",
|
||||
"Parent:willRender",
|
||||
"DelayedChild:setup",
|
||||
"DelayedChild:willStart",
|
||||
@@ -2773,23 +2772,20 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
||||
"ReactiveChild:mounted",
|
||||
"DelayedChild:mounted",
|
||||
"Parent:mounted",
|
||||
"GrandParent:mounted",
|
||||
]).toBeLogged();
|
||||
|
||||
promise = makeDeferred();
|
||||
const prom1 = promise;
|
||||
parent.state.value = 1;
|
||||
child.render(); // trigger a root rendering first
|
||||
parent.render(true);
|
||||
parent.render();
|
||||
reactiveChild.render();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
||||
expect([
|
||||
"DelayedChild:willRender",
|
||||
"DelayedChild:rendered",
|
||||
"GrandParent:willRender",
|
||||
"Parent:willUpdateProps",
|
||||
"GrandParent:rendered",
|
||||
"ReactiveChild:willRender",
|
||||
"ReactiveChild:rendered",
|
||||
"Parent:willRender",
|
||||
@@ -2804,14 +2800,12 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
||||
const prom2 = promise;
|
||||
child.render(); // trigger a root rendering first
|
||||
parent.state.value = 2;
|
||||
parent.render(true);
|
||||
parent.render();
|
||||
reactiveChild.render();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
||||
expect([
|
||||
"GrandParent:willRender",
|
||||
"Parent:willUpdateProps",
|
||||
"GrandParent:rendered",
|
||||
"ReactiveChild:willRender",
|
||||
"ReactiveChild:rendered",
|
||||
"Parent:willRender",
|
||||
@@ -2828,14 +2822,12 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
||||
expect([
|
||||
"DelayedChild:willRender",
|
||||
"DelayedChild:rendered",
|
||||
"GrandParent:willPatch",
|
||||
"Parent:willPatch",
|
||||
"ReactiveChild:willPatch",
|
||||
"DelayedChild:willPatch",
|
||||
"DelayedChild:patched",
|
||||
"ReactiveChild:patched",
|
||||
"Parent:patched",
|
||||
"GrandParent:patched",
|
||||
]).toBeLogged();
|
||||
|
||||
prom1.resolve();
|
||||
|
||||
@@ -223,7 +223,7 @@ describe("hooks", () => {
|
||||
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
||||
someVal = "brain";
|
||||
someVal2 = "maggot";
|
||||
component.render(true);
|
||||
component.render();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
|
||||
});
|
||||
|
||||
@@ -849,9 +849,8 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Child someValue="state.value" />`;
|
||||
<Child />`;
|
||||
static components = { Child };
|
||||
state = useState({ value: 1 });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
@@ -872,7 +871,7 @@ describe("lifecycle hooks", () => {
|
||||
"Parent:mounted",
|
||||
]).toBeLogged();
|
||||
|
||||
parent.state.value++; // to block child render
|
||||
parent.render(); // to block child render
|
||||
await nextTick();
|
||||
expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged();
|
||||
|
||||
@@ -1009,15 +1008,20 @@ describe("lifecycle hooks", () => {
|
||||
await nextTick();
|
||||
expect([
|
||||
"C:willRender",
|
||||
"D:willUpdateProps",
|
||||
"F:setup",
|
||||
"F:willStart",
|
||||
"C:rendered",
|
||||
"D:willRender",
|
||||
"D:rendered",
|
||||
"F:willRender",
|
||||
"F:rendered",
|
||||
"C:willPatch",
|
||||
"D:willPatch",
|
||||
"E:willUnmount",
|
||||
"E:willDestroy",
|
||||
"F:mounted",
|
||||
"D:patched",
|
||||
"C:patched",
|
||||
]).toBeLogged();
|
||||
});
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
import { Component, mount, onRendered, onWillUpdateProps, useState, xml } from "../../src";
|
||||
import {
|
||||
makeTestFixture,
|
||||
snapshotEverything,
|
||||
nextTick,
|
||||
useLogLifecycle,
|
||||
makeDeferred,
|
||||
} from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("rendering semantics", () => {
|
||||
test("can render a parent without rendering child", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`child`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-esc="state.value"/>
|
||||
<Child/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({ value: "A" });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("Achild");
|
||||
expect([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]).toBeLogged();
|
||||
|
||||
parent.state.value = "B";
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("Bchild");
|
||||
expect([
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Parent:patched",
|
||||
]).toBeLogged();
|
||||
});
|
||||
|
||||
test("can force a render to update sub tree", async () => {
|
||||
let childN = 0;
|
||||
let parentN = 0;
|
||||
class Child extends Component {
|
||||
static template = xml`child`;
|
||||
setup() {
|
||||
onRendered(() => childN++);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<t t-esc="state.value"/>
|
||||
<Child/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = { value: "A" };
|
||||
setup() {
|
||||
onRendered(() => parentN++);
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("Achild");
|
||||
expect(parentN).toBe(1);
|
||||
expect(childN).toBe(1);
|
||||
|
||||
parent.state.value = "B";
|
||||
parent.render(true);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("Bchild");
|
||||
expect(parentN).toBe(2);
|
||||
expect(childN).toBe(2);
|
||||
});
|
||||
|
||||
test("props are reactive", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.a.b"/>`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Child a="state"/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({ b: 1 });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("1");
|
||||
expect([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]).toBeLogged();
|
||||
|
||||
parent.state.b = 3;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("3");
|
||||
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged();
|
||||
});
|
||||
|
||||
test("props are reactive (nested prop)", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.a.b.c"/>`;
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<Child a="state"/>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
state = useState({ b: { c: 1 } });
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("1");
|
||||
expect([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]).toBeLogged();
|
||||
|
||||
parent.state.b.c = 3; // parent is now subscribed to 'b' key
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("3");
|
||||
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged();
|
||||
|
||||
parent.state.b = { c: 444 }; // triggers a parent and a child render
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("444");
|
||||
expect([
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Parent:patched",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
]).toBeLogged();
|
||||
});
|
||||
|
||||
test("rendering is atomic (for one subtree)", async () => {
|
||||
const def = makeDeferred();
|
||||
|
||||
class C extends Component {
|
||||
static template = xml`<t t-esc="props.obj.val"/>`;
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
class B extends Component {
|
||||
static template = xml`<C obj="props.obj"/>`;
|
||||
static components = { C };
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onWillUpdateProps(() => def);
|
||||
}
|
||||
}
|
||||
|
||||
class A extends Component {
|
||||
static template = xml`<t t-esc="state.obj.val"/><B obj="state.obj"/>`;
|
||||
static components = { B };
|
||||
|
||||
state = useState({ obj: { val: 1 } });
|
||||
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(A, fixture);
|
||||
expect(fixture.innerHTML).toBe("11");
|
||||
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();
|
||||
|
||||
parent.state.obj.val = 3;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("33");
|
||||
expect([
|
||||
"A:willRender",
|
||||
"A:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"A:willPatch",
|
||||
"A:patched",
|
||||
"C:willPatch",
|
||||
"C:patched",
|
||||
]).toBeLogged();
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
expect([]).toBeLogged();
|
||||
});
|
||||
});
|
||||
|
||||
test("force render in case of existing render", async () => {
|
||||
const def = makeDeferred();
|
||||
|
||||
class C extends Component {
|
||||
static template = xml`C`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
class B extends Component {
|
||||
static template = xml`<C/><t t-esc="props.val"/>`;
|
||||
static components = { C };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onWillUpdateProps(() => def);
|
||||
}
|
||||
}
|
||||
class A extends Component {
|
||||
static template = xml`<B val="state.val"/>`;
|
||||
static components = { B };
|
||||
state = useState({ val: 1 });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
const parent = await mount(A, fixture);
|
||||
expect(fixture.innerHTML).toBe("C1");
|
||||
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();
|
||||
|
||||
// trigger a new rendering, blocked in B
|
||||
parent.state.val = 2;
|
||||
await nextTick();
|
||||
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
|
||||
|
||||
// initiate a new render with force=true. it should cancel the current render
|
||||
// and also be blocked in B
|
||||
parent.render(true);
|
||||
await nextTick();
|
||||
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
// we check here that the render reaches C (so, that it was properly forced)
|
||||
expect([
|
||||
"B:willRender",
|
||||
"C:willUpdateProps",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"A:willPatch",
|
||||
"B:willPatch",
|
||||
"C:willPatch",
|
||||
"C:patched",
|
||||
"B:patched",
|
||||
"A:patched",
|
||||
]).toBeLogged();
|
||||
});
|
||||
@@ -65,7 +65,7 @@ describe("t-props", () => {
|
||||
`;
|
||||
setup() {
|
||||
expect(this.props).toEqual({ a: 1, b: 2 });
|
||||
expect(this.props).not.toBe(props);
|
||||
expect(this.props).toBe(props);
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
|
||||
@@ -1612,7 +1612,7 @@ describe("Reactivity: useState", () => {
|
||||
expect([...steps]).toEqual(["list"]);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>");
|
||||
expect([...steps]).toEqual(["list"]);
|
||||
expect([...steps]).toEqual(["list", "quantity1"]);
|
||||
steps.clear();
|
||||
|
||||
secondQuantity.quantity = 2;
|
||||
|
||||
Reference in New Issue
Block a user