Compare commits

..

7 Commits

Author SHA1 Message Date
Samuel Degueldre b04013f82f [IMP] reactivity: add support for derived properties 2024-03-19 13:48:08 +01:00
Arnaud Baes dd292472b9 [IMP] owl-vision: Better snippets
- Adds a basic XML owl template
- Make use of `$TM_FILENAME_BASE` and `$RELATIVE_FILEPATH` and attempt
  to predict the component and template names.

See: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables
2024-03-14 12:40:37 +01:00
Trịnh Đức Độ 9b18b57fdf [FIX] docs: code in example cannot run 2024-03-14 08:10:42 +01:00
Samuel Degueldre 68f491cd32 [REL] v2.2.9
# v2.2.9

 - [IMP] reactivity: replace sets with small arrays for performance
2024-01-12 15:44:13 +01:00
Samuel Degueldre 7b3e39ba27 [IMP] reactivity: replace sets with small arrays for performance
While Sets have better lookup complexity than arrays, because of the
large constant factors, small arrays can perform better than small sets
when checking for inclusion.

In practice, replacing both of the raw types sets with arrays can
improve performance of reactive-heavy workloads by as much as 30%.

Considering the reactivity code is very hot when rendering data-heavy
components, and the low impact on readability of the fix, the
cost-benefit analysis is clearly in favour of making the fix.
2024-01-12 15:40:14 +01:00
Samuel Degueldre 61fc3f4fdc [REL] v2.2.8
# v2.2.8

 - [IMP] template set config: getTemplate function
 - [IMP] parser: .trim modifier implies .lazy modifier
 - [REF] parser, template_set: factor out parseXML function
