mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 076b0d774e | |||
| f405fe9323 | |||
| 1ae9d514b9 | |||
| 592d9a458e | |||
| 0dbd2bd463 | |||
| 8ec7a6f9bf | |||
| 12b8ce963e | |||
| bb9d65e95b |
+26
-1
@@ -31,6 +31,7 @@ All changes are documented here in no particular order.
|
|||||||
- breaking: Support for inline css (`css` tag and static `style`) has been removed ([details](#37-support-for-inline-css-css-tag-and-static-style-has-been-removed))
|
- breaking: Support for inline css (`css` tag and static `style`) has been removed ([details](#37-support-for-inline-css-css-tag-and-static-style-has-been-removed))
|
||||||
- new: prop validation system can now describe that additional props are allowed (with `*`) ([doc](doc/reference/props.md#props-validation))
|
- new: prop validation system can now describe that additional props are allowed (with `*`) ([doc](doc/reference/props.md#props-validation))
|
||||||
- breaking: prop validation system does not allow default prop on a mandatory (not optional) prop ([doc](doc/reference/props.md#props-validation))
|
- breaking: prop validation system does not allow default prop on a mandatory (not optional) prop ([doc](doc/reference/props.md#props-validation))
|
||||||
|
- breaking: rendering a component does not necessarily render child components ([details](#40-rendering-a-component-does-not-necessarily-render-child-components))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -798,4 +799,28 @@ Rationale: the `browser` object caused more trouble than it was worth. Also, it
|
|||||||
seems like this should be done in user space, not at the framework level.
|
seems like this should be done in user space, not at the framework level.
|
||||||
|
|
||||||
Migration: code should just be adapted to either use another browser object,
|
Migration: code should just be adapted to either use another browser object,
|
||||||
or to use native browser function (and then, just mock them directly).
|
or to use native browser function (and then, just mock them directly).
|
||||||
|
|
||||||
|
## 40. Rendering a component does not necessarily render child components
|
||||||
|
|
||||||
|
Before, if one had the following component tree:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
A-->C;
|
||||||
|
```
|
||||||
|
|
||||||
|
when `A` would render, it would also render `B` and `C`. Now, in Owl 2, it will
|
||||||
|
(shallow) compare the before and after props, and `B` or `C` will only be rerendered
|
||||||
|
if their props have changed.
|
||||||
|
|
||||||
|
Now, the question is what happens if the props have changed, but in a deeper way?
|
||||||
|
In that case, Owl will know, because each props are now reactive. So, if some
|
||||||
|
inner value read by `B` was changed, then only `B` will be updated.
|
||||||
|
|
||||||
|
Rationale: This was just not possible in Owl 1, but it now possible. This is
|
||||||
|
due to the rewriteof the underlying rendering engine and the reactivity
|
||||||
|
system. The goal is to have a big performance boost in large screen with many
|
||||||
|
components: now Owl only rerender what is strictly useful.
|
||||||
|
|
||||||
|
|||||||
@@ -68,12 +68,16 @@ The `Component` class has a very small API.
|
|||||||
component will go through the following lifecycle methods: `willUpdateProps`,
|
component will go through the following lifecycle methods: `willUpdateProps`,
|
||||||
`willPatch` and `patched`.
|
`willPatch` and `patched`.
|
||||||
|
|
||||||
* **`render()`**: calling this method directly will cause a rerender. Note
|
* **`render(deep[=false])`**: calling this method directly will cause a rerender. Note
|
||||||
that with the reactivity system, this should be rare to have to do it manually.
|
that with the reactivity system, this should be rare to have to do it manually.
|
||||||
Also, the rendering operation is asynchronous, so the DOM will only be updated
|
Also, the rendering operation is asynchronous, so the DOM will only be updated
|
||||||
slightly later (at the next animation frame, if no component delays the
|
slightly later (at the next animation frame, if no component delays the
|
||||||
rendering)
|
rendering)
|
||||||
|
|
||||||
|
By default, the render initiated by this method will stop at each child
|
||||||
|
component if their props are (shallow) equal. To force a render to update
|
||||||
|
all child components, one can use the optional `deep` argument.
|
||||||
|
|
||||||
## Static Properties
|
## Static Properties
|
||||||
|
|
||||||
- **`template (string)`**: this is the name of the template that
|
- **`template (string)`**: this is the name of the template that
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ To solve this issue, Owl provides two reactivity primitives:
|
|||||||
|
|
||||||
Most of the time, the `useState` hook is the best solution.
|
Most of the time, the `useState` hook is the best solution.
|
||||||
|
|
||||||
|
Since version 2.0, Owl applies the fine grained reactivity at the component
|
||||||
|
level: props are automatically turned into reactive object, so Owl can track
|
||||||
|
which part of these props are consumed by each component, and is therefore able
|
||||||
|
to only rerender the impacted components.
|
||||||
|
|
||||||
## `useState`
|
## `useState`
|
||||||
|
|
||||||
Let us start by an example of how `useState` could be used:
|
Let us start by an example of how `useState` could be used:
|
||||||
@@ -117,3 +122,10 @@ rawState.value = 3; // will NOT be picked up by the reactivity system!!!
|
|||||||
Here again, this is useful in some situations where we want to explicitely bypass
|
Here again, this is useful in some situations where we want to explicitely bypass
|
||||||
Owl, but using this function means that the responsability of coordinating
|
Owl, but using this function means that the responsability of coordinating
|
||||||
state update is given to the user code, instead of Owl. Subtle bugs may arise!
|
state update is given to the user code, instead of Owl. Subtle bugs may arise!
|
||||||
|
|
||||||
|
Also, normal (non-reactive objects) will be directly returned by `toRaw`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const obj = { a: 1 };
|
||||||
|
console.log(toRaw(obj) === obj); // true
|
||||||
|
```
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
- [Fragments](#fragments)
|
- [Fragments](#fragments)
|
||||||
- [Inline templates](#inline-templates)
|
- [Inline templates](#inline-templates)
|
||||||
- [Rendering svg](#rendering-svg)
|
- [Rendering svg](#rendering-svg)
|
||||||
|
- [Restrictions](#restrictions)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -680,3 +681,13 @@ if a template is supposed to be included in a svg namespace or not. Therefore,
|
|||||||
Owl depends on a heuristic: if a tag is either `svg`, `g` or `path`, then it will
|
Owl depends on a heuristic: if a tag is either `svg`, `g` or `path`, then it will
|
||||||
be considered as svg. In practice, this means that each component or each sub
|
be considered as svg. In practice, this means that each component or each sub
|
||||||
templates (included with `t-call`) should have one of these tag as root tag.
|
templates (included with `t-call`) should have one of these tag as root tag.
|
||||||
|
|
||||||
|
## Restrictions
|
||||||
|
|
||||||
|
Note that Owl templates forbid the use of tag and or attributes starting with
|
||||||
|
the `block-` string. This restriction prevents name collision with the internal
|
||||||
|
code of Owl.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div><block-1>this will not be accepted by Owl</block-1></div>
|
||||||
|
```
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.0.0-alpha.2",
|
"version": "2.0.0-alpha.3",
|
||||||
"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",
|
||||||
|
|||||||
+6
-2
@@ -29,7 +29,7 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
|||||||
|
|
||||||
export class App<
|
export class App<
|
||||||
T extends abstract new (...args: any) => any = any,
|
T extends abstract new (...args: any) => any = any,
|
||||||
P = any,
|
P extends object = any,
|
||||||
E = any
|
E = any
|
||||||
> extends TemplateSet {
|
> extends TemplateSet {
|
||||||
static validateTarget = validateTarget;
|
static validateTarget = validateTarget;
|
||||||
@@ -102,7 +102,11 @@ export class App<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mount<T extends abstract new (...args: any) => any = any, P = any, E = any>(
|
export async function mount<
|
||||||
|
T extends abstract new (...args: any) => any = any,
|
||||||
|
P extends object = any,
|
||||||
|
E = any
|
||||||
|
>(
|
||||||
C: T & ComponentConstructor<P, E>,
|
C: T & ComponentConstructor<P, E>,
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
config: AppConfig<P, E> & MountOptions = {}
|
config: AppConfig<P, E> & MountOptions = {}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { BDom, multi, text, toggler } from "../blockdom";
|
|||||||
import { validateProps } from "../component/props_validation";
|
import { validateProps } from "../component/props_validation";
|
||||||
import { Markup } from "../utils";
|
import { Markup } from "../utils";
|
||||||
import { html } from "../blockdom/index";
|
import { html } from "../blockdom/index";
|
||||||
|
import { TARGET } from "../reactivity";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains utility functions that will be injected in each template,
|
* This file contains utility functions that will be injected in each template,
|
||||||
* to perform various useful tasks in the compiled code.
|
* to perform various useful tasks in the compiled code.
|
||||||
@@ -21,7 +23,7 @@ function callSlot(
|
|||||||
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
||||||
): BDom {
|
): BDom {
|
||||||
key = key + "__slot_" + name;
|
key = key + "__slot_" + name;
|
||||||
const slots = (ctx.props && ctx.props.slots) || {};
|
const slots = ctx.props[TARGET].slots || {};
|
||||||
const { __render, __ctx, __scope } = slots[name] || {};
|
const { __render, __ctx, __scope } = slots[name] || {};
|
||||||
const slotScope = Object.create(__ctx || {});
|
const slotScope = Object.create(__ctx || {});
|
||||||
if (__scope) {
|
if (__scope) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
|
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
|
||||||
import { compile, Template } from "../compiler";
|
import { compile, Template } from "../compiler";
|
||||||
import { component } from "../component/component_node";
|
import { component, getCurrent } from "../component/component_node";
|
||||||
import { UTILS } from "./template_helpers";
|
import { UTILS } from "./template_helpers";
|
||||||
|
|
||||||
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
|
||||||
@@ -94,7 +94,12 @@ export class TemplateSet {
|
|||||||
if (!(name in this.templates)) {
|
if (!(name in this.templates)) {
|
||||||
const rawTemplate = this.rawTemplates[name];
|
const rawTemplate = this.rawTemplates[name];
|
||||||
if (rawTemplate === undefined) {
|
if (rawTemplate === undefined) {
|
||||||
throw new Error(`Missing template: "${name}"`);
|
let extraInfo = "";
|
||||||
|
try {
|
||||||
|
const componentName = getCurrent().component.constructor.name;
|
||||||
|
extraInfo = ` (for component "${componentName}")`;
|
||||||
|
} catch {}
|
||||||
|
throw new Error(`Missing template: "${name}"${extraInfo}`);
|
||||||
}
|
}
|
||||||
const templateFn = this._compileTemplate(name, rawTemplate);
|
const templateFn = this._compileTemplate(name, rawTemplate);
|
||||||
// first add a function to lazily get the template, in case there is a
|
// first add a function to lazily get the template, in case there is a
|
||||||
|
|||||||
@@ -525,54 +525,6 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
|
|||||||
this.parentEl = parent;
|
this.parentEl = parent;
|
||||||
}
|
}
|
||||||
patch(other: Block, withBeforeRemove: boolean) {}
|
patch(other: Block, withBeforeRemove: boolean) {}
|
||||||
|
|
||||||
hydrate(parent: HTMLElement, el: HTMLElement) {
|
|
||||||
this.parentEl = parent;
|
|
||||||
this.el = el;
|
|
||||||
const refs: Node[] = new Array(refN);
|
|
||||||
this.refs = refs;
|
|
||||||
refs[0] = el;
|
|
||||||
for (let i = 0; i < colN; i++) {
|
|
||||||
const w = collectors[i];
|
|
||||||
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// applying data to all update points
|
|
||||||
if (locN) {
|
|
||||||
const data = this.data!;
|
|
||||||
for (let i = 0; i < locN; i++) {
|
|
||||||
const loc = locations[i];
|
|
||||||
loc.setData.call(refs[loc.refIdx], data[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// preparing all children
|
|
||||||
if (childN) {
|
|
||||||
const children = this.children;
|
|
||||||
for (let i = 0; i < childN; i++) {
|
|
||||||
const child = children![i];
|
|
||||||
if (child) {
|
|
||||||
const loc = childrenLocs[i];
|
|
||||||
let target: HTMLElement;
|
|
||||||
if (loc.afterRefIdx) {
|
|
||||||
target = refs[loc.afterRefIdx] as HTMLElement;
|
|
||||||
const afterNode = document.createTextNode("");
|
|
||||||
target.parentElement!.insertBefore(afterNode, target.nextSibling);
|
|
||||||
refs[loc.afterRefIdx!] = afterNode;
|
|
||||||
} else {
|
|
||||||
target = refs[loc.parentRefIdx].firstChild! as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const target = (loc.afterRefIdx ? refs[loc.afterRefIdx] : null) as HTMLElement;
|
|
||||||
// const afterNode = document.createTextNode("");
|
|
||||||
// target.parentElement!.insertBefore(afterNode, target.nextSibling);
|
|
||||||
// refs[loc.afterRefIdx!] = afterNode;
|
|
||||||
child.isOnlyChild = loc.isOnlyChild;
|
|
||||||
(child as any).hydrate(target.parentElement, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDynamic) {
|
if (isDynamic) {
|
||||||
|
|||||||
@@ -42,7 +42,3 @@ export function withKey(vnode: VNode, key: any) {
|
|||||||
vnode.key = key;
|
vnode.key = key;
|
||||||
return vnode;
|
return vnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hydrate(vnode: VNode, target: HTMLElement) {
|
|
||||||
(vnode as any).hydrate(target.parentElement, target);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,12 +23,6 @@ abstract class VSimpleNode {
|
|||||||
this.el = node;
|
this.el = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(parent: HTMLElement, elem: Node) {
|
|
||||||
this.parentEl = parent;
|
|
||||||
this.el = elem;
|
|
||||||
// this.mountNode(elem, parent, elem.nextSibling);
|
|
||||||
}
|
|
||||||
|
|
||||||
moveBefore(other: VText | null, afterNode: Node | null) {
|
moveBefore(other: VText | null, afterNode: Node | null) {
|
||||||
const target = other ? other.el! : afterNode;
|
const target = other ? other.el! : afterNode;
|
||||||
nodeInsertBefore.call(this.parentEl, this.el!, target);
|
nodeInsertBefore.call(this.parentEl, this.el!, target);
|
||||||
|
|||||||
@@ -1125,18 +1125,17 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
||||||
props.push(`slots: ${slotDef}`);
|
this.helpers.add("markRaw");
|
||||||
|
props.push(`slots: markRaw(${slotDef})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const propStr = `{${props.join(",")}}`;
|
const propStr = `{${props.join(",")}}`;
|
||||||
|
|
||||||
let propString = propStr;
|
let propString = propStr;
|
||||||
if (ast.dynamicProps) {
|
if (ast.dynamicProps) {
|
||||||
if (!props.length) {
|
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${
|
||||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
|
props.length ? ", " + propStr : ""
|
||||||
} else {
|
})`;
|
||||||
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let propVar: string;
|
let propVar: string;
|
||||||
@@ -1147,7 +1146,8 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
|
||||||
this.addLine(`${propVar!}.slots = Object.assign(${slotDef}, ${propVar!}.slots)`);
|
this.helpers.add("markRaw");
|
||||||
|
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotDef}, ${propVar!}.slots))`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmap key
|
// cmap key
|
||||||
|
|||||||
@@ -305,6 +305,9 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
if (tagName === "t" && !dynamicTag) {
|
if (tagName === "t" && !dynamicTag) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (tagName.startsWith("block-")) {
|
||||||
|
throw new Error(`Invalid tag name: '${tagName}'`);
|
||||||
|
}
|
||||||
ctx = Object.assign({}, ctx);
|
ctx = Object.assign({}, ctx);
|
||||||
if (tagName === "pre") {
|
if (tagName === "pre") {
|
||||||
ctx.inPreTag = true;
|
ctx.inPreTag = true;
|
||||||
@@ -371,6 +374,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
|||||||
ctx = Object.assign({}, ctx);
|
ctx = Object.assign({}, ctx);
|
||||||
ctx.tModelInfo = model;
|
ctx.tModelInfo = model;
|
||||||
}
|
}
|
||||||
|
} else if (attr.startsWith("block-")) {
|
||||||
|
throw new Error(`Invalid attribute: '${attr}'`);
|
||||||
} else if (attr !== "t-name") {
|
} else if (attr !== "t-name") {
|
||||||
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
|
||||||
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
throw new Error(`Unknown QWeb directive: '${attr}'`);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class Component<Props = any, Env = any> {
|
|||||||
|
|
||||||
setup() {}
|
setup() {}
|
||||||
|
|
||||||
render() {
|
render(deep: boolean = false) {
|
||||||
this.__owl__.render();
|
this.__owl__.render(deep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { App, Env } from "../app/app";
|
import type { App, Env } from "../app/app";
|
||||||
import { BDom, VNode } from "../blockdom";
|
import { BDom, VNode } from "../blockdom";
|
||||||
|
import { clearReactivesForCallback, Reactive, reactive, TARGET, NonReactive } from "../reactivity";
|
||||||
|
import { batched, Callback } from "../utils";
|
||||||
import { Component, ComponentConstructor } from "./component";
|
import { Component, ComponentConstructor } from "./component";
|
||||||
|
import { fibersInError, handleError } from "./error_handling";
|
||||||
import {
|
import {
|
||||||
Fiber,
|
Fiber,
|
||||||
makeChildFiber,
|
makeChildFiber,
|
||||||
@@ -9,7 +12,6 @@ import {
|
|||||||
MountOptions,
|
MountOptions,
|
||||||
RootFiber,
|
RootFiber,
|
||||||
} from "./fibers";
|
} from "./fibers";
|
||||||
import { handleError, fibersInError } from "./error_handling";
|
|
||||||
import { applyDefaultProps } from "./props_validation";
|
import { applyDefaultProps } from "./props_validation";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
|
|
||||||
@@ -26,13 +28,54 @@ export function useComponent(): Component {
|
|||||||
return currentNode!.component;
|
return currentNode!.component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function component(
|
// -----------------------------------------------------------------------------
|
||||||
name: string | typeof Component,
|
// Integration with reactivity system (useState)
|
||||||
props: any,
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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> | NonReactive<T> {
|
||||||
|
const node = getCurrent();
|
||||||
|
let render = batchedRenderFunctions.get(node)!;
|
||||||
|
if (!render) {
|
||||||
|
render = batched(node.render.bind(node));
|
||||||
|
batchedRenderFunctions.set(node, render);
|
||||||
|
// manual implementation of onWillDestroy to break cyclic dependency
|
||||||
|
node.willDestroy.push(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 Object.keys(props1).length !== Object.keys(props2).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function component<P extends object>(
|
||||||
|
name: string | ComponentConstructor<P>,
|
||||||
|
props: P,
|
||||||
key: string,
|
key: string,
|
||||||
ctx: ComponentNode,
|
ctx: ComponentNode,
|
||||||
parent: any
|
parent: any
|
||||||
): ComponentNode {
|
): ComponentNode<P> {
|
||||||
let node: any = ctx.children[key];
|
let node: any = ctx.children[key];
|
||||||
let isDynamic = typeof name !== "string";
|
let isDynamic = typeof name !== "string";
|
||||||
|
|
||||||
@@ -50,7 +93,10 @@ export function component(
|
|||||||
|
|
||||||
const parentFiber = ctx.fiber!;
|
const parentFiber = ctx.fiber!;
|
||||||
if (node) {
|
if (node) {
|
||||||
node.updateAndRender(props, parentFiber);
|
const currentProps = node.component.props[TARGET];
|
||||||
|
if (parentFiber.deep || arePropsDifferent(currentProps, props)) {
|
||||||
|
node.updateAndRender(props, parentFiber);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// new component
|
// new component
|
||||||
let C;
|
let C;
|
||||||
@@ -65,19 +111,18 @@ export function component(
|
|||||||
node = new ComponentNode(C, props, ctx.app, ctx);
|
node = new ComponentNode(C, props, ctx.app, ctx);
|
||||||
ctx.children[key] = node;
|
ctx.children[key] = node;
|
||||||
|
|
||||||
const fiber = makeChildFiber(node, parentFiber);
|
node.initiateRender(new Fiber(node, parentFiber));
|
||||||
node.initiateRender(fiber);
|
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Component VNode
|
// Component VNode class
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
type LifecycleHook = Function;
|
type LifecycleHook = Function;
|
||||||
|
|
||||||
export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E>> {
|
export class ComponentNode<P extends object = any, E = any> implements VNode<ComponentNode<P, E>> {
|
||||||
el?: HTMLElement | Text | undefined;
|
el?: HTMLElement | Text | undefined;
|
||||||
app: App;
|
app: App;
|
||||||
fiber: Fiber | null = null;
|
fiber: Fiber | null = null;
|
||||||
@@ -108,6 +153,7 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
|
|||||||
applyDefaultProps(props, C);
|
applyDefaultProps(props, C);
|
||||||
const env = (parent && parent.childEnv) || app.env;
|
const env = (parent && parent.childEnv) || app.env;
|
||||||
this.childEnv = env;
|
this.childEnv = env;
|
||||||
|
props = useState(props);
|
||||||
this.component = new C(props, env, this) as any;
|
this.component = new C(props, env, this) as any;
|
||||||
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
||||||
this.component.setup();
|
this.component.setup();
|
||||||
@@ -137,21 +183,29 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async render() {
|
async render(deep: boolean = false) {
|
||||||
let current = this.fiber;
|
let current = this.fiber;
|
||||||
if (current && current.root!.locked) {
|
if (current && current.root!.locked) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
if (current && !current.bdom && !fibersInError.has(current)) {
|
if (current) {
|
||||||
return;
|
if (!current.bdom && !fibersInError.has(current)) {
|
||||||
}
|
if (deep) {
|
||||||
if (!this.bdom && !current) {
|
// we want the render from this point on to be with deep=true
|
||||||
|
current.deep = deep;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if current rendering was with deep=true, we want this one to be the same
|
||||||
|
deep = deep || current.deep;
|
||||||
|
} else if (!this.bdom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fiber = makeRootFiber(this);
|
const fiber = makeRootFiber(this);
|
||||||
|
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();
|
||||||
@@ -213,6 +267,10 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
|
|||||||
this.fiber = fiber;
|
this.fiber = fiber;
|
||||||
const component = this.component;
|
const component = this.component;
|
||||||
applyDefaultProps(props, component.constructor as any);
|
applyDefaultProps(props, component.constructor as any);
|
||||||
|
|
||||||
|
currentNode = this;
|
||||||
|
props = useState(props);
|
||||||
|
currentNode = null;
|
||||||
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
|
||||||
await prom;
|
await prom;
|
||||||
if (fiber !== this.fiber) {
|
if (fiber !== this.fiber) {
|
||||||
@@ -272,20 +330,19 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
|
|||||||
this.fiber = null;
|
this.fiber = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(parent: HTMLElement, el: HTMLElement) {
|
|
||||||
const bdom = this.fiber!.bdom!;
|
|
||||||
this.bdom = bdom;
|
|
||||||
(bdom as any).hydrate(parent, el);
|
|
||||||
this.status = STATUS.MOUNTED;
|
|
||||||
this.fiber!.appliedToDom = true;
|
|
||||||
this.fiber = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
moveBefore(other: ComponentNode | null, afterNode: Node | null) {
|
moveBefore(other: ComponentNode | null, afterNode: Node | null) {
|
||||||
this.bdom!.moveBefore(other ? other.bdom : null, afterNode);
|
this.bdom!.moveBefore(other ? other.bdom : null, afterNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
patch() {
|
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;
|
const hasChildren = Object.keys(this.children).length > 0;
|
||||||
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
|
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
|
|||||||
+9
-16
@@ -1,4 +1,4 @@
|
|||||||
import { BDom, hydrate, mount } from "../blockdom";
|
import { BDom, mount } from "../blockdom";
|
||||||
import type { ComponentNode } from "./component_node";
|
import type { ComponentNode } from "./component_node";
|
||||||
import { fibersInError, handleError } from "./error_handling";
|
import { fibersInError, handleError } from "./error_handling";
|
||||||
import { STATUS } from "./status";
|
import { STATUS } from "./status";
|
||||||
@@ -6,8 +6,7 @@ import { STATUS } from "./status";
|
|||||||
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
|
||||||
let current = node.fiber;
|
let current = node.fiber;
|
||||||
if (current) {
|
if (current) {
|
||||||
let root = parent.root;
|
cancelFibers(current.children);
|
||||||
cancelFibers(root, current.children);
|
|
||||||
current.root = null;
|
current.root = null;
|
||||||
}
|
}
|
||||||
return new Fiber(node, parent);
|
return new Fiber(node, parent);
|
||||||
@@ -17,9 +16,8 @@ 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!;
|
||||||
root.counter -= cancelFibers(root, current.children);
|
root.counter = root.counter + 1 - cancelFibers(current.children);
|
||||||
current.children = [];
|
current.children = [];
|
||||||
root.counter++;
|
|
||||||
current.bdom = null;
|
current.bdom = null;
|
||||||
if (fibersInError.has(current)) {
|
if (fibersInError.has(current)) {
|
||||||
fibersInError.delete(current);
|
fibersInError.delete(current);
|
||||||
@@ -35,22 +33,20 @@ export function makeRootFiber(node: ComponentNode): Fiber {
|
|||||||
if (node.patched.length) {
|
if (node.patched.length) {
|
||||||
fiber.patched.push(fiber);
|
fiber.patched.push(fiber);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fiber;
|
return fiber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns number of not-yet rendered fibers cancelled
|
* @returns number of not-yet rendered fibers cancelled
|
||||||
*/
|
*/
|
||||||
function cancelFibers(root: any, 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;
|
fiber.node.fiber = null;
|
||||||
fiber.root = root;
|
|
||||||
if (!fiber.bdom) {
|
if (!fiber.bdom) {
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
result += cancelFibers(root, fiber.children);
|
result += cancelFibers(fiber.children);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -62,11 +58,13 @@ export class Fiber {
|
|||||||
parent: Fiber | null;
|
parent: Fiber | null;
|
||||||
children: Fiber[] = [];
|
children: Fiber[] = [];
|
||||||
appliedToDom = false;
|
appliedToDom = false;
|
||||||
|
deep: boolean = false;
|
||||||
|
|
||||||
constructor(node: ComponentNode, parent: Fiber | null) {
|
constructor(node: ComponentNode, parent: Fiber | null) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
this.deep = parent.deep;
|
||||||
const root = parent.root!;
|
const root = parent.root!;
|
||||||
root.counter++;
|
root.counter++;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
@@ -109,7 +107,7 @@ export class RootFiber extends Fiber {
|
|||||||
current = undefined;
|
current = undefined;
|
||||||
|
|
||||||
// Step 2: patching the dom
|
// Step 2: patching the dom
|
||||||
node.patch();
|
node._patch();
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
|
|
||||||
// Step 4: calling all mounted lifecycle hooks
|
// Step 4: calling all mounted lifecycle hooks
|
||||||
@@ -144,18 +142,15 @@ type Position = "first-child" | "last-child";
|
|||||||
|
|
||||||
export interface MountOptions {
|
export interface MountOptions {
|
||||||
position?: Position;
|
position?: Position;
|
||||||
hydrate?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MountFiber extends RootFiber {
|
export class MountFiber extends RootFiber {
|
||||||
target: HTMLElement;
|
target: HTMLElement;
|
||||||
position: Position;
|
position: Position;
|
||||||
hydrate?: boolean;
|
|
||||||
|
|
||||||
constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) {
|
constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) {
|
||||||
super(node, null);
|
super(node, null);
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.hydrate = options.hydrate;
|
|
||||||
this.position = options.position || "last-child";
|
this.position = options.position || "last-child";
|
||||||
}
|
}
|
||||||
complete() {
|
complete() {
|
||||||
@@ -171,9 +166,7 @@ export class MountFiber extends RootFiber {
|
|||||||
node.updateDom();
|
node.updateDom();
|
||||||
} else {
|
} else {
|
||||||
node.bdom = this.bdom;
|
node.bdom = this.bdom;
|
||||||
if (this.hydrate) {
|
if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
||||||
hydrate(node.bdom!, this.target);
|
|
||||||
} else if (this.position === "last-child" || this.target.childNodes.length === 0) {
|
|
||||||
mount(node.bdom!, this.target);
|
mount(node.bdom!, this.target);
|
||||||
} else {
|
} else {
|
||||||
const firstChild = this.target.childNodes[0];
|
const firstChild = this.target.childNodes[0];
|
||||||
|
|||||||
+4
-2
@@ -14,11 +14,13 @@ import {
|
|||||||
} from "./blockdom";
|
} from "./blockdom";
|
||||||
import { mainEventHandler } from "./component/handler";
|
import { mainEventHandler } from "./component/handler";
|
||||||
import { Portal } from "./portal";
|
import { Portal } from "./portal";
|
||||||
|
import { markRaw } from "./reactivity";
|
||||||
export type { Reactive } from "./reactivity";
|
export type { Reactive } from "./reactivity";
|
||||||
|
|
||||||
config.shouldNormalizeDom = false;
|
config.shouldNormalizeDom = false;
|
||||||
config.mainEventHandler = mainEventHandler;
|
config.mainEventHandler = mainEventHandler;
|
||||||
(UTILS as any).Portal = Portal;
|
(UTILS as any).Portal = Portal;
|
||||||
|
(UTILS as any).markRaw = markRaw;
|
||||||
|
|
||||||
export const blockDom = {
|
export const blockDom = {
|
||||||
config,
|
config,
|
||||||
@@ -38,11 +40,11 @@ export const blockDom = {
|
|||||||
|
|
||||||
export { App, mount } from "./app/app";
|
export { App, mount } from "./app/app";
|
||||||
export { Component } from "./component/component";
|
export { Component } from "./component/component";
|
||||||
export { useComponent } from "./component/component_node";
|
export { useComponent, useState } from "./component/component_node";
|
||||||
export { status } from "./component/status";
|
export { status } from "./component/status";
|
||||||
export { Memo } from "./memo";
|
export { Memo } from "./memo";
|
||||||
export { xml } from "./app/template_set";
|
export { xml } from "./app/template_set";
|
||||||
export { useState, reactive, markRaw, toRaw } from "./reactivity";
|
export { reactive, markRaw, toRaw } from "./reactivity";
|
||||||
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
|
||||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||||
export {
|
export {
|
||||||
|
|||||||
+5
-32
@@ -1,9 +1,7 @@
|
|||||||
import { onWillDestroy } from "./component/lifecycle_hooks";
|
import { Callback } from "./utils";
|
||||||
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)
|
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||||
const TARGET = Symbol("Target");
|
export const TARGET = Symbol("Target");
|
||||||
// Escape hatch to prevent reactivity system to turn something into a reactive
|
// Escape hatch to prevent reactivity system to turn something into a reactive
|
||||||
const SKIP = Symbol("Skip");
|
const SKIP = Symbol("Skip");
|
||||||
// Special key to subscribe to, to be notified of key creation/deletion
|
// Special key to subscribe to, to be notified of key creation/deletion
|
||||||
@@ -17,7 +15,7 @@ export type Reactive<T extends Target = Target> = T & {
|
|||||||
[TARGET]: any;
|
[TARGET]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NonReactive<T extends Target = Target> = T & {
|
export type NonReactive<T extends Target = Target> = T & {
|
||||||
[SKIP]: any;
|
[SKIP]: any;
|
||||||
};
|
};
|
||||||
const objectToString = Object.prototype.toString;
|
const objectToString = Object.prototype.toString;
|
||||||
@@ -57,7 +55,7 @@ export function markRaw<T extends Target>(value: T): NonReactive<T> {
|
|||||||
* @returns the underlying value
|
* @returns the underlying value
|
||||||
*/
|
*/
|
||||||
export function toRaw<T extends object>(value: Reactive<T>): T {
|
export function toRaw<T extends object>(value: Reactive<T>): T {
|
||||||
return value[TARGET];
|
return value[TARGET] || value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
|
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
|
||||||
@@ -115,7 +113,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
|
|||||||
*
|
*
|
||||||
* @param callback the callback for which the reactives need to be cleared
|
* @param callback the callback for which the reactives need to be cleared
|
||||||
*/
|
*/
|
||||||
function clearReactivesForCallback(callback: Callback): void {
|
export function clearReactivesForCallback(callback: Callback): void {
|
||||||
const targetsToClear = callbacksToTargets.get(callback);
|
const targetsToClear = callbacksToTargets.get(callback);
|
||||||
if (!targetsToClear) {
|
if (!targetsToClear) {
|
||||||
return;
|
return;
|
||||||
@@ -226,28 +224,3 @@ export function reactive<T extends Target>(
|
|||||||
}
|
}
|
||||||
return reactivesForTarget.get(callback) as Reactive<T>;
|
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> | NonReactive<T> {
|
|
||||||
const node = getCurrent();
|
|
||||||
if (!batchedRenderFunctions.has(node)) {
|
|
||||||
batchedRenderFunctions.set(
|
|
||||||
node,
|
|
||||||
batched(() => node.render())
|
|
||||||
);
|
|
||||||
onWillDestroy(() => clearReactivesForCallback(render));
|
|
||||||
}
|
|
||||||
const render = batchedRenderFunctions.get(node)!;
|
|
||||||
const reactiveState = reactive(state, render);
|
|
||||||
return reactiveState;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { hydrate, patch, text, createBlock } from "../../src/blockdom";
|
|
||||||
import { makeTestFixture } from "./helpers";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Setup and helpers
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = makeTestFixture();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
fixture.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hydration", () => {
|
|
||||||
test("simple text node", async () => {
|
|
||||||
fixture.innerHTML = "some text";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const tree = text("some text");
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("some text");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
|
|
||||||
patch(tree, text("checkmate"));
|
|
||||||
expect(fixture.innerHTML).toBe("checkmate");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("simple static block", async () => {
|
|
||||||
fixture.innerHTML = "<div>some text</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const block = createBlock("<div>some text</div>");
|
|
||||||
|
|
||||||
const tree = block();
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>some text</div>");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("simple dynamic block", async () => {
|
|
||||||
fixture.innerHTML = "<div>some text</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const block = createBlock("<div><block-text-0/></div>");
|
|
||||||
|
|
||||||
const tree = block(["some text"]);
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>some text</div>");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
|
|
||||||
patch(tree, block(["giuoco piano"]));
|
|
||||||
expect(fixture.innerHTML).toBe("<div>giuoco piano</div>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("block with sub block", async () => {
|
|
||||||
fixture.innerHTML = "<div>queen<p>gambit</p></div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
const block1 = createBlock("<div><block-text-0/><block-child-0/></div>");
|
|
||||||
const block2 = createBlock("<p><block-text-0/></p>");
|
|
||||||
|
|
||||||
const tree = block1(["queen"], [block2(["gambit"])]);
|
|
||||||
expect(tree.el).toBe(undefined);
|
|
||||||
hydrate(tree, target);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>queen<p>gambit</p></div>");
|
|
||||||
expect(tree.el).toBe(target);
|
|
||||||
|
|
||||||
patch(tree, block1(["king"], [block2(["pawn"])]));
|
|
||||||
expect(fixture.innerHTML).toBe("<div>king<p>pawn</p></div>");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { renderToString } from "../helpers";
|
||||||
|
|
||||||
|
describe("blacklisted tags and attributes", () => {
|
||||||
|
test("template with block-text tag", () => {
|
||||||
|
const template = `<div><block-text-0/>hello</div>`;
|
||||||
|
expect(() => renderToString(template)).toThrow("Invalid tag name: 'block-text-0'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("template with block-handler tag", () => {
|
||||||
|
const template = `<div block-handler-0="click">hello</div>`;
|
||||||
|
expect(() => renderToString(template)).toThrow("Invalid attribute: 'block-handler-0'");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1067,6 +1067,33 @@ exports[`delay willUpdateProps with rendering grandchild 4`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`destroying/recreating a subcomponent, other scenario 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(\`parent\`);
|
||||||
|
if (ctx['state'].hasChild) {
|
||||||
|
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`destroying/recreating a subcomponent, other scenario 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[`destroying/recreating a subwidget with different props (if start is not over) 1`] = `
|
exports[`destroying/recreating a subwidget with different props (if start is not over) 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1145,7 +1172,7 @@ exports[`properly behave when destroyed/unmounted while rendering 2`] = `
|
|||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx);
|
let b2 = component(\`SubChild\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx);
|
||||||
return block1([], [b2]);
|
return block1([], [b2]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ exports[`can catch errors can catch an error in a component render function 1`]
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ exports[`can catch errors can catch an error in a component render function 1`]
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -152,6 +153,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -190,6 +192,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -200,7 +203,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
let b5 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return block1([], [b5]);
|
return block1([], [b5]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -269,6 +272,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -277,7 +281,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -321,6 +325,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -331,7 +336,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3;
|
let b3;
|
||||||
if (ctx['state'].flag) {
|
if (ctx['state'].flag) {
|
||||||
b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
@@ -465,6 +470,7 @@ exports[`can catch errors can catch an error in the mounted call 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -473,7 +479,7 @@ exports[`can catch errors can catch an error in the mounted call 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -516,6 +522,7 @@ exports[`can catch errors can catch an error in the willPatch call 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -525,7 +532,7 @@ exports[`can catch errors can catch an error in the willPatch call 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['state'].message;
|
let txt1 = ctx['state'].message;
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([txt1], [b3]);
|
return block1([txt1], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -569,6 +576,7 @@ exports[`can catch errors can catch an error in the willStart call 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -577,7 +585,7 @@ exports[`can catch errors can catch an error in the willStart call 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -620,6 +628,7 @@ exports[`can catch errors can catch an error origination from a child's willStar
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -630,7 +639,7 @@ exports[`can catch errors can catch an error origination from a child's willStar
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
let b5 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return block1([], [b5]);
|
return block1([], [b5]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -733,7 +742,7 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { prepareList, capture, withKey } = helpers;
|
let { prepareList, capture, markRaw, withKey } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let Comp1 = ctx['cp'].Comp;
|
let Comp1 = ctx['cp'].Comp;
|
||||||
@@ -748,7 +757,7 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
|
|||||||
let key1 = ctx['cp'].id;
|
let key1 = ctx['cp'].id;
|
||||||
const v1 = ctx['cp'];
|
const v1 = ctx['cp'];
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
c_block1[i1] = withKey(component(\`ErrorHandler\`, {onError: ()=>this.cleanUp(v1.id),slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2__\${key1}\`, node, ctx), key1);
|
c_block1[i1] = withKey(component(\`ErrorHandler\`, {onError: ()=>this.cleanUp(v1.id),slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, ctx), key1);
|
||||||
}
|
}
|
||||||
return list(c_block1);
|
return list(c_block1);
|
||||||
}
|
}
|
||||||
@@ -808,7 +817,7 @@ exports[`can catch errors catching in child makes parent render 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { prepareList, capture, withKey } = helpers;
|
let { prepareList, capture, markRaw, withKey } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let Comp1 = ctx['elem'][1];
|
let Comp1 = ctx['elem'][1];
|
||||||
@@ -823,7 +832,7 @@ exports[`can catch errors catching in child makes parent render 1`] = `
|
|||||||
let key1 = ctx['elem'][0];
|
let key1 = ctx['elem'][0];
|
||||||
const v1 = ctx['elem'];
|
const v1 = ctx['elem'];
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
c_block1[i1] = withKey(component(\`Catch\`, {onError: (_error)=>this.onError(v1[0],_error),slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2__\${key1}\`, node, ctx), key1);
|
c_block1[i1] = withKey(component(\`Catch\`, {onError: (_error)=>this.onError(v1[0],_error),slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, ctx), key1);
|
||||||
}
|
}
|
||||||
return list(c_block1);
|
return list(c_block1);
|
||||||
}
|
}
|
||||||
@@ -873,6 +882,7 @@ exports[`can catch errors error in mounted on a component with a sibling (proper
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
|
||||||
|
|
||||||
@@ -882,7 +892,7 @@ exports[`can catch errors error in mounted on a component with a sibling (proper
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`OK\`, {}, key + \`__1\`, node, ctx);
|
let b2 = component(\`OK\`, {}, key + \`__1\`, node, ctx);
|
||||||
let b4 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
let b4 = component(\`ErrorBoundary\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return block1([], [b2, b4]);
|
return block1([], [b2, b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a component with a handler 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div block-handler-0=\\"click\\"><block-text-1/></div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let hdlr1 = [ctx['inc'], ctx];
|
|
||||||
let txt1 = ctx['state'].value;
|
|
||||||
return block1([hdlr1, txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a component with a sub component 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
let b2 = component(\`Counter\`, {}, key + \`__1\`, node, ctx);
|
|
||||||
return block1([], [b2]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a component with a sub component 2`] = `
|
|
||||||
"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['inc'], ctx];
|
|
||||||
let txt1 = ctx['state'].value;
|
|
||||||
return block1([hdlr1, txt1]);
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`hydration can hydrate a simple static component 1`] = `
|
|
||||||
"function anonymous(bdom, helpers
|
|
||||||
) {
|
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
|
||||||
|
|
||||||
let block1 = createBlock(\`<div>giuoco pianissimo</div>\`);
|
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
|
||||||
return block1();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
@@ -527,7 +527,7 @@ exports[`lifecycle hooks onWillRender 1`] = `
|
|||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ exports[`refs refs are properly bound in slots 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { capture } = helpers;
|
let { capture, markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
|
||||||
let block2 = createBlock(\`<button block-handler-0=\\"click\\" block-ref=\\"1\\">do something</button>\`);
|
let block2 = createBlock(\`<button block-handler-0=\\"click\\" block-ref=\\"1\\">do something</button>\`);
|
||||||
@@ -77,7 +77,7 @@ exports[`refs refs are properly bound in slots 1`] = `
|
|||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let txt1 = ctx['state'].val;
|
let txt1 = ctx['state'].val;
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
let b3 = component(\`Dialog\`, {slots: {'footer': {__render: slot1, __ctx: ctx1}}}, key + \`__1\`, node, ctx);
|
let b3 = component(\`Dialog\`, {slots: markRaw({'footer': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, ctx);
|
||||||
return block1([txt1], [b3]);
|
return block1([txt1], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
// 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 render with deep=true followed by render with deep=false work as expected 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = text(\`parent\`);
|
||||||
|
let b3 = text(ctx['state'].value);
|
||||||
|
let b4 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
|
||||||
|
return multi([b2, b3, b4]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics render with deep=true followed by render with deep=false work as expected 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let b2 = text(\`child\`);
|
||||||
|
let b3 = text(ctx['env'].getValue());
|
||||||
|
return multi([b2, b3]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics works as expected for dynamic number of props 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return component(\`Child\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`rendering semantics works as expected for dynamic number of props 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
return text(Object.keys(ctx['props']).length);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { isBoundary, withDefault, setContextValue, capture } = helpers;
|
let { isBoundary, withDefault, setContextValue, capture, markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`);
|
let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`);
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 1`] = `
|
|||||||
setContextValue(ctx, \\"iter\\", 'source');
|
setContextValue(ctx, \\"iter\\", 'source');
|
||||||
let txt1 = ctx['iter'];
|
let txt1 = ctx['iter'];
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
let b2 = component(\`Childcomp\`, {slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1\`, node, ctx);
|
let b2 = component(\`Childcomp\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, ctx);
|
||||||
let txt2 = ctx['iter'];
|
let txt2 = ctx['iter'];
|
||||||
return block1([txt1, txt2], [b2]);
|
return block1([txt1, txt2], [b2]);
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ exports[`t-set slots with a t-set with a component in body 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { capture, isBoundary, withDefault, LazyValue, safeOutput } = helpers;
|
let { capture, isBoundary, withDefault, LazyValue, safeOutput, markRaw } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
@@ -68,7 +68,7 @@ exports[`t-set slots with a t-set with a component in body 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2\`, node, ctx);
|
return component(\`Child\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -102,7 +102,7 @@ exports[`t-set slots with an t-set with a component in body 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { capture, isBoundary, withDefault, LazyValue, safeOutput } = helpers;
|
let { capture, isBoundary, withDefault, LazyValue, safeOutput, markRaw } = helpers;
|
||||||
|
|
||||||
let block4 = createBlock(\`<div>coffee</div>\`);
|
let block4 = createBlock(\`<div>coffee</div>\`);
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ exports[`t-set slots with an t-set with a component in body 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
return component(\`Blorg\`, {slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2\`, node, ctx);
|
return component(\`Blorg\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -157,7 +157,7 @@ exports[`t-set slots with an unused t-set with a component in body 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
let { capture, isBoundary, withDefault, LazyValue } = helpers;
|
let { capture, isBoundary, withDefault, LazyValue, markRaw } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
ctx = Object.create(ctx);
|
ctx = Object.create(ctx);
|
||||||
@@ -172,7 +172,7 @@ exports[`t-set slots with an unused t-set with a component in body 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
const ctx1 = capture(ctx);
|
const ctx1 = capture(ctx);
|
||||||
return component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2\`, node, ctx);
|
return component(\`Child\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__2\`, node, ctx);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { App, Component, mount, status, useState, xml } from "../../src";
|
import { App, Component, mount, status, toRaw, useState, xml } from "../../src";
|
||||||
import { elem, makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
|
import { elem, makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
|
||||||
import { markup } from "../../src/utils";
|
import { markup } from "../../src/utils";
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ describe("basics", () => {
|
|||||||
class Test extends Component {
|
class Test extends Component {
|
||||||
static template = xml`<span>simple vnode</span>`;
|
static template = xml`<span>simple vnode</span>`;
|
||||||
setup() {
|
setup() {
|
||||||
expect(this.props).toBe(p);
|
expect(toRaw(this.props)).toBe(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ describe("basics", () => {
|
|||||||
error = e as Error;
|
error = e as Error;
|
||||||
}
|
}
|
||||||
expect(error!).toBeDefined();
|
expect(error!).toBeDefined();
|
||||||
expect(error!.message).toBe('Missing template: "wrongtemplate"');
|
expect(error!.message).toBe('Missing template: "wrongtemplate" (for component "Test")');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("class component with dynamic text", async () => {
|
test("class component with dynamic text", async () => {
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ Scheduler.prototype.addFiber = function (fiber: Fiber) {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (lastScheduler && lastScheduler.tasks.size > 0) {
|
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...");
|
throw new Error("we got a memory leak...");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -131,6 +133,58 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
|||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("destroying/recreating a subcomponent, other scenario", async () => {
|
||||||
|
let flag = false;
|
||||||
|
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`child`;
|
||||||
|
setup() {
|
||||||
|
if (!flag) {
|
||||||
|
flag = true;
|
||||||
|
parent.render(true);
|
||||||
|
}
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`parent<Child t-if="state.hasChild"/>`;
|
||||||
|
static components = { Child };
|
||||||
|
state = useState({ hasChild: false });
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
|
||||||
|
expect([
|
||||||
|
"Parent:setup",
|
||||||
|
"Parent:willStart",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Parent:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
expect(fixture.innerHTML).toBe("parent");
|
||||||
|
|
||||||
|
parent.state.hasChild = true;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:setup",
|
||||||
|
"Child:willStart",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willRender",
|
||||||
|
"Child:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Child:mounted",
|
||||||
|
"Parent:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("parentchild");
|
||||||
|
});
|
||||||
|
|
||||||
test("creating two async components, scenario 1", async () => {
|
test("creating two async components, scenario 1", async () => {
|
||||||
let defA = makeDeferred();
|
let defA = makeDeferred();
|
||||||
let defB = makeDeferred();
|
let defB = makeDeferred();
|
||||||
@@ -521,7 +575,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div><SubChild /></div>`;
|
static template = xml`<div><SubChild val="props.val"/></div>`;
|
||||||
static components = { SubChild };
|
static components = { SubChild };
|
||||||
setup() {
|
setup() {
|
||||||
useLogLifecycle();
|
useLogLifecycle();
|
||||||
@@ -1907,18 +1961,13 @@ test("concurrent renderings scenario 13", async () => {
|
|||||||
await nextTick(); // wait for this change to be applied
|
await nextTick(); // wait for this change to be applied
|
||||||
expect([
|
expect([
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"Child:willUpdateProps",
|
|
||||||
"Child:setup",
|
"Child:setup",
|
||||||
"Child:willStart",
|
"Child:willStart",
|
||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
"Child:willRender",
|
"Child:willRender",
|
||||||
"Child:rendered",
|
"Child:rendered",
|
||||||
"Child:willRender",
|
|
||||||
"Child:rendered",
|
|
||||||
"Parent:willPatch",
|
"Parent:willPatch",
|
||||||
"Child:willPatch",
|
|
||||||
"Child:mounted",
|
"Child:mounted",
|
||||||
"Child:patched",
|
|
||||||
"Parent:patched",
|
"Parent:patched",
|
||||||
"Child:willRender",
|
"Child:willRender",
|
||||||
"Child:rendered",
|
"Child:rendered",
|
||||||
@@ -2472,9 +2521,9 @@ test("two renderings initiated between willPatch and patched", async () => {
|
|||||||
useLogLifecycle();
|
useLogLifecycle();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
this.mounted = "Mounted";
|
this.mounted = "Mounted";
|
||||||
parent.render();
|
parent.render(true);
|
||||||
});
|
});
|
||||||
onWillUnmount(() => parent.render());
|
onWillUnmount(() => parent.render(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2507,15 +2556,11 @@ test("two renderings initiated between willPatch and patched", async () => {
|
|||||||
"Parent:rendered",
|
"Parent:rendered",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
|
await nextMicroTick();
|
||||||
|
expect(["Panel:willRender", "Panel:rendered"]).toBeLogged();
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect([
|
expect(["Parent:willPatch", "Panel:willPatch", "Panel:patched", "Parent:patched"]).toBeLogged();
|
||||||
"Panel:willRender",
|
|
||||||
"Panel:rendered",
|
|
||||||
"Parent:willPatch",
|
|
||||||
"Panel:willPatch",
|
|
||||||
"Panel:patched",
|
|
||||||
"Parent:patched",
|
|
||||||
]).toBeLogged();
|
|
||||||
expect(fixture.innerHTML).toBe("<div><abc>Panel1Mounted</abc></div>");
|
expect(fixture.innerHTML).toBe("<div><abc>Panel1Mounted</abc></div>");
|
||||||
|
|
||||||
parent.state.panel = "Panel2";
|
parent.state.panel = "Panel2";
|
||||||
@@ -2753,12 +2798,20 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
|||||||
static template = xml`<Parent state="state"/>`;
|
static template = xml`<Parent state="state"/>`;
|
||||||
static components = { Parent };
|
static components = { Parent };
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = await mount(GrandParent, fixture);
|
const parent = await mount(GrandParent, fixture);
|
||||||
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
||||||
expect([
|
expect([
|
||||||
|
"GrandParent:setup",
|
||||||
|
"GrandParent:willStart",
|
||||||
|
"GrandParent:willRender",
|
||||||
"Parent:setup",
|
"Parent:setup",
|
||||||
"Parent:willStart",
|
"Parent:willStart",
|
||||||
|
"GrandParent:rendered",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
"DelayedChild:setup",
|
"DelayedChild:setup",
|
||||||
"DelayedChild:willStart",
|
"DelayedChild:willStart",
|
||||||
@@ -2772,20 +2825,23 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
|||||||
"ReactiveChild:mounted",
|
"ReactiveChild:mounted",
|
||||||
"DelayedChild:mounted",
|
"DelayedChild:mounted",
|
||||||
"Parent:mounted",
|
"Parent:mounted",
|
||||||
|
"GrandParent:mounted",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
promise = makeDeferred();
|
promise = makeDeferred();
|
||||||
const prom1 = promise;
|
const prom1 = promise;
|
||||||
parent.state.value = 1;
|
parent.state.value = 1;
|
||||||
child.render(); // trigger a root rendering first
|
child.render(); // trigger a root rendering first
|
||||||
parent.render();
|
parent.render(true);
|
||||||
reactiveChild.render();
|
reactiveChild.render();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
||||||
expect([
|
expect([
|
||||||
"DelayedChild:willRender",
|
"DelayedChild:willRender",
|
||||||
"DelayedChild:rendered",
|
"DelayedChild:rendered",
|
||||||
|
"GrandParent:willRender",
|
||||||
"Parent:willUpdateProps",
|
"Parent:willUpdateProps",
|
||||||
|
"GrandParent:rendered",
|
||||||
"ReactiveChild:willRender",
|
"ReactiveChild:willRender",
|
||||||
"ReactiveChild:rendered",
|
"ReactiveChild:rendered",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
@@ -2800,12 +2856,14 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
|||||||
const prom2 = promise;
|
const prom2 = promise;
|
||||||
child.render(); // trigger a root rendering first
|
child.render(); // trigger a root rendering first
|
||||||
parent.state.value = 2;
|
parent.state.value = 2;
|
||||||
parent.render();
|
parent.render(true);
|
||||||
reactiveChild.render();
|
reactiveChild.render();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
expect(fixture.innerHTML).toBe("0_0<div></div>");
|
||||||
expect([
|
expect([
|
||||||
|
"GrandParent:willRender",
|
||||||
"Parent:willUpdateProps",
|
"Parent:willUpdateProps",
|
||||||
|
"GrandParent:rendered",
|
||||||
"ReactiveChild:willRender",
|
"ReactiveChild:willRender",
|
||||||
"ReactiveChild:rendered",
|
"ReactiveChild:rendered",
|
||||||
"Parent:willRender",
|
"Parent:willRender",
|
||||||
@@ -2822,12 +2880,14 @@ test("delay willUpdateProps with rendering grandchild", async () => {
|
|||||||
expect([
|
expect([
|
||||||
"DelayedChild:willRender",
|
"DelayedChild:willRender",
|
||||||
"DelayedChild:rendered",
|
"DelayedChild:rendered",
|
||||||
|
"GrandParent:willPatch",
|
||||||
"Parent:willPatch",
|
"Parent:willPatch",
|
||||||
"ReactiveChild:willPatch",
|
"ReactiveChild:willPatch",
|
||||||
"DelayedChild:willPatch",
|
"DelayedChild:willPatch",
|
||||||
"DelayedChild:patched",
|
"DelayedChild:patched",
|
||||||
"ReactiveChild:patched",
|
"ReactiveChild:patched",
|
||||||
"Parent:patched",
|
"Parent:patched",
|
||||||
|
"GrandParent:patched",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
prom1.resolve();
|
prom1.resolve();
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ describe("hooks", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
||||||
someVal = "brain";
|
someVal = "brain";
|
||||||
someVal2 = "maggot";
|
someVal2 = "maggot";
|
||||||
component.render();
|
component.render(true);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
|
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
|
||||||
});
|
});
|
||||||
@@ -272,7 +272,7 @@ describe("hooks", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
|
||||||
someVal = "brain";
|
someVal = "brain";
|
||||||
someVal2 = "maggot";
|
someVal2 = "maggot";
|
||||||
component.render();
|
component.render(true);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
|
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import { Component, mount, useState, xml } from "../../src";
|
|
||||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
|
||||||
|
|
||||||
let fixture: HTMLElement;
|
|
||||||
|
|
||||||
snapshotEverything();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = makeTestFixture();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hydration", () => {
|
|
||||||
test("can hydrate a simple static component", async () => {
|
|
||||||
fixture.innerHTML = "<div>giuoco pianissimo</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
class Test extends Component {
|
|
||||||
static template = xml`<div>giuoco pianissimo</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Test, target, { hydrate: true });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>giuoco pianissimo</div>");
|
|
||||||
expect(fixture.firstChild).toBe(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can hydrate a component with a handler", async () => {
|
|
||||||
fixture.innerHTML = "<div>0</div>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
static template = xml`<div t-on-click="inc"><t t-esc="state.value"/></div>`;
|
|
||||||
|
|
||||||
state = useState({ value: 0 });
|
|
||||||
|
|
||||||
inc() {
|
|
||||||
this.state.value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Counter, target, { hydrate: true });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>0</div>");
|
|
||||||
expect(fixture.firstChild).toBe(target);
|
|
||||||
|
|
||||||
target.click();
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<div>1</div>");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can hydrate a component with a sub component", async () => {
|
|
||||||
fixture.innerHTML = "<p><button>0</button></p>";
|
|
||||||
const target = fixture.firstChild as any;
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
static template = xml`<button t-on-click="inc"><t t-esc="state.value"/></button>`;
|
|
||||||
|
|
||||||
state = useState({ value: 0 });
|
|
||||||
|
|
||||||
inc() {
|
|
||||||
this.state.value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent extends Component {
|
|
||||||
static template = xml`<p><Counter/></p>`;
|
|
||||||
static components = { Counter };
|
|
||||||
}
|
|
||||||
|
|
||||||
await mount(Parent, target, { hydrate: true });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<p><button>0</button></p>");
|
|
||||||
expect(fixture.firstChild).toBe(target);
|
|
||||||
|
|
||||||
target.querySelector("button")!.click();
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<p><button>1</button></p>");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -849,8 +849,9 @@ describe("lifecycle hooks", () => {
|
|||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<Child />`;
|
<Child someValue="state.value" />`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
|
state = useState({ value: 1 });
|
||||||
setup() {
|
setup() {
|
||||||
useLogLifecycle();
|
useLogLifecycle();
|
||||||
}
|
}
|
||||||
@@ -871,7 +872,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"Parent:mounted",
|
"Parent:mounted",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
|
|
||||||
parent.render(); // to block child render
|
parent.state.value++; // to block child render
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged();
|
expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged();
|
||||||
|
|
||||||
@@ -1008,20 +1009,15 @@ describe("lifecycle hooks", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect([
|
expect([
|
||||||
"C:willRender",
|
"C:willRender",
|
||||||
"D:willUpdateProps",
|
|
||||||
"F:setup",
|
"F:setup",
|
||||||
"F:willStart",
|
"F:willStart",
|
||||||
"C:rendered",
|
"C:rendered",
|
||||||
"D:willRender",
|
|
||||||
"D:rendered",
|
|
||||||
"F:willRender",
|
"F:willRender",
|
||||||
"F:rendered",
|
"F:rendered",
|
||||||
"C:willPatch",
|
"C:willPatch",
|
||||||
"D:willPatch",
|
|
||||||
"E:willUnmount",
|
"E:willUnmount",
|
||||||
"E:willDestroy",
|
"E:willDestroy",
|
||||||
"F:mounted",
|
"F:mounted",
|
||||||
"D:patched",
|
|
||||||
"C:patched",
|
"C:patched",
|
||||||
]).toBeLogged();
|
]).toBeLogged();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,442 @@
|
|||||||
|
import { Component, mount, onRendered, onWillUpdateProps, useState, xml } from "../../src";
|
||||||
|
import {
|
||||||
|
makeTestFixture,
|
||||||
|
snapshotEverything,
|
||||||
|
nextTick,
|
||||||
|
useLogLifecycle,
|
||||||
|
makeDeferred,
|
||||||
|
nextMicroTick,
|
||||||
|
} 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("render with deep=true followed by render with deep=false work as expected", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`child<t t-esc="env.getValue()"/>`;
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`parent<t t-esc="state.value"/><Child/>`;
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
state = useState({ value: "A" });
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
useLogLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let value = 3;
|
||||||
|
const env = {
|
||||||
|
getValue() {
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture, { env });
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("parentAchild3");
|
||||||
|
expect([
|
||||||
|
"Parent:setup",
|
||||||
|
"Parent:willStart",
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:setup",
|
||||||
|
"Child:willStart",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willRender",
|
||||||
|
"Child:rendered",
|
||||||
|
"Child:mounted",
|
||||||
|
"Parent:mounted",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
value = 4;
|
||||||
|
parent.render(true);
|
||||||
|
|
||||||
|
// wait for child to be rendered, but dom not yet patched
|
||||||
|
await nextMicroTick();
|
||||||
|
await nextMicroTick();
|
||||||
|
await nextMicroTick();
|
||||||
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:willUpdateProps",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willRender",
|
||||||
|
"Child:rendered",
|
||||||
|
]).toBeLogged();
|
||||||
|
|
||||||
|
parent.state.value = "B";
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("parentBchild4");
|
||||||
|
expect([
|
||||||
|
"Parent:willRender",
|
||||||
|
"Child:willUpdateProps",
|
||||||
|
"Parent:rendered",
|
||||||
|
"Child:willRender",
|
||||||
|
"Child:rendered",
|
||||||
|
"Parent:willPatch",
|
||||||
|
"Child:willPatch",
|
||||||
|
"Child:patched",
|
||||||
|
"Parent:patched",
|
||||||
|
]).toBeLogged();
|
||||||
|
});
|
||||||
|
|
||||||
|
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("works as expected for dynamic number of props", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<t t-esc="Object.keys(props).length"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<Child t-props="state"/>
|
||||||
|
`;
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
state: any = useState({ b: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("1");
|
||||||
|
parent.state.newkey = 3;
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("2");
|
||||||
|
});
|
||||||
|
|
||||||
|
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 deep=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();
|
||||||
|
});
|
||||||
@@ -1675,4 +1675,60 @@ describe("slots", () => {
|
|||||||
await mount(Parent, fixture);
|
await mount(Parent, fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>SlotDisplay</div><div>Parent</div>");
|
expect(fixture.innerHTML).toBe("<div>SlotDisplay</div><div>Parent</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mix of slots, t-call, t-call with body, and giving own props child", async () => {
|
||||||
|
expect.assertions(11);
|
||||||
|
|
||||||
|
class C extends Component {
|
||||||
|
static template = xml`[C]<t t-slot="default" />`;
|
||||||
|
}
|
||||||
|
class B extends Component {
|
||||||
|
static template = xml`[B]<C slots="props.slots" />`;
|
||||||
|
static components = { C };
|
||||||
|
}
|
||||||
|
|
||||||
|
const subTemplate2 = xml`[sub2<t t-esc="v"/>]`;
|
||||||
|
const subTemplate1 = xml`[sub1]
|
||||||
|
<t t-set="dummy" t-value="validate"/>
|
||||||
|
<t t-call="${subTemplate2}">
|
||||||
|
<t t-set="v" t-value="props.number"/>
|
||||||
|
</t>`;
|
||||||
|
|
||||||
|
let a: any;
|
||||||
|
class A extends Component {
|
||||||
|
static components = { B };
|
||||||
|
static template = xml`<B>[A]<t t-call="${subTemplate1}"/></B>`;
|
||||||
|
setup() {
|
||||||
|
a = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get validate() {
|
||||||
|
// we check here that the actual component was not lost somehow
|
||||||
|
expect(this.__owl__.component === a).toBe(true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class P extends Component {
|
||||||
|
static components = { A };
|
||||||
|
static template = xml`<button t-on-click="inc">inc</button><A number="state.number"/>`;
|
||||||
|
|
||||||
|
state = useState({ number: 333 });
|
||||||
|
inc() {
|
||||||
|
this.state.number++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<P/>`;
|
||||||
|
static components = { P };
|
||||||
|
}
|
||||||
|
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2333]");
|
||||||
|
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2334]");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe("t-props", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("basic use", async () => {
|
test("basic use", async () => {
|
||||||
expect.assertions(4);
|
expect.assertions(5);
|
||||||
|
|
||||||
let props = { a: 1, b: 2 };
|
let props = { a: 1, b: 2 };
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ describe("t-props", () => {
|
|||||||
`;
|
`;
|
||||||
setup() {
|
setup() {
|
||||||
expect(this.props).toEqual({ a: 1, b: 2 });
|
expect(this.props).toEqual({ a: 1, b: 2 });
|
||||||
|
expect(this.props).not.toBe(props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ exports[`Memo if no prop change, prevent renderings from above 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
let b6 = text(ctx['state'].a);
|
let b6 = text(ctx['state'].a);
|
||||||
@@ -16,7 +17,7 @@ exports[`Memo if no prop change, prevent renderings from above 1`] = `
|
|||||||
let b2 = text(ctx['state'].a);
|
let b2 = text(ctx['state'].a);
|
||||||
let b3 = text(ctx['state'].b);
|
let b3 = text(ctx['state'].b);
|
||||||
let b4 = text(ctx['state'].c);
|
let b4 = text(ctx['state'].c);
|
||||||
let b9 = component(\`Memo\`, {a: ctx['state'].a, b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b9 = component(\`Memo\`, {a: ctx['state'].a, b: ctx['state'].b,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__1\`, node, ctx);
|
||||||
return multi([b2, b3, b4, b9]);
|
return multi([b2, b3, b4, b9]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -26,6 +27,7 @@ exports[`Memo if no props, prevent renderings from above 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return component(\`Child\`, {value: ctx['state'].value}, key + \`__2\`, node, ctx);
|
return component(\`Child\`, {value: ctx['state'].value}, key + \`__2\`, node, ctx);
|
||||||
@@ -33,7 +35,7 @@ exports[`Memo if no props, prevent renderings from above 1`] = `
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||||
let b4 = component(\`Memo\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
let b4 = component(\`Memo\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__3\`, node, ctx);
|
||||||
return multi([b2, b4]);
|
return multi([b2, b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -54,6 +56,7 @@ exports[`Memo if no props, prevent renderings from above (work with simple html)
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
function slot1(ctx, node, key = \\"\\") {
|
function slot1(ctx, node, key = \\"\\") {
|
||||||
return text(ctx['state'].value);
|
return text(ctx['state'].value);
|
||||||
@@ -61,7 +64,7 @@ exports[`Memo if no props, prevent renderings from above (work with simple html)
|
|||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b2 = text(ctx['state'].value);
|
let b2 = text(ctx['state'].value);
|
||||||
let b4 = component(\`Memo\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
|
let b4 = component(\`Memo\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__1\`, node, ctx);
|
||||||
return multi([b2, b4]);
|
return multi([b2, b4]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ exports[`Portal Portal composed with t-slot 1`] = `
|
|||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { markRaw } = helpers;
|
||||||
|
|
||||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ exports[`Portal Portal composed with t-slot 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function template(ctx, node, key = \\"\\") {
|
return function template(ctx, node, key = \\"\\") {
|
||||||
let b3 = component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
let b3 = component(\`Child\`, {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, ctx);
|
||||||
return block1([], [b3]);
|
return block1([], [b3]);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -421,6 +421,7 @@ describe("Portal", () => {
|
|||||||
addOutsideDiv(fixture);
|
addOutsideDiv(fixture);
|
||||||
const parent = await mount(Parent, fixture);
|
const parent = await mount(Parent, fixture);
|
||||||
expect(steps).toEqual(["parent:mounted"]);
|
expect(steps).toEqual(["parent:mounted"]);
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div><div></div>');
|
||||||
|
|
||||||
parent.state.hasChild = true;
|
parent.state.hasChild = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -430,6 +431,7 @@ describe("Portal", () => {
|
|||||||
"child:mounted",
|
"child:mounted",
|
||||||
"parent:patched",
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"><span>1</span></div><div></div>');
|
||||||
|
|
||||||
parent.state.val = 2;
|
parent.state.val = 2;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -443,6 +445,7 @@ describe("Portal", () => {
|
|||||||
"child:patched",
|
"child:patched",
|
||||||
"parent:patched",
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"><span>2</span></div><div></div>');
|
||||||
|
|
||||||
parent.state.hasChild = false;
|
parent.state.hasChild = false;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -459,6 +462,7 @@ describe("Portal", () => {
|
|||||||
"child:willUnmount",
|
"child:willUnmount",
|
||||||
"parent:patched",
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
|
expect(fixture.innerHTML).toBe('<div id="outside"></div><div></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("portal destroys on crash", async () => {
|
test("portal destroys on crash", async () => {
|
||||||
|
|||||||
@@ -1131,6 +1131,11 @@ describe("toRaw", () => {
|
|||||||
expect(reactiveObj).not.toBe(obj);
|
expect(reactiveObj).not.toBe(obj);
|
||||||
expect(toRaw(reactiveObj as Reactive<typeof obj>)).toBe(obj);
|
expect(toRaw(reactiveObj as Reactive<typeof obj>)).toBe(obj);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("giving a non reactive to toRaw return the object itself", () => {
|
||||||
|
const obj = { value: 1 };
|
||||||
|
expect(toRaw(obj as Reactive<typeof obj>)).toBe(obj);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Reactivity: useState", () => {
|
describe("Reactivity: useState", () => {
|
||||||
@@ -1652,7 +1657,7 @@ describe("Reactivity: useState", () => {
|
|||||||
expect([...steps]).toEqual(["list"]);
|
expect([...steps]).toEqual(["list"]);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>");
|
expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>");
|
||||||
expect([...steps]).toEqual(["list", "quantity1"]);
|
expect([...steps]).toEqual(["list"]);
|
||||||
steps.clear();
|
steps.clear();
|
||||||
|
|
||||||
secondQuantity.quantity = 2;
|
secondQuantity.quantity = 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user