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))
- 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 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: 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))
@@ -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: 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: `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: `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))
@@ -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
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
AND the component. It now only defines an environment for the children.
Rationale: This was a subtle cause for bugs: some code had to be rrun
before the call to `useSubEnv`, otherwise it could interfere with the sub environment.
Documentation: [Hooks](doc/reference/hooks.md#usesubenv)
In Owl, a call to `useSubEnv` would define a new 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:
[`useChildSubEnv`](doc/reference/hooks.md#usesubenv-and-usechildsubenv)
### 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) {
class C extends Component {
static template = template;
setup () {
Object.assign(this, context);
}
}
const div = document.createElement('div');
document.body.appendChild(div);
const component = await mount(C, div);
const app = new App(C);
await app.mount(div);
const result = div.innerHTML;
app.destroy();
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`
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)
- [`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
- [`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)
- [`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:
@@ -31,7 +36,8 @@ Other hooks:
- [`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
- [`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:
+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
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
class SomeComponent extends Component {
+18 -7
View File
@@ -8,7 +8,7 @@
- [Other hooks](#other-hooks)
- [`useState`](#usestate)
- [`useRef`](#useref)
- [`useSubEnv`](#usesubenv)
- [`useSubEnv` and `useChildSubEnv`](#usesubenv-and-usechildsubenv)
- [`useExternalListener`](#useexternallistener)
- [`useComponent`](#usecomponent)
- [`useEnv`](#useenv)
@@ -152,14 +152,14 @@ this.ref2 = useRef("component_2");
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`.
### `useSubEnv`
### `useSubEnv` and `useChildSubEnv`
The environment is sometimes useful to share some common information between
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
`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
can access it:
@@ -167,15 +167,26 @@ can access it:
class FormComponent extends Component {
setup() {
const model = makeModel();
// model will be available on this.env for this component and all children
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
will be added to the parent environment. Note that it will extend, not replace
the parent environment. And of course, the parent environment will not be
affected.
The `useSubEnv` and `useChildSubEnv` hooks take one argument: an object which
contains some key/value that will be added to the current environment. These hooks
will create a new env object with the new information:
- `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`
+39
View File
@@ -5,6 +5,8 @@
- [Overview](#overview)
- [`useState`](#usestate)
- [`reactive`](#reactive)
- [`markRaw`](#markraw)
- [`toRaw`](#toraw)
## 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
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
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
everything given by the child component:
```xml
<div>
<t t-set-slot="foo" t-set-scope="scope">
<t t-set-slot="foo" t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<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
here: the `Node` component uses itself as a subcomponent.
Note that since SVG needs to be handled in a specific way (its namespace needs
to be properly set), there is a small constraint for Owl components: if an owl
component is supposed to be a part of an svg graph, then its root node needs to
be a `g` tag, so Owl can properly set the namespace.
**Important note:** Owl needs to properly set the namespace for each svg elements.
Since Owl compile each template separately, it is not able to determine easily
if a template is supposed to be included in a svg namespace or not. Therefore,
Owl depends on a heuristic: if a tag is either `svg`, `g` or `path`, then it will
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 { MountOptions } from "../component/fibers";
import { Scheduler } from "../component/scheduler";
@@ -11,9 +11,9 @@ export interface Env {
[key: string]: any;
}
export interface AppConfig extends TemplateSetConfig {
env?: Env;
props?: any;
export interface AppConfig<P, E> extends TemplateSetConfig {
props?: P;
env?: E;
}
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.
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 {
Root: T;
props: any;
env: Env;
export class App<
T extends abstract new (...args: any) => any = any,
P = any,
E = any
> extends TemplateSet {
Root: ComponentConstructor<P, E>;
props: P;
env: E;
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);
this.Root = Root;
if (config.dev) {
console.info(DEV_MSG);
}
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
this.env = Object.freeze(Object.defineProperties({}, descrs));
this.props = config.props || {};
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
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);
const node = this.makeNode(this.Root, this.props);
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);
}
@@ -96,10 +100,10 @@ export class App<T extends typeof Component = any> extends TemplateSet {
}
}
export async function mount<T extends typeof Component>(
C: T,
export async function mount<T extends abstract new (...args: any) => any = any, P = any, E = any>(
C: T & ComponentConstructor<P, E>,
target: HTMLElement,
config: AppConfig & MountOptions = {}
): Promise<InstanceType<T>> {
config: AppConfig<P, E> & MountOptions = {}
): Promise<Component<P, E> & InstanceType<T>> {
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 { Markup } from "../utils";
import { html } from "../blockdom/index";
import { TARGET } from "../reactivity";
/**
* This file contains utility functions that will be injected in each template,
* to perform various useful tasks in the compiled code.
@@ -23,8 +21,7 @@ function callSlot(
defaultContent?: (ctx: any, node: any, key: string) => BDom
): BDom {
key = key + "__slot_" + name;
const nonReactiveProps = ctx.props && ctx.props[TARGET];
const slots = nonReactiveProps ? nonReactiveProps.slots || {} : {};
const slots = (ctx.props && ctx.props.slots) || {};
const { __render, __ctx, __scope } = slots[name] || {};
const slotScope = Object.create(__ctx || {});
if (__scope) {
+7 -4
View File
@@ -5,7 +5,7 @@ import { UTILS } from "./template_helpers";
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 {
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) {
throw new Error(`Template ${name} already defined`);
}
@@ -82,7 +86,6 @@ export class TemplateSet {
xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name")!;
template.removeAttribute("t-name");
this.addTemplate(name, template, options);
}
}
@@ -106,7 +109,7 @@ export class TemplateSet {
return this.templates[name];
}
_compileTemplate(name: string, template: string | Node) {
_compileTemplate(name: string, template: string | Element) {
return compile(template, {
name,
dev: this.dev,
+1
View File
@@ -61,6 +61,7 @@ class VHtml {
// remove current content
this.remove();
this.content = content;
this.html = other.html;
}
}
+43 -19
View File
@@ -128,6 +128,7 @@ interface Context {
translate: boolean;
tKeyExpr: string | null;
nameSpace?: string;
tModelSelectedExpr?: string;
}
function createContext(parentCtx: Context, params?: Partial<Context>) {
@@ -139,6 +140,7 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
translate: parentCtx.translate,
tKeyExpr: null,
nameSpace: parentCtx.nameSpace,
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
},
params
);
@@ -522,10 +524,10 @@ export class CodeGenerator {
compileTDomNode(ast: ASTDomNode, ctx: Context) {
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;
if (isNewBlock) {
if ((ast.dynamicTag || ctx.tKeyExpr) && ctx.block) {
if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
this.insertAnchor(ctx.block!);
}
block = this.createBlock(block, "block", ctx);
@@ -543,24 +545,36 @@ export class CodeGenerator {
// specific namespace uri
attrs["block-ns"] = nameSpace;
}
for (let key in ast.attrs) {
let expr, attrName;
if (key.startsWith("t-attf")) {
let expr = interpolate(ast.attrs[key]);
expr = interpolate(ast.attrs[key]);
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")) {
let expr = compileExpr(ast.attrs[key]);
expr = compileExpr(ast.attrs[key]);
const idx = block!.insertData(expr, "attr");
if (key === "t-att") {
attrs[`block-attributes`] = String(idx);
} else {
attrs[`block-attribute-${idx}`] = key.slice(6);
attrName = key.slice(6);
attrs[`block-attribute-${idx}`] = attrName;
}
} else if (this.translatableAttributes.includes(key)) {
attrs[key] = this.translateFn(ast.attrs[key]);
} else {
expr = `"${ast.attrs[key]}"`;
attrName = 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
@@ -600,8 +614,10 @@ export class CodeGenerator {
}
// t-model
let tModelSelectedExpr;
if (ast.model) {
const {
hasDynamicChildren,
baseExpr,
expr,
eventType,
@@ -612,20 +628,25 @@ export class CodeGenerator {
} = ast.model;
const baseExpression = compileExpr(baseExpr);
const id = this.generateId();
this.addLine(`const bExpr${id} = ${baseExpression};`);
const bExprId = this.generateId("bExpr");
this.addLine(`const ${bExprId} = ${baseExpression};`);
const expression = compileExpr(expr);
const exprId = this.generateId("expr");
this.addLine(`const ${exprId} = ${expression};`);
const fullExpression = `${bExprId}[${exprId}]`;
let idx: number;
if (specialInitTargetAttr) {
idx = block!.insertData(
`${baseExpression}[${expression}] === '${attrs[targetAttr]}'`,
"attr"
);
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = this.generateId("bValue");
tModelSelectedExpr = `${bValueId}`;
this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
} else {
idx = block!.insertData(`${baseExpression}[${expression}]`, "attr");
idx = block!.insertData(`${fullExpression}`, "attr");
attrs[`block-attribute-${idx}`] = targetAttr;
}
this.helpers.add("toNumber");
@@ -633,7 +654,7 @@ export class CodeGenerator {
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
const handler = `[(ev) => { bExpr${id}[${expression}] = ${valueCode}; }]`;
const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
idx = block!.insertData(handler, "hdlr");
attrs[`block-handler-${idx}`] = eventType;
}
@@ -658,6 +679,7 @@ export class CodeGenerator {
isLast: ctx.isLast && i === children.length - 1,
tKeyExpr: ctx.tKeyExpr,
nameSpace,
tModelSelectedExpr,
});
this.compileAST(child, subCtx);
}
@@ -1081,15 +1103,17 @@ export class CodeGenerator {
let propString = propStr;
if (ast.dynamicProps) {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${
props.length ? ", " + propStr : ""
})`;
if (!props.length) {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
} else {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
}
}
let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = this.generateId("props");
this.addLine(`const ${propVar!} = ${propString}`);
this.addLine(`const ${propVar!} = ${propString};`);
propString = propVar!;
}
@@ -1108,7 +1132,7 @@ export class CodeGenerator {
}
if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
}
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 {
name?: string;
}
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction {
export function compile(
template: string | Element,
options: CompileOptions = {}
): TemplateFunction {
// parsing
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.
// This assumes the expression has only one scope (incorrect but "good enough for now")
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;
}
}
+54 -28
View File
@@ -33,6 +33,17 @@ export interface ASTComment {
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 {
type: ASTType.DomNode;
tag: string;
@@ -41,15 +52,7 @@ export interface ASTDomNode {
content: AST[];
ref: string | null;
on: { [key: string]: string };
model: {
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
} | null;
model?: TModelInfo | null;
ns: string | null;
}
@@ -177,23 +180,35 @@ export type AST =
// -----------------------------------------------------------------------------
// Parser
// -----------------------------------------------------------------------------
interface ParsingContext {
inPreTag: boolean;
inSVG: boolean;
}
const cache: WeakMap<Element, AST> = new WeakMap();
export function parse(xml: string | Node): AST {
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
normalizeXML(node);
const ctx = { inPreTag: false, inSVG: false };
const ast = parseNode(node, ctx);
export function parse(xml: string | Element): AST {
if (typeof xml === "string") {
const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
return _parse(elem);
}
let ast = cache.get(xml);
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;
}
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)) {
return parseTextCommentNode(node, ctx);
}
@@ -233,7 +248,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
const lineBreakRE = /[\r\n]/;
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) {
let value = node.textContent || "";
if (!ctx.inPreTag) {
@@ -281,6 +296,8 @@ function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
const { tagName } = node;
const dynamicTag = node.getAttribute("t-tag");
@@ -292,18 +309,16 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (tagName === "pre") {
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;
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
const ref = node.getAttribute("t-ref");
node.removeAttribute("t-ref");
const children = parseChildren(node, ctx);
const nodeAttrsNames = node.getAttributeNames();
const attrs: ASTDomNode["attrs"] = {};
const on: ASTDomNode["on"] = {};
let model: ASTDomNode["model"] = null;
let model: TModelInfo | null = null;
for (let attr of nodeAttrsNames) {
const value = node.getAttribute(attr)!;
@@ -351,13 +366,24 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
shouldTrim: hasTrimMod && (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")) {
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;
}
}
const children = parseChildren(node, ctx);
return {
type: ASTType.DomNode,
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
*/
function parseChildren(node: Node, ctx: ParsingContext): AST[] {
function parseChildren(node: Element, ctx: ParsingContext): AST[] {
const children: AST[] = [];
for (let child of node.childNodes) {
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.
* 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);
switch (children.length) {
case 0:
+18 -2
View File
@@ -4,9 +4,25 @@ import type { ComponentNode } from "./component_node";
// 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> {
static template: string = "";
static props?: any;
static defaultProps?: any;
props: Props;
env: Env;
@@ -20,7 +36,7 @@ export class Component<Props = any, Env = any> {
setup() {}
render(force: boolean = false) {
this.__owl__.render(force);
render() {
this.__owl__.render();
}
}
+10 -71
View File
@@ -1,9 +1,6 @@
import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom";
import { clearReactivesForCallback, Reactive, reactive, TARGET } from "../reactivity";
import { batched, Callback } from "../utils";
import { Component } from "./component";
import { fibersInError, handleError } from "./error_handling";
import { Component, ComponentConstructor } from "./component";
import {
Fiber,
makeChildFiber,
@@ -12,6 +9,7 @@ import {
MountOptions,
RootFiber,
} from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { applyDefaultProps } from "./props_validation";
import { STATUS } from "./status";
@@ -25,46 +23,6 @@ export function useComponent(): Component {
return currentNode!.component;
}
// -----------------------------------------------------------------------------
// Integration with reactivity system (useState)
// -----------------------------------------------------------------------------
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
* component to subscribe to that data and be rerendered when it changes.
*
* @param state the state to observe
* @returns a reactive object that will cause the component to re-render on
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> {
const node = currentNode!;
let render = batchedRenderFunctions.get(node)!;
if (!render) {
render = batched(node.render.bind(node));
batchedRenderFunctions.set(node, render);
// manual implementation of onWillUnmount to break cyclic dependency
node.willUnmount.unshift(clearReactivesForCallback.bind(null, render));
}
return reactive(state, render);
}
// -----------------------------------------------------------------------------
// component function (used in compiled template code)
// -----------------------------------------------------------------------------
type Props = { [key: string]: any };
function arePropsDifferent(props1: Props, props2: Props): boolean {
for (let k in props1) {
if (props1[k] !== props2[k]) {
return true;
}
}
return false;
}
export function component(
name: string | typeof Component,
props: any,
@@ -89,10 +47,7 @@ export function component(
const parentFiber = ctx.fiber!;
if (node) {
const currentProps = node.component.props[TARGET];
if (parentFiber.force || arePropsDifferent(currentProps, props)) {
node.updateAndRender(props, parentFiber);
}
node.updateAndRender(props, parentFiber);
} else {
// new component
let C;
@@ -114,18 +69,16 @@ export function component(
}
// -----------------------------------------------------------------------------
// Component VNode class
// Component VNode
// -----------------------------------------------------------------------------
type LifecycleHook = Function;
export class ComponentNode<T extends typeof Component = typeof Component>
implements VNode<ComponentNode>
{
export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E>> {
el?: HTMLElement | Text | undefined;
app: App;
fiber: Fiber | null = null;
component: InstanceType<T>;
component: Component<P, E>;
bdom: BDom | null = null;
status: STATUS = STATUS.NEW;
@@ -144,7 +97,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
patched: 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;
this.app = app;
this.parent = parent || null;
@@ -152,7 +105,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
applyDefaultProps(props, C);
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
props = useState(props);
this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
this.component.setup();
@@ -181,7 +133,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
}
}
async render(force: boolean = false) {
async render() {
let current = this.fiber;
if (current && current.root!.locked) {
await Promise.resolve();
@@ -189,15 +141,13 @@ export class ComponentNode<T extends typeof Component = typeof Component>
current = this.fiber;
}
if (current && !current.bdom && !fibersInError.has(current)) {
if (current.force || force === false) {
return;
}
return;
}
if (!this.bdom && !current) {
return;
}
const fiber = makeRootFiber(this, force);
const fiber = makeRootFiber(this);
this.fiber = fiber;
this.app.scheduler.addFiber(fiber);
await Promise.resolve();
@@ -259,9 +209,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
this.fiber = fiber;
const component = this.component;
applyDefaultProps(props, component.constructor as any);
currentNode = this;
props = useState(props);
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
if (fiber !== this.fiber) {
@@ -326,14 +273,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
}
patch() {
if (this.fiber && this.fiber.parent) {
// we only patch here renderings coming from above. renderings initiated
// by the component will be patched independently in the appropriate
// fiber.complete
this._patch();
}
}
_patch() {
const hasChildren = Object.keys(this.children).length > 0;
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
if (hasChildren) {
+3 -6
View File
@@ -13,7 +13,7 @@ export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
return new Fiber(node, parent);
}
export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
export function makeRootFiber(node: ComponentNode): Fiber {
let current = node.fiber;
if (current) {
let root = current.root!;
@@ -21,7 +21,6 @@ export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
current.children = [];
root.counter++;
current.bdom = null;
current.force = force;
if (fibersInError.has(current)) {
fibersInError.delete(current);
fibersInError.delete(root);
@@ -36,7 +35,7 @@ export function makeRootFiber(node: ComponentNode, force: boolean): Fiber {
if (node.patched.length) {
fiber.patched.push(fiber);
}
fiber.force = force;
return fiber;
}
@@ -63,13 +62,11 @@ export class Fiber {
parent: Fiber | null;
children: Fiber[] = [];
appliedToDom = false;
force: boolean = false;
constructor(node: ComponentNode, parent: Fiber | null) {
this.node = node;
this.parent = parent;
if (parent) {
this.force = parent.force;
const root = parent.root!;
root.counter++;
this.root = root;
@@ -112,7 +109,7 @@ export class RootFiber extends Fiber {
current = undefined;
// Step 2: patching the dom
node._patch();
node.patch();
this.locked = false;
// Step 4: calling all mounted lifecycle hooks
+18 -12
View File
@@ -1,16 +1,16 @@
import { Component } from "./component";
import { ComponentConstructor } from "./component";
/**
* Apply default props (only top level).
*
* Note that this method does modify in place the props
*/
export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: typeof Component) {
const defaultProps = (ComponentClass as any).defaultProps;
export function applyDefaultProps<P>(props: P, ComponentClass: ComponentConstructor<P>) {
const defaultProps = ComponentClass.defaultProps;
if (defaultProps) {
for (let propName in defaultProps) {
if (props![propName] === undefined) {
props![propName] = defaultProps[propName];
if ((props as any)[propName] === undefined) {
(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.
* This is why it is only done in 'dev' mode.
*/
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) {
const ComponentClass = (
typeof name !== "string" ? name : parent.constructor.components[name]
) as typeof Component;
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
const ComponentClass =
typeof name !== "string"
? 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);
let propsDef = getPropDescription(ComponentClass.props);
@@ -48,7 +54,7 @@ export const validateProps = function (name: string | typeof Component, props: a
if (propName === "*") {
continue;
}
if (props[propName] === undefined) {
if ((props as any)[propName] === undefined) {
if (propsDef[propName] && !propsDef[propName].optional) {
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
} else {
@@ -57,7 +63,7 @@ export const validateProps = function (name: string | typeof Component, props: a
}
let isValid;
try {
isValid = isValidProp(props[propName], propsDef[propName]);
isValid = isValidProp((props as any)[propName], propsDef[propName]);
} catch (e) {
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
(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
+13 -3
View File
@@ -32,6 +32,12 @@ export function useEnv<E extends Env>(): E {
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
* 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) {
const node = getCurrent()!;
const env = Object.create(node.childEnv);
const descrs = Object.getOwnPropertyDescriptors(envExtension);
node.childEnv = Object.freeze(Object.defineProperties(env, descrs));
const newEnv = extendEnv(node.component.env as any, envExtension);
node.component.env = extendEnv(node.component.env as any, envExtension);
node.childEnv = newEnv;
}
export function useChildSubEnv(envExtension: Env) {
const node = getCurrent()!;
node.childEnv = extendEnv(node.childEnv, envExtension);
}
// -----------------------------------------------------------------------------
// useEffect
// -----------------------------------------------------------------------------
+3 -4
View File
@@ -42,10 +42,9 @@ export { useComponent } from "./component/component_node";
export { status } from "./component/status";
export { Memo } from "./memo";
export { xml } from "./app/template_set";
export { reactive } from "./reactivity";
export { useState } from "./component/component_node";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils";
export { useState, reactive, markRaw, toRaw } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export { batched, EventBus, whenReady, loadFile, markup } from "./utils";
export {
onWillStart,
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)
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
const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol;
type Target = object;
export type Reactive<T extends Target = Target> = T & {
[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.
*
@@ -18,14 +29,35 @@ export type Reactive<T extends Target = Target> = T & {
* @returns whether the value can be made reactive
*/
function canBeMadeReactive(value: any): boolean {
return (
typeof value === "object" &&
value !== null &&
!(value instanceof Date) &&
!(value instanceof Promise) &&
!(value instanceof String) &&
!(value instanceof Number)
);
if (typeof value !== "object") {
return false;
}
// extract "RawType" from strings like "[object RawType]" => this lets us
// ignore many native objects such as Promise (whose toString is [object Promise])
// or Date ([object Date]).
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>>>();
@@ -83,7 +115,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
*
* @param callback the callback for which the reactives need to be cleared
*/
export function clearReactivesForCallback(callback: Callback): void {
function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) {
return;
@@ -128,10 +160,16 @@ const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive>>();
* reactive has changed
* @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)) {
throw new Error(`Cannot make the given value reactive`);
}
if (SKIP in target) {
return target as NonReactive<T>;
}
const originalTarget = (target as Reactive)[TARGET];
if (originalTarget) {
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>;
}
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'];
const v1 = ctx['onEdit'];
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;
c_block2[i1] = withKey(block3([hdlr1, txt1]), key1);
}
@@ -30,7 +30,7 @@ exports[`misc complex template 1`] = `
b3 = block3();
}
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++) {
ctx[\`slot\`] = v_block4[i1];
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`] = `
"function anonymous(bdom, helpers
) {
@@ -1,5 +1,19 @@
// 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`] = `
"function anonymous(bdom, helpers
) {
+10 -6
View File
@@ -163,17 +163,21 @@ describe("expression evaluation", () => {
});
test("arrow functions", () => {
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) => e)")).toBe("ctx['list'].map((e)=>e)");
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) => e)")).toBe("ctx['list'].map((_e)=>_e)");
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", () => {
// 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", () => {
+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
// ---------------------------------------------------------------------------
+62
View File
@@ -1,5 +1,7 @@
import { renderToString, renderToBdom, snapshotEverything, makeTestFixture } from "../helpers";
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
snapshotEverything();
@@ -52,4 +54,64 @@ describe("properly support 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>");
});
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", () => {
const data = `<?xml version="1.0" encoding="UTF-8"?>
<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`] = `
"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>\`);
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]);
}
}"
@@ -1,5 +1,18 @@
// 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`] = `
"function anonymous(bdom, helpers
) {
@@ -799,7 +812,7 @@ exports[`can catch errors catching in child makes parent render 1`] = `
let key1 = ctx['elem'][0];
const v1 = ctx['elem'];
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);
}
@@ -105,7 +105,7 @@ exports[`event handling objects from scope are properly captured by t-on 1`] = `
let key1 = ctx['item'];
const v1 = ctx['onClick'];
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);
}
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'];
const v1 = ctx['onClick'];
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);
}
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
) {
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
) {
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
) {
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
) {
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
) {
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`] = `
"function anonymous(bdom, helpers
) {
@@ -499,7 +527,7 @@ exports[`lifecycle hooks onWillRender 1`] = `
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx);
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -42,7 +42,7 @@ exports[`basics arrow functions as prop correctly capture their scope 1`] = `
let key1 = ctx['item'].val;
const v1 = ctx['onClick'];
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);
}
@@ -8,8 +8,8 @@ exports[`default props can set default required boolean values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -44,8 +44,8 @@ exports[`default props can set default values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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;
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'}
helpers.validateProps(\`Child\`, props1, ctx)
const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, 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;
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'}
helpers.validateProps(\`Child\`, props1, ctx)
const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, 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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -500,8 +500,8 @@ exports[`props validation can validate an optional props 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -529,8 +529,8 @@ exports[`props validation can validate an optional props 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -558,8 +558,8 @@ exports[`props validation can validate an optional props 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -694,8 +694,8 @@ exports[`props validation mix of optional and mandatory 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`Child\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 1}
helpers.validateProps(\`Child\`, props1, ctx)
const props1 = {message: 1};
helpers.validateProps(\`Child\`, props1, ctx);
let b2 = component(\`Child\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -770,8 +770,8 @@ exports[`props validation props: list of strings 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -786,8 +786,8 @@ exports[`props validation validate simple types 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -802,8 +802,8 @@ exports[`props validation validate simple types 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -831,8 +831,8 @@ exports[`props validation validate simple types 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -847,8 +847,8 @@ exports[`props validation validate simple types 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -863,8 +863,8 @@ exports[`props validation validate simple types 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -892,8 +892,8 @@ exports[`props validation validate simple types 8`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -908,8 +908,8 @@ exports[`props validation validate simple types 9`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -924,8 +924,8 @@ exports[`props validation validate simple types 10`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -953,8 +953,8 @@ exports[`props validation validate simple types 12`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -969,8 +969,8 @@ exports[`props validation validate simple types 13`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -985,8 +985,8 @@ exports[`props validation validate simple types 14`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1014,8 +1014,8 @@ exports[`props validation validate simple types 16`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1030,8 +1030,8 @@ exports[`props validation validate simple types 17`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1046,8 +1046,8 @@ exports[`props validation validate simple types 18`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1075,8 +1075,8 @@ exports[`props validation validate simple types 20`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1091,8 +1091,8 @@ exports[`props validation validate simple types 21`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1107,8 +1107,8 @@ exports[`props validation validate simple types 22`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1136,8 +1136,8 @@ exports[`props validation validate simple types 24`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1152,8 +1152,8 @@ exports[`props validation validate simple types, alternate form 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1168,8 +1168,8 @@ exports[`props validation validate simple types, alternate form 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1197,8 +1197,8 @@ exports[`props validation validate simple types, alternate form 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1213,8 +1213,8 @@ exports[`props validation validate simple types, alternate form 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1229,8 +1229,8 @@ exports[`props validation validate simple types, alternate form 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1258,8 +1258,8 @@ exports[`props validation validate simple types, alternate form 8`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1274,8 +1274,8 @@ exports[`props validation validate simple types, alternate form 9`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1290,8 +1290,8 @@ exports[`props validation validate simple types, alternate form 10`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1319,8 +1319,8 @@ exports[`props validation validate simple types, alternate form 12`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1335,8 +1335,8 @@ exports[`props validation validate simple types, alternate form 13`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1351,8 +1351,8 @@ exports[`props validation validate simple types, alternate form 14`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1380,8 +1380,8 @@ exports[`props validation validate simple types, alternate form 16`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1396,8 +1396,8 @@ exports[`props validation validate simple types, alternate form 17`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1412,8 +1412,8 @@ exports[`props validation validate simple types, alternate form 18`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1441,8 +1441,8 @@ exports[`props validation validate simple types, alternate form 20`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1457,8 +1457,8 @@ exports[`props validation validate simple types, alternate form 21`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1473,8 +1473,8 @@ exports[`props validation validate simple types, alternate form 22`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
@@ -1502,8 +1502,8 @@ exports[`props validation validate simple types, alternate form 24`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
let b2 = component(\`SubComp\`, props1, key + \`__1\`, node, ctx);
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';
if (keys1.has(key1)) { throw new Error(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(key1);
const props1 = {}
helpers.validateProps(\`Child\`, props1, ctx)
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
c_block1[i1] = withKey(component(\`Child\`, props1, key + \`__1__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
@@ -10,8 +10,9 @@ exports[`t-model directive .lazy modifier 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text;
return block1([attr1, hdlr1, txt1]);
}
@@ -28,8 +29,9 @@ exports[`t-model directive .number modifier 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['number'];
let hdlr1 = [(ev) => { bExpr1['number'] = toNumber(ev.target.value); }];
const expr1 = 'number';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = toNumber(ev.target.value); }];
let txt1 = ctx['state'].number;
return block1([attr1, hdlr1, txt1]);
}
@@ -46,8 +48,9 @@ exports[`t-model directive .trim modifier 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value.trim(); }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value.trim(); }];
let txt1 = ctx['state'].text;
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 = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text;
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 = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text;
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 = \\"\\") {
const bExpr1 = ctx['some'];
let attr1 = ctx['some']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['some'].text;
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 = \\"\\") {
let hdlr1 = [ctx['onInput'], ctx];
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr2 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
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 = \\"\\") {
let hdlr1 = [ctx['onClick'], ctx];
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['choice'] === 'One';
let hdlr2 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One';
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let hdlr3 = [ctx['onClick'], ctx];
const bExpr2 = ctx['state'];
let attr2 = ctx['state']['choice'] === 'Two';
let hdlr4 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two';
let hdlr4 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let hdlr5 = [ctx['onClick'], ctx];
const bExpr3 = ctx['state'];
let attr3 = ctx['state']['choice'] === 'Three';
let hdlr6 = [(ev) => { bExpr3['choice'] = ev.target.value; }];
const expr3 = 'choice';
let attr3 = bExpr3[expr3] === 'Three';
let hdlr6 = [(ev) => { bExpr3[expr3] = ev.target.value; }];
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
setContextValue(ctx, \\"admiral\\", 'Bruno');
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([attr1, hdlr1]);
}
}"
@@ -188,8 +199,9 @@ exports[`t-model directive in a t-foreach 1`] = `
ctx[\`thing\`] = v_block2[i1];
let key1 = ctx['thing'].id;
const bExpr1 = ctx['thing'];
let attr1 = ctx['thing']['f'];
let hdlr1 = [(ev) => { bExpr1['f'] = ev.target.checked; }];
const expr1 = 'f';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.checked; }];
c_block2[i1] = withKey(block3([attr1, hdlr1]), key1);
}
let b2 = list(c_block2);
@@ -215,8 +227,37 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
ctx[\`thing_index\`] = i1;
let key1 = ctx['thing_index'];
const bExpr1 = ctx['state'];
let attr1 = ctx['state'][ctx['thing_index']];
let hdlr1 = [(ev) => { bExpr1[ctx['thing_index']] = ev.target.value; }];
const expr1 = ctx['thing_index'];
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);
}
let b2 = list(c_block2);
@@ -235,8 +276,9 @@ exports[`t-model directive on a select 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['color'];
let hdlr1 = [(ev) => { bExpr1['color'] = ev.target.value; }];
const expr1 = 'color';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].color;
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 = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['color'];
let hdlr1 = [(ev) => { bExpr1['color'] = ev.target.value; }];
const expr1 = 'color';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([attr1, hdlr1]);
}
}"
@@ -270,8 +313,9 @@ exports[`t-model directive on a sub state key 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'].something;
let attr1 = ctx['state'].something['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].something.text;
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 = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['choice'] === 'One';
let hdlr1 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One';
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
const bExpr2 = ctx['state'];
let attr2 = ctx['state']['choice'] === 'Two';
let hdlr2 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two';
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let txt1 = ctx['state'].choice;
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 = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['choice'] === 'One';
let hdlr1 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One';
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
const bExpr2 = ctx['state'];
let attr2 = ctx['state']['choice'] === 'Two';
let hdlr2 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two';
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
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 = \\"\\") {
let b2,b3;
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['flag'];
let hdlr1 = [(ev) => { bExpr1['flag'] = ev.target.checked; }];
const expr1 = 'flag';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.checked; }];
if (ctx['state'].flag) {
b2 = text(\`yes\`);
} else {
@@ -352,14 +401,137 @@ exports[`t-model directive on an textarea 1`] = `
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text'];
let hdlr1 = [(ev) => { bExpr1['text'] = ev.target.value; }];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let txt1 = ctx['state'].text;
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`] = `
"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;
if (ctx['state'].flag) {
const bExpr1 = ctx['state'];
let attr1 = ctx['state']['text1'];
let hdlr1 = [(ev) => { bExpr1['text1'] = ev.target.value; }];
const expr1 = 'text1';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
b2 = block2([attr1, hdlr1]);
}
if (!ctx['state'].flag) {
const bExpr2 = ctx['state'];
let attr2 = ctx['state']['text2'];
let hdlr2 = [(ev) => { bExpr2['text2'] = ev.target.value; }];
const expr2 = 'text2';
let attr2 = bExpr2[expr2];
let hdlr2 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
b3 = block3([attr2, hdlr2]);
}
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`] = `
"function anonymous(bdom, helpers
) {
+28 -1
View File
@@ -121,7 +121,7 @@ describe("basics", () => {
class Test extends Component {
static template = xml`<span>simple vnode</span>`;
setup() {
expect(this.props).not.toBe(p);
expect(this.props).toBe(p);
}
}
@@ -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>"
);
});
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(() => {
if (lastScheduler && lastScheduler.tasks.size > 0) {
// we still clear the scheduler to prevent additional noise
lastScheduler.tasks.clear();
throw new Error("we got a memory leak...");
}
});
@@ -523,7 +521,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
}
class Child extends Component {
static template = xml`<div><SubChild val="props.val"/></div>`;
static template = xml`<div><SubChild /></div>`;
static components = { SubChild };
setup() {
useLogLifecycle();
@@ -1909,13 +1907,18 @@ test("concurrent renderings scenario 13", async () => {
await nextTick(); // wait for this change to be applied
expect([
"Parent:willRender",
"Child:willUpdateProps",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:willRender",
"Child:rendered",
"Parent:willPatch",
"Child:willPatch",
"Child:mounted",
"Child:patched",
"Parent:patched",
"Child:willRender",
"Child:rendered",
@@ -2469,9 +2472,9 @@ test("two renderings initiated between willPatch and patched", async () => {
useLogLifecycle();
onMounted(() => {
this.mounted = "Mounted";
parent.render(true);
parent.render();
});
onWillUnmount(() => parent.render(true));
onWillUnmount(() => parent.render());
}
}
@@ -2504,11 +2507,15 @@ test("two renderings initiated between willPatch and patched", async () => {
"Parent:rendered",
]).toBeLogged();
await nextMicroTick();
expect(["Panel:willRender", "Panel:rendered"]).toBeLogged();
await nextTick();
expect(["Parent:willPatch", "Panel:willPatch", "Panel:patched", "Parent:patched"]).toBeLogged();
expect([
"Panel:willRender",
"Panel:rendered",
"Parent:willPatch",
"Panel:willPatch",
"Panel:patched",
"Parent:patched",
]).toBeLogged();
expect(fixture.innerHTML).toBe("<div><abc>Panel1Mounted</abc></div>");
parent.state.panel = "Panel2";
@@ -2746,20 +2753,12 @@ test("delay willUpdateProps with rendering grandchild", async () => {
static template = xml`<Parent state="state"/>`;
static components = { Parent };
state = { value: 0 };
setup() {
useLogLifecycle();
}
}
const parent = await mount(GrandParent, fixture);
expect(fixture.innerHTML).toBe("0_0<div></div>");
expect([
"GrandParent:setup",
"GrandParent:willStart",
"GrandParent:willRender",
"Parent:setup",
"Parent:willStart",
"GrandParent:rendered",
"Parent:willRender",
"DelayedChild:setup",
"DelayedChild:willStart",
@@ -2773,23 +2772,20 @@ test("delay willUpdateProps with rendering grandchild", async () => {
"ReactiveChild:mounted",
"DelayedChild:mounted",
"Parent:mounted",
"GrandParent:mounted",
]).toBeLogged();
promise = makeDeferred();
const prom1 = promise;
parent.state.value = 1;
child.render(); // trigger a root rendering first
parent.render(true);
parent.render();
reactiveChild.render();
await nextTick();
expect(fixture.innerHTML).toBe("0_0<div></div>");
expect([
"DelayedChild:willRender",
"DelayedChild:rendered",
"GrandParent:willRender",
"Parent:willUpdateProps",
"GrandParent:rendered",
"ReactiveChild:willRender",
"ReactiveChild:rendered",
"Parent:willRender",
@@ -2804,14 +2800,12 @@ test("delay willUpdateProps with rendering grandchild", async () => {
const prom2 = promise;
child.render(); // trigger a root rendering first
parent.state.value = 2;
parent.render(true);
parent.render();
reactiveChild.render();
await nextTick();
expect(fixture.innerHTML).toBe("0_0<div></div>");
expect([
"GrandParent:willRender",
"Parent:willUpdateProps",
"GrandParent:rendered",
"ReactiveChild:willRender",
"ReactiveChild:rendered",
"Parent:willRender",
@@ -2828,14 +2822,12 @@ test("delay willUpdateProps with rendering grandchild", async () => {
expect([
"DelayedChild:willRender",
"DelayedChild:rendered",
"GrandParent:willPatch",
"Parent:willPatch",
"ReactiveChild:willPatch",
"DelayedChild:willPatch",
"DelayedChild:patched",
"ReactiveChild:patched",
"Parent:patched",
"GrandParent:patched",
]).toBeLogged();
prom1.resolve();
+23
View File
@@ -81,6 +81,29 @@ describe("basics", () => {
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 () => {
class Boom extends Component {
static template = xml`<div t-esc="a.b.c"/>`;
+70 -4
View File
@@ -14,6 +14,7 @@ import {
useExternalListener,
useRef,
useState,
useChildSubEnv,
useSubEnv,
xml,
} from "../../src/index";
@@ -180,7 +181,7 @@ describe("hooks", () => {
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 {
static template = xml`<div><t t-esc="env.val"/></div>`;
setup() {
@@ -190,11 +191,25 @@ describe("hooks", () => {
const env = { val: 3 };
const component = await mount(Test, fixture, { env });
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).toHaveProperty("val");
});
test("use sub env supports arbitrary descriptor", async () => {
test("useSubEnv supports arbitrary descriptor", async () => {
let someVal = "maggot";
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";
const env = {
get someVal() {
@@ -223,7 +272,7 @@ describe("hooks", () => {
expect(fixture.innerHTML).toBe("<div>maggot brain</div>");
someVal = "brain";
someVal2 = "maggot";
component.render(true);
component.render();
await nextTick();
expect(fixture.innerHTML).toBe("<div>brain maggot</div>");
});
@@ -239,7 +288,7 @@ describe("hooks", () => {
await mount(Test, fixture);
});
test("parent and child env", async () => {
test("parent and child env (with useSubEnv)", async () => {
class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
}
@@ -253,6 +302,23 @@ describe("hooks", () => {
}
const env = { val: 3 };
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>");
});
+59 -3
View File
@@ -849,9 +849,8 @@ describe("lifecycle hooks", () => {
class Parent extends Component {
static template = xml`
<Child someValue="state.value" />`;
<Child />`;
static components = { Child };
state = useState({ value: 1 });
setup() {
useLogLifecycle();
}
@@ -872,7 +871,7 @@ describe("lifecycle hooks", () => {
"Parent:mounted",
]).toBeLogged();
parent.state.value++; // to block child render
parent.render(); // to block child render
await nextTick();
expect(["Parent:willRender", "Child:willUpdateProps", "Parent:rendered"]).toBeLogged();
@@ -1009,15 +1008,20 @@ describe("lifecycle hooks", () => {
await nextTick();
expect([
"C:willRender",
"D:willUpdateProps",
"F:setup",
"F:willStart",
"C:rendered",
"D:willRender",
"D:rendered",
"F:willRender",
"F:rendered",
"C:willPatch",
"D:willPatch",
"E:willUnmount",
"E:willDestroy",
"F:mounted",
"D:patched",
"C:patched",
]).toBeLogged();
});
@@ -1249,4 +1253,56 @@ describe("lifecycle hooks", () => {
"onWillDestroy",
]).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>");
});
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 () => {
class SomeComponent extends Component {
static template = xml`
@@ -360,6 +388,26 @@ describe("t-model directive", () => {
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 () => {
class SomeComponent extends Component {
static template = xml`
@@ -452,4 +500,116 @@ describe("t-model directive", () => {
expect(comp.state.choice).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 () => {
expect.assertions(5);
expect.assertions(4);
let props = { a: 1, b: 2 };
@@ -65,7 +65,6 @@ describe("t-props", () => {
`;
setup() {
expect(this.props).toEqual({ a: 1, b: 2 });
expect(this.props).not.toBe(props);
}
}
class Parent extends Component {
@@ -105,4 +104,23 @@ describe("t-props", () => {
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;
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Node) {
TemplateSet.prototype._compileTemplate = function (name: string, template: string | Element) {
const fn = originalCompileTemplate.call(this, "", template);
if (!globalTemplateNames.has(name)) {
expect(fn.toString()).toMatchSnapshot();
+42 -2
View File
@@ -6,8 +6,10 @@ import {
onWillUpdateProps,
useState,
xml,
markRaw,
toRaw,
} from "../src";
import { reactive } from "../src/reactivity";
import { reactive, Reactive } from "../src/reactivity";
import { batched } from "../src/utils";
import {
makeDeferred,
@@ -1091,6 +1093,44 @@ describe("Reactivity", () => {
expect(n).toBe(1);
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", () => {
@@ -1612,7 +1652,7 @@ describe("Reactivity: useState", () => {
expect([...steps]).toEqual(["list"]);
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>");
expect([...steps]).toEqual(["list"]);
expect([...steps]).toEqual(["list", "quantity1"]);
steps.clear();
secondQuantity.quantity = 2;