2024-01-12 11:03:03 +01:00
Mathieu Duckerts-Antoine 7b454dae66 [IMP] template set config: getTemplate function
A template set can now obtain a template by calling the function
getTemplate (if any) received in the initial config.
The returned value can be an Element instance, a template string, a
function (i.e. a compiled template), or null. In the last case, owl will
look into the template set rawTemplates object.
2024-01-12 10:56:02 +01:00
20 changed files with 466 additions and 178 deletions
+2
View File
@@ -61,6 +61,8 @@ The `config` object is an object with some of the following keys:
templates (see [translations](translations.md)) templates (see [translations](translations.md))
- **`templates (string | xml document)`**: all the templates that will be used by - **`templates (string | xml document)`**: all the templates that will be used by
the components created by the application. the components created by the application.
- **`getTemplate ((s: string) => Element | Function | string | void)`**: a function that will be called by owl when it
needs a template. If undefined is returned, owl looks into the app templates.
- **`warnIfNoStaticProps (boolean, default=false)`**: if true, Owl will log a warning - **`warnIfNoStaticProps (boolean, default=false)`**: if true, Owl will log a warning
whenever it encounters a component that does not provide a [static props description](props.md#props-validation). whenever it encounters a component that does not provide a [static props description](props.md#props-validation).
+1 -1
View File
@@ -152,7 +152,7 @@ This may seem counter-intuitive, but it makes perfect sense in the context of co
```js ```js
class DoubleCounter extends Component { class DoubleCounter extends Component {
static template = xml` static template = xml`
<t t-esc="state.selected + ': ' + state[state.selected].value"/> <t t-esc="'selected: ' + state.selected + ', value: ' + state[state.selected]"/>
<button t-on-click="() => this.state.count1++">increment count 1</button> <button t-on-click="() => this.state.count1++">increment count 1</button>
<button t-on-click="() => this.state.count2++">increment count 2</button> <button t-on-click="() => this.state.count2++">increment count 2</button>
<button t-on-click="changeCounter">Switch counter</button> <button t-on-click="changeCounter">Switch counter</button>
+25 -49
View File
@@ -1850,8 +1850,9 @@ const NO_CALLBACK = () => {
}; };
const objectToString = Object.prototype.toString; const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty; const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]); // Use arrays because Array.includes is faster than Set.has for small arrays
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]); const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
/** /**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore * extract "RawType" from strings like "[object RawType]" => this lets us ignore
* many native objects such as Promise (whose toString is [object Promise]) * many native objects such as Promise (whose toString is [object Promise])
@@ -1874,7 +1875,7 @@ function canBeMadeReactive(value) {
if (typeof value !== "object") { if (typeof value !== "object") {
return false; return false;
} }
return SUPPORTED_RAW_TYPES.has(rawType(value)); return SUPPORTED_RAW_TYPES.includes(rawType(value));
} }
/** /**
* Creates a reactive from the given object/callback if possible and returns it, * Creates a reactive from the given object/callback if possible and returns it,
@@ -2044,7 +2045,7 @@ function reactive(target, callback = NO_CALLBACK) {
const reactivesForTarget = reactiveCache.get(target); const reactivesForTarget = reactiveCache.get(target);
if (!reactivesForTarget.has(callback)) { if (!reactivesForTarget.has(callback)) {
const targetRawType = rawType(target); const targetRawType = rawType(target);
const handler = COLLECTION_RAWTYPES.has(targetRawType) const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
? collectionsProxyHandler(target, callback, targetRawType) ? collectionsProxyHandler(target, callback, targetRawType)
: basicProxyHandler(callback); : basicProxyHandler(callback);
const proxy = new Proxy(target, handler); const proxy = new Proxy(target, handler);
@@ -3159,8 +3160,14 @@ const helpers = {
makeRefWrapper, makeRefWrapper,
}; };
const bdom = { text, createBlock, list, multi, html, toggler, comment }; /**
function parseXML$1(xml) { * Parses an XML string into an XML document, throwing errors on parser errors
* instead of returning an XML document containing the parseerror.
*
* @param xml the string to parse
* @returns an XML document corresponding to the content of the string
*/
function parseXML(xml) {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml"); const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) { if (doc.getElementsByTagName("parsererror").length) {
@@ -3187,7 +3194,9 @@ function parseXML$1(xml) {
throw new OwlError(msg); throw new OwlError(msg);
} }
return doc; return doc;
} }
const bdom = { text, createBlock, list, multi, html, toggler, comment };
class TemplateSet { class TemplateSet {
constructor(config = {}) { constructor(config = {}) {
this.rawTemplates = Object.create(globalTemplates); this.rawTemplates = Object.create(globalTemplates);
@@ -3206,6 +3215,7 @@ class TemplateSet {
} }
} }
} }
this.getRawTemplate = config.getTemplate;
} }
static registerTemplate(name, fn) { static registerTemplate(name, fn) {
globalTemplates[name] = fn; globalTemplates[name] = fn;
@@ -3235,15 +3245,16 @@ class TemplateSet {
// empty string // empty string
return; return;
} }
xml = xml instanceof Document ? xml : parseXML$1(xml); xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) { for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name"); const name = template.getAttribute("t-name");
this.addTemplate(name, template); this.addTemplate(name, template);
} }
} }
getTemplate(name) { getTemplate(name) {
var _a;
if (!(name in this.templates)) { if (!(name in this.templates)) {
const rawTemplate = this.rawTemplates[name]; const rawTemplate = ((_a = this.getRawTemplate) === null || _a === void 0 ? void 0 : _a.call(this, name)) || this.rawTemplates[name];
if (rawTemplate === undefined) { if (rawTemplate === undefined) {
let extraInfo = ""; let extraInfo = "";
try { try {
@@ -4953,9 +4964,9 @@ function parseDOMNode(node, ctx) {
const isSelect = tagName === "select"; const isSelect = tagName === "select";
const isCheckboxInput = isInput && typeAttr === "checkbox"; const isCheckboxInput = isInput && typeAttr === "checkbox";
const isRadioInput = isInput && typeAttr === "radio"; const isRadioInput = isInput && typeAttr === "radio";
const hasLazyMod = attr.includes(".lazy");
const hasNumberMod = attr.includes(".number");
const hasTrimMod = attr.includes(".trim"); const hasTrimMod = attr.includes(".trim");
const hasLazyMod = hasTrimMod || attr.includes(".lazy");
const hasNumberMod = attr.includes(".number");
const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input"; const eventType = isRadioInput ? "click" : isSelect || hasLazyMod ? "change" : "input";
model = { model = {
baseExpr, baseExpr,
@@ -5501,41 +5512,6 @@ function normalizeTEscTOut(el) {
function normalizeXML(el) { function normalizeXML(el) {
normalizeTIf(el); normalizeTIf(el);
normalizeTEscTOut(el); normalizeTEscTOut(el);
}
/**
* Parses an XML string into an XML document, throwing errors on parser errors
* instead of returning an XML document containing the parseerror.
*
* @param xml the string to parse
* @returns an XML document corresponding to the content of the string
*/
function parseXML(xml) {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
let msg = "Invalid XML in template.";
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
if (parsererrorText) {
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
const re = /\d+/g;
const firstMatch = re.exec(parsererrorText);
if (firstMatch) {
const lineNumber = Number(firstMatch[0]);
const line = xml.split("\n")[lineNumber - 1];
const secondMatch = re.exec(parsererrorText);
if (line && secondMatch) {
const columnIndex = Number(secondMatch[0]) - 1;
if (line[columnIndex]) {
msg +=
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
`${line}\n${"-".repeat(columnIndex - 1)}^`;
}
}
}
}
throw new OwlError(msg);
}
return doc;
} }
function compile(template, options = {}) { function compile(template, options = {}) {
@@ -5562,7 +5538,7 @@ function compile(template, options = {}) {
} }
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
const version = "2.2.7"; const version = "2.2.9";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Scheduler // Scheduler
@@ -5991,6 +5967,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml }; export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
__info__.date = '2023-12-06T13:56:01.636Z'; __info__.date = '2024-01-12T14:43:56.804Z';
__info__.hash = 'e94428a'; __info__.hash = '7b3e39b';
__info__.url = 'https://github.com/odoo/owl'; __info__.url = 'https://github.com/odoo/owl';
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.2.7", "version": "2.2.9",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.2.7", "version": "2.2.9",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"module": "dist/owl.es.js", "module": "dist/owl.es.js",
+4 -5
View File
@@ -3,7 +3,6 @@ import type { BDom } from "../runtime/blockdom";
import { CodeGenerator, Config } from "./code_generator"; import { CodeGenerator, Config } from "./code_generator";
import { parse } from "./parser"; import { parse } from "./parser";
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { parseXML } from "../common/utils";
export type Template = (context: any, vnode: any, key?: string) => BDom; export type Template = (context: any, vnode: any, key?: string) => BDom;
@@ -17,13 +16,13 @@ export function compile(
options: CompileOptions = {} options: CompileOptions = {}
): TemplateFunction { ): TemplateFunction {
// parsing // parsing
if (typeof template === "string") {
template = parseXML(`<t>${template}</t>`).firstChild as Element;
}
const ast = parse(template); const ast = parse(template);
// some work // some work
const hasSafeContext = template.querySelector("[t-set], [t-call]") === null; const hasSafeContext =
template instanceof Node
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
: !template.includes("t-set") && !template.includes("t-call");
// code generation // code generation
const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext }); const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
+6 -1
View File
@@ -1,4 +1,5 @@
import { OwlError } from "../common/owl_error"; import { OwlError } from "../common/owl_error";
import { parseXML } from "../common/utils";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// AST Type definition // AST Type definition
@@ -197,7 +198,11 @@ export type AST =
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const cache: WeakMap<Element, AST> = new WeakMap(); const cache: WeakMap<Element, AST> = new WeakMap();
export function parse(xml: Element): AST { 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); let ast = cache.get(xml);
if (!ast) { if (!ast) {
// we clone here the xml to prevent modifying it in place // we clone here the xml to prevent modifying it in place
+44 -4
View File
@@ -20,8 +20,9 @@ type CollectionRawType = "Set" | "Map" | "WeakMap";
const objectToString = Object.prototype.toString; const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty; const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]); // Use arrays because Array.includes is faster than Set.has for small arrays
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]); const SUPPORTED_RAW_TYPES = ["Object", "Array", "Set", "Map", "WeakMap"];
const COLLECTION_RAW_TYPES = ["Set", "Map", "WeakMap"];
/** /**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore * extract "RawType" from strings like "[object RawType]" => this lets us ignore
@@ -45,7 +46,7 @@ function canBeMadeReactive(value: any): boolean {
if (typeof value !== "object") { if (typeof value !== "object") {
return false; return false;
} }
return SUPPORTED_RAW_TYPES.has(rawType(value)); return SUPPORTED_RAW_TYPES.includes(rawType(value));
} }
/** /**
* Creates a reactive from the given object/callback if possible and returns it, * Creates a reactive from the given object/callback if possible and returns it,
@@ -220,12 +221,36 @@ export function reactive<T extends Target>(target: T, callback: Callback = NO_CA
const reactivesForTarget = reactiveCache.get(target)!; const reactivesForTarget = reactiveCache.get(target)!;
if (!reactivesForTarget.has(callback)) { if (!reactivesForTarget.has(callback)) {
const targetRawType = rawType(target); const targetRawType = rawType(target);
const handler = COLLECTION_RAWTYPES.has(targetRawType) const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType) ? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType)
: basicProxyHandler<T>(callback); : basicProxyHandler<T>(callback);
const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>; const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
reactivesForTarget.set(callback, proxy); reactivesForTarget.set(callback, proxy);
targets.set(proxy, target); targets.set(proxy, target);
// FIXME: this probably slows down reactive creation significantly, we probably don't want to do
// it all the time. Maybe should be a separate function.
const derivedDescriptors = Object.entries(Object.getOwnPropertyDescriptors(target)).filter(
([k, descriptor]) => {
if (toRaw(descriptor.value)?.[IS_DERIVED_DESCRIPTOR]) {
delete target[k as keyof typeof target]; // prevent circular call in effect below
return true;
}
return false;
}
);
for (const [
key,
{
value: [deps, compute],
},
] of derivedDescriptors) {
effect(
(proxy, deps) => {
proxy[key as keyof typeof proxy] = Reflect.apply(compute, proxy, deps);
},
[proxy, deps]
);
}
} }
return reactivesForTarget.get(callback) as Reactive<T>; return reactivesForTarget.get(callback) as Reactive<T>;
} }
@@ -462,3 +487,18 @@ function collectionsProxyHandler<T extends Collection>(
}, },
}) as ProxyHandler<T>; }) as ProxyHandler<T>;
} }
const IS_DERIVED_DESCRIPTOR = Symbol("is derived descriptor");
export function derived<T extends Reactive<any>[], U>(deps: T, compute: (...args: T) => U) {
return Object.assign([deps, compute], { [IS_DERIVED_DESCRIPTOR]: true }) as unknown as U;
}
/**
* Creates a side-effect that runs based on the content of reactive objects.
*/
export function effect<T extends object[]>(cb: (...args: [...T]) => void, deps: [...T]) {
const reactiveDeps = reactive(deps, () => {
cb(...reactiveDeps);
});
cb(...reactiveDeps);
}
+4 -1
View File
@@ -13,6 +13,7 @@ export interface TemplateSetConfig {
translatableAttributes?: string[]; translatableAttributes?: string[];
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
templates?: string | Document | Record<string, string>; templates?: string | Document | Record<string, string>;
getTemplate?: (s: string) => Element | Function | string | void;
} }
export class TemplateSet { export class TemplateSet {
@@ -22,6 +23,7 @@ export class TemplateSet {
dev: boolean; dev: boolean;
rawTemplates: typeof globalTemplates = Object.create(globalTemplates); rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
templates: { [name: string]: Template } = {}; templates: { [name: string]: Template } = {};
getRawTemplate?: (s: string) => Element | Function | string | void;
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
translatableAttributes?: string[]; translatableAttributes?: string[];
Portal = Portal; Portal = Portal;
@@ -39,6 +41,7 @@ export class TemplateSet {
} }
} }
} }
this.getRawTemplate = config.getTemplate;
} }
addTemplate(name: string, template: string | Element) { addTemplate(name: string, template: string | Element) {
@@ -77,7 +80,7 @@ export class TemplateSet {
getTemplate(name: string): Template { getTemplate(name: string): Template {
if (!(name in this.templates)) { if (!(name in this.templates)) {
const rawTemplate = this.rawTemplates[name]; const rawTemplate = this.getRawTemplate?.(name) || this.rawTemplates[name];
if (rawTemplate === undefined) { if (rawTemplate === undefined) {
let extraInfo = ""; let extraInfo = "";
try { try {
+1 -1
View File
@@ -1,2 +1,2 @@
// do not modify manually. This file is generated by the release script. // do not modify manually. This file is generated by the release script.
export const version = "2.2.7"; export const version = "2.2.9";
@@ -101,3 +101,55 @@ exports[`loading templates can load a few templates from an XMLDocument 2`] = `
} }
}" }"
`; `;
exports[`loading templates getTemplate: element returned (2) 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates getTemplate: element returned 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates getTemplate: template string returned 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates getTemplate: undefined returned 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>Hello World!</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
+1 -4
View File
@@ -1,7 +1,4 @@
import { parseXML } from "../../src/common/utils"; import { ASTType, parse } from "../../src/compiler/parser";
import { ASTType, parse as _parse } from "../../src/compiler/parser";
const parse = (template: string) => _parse(parseXML(`<t>${template}</t>`).firstChild as Element);
describe("qweb parser", () => { describe("qweb parser", () => {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
+58
View File
@@ -78,4 +78,62 @@ describe("loading templates", () => {
context.addTemplates(xml); context.addTemplates(xml);
expect(Object.keys(context.rawTemplates)).toEqual([]); expect(Object.keys(context.rawTemplates)).toEqual([]);
}); });
test("getTemplate: element returned", () => {
const context = new TestContext({
getTemplate: (name) => {
if (name === "main") {
const data = `<div>Hello World!</div>`;
const xml = new DOMParser().parseFromString(data, "text/xml");
return xml.firstChild as Element;
}
return;
},
});
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
test("getTemplate: element returned (2)", () => {
const context = new TestContext({
getTemplate: (name) => {
if (name === "main") {
const doc = new Document();
const div = doc.createElement("div");
div.append(doc.createTextNode("Hello World!"));
return div;
}
return;
},
});
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
test("getTemplate: template string returned", () => {
const context = new TestContext({
getTemplate: (name) => {
if (name === "main") {
return `<div>Hello World!</div>`;
}
return;
},
});
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
test("getTemplate: undefined returned", () => {
const context = new TestContext({
getTemplate: () => {},
});
const data = `<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<div t-name="main">Hello World!</div>
</templates>`;
const xml = new DOMParser().parseFromString(data, "text/xml");
context.addTemplates(xml);
const result = context.renderToString("main");
expect(result).toBe("<div>Hello World!</div>");
});
}); });
@@ -99,7 +99,7 @@ exports[`refs refs are properly bound in slots 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
@@ -113,7 +113,8 @@ exports[`refs refs are properly bound in slots 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['state'].val; let txt1 = ctx['state'].val;
const b3 = comp1({slots: markRaw({'footer': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b3 = comp1({slots: markRaw({'footer': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([txt1], [b3]); return block1([txt1], [b3]);
} }
}" }"
@@ -38,7 +38,7 @@ exports[`slots can define and call slots 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -54,7 +54,8 @@ exports[`slots can define and call slots 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b4 = comp1({slots: markRaw({'header': {__render: slot1.bind(this), __ctx: ctx}, 'footer': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b4 = comp1({slots: markRaw({'header': {__render: slot1.bind(this), __ctx: ctx1}, 'footer': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b4]); return block1([], [b4]);
} }
}" }"
@@ -80,7 +81,7 @@ exports[`slots can define and call slots with bound params 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
@@ -88,7 +89,8 @@ exports[`slots can define and call slots with bound params 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'abc': {__render: slot1.bind(this), __ctx: ctx, getValue: (ctx['getValue']).bind(this)}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'abc': {__render: slot1.bind(this), __ctx: ctx1, getValue: (ctx['getValue']).bind(this)}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -111,7 +113,7 @@ exports[`slots can define and call slots with params 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -127,7 +129,8 @@ exports[`slots can define and call slots with params 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b4 = comp1({slots: markRaw({'header': {__render: slot1.bind(this), __ctx: ctx, param: ctx['var']}, 'footer': {__render: slot2.bind(this), __ctx: ctx, param: '5'}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b4 = comp1({slots: markRaw({'header': {__render: slot1.bind(this), __ctx: ctx1, param: ctx['var']}, 'footer': {__render: slot2.bind(this), __ctx: ctx1, param: '5'}})}, key + \`__1\`, node, this, null);
return block1([], [b4]); return block1([], [b4]);
} }
}" }"
@@ -349,7 +352,7 @@ exports[`slots default content is not rendered if named slot is provided 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -359,7 +362,8 @@ exports[`slots default content is not rendered if named slot is provided 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b3 = comp1({slots: markRaw({'header': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b3 = comp1({slots: markRaw({'header': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -427,7 +431,7 @@ exports[`slots default slot next to named slot, with default content 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -437,7 +441,8 @@ exports[`slots default slot next to named slot, with default content 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b3 = comp1({slots: markRaw({'footer': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b3 = comp1({slots: markRaw({'footer': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -602,7 +607,7 @@ exports[`slots dynamic slot in multiple locations 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, []); const comp1 = app.createComponent(\`Child\`, true, false, false, []);
const comp2 = app.createComponent(\`Slotter\`, true, true, false, [\\"location\\"]); const comp2 = app.createComponent(\`Slotter\`, true, true, false, [\\"location\\"]);
@@ -613,7 +618,8 @@ exports[`slots dynamic slot in multiple locations 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp2({location: ctx['state'].location,slots: markRaw({'coffee': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__2\`, node, this, null); const ctx1 = capture(ctx);
return comp2({location: ctx['state'].location,slots: markRaw({'coffee': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
} }
}" }"
`; `;
@@ -659,7 +665,7 @@ exports[`slots dynamic t-slot call 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Toggler\`, true, true, false, []); const comp1 = app.createComponent(\`Toggler\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -678,7 +684,8 @@ exports[`slots dynamic t-slot call 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b6 = comp1({slots: markRaw({'slot1': {__render: slot1.bind(this), __ctx: ctx}, 'slot2': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b6 = comp1({slots: markRaw({'slot1': {__render: slot1.bind(this), __ctx: ctx1}, 'slot2': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b6]); return block1([], [b6]);
} }
}" }"
@@ -705,7 +712,7 @@ exports[`slots dynamic t-slot call with default 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Toggler\`, true, true, false, []); const comp1 = app.createComponent(\`Toggler\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -724,7 +731,8 @@ exports[`slots dynamic t-slot call with default 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b6 = comp1({slots: markRaw({'slot1': {__render: slot1.bind(this), __ctx: ctx}, 'slot2': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b6 = comp1({slots: markRaw({'slot1': {__render: slot1.bind(this), __ctx: ctx1}, 'slot2': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b6]); return block1([], [b6]);
} }
}" }"
@@ -968,7 +976,7 @@ exports[`slots multiple roots are allowed in a named slot 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -982,7 +990,8 @@ exports[`slots multiple roots are allowed in a named slot 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b5 = comp1({slots: markRaw({'content': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b5 = comp1({slots: markRaw({'content': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -1007,7 +1016,7 @@ exports[`slots multiple slots containing components 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`C\`, true, false, false, [\\"val\\"]); const comp1 = app.createComponent(\`C\`, true, false, false, [\\"val\\"]);
const comp2 = app.createComponent(\`C\`, true, false, false, [\\"val\\"]); const comp2 = app.createComponent(\`C\`, true, false, false, [\\"val\\"]);
const comp3 = app.createComponent(\`B\`, true, true, false, []); const comp3 = app.createComponent(\`B\`, true, true, false, []);
@@ -1021,7 +1030,8 @@ exports[`slots multiple slots containing components 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp3({slots: markRaw({'s1': {__render: slot1.bind(this), __ctx: ctx}, 's2': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__3\`, node, this, null); const ctx1 = capture(ctx);
return comp3({slots: markRaw({'s1': {__render: slot1.bind(this), __ctx: ctx1}, 's2': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__3\`, node, this, null);
} }
}" }"
`; `;
@@ -1060,14 +1070,15 @@ exports[`slots named slot inside named slot in t-component 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(null, false, true, false, []); const comp1 = app.createComponent(null, false, true, false, []);
const comp2 = app.createComponent(\`Child\`, true, true, false, []); const comp2 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
const b2 = text(\` outer \`); const b2 = text(\` outer \`);
const ctx2 = capture(ctx);
const Comp1 = ctx['Child']; const Comp1 = ctx['Child'];
const b4 = toggler(Comp1, comp1({slots: markRaw({'brol': {__render: slot2.bind(this), __ctx: ctx}})}, (Comp1).name + key + \`__1\`, node, this, Comp1)); const b4 = toggler(Comp1, comp1({slots: markRaw({'brol': {__render: slot2.bind(this), __ctx: ctx2}})}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return multi([b2, b4]); return multi([b2, b4]);
} }
@@ -1076,7 +1087,8 @@ exports[`slots named slot inside named slot in t-component 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__2\`, node, this, null); const ctx1 = capture(ctx);
return comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
} }
}" }"
`; `;
@@ -1097,7 +1109,7 @@ exports[`slots named slot inside slot 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
const comp2 = app.createComponent(\`Child\`, true, true, false, []); const comp2 = app.createComponent(\`Child\`, true, true, false, []);
@@ -1111,7 +1123,8 @@ exports[`slots named slot inside slot 1`] = `
} }
function slot2(ctx, node, key = \\"\\") { function slot2(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'brol': {__render: slot3.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx2 = capture(ctx);
return comp1({slots: markRaw({'brol': {__render: slot3.bind(this), __ctx: ctx2}})}, key + \`__1\`, node, this, null);
} }
function slot3(ctx, node, key = \\"\\") { function slot3(ctx, node, key = \\"\\") {
@@ -1120,7 +1133,8 @@ exports[`slots named slot inside slot 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b5 = comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx}, 'default': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__2\`, node, this, null); const ctx1 = capture(ctx);
const b5 = comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx1}, 'default': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -1146,7 +1160,7 @@ exports[`slots named slot inside slot, part 3 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
const comp2 = app.createComponent(\`Child\`, true, true, false, []); const comp2 = app.createComponent(\`Child\`, true, true, false, []);
@@ -1160,7 +1174,8 @@ exports[`slots named slot inside slot, part 3 1`] = `
} }
function slot2(ctx, node, key = \\"\\") { function slot2(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'brol': {__render: slot3.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx2 = capture(ctx);
return comp1({slots: markRaw({'brol': {__render: slot3.bind(this), __ctx: ctx2}})}, key + \`__1\`, node, this, null);
} }
function slot3(ctx, node, key = \\"\\") { function slot3(ctx, node, key = \\"\\") {
@@ -1169,7 +1184,8 @@ exports[`slots named slot inside slot, part 3 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b5 = comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx}, 'default': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__2\`, node, this, null); const ctx1 = capture(ctx);
const b5 = comp2({slots: markRaw({'brol': {__render: slot1.bind(this), __ctx: ctx1}, 'default': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -1229,7 +1245,7 @@ exports[`slots named slots inside slot, again 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
const comp2 = app.createComponent(\`Child\`, true, true, false, []); const comp2 = app.createComponent(\`Child\`, true, true, false, []);
@@ -1243,7 +1259,8 @@ exports[`slots named slots inside slot, again 1`] = `
} }
function slot2(ctx, node, key = \\"\\") { function slot2(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'brol2': {__render: slot3.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx2 = capture(ctx);
return comp1({slots: markRaw({'brol2': {__render: slot3.bind(this), __ctx: ctx2}})}, key + \`__1\`, node, this, null);
} }
function slot3(ctx, node, key = \\"\\") { function slot3(ctx, node, key = \\"\\") {
@@ -1252,7 +1269,8 @@ exports[`slots named slots inside slot, again 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b5 = comp2({slots: markRaw({'brol1': {__render: slot1.bind(this), __ctx: ctx}, 'default': {__render: slot2.bind(this), __ctx: ctx}})}, key + \`__2\`, node, this, null); const ctx1 = capture(ctx);
const b5 = comp2({slots: markRaw({'brol1': {__render: slot1.bind(this), __ctx: ctx1}, 'default': {__render: slot2.bind(this), __ctx: ctx1}})}, key + \`__2\`, node, this, null);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -1575,7 +1593,7 @@ exports[`slots simple dynamic slot with slot scope 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
@@ -1589,7 +1607,8 @@ exports[`slots simple dynamic slot with slot scope 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'slotName': {__render: slot1.bind(this), __ctx: ctx, __scope: \\"slotScope\\"}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'slotName': {__render: slot1.bind(this), __ctx: ctx1, __scope: \\"slotScope\\"}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -1614,10 +1633,11 @@ exports[`slots simple named and empty slot -- 2 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return comp1({slots: markRaw({'myEmptySlot': {myProp: 'myProp text'}})}, key + \`__1\`, node, this, null); return comp1({slots: markRaw({'myEmptySlot': {myProp: 'myProp text'}})}, key + \`__1\`, node, this, null);
} }
}" }"
@@ -1646,7 +1666,7 @@ exports[`slots simple named and empty slot 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
@@ -1654,7 +1674,8 @@ exports[`slots simple named and empty slot 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'myEmptySlot': {}, 'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'myEmptySlot': {}, 'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -1679,7 +1700,7 @@ exports[`slots simple slot with slot scope 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
@@ -1693,7 +1714,8 @@ exports[`slots simple slot with slot scope 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'slotName': {__render: slot1.bind(this), __ctx: ctx, __scope: \\"slotScope\\"}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'slotName': {__render: slot1.bind(this), __ctx: ctx1, __scope: \\"slotScope\\"}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -2287,7 +2309,7 @@ exports[`slots slot with slot scope and t-props 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
let block2 = createBlock(\`<p><block-text-0/></p>\`); let block2 = createBlock(\`<p><block-text-0/></p>\`);
@@ -2302,7 +2324,8 @@ exports[`slots slot with slot scope and t-props 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'slotName': {__render: slot1.bind(this), __ctx: ctx, __scope: \\"info\\"}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'slotName': {__render: slot1.bind(this), __ctx: ctx1, __scope: \\"info\\"}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -2391,7 +2414,7 @@ exports[`slots slots are rendered with proper context 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
@@ -2404,7 +2427,8 @@ exports[`slots slots are rendered with proper context 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['state'].val; let txt1 = ctx['state'].val;
const b3 = comp1({slots: markRaw({'footer': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b3 = comp1({slots: markRaw({'footer': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([txt1], [b3]); return block1([txt1], [b3]);
} }
}" }"
@@ -2779,7 +2803,7 @@ exports[`slots t-debug on a t-set-slot (defining a slot) 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, []); const comp1 = app.createComponent(\`Dialog\`, true, true, false, []);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -2790,7 +2814,8 @@ exports[`slots t-debug on a t-set-slot (defining a slot) 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b3 = comp1({slots: markRaw({'content': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b3 = comp1({slots: markRaw({'content': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -2850,40 +2875,11 @@ exports[`slots t-set t-value in a slot 2`] = `
}" }"
`; `;
exports[`slots t-set-slot doesn't cause context to be captured 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") {
return text(ctx['someVal']);
}
return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null);
}
}"
`;
exports[`slots t-set-slot doesn't cause context to be captured 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {});
}
}"
`;
exports[`slots t-set-slot=default has priority over rest of the content 1`] = ` exports[`slots t-set-slot=default has priority over rest of the content 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
@@ -2891,7 +2887,8 @@ exports[`slots t-set-slot=default has priority over rest of the content 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -153,7 +153,7 @@ exports[`list of components order is correct when slots are not of same type 1`]
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers; let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
let block2 = createBlock(\`<div>A</div>\`); let block2 = createBlock(\`<div>A</div>\`);
@@ -175,7 +175,8 @@ exports[`list of components order is correct when slots are not of same type 1`]
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'a': {__render: slot1.bind(this), __ctx: ctx, active: !ctx['state'].active}, 'b': {__render: slot2.bind(this), __ctx: ctx, active: true}, 'c': {__render: slot3.bind(this), __ctx: ctx, active: ctx['state'].active}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
return comp1({slots: markRaw({'a': {__render: slot1.bind(this), __ctx: ctx1, active: !ctx['state'].active}, 'b': {__render: slot2.bind(this), __ctx: ctx1, active: true}, 'c': {__render: slot3.bind(this), __ctx: ctx1, active: ctx['state'].active}})}, key + \`__1\`, node, this, null);
} }
}" }"
`; `;
@@ -386,7 +386,7 @@ exports[`t-on t-on on t-set-slots 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { createCatcher, markRaw } = helpers; let { capture, createCatcher, markRaw } = helpers;
const catcher1 = createCatcher({\\"click\\":0}); const catcher1 = createCatcher({\\"click\\":0});
const comp1 = app.createComponent(\`Child\`, true, true, false, []); const comp1 = app.createComponent(\`Child\`, true, true, false, []);
@@ -405,7 +405,8 @@ exports[`t-on t-on on t-set-slots 1`] = `
const b2 = text(\` [\`); const b2 = text(\` [\`);
const b3 = text(ctx['state'].count); const b3 = text(ctx['state'].count);
const b4 = text(\`] \`); const b4 = text(\`] \`);
const b8 = comp1({slots: markRaw({'myslot': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null); const ctx1 = capture(ctx);
const b8 = comp1({slots: markRaw({'myslot': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return multi([b2, b3, b4, b8]); return multi([b2, b3, b4, b8]);
} }
}" }"
+1 -23
View File
@@ -1,4 +1,4 @@
import { App, Component, mount, onMounted, onRendered, useState, xml } from "../../src/index"; import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
import { children, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers"; import { children, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
snapshotEverything(); snapshotEverything();
@@ -62,28 +62,6 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some other text"); expect(fixture.innerHTML).toBe("some other text");
}); });
test("t-set-slot doesn't cause context to be captured", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/>`;
}
class Parent extends Component {
static template = xml`<Child>
<t t-set-slot="default"><t t-esc="someVal"/></t>
</Child>`;
static components = { Child };
someVal = "some text";
setup() {
onRendered(() => {
this.someVal = "some other text";
});
}
}
await mount(Parent, fixture);
expect(fixture.textContent).toBe("some other text");
});
test("simple slot with slot scope", async () => { test("simple slot with slot scope", async () => {
let child: any; let child: any;
class Child extends Component { class Child extends Component {
+154 -1
View File
@@ -9,7 +9,7 @@ import {
markRaw, markRaw,
toRaw, toRaw,
} from "../src"; } from "../src";
import { reactive, getSubscriptions } from "../src/runtime/reactivity"; import { reactive, getSubscriptions, derived } from "../src/runtime/reactivity";
import { batched } from "../src/runtime/utils"; import { batched } from "../src/runtime/utils";
import { import {
makeDeferred, makeDeferred,
@@ -2424,3 +2424,156 @@ describe("Reactivity: useState", () => {
expect(fixture.innerHTML).toBe("<div><p><span>2b</span></p></div>"); expect(fixture.innerHTML).toBe("<div><p><span>2b</span></p></div>");
}); });
}); });
describe("derived", () => {
test("can read", async () => {
const state = reactive({ a: derived([], () => 1) });
expect(state.a).toBe(1);
});
test("can create new keys", () => {
const state: any = reactive({ b: derived([], () => 2) });
state.a = 1;
expect(state.a).toBe(1);
});
test("can update", () => {
const o = reactive({ a: 1 });
let computeCall = 0;
const state = reactive({
a: derived([o], (o) => {
computeCall++;
return o.a;
}),
});
expect(computeCall).toBe(1);
expect(state.a).toBe(1);
o.a = 2;
expect(computeCall).toBe(2);
expect(state.a).toBe(2);
});
test("callback is called when changing an observed property", async () => {
let notifyCount = 0;
const o = reactive({ a: 1 });
let computeCall = 0;
const state = reactive(
{
a: derived([o], (o) => {
computeCall++;
return o.a;
}),
},
() => notifyCount++
);
expect(computeCall).toBe(1);
expect(notifyCount).toBe(0);
expect(state.a).toBe(1);
o.a = 2;
expect(computeCall).toBe(2);
expect(notifyCount).toBe(1);
expect(state.a).toBe(2);
o.a = 5;
expect(computeCall).toBe(3);
expect(notifyCount).toBe(2);
expect(state.a).toBe(5);
});
test("multiple dependencies", async () => {
let notifyCount = 0;
const a = reactive({ val: 1 });
const b = reactive({ val: 2 });
let computeCall = 0;
const state = reactive(
{
c: derived([a, b], (a, b) => {
computeCall++;
return a.val + b.val;
}),
},
() => notifyCount++
);
expect(computeCall).toBe(1);
a.val = 2;
expect(computeCall).toBe(2);
expect(notifyCount).toBe(0);
expect(state.c).toBe(4);
a.val = 4;
expect(computeCall).toBe(3);
expect(notifyCount).toBe(1);
expect(state.c).toBe(6);
b.val = 3;
expect(computeCall).toBe(4);
expect(notifyCount).toBe(2);
expect(state.c).toBe(7);
});
test("dependency on own fields", async () => {
let notifyCount = 0;
const a = reactive({ val: 1 });
let computeCall = 0;
const state = reactive(
{
b: 2,
c: derived([a], function (this: any, a) {
computeCall++;
return a.val + this.b;
}),
},
() => notifyCount++
);
expect(computeCall).toBe(1);
a.val = 2;
expect(computeCall).toBe(2);
expect(notifyCount).toBe(0);
expect(state.c).toBe(4);
a.val = 4;
expect(computeCall).toBe(3);
expect(notifyCount).toBe(1);
expect(state.c).toBe(6);
state.b = 3;
expect(computeCall).toBe(4);
expect(notifyCount).toBe(2);
expect(state.c).toBe(7);
});
test("dependency on derived property", () => {
let computeB = 0;
let computeC = 0;
const state = reactive({
a: 1,
b: derived([], function (this: any) {
computeB++;
return this.a + 1;
}),
c: derived([], function (this: any) {
computeC++;
return this.b + 1;
}),
});
expect(computeB).toBe(1);
expect(computeC).toBe(1);
expect(state.c).toBe(3);
});
test("dependency on derived property appearing later in object", () => {
let computeB = 0;
let computeC = 0;
const state = reactive({
a: 1,
c: derived([], function (this: any) {
computeC++;
return this.b + 1;
}),
b: derived([], function (this: any) {
computeB++;
return this.a + 1;
}),
});
expect(computeB).toBe(1);
// because computation is eager and naive, C is first computed to be undefined, then B is computed
// to be 2, and the computation of B causes C to recompute and become 3. This causes C to compute twice.
expect(computeC).toBe(2);
expect(state.c).toBe(3);
});
});
+29 -4
View File
@@ -1,18 +1,43 @@
{ {
"Basic owl component": { "Basic OWL Component": {
"prefix": "owlcomponent", "prefix": "owlcomponent",
"scope": "javascript,typescript",
"body": [ "body": [
"export class ${1:component-name} extends Component {", "import { Component } from \"@odoo/owl\";",
" static template = \"${2:template-name}\";", "",
"class ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}} extends ${2:Component} {",
"",
" static template = \"${3:${RELATIVE_FILEPATH/(.*[\\|\\/])??([a-zA-Z_]+)([\\|\\/]static[\\|\\/].*)/${2}/g}}.${4:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}}\";",
" static components = {};", " static components = {};",
" static props = {};", " static props = {};",
"", "",
" setup() {", " setup() {",
"", " ${5:super.setup();}",
" }", " }",
"",
" ${6:// Do Something}",
"}", "}",
"" ""
], ],
"description": "The starting base for an owl component" "description": "The starting base for an owl component"
},
"Basic OWL Template": {
"prefix": "owltemplate",
"scope": "xml",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>",
"",
"<templates xml:space=\"preserve\">",
"",
" <t t-name=\"${2:${RELATIVE_FILEPATH/(.*[\\|\\/])??([a-zA-Z_]+)([\\|\\/]static[\\|\\/].*)/${2}/g}}.${3:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}}\">",
" ${3:<h1>Hello World</h1>}",
" </t>",
"",
"</templates>",
""
],
"description": "Generate a basic OWL template XML file"
} }
} }