Compare commits

..

20 Commits

Author SHA1 Message Date
Samuel Degueldre c8f7274977 [IMP] index: export batched utils function 2022-01-28 11:18:41 +01:00
Géry Debongnie 924dffeb36 [TEST] component: add test to make sure a specific issue does not arise 2022-01-27 15:02:44 +01:00
Géry Debongnie 0dbc807e01 [IMP] typing: make app and mount method properly generic 2022-01-27 14:43:33 +01:00
Bruno Boi 4b88787a72 [FIX] compiler: never add _ prefix to non variable token 2022-01-27 14:41:16 +01:00
Samuel Degueldre 6f0a4b1c92 [FIX] compiler: do not pass dynamic props object as is
The child receiving the props can observe changes made to the passed
t-props object which is not desirable.
2022-01-27 13:19:14 +01:00
Bruno Boi 3f575148b6 [FIX] compiler: add _ prefix to local variables while compiling an expression 2022-01-27 10:57:13 +01:00
Géry Debongnie 4a971f2963 [IMP] hooks: introduce useChildSubEnv and change useSubEnv 2022-01-27 10:35:06 +01:00
Géry Debongnie d7850aaf7a [FIX] compiler: does not modify xml doc in place 2022-01-27 10:34:41 +01:00
Bruno Boi 0e13b859d0 [FIX] component: properly capture expression of t-model 2022-01-27 09:23:52 +01:00
Géry Debongnie c813a1ce03 [DOC] fix error in slot documentation 2022-01-25 12:37:07 +01:00
Géry Debongnie 54d69c2dde [FIX] reactivity: clear callbacks at destroy time instead of unmount 2022-01-24 16:36:32 +01:00
Géry Debongnie 11d4aae8d2 [FIX] compiler: add missing ; in some places 2022-01-24 14:51:12 +01:00
Géry Debongnie b159a073d9 [FIX] component: proper error message in dev mode in some cases 2022-01-24 13:18:19 +01:00
Lucas Perais (lpe) d9b189bcba [FIX] compiler: force new block for svg nested in html 2022-01-24 12:56:43 +01:00
Géry Debongnie 7ef1fe0b99 [FIX] svg: allow path as root tag 2022-01-24 10:29:22 +01:00
Samuel Degueldre 211ecdf689 [FIX] blockdom: fix VHtml patching not setting its html correctly 2022-01-24 09:36:43 +01:00
Lucas Perais (lpe) b208894d38 [FIX] compiler: t-model on select with options with dynamic values 2022-01-21 16:38:58 +01:00
Géry Debongnie 0737bb39b4 [DOC] update changelog renderToString example 2022-01-21 15:40:27 +01:00
Géry Debongnie de584b01a8 [FIX] reactivity: do not observe eventtarget and other stuff 2022-01-21 15:40:27 +01:00
Géry Debongnie 525029b682 [IMP] reactivity: introduces markRaw and toRaw functions 2022-01-21 15:40:27 +01:00
52 changed files with 1582 additions and 967 deletions
+36 -11
View File
@@ -73,6 +73,8 @@ All changes are documented here in no particular order.
- new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect)) - new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
- breaking: `Context` is removed ([details](#15-context-is-removed)) - breaking: `Context` is removed ([details](#15-context-is-removed))
- breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty)) - breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty))
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
- new hook: `useChildSubEnv` (only applies to child components) ([details](#27-usechildsubenv-only-applies-to-child-components))
- breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level)) - breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level))
- breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes)) - breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes))
- breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget)) - breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget))
@@ -81,8 +83,6 @@ All changes are documented here in no particular order.
- breaking: transition system is removed ([details](#24-transition-system-is-removed)) - breaking: transition system is removed ([details](#24-transition-system-is-removed))
- breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates)) - breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates))
- breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed)) - breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed))
- breaking: `useSubEnv` only applies to child components ([details](#27-usesubenv-only-applies-to-child-components))
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
- breaking: `renderToString` function on qweb has been removed ([details](#32-rendertostring-on-qweb-has-been-removed)) - breaking: `renderToString` function on qweb has been removed ([details](#32-rendertostring-on-qweb-has-been-removed))
- breaking: `debounce` utility function has been removed ([details](#34-debounce-utility-function-has-been-removed)) - breaking: `debounce` utility function has been removed ([details](#34-debounce-utility-function-has-been-removed))
- breaking: `browser` object has been removed ([details](#39-browser-object-has-been-removed)) - breaking: `browser` object has been removed ([details](#39-browser-object-has-been-removed))
@@ -565,15 +565,12 @@ either a fallback when the data is not ready, or the actual component with data
as props. If there is no escape, and `AsyncRoot` is needed, please reach out to as props. If there is no escape, and `AsyncRoot` is needed, please reach out to
us so we can study this usecase. us so we can study this usecase.
### 27. `useSubEnv` only applies to child components ### 27. `useChildSubEnv` (only applies to child components)
In Owl 1, a call to `useSubEnv` would define a new environment for the children In Owl, a call to `useSubEnv` would define a new environment for the children
AND the component. It now only defines an environment for the children. AND the component. It is very useful, but in some cases, one only need to update
the children component environment. This can now be done with a new hook:
Rationale: This was a subtle cause for bugs: some code had to be rrun [`useChildSubEnv`](doc/reference/hooks.md#usesubenv-and-usechildsubenv)
before the call to `useSubEnv`, otherwise it could interfere with the sub environment.
Documentation: [Hooks](doc/reference/hooks.md#usesubenv)
### 28. `env` is now frozen ### 28. `env` is now frozen
@@ -659,10 +656,14 @@ Also, this can easily be done in userspace, by mounting a component in a div. F
export async function renderToString(template, context) { export async function renderToString(template, context) {
class C extends Component { class C extends Component {
static template = template; static template = template;
setup () {
Object.assign(this, context);
}
} }
const div = document.createElement('div'); const div = document.createElement('div');
document.body.appendChild(div); document.body.appendChild(div);
const component = await mount(C, div); const app = new App(C);
await app.mount(div);
const result = div.innerHTML; const result = div.innerHTML;
app.destroy(); app.destroy();
div.remove(); div.remove();
@@ -670,6 +671,30 @@ export async function renderToString(template, context) {
} }
``` ```
The function above works for most cases, but is asynchronous. An alternative
function could look like this:
```js
const { App, blockDom } = owl;
const app = new App(Component); // act as a template repository
function renderToString(template, context = {}) {
app.addTemplate(template, template, { allowDuplicate: true });
const templateFn = app.getTemplate(template);
const bdom = templateFn(context, {});
const div = document.createElement('div')
blockDom.mount(bdom, div);
return div.innerHTML;
}
```
This is a synchronous function, so it will not work with components, but it should
be useful for most simple templates.
Also note that these two examples do not translate their templates. To do that,
they need to be modified to pass the proper translate function to the `App`
configuration.
### 33. Portal are now defined with `t-portal` ### 33. Portal are now defined with `t-portal`
Before Owl 2, one could use the `Portal` component by importing it and using it. Before Owl 2, one could use the `Portal` component by importing it and using it.
+8 -2
View File
@@ -7,9 +7,14 @@ Main entities:
- [`App`](reference/app.md): represent an Owl application (mainly a root component,a set of templates, and a config) - [`App`](reference/app.md): represent an Owl application (mainly a root component,a set of templates, and a config)
- [`Component`](reference/component.md): the main class to define a concrete Owl component - [`Component`](reference/component.md): the main class to define a concrete Owl component
- [`mount`](reference/app.md#mount-helper): main entry point for most application: mount a component to a target - [`mount`](reference/app.md#mount-helper): main entry point for most application: mount a component to a target
- [`xml`](reference/templates.md#inline-templates): helper to define an inline template
Reactivity
- [`useState`](reference/reactivity.md#usestate): create a reactive object (hook, linked to a specific component) - [`useState`](reference/reactivity.md#usestate): create a reactive object (hook, linked to a specific component)
- [`reactive`](reference/reactivity.md#reactive): create a reactive object (not linked to any component) - [`reactive`](reference/reactivity.md#reactive): create a reactive object (not linked to any component)
- [`xml`](reference/templates.md#inline-templates): helper to define an inline template - [`markRaw`](reference/reactivity.md#markraw): mark an object or array so that it is ignored by the reactivity system
- [`toRaw`](reference/reactivity.md#toraw): given a reactive objet, return the raw (non reactive) underlying object
Lifecycle hooks: Lifecycle hooks:
@@ -31,7 +36,8 @@ Other hooks:
- [`useEnv`](reference/hooks.md#useenv): return a reference to the current env - [`useEnv`](reference/hooks.md#useenv): return a reference to the current env
- [`useExternalListener`](reference/hooks.md#useexternallistener): add a listener outside of a component DOM - [`useExternalListener`](reference/hooks.md#useexternallistener): add a listener outside of a component DOM
- [`useRef`](reference/hooks.md#useref): get an object representing a reference (`t-ref`) - [`useRef`](reference/hooks.md#useref): get an object representing a reference (`t-ref`)
- [`useSubEnv`](reference/hooks.md#usesubenv): extend the current env with additional information for child components - [`useChildSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for child components)
- [`useSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for current component and child components)
Utility/helpers: Utility/helpers:
+1 -1
View File
@@ -52,7 +52,7 @@ It is sometimes useful to add one (or more) specific keys to the environment,
from the perspective of a specific component and its children. In that case, the from the perspective of a specific component and its children. In that case, the
solution presented above will not work, since it sets the global environment. solution presented above will not work, since it sets the global environment.
There is a hook for this situation: [`useSubEnv`](hooks.md#usesubenv). There are two hooks for this situation: [`useSubEnv` and `useChildSubEnv`](hooks.md#usesubenv-and-usechildsubenv).
```js ```js
class SomeComponent extends Component { class SomeComponent extends Component {
+18 -7
View File
@@ -8,7 +8,7 @@
- [Other hooks](#other-hooks) - [Other hooks](#other-hooks)
- [`useState`](#usestate) - [`useState`](#usestate)
- [`useRef`](#useref) - [`useRef`](#useref)
- [`useSubEnv`](#usesubenv) - [`useSubEnv` and `useChildSubEnv`](#usesubenv-and-usechildsubenv)
- [`useExternalListener`](#useexternallistener) - [`useExternalListener`](#useexternallistener)
- [`useComponent`](#usecomponent) - [`useComponent`](#usecomponent)
- [`useEnv`](#useenv) - [`useEnv`](#useenv)
@@ -152,14 +152,14 @@ this.ref2 = useRef("component_2");
References are only guaranteed to be active while the parent component is mounted. References are only guaranteed to be active while the parent component is mounted.
If this is not the case, accessing `el` or `comp` on it will return `null`. If this is not the case, accessing `el` or `comp` on it will return `null`.
### `useSubEnv` ### `useSubEnv` and `useChildSubEnv`
The environment is sometimes useful to share some common information between The environment is sometimes useful to share some common information between
all components. But sometimes, we want to _scope_ that knowledge to a subtree. all components. But sometimes, we want to _scope_ that knowledge to a subtree.
For example, if we have a form view component, maybe we would like to make some For example, if we have a form view component, maybe we would like to make some
`model` object available to all sub components, but not to the whole application. `model` object available to all sub components, but not to the whole application.
This is where the `useSubEnv` hook may be useful: it lets a component add some This is where the `useChildSubEnv` hook may be useful: it lets a component add some
information to the environment in a way that only its children information to the environment in a way that only its children
can access it: can access it:
@@ -167,15 +167,26 @@ can access it:
class FormComponent extends Component { class FormComponent extends Component {
setup() { setup() {
const model = makeModel(); const model = makeModel();
// model will be available on this.env for this component and all children
useSubEnv({ model }); useSubEnv({ model });
// someKey will be available on this.env for all children
useChildSubEnv({ someKey: "value" });
} }
} }
``` ```
The `useSubEnv` takes one argument: an object which contains some key/value that The `useSubEnv` and `useChildSubEnv` hooks take one argument: an object which
will be added to the parent environment. Note that it will extend, not replace contains some key/value that will be added to the current environment. These hooks
the parent environment. And of course, the parent environment will not be will create a new env object with the new information:
affected.
- `useSubEnv` will assign this new `env` to itself and to all children components
- `useChildSubEnv` will only assign this new `env` to all children components.
As usual in Owl, [environments](environment.md) created with these two hooks are
frozen, to prevent unwanted modifications.
Note that both these hooks can be called an arbitrary number of times. The `env`
will then be updated accordingly.
### `useExternalListener` ### `useExternalListener`
+39
View File
@@ -5,6 +5,8 @@
- [Overview](#overview) - [Overview](#overview)
- [`useState`](#usestate) - [`useState`](#usestate)
- [`reactive`](#reactive) - [`reactive`](#reactive)
- [`markRaw`](#markraw)
- [`toRaw`](#toraw)
## Overview ## Overview
@@ -78,3 +80,40 @@ obj2.b = 3; // log 'observer1' and 'observer2'
Obviously, one can use `reactive` on the result of a `useState` if wanted, this Obviously, one can use `reactive` on the result of a `useState` if wanted, this
is the proper way to watch for some state changes. is the proper way to watch for some state changes.
## `markRaw`
Marks an object so that it is ignored by the reactivity system. This function returns its argument.
```js
const someObject = markRaw(...);
const state = useState({
a: 1,
obj: someObject
});
// here, state.obj === someObject
```
This is useful in some rare cases. For example, some complex and large object such
that going through the reactivity system may cause a non trivial performance slowdown.
However, use this function with caution: this is an escape hatch from the reactivity
system, and as such, using it may cause subtle and unintended issues!
## `toRaw`
Given a reactive object, this function returns the underlying, non-reactive,
corresponding object.
```js
// in setup
const state = useState({ value: 1 });
// later:
const rawState = toRaw(this.state);
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
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!
+2 -2
View File
@@ -215,13 +215,13 @@ For other kind of advanced use cases, the content of a slot may depends on some
specific information specific to the generic component. This is the opposite specific information specific to the generic component. This is the opposite
of the slot params. of the slot params.
To solve this kind of problems, one can use the `t-set-scope` directive along To solve this kind of problems, one can use the `t-slot-scope` directive along
with the `t-set-slot`. This defines the name of a variable that can access with the `t-set-slot`. This defines the name of a variable that can access
everything given by the child component: everything given by the child component:
```xml ```xml
<div> <div>
<t t-set-slot="foo" t-set-scope="scope"> <t t-set-slot="foo" t-slot-scope="scope">
content content
<t t-esc="scope.bool"/> <t t-esc="scope.bool"/>
<t t-esc="scope.num"/> <t t-esc="scope.num"/>
+6 -4
View File
@@ -674,7 +674,9 @@ This `RootNode` component will then display a live SVG representation of the
graph described by the `graph` property. Note that there is a recursive structure graph described by the `graph` property. Note that there is a recursive structure
here: the `Node` component uses itself as a subcomponent. here: the `Node` component uses itself as a subcomponent.
Note that since SVG needs to be handled in a specific way (its namespace needs **Important note:** Owl needs to properly set the namespace for each svg elements.
to be properly set), there is a small constraint for Owl components: if an owl Since Owl compile each template separately, it is not able to determine easily
component is supposed to be a part of an svg graph, then its root node needs to if a template is supposed to be included in a svg namespace or not. Therefore,
be a `g` tag, so Owl can properly set the namespace. 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
templates (included with `t-call`) should have one of these tag as root tag.
+22 -18
View File
@@ -1,4 +1,4 @@
import { Component } from "../component/component"; import { Component, ComponentConstructor } from "../component/component";
import { ComponentNode } from "../component/component_node"; import { ComponentNode } from "../component/component_node";
import { MountOptions } from "../component/fibers"; import { MountOptions } from "../component/fibers";
import { Scheduler } from "../component/scheduler"; import { Scheduler } from "../component/scheduler";
@@ -11,9 +11,9 @@ export interface Env {
[key: string]: any; [key: string]: any;
} }
export interface AppConfig extends TemplateSetConfig { export interface AppConfig<P, E> extends TemplateSetConfig {
env?: Env; props?: P;
props?: any; env?: E;
} }
export const DEV_MSG = `Owl is running in 'dev' mode. export const DEV_MSG = `Owl is running in 'dev' mode.
@@ -21,25 +21,29 @@ export const DEV_MSG = `Owl is running in 'dev' mode.
This is not suitable for production use. This is not suitable for production use.
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`; See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`;
export class App<T extends typeof Component = any> extends TemplateSet { export class App<
Root: T; T extends abstract new (...args: any) => any = any,
props: any; P = any,
env: Env; E = any
> extends TemplateSet {
Root: ComponentConstructor<P, E>;
props: P;
env: E;
scheduler = new Scheduler(); scheduler = new Scheduler();
root: ComponentNode | null = null; root: ComponentNode<P, E> | null = null;
constructor(Root: T, config: AppConfig = {}) { constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
super(config); super(config);
this.Root = Root; this.Root = Root;
if (config.dev) { if (config.dev) {
console.info(DEV_MSG); console.info(DEV_MSG);
} }
const descrs = Object.getOwnPropertyDescriptors(config.env || {}); const descrs = Object.getOwnPropertyDescriptors(config.env || {});
this.env = Object.freeze(Object.defineProperties({}, descrs)); this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
this.props = config.props || {}; this.props = config.props || ({} as P);
} }
mount(target: HTMLElement, options?: MountOptions): Promise<InstanceType<T>> { mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
this.checkTarget(target); this.checkTarget(target);
const node = this.makeNode(this.Root, this.props); const node = this.makeNode(this.Root, this.props);
const prom = this.mountNode(node, target, options); const prom = this.mountNode(node, target, options);
@@ -56,7 +60,7 @@ export class App<T extends typeof Component = any> extends TemplateSet {
} }
} }
makeNode(Component: T, props: any): ComponentNode { makeNode(Component: ComponentConstructor, props: any): ComponentNode {
return new ComponentNode(Component, props, this); return new ComponentNode(Component, props, this);
} }
@@ -96,10 +100,10 @@ export class App<T extends typeof Component = any> extends TemplateSet {
} }
} }
export async function mount<T extends typeof Component>( export async function mount<T extends abstract new (...args: any) => any = any, P = any, E = any>(
C: T, C: T & ComponentConstructor<P, E>,
target: HTMLElement, target: HTMLElement,
config: AppConfig & MountOptions = {} config: AppConfig<P, E> & MountOptions = {}
): Promise<InstanceType<T>> { ): Promise<Component<P, E> & InstanceType<T>> {
return new App(C, config).mount(target, config); return new App(C, config).mount(target, config);
} }
+1 -4
View File
@@ -2,8 +2,6 @@ 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.
@@ -23,8 +21,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 nonReactiveProps = ctx.props && ctx.props[TARGET]; const slots = (ctx.props && ctx.props.slots) || {};
const slots = nonReactiveProps ? nonReactiveProps.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) {
+7 -4
View File
@@ -5,7 +5,7 @@ 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 };
export const globalTemplates: { [key: string]: string | Node } = {}; export const globalTemplates: { [key: string]: string | Element } = {};
function parseXML(xml: string): Document { function parseXML(xml: string): Document {
const parser = new DOMParser(); const parser = new DOMParser();
@@ -67,7 +67,11 @@ export class TemplateSet {
} }
} }
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) { addTemplate(
name: string,
template: string | Element,
options: { allowDuplicate?: boolean } = {}
) {
if (name in this.rawTemplates && !options.allowDuplicate) { if (name in this.rawTemplates && !options.allowDuplicate) {
throw new Error(`Template ${name} already defined`); throw new Error(`Template ${name} already defined`);
} }
@@ -82,7 +86,6 @@ export class TemplateSet {
xml = xml instanceof Document ? xml : parseXML(xml); xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) { for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name")!; const name = template.getAttribute("t-name")!;
template.removeAttribute("t-name");
this.addTemplate(name, template, options); this.addTemplate(name, template, options);
} }
} }
@@ -106,7 +109,7 @@ export class TemplateSet {
return this.templates[name]; return this.templates[name];
} }
_compileTemplate(name: string, template: string | Node) { _compileTemplate(name: string, template: string | Element) {
return compile(template, { return compile(template, {
name, name,
dev: this.dev, dev: this.dev,
+1
View File
@@ -61,6 +61,7 @@ class VHtml {
// remove current content // remove current content
this.remove(); this.remove();
this.content = content; this.content = content;
this.html = other.html;
} }
} }
+43 -19
View File
@@ -128,6 +128,7 @@ interface Context {
translate: boolean; translate: boolean;
tKeyExpr: string | null; tKeyExpr: string | null;
nameSpace?: string; nameSpace?: string;
tModelSelectedExpr?: string;
} }
function createContext(parentCtx: Context, params?: Partial<Context>) { function createContext(parentCtx: Context, params?: Partial<Context>) {
@@ -139,6 +140,7 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
translate: parentCtx.translate, translate: parentCtx.translate,
tKeyExpr: null, tKeyExpr: null,
nameSpace: parentCtx.nameSpace, nameSpace: parentCtx.nameSpace,
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
}, },
params params
); );
@@ -522,10 +524,10 @@ export class CodeGenerator {
compileTDomNode(ast: ASTDomNode, ctx: Context) { compileTDomNode(ast: ASTDomNode, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null; const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
let codeIdx = this.target.code.length; let codeIdx = this.target.code.length;
if (isNewBlock) { if (isNewBlock) {
if ((ast.dynamicTag || ctx.tKeyExpr) && ctx.block) { if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
this.insertAnchor(ctx.block!); this.insertAnchor(ctx.block!);
} }
block = this.createBlock(block, "block", ctx); block = this.createBlock(block, "block", ctx);
@@ -543,24 +545,36 @@ export class CodeGenerator {
// specific namespace uri // specific namespace uri
attrs["block-ns"] = nameSpace; attrs["block-ns"] = nameSpace;
} }
for (let key in ast.attrs) { for (let key in ast.attrs) {
let expr, attrName;
if (key.startsWith("t-attf")) { if (key.startsWith("t-attf")) {
let expr = interpolate(ast.attrs[key]); expr = interpolate(ast.attrs[key]);
const idx = block!.insertData(expr, "attr"); const idx = block!.insertData(expr, "attr");
attrs["block-attribute-" + idx] = key.slice(7);
attrName = key.slice(7);
attrs["block-attribute-" + idx] = attrName;
} else if (key.startsWith("t-att")) { } else if (key.startsWith("t-att")) {
let expr = compileExpr(ast.attrs[key]); expr = compileExpr(ast.attrs[key]);
const idx = block!.insertData(expr, "attr"); const idx = block!.insertData(expr, "attr");
if (key === "t-att") { if (key === "t-att") {
attrs[`block-attributes`] = String(idx); attrs[`block-attributes`] = String(idx);
} else { } else {
attrs[`block-attribute-${idx}`] = key.slice(6); attrName = key.slice(6);
attrs[`block-attribute-${idx}`] = attrName;
} }
} else if (this.translatableAttributes.includes(key)) { } else if (this.translatableAttributes.includes(key)) {
attrs[key] = this.translateFn(ast.attrs[key]); attrs[key] = this.translateFn(ast.attrs[key]);
} else { } else {
expr = `"${ast.attrs[key]}"`;
attrName = key;
attrs[key] = ast.attrs[key]; attrs[key] = ast.attrs[key];
} }
if (attrName === "value" && ctx.tModelSelectedExpr) {
let selectedId = block!.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
attrs[`block-attribute-${selectedId}`] = "selected";
}
} }
// event handlers // event handlers
@@ -600,8 +614,10 @@ export class CodeGenerator {
} }
// t-model // t-model
let tModelSelectedExpr;
if (ast.model) { if (ast.model) {
const { const {
hasDynamicChildren,
baseExpr, baseExpr,
expr, expr,
eventType, eventType,
@@ -612,20 +628,25 @@ export class CodeGenerator {
} = ast.model; } = ast.model;
const baseExpression = compileExpr(baseExpr); const baseExpression = compileExpr(baseExpr);
const id = this.generateId(); const bExprId = this.generateId("bExpr");
this.addLine(`const bExpr${id} = ${baseExpression};`); this.addLine(`const ${bExprId} = ${baseExpression};`);
const expression = compileExpr(expr); const expression = compileExpr(expr);
const exprId = this.generateId("expr");
this.addLine(`const ${exprId} = ${expression};`);
const fullExpression = `${bExprId}[${exprId}]`;
let idx: number; let idx: number;
if (specialInitTargetAttr) { if (specialInitTargetAttr) {
idx = block!.insertData( idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
`${baseExpression}[${expression}] === '${attrs[targetAttr]}'`,
"attr"
);
attrs[`block-attribute-${idx}`] = specialInitTargetAttr; attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = this.generateId("bValue");
tModelSelectedExpr = `${bValueId}`;
this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
} else { } else {
idx = block!.insertData(`${baseExpression}[${expression}]`, "attr"); idx = block!.insertData(`${fullExpression}`, "attr");
attrs[`block-attribute-${idx}`] = targetAttr; attrs[`block-attribute-${idx}`] = targetAttr;
} }
this.helpers.add("toNumber"); this.helpers.add("toNumber");
@@ -633,7 +654,7 @@ export class CodeGenerator {
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode; valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode; valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
const handler = `[(ev) => { bExpr${id}[${expression}] = ${valueCode}; }]`; const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
idx = block!.insertData(handler, "hdlr"); idx = block!.insertData(handler, "hdlr");
attrs[`block-handler-${idx}`] = eventType; attrs[`block-handler-${idx}`] = eventType;
} }
@@ -658,6 +679,7 @@ export class CodeGenerator {
isLast: ctx.isLast && i === children.length - 1, isLast: ctx.isLast && i === children.length - 1,
tKeyExpr: ctx.tKeyExpr, tKeyExpr: ctx.tKeyExpr,
nameSpace, nameSpace,
tModelSelectedExpr,
}); });
this.compileAST(child, subCtx); this.compileAST(child, subCtx);
} }
@@ -1081,15 +1103,17 @@ export class CodeGenerator {
let propString = propStr; let propString = propStr;
if (ast.dynamicProps) { if (ast.dynamicProps) {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${ if (!props.length) {
props.length ? ", " + propStr : "" propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
})`; } else {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
}
} }
let propVar: string; let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) { if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = this.generateId("props"); propVar = this.generateId("props");
this.addLine(`const ${propVar!} = ${propString}`); this.addLine(`const ${propVar!} = ${propString};`);
propString = propVar!; propString = propVar!;
} }
@@ -1108,7 +1132,7 @@ export class CodeGenerator {
} }
if (this.dev) { if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`); this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
} }
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) { if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
+4 -1
View File
@@ -9,7 +9,10 @@ export type TemplateFunction = (blocks: any, utils: any) => Template;
interface CompileOptions extends Config { interface CompileOptions extends Config {
name?: string; name?: string;
} }
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction { export function compile(
template: string | Element,
options: CompileOptions = {}
): TemplateFunction {
// parsing // parsing
const ast = parse(template); const ast = parse(template);
+3 -1
View File
@@ -329,7 +329,9 @@ export function compileExprToArray(expr: string): Token[] {
// Mark all variables that have been used locally. // Mark all variables that have been used locally.
// This assumes the expression has only one scope (incorrect but "good enough for now") // This assumes the expression has only one scope (incorrect but "good enough for now")
for (const token of tokens) { for (const token of tokens) {
if (token.type === "SYMBOL" && localVars.has(token.value)) { if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
token.originalValue = token.value;
token.value = `_${token.value}`;
token.isLocal = true; token.isLocal = true;
} }
} }
+54 -28
View File
@@ -33,6 +33,17 @@ export interface ASTComment {
value: string; value: string;
} }
interface TModelInfo {
hasDynamicChildren?: boolean;
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
}
export interface ASTDomNode { export interface ASTDomNode {
type: ASTType.DomNode; type: ASTType.DomNode;
tag: string; tag: string;
@@ -41,15 +52,7 @@ export interface ASTDomNode {
content: AST[]; content: AST[];
ref: string | null; ref: string | null;
on: { [key: string]: string }; on: { [key: string]: string };
model: { model?: TModelInfo | null;
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
} | null;
ns: string | null; ns: string | null;
} }
@@ -177,23 +180,35 @@ export type AST =
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Parser // Parser
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
interface ParsingContext { const cache: WeakMap<Element, AST> = new WeakMap();
inPreTag: boolean;
inSVG: boolean;
}
export function parse(xml: string | Node): AST { export function parse(xml: string | Element): AST {
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element); if (typeof xml === "string") {
normalizeXML(node); const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
const ctx = { inPreTag: false, inSVG: false }; return _parse(elem);
const ast = parseNode(node, ctx); }
let ast = cache.get(xml);
if (!ast) { if (!ast) {
return { type: ASTType.Text, value: "" }; // we clone here the xml to prevent modifying it in place
ast = _parse(xml.cloneNode(true) as Element);
cache.set(xml, ast);
} }
return ast; return ast;
} }
function parseNode(node: ChildNode, ctx: ParsingContext): AST | null { function _parse(xml: Element): AST {
normalizeXML(xml);
const ctx = { inPreTag: false, inSVG: false };
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
}
interface ParsingContext {
tModelInfo?: TModelInfo | null;
inPreTag: boolean;
inSVG: boolean;
}
function parseNode(node: Node, ctx: ParsingContext): AST | null {
if (!(node instanceof Element)) { if (!(node instanceof Element)) {
return parseTextCommentNode(node, ctx); return parseTextCommentNode(node, ctx);
} }
@@ -233,7 +248,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
const lineBreakRE = /[\r\n]/; const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g; const whitespaceRE = /\s+/g;
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null { function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
let value = node.textContent || ""; let value = node.textContent || "";
if (!ctx.inPreTag) { if (!ctx.inPreTag) {
@@ -281,6 +296,8 @@ function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
const hasDotAtTheEnd = /\.[\w_]+\s*$/; const hasDotAtTheEnd = /\.[\w_]+\s*$/;
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/; const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
const { tagName } = node; const { tagName } = node;
const dynamicTag = node.getAttribute("t-tag"); const dynamicTag = node.getAttribute("t-tag");
@@ -292,18 +309,16 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (tagName === "pre") { if (tagName === "pre") {
ctx.inPreTag = true; ctx.inPreTag = true;
} }
const shouldAddSVGNS = tagName === "svg" || (tagName === "g" && !ctx.inSVG); const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
ctx.inSVG = ctx.inSVG || shouldAddSVGNS; ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null; const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
const ref = node.getAttribute("t-ref"); const ref = node.getAttribute("t-ref");
node.removeAttribute("t-ref"); node.removeAttribute("t-ref");
const children = parseChildren(node, ctx);
const nodeAttrsNames = node.getAttributeNames(); const nodeAttrsNames = node.getAttributeNames();
const attrs: ASTDomNode["attrs"] = {}; const attrs: ASTDomNode["attrs"] = {};
const on: ASTDomNode["on"] = {}; const on: ASTDomNode["on"] = {};
let model: ASTDomNode["model"] = null; let model: TModelInfo | null = null;
for (let attr of nodeAttrsNames) { for (let attr of nodeAttrsNames) {
const value = node.getAttribute(attr)!; const value = node.getAttribute(attr)!;
@@ -351,13 +366,24 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
shouldTrim: hasTrimMod && (isOtherInput || isTextarea), shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea), shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
}; };
} else { if (isSelect) {
// don't pollute the original ctx
ctx = Object.assign({}, ctx);
ctx.tModelInfo = model;
}
} 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}'`);
} }
const tModel = ctx.tModelInfo;
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
tModel.hasDynamicChildren = true;
}
attrs[attr] = value; attrs[attr] = value;
} }
} }
const children = parseChildren(node, ctx);
return { return {
type: ASTType.DomNode, type: ASTType.DomNode,
tag: tagName, tag: tagName,
@@ -786,7 +812,7 @@ function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
/** /**
* Parse all the child nodes of a given node and return a list of ast elements * Parse all the child nodes of a given node and return a list of ast elements
*/ */
function parseChildren(node: Node, ctx: ParsingContext): AST[] { function parseChildren(node: Element, ctx: ParsingContext): AST[] {
const children: AST[] = []; const children: AST[] = [];
for (let child of node.childNodes) { for (let child of node.childNodes) {
const childAst = parseNode(child, ctx); const childAst = parseNode(child, ctx);
@@ -805,7 +831,7 @@ function parseChildren(node: Node, ctx: ParsingContext): AST[] {
* Parse all the child nodes of a given node and return an ast if possible. * Parse all the child nodes of a given node and return an ast if possible.
* In the case there are multiple children, they are wrapped in a astmulti. * In the case there are multiple children, they are wrapped in a astmulti.
*/ */
function parseChildNodes(node: Node, ctx: ParsingContext): AST | null { function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
const children = parseChildren(node, ctx); const children = parseChildren(node, ctx);
switch (children.length) { switch (children.length) {
case 0: case 0:
+18 -2
View File
@@ -4,9 +4,25 @@ import type { ComponentNode } from "./component_node";
// Component Class // Component Class
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
type Props = { [key: string]: any };
interface StaticComponentProperties {
template: string;
defaultProps?: any;
props?: any;
}
export type ComponentConstructor<P extends Props = any, E = any> = (new (
props: P,
env: E,
node: ComponentNode
) => Component<P, E>) &
StaticComponentProperties;
export class Component<Props = any, Env = any> { export class Component<Props = any, Env = any> {
static template: string = ""; static template: string = "";
static props?: any; static props?: any;
static defaultProps?: any;
props: Props; props: Props;
env: Env; env: Env;
@@ -20,7 +36,7 @@ export class Component<Props = any, Env = any> {
setup() {} setup() {}
render(force: boolean = false) { render() {
this.__owl__.render(force); this.__owl__.render();
} }
} }
+8 -69
View File
@@ -1,9 +1,6 @@
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 } from "../reactivity"; import { Component, ComponentConstructor } from "./component";
import { batched, Callback } from "../utils";
import { Component } from "./component";
import { fibersInError, handleError } from "./error_handling";
import { import {
Fiber, Fiber,
makeChildFiber, makeChildFiber,
@@ -12,6 +9,7 @@ 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";
@@ -25,46 +23,6 @@ export function useComponent(): Component {
return currentNode!.component; return currentNode!.component;
} }
// -----------------------------------------------------------------------------
// Integration with reactivity system (useState)
// -----------------------------------------------------------------------------
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
* component to subscribe to that data and be rerendered when it changes.
*
* @param state the state to observe
* @returns a reactive object that will cause the component to re-render on
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> {
const node = currentNode!;
let render = batchedRenderFunctions.get(node)!;
if (!render) {
render = batched(node.render.bind(node));
batchedRenderFunctions.set(node, render);
// manual implementation of onWillUnmount to break cyclic dependency
node.willUnmount.unshift(clearReactivesForCallback.bind(null, render));
}
return reactive(state, render);
}
// -----------------------------------------------------------------------------
// component function (used in compiled template code)
// -----------------------------------------------------------------------------
type Props = { [key: string]: any };
function arePropsDifferent(props1: Props, props2: Props): boolean {
for (let k in props1) {
if (props1[k] !== props2[k]) {
return true;
}
}
return false;
}
export function component( export function component(
name: string | typeof Component, name: string | typeof Component,
props: any, props: any,
@@ -89,10 +47,7 @@ export function component(
const parentFiber = ctx.fiber!; const parentFiber = ctx.fiber!;
if (node) { if (node) {
const currentProps = node.component.props[TARGET];
if (parentFiber.force || arePropsDifferent(currentProps, props)) {
node.updateAndRender(props, parentFiber); node.updateAndRender(props, parentFiber);
}
} else { } else {
// new component // new component
let C; let C;
@@ -114,18 +69,16 @@ export function component(
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Component VNode class // Component VNode
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
type LifecycleHook = Function; type LifecycleHook = Function;
export class ComponentNode<T extends typeof Component = typeof Component> export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E>> {
implements VNode<ComponentNode>
{
el?: HTMLElement | Text | undefined; el?: HTMLElement | Text | undefined;
app: App; app: App;
fiber: Fiber | null = null; fiber: Fiber | null = null;
component: InstanceType<T>; component: Component<P, E>;
bdom: BDom | null = null; bdom: BDom | null = null;
status: STATUS = STATUS.NEW; status: STATUS = STATUS.NEW;
@@ -144,7 +97,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
patched: LifecycleHook[] = []; patched: LifecycleHook[] = [];
willDestroy: LifecycleHook[] = []; willDestroy: LifecycleHook[] = [];
constructor(C: T, props: any, app: App, parent?: ComponentNode) { constructor(C: ComponentConstructor<P, E>, props: P, app: App, parent?: ComponentNode) {
currentNode = this; currentNode = this;
this.app = app; this.app = app;
this.parent = parent || null; this.parent = parent || null;
@@ -152,7 +105,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
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();
@@ -181,7 +133,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
} }
async render(force: boolean = false) { async render() {
let current = this.fiber; let current = this.fiber;
if (current && current.root!.locked) { if (current && current.root!.locked) {
await Promise.resolve(); await Promise.resolve();
@@ -189,15 +141,13 @@ export class ComponentNode<T extends typeof Component = typeof Component>
current = this.fiber; current = this.fiber;
} }
if (current && !current.bdom && !fibersInError.has(current)) { if (current && !current.bdom && !fibersInError.has(current)) {
if (current.force || force === false) {
return; return;
} }
}
if (!this.bdom && !current) { if (!this.bdom && !current) {
return; return;
} }
const fiber = makeRootFiber(this, force); const fiber = makeRootFiber(this);
this.fiber = fiber; this.fiber = fiber;
this.app.scheduler.addFiber(fiber); this.app.scheduler.addFiber(fiber);
await Promise.resolve(); await Promise.resolve();
@@ -259,9 +209,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
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);
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) {
@@ -326,14 +273,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
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) {
+3 -6
View File
@@ -13,7 +13,7 @@ export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
return new Fiber(node, parent); return new Fiber(node, parent);
} }
export function makeRootFiber(node: ComponentNode, force: boolean): Fiber { 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!;
@@ -21,7 +21,6 @@ export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
current.children = []; current.children = [];
root.counter++; root.counter++;
current.bdom = null; current.bdom = null;
current.force = force;
if (fibersInError.has(current)) { if (fibersInError.has(current)) {
fibersInError.delete(current); fibersInError.delete(current);
fibersInError.delete(root); fibersInError.delete(root);
@@ -36,7 +35,7 @@ export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
if (node.patched.length) { if (node.patched.length) {
fiber.patched.push(fiber); fiber.patched.push(fiber);
} }
fiber.force = force;
return fiber; return fiber;
} }
@@ -63,13 +62,11 @@ export class Fiber {
parent: Fiber | null; parent: Fiber | null;
children: Fiber[] = []; children: Fiber[] = [];
appliedToDom = false; appliedToDom = false;
force: 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.force = parent.force;
const root = parent.root!; const root = parent.root!;
root.counter++; root.counter++;
this.root = root; this.root = root;
@@ -112,7 +109,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
+18 -12
View File
@@ -1,16 +1,16 @@
import { Component } from "./component"; import { ComponentConstructor } from "./component";
/** /**
* Apply default props (only top level). * Apply default props (only top level).
* *
* Note that this method does modify in place the props * Note that this method does modify in place the props
*/ */
export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: typeof Component) { export function applyDefaultProps<P>(props: P, ComponentClass: ComponentConstructor<P>) {
const defaultProps = (ComponentClass as any).defaultProps; const defaultProps = ComponentClass.defaultProps;
if (defaultProps) { if (defaultProps) {
for (let propName in defaultProps) { for (let propName in defaultProps) {
if (props![propName] === undefined) { if ((props as any)[propName] === undefined) {
props![propName] = defaultProps[propName]; (props as any)[propName] = defaultProps[propName];
} }
} }
} }
@@ -34,11 +34,17 @@ function getPropDescription(staticProps: any) {
* visit recursively the props and all the children to check if they are valid. * visit recursively the props and all the children to check if they are valid.
* This is why it is only done in 'dev' mode. * This is why it is only done in 'dev' mode.
*/ */
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) { export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
const ComponentClass = ( const ComponentClass =
typeof name !== "string" ? name : parent.constructor.components[name] typeof name !== "string"
) as typeof Component; ? name
: (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
if (!ComponentClass) {
// this is an error, wrong component. We silently return here instead so the
// error is triggered by the usual path ('component' function)
return;
}
applyDefaultProps(props, ComponentClass); applyDefaultProps(props, ComponentClass);
let propsDef = getPropDescription(ComponentClass.props); let propsDef = getPropDescription(ComponentClass.props);
@@ -48,7 +54,7 @@ export const validateProps = function (name: string | typeof Component, props: a
if (propName === "*") { if (propName === "*") {
continue; continue;
} }
if (props[propName] === undefined) { if ((props as any)[propName] === undefined) {
if (propsDef[propName] && !propsDef[propName].optional) { if (propsDef[propName] && !propsDef[propName].optional) {
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`); throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
} else { } else {
@@ -57,7 +63,7 @@ export const validateProps = function (name: string | typeof Component, props: a
} }
let isValid; let isValid;
try { try {
isValid = isValidProp(props[propName], propsDef[propName]); isValid = isValidProp((props as any)[propName], propsDef[propName]);
} catch (e) { } catch (e) {
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${ (e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
(e as Error).message (e as Error).message
@@ -75,7 +81,7 @@ export const validateProps = function (name: string | typeof Component, props: a
} }
} }
} }
}; }
/** /**
* Check if an invidual prop value matches its (static) prop definition * Check if an invidual prop value matches its (static) prop definition
+13 -3
View File
@@ -32,6 +32,12 @@ export function useEnv<E extends Env>(): E {
return getCurrent()!.component.env as any; return getCurrent()!.component.env as any;
} }
function extendEnv(currentEnv: Object, extension: Object): Object {
const env = Object.create(currentEnv);
const descrs = Object.getOwnPropertyDescriptors(extension);
return Object.freeze(Object.defineProperties(env, descrs));
}
/** /**
* This hook is a simple way to let components use a sub environment. Note that * This hook is a simple way to let components use a sub environment. Note that
* like for all hooks, it is important that this is only called in the * like for all hooks, it is important that this is only called in the
@@ -39,11 +45,15 @@ export function useEnv<E extends Env>(): E {
*/ */
export function useSubEnv(envExtension: Env) { export function useSubEnv(envExtension: Env) {
const node = getCurrent()!; const node = getCurrent()!;
const env = Object.create(node.childEnv); const newEnv = extendEnv(node.component.env as any, envExtension);
const descrs = Object.getOwnPropertyDescriptors(envExtension); node.component.env = extendEnv(node.component.env as any, envExtension);
node.childEnv = Object.freeze(Object.defineProperties(env, descrs)); node.childEnv = newEnv;
} }
export function useChildSubEnv(envExtension: Env) {
const node = getCurrent()!;
node.childEnv = extendEnv(node.childEnv, envExtension);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// useEffect // useEffect
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
+3 -4
View File
@@ -42,10 +42,9 @@ export { useComponent } 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 { reactive } from "./reactivity"; export { useState, reactive, markRaw, toRaw } from "./reactivity";
export { useState } from "./component/component_node"; export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks"; export { batched, EventBus, whenReady, loadFile, markup } from "./utils";
export { EventBus, whenReady, loadFile, markup } from "./utils";
export { export {
onWillStart, onWillStart,
onMounted, onMounted,
+75 -12
View File
@@ -1,16 +1,27 @@
import { Callback } from "./utils"; import { onWillDestroy } from "./component/lifecycle_hooks";
import { ComponentNode, getCurrent } from "./component/component_node";
import { batched, Callback } from "./utils";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object) // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
export const TARGET = Symbol("Target"); const TARGET = Symbol("Target");
// Escape hatch to prevent reactivity system to turn something into a reactive
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
const KEYCHANGES = Symbol("Key changes"); const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol; type ObjectKey = string | number | symbol;
type Target = object; type Target = object;
export type Reactive<T extends Target = Target> = T & { export type Reactive<T extends Target = Target> = T & {
[TARGET]: any; [TARGET]: any;
}; };
type NonReactive<T extends Target = Target> = T & {
[SKIP]: any;
};
const objectToString = Object.prototype.toString;
/** /**
* Checks whether a given value can be made into a reactive object. * Checks whether a given value can be made into a reactive object.
* *
@@ -18,14 +29,35 @@ export type Reactive<T extends Target = Target> = T & {
* @returns whether the value can be made reactive * @returns whether the value can be made reactive
*/ */
function canBeMadeReactive(value: any): boolean { function canBeMadeReactive(value: any): boolean {
return ( if (typeof value !== "object") {
typeof value === "object" && return false;
value !== null && }
!(value instanceof Date) && // extract "RawType" from strings like "[object RawType]" => this lets us
!(value instanceof Promise) && // ignore many native objects such as Promise (whose toString is [object Promise])
!(value instanceof String) && // or Date ([object Date]).
!(value instanceof Number) const rawType = objectToString.call(value).slice(8, -1);
); return rawType === "Object" || rawType === "Array";
}
/**
* Mark an object or array so that it is ignored by the reactivity system
*
* @param value the value to mark
* @returns the object itself
*/
export function markRaw<T extends Target>(value: T): NonReactive<T> {
(value as any)[SKIP] = true;
return value as NonReactive<T>;
}
/**
* Given a reactive objet, return the raw (non reactive) underlying object
*
* @param value a reactive value
* @returns the underlying value
*/
export function toRaw<T extends object>(value: Reactive<T>): T {
return value[TARGET];
} }
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>(); const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
@@ -83,7 +115,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
*/ */
export function clearReactivesForCallback(callback: Callback): void { function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback); const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) { if (!targetsToClear) {
return; return;
@@ -128,10 +160,16 @@ const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive>>();
* reactive has changed * reactive has changed
* @returns a proxy that tracks changes to it * @returns a proxy that tracks changes to it
*/ */
export function reactive<T extends Target>(target: T, callback: Callback = () => {}): Reactive<T> { export function reactive<T extends Target>(
target: T,
callback: Callback = () => {}
): Reactive<T> | NonReactive<T> {
if (!canBeMadeReactive(target)) { if (!canBeMadeReactive(target)) {
throw new Error(`Cannot make the given value reactive`); throw new Error(`Cannot make the given value reactive`);
} }
if (SKIP in target) {
return target as NonReactive<T>;
}
const originalTarget = (target as Reactive)[TARGET]; const originalTarget = (target as Reactive)[TARGET];
if (originalTarget) { if (originalTarget) {
return reactive(originalTarget, callback); return reactive(originalTarget, callback);
@@ -188,3 +226,28 @@ export function reactive<T extends Target>(target: T, callback: Callback = () =>
} }
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;
}
@@ -353,7 +353,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
let key1 = ctx['project']; let key1 = ctx['project'];
const v1 = ctx['onEdit']; const v1 = ctx['onEdit'];
const v2 = ctx['project']; const v2 = ctx['project'];
let hdlr1 = [\\"prevent\\", ev=>v1(v2.id,ev), ctx]; let hdlr1 = [\\"prevent\\", _ev=>v1(v2.id,_ev), ctx];
let txt1 = ctx['project'].name; let txt1 = ctx['project'].name;
c_block2[i1] = withKey(block3([hdlr1, txt1]), key1); c_block2[i1] = withKey(block3([hdlr1, txt1]), key1);
} }
@@ -30,7 +30,7 @@ exports[`misc complex template 1`] = `
b3 = block3(); b3 = block3();
} }
ctx = Object.create(ctx); ctx = Object.create(ctx);
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(slot=>slot.build_id.id&&!slot.trigger_id.manual&&(ctx['options'].trigger_display[slot.trigger_id.id]))); const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(_slot=>_slot.build_id.id&&!_slot.trigger_id.manual&&(ctx['options'].trigger_display[_slot.trigger_id.id])));
for (let i1 = 0; i1 < l_block4; i1++) { for (let i1 = 0; i1 < l_block4; i1++) {
ctx[\`slot\`] = v_block4[i1]; ctx[\`slot\`] = v_block4[i1];
let key1 = ctx['slot'].id; let key1 = ctx['slot'].id;
@@ -52,6 +52,70 @@ exports[`properly support svg namespace to svg tags added even if already in svg
}" }"
`; `;
exports[`properly support svg svg creates new block if it is within html -- 2 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0/></svg>\`);
let block3 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let b3;
if (ctx['hasPath']) {
b3 = block3();
}
let b2 = block2([], [b3]);
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg creates new block if it is within html 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = block2();
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg namespace added to sub templates if root tag is path 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`path\`);
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg namespace added to sub templates if root tag is path 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`properly support svg svg namespace added to sub-blocks 1`] = ` exports[`properly support svg svg namespace added to sub-blocks 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -1,5 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loading templates addTemplates does not modify its xml document in place 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['value'];
return block1([txt1]);
}
}"
`;
exports[`loading templates can initialize qweb with a string 1`] = ` exports[`loading templates can initialize qweb with a string 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
+10 -6
View File
@@ -163,17 +163,21 @@ describe("expression evaluation", () => {
}); });
test("arrow functions", () => { test("arrow functions", () => {
expect(compileExpr("list.map(e => e.val)")).toBe("ctx['list'].map(e=>e.val)"); expect(compileExpr("list.map(e => e.val)")).toBe("ctx['list'].map(_e=>_e.val)");
expect(compileExpr("list.map(e => a + e)")).toBe("ctx['list'].map(e=>ctx['a']+e)"); expect(compileExpr("list.map(e => a + e)")).toBe("ctx['list'].map(_e=>ctx['a']+_e)");
expect(compileExpr("list.map((e) => e)")).toBe("ctx['list'].map((e)=>e)"); expect(compileExpr("list.map((e) => e)")).toBe("ctx['list'].map((_e)=>_e)");
expect(compileExpr("list.map((elem, index) => elem + index)")).toBe( expect(compileExpr("list.map((elem, index) => elem + index)")).toBe(
"ctx['list'].map((elem,index)=>elem+index)" "ctx['list'].map((_elem,_index)=>_elem+_index)"
);
expect(compileExpr("(ev => ev)(e)")).toBe("(_ev=>_ev)(ctx['e'])");
expect(compileExpr("(v1) => myFunc(v1)")).toBe("(_v1)=>ctx['myFunc'](_v1)");
expect(compileExpr("list.data.map((data) => data)")).toBe(
"ctx['list'].data.map((_data)=>_data)"
); );
expect(compileExpr("(ev => ev)(e)")).toBe("(ev=>ev)(ctx['e'])");
}); });
test.skip("arrow functions: not yet supported", () => { test.skip("arrow functions: not yet supported", () => {
// e is added to localvars in inline_expression but not removed after the arrow func body // e is added to localvars in inline_expression but not removed after the arrow func body
expect(compileExpr("(e => e)(e)")).toBe("(e=>e)(ctx['e'])"); expect(compileExpr("(e => e)(e)")).toBe("(_e=>_e)(ctx['e'])");
}); });
test("assignation", () => { test("assignation", () => {
+75
View File
@@ -1132,6 +1132,81 @@ describe("qweb parser", () => {
}); });
}); });
// ---------------------------------------------------------------------------
// t-model
// ---------------------------------------------------------------------------
test("t-model select", async () => {
expect(parse(`<select t-model="state.model"><option value="1" /></select>`)).toEqual({
type: 2,
tag: "select",
dynamicTag: null,
attrs: {},
on: {},
ref: null,
content: [
{
type: 2,
tag: "option",
dynamicTag: null,
attrs: { value: "1" },
on: {},
ref: null,
content: [],
model: null,
ns: null,
},
],
model: {
baseExpr: "state",
expr: "'model'",
targetAttr: "value",
specialInitTargetAttr: null,
eventType: "change",
shouldTrim: false,
shouldNumberize: false,
},
ns: null,
});
});
test("t-model select dynamic options", async () => {
expect(
parse(`<select t-model="state.model"><option t-att-value="valueVar" /></select>`)
).toEqual({
type: 2,
tag: "select",
dynamicTag: null,
attrs: {},
on: {},
ref: null,
content: [
{
type: 2,
tag: "option",
dynamicTag: null,
attrs: { "t-att-value": "valueVar" },
on: {},
ref: null,
content: [],
model: null,
ns: null,
},
],
model: {
baseExpr: "state",
expr: "'model'",
targetAttr: "value",
specialInitTargetAttr: null,
eventType: "change",
shouldTrim: false,
shouldNumberize: false,
hasDynamicChildren: true,
},
ns: null,
});
});
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// t-component // t-component
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
+62
View File
@@ -1,5 +1,7 @@
import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers"; import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers";
import { mount } from "../../src/blockdom"; import { mount } from "../../src/blockdom";
import { mount as mountComponent, Component, xml } from "../../src/index";
// NB: check the snapshots to see where the SVG namespaces are added // NB: check the snapshots to see where the SVG namespaces are added
snapshotEverything(); snapshotEverything();
@@ -52,4 +54,64 @@ describe("properly support svg", () => {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg"); expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
} }
}); });
test("svg namespace added to sub templates if root tag is path", async () => {
const templates = `<t>
<t t-name="svg"><svg><t t-call="path" /></svg></t>
<t t-name="path"><path /></t>
</t>
`;
const fixture = makeTestFixture();
class Svg extends Component {
static template = "svg";
}
await mountComponent(Svg, fixture, { templates });
const elems = fixture.querySelectorAll("svg, path");
expect(elems.length).toEqual(2);
for (const el of elems) {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
}
});
test("svg creates new block if it is within html", async () => {
class Test extends Component {
static template = xml`
<div>
<svg>
<polygon fill="#000000" points="0 0 4 4 8 0" transform="translate(5 7)"/>
</svg>
</div>
`;
}
const fixture = makeTestFixture();
await mountComponent(Test, fixture);
const elems = fixture.querySelectorAll("svg, polygon");
expect(elems.length).toEqual(2);
for (const el of elems) {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
}
});
test("svg creates new block if it is within html -- 2", async () => {
class Test extends Component {
static template = xml`
<div>
<svg>
<polygon fill="#000000" points="0 0 4 4 8 0" transform="translate(5 7)"/>
<path t-if="hasPath" />
</svg>
</div>
`;
hasPath = true;
}
const fixture = makeTestFixture();
await mountComponent(Test, fixture);
const elems = fixture.querySelectorAll("svg, polygon, path");
expect(elems.length).toEqual(3);
for (const el of elems) {
expect(el.namespaceURI).toBe("http://www.w3.org/2000/svg");
}
});
}); });
+11
View File
@@ -24,6 +24,17 @@ describe("loading templates", () => {
expect(context.renderToString("hey")).toBe("<div>jupiler</div>"); expect(context.renderToString("hey")).toBe("<div>jupiler</div>");
}); });
test("addTemplates does not modify its xml document in place", () => {
const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve"><div t-name="hey"><t t-esc="value"/></div></templates>`;
const xml = new DOMParser().parseFromString(data, "text/xml");
const context = new TestContext();
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
context.addTemplates(xml);
expect(context.renderToString("hey", { value: 123 })).toBe("<div>123</div>");
expect(xml.firstElementChild!.innerHTML).toBe(`<div t-name="hey"><t t-esc="value"/></div>`);
});
test("can load a few templates from a xml string", () => { test("can load a few templates from a xml string", () => {
const data = `<?xml version="1.0" encoding="UTF-8"?> const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve"> <templates id="template" xml:space="preserve">
@@ -1217,6 +1217,20 @@ exports[`t-out in components can render list of t-out 1`] = `
}" }"
`; `;
exports[`t-out in components can switch the contents of two t-out repeatedly 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
let b2 = safeOutput(ctx['state'].a);
let b3 = safeOutput(ctx['state'].b);
return multi([b2, b3]);
}
}"
`;
exports[`t-out in components update properly on state changes 1`] = ` exports[`t-out in components update properly on state changes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -1145,7 +1145,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\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx); let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -1,5 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`basics display a nice error if it cannot find component (in dev mode) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SomeMispelledComponent\`, props1, ctx);
return component(\`SomeMispelledComponent\`, props1, key + \`__1\`, node, ctx);
}
}"
`;
exports[`basics display a nice error if it cannot find component 1`] = ` exports[`basics display a nice error if it cannot find component 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -799,7 +812,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: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__2__\${key1}\`, node, ctx), key1);
} }
return list(c_block1); return list(c_block1);
} }
@@ -105,7 +105,7 @@ exports[`event handling objects from scope are properly captured by t-on 1`] = `
let key1 = ctx['item']; let key1 = ctx['item'];
const v1 = ctx['onClick']; const v1 = ctx['onClick'];
const v2 = ctx['item']; const v2 = ctx['item'];
let hdlr1 = [ev=>v1(v2.val,ev), ctx]; let hdlr1 = [_ev=>v1(v2.val,_ev), ctx];
c_block2[i1] = withKey(block3([hdlr1]), key1); c_block2[i1] = withKey(block3([hdlr1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
@@ -146,7 +146,7 @@ exports[`event handling t-on with handler bound to dynamic argument on a t-forea
let key1 = ctx['item']; let key1 = ctx['item'];
const v1 = ctx['onClick']; const v1 = ctx['onClick'];
const v2 = ctx['item']; const v2 = ctx['item'];
let hdlr1 = [ev=>v1(v2,ev), ctx]; let hdlr1 = [_ev=>v1(v2,_ev), ctx];
c_block2[i1] = withKey(block3([hdlr1]), key1); c_block2[i1] = withKey(block3([hdlr1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
@@ -103,7 +103,7 @@ exports[`hooks mounted callbacks should be called in reverse order from willUnmo
}" }"
`; `;
exports[`hooks parent and child env 1`] = ` exports[`hooks parent and child env (with useChildSubEnv) 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;
@@ -116,7 +116,34 @@ exports[`hooks parent and child env 1`] = `
}" }"
`; `;
exports[`hooks parent and child env 2`] = ` exports[`hooks parent and child env (with useChildSubEnv) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].val;
return block1([txt1]);
}
}"
`;
exports[`hooks parent and child env (with useSubEnv) 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['env'].val);
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
exports[`hooks parent and child env (with useSubEnv) 2`] = `
"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;
@@ -144,7 +171,7 @@ exports[`hooks two different call to willPatch/patched should work 1`] = `
}" }"
`; `;
exports[`hooks use sub env does not pollute user env 1`] = ` exports[`hooks useChildSubEnv does not pollute user env 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;
@@ -158,7 +185,7 @@ exports[`hooks use sub env does not pollute user env 1`] = `
}" }"
`; `;
exports[`hooks use sub env supports arbitrary descriptor 1`] = ` exports[`hooks useChildSubEnv supports arbitrary descriptor 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;
@@ -169,7 +196,7 @@ exports[`hooks use sub env supports arbitrary descriptor 1`] = `
}" }"
`; `;
exports[`hooks use sub env supports arbitrary descriptor 2`] = ` exports[`hooks useChildSubEnv supports arbitrary descriptor 2`] = `
"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;
@@ -287,3 +314,43 @@ exports[`hooks useRef hook: basic use 1`] = `
} }
}" }"
`; `;
exports[`hooks useSubEnv modifies user env 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].val;
return block1([txt1]);
}
}"
`;
exports[`hooks useSubEnv supports arbitrary descriptor 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`hooks useSubEnv supports arbitrary descriptor 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/> <block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].someVal;
let txt2 = ctx['env'].someVal2;
return block1([txt1, txt2]);
}
}"
`;
@@ -160,6 +160,34 @@ exports[`lifecycle hooks components are unmounted destroyed if no longer in DOM
}" }"
`; `;
exports[`lifecycle hooks destroy new children before being mountged 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2,b3,b4;
b2 = text(\`before\`);
if (ctx['state'].flag) {
b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
b4 = text(\`after\`);
return multi([b2, b3, b4]);
}
}"
`;
exports[`lifecycle hooks destroy new children before being mountged 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[`lifecycle hooks hooks are called in proper order in widget creation/destruction 1`] = ` exports[`lifecycle hooks hooks are called in proper order in widget creation/destruction 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -499,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\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx); return component(\`Child\`, {}, key + \`__1\`, node, ctx);
} }
}" }"
`; `;
@@ -42,7 +42,7 @@ exports[`basics arrow functions as prop correctly capture their scope 1`] = `
let key1 = ctx['item'].val; let key1 = ctx['item'].val;
const v1 = ctx['onClick']; const v1 = ctx['onClick'];
const v2 = ctx['item']; const v2 = ctx['item'];
c_block1[i1] = withKey(component(\`Child\`, {onClick: ev=>v1(v2.val,ev)}, key + \`__1__\${key1}\`, node, ctx), key1); c_block1[i1] = withKey(component(\`Child\`, {onClick: _ev=>v1(v2.val,_ev)}, key + \`__1__\${key1}\`, node, ctx), key1);
} }
return list(c_block1); return list(c_block1);
} }
@@ -8,8 +8,8 @@ exports[`default props can set default required boolean values 1`] = `
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 = \\"\\") {
const props1 = {} const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -44,8 +44,8 @@ exports[`default props can set default values 1`] = `
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 = \\"\\") {
const props1 = {} const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -74,8 +74,8 @@ exports[`default props default values are also set whenever component is updated
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 = \\"\\") {
const props1 = {p: ctx['state'].p} const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -102,8 +102,8 @@ exports[`props validation can specify that additional props are allowed (array)
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 = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'} const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, ctx) helpers.validateProps(\`Child\`, props1, ctx);
return component(\`Child\`, props1, key + \`__1\`, node, ctx); return component(\`Child\`, props1, key + \`__1\`, node, ctx);
} }
}" }"
@@ -128,8 +128,8 @@ exports[`props validation can specify that additional props are allowed (object)
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 = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'} const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, ctx) helpers.validateProps(\`Child\`, props1, ctx);
return component(\`Child\`, props1, key + \`__1\`, node, ctx); return component(\`Child\`, props1, key + \`__1\`, node, ctx);
} }
}" }"
@@ -156,8 +156,8 @@ exports[`props validation can validate a prop with multiple types 1`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -185,8 +185,8 @@ exports[`props validation can validate a prop with multiple types 3`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -214,8 +214,8 @@ exports[`props validation can validate a prop with multiple types 5`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -230,8 +230,8 @@ exports[`props validation can validate an array with given primitive type 1`] =
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -259,8 +259,8 @@ exports[`props validation can validate an array with given primitive type 3`] =
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -288,8 +288,8 @@ exports[`props validation can validate an array with given primitive type 5`] =
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -304,8 +304,8 @@ exports[`props validation can validate an array with given primitive type 6`] =
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -320,8 +320,8 @@ exports[`props validation can validate an array with multiple sub element types
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -349,8 +349,8 @@ exports[`props validation can validate an array with multiple sub element types
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -378,8 +378,8 @@ exports[`props validation can validate an array with multiple sub element types
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -407,8 +407,8 @@ exports[`props validation can validate an array with multiple sub element types
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -423,8 +423,8 @@ exports[`props validation can validate an object with simple shape 1`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -452,8 +452,8 @@ exports[`props validation can validate an object with simple shape 3`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -468,8 +468,8 @@ exports[`props validation can validate an object with simple shape 4`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -484,8 +484,8 @@ exports[`props validation can validate an object with simple shape 5`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -500,8 +500,8 @@ exports[`props validation can validate an optional props 1`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -529,8 +529,8 @@ exports[`props validation can validate an optional props 3`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -558,8 +558,8 @@ exports[`props validation can validate an optional props 5`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -574,8 +574,8 @@ exports[`props validation can validate recursively complicated prop def 1`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -603,8 +603,8 @@ exports[`props validation can validate recursively complicated prop def 3`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -632,8 +632,8 @@ exports[`props validation can validate recursively complicated prop def 5`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -648,8 +648,8 @@ exports[`props validation default values are applied before validating props at
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 = \\"\\") {
const props1 = {p: ctx['state'].p} const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -678,8 +678,8 @@ exports[`props validation missing required boolean prop causes an error 1`] = `
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 = \\"\\") {
const props1 = {} const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -694,8 +694,8 @@ exports[`props validation mix of optional and mandatory 1`] = `
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 = \\"\\") {
const props1 = {} const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx) helpers.validateProps(\`Child\`, props1, ctx);
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -710,8 +710,8 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
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 = \\"\\") {
const props1 = {message: 1} const props1 = {message: 1};
helpers.validateProps(\`Child\`, props1, ctx) helpers.validateProps(\`Child\`, props1, ctx);
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -740,8 +740,8 @@ exports[`props validation props are validated whenever component is updated 1`]
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 = \\"\\") {
const props1 = {p: ctx['state'].p} const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -770,8 +770,8 @@ exports[`props validation props: list of strings 1`] = `
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 = \\"\\") {
const props1 = {} const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -786,8 +786,8 @@ exports[`props validation validate simple types 1`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -802,8 +802,8 @@ exports[`props validation validate simple types 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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -831,8 +831,8 @@ exports[`props validation validate simple types 4`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -847,8 +847,8 @@ exports[`props validation validate simple types 5`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -863,8 +863,8 @@ exports[`props validation validate simple types 6`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -892,8 +892,8 @@ exports[`props validation validate simple types 8`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -908,8 +908,8 @@ exports[`props validation validate simple types 9`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -924,8 +924,8 @@ exports[`props validation validate simple types 10`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -953,8 +953,8 @@ exports[`props validation validate simple types 12`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -969,8 +969,8 @@ exports[`props validation validate simple types 13`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -985,8 +985,8 @@ exports[`props validation validate simple types 14`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1014,8 +1014,8 @@ exports[`props validation validate simple types 16`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1030,8 +1030,8 @@ exports[`props validation validate simple types 17`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1046,8 +1046,8 @@ exports[`props validation validate simple types 18`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1075,8 +1075,8 @@ exports[`props validation validate simple types 20`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1091,8 +1091,8 @@ exports[`props validation validate simple types 21`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1107,8 +1107,8 @@ exports[`props validation validate simple types 22`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1136,8 +1136,8 @@ exports[`props validation validate simple types 24`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1152,8 +1152,8 @@ exports[`props validation validate simple types, alternate form 1`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1168,8 +1168,8 @@ exports[`props validation validate simple types, alternate form 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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1197,8 +1197,8 @@ exports[`props validation validate simple types, alternate form 4`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1213,8 +1213,8 @@ exports[`props validation validate simple types, alternate form 5`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1229,8 +1229,8 @@ exports[`props validation validate simple types, alternate form 6`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1258,8 +1258,8 @@ exports[`props validation validate simple types, alternate form 8`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1274,8 +1274,8 @@ exports[`props validation validate simple types, alternate form 9`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1290,8 +1290,8 @@ exports[`props validation validate simple types, alternate form 10`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1319,8 +1319,8 @@ exports[`props validation validate simple types, alternate form 12`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1335,8 +1335,8 @@ exports[`props validation validate simple types, alternate form 13`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1351,8 +1351,8 @@ exports[`props validation validate simple types, alternate form 14`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1380,8 +1380,8 @@ exports[`props validation validate simple types, alternate form 16`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1396,8 +1396,8 @@ exports[`props validation validate simple types, alternate form 17`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1412,8 +1412,8 @@ exports[`props validation validate simple types, alternate form 18`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1441,8 +1441,8 @@ exports[`props validation validate simple types, alternate form 20`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1457,8 +1457,8 @@ exports[`props validation validate simple types, alternate form 21`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1473,8 +1473,8 @@ exports[`props validation validate simple types, alternate form 22`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1502,8 +1502,8 @@ exports[`props validation validate simple types, alternate form 24`] = `
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 = \\"\\") {
const props1 = {p: ctx['p']} const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1518,8 +1518,8 @@ exports[`props validation validation is only done in dev mode 1`] = `
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 = \\"\\") {
const props1 = {} const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx) helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1,163 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`force render in case of existing render 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`B\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`force render in case of existing render 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`C\`, {}, key + \`__1\`, node, ctx);
let b3 = text(ctx['props'].val);
return multi([b2, b3]);
}
}"
`;
exports[`force render in case of existing render 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`C\`);
}
}"
`;
exports[`rendering semantics can force a render to update sub tree 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].value);
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
exports[`rendering semantics can force a render to update sub tree 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
exports[`rendering semantics can render a parent without rendering child 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].value);
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
exports[`rendering semantics can render a parent without rendering child 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
exports[`rendering semantics props are reactive (nested prop) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`rendering semantics props are reactive (nested prop) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].a.b.c);
}
}"
`;
exports[`rendering semantics props are reactive 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`rendering semantics props are reactive 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].a.b);
}
}"
`;
exports[`rendering semantics rendering is atomic (for one subtree) 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].obj.val);
let b3 = component(\`B\`, {obj: ctx['state'].obj}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
exports[`rendering semantics rendering is atomic (for one subtree) 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`C\`, {obj: ctx['props'].obj}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`rendering semantics rendering is atomic (for one subtree) 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].obj.val);
}
}"
`;
@@ -53,8 +53,8 @@ exports[`list of components crash on duplicate key in dev mode 1`] = `
let key1 = 'child'; let key1 = 'child';
if (keys1.has(key1)) { throw new Error(\`Got duplicate key in t-foreach: \${key1}\`)} if (keys1.has(key1)) { throw new Error(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(key1); keys1.add(key1);
const props1 = {} const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx) helpers.validateProps(\`Child\`, props1, ctx);
c_block1[i1] = withKey(component(\`Child\`, props1, key + \`__1__\${key1}\`, node, ctx), key1); c_block1[i1] = withKey(component(\`Child\`, props1, key + \`__1__\${key1}\`, node, ctx), key1);
} }
return list(c_block1); return list(c_block1);
@@ -10,8 +10,9 @@ exports[`t-model directive .lazy modifier 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text; let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -28,8 +29,9 @@ exports[`t-model directive .number modifier 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['number']; const expr1 = 'number';
let hdlr1 = [(ev) => { bExpr1['number'] = toNumber(ev.target.value); }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = toNumber(ev.target.value); }];
let txt1 = ctx['state'].number; let txt1 = ctx['state'].number;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -46,8 +48,9 @@ exports[`t-model directive .trim modifier 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value.trim(); }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value.trim(); }];
let txt1 = ctx['state'].text; let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -64,8 +67,9 @@ exports[`t-model directive basic use, on an input 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text; let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -82,8 +86,9 @@ exports[`t-model directive basic use, on an input with bracket expression 1`] =
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text; let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -100,8 +105,9 @@ exports[`t-model directive basic use, on another key in component 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['some']; const bExpr1 = ctx['some'];
let attr1 = ctx['some']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['some'].text; let txt1 = ctx['some'].text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -119,8 +125,9 @@ exports[`t-model directive can also define t-on directive on same event, part 1
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['onInput'], ctx]; let hdlr1 = [ctx['onInput'], ctx];
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr2 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([hdlr1, attr1, hdlr2]); return block1([hdlr1, attr1, hdlr2]);
} }
}" }"
@@ -137,16 +144,19 @@ exports[`t-model directive can also define t-on directive on same event, part 2
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['onClick'], ctx]; let hdlr1 = [ctx['onClick'], ctx];
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['choice'] === 'One'; const expr1 = 'choice';
let hdlr2 = [(ev) => { bExpr1['choice'] = ev.target.value; }]; let attr1 = bExpr1[expr1] === 'One';
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let hdlr3 = [ctx['onClick'], ctx]; let hdlr3 = [ctx['onClick'], ctx];
const bExpr2 = ctx['state']; const bExpr2 = ctx['state'];
let attr2 = ctx['state']['choice'] === 'Two'; const expr2 = 'choice';
let hdlr4 = [(ev) => { bExpr2['choice'] = ev.target.value; }]; let attr2 = bExpr2[expr2] === 'Two';
let hdlr4 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let hdlr5 = [ctx['onClick'], ctx]; let hdlr5 = [ctx['onClick'], ctx];
const bExpr3 = ctx['state']; const bExpr3 = ctx['state'];
let attr3 = ctx['state']['choice'] === 'Three'; const expr3 = 'choice';
let hdlr6 = [(ev) => { bExpr3['choice'] = ev.target.value; }]; let attr3 = bExpr3[expr3] === 'Three';
let hdlr6 = [(ev) => { bExpr3[expr3] = ev.target.value; }];
return block1([hdlr1, attr1, hdlr2, hdlr3, attr2, hdlr4, hdlr5, attr3, hdlr6]); return block1([hdlr1, attr1, hdlr2, hdlr3, attr2, hdlr4, hdlr5, attr3, hdlr6]);
} }
}" }"
@@ -165,8 +175,9 @@ exports[`t-model directive following a scope protecting directive (e.g. t-set) 1
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"admiral\\", 'Bruno'); setContextValue(ctx, \\"admiral\\", 'Bruno');
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([attr1, hdlr1]); return block1([attr1, hdlr1]);
} }
}" }"
@@ -188,8 +199,9 @@ exports[`t-model directive in a t-foreach 1`] = `
ctx[\`thing\`] = v_block2[i1]; ctx[\`thing\`] = v_block2[i1];
let key1 = ctx['thing'].id; let key1 = ctx['thing'].id;
const bExpr1 = ctx['thing']; const bExpr1 = ctx['thing'];
let attr1 = ctx['thing']['f']; const expr1 = 'f';
let hdlr1 = [(ev) => { bExpr1['f'] = ev.target.checked; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.checked; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1); c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
@@ -215,8 +227,37 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
ctx[\`thing_index\`] = i1; ctx[\`thing_index\`] = i1;
let key1 = ctx['thing_index']; let key1 = ctx['thing_index'];
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state'][ctx['thing_index']]; const expr1 = ctx['thing_index'];
let hdlr1 = [(ev) => { bExpr1[ctx['thing_index']] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
exports[`t-model directive in a t-foreach, part 3 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, toNumber, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['names']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`name\`] = v_block2[i1];
ctx[\`name_index\`] = i1;
let key1 = ctx['name_index'];
const bExpr1 = ctx['state'].values;
const expr1 = ctx['name'];
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1); c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
@@ -235,8 +276,9 @@ exports[`t-model directive on a select 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['color']; const expr1 = 'color';
let hdlr1 = [(ev) => { bExpr1['color'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].color; let txt1 = ctx['state'].color;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -253,8 +295,9 @@ exports[`t-model directive on a select, initial state 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['color']; const expr1 = 'color';
let hdlr1 = [(ev) => { bExpr1['color'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([attr1, hdlr1]); return block1([attr1, hdlr1]);
} }
}" }"
@@ -270,8 +313,9 @@ exports[`t-model directive on a sub state key 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'].something; const bExpr1 = ctx['state'].something;
let attr1 = ctx['state'].something['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].something.text; let txt1 = ctx['state'].something.text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
@@ -288,11 +332,13 @@ exports[`t-model directive on an input type=radio 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['choice'] === 'One'; const expr1 = 'choice';
let hdlr1 = [(ev) => { bExpr1['choice'] = ev.target.value; }]; let attr1 = bExpr1[expr1] === 'One';
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
const bExpr2 = ctx['state']; const bExpr2 = ctx['state'];
let attr2 = ctx['state']['choice'] === 'Two'; const expr2 = 'choice';
let hdlr2 = [(ev) => { bExpr2['choice'] = ev.target.value; }]; let attr2 = bExpr2[expr2] === 'Two';
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let txt1 = ctx['state'].choice; let txt1 = ctx['state'].choice;
return block1([attr1, hdlr1, attr2, hdlr2, txt1]); return block1([attr1, hdlr1, attr2, hdlr2, txt1]);
} }
@@ -309,11 +355,13 @@ exports[`t-model directive on an input type=radio, with initial value 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['choice'] === 'One'; const expr1 = 'choice';
let hdlr1 = [(ev) => { bExpr1['choice'] = ev.target.value; }]; let attr1 = bExpr1[expr1] === 'One';
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
const bExpr2 = ctx['state']; const bExpr2 = ctx['state'];
let attr2 = ctx['state']['choice'] === 'Two'; const expr2 = 'choice';
let hdlr2 = [(ev) => { bExpr2['choice'] = ev.target.value; }]; let attr2 = bExpr2[expr2] === 'Two';
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
return block1([attr1, hdlr1, attr2, hdlr2]); return block1([attr1, hdlr1, attr2, hdlr2]);
} }
}" }"
@@ -330,8 +378,9 @@ exports[`t-model directive on an input, type=checkbox 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3; let b2,b3;
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['flag']; const expr1 = 'flag';
let hdlr1 = [(ev) => { bExpr1['flag'] = ev.target.checked; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.checked; }];
if (ctx['state'].flag) { if (ctx['state'].flag) {
b2 = text(\`yes\`); b2 = text(\`yes\`);
} else { } else {
@@ -352,14 +401,137 @@ exports[`t-model directive on an textarea 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text']; const expr1 = 'text';
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text; let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]); return block1([attr1, hdlr1, txt1]);
} }
}" }"
`; `;
exports[`t-model directive t-model on select with static options 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-attribute-0=\\"value\\" block-handler-1=\\"change\\"><option value=\\"a\\"><block-text-2/></option><option value=\\"b\\"><block-text-3/></option><option value=\\"c\\"><block-text-4/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = 'a';
let txt2 = 'b';
let txt3 = 'c';
return block1([attr1, hdlr1, txt1, txt2, txt3]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options -- 2 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option block-attribute-4=\\"value\\" block-attribute-5=\\"selected\\"><block-text-6/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['options'][0];
let attr2 = bValue1 === ctx['options'][0];
let txt1 = ctx['options'][0];
let attr3 = (ctx['options'][1]);
let attr4 = bValue1 === (ctx['options'][1]);
let txt2 = ctx['options'][1];
return block1([hdlr1, attr1, attr2, txt1, attr3, attr4, txt2]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options -- 3 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option value=\\"b\\" block-attribute-4=\\"selected\\"><block-text-5/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['options'][0];
let attr2 = bValue1 === ctx['options'][0];
let txt1 = ctx['options'][0];
let attr3 = bValue1 === \\"b\\";
let txt2 = ctx['options'][1];
return block1([hdlr1, attr1, attr2, txt1, attr3, txt2]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><option block-attribute-1=\\"value\\" block-attribute-2=\\"selected\\"><block-text-3/></option><option block-attribute-4=\\"value\\" block-attribute-5=\\"selected\\"><block-text-6/></option></select></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let attr1 = ctx['options'][0];
let attr2 = bValue1 === ctx['options'][0];
let txt1 = ctx['options'][0];
let attr3 = ctx['options'][1];
let attr4 = bValue1 === ctx['options'][1];
let txt2 = ctx['options'][1];
return block1([hdlr1, attr1, attr2, txt1, attr3, attr4, txt2]);
}
}"
`;
exports[`t-model directive t-model with dynamic values on select options in foreach 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber, prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><select block-handler-0=\\"change\\"><block-child-0/></select></div>\`);
let block3 = createBlock(\`<option block-attribute-0=\\"value\\" block-attribute-1=\\"selected\\"><block-text-2/></option>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'model';
let bValue1 = bExpr1[expr1]
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['options']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`v\`] = v_block2[i1];
let key1 = ctx['v'];
let attr1 = ctx['v'];
let attr2 = bValue1 === ctx['v'];
let txt1 = ctx['v'];
c_block2[i1] = withKey(block3([attr1, attr2, txt1]), key1);
}
let b2 = list(c_block2);
return block1([hdlr1], [b2]);
}
}"
`;
exports[`t-model directive two inputs in a div alternating with a t-if 1`] = ` exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -374,17 +546,38 @@ exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
let b2,b3; let b2,b3;
if (ctx['state'].flag) { if (ctx['state'].flag) {
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text1']; const expr1 = 'text1';
let hdlr1 = [(ev) => { bExpr1['text1'] = ev.target.value; }]; let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
b2 = block2([attr1, hdlr1]); b2 = block2([attr1, hdlr1]);
} }
if (!ctx['state'].flag) { if (!ctx['state'].flag) {
const bExpr2 = ctx['state']; const bExpr2 = ctx['state'];
let attr2 = ctx['state']['text2']; const expr2 = 'text2';
let hdlr2 = [(ev) => { bExpr2['text2'] = ev.target.value; }]; let attr2 = bExpr2[expr2];
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
b3 = block3([attr2, hdlr2]); b3 = block3([attr2, hdlr2]);
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
}" }"
`; `;
exports[`t-model directive with expression having a changing key 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/><span><block-text-2/></span></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'].something;
const expr1 = ctx['text'].key;
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].something[ctx['text'].key];
return block1([attr1, hdlr1, txt1]);
}
}"
`;
@@ -28,6 +28,30 @@ exports[`t-props basic use 2`] = `
}" }"
`; `;
exports[`t-props child receives a copy of the t-props object, not the original 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['childProps']), key + \`__1\`, node, ctx);
}
}"
`;
exports[`t-props child receives a copy of the t-props object, not the original 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`t-props t-props and other props 1`] = ` exports[`t-props t-props and other props 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
+28 -1
View File
@@ -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).not.toBe(p); expect(this.props).toBe(p);
} }
} }
@@ -1006,4 +1006,31 @@ describe("t-out in components", () => {
"<div>&lt;b&gt;one&lt;/b&gt;<b>one</b>&lt;b&gt;two&lt;/b&gt;<b>two</b>&lt;b&gt;tree&lt;/b&gt;<b>tree</b></div>" "<div>&lt;b&gt;one&lt;/b&gt;<b>one</b>&lt;b&gt;two&lt;/b&gt;<b>two</b>&lt;b&gt;tree&lt;/b&gt;<b>tree</b></div>"
); );
}); });
test("can switch the contents of two t-out repeatedly", async () => {
class Test extends Component {
static template = xml`
<t t-out="state.a"/>
<t t-out="state.b"/>
`;
state = useState({
a: markup("<div>1</div>"),
b: markup("<div>2</div>"),
});
reverse() {
const { state } = this;
[state.a, state.b] = [state.b, state.a];
}
}
const comp = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div>1</div><div>2</div>");
comp.reverse();
await nextTick();
expect(fixture.innerHTML).toBe("<div>2</div><div>1</div>");
comp.reverse();
await nextTick();
expect(fixture.innerHTML).toBe("<div>1</div><div>2</div>");
});
}); });
+18 -26
View File
@@ -39,8 +39,6 @@ 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...");
} }
}); });
@@ -523,7 +521,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
} }
class Child extends Component { class Child extends Component {
static template = xml`<div><SubChild val="props.val"/></div>`; static template = xml`<div><SubChild /></div>`;
static components = { SubChild }; static components = { SubChild };
setup() { setup() {
useLogLifecycle(); useLogLifecycle();
@@ -1909,13 +1907,18 @@ 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",
@@ -2469,9 +2472,9 @@ test("two renderings initiated between willPatch and patched", async () => {
useLogLifecycle(); useLogLifecycle();
onMounted(() => { onMounted(() => {
this.mounted = "Mounted"; this.mounted = "Mounted";
parent.render(true); parent.render();
}); });
onWillUnmount(() => parent.render(true)); onWillUnmount(() => parent.render());
} }
} }
@@ -2504,11 +2507,15 @@ test("two renderings initiated between willPatch and patched", async () => {
"Parent:rendered", "Parent:rendered",
]).toBeLogged(); ]).toBeLogged();
await nextMicroTick();
expect(["Panel:willRender", "Panel:rendered"]).toBeLogged();
await nextTick(); await nextTick();
expect(["Parent:willPatch", "Panel:willPatch", "Panel:patched", "Parent:patched"]).toBeLogged(); expect([
"Panel:willRender",
"Panel:rendered",
"Parent:willPatch",
"Panel:willPatch",
"Panel:patched",
"Parent:patched",
]).toBeLogged();
expect(fixture.innerHTML).toBe("<div><abc>Panel1Mounted</abc></div>"); expect(fixture.innerHTML).toBe("<div><abc>Panel1Mounted</abc></div>");
parent.state.panel = "Panel2"; parent.state.panel = "Panel2";
@@ -2746,20 +2753,12 @@ 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",
@@ -2773,23 +2772,20 @@ 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(true); parent.render();
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",
@@ -2804,14 +2800,12 @@ 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(true); parent.render();
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",
@@ -2828,14 +2822,12 @@ 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();
+23
View File
@@ -81,6 +81,29 @@ describe("basics", () => {
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
}); });
test("display a nice error if it cannot find component (in dev mode)", async () => {
const info = console.info;
console.info = jest.fn(() => {}); // dev mode message
class SomeComponent extends Component {}
class Parent extends Component {
static template = xml`<SomeMispelledComponent />`;
static components = { SomeComponent };
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
expect(console.info).toBeCalledTimes(1);
console.info = info;
});
test("simple catchError", async () => { test("simple catchError", async () => {
class Boom extends Component { class Boom extends Component {
static template = xml`<div t-esc="a.b.c"/>`; static template = xml`<div t-esc="a.b.c"/>`;
+70 -4
View File
@@ -14,6 +14,7 @@ import {
useExternalListener, useExternalListener,
useRef, useRef,
useState, useState,
useChildSubEnv,
useSubEnv, useSubEnv,
xml, xml,
} from "../../src/index"; } from "../../src/index";
@@ -180,7 +181,7 @@ describe("hooks", () => {
expect(fixture.innerHTML).toBe("<div>1</div>"); expect(fixture.innerHTML).toBe("<div>1</div>");
}); });
test("use sub env does not pollute user env", async () => { test("useSubEnv modifies user env", async () => {
class Test extends Component { class Test extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`; static template = xml`<div><t t-esc="env.val"/></div>`;
setup() { setup() {
@@ -190,11 +191,25 @@ describe("hooks", () => {
const env = { val: 3 }; const env = { val: 3 };
const component = await mount(Test, fixture, { env }); const component = await mount(Test, fixture, { env });
expect(fixture.innerHTML).toBe("<div>3</div>"); expect(fixture.innerHTML).toBe("<div>3</div>");
expect(component.env).toHaveProperty("val2");
expect(component.env).toHaveProperty("val");
});
test("useChildSubEnv does not pollute user env", async () => {
class Test extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
setup() {
useChildSubEnv({ val2: 1 });
}
}
const env = { val: 3 };
const component = await mount(Test, fixture, { env });
expect(fixture.innerHTML).toBe("<div>3</div>");
expect(component.env).not.toHaveProperty("val2"); expect(component.env).not.toHaveProperty("val2");
expect(component.env).toHaveProperty("val"); expect(component.env).toHaveProperty("val");
}); });
test("use sub env supports arbitrary descriptor", async () => { test("useSubEnv supports arbitrary descriptor", async () => {
let someVal = "maggot"; let someVal = "maggot";
let someVal2 = "brain"; let someVal2 = "brain";
@@ -213,6 +228,40 @@ describe("hooks", () => {
}); });
} }
} }
const env = {
get someVal() {
return someVal;
},
};
const component = await mount(Test, fixture, { env });
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
someVal = "brain";
someVal2 = "maggot";
component.render();
await nextTick();
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
});
test("useChildSubEnv supports arbitrary descriptor", async () => {
let someVal = "maggot";
let someVal2 = "brain";
class Child extends Component {
static template = xml`<div><t t-esc="env.someVal" /> <t t-esc="env.someVal2" /></div>`;
}
class Test extends Component {
static template = xml`<Child />`;
static components = { Child };
setup() {
useChildSubEnv({
get someVal2() {
return someVal2;
},
});
}
}
someVal = "maggot"; someVal = "maggot";
const env = { const env = {
get someVal() { get someVal() {
@@ -223,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(true); component.render();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div>brain maggot</div>"); expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
}); });
@@ -239,7 +288,7 @@ describe("hooks", () => {
await mount(Test, fixture); await mount(Test, fixture);
}); });
test("parent and child env", async () => { test("parent and child env (with useSubEnv)", async () => {
class Child extends Component { class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`; static template = xml`<div><t t-esc="env.val"/></div>`;
} }
@@ -253,6 +302,23 @@ describe("hooks", () => {
} }
const env = { val: 3 }; const env = { val: 3 };
await mount(Parent, fixture, { env }); await mount(Parent, fixture, { env });
expect(fixture.innerHTML).toBe("5<div>5</div>");
});
test("parent and child env (with useChildSubEnv)", async () => {
class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
}
class Parent extends Component {
static template = xml`<t t-esc="env.val"/><Child/>`;
static components = { Child };
setup() {
useChildSubEnv({ val: 5 });
}
}
const env = { val: 3 };
await mount(Parent, fixture, { env });
expect(fixture.innerHTML).toBe("3<div>5</div>"); expect(fixture.innerHTML).toBe("3<div>5</div>");
}); });
+59 -3
View File
@@ -849,9 +849,8 @@ describe("lifecycle hooks", () => {
class Parent extends Component { class Parent extends Component {
static template = xml` static template = xml`
<Child someValue="state.value" />`; <Child />`;
static components = { Child }; static components = { Child };
state = useState({ value: 1 });
setup() { setup() {
useLogLifecycle(); useLogLifecycle();
} }
@@ -872,7 +871,7 @@ describe("lifecycle hooks", () => {
"Parent:mounted", "Parent:mounted",
]).toBeLogged(); ]).toBeLogged();
parent.state.value++; // to block child render parent.render(); // to block child render
await nextTick(); await nextTick();
expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged(); expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged();
@@ -1009,15 +1008,20 @@ 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();
}); });
@@ -1249,4 +1253,56 @@ describe("lifecycle hooks", () => {
"onWillDestroy", "onWillDestroy",
]).toBeLogged(); ]).toBeLogged();
}); });
test("destroy new children before being mountged", async () => {
class Child extends Component {
static template = xml`child`;
setup() {
useLogLifecycle();
}
}
class Parent extends Component {
static template = xml`before<Child t-if="state.flag"/>after`;
static components = { Child };
state = useState({ flag: false });
setup() {
useLogLifecycle();
onRendered(async () => {
// we destroy here the app after the new child component has been
// created, but before this rendering has been patched to the DOM
if (this.state.flag) {
await Promise.resolve();
app.destroy();
}
});
}
}
const app = new App(Parent);
const parent = await app.mount(fixture);
expect(fixture.innerHTML).toBe("beforeafter");
expect([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Parent:rendered",
"Parent:mounted",
]).toBeLogged();
parent.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("");
expect([
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Parent:willUnmount",
"Child:willDestroy",
"Parent:willDestroy",
]).toBeLogged();
});
}); });
-346
View File
@@ -1,346 +0,0 @@
import { Component, mount, onRendered, onWillUpdateProps, useState, xml } from "../../src";
import {
makeTestFixture,
snapshotEverything,
nextTick,
useLogLifecycle,
makeDeferred,
} from "../helpers";
let fixture: HTMLElement;
snapshotEverything();
beforeEach(() => {
fixture = makeTestFixture();
});
describe("rendering semantics", () => {
test("can render a parent without rendering child", async () => {
class Child extends Component {
static template = xml`child`;
setup() {
useLogLifecycle();
}
}
class Parent extends Component {
static template = xml`
<t t-esc="state.value"/>
<Child/>
`;
static components = { Child };
state = useState({ value: "A" });
setup() {
useLogLifecycle();
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Achild");
expect([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted",
"Parent:mounted",
]).toBeLogged();
parent.state.value = "B";
await nextTick();
expect(fixture.innerHTML).toBe("Bchild");
expect([
"Parent:willRender",
"Parent:rendered",
"Parent:willPatch",
"Parent:patched",
]).toBeLogged();
});
test("can force a render to update sub tree", async () => {
let childN = 0;
let parentN = 0;
class Child extends Component {
static template = xml`child`;
setup() {
onRendered(() => childN++);
}
}
class Parent extends Component {
static template = xml`
<t t-esc="state.value"/>
<Child/>
`;
static components = { Child };
state = { value: "A" };
setup() {
onRendered(() => parentN++);
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Achild");
expect(parentN).toBe(1);
expect(childN).toBe(1);
parent.state.value = "B";
parent.render(true);
await nextTick();
expect(fixture.innerHTML).toBe("Bchild");
expect(parentN).toBe(2);
expect(childN).toBe(2);
});
test("props are reactive", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.a.b"/>`;
setup() {
useLogLifecycle();
}
}
class Parent extends Component {
static template = xml`
<Child a="state"/>
`;
static components = { Child };
state = useState({ b: 1 });
setup() {
useLogLifecycle();
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1");
expect([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted",
"Parent:mounted",
]).toBeLogged();
parent.state.b = 3;
await nextTick();
expect(fixture.innerHTML).toBe("3");
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged();
});
test("props are reactive (nested prop)", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.a.b.c"/>`;
setup() {
useLogLifecycle();
}
}
class Parent extends Component {
static template = xml`
<Child a="state"/>
`;
static components = { Child };
state = useState({ b: { c: 1 } });
setup() {
useLogLifecycle();
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1");
expect([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted",
"Parent:mounted",
]).toBeLogged();
parent.state.b.c = 3; // parent is now subscribed to 'b' key
await nextTick();
expect(fixture.innerHTML).toBe("3");
expect(["Child:willRender", "Child:rendered", "Child:willPatch", "Child:patched"]).toBeLogged();
parent.state.b = { c: 444 }; // triggers a parent and a child render
await nextTick();
expect(fixture.innerHTML).toBe("444");
expect([
"Parent:willRender",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Parent:willPatch",
"Parent:patched",
"Child:willPatch",
"Child:patched",
]).toBeLogged();
});
test("rendering is atomic (for one subtree)", async () => {
const def = makeDeferred();
class C extends Component {
static template = xml`<t t-esc="props.obj.val"/>`;
setup() {
useLogLifecycle();
}
}
class B extends Component {
static template = xml`<C obj="props.obj"/>`;
static components = { C };
setup() {
useLogLifecycle();
onWillUpdateProps(() => def);
}
}
class A extends Component {
static template = xml`<t t-esc="state.obj.val"/><B obj="state.obj"/>`;
static components = { B };
state = useState({ obj: { val: 1 } });
setup() {
useLogLifecycle();
}
}
const parent = await mount(A, fixture);
expect(fixture.innerHTML).toBe("11");
expect([
"A:setup",
"A:willStart",
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
"B:willRender",
"C:setup",
"C:willStart",
"B:rendered",
"C:willRender",
"C:rendered",
"C:mounted",
"B:mounted",
"A:mounted",
]).toBeLogged();
parent.state.obj.val = 3;
await nextTick();
expect(fixture.innerHTML).toBe("33");
expect([
"A:willRender",
"A:rendered",
"C:willRender",
"C:rendered",
"A:willPatch",
"A:patched",
"C:willPatch",
"C:patched",
]).toBeLogged();
def.resolve();
await nextTick();
expect([]).toBeLogged();
});
});
test("force render in case of existing render", async () => {
const def = makeDeferred();
class C extends Component {
static template = xml`C`;
setup() {
useLogLifecycle();
}
}
class B extends Component {
static template = xml`<C/><t t-esc="props.val"/>`;
static components = { C };
setup() {
useLogLifecycle();
onWillUpdateProps(() => def);
}
}
class A extends Component {
static template = xml`<B val="state.val"/>`;
static components = { B };
state = useState({ val: 1 });
setup() {
useLogLifecycle();
}
}
const parent = await mount(A, fixture);
expect(fixture.innerHTML).toBe("C1");
expect([
"A:setup",
"A:willStart",
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
"B:willRender",
"C:setup",
"C:willStart",
"B:rendered",
"C:willRender",
"C:rendered",
"C:mounted",
"B:mounted",
"A:mounted",
]).toBeLogged();
// trigger a new rendering, blocked in B
parent.state.val = 2;
await nextTick();
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
// initiate a new render with force=true. it should cancel the current render
// and also be blocked in B
parent.render(true);
await nextTick();
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
def.resolve();
await nextTick();
// we check here that the render reaches C (so, that it was properly forced)
expect([
"B:willRender",
"C:willUpdateProps",
"B:rendered",
"C:willRender",
"C:rendered",
"A:willPatch",
"B:willPatch",
"C:willPatch",
"C:patched",
"B:patched",
"A:patched",
]).toBeLogged();
});
+160
View File
@@ -246,6 +246,34 @@ describe("t-model directive", () => {
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>"); expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
}); });
test("with expression having a changing key", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<input t-model="state.something[text.key]"/>
<span><t t-esc="state.something[text.key]"/></span>
</div>
`;
state: { something: { [key: string]: string } } = useState({ something: {} });
text = useState({ key: "foo" });
}
const comp = await mount(SomeComponent, fixture);
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
let input = fixture.querySelector("input")!;
await editInput(input, "footest");
expect(comp.state.something[comp.text.key]).toBe("footest");
expect(fixture.innerHTML).toBe("<div><input><span>footest</span></div>");
comp.text.key = "bar";
await nextTick();
input = fixture.querySelector("input")!;
await editInput(input, "test bar");
expect(comp.state.something[comp.text.key]).toBe("test bar");
expect(fixture.innerHTML).toBe("<div><input><span>test bar</span></div>");
});
test(".lazy modifier", async () => { test(".lazy modifier", async () => {
class SomeComponent extends Component { class SomeComponent extends Component {
static template = xml` static template = xml`
@@ -360,6 +388,26 @@ describe("t-model directive", () => {
expect(comp.state).toEqual(["zuko", "uncle iroh"]); expect(comp.state).toEqual(["zuko", "uncle iroh"]);
}); });
test("in a t-foreach, part 3", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<t t-foreach="names" t-as="name" t-key="name_index">
<input t-model="state.values[name]"/>
</t>
</div>
`;
names = ["Crusher", "Data", "Riker", "Worf"];
state = useState({ values: {} });
}
const comp = await mount(SomeComponent, fixture);
expect(comp.state).toEqual({ values: {} });
const input = fixture.querySelectorAll("input")[1]!;
await editInput(input, "Commander");
expect(comp.state).toEqual({ values: { Data: "Commander" } });
});
test("two inputs in a div alternating with a t-if", async () => { test("two inputs in a div alternating with a t-if", async () => {
class SomeComponent extends Component { class SomeComponent extends Component {
static template = xml` static template = xml`
@@ -452,4 +500,116 @@ describe("t-model directive", () => {
expect(comp.state.choice).toBe("Three"); expect(comp.state.choice).toBe("Three");
expect(comp.state.lastClicked).toBe("Three"); expect(comp.state.lastClicked).toBe("Three");
}); });
test("t-model on select with static options", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option value="a" t-esc="'a'"/>
<option value="b" t-esc="'b'"/>
<option value="c" t-esc="'c'"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b", "c"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option t-att-value="options[0]" t-esc="options[0]"/>
<option t-att-value="options[1]" t-esc="options[1]"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options -- 2", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option t-att-value="options[0]" t-esc="options[0]"/>
<option t-attf-value="{{ options[1] }}" t-esc="options[1]"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options -- 3", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<option t-att-value="options[0]" t-esc="options[0]"/>
<option value="b" t-esc="options[1]"/>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
test("t-model with dynamic values on select options in foreach", async () => {
class Test extends Component {
static template = xml`
<div>
<select t-model="state.model">
<t t-foreach="options" t-as="v" t-key="v">
<option t-att-value="v" t-esc="v"/>
</t>
</select>
</div>
`;
state: any;
options: any;
setup() {
this.state = useState({ model: "b" });
this.options = ["a", "b", "c"];
}
}
await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b");
});
}); });
+20 -2
View File
@@ -53,7 +53,7 @@ describe("t-props", () => {
}); });
test("basic use", async () => { test("basic use", async () => {
expect.assertions(5); expect.assertions(4);
let props = { a: 1, b: 2 }; let props = { a: 1, b: 2 };
@@ -65,7 +65,6 @@ 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 {
@@ -105,4 +104,23 @@ describe("t-props", () => {
await mount(Parent, fixture); await mount(Parent, fixture);
}); });
test("child receives a copy of the t-props object, not the original", async () => {
class Child extends Component {
static template = xml`<div/>`;
setup() {
expect(this.props).toEqual({ a: 1, b: 2 });
this.props.d = 5;
}
}
class Parent extends Component {
static template = xml`<Child t-props="childProps"/>`;
static components = { Child };
childProps = { a: 1, b: 2 };
}
const parent = await mount(Parent, fixture);
expect(parent.childProps).not.toHaveProperty("d");
});
}); });
+1 -1
View File
@@ -123,7 +123,7 @@ export function snapshotEverything() {
}); });
const originalCompileTemplate = TemplateSet.prototype._compileTemplate; const originalCompileTemplate = TemplateSet.prototype._compileTemplate;
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Node) { TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) {
const fn = originalCompileTemplate.call(this, "", template); const fn = originalCompileTemplate.call(this, "", template);
if (!globalTemplateNames.has(name)) { if (!globalTemplateNames.has(name)) {
expect(fn.toString()).toMatchSnapshot(); expect(fn.toString()).toMatchSnapshot();
+42 -2
View File
@@ -6,8 +6,10 @@ import {
onWillUpdateProps, onWillUpdateProps,
useState, useState,
xml, xml,
markRaw,
toRaw,
} from "../src"; } from "../src";
import { reactive } from "../src/reactivity"; import { reactive, Reactive } from "../src/reactivity";
import { batched } from "../src/utils"; import { batched } from "../src/utils";
import { import {
makeDeferred, makeDeferred,
@@ -1091,6 +1093,44 @@ describe("Reactivity", () => {
expect(n).toBe(1); expect(n).toBe(1);
expect(state.k).toEqual({ n: 2 }); expect(state.k).toEqual({ n: 2 });
}); });
test("can add collections set/weakset/map/weakmap in a reactive object", () => {
const rawSet = new Set();
const rawWeakSet = new WeakSet();
const rawMap = new Map();
const rawWeakMap = new WeakMap();
const obj = reactive({ rawSet, rawWeakSet, rawMap, rawWeakMap });
expect(obj.rawSet).toBe(rawSet);
expect(obj.rawWeakSet).toBe(rawWeakSet);
expect(obj.rawMap).toBe(rawMap);
expect(obj.rawWeakMap).toBe(rawWeakMap);
});
});
describe("markRaw", () => {
test("markRaw works as expected: value is not observed", () => {
const obj1: any = markRaw({ value: 1 });
const obj2 = { value: 1 };
let n = 0;
const r = reactive({ obj1, obj2 }, () => n++);
expect(n).toBe(0);
r.obj1.value = r.obj1.value + 1;
expect(n).toBe(0);
r.obj2.value = r.obj2.value + 1;
expect(n).toBe(1);
expect(r.obj1).toBe(obj1);
expect(r.obj2).not.toBe(obj2);
});
});
describe("toRaw", () => {
test("toRaw works as expected", () => {
const obj = { value: 1 };
const reactiveObj = reactive(obj);
expect(reactiveObj).not.toBe(obj);
expect(toRaw(reactiveObj as Reactive<typeof obj>)).toBe(obj);
});
}); });
describe("Reactivity: useState", () => { describe("Reactivity: useState", () => {
@@ -1612,7 +1652,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"]); expect([...steps]).toEqual(["list", "quantity1"]);
steps.clear(); steps.clear();
secondQuantity.quantity = 2; secondQuantity.quantity = 2;