Compare commits

..

18 Commits

Author SHA1 Message Date
Lucas Perais (lpe) 21b07afb60 [WIP] transitions in user space: playground 2021-11-25 18:04:54 +01:00
Géry Debongnie 13c3178760 [FIX] slots: properly bind this in t-on arrow functions 2021-11-25 14:45:30 +01:00
Géry Debongnie 2b565ce1b4 [REF] component: small cleanup
This commit makes it simpler to understand the way fibers are assigned
to nodes.
2021-11-25 11:34:23 +01:00
Géry Debongnie cfcf2c6714 [FIX] component: concurrency issue
When a parent and a child were rendered at the same time, it was
possible for the 2 renders to decrement the same fiber internal
counter, which meant that the render was stalled.
2021-11-25 11:12:31 +01:00
Samuel Degueldre 92174559a1 [FIX] slots: allow t-call and components in slot default content 2021-11-24 15:47:51 +01:00
Bruno Boi 840348892f [IMP] owl: upgrade rollup-plugin-typescript2 to version 0.31.1 2021-11-24 11:45:12 +01:00
Samuel Degueldre b988a68b6f [IMP] parser: normalize document before parsing 2021-11-24 11:19:45 +01:00
Samuel Degueldre 7869a1f2c6 [IMP] components: add test for template string in props 2021-11-24 10:09:52 +01:00
Samuel Degueldre 9e81d14d50 [IMP] parser: throw when using unsupported directive on component 2021-11-24 10:09:52 +01:00
Samuel Degueldre 0e4a55ba09 [FIX] components: allow prop names that are not valid bare property name 2021-11-24 10:09:52 +01:00
Mathieu Duckerts-Antoine 6e9b68dafa [FIX] props: prop names can contain - 2021-11-23 10:20:45 +01:00
Bruno Boi ba7c9063c0 Update CHANGELOG.md 2021-11-23 10:09:39 +01:00
Mathieu Duckerts-Antoine 3a361876ad [IMP] slots: via prop 'slots'
The slot inner working has been reworked. A prop "slots" is now passed
explicitely to the component. It looks like

{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }

with the objects slotInfo_i with mandatory keys "__render", "__ctx",
and optional key "__scope" and possibly others.

Here is how a slotInfo object can be created:
A slotInfo object is normally created by setting in a template something
like

<div>
    <t t-set-slot="foo" t-set-scope="scope" param_1="var" param_2="3">
        content
        <t t-esc="scope.bool"/>
        <t t-esc="scope.num"/>
    </t>
</div>

and it will be used somewhere like

<div>
    <t t-esc="props.slots.foo.param_1"/>
    <t t-slot="foo" bool="other_var" num="5">
</div>

In the above example, the function "__render" produces the block dom
element for the content of the t-set-slot.
The context "__ctx" will have a key "scope" with value { bool: ..., num: 5 }
and "__scope" will be set to "scope".
2021-11-22 16:36:43 +01:00
Samuel Degueldre 58b8572f0a [FIX] components: capture context in prop expressions 2021-11-22 13:18:17 +01:00
Samuel Degueldre c23637e6d8 [FIX] components: throw on duplicate t-key instead of hanging the app 2021-11-22 11:11:49 +01:00
Géry Debongnie 4a4b1fbba5 [IMP] app: add templates in app config
Also, improve the parsing code
2021-11-22 10:56:16 +01:00
Géry Debongnie 536d9e1762 [REM] tools: remove benchmarks/debug script
They are either no longer relevant, or less useful than some
alternatives (such as the js framework benchmark project)
2021-11-22 10:56:16 +01:00
Lucas Perais (lpe) 6ed68372a6 [FIX] package: bump owl version to 2.0.0-alpha1 2021-11-22 10:49:51 +01:00
121 changed files with 2110 additions and 67217 deletions
+4 -2
View File
@@ -447,8 +447,10 @@ bus.addEventListener('event-name', callback);
Rationale: it makes it easier to have just one interface to remember, it makes Rationale: it makes it easier to have just one interface to remember, it makes
the code simpler the code simpler
Migration: most bus methods need to be adapted. So, `bus.on(...)` has to be Migration: most bus methods need to be adapted. So, `bus.on("event-type", owner, (info) => {...})` has to be
rewritten like this: `bus.addEventListener(...)`. rewritten like this: `bus.addEventListener("event-type", (({detail: info}) => {...}).bind(owner))`.
Do not forget to similarly replace `bus.off(...)` by `bus.removeEventListener(...)`
### 22. `Store` is removed ### 22. `Store` is removed
-43
View File
@@ -1,43 +0,0 @@
# 🦉 How to debug Owl applications 🦉
Non trivial applications become quickly more difficult to understand. It is then
useful to have a solid understanding of what is going on. To help with that,
logging useful information is extremely valuable. There is a [javascript file](../../tools/debug.js) which can be evaluated in an application.
Once it is executed, it will log a lot of information on each component main hooks. The following code is a minified version to make it easier to copy/paste:
```
function debugOwl(t,e){let n,o="[OWL_DEBUG]";function r(t){let e;try{e=JSON.stringify(t||{})}catch(t){e="<JSON error>"}return e.length>200&&(e=e.slice(0,200)+"..."),e}if(Object.defineProperty(t.Component,"current",{get:()=>n,set(s){n=s;const i=s.constructor.name;if(e.componentBlackList&&e.componentBlackList.test(i))return;if(e.componentWhiteList&&!e.componentWhiteList.test(i))return;let l;Object.defineProperty(n,"__owl__",{get:()=>l,set(n){!function(n,s,i){let l=`${s}<id=${i}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!e.methodBlackList||!e.methodBlackList.includes(t))&&!(e.methodWhiteList&&!e.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${r(n.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${r(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=n.__render.bind(n);n.__render=function(...t){c("rendering template"),d(...t)};const h=n.render.bind(n);n.render=function(...t){const e=n.__owl__;let o="render";return e.isMounted||e.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=n.mount.bind(n);n.mount=function(...t){return c("mount"),p(...t)}}(s,i,(l=n).id)}})}}),e.logScheduler){let e=t.Component.scheduler.start,n=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),e.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),n.call(this)}}if(e.logStore){let e=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...n){return console.log(`${o} store: action '${t}' dispatched. Payload: '${r(n)}'`),e.call(this,t,...n)}}}
debugOwl(owl, {
// componentBlackList: /App/, // regexp
// componentWhiteList: /SomeComponent/, // regexp
// methodBlackList: ["mounted"], // list of method names
// methodWhiteList: ["willStart"], // list of method names
logScheduler: false, // display/mute scheduler logs
logStore: true, // display/mute store logs
});
```
The above code, once pasted somewhere in the main javascript file of an owl
application, will log information looking like this:
```
[OWL_DEBUG] TodoApp<id=1> constructor, props={}
[OWL_DEBUG] TodoApp<id=1> mount
[OWL_DEBUG] TodoApp<id=1> willStart
[OWL_DEBUG] TodoApp<id=1> rendering template
[OWL_DEBUG] TodoItem<id=2> constructor, props={"id":2,"completed":false,"title":"hey"}
[OWL_DEBUG] TodoItem<id=2> willStart
[OWL_DEBUG] TodoItem<id=3> constructor, props={"id":4,"completed":false,"title":"aaa"}
[OWL_DEBUG] TodoItem<id=3> willStart
[OWL_DEBUG] TodoItem<id=2> rendering template
[OWL_DEBUG] TodoItem<id=3> rendering template
[OWL_DEBUG] TodoItem<id=3> mounted
[OWL_DEBUG] TodoItem<id=2> mounted
[OWL_DEBUG] TodoApp<id=1> mounted
```
Each component has an internal `id`, which is very useful when debugging.
Note that it is certainly useful to run this code at some point in an application,
just to get a feel of what each user action implies, for the framework.
-1
View File
@@ -9,7 +9,6 @@ Are you new to Owl? This is the place to start!
- [How to start an Owl project](learning/quick_start.md) - [How to start an Owl project](learning/quick_start.md)
- [How to test Components](learning/how_to_test.md) - [How to test Components](learning/how_to_test.md)
- [How to write Single File Components](learning/how_to_write_sfc.md) - [How to write Single File Components](learning/how_to_write_sfc.md)
- [How to write debug Owl applications](learning/how_to_debug.md)
## Reference ## Reference
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "1.4.7", "version": "2.0.0-alpha1",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js", "browser": "dist/owl.iife.js",
@@ -18,10 +18,10 @@
"test": "jest", "test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"tools:serve": "python3 tools/server.py || python tools/server.py", "playground:serve": "python3 tools/server.py || python tools/server.py",
"tools": "npm run build && npm run tools:serve", "playground": "npm run build && npm run playground:serve",
"pretools:watch": "npm run build", "preplayground:watch": "npm run build",
"tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\"", "playground:watch": "npm-run-all --parallel playground:serve \"build:* -- --watch\"",
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write", "prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write",
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --check", "check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --check",
"publish": "npm run build && npm publish", "publish": "npm run build && npm publish",
@@ -52,7 +52,7 @@
"prettier": "2.4.1", "prettier": "2.4.1",
"rollup": "^2.56.3", "rollup": "^2.56.3",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-typescript2": "^0.31.1",
"sass": "^1.16.1", "sass": "^1.16.1",
"source-map-support": "^0.5.10", "source-map-support": "^0.5.10",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
+4
View File
@@ -15,6 +15,7 @@ export interface AppConfig {
env?: Env; env?: Env;
translatableAttributes?: string[]; translatableAttributes?: string[];
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
templates?: string | Document;
} }
export const DEV_MSG = `Owl is running in 'dev' mode. export const DEV_MSG = `Owl is running in 'dev' mode.
@@ -49,6 +50,9 @@ export class App<T extends typeof Component = any> extends TemplateSet {
if (config.translatableAttributes) { if (config.translatableAttributes) {
this.translatableAttributes = config.translatableAttributes; this.translatableAttributes = config.translatableAttributes;
} }
if (config.templates) {
this.addTemplates(config.templates);
}
return this; return this;
} }
+12 -7
View File
@@ -17,19 +17,24 @@ function callSlot(
parent: any, parent: any,
key: string, key: string,
name: string, name: string,
defaultSlot?: (ctx: any, key: string) => BDom, dynamic: boolean,
dynamic?: boolean extra: any,
defaultContent?: (ctx: any, node: any, key: string) => BDom
): BDom { ): BDom {
const slots = ctx.__owl__.slots; const slots = (ctx.props && ctx.props.slots) || {};
const slotFn = slots[name]; const { __render, __ctx, __scope } = slots[name] || {};
const slotBDom = slotFn ? slotFn(parent, key) : null; const slotScope = Object.create(__ctx || {});
if (defaultSlot) { if (__scope) {
slotScope[__scope] = extra || {};
}
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
if (defaultContent) {
let child1: BDom | undefined = undefined; let child1: BDom | undefined = undefined;
let child2: BDom | undefined = undefined; let child2: BDom | undefined = undefined;
if (slotBDom) { if (slotBDom) {
child1 = dynamic ? toggler(name, slotBDom) : slotBDom; child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
} else { } else {
child2 = defaultSlot(parent, key); child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
} }
return multi([child1, child2]); return multi([child1, child2]);
} }
+35 -1
View File
@@ -7,6 +7,36 @@ const bdom = { text, createBlock, list, multi, html, toggler, component };
export const globalTemplates: { [key: string]: string | Node } = {}; export const globalTemplates: { [key: string]: string | Node } = {};
function parseXML(xml: string): Document {
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 Error(msg);
}
return doc;
}
export class TemplateSet { export class TemplateSet {
rawTemplates: typeof globalTemplates = Object.create(globalTemplates); rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
templates: { [name: string]: Template } = {}; templates: { [name: string]: Template } = {};
@@ -33,7 +63,11 @@ export class TemplateSet {
} }
addTemplates(xml: string | Document, options: { allowDuplicate?: boolean } = {}) { addTemplates(xml: string | Document, options: { allowDuplicate?: boolean } = {}) {
xml = xml instanceof Document ? xml : new DOMParser().parseFromString(xml, "text/xml"); if (!xml) {
// empty string
return;
}
xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) { for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name")!; const name = template.getAttribute("t-name")!;
template.removeAttribute("t-name"); template.removeAttribute("t-name");
+109 -54
View File
@@ -137,7 +137,6 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
class CodeTarget { class CodeTarget {
name: string; name: string;
signature: string = "";
indentLevel = 0; indentLevel = 0;
loopLevel = 0; loopLevel = 0;
code: string[] = []; code: string[] = [];
@@ -334,7 +333,7 @@ export class CodeGenerator {
generateFunctions(fn: CodeTarget) { generateFunctions(fn: CodeTarget) {
this.addLine(""); this.addLine("");
this.addLine(`const ${fn.name} = ${fn.signature}`); this.addLine(`function ${fn.name}(ctx, node, key) {`);
if (fn.hasCache) { if (fn.hasCache) {
this.addLine(`let cache = ctx.cache || {};`); this.addLine(`let cache = ctx.cache || {};`);
this.addLine(`let nextCache = ctx.cache = {};`); this.addLine(`let nextCache = ctx.cache = {};`);
@@ -344,8 +343,23 @@ export class CodeGenerator {
} }
this.addLine(`}`); this.addLine(`}`);
} }
/**
captureExpression(expr: string): string { * Captures variables that are used inside of an expression. This is useful
* because in compiled code, almost all variables are accessed through the ctx
* object. In the case of functions, that lookup in the context can be delayed
* which can cause issues if the value has changed since the function was
* defined.
*
* @param expr the expression to capture
* @param forceCapture whether the expression should capture its scope even if
* it doesn't contain a function. Useful when the expression will be used as
* a function body.
* @returns a new expression that uses the captured values
*/
captureExpression(expr: string, forceCapture: boolean = false): string {
if (!forceCapture && !expr.includes("=>")) {
return compileExpr(expr);
}
const tokens = compileExprToArray(expr); const tokens = compileExprToArray(expr);
const mapping = new Map<string, string>(); const mapping = new Map<string, string>();
return tokens return tokens
@@ -535,7 +549,7 @@ export class CodeGenerator {
if (isDynamic) { if (isDynamic) {
const str = ast.ref.replace( const str = ast.ref.replace(
INTERP_REGEXP, INTERP_REGEXP,
(expr) => "${" + this.captureExpression(expr.slice(2, -2)) + "}" (expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}"
); );
const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`); const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`);
attrs["block-ref"] = String(idx); attrs["block-ref"] = String(idx);
@@ -734,6 +748,10 @@ export class CodeGenerator {
this.addLine( this.addLine(
`const [${keys}, ${vals}, ${l}, ${c}] = prepareList(${compileExpr(ast.collection)});` `const [${keys}, ${vals}, ${l}, ${c}] = prepareList(${compileExpr(ast.collection)});`
); );
// Throw errors on duplicate keys in dev mode
if (this.dev) {
this.addLine(`const keys${block.id} = new Set();`);
}
this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`); this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
this.target.indentLevel++; this.target.indentLevel++;
this.addLine(`ctx[\`${ast.elem}\`] = ${vals}[${loopVar}];`); this.addLine(`ctx[\`${ast.elem}\`] = ${vals}[${loopVar}];`);
@@ -750,6 +768,13 @@ export class CodeGenerator {
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`); this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
} }
this.addLine(`let key${this.target.loopLevel} = ${ast.key ? compileExpr(ast.key) : loopVar};`); this.addLine(`let key${this.target.loopLevel} = ${ast.key ? compileExpr(ast.key) : loopVar};`);
if (this.dev) {
// Throw error on duplicate keys in dev mode
this.addLine(
`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`
);
this.addLine(`keys${block.id}.add(key${this.target.loopLevel});`);
}
let id: string; let id: string;
if (ast.memo) { if (ast.memo) {
this.target.hasCache = true; this.target.hasCache = true;
@@ -936,24 +961,84 @@ export class CodeGenerator {
compileComponent(ast: ASTComponent, ctx: Context) { compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx; let { block } = ctx;
let extraArgs: { [key: string]: string } = {};
// props // props
const props: string[] = []; const props: string[] = [];
let hasSlotsProp = false;
for (let p in ast.props) { for (let p in ast.props) {
props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`); const propName = /^[a-z_]+$/i.test(p) ? p : `'${p}'`;
props.push(`${propName}: ${this.captureExpression(ast.props[p]) || undefined}`);
if (p === "slots") {
hasSlotsProp = true;
}
} }
// slots
const hasSlot = !!Object.keys(ast.slots).length;
let slotDef: string = "";
if (hasSlot) {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
this.addLine(`const ${ctxStr} = capture(ctx);`);
}
let slotStr: string[] = [];
const initialTarget = this.target;
for (let slotName in ast.slots) {
let name = this.generateId("slot");
const slot = new CodeTarget(name);
this.functions.push(slot);
this.target = slot;
const subCtx: Context = createContext(ctx);
this.compileAST(ast.slots[slotName].content, subCtx);
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
const scope = ast.slots[slotName].scope;
if (scope) {
params.push(`__scope: "${scope}"`);
}
if (ast.slots[slotName].attrs) {
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
}
const slotInfo = `{${params.join(", ")}}`;
if (this.hasRef) {
slot.code.unshift(` const refs = ctx.__owl__.refs`);
slotStr.push(`'${slotName}': ${slotInfo}`);
} else {
slotStr.push(`'${slotName}': ${slotInfo}`);
}
}
this.target = initialTarget;
slotDef = `{${slotStr.join(", ")}}`;
}
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
props.push(`slots: ${slotDef}`);
}
const propStr = `{${props.join(",")}}`; const propStr = `{${props.join(",")}}`;
let propString = propStr; let propString = propStr;
if (ast.dynamicProps) { if (ast.dynamicProps) {
if (!props.length) { if (!props.length) {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`; propString = `${compileExpr(ast.dynamicProps)}`;
} else { } else {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`; 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}`);
propString = propVar!;
}
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
this.addLine(`${propVar!}.slots = Object.assign(${slotDef}, ${propVar!}.slots)`);
}
// cmap key // cmap key
const key = this.generateComponentKey(); const key = this.generateComponentKey();
let expr: string; let expr: string;
@@ -965,41 +1050,7 @@ export class CodeGenerator {
} }
if (this.dev) { if (this.dev) {
const propVar = this.generateId("props"); this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
this.addLine(`const ${propVar} = ${propString}`);
this.addLine(`helpers.validateProps(${expr}, ${propVar}, ctx)`);
propString = propVar;
}
// slots
const hasSlot = !!Object.keys(ast.slots).length;
let slotDef: string;
if (hasSlot) {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
this.addLine(`const ${ctxStr} = capture(ctx);`);
}
let slotStr: string[] = [];
const initialTarget = this.target;
for (let slotName in ast.slots) {
let name = this.generateId("slot");
const slot = new CodeTarget(name);
slot.signature = "ctx => (node, key) => {";
this.functions.push(slot);
this.target = slot;
const subCtx: Context = createContext(ctx);
this.compileAST(ast.slots[slotName], subCtx);
if (this.hasRef) {
slot.code.unshift(` const refs = ctx.__owl__.refs`);
slotStr.push(`'${slotName}': ${name}(${ctxStr})`);
} else {
slotStr.push(`'${slotName}': ${name}(${ctxStr})`);
}
}
this.target = initialTarget;
slotDef = `{${slotStr.join(", ")}}`;
extraArgs.slots = slotDef;
} }
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) { if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1012,12 +1063,7 @@ export class CodeGenerator {
keyArg = `${ctx.tKeyExpr} + ${keyArg}`; keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
} }
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`; const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
let blockExpr = `component(${blockArgs}${hasSlot ? ", true" : ""})`; let blockExpr = `component(${blockArgs})`;
if (Object.keys(extraArgs).length) {
this.shouldDefineAssign = true;
const content = Object.keys(extraArgs).map((k) => `${k}: ${extraArgs[k]}`);
blockExpr = `assign(${blockExpr}, {${content.join(", ")}})`;
}
if (ast.isDynamic) { if (ast.isDynamic) {
blockExpr = `toggler(${expr}, ${blockExpr})`; blockExpr = `toggler(${expr}, ${blockExpr})`;
} }
@@ -1036,24 +1082,33 @@ export class CodeGenerator {
} else { } else {
slotName = "'" + ast.name + "'"; slotName = "'" + ast.name + "'";
} }
let scope = null;
if (ast.attrs) {
const params = [];
for (const [n, v] of Object.entries(ast.attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
scope = `{${params.join(", ")}}`;
}
if (ast.defaultContent) { if (ast.defaultContent) {
let name = this.generateId("defaultSlot"); let name = this.generateId("defaultContent");
const slot = new CodeTarget(name); const slot = new CodeTarget(name);
slot.signature = "ctx => {";
this.functions.push(slot); this.functions.push(slot);
const initialTarget = this.target; const initialTarget = this.target;
const subCtx: Context = createContext(ctx); const subCtx: Context = createContext(ctx);
this.target = slot; this.target = slot;
this.compileAST(ast.defaultContent, subCtx); this.compileAST(ast.defaultContent, subCtx);
this.target = initialTarget; this.target = initialTarget;
blockString = `callSlot(ctx, node, key, ${slotName}, ${name}, ${dynamic})`; blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
} else { } else {
if (dynamic) { if (dynamic) {
let name = this.generateId("slot"); let name = this.generateId("slot");
this.addLine(`const ${name} = ${slotName};`); this.addLine(`const ${name} = ${slotName};`);
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}))`; blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
} else { } else {
blockString = `callSlot(ctx, node, key, ${slotName})`; blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
} }
} }
if (block) { if (block) {
+133 -37
View File
@@ -118,12 +118,13 @@ export interface ASTComponent {
isDynamic: boolean; isDynamic: boolean;
dynamicProps: string | null; dynamicProps: string | null;
props: { [name: string]: string }; props: { [name: string]: string };
slots: { [name: string]: AST }; slots: { [name: string]: { content: AST; attrs?: { [key: string]: string }; scope?: string } };
} }
export interface ASTSlot { export interface ASTSlot {
type: ASTType.TSlot; type: ASTType.TSlot;
name: string; name: string;
attrs: { [key: string]: string };
defaultContent: AST | null; defaultContent: AST | null;
} }
@@ -176,7 +177,8 @@ interface ParsingContext {
} }
export function parse(xml: string | Node): AST { export function parse(xml: string | Node): AST {
const node = xml instanceof Element ? xml : parseXML(`<t>${xml}</t>`).firstChild!; const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
normalizeXML(node);
const ctx = { inPreTag: false, inSVG: false }; const ctx = { inPreTag: false, inSVG: false };
const ast = parseNode(node, ctx); const ast = parseNode(node, ctx);
if (!ast) { if (!ast) {
@@ -593,7 +595,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
if (ast && ast.type === ASTType.TComponent) { if (ast && ast.type === ASTType.TComponent) {
return { return {
...ast, ...ast,
slots: { default: tcall }, slots: { default: { content: tcall } },
}; };
} }
} }
@@ -702,6 +704,20 @@ function parseTSetNode(node: Element, ctx: ParsingContext): AST | null {
// Components // Components
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Error messages when trying to use an unsupported directive on a component
const directiveErrorMap = new Map([
["t-on", "t-on is no longer supported on components. Consider passing a callback in props."],
[
"t-ref",
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop.",
],
["t-att", "t-att makes no sense on component: props are already treated as expressions"],
[
"t-attf",
"t-attf is not supported on components: use template strings for string interpolation in props",
],
]);
function parseComponent(node: Element, ctx: ParsingContext): AST | null { function parseComponent(node: Element, ctx: ParsingContext): AST | null {
let name = node.tagName; let name = node.tagName;
const firstLetter = name[0]; const firstLetter = name[0];
@@ -725,10 +741,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const props: ASTComponent["props"] = {}; const props: ASTComponent["props"] = {};
for (let name of node.getAttributeNames()) { for (let name of node.getAttributeNames()) {
const value = node.getAttribute(name)!; const value = node.getAttribute(name)!;
if (name.startsWith("t-on-")) { if (name.startsWith("t-")) {
throw new Error( const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
"t-on is no longer supported on Component node. Consider passing a callback in props." throw new Error(message || `unsupported directive on Component: ${name}`);
);
} else { } else {
props[name] = value; props[name] = value;
} }
@@ -741,6 +756,11 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
// named slots // named slots
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]")); const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
for (let slotNode of slotNodes) { for (let slotNode of slotNodes) {
if (slotNode.tagName !== "t") {
throw new Error(
`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`
);
}
const name = slotNode.getAttribute("t-set-slot")!; const name = slotNode.getAttribute("t-set-slot")!;
// check if this is defined in a sub component (in which case it should // check if this is defined in a sub component (in which case it should
@@ -762,14 +782,27 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
slotNode.remove(); slotNode.remove();
const slotAst = parseNode(slotNode, ctx); const slotAst = parseNode(slotNode, ctx);
if (slotAst) { if (slotAst) {
slots[name] = slotAst; const slotInfo: any = { content: slotAst };
const attrs: { [key: string]: string } = {};
for (let attributeName of slotNode.getAttributeNames()) {
const value = slotNode.getAttribute(attributeName)!;
if (attributeName === "t-slot-scope") {
slotInfo.scope = value;
continue;
}
attrs[attributeName] = value;
}
if (Object.keys(attrs).length) {
slotInfo.attrs = attrs;
}
slots[name] = slotInfo;
} }
} }
// default slot // default slot
const defaultContent = parseChildNodes(clone, ctx); const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) { if (defaultContent) {
slots.default = defaultContent; slots.default = { content: defaultContent };
} }
} }
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots }; return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
@@ -783,9 +816,17 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-slot")) { if (!node.hasAttribute("t-slot")) {
return null; return null;
} }
const name = node.getAttribute("t-slot")!;
node.removeAttribute("t-slot");
const attrs: { [key: string]: string } = {};
for (let attributeName of node.getAttributeNames()) {
const value = node.getAttribute(attributeName)!;
attrs[attributeName] = value;
}
return { return {
type: ASTType.TSlot, type: ASTType.TSlot,
name: node.getAttribute("t-slot")!, name,
attrs,
defaultContent: parseChildNodes(node, ctx), defaultContent: parseChildNodes(node, ctx),
}; };
} }
@@ -822,34 +863,17 @@ function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
return { type: ASTType.Multi, content: children }; return { type: ASTType.Multi, content: children };
} }
} }
function parseXML(xml: string): Document {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml"); /**
if (doc.getElementsByTagName("parsererror").length) { * Normalizes the content of an Element so that t-if/t-elif/t-else directives
let msg = "Invalid XML in template."; * immediately follow one another (by removing empty text nodes or comments).
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent; * Throws an error when a conditional branching statement is malformed. This
if (parsererrorText) { * function modifies the Element in place.
msg += "\nThe parser has produced the following error message:\n" + parsererrorText; *
const re = /\d+/g; * @param el the element containing the tree that should be normalized
const firstMatch = re.exec(parsererrorText); */
if (firstMatch) { function normalizeTIf(el: Element) {
const lineNumber = Number(firstMatch[0]); let tbranch = el.querySelectorAll("[t-elif], [t-else]");
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 Error(msg);
}
let tbranch = doc.querySelectorAll("[t-elif], [t-else]");
for (let i = 0, ilen = tbranch.length; i < ilen; i++) { for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
let node = tbranch[i]; let node = tbranch[i];
let prevElem = node.previousElementSibling!; let prevElem = node.previousElementSibling!;
@@ -883,6 +907,78 @@ function parseXML(xml: string): Document {
); );
} }
} }
}
/**
* Normalizes the content of an Element so that t-esc directives on components
* are removed and instead places a <t t-esc=""> as the default slot of the
* component. Also throws if the component already has content. This function
* modifies the Element in place.
*
* @param el the element containing the tree that should be normalized
*/
function normalizeTEsc(el: Element) {
const elements = [...el.querySelectorAll("[t-esc]")].filter(
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
);
for (const el of elements) {
if (el.childNodes.length) {
throw new Error("Cannot have t-esc on a component that already has content");
}
const value = el.getAttribute("t-esc");
el.removeAttribute("t-esc");
const t = el.ownerDocument.createElement("t");
if (value != null) {
t.setAttribute("t-esc", value);
}
el.appendChild(t);
}
}
/**
* Normalizes the tree inside a given element and do some preliminary validation
* on it. This function modifies the Element in place.
*
* @param el the element containing the tree that should be normalized
*/
function normalizeXML(el: Element) {
normalizeTIf(el);
normalizeTEsc(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: string): XMLDocument {
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 Error(msg);
}
return doc; return doc;
} }
+2 -4
View File
@@ -1,8 +1,6 @@
import type { Env } from "../app/app"; import type { Env } from "../app/app";
import type { ComponentNode } from "./component_node"; import type { ComponentNode } from "./component_node";
export type Props = { [key: string]: any };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Component Class // Component Class
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -28,7 +26,7 @@ export class Component {
setup() {} setup() {}
render(deep: boolean = false): Promise<void> { render(): Promise<void> {
return this.__owl__.render(deep); return this.__owl__.render();
} }
} }
+33 -77
View File
@@ -1,9 +1,6 @@
import type { App, Env } from "../app/app"; import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom"; import { BDom, VNode } from "../blockdom";
import { clearReactivesForCallback, Reactive, reactive } from "../reactivity"; import { Component } from "./component";
import { batched, Callback } from "../utils";
import { Component, Props } from "./component";
import { fibersInError, handleError } from "./error_handling";
import { import {
Fiber, Fiber,
makeChildFiber, makeChildFiber,
@@ -11,68 +8,20 @@ import {
MountFiber, MountFiber,
MountOptions, MountOptions,
RootFiber, RootFiber,
__internal__destroyed __internal__destroyed,
} from "./fibers"; } from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { applyDefaultProps } from "./props_validation"; import { applyDefaultProps } from "./props_validation";
import { STATUS } from "./status"; import { STATUS } from "./status";
import { applyStyles } from "./style"; import { applyStyles } from "./style";
let currentNode: ComponentNode | null = null;
export function getCurrent(): ComponentNode | null {
return currentNode;
}
// -----------------------------------------------------------------------------
// 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> {
if (!batchedRenderFunctions.has(currentNode!)) {
batchedRenderFunctions.set(
currentNode!,
batched(() => currentNode!.render())
);
}
const render = batchedRenderFunctions.get(currentNode!)!;
const reactiveState = reactive(state, render);
// manual implementation of onWillUnmount to break cyclic dependency
currentNode!.willUnmount.unshift( clearReactivesForCallback.bind(null, render))
return reactiveState;
}
// -----------------------------------------------------------------------------
// component function (used in compiled template code)
// -----------------------------------------------------------------------------
function arePropsDifferent(props1: Props, props2: Props): boolean {
for (let k in props1) {
if (props1[k] !== props2[k]) {
return true;
}
}
return false;
}
export function component( export function component(
name: string | typeof Component, name: string | typeof Component,
props: any, props: any,
key: string, key: string,
ctx: ComponentNode, ctx: ComponentNode,
parent: any, parent: any
hasSlots: boolean = false
): ComponentNode { ): ComponentNode {
console.warn('asdf')
let node: any = ctx.children[key]; let node: any = ctx.children[key];
let isDynamic = typeof name !== "string"; let isDynamic = typeof name !== "string";
@@ -90,11 +39,7 @@ export function component(
const parentFiber = ctx.fiber!; const parentFiber = ctx.fiber!;
if (node) { if (node) {
console.warn('coucou'); node.updateAndRender(props, parentFiber);
if (hasSlots || parentFiber.deep || arePropsDifferent(node.component.props, props)) {
console.warn('coucou3');
node.updateAndRender(props, parentFiber);
}
} else { } else {
// new component // new component
let C; let C;
@@ -116,9 +61,15 @@ export function component(
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Component VNode class // Component VNode
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
let currentNode: ComponentNode | null = null;
export function getCurrent(): ComponentNode | null {
return currentNode;
}
type LifecycleHook = Function; type LifecycleHook = Function;
export class ComponentNode<T extends typeof Component = typeof Component> export class ComponentNode<T extends typeof Component = typeof Component>
@@ -136,7 +87,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
level: number; level: number;
childEnv: Env; childEnv: Env;
children: { [key: string]: ComponentNode } = Object.create(null); children: { [key: string]: ComponentNode } = Object.create(null);
slots: any = {};
refs: any = {}; refs: any = {};
willStart: LifecycleHook[] = []; willStart: LifecycleHook[] = [];
@@ -155,9 +105,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
applyDefaultProps(props, C); applyDefaultProps(props, C);
const env = (parent && parent.childEnv) || app.env; const env = (parent && parent.childEnv) || app.env;
this.childEnv = env; this.childEnv = env;
// if (props) {
// props = useState(props);
// }
this.component = new C(props, env, this) as any; this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this); this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
if (C.style) { if (C.style) {
@@ -174,6 +121,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
async initiateRender(fiber: Fiber | MountFiber) { async initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber;
if (this.mounted.length) { if (this.mounted.length) {
fiber.root.mounted.push(fiber); fiber.root.mounted.push(fiber);
} }
@@ -189,23 +137,34 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
} }
async render(deep: boolean = false) { async render() {
let fiber = this.fiber; const current = this.fiber;
if (fiber && !fiber.bdom && !fibersInError.has(fiber)) { if (current && !current.bdom && !fibersInError.has(current)) {
return fiber.root.promise; return current.root.promise;
} }
if (!this.bdom && !fiber) { if (!this.bdom && !current) {
// should find a way to return the future mounting promise // should find a way to return the future mounting promise
return; return;
} }
fiber = makeRootFiber(this); const fiber = makeRootFiber(this);
fiber.deep = deep; this.fiber = fiber;
this.app.scheduler.addFiber(fiber); this.app.scheduler.addFiber(fiber);
await Promise.resolve(); await Promise.resolve();
if (this.status === STATUS.DESTROYED) { if (this.status === STATUS.DESTROYED) {
return; return;
} }
if (this.fiber === fiber) { // We only want to actually render the component if the following two
// conditions are true:
// * this.fiber: it could be null, in which case the render has been cancelled
// * (current || !fiber.parent): if current is not null, this means that the
// render function was called when a render was already occurring. In this
// case, the pending rendering was cancelled, and the fiber needs to be
// rendered to complete the work. If current is null, we check that the
// fiber has no parent. If that is the case, the fiber was downgraded from
// a root fiber to a child fiber in the previous microtick, because it was
// embedded in a rendering coming from above, so the fiber will be rendered
// in the next microtick anyway, so we should not render it again.
if (this.fiber && (current || !fiber.parent)) {
this._render(fiber); this._render(fiber);
} }
return fiber.root.promise; return fiber.root.promise;
@@ -254,6 +213,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
async updateAndRender(props: any, parentFiber: Fiber) { async updateAndRender(props: any, parentFiber: Fiber) {
// update // update
const fiber = makeChildFiber(this, parentFiber); const fiber = makeChildFiber(this, parentFiber);
this.fiber = fiber;
if (this.willPatch.length) { if (this.willPatch.length) {
parentFiber.root.willPatch.push(fiber); parentFiber.root.willPatch.push(fiber);
} }
@@ -294,10 +254,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
patch() { patch() {
if (!this.fiber) {
// component was not rendered => no need to do anything
return;
}
this.bdom!.patch(this!.fiber!.bdom!, false); this.bdom!.patch(this!.fiber!.bdom!, false);
this.fiber!.appliedToDom = true; this.fiber!.appliedToDom = true;
this.fiber = null; this.fiber = null;
-2
View File
@@ -67,11 +67,9 @@ export class Fiber {
parent: Fiber | null; parent: Fiber | null;
children: Fiber[] = []; children: Fiber[] = [];
appliedToDom = false; appliedToDom = false;
deep: boolean = false;
constructor(node: ComponentNode, parent: Fiber | null) { constructor(node: ComponentNode, parent: Fiber | null) {
this.node = node; this.node = node;
node.fiber = this;
this.parent = parent; this.parent = parent;
if (parent) { if (parent) {
const root = parent.root; const root = parent.root;
+1 -2
View File
@@ -34,7 +34,6 @@ import type { AppConfig } from "./app/app";
import { App } from "./app/app"; import { App } from "./app/app";
import { Component } from "./component/component"; import { Component } from "./component/component";
import { getCurrent } from "./component/component_node"; import { getCurrent } from "./component/component_node";
export { useState } from "./component/component_node";
export { App, Component }; export { App, Component };
@@ -56,7 +55,7 @@ export { status } from "./component/status";
export { Portal } from "./portal"; export { Portal } from "./portal";
export { Memo } from "./memo"; export { Memo } from "./memo";
export { css, xml } from "./tags"; export { css, xml } from "./tags";
export { reactive } from "./reactivity"; export { useState, reactive } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks"; export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils"; export { EventBus, whenReady, loadFile, markup } from "./utils";
+1 -1
View File
@@ -39,7 +39,7 @@ export class Memo extends Component {
*/ */
function shallowEqual(p1: any, p2: any): boolean { function shallowEqual(p1: any, p2: any): boolean {
for (let k in p1) { for (let k in p1) {
if (p1[k] !== p2[k]) { if (k !== "slots" && p1[k] !== p2[k]) {
return false; return false;
} }
} }
+53 -3
View File
@@ -1,4 +1,5 @@
import { Callback } from "./utils"; import { onWillUnmount } from "./component/lifecycle_hooks";
import { ComponentNode, getCurrent } from "./component/component_node";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object) // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
const TARGET = Symbol("Target"); const TARGET = Symbol("Target");
@@ -7,7 +8,8 @@ const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol; type ObjectKey = string | number | symbol;
type Target = object; type Target = object;
export type Reactive<T extends Target = Target> = T & { type Callback = () => void;
type Reactive<T extends Target = Target> = T & {
[TARGET]: any; [TARGET]: any;
}; };
@@ -79,7 +81,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
* *
* @param callback the callback for which the reactives need to be cleared * @param callback the callback for which the reactives need to be cleared
*/ */
export function clearReactivesForCallback(callback: Callback): void { function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback); const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) { if (!targetsToClear) {
return; return;
@@ -188,3 +190,51 @@ export function reactive<T extends Target>(target: T, callback: Callback): React
return reactivesForTarget.get(callback) as Reactive<T>; return reactivesForTarget.get(callback) as Reactive<T>;
} }
/**
* Creates a batched version of a callback so that all calls to it in the same
* microtick will only call the original callback once.
*
* @param callback the callback to batch
* @returns a batched version of the original callback
*/
export function batched(callback: Callback): Callback {
let called = false;
return async () => {
// This await blocks all calls to the callback here, then releases them sequentially
// in the next microtick. This line decides the granularity of the batch.
await Promise.resolve();
if (!called) {
called = true;
callback();
// wait for all calls in this microtick to fall through before resetting "called"
// so that only the first call to the batched function calls the original callback
await Promise.resolve();
called = false;
}
};
}
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
* component to subscribe to that data and be rerendered when it changes.
*
* @param state the state to observe
* @returns a reactive object that will cause the component to re-render on
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> {
const node = getCurrent()!;
if (!batchedRenderFunctions.has(node)) {
batchedRenderFunctions.set(
node,
batched(() => node.render())
);
}
const render = batchedRenderFunctions.get(node)!;
const reactiveState = reactive(state, render);
onWillUnmount(() => clearReactivesForCallback(render));
return reactiveState;
}
+2 -2
View File
@@ -5,9 +5,9 @@ import { globalTemplates } from "./app/template_set";
// Global templates // Global templates
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export function xml(strings: TemplateStringsArray, ...args: any[]) { export function xml(...args: Parameters<typeof String.raw>) {
const name = `__template__${xml.nextId++}`; const name = `__template__${xml.nextId++}`;
const value = String.raw(strings, ...args); const value = String.raw(...args);
globalTemplates[name] = value; globalTemplates[name] = value;
return name; return name;
} }
-26
View File
@@ -1,29 +1,3 @@
export type Callback = () => void;
/**
* Creates a batched version of a callback so that all calls to it in the same
* microtick will only call the original callback once.
*
* @param callback the callback to batch
* @returns a batched version of the original callback
*/
export function batched(callback: Callback): Callback {
let called = false;
return async () => {
// This await blocks all calls to the callback here, then releases them sequentially
// in the next microtick. This line decides the granularity of the batch.
await Promise.resolve();
if (!called) {
called = true;
callback();
// wait for all calls in this microtick to fall through before resetting "called"
// so that only the first call to the batched function calls the original callback
await Promise.resolve();
called = false;
}
};
}
export class EventBus extends EventTarget { export class EventBus extends EventTarget {
trigger(name: string, payload?: any) { trigger(name: string, payload?: any) {
this.dispatchEvent(new CustomEvent(name, { detail: payload })); this.dispatchEvent(new CustomEvent(name, { detail: payload }));
@@ -9,8 +9,7 @@ exports[`t-on can bind event handler 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; let d1 = [ctx['add'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -116,10 +115,8 @@ exports[`t-on can bind two event handlers 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['handleClick']; let d1 = [ctx['handleClick'], ctx];
let d1 = [v1, ctx]; let d2 = [ctx['handleDblClick'], ctx];
const v2 = ctx['handleDblClick'];
let d2 = [v2, ctx];
return block1([d1, d2]); return block1([d1, d2]);
} }
}" }"
@@ -134,8 +131,7 @@ exports[`t-on handler is bound to proper owner 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; let d1 = [ctx['add'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -155,8 +151,7 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) { for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`value\`] = v_block1[i1]; ctx[\`value\`] = v_block1[i1];
let key1 = ctx['value']; let key1 = ctx['value'];
const v1 = ctx['add']; let d1 = [ctx['add'], ctx];
let d1 = [v1, ctx];
c_block1[i1] = withKey(block2([d1]), key1); c_block1[i1] = withKey(block2([d1]), key1);
} }
return list(c_block1); return list(c_block1);
@@ -173,8 +168,7 @@ exports[`t-on handler is bound to proper owner, part 3 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; let d1 = [ctx['add'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -202,8 +196,7 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; let d1 = [ctx['add'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -242,8 +235,7 @@ exports[`t-on receive event in first argument 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; let d1 = [ctx['add'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -258,10 +250,8 @@ exports[`t-on t-on modifiers (native listener) basic support for native listener
let block1 = createBlock(\`<div class=\\"myClass\\" block-handler-0=\\"click\\"><button block-handler-1=\\"click\\">Button</button></div>\`); let block1 = createBlock(\`<div class=\\"myClass\\" block-handler-0=\\"click\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['divClicked']; let d1 = [ctx['divClicked'], ctx];
let d1 = [v1, ctx]; let d2 = [ctx['btnClicked'], ctx];
const v2 = ctx['btnClicked'];
let d2 = [v2, ctx];
return block1([d1, d2]); return block1([d1, d2]);
} }
}" }"
@@ -276,8 +266,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-text-1/></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-text-1/></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [ctx['onClick'], ctx];
let d1 = [v1, ctx];
let d2 = ctx['text']; let d2 = ctx['text'];
return block1([d1, d2]); return block1([d1, d2]);
} }
@@ -293,8 +282,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-child-0/></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-child-0/></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [ctx['onClick'], ctx];
let d1 = [v1, ctx];
let b2 = safeOutput(ctx['html']); let b2 = safeOutput(ctx['html']);
return block1([d1], [b2]); return block1([d1], [b2]);
} }
@@ -310,10 +298,8 @@ exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] =
let block1 = createBlock(\`<div block-handler-0=\\"click.capture\\"><button block-handler-1=\\"click\\">Button</button></div>\`); let block1 = createBlock(\`<div block-handler-0=\\"click.capture\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onCapture']; let d1 = [\\"capture\\", ctx['onCapture'], ctx];
let d1 = [\\"capture\\", v1, ctx]; let d2 = [ctx['doSomething'], ctx];
const v2 = ctx['doSomething'];
let d2 = [v2, ctx];
return block1([d1, d2]); return block1([d1, d2]);
} }
}" }"
@@ -343,8 +329,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifi
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent.self\\"><span>Button</span></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent.self\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx];
let d1 = [\\"prevent\\",\\"self\\", v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -359,12 +344,9 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop mod
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button 1</button><button block-handler-1=\\"click.stop\\">Button 2</button><button block-handler-2=\\"click.prevent.stop\\">Button 3</button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button 1</button><button block-handler-1=\\"click.stop\\">Button 2</button><button block-handler-2=\\"click.prevent.stop\\">Button 3</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClickPrevented']; let d1 = [\\"prevent\\", ctx['onClickPrevented'], ctx];
let d1 = [\\"prevent\\", v1, ctx]; let d2 = [\\"stop\\", ctx['onClickStopped'], ctx];
const v2 = ctx['onClickStopped']; let d3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx];
let d2 = [\\"stop\\", v2, ctx];
const v3 = ctx['onClickPreventedAndStopped'];
let d3 = [\\"prevent\\",\\"stop\\", v3, ctx];
return block1([d1, d2, d3]); return block1([d1, d2, d3]);
} }
}" }"
@@ -406,8 +388,7 @@ exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifi
let block1 = createBlock(\`<div><button block-handler-0=\\"click.self.prevent\\"><span>Button</span></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.self.prevent\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx];
let d1 = [\\"self\\",\\"prevent\\", v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -422,10 +403,8 @@ exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><span>Button</span></button><button block-handler-1=\\"click.self\\"><span>Button</span></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><span>Button</span></button><button block-handler-1=\\"click.self\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [ctx['onClick'], ctx];
let d1 = [v1, ctx]; let d2 = [\\"self\\", ctx['onClickSelf'], ctx];
const v2 = ctx['onClickSelf'];
let d2 = [\\"self\\", v2, ctx];
return block1([d1, d2]); return block1([d1, d2]);
} }
}" }"
@@ -440,10 +419,8 @@ exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`
let block1 = createBlock(\`<div block-handler-0=\\"click.synthetic\\"><button block-handler-1=\\"click.synthetic\\">Button</button></div>\`); let block1 = createBlock(\`<div block-handler-0=\\"click.synthetic\\"><button block-handler-1=\\"click.synthetic\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['divClicked']; let d1 = [\\"synthetic\\", ctx['divClicked'], ctx];
let d1 = [\\"synthetic\\", v1, ctx]; let d2 = [\\"synthetic\\", ctx['btnClicked'], ctx];
const v2 = ctx['btnClicked'];
let d2 = [\\"synthetic\\", v2, ctx];
return block1([d1, d2]); return block1([d1, d2]);
} }
}" }"
@@ -523,8 +500,7 @@ exports[`t-on t-on with t-call 1`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`); let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['update']; let d1 = [ctx['update'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
+15 -27
View File
@@ -182,7 +182,7 @@ exports[`misc other complex template 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_14 = getTemplate(\`LOAD_INFOS_TEMPLATE\`); const callTemplate_2 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
let block1 = createBlock(\`<div><header><nav class=\\"navbar navbar-expand-md navbar-light bg-light\\"><a block-attribute-0=\\"href\\"><b style=\\"color:#777;\\"><block-text-1/></b></a><button type=\\"button\\" class=\\"navbar-toggler\\" data-toggle=\\"collapse\\" data-target=\\"#top_menu_collapse\\"><span class=\\"navbar-toggler-icon\\"/></button><div class=\\"collapse navbar-collapse\\" id=\\"top_menu_collapse\\" aria-expanded=\\"false\\"><ul class=\\"nav navbar-nav ml-auto text-right\\" id=\\"top_menu\\"><block-child-0/><li class=\\"nav-item divider\\"/><block-child-1/></ul><div><div class=\\"input-group input-group-sm\\"><div class=\\"input-group-prepend input-group-sm\\"><button class=\\"btn btn-default fa fa-cog\\" title=\\"Settings\\" block-handler-2=\\"click\\"/><button class=\\"btn btn-default\\" block-handler-3=\\"click\\"> More </button><block-child-2/></div><input class=\\"form-control\\" type=\\"text\\" placeholder=\\"Search\\" aria-label=\\"Search\\" name=\\"search\\" block-attribute-4=\\"value\\" block-handler-5=\\"keyup\\" block-handler-6=\\"change\\" block-ref=\\"7\\"/><div class=\\"input-group-append\\"><button class=\\"btn btn-default fa fa-eraser\\" block-handler-8=\\"click\\"/></div></div></div></div></nav></header><div class=\\"container-fluid\\" block-ref=\\"9\\"><div class=\\"row\\"><!--div class=\\"form-group col-md-6\\"> let block1 = createBlock(\`<div><header><nav class=\\"navbar navbar-expand-md navbar-light bg-light\\"><a block-attribute-0=\\"href\\"><b style=\\"color:#777;\\"><block-text-1/></b></a><button type=\\"button\\" class=\\"navbar-toggler\\" data-toggle=\\"collapse\\" data-target=\\"#top_menu_collapse\\"><span class=\\"navbar-toggler-icon\\"/></button><div class=\\"collapse navbar-collapse\\" id=\\"top_menu_collapse\\" aria-expanded=\\"false\\"><ul class=\\"nav navbar-nav ml-auto text-right\\" id=\\"top_menu\\"><block-child-0/><li class=\\"nav-item divider\\"/><block-child-1/></ul><div><div class=\\"input-group input-group-sm\\"><div class=\\"input-group-prepend input-group-sm\\"><button class=\\"btn btn-default fa fa-cog\\" title=\\"Settings\\" block-handler-2=\\"click\\"/><button class=\\"btn btn-default\\" block-handler-3=\\"click\\"> More </button><block-child-2/></div><input class=\\"form-control\\" type=\\"text\\" placeholder=\\"Search\\" aria-label=\\"Search\\" name=\\"search\\" block-attribute-4=\\"value\\" block-handler-5=\\"keyup\\" block-handler-6=\\"change\\" block-ref=\\"7\\"/><div class=\\"input-group-append\\"><button class=\\"btn btn-default fa fa-eraser\\" block-handler-8=\\"click\\"/></div></div></div></div></nav></header><div class=\\"container-fluid\\" block-ref=\\"9\\"><div class=\\"row\\"><!--div class=\\"form-group col-md-6\\">
<h5>Search options</h5> <h5>Search options</h5>
@@ -223,9 +223,7 @@ exports[`misc other complex template 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`project\`] = v_block2[i1]; ctx[\`project\`] = v_block2[i1];
let key1 = ctx['project'].id; let key1 = ctx['project'].id;
const v1 = ctx['selectProject']; let d3 = [ctx['selectProject'](ctx['project']), ctx];
const v2 = ctx['project'];
let d3 = [v1(v2), ctx];
let d4 = ctx['project'].name; let d4 = ctx['project'].name;
c_block2[i1] = withKey(block3([d3, d4]), key1); c_block2[i1] = withKey(block3([d3, d4]), key1);
} }
@@ -257,10 +255,8 @@ exports[`misc other complex template 1`] = `
} }
b4 = multi([b5, b6]); b4 = multi([b5, b6]);
} }
const v3 = ctx['toggleSettingsMenu']; let d11 = [ctx['toggleSettingsMenu'], ctx];
let d11 = [v3, ctx]; let d12 = [ctx['toggleMore'], ctx];
const v4 = ctx['toggleMore'];
let d12 = [v4, ctx];
if (ctx['categories']&&ctx['categories'].length>1) { if (ctx['categories']&&ctx['categories'].length>1) {
ctx = Object.create(ctx); ctx = Object.create(ctx);
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']); const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);
@@ -277,13 +273,10 @@ exports[`misc other complex template 1`] = `
b14 = block14([], [b15]); b14 = block14([], [b15]);
} }
let d16 = ctx['search'].value; let d16 = ctx['search'].value;
const v5 = ctx['updateFilter']; let d17 = [ctx['updateFilter'], ctx];
let d17 = [v5, ctx]; let d18 = [ctx['updateFilter'], ctx];
const v6 = ctx['updateFilter'];
let d18 = [v6, ctx];
let d19 = (el) => refs[\`search_input\`] = el; let d19 = (el) => refs[\`search_input\`] = el;
const v7 = ctx['clearSearch']; let d20 = [ctx['clearSearch'], ctx];
let d20 = [v7, ctx];
let d21 = (el) => refs[\`settings_menu\`] = el; let d21 = (el) => refs[\`settings_menu\`] = el;
if (ctx['triggers']) { if (ctx['triggers']) {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -297,8 +290,7 @@ exports[`misc other complex template 1`] = `
let d23 = \`trigger_\${ctx['trigger'].id}\`; let d23 = \`trigger_\${ctx['trigger'].id}\`;
let d24 = ctx['options'].trigger_display[ctx['trigger'].id]; let d24 = ctx['options'].trigger_display[ctx['trigger'].id];
let d25 = ctx['trigger'].id; let d25 = ctx['trigger'].id;
const v8 = ctx['updateTriggerDisplay']; let d26 = [ctx['updateTriggerDisplay'], ctx];
let d26 = [v8, ctx];
let d27 = \`trigger_\${ctx['trigger'].id}\`; let d27 = \`trigger_\${ctx['trigger'].id}\`;
let d28 = ctx['trigger'].name; let d28 = ctx['trigger'].name;
b20 = block20([d22, d23, d24, d25, d26, d27, d28]); b20 = block20([d22, d23, d24, d25, d26, d27, d28]);
@@ -307,19 +299,15 @@ exports[`misc other complex template 1`] = `
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b18 = list(c_block18); let b18 = list(c_block18);
const v9 = ctx['triggerAll']; let d29 = [ctx['triggerAll'], ctx];
let d29 = [v9, ctx]; let d30 = [ctx['triggerNone'], ctx];
const v10 = ctx['triggerNone']; let d31 = [ctx['triggerDefault'], ctx];
let d30 = [v10, ctx]; let d32 = [ctx['toggleSettingsMenu'], ctx];
const v11 = ctx['triggerDefault'];
let d31 = [v11, ctx];
const v12 = ctx['toggleSettingsMenu'];
let d32 = [v12, ctx];
let b21 = block21([d29, d30, d31, d32]); let b21 = block21([d29, d30, d31, d32]);
b17 = multi([b18, b21]); b17 = multi([b18, b21]);
} }
if (ctx['load_infos']) { if (ctx['load_infos']) {
b22 = callTemplate_14.call(this, ctx, node, key + \`__13\`); b22 = callTemplate_2.call(this, ctx, node, key + \`__1\`);
} }
if (ctx['message']) { if (ctx['message']) {
let d33 = ctx['message']; let d33 = ctx['message'];
@@ -328,8 +316,8 @@ exports[`misc other complex template 1`] = `
if (!ctx['project']) { if (!ctx['project']) {
b24 = block24(); b24 = block24();
} else { } else {
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__15\`, node, ctx); let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__3\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__16\`, node, ctx); let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__4\`, node, ctx);
b25 = block25([], [b26, b27]); b25 = block25([], [b26, b27]);
} }
return block1([d1, d2, d11, d12, d16, d17, d18, d19, d20, d21], [b2, b4, b14, b17, b22, b23, b24, b25]); return block1([d1, d2, d11, d12, d16, d17, d18, d19, d20, d21], [b2, b4, b14, b17, b22, b23, b24, b25]);
+6 -6
View File
@@ -21,12 +21,12 @@ describe("error handling", () => {
expect(() => context.addTemplate("test", "<div/>")).toThrow("already defined"); expect(() => context.addTemplate("test", "<div/>")).toThrow("already defined");
}); });
// test("addTemplates throw if parser error", () => { test("addTemplates throw if parser error", () => {
// const context = new TestContext(); const context = new TestContext();
// expect(() => { expect(() => {
// context.addTemplates("<templates><abc>></templates>"); context.addTemplates("<templates><abc>></templates>");
// }).toThrow("Invalid XML in template"); }).toThrow("Invalid XML in template");
// }); });
test("nice error when t-on is evaluated with a missing event", () => { test("nice error when t-on is evaluated with a missing event", () => {
expect(() => renderToString(`<div t-on="somemethod"></div>`)).toThrow( expect(() => renderToString(`<div t-on="somemethod"></div>`)).toThrow(
+118 -48
View File
@@ -1069,7 +1069,31 @@ describe("qweb parser", () => {
test("component with event handler", async () => { test("component with event handler", async () => {
expect(() => parse(`<MyComponent t-on-click="someMethod"/>`)).toThrow( expect(() => parse(`<MyComponent t-on-click="someMethod"/>`)).toThrow(
"t-on is no longer supported on Component node. Consider passing a callback in props." "t-on is no longer supported on components. Consider passing a callback in props."
);
});
test("component with t-ref", async () => {
expect(() => parse(`<MyComponent t-ref="something"/>`)).toThrow(
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop."
);
});
test("component with t-att", async () => {
expect(() => parse(`<MyComponent t-att="something"/>`)).toThrow(
"t-att makes no sense on component: props are already treated as expressions"
);
});
test("component with t-attf", async () => {
expect(() => parse(`<MyComponent t-attf="something"/>`)).toThrow(
"t-attf is not supported on components: use template strings for string interpolation in props"
);
});
test("component with other unsupported directive", async () => {
expect(() => parse(`<MyComponent t-something="5"/>`)).toThrow(
"unsupported directive on Component: t-something"
); );
}); });
@@ -1080,7 +1104,22 @@ describe("qweb parser", () => {
dynamicProps: null, dynamicProps: null,
props: {}, props: {},
isDynamic: false, isDynamic: false,
slots: { default: { type: ASTType.Text, value: "foo" } }, slots: { default: { content: { type: ASTType.Text, value: "foo" } } },
});
});
test("a component with a default slot with attributes", async () => {
expect(
parse(`<MyComponent><t t-set-slot="default" param="param">foo</t></MyComponent>`)
).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
dynamicProps: null,
props: {},
isDynamic: false,
slots: {
default: { content: { type: ASTType.Text, value: "foo" }, attrs: { param: "param" } },
},
}); });
}); });
@@ -1093,31 +1132,33 @@ describe("qweb parser", () => {
props: {}, props: {},
slots: { slots: {
default: { default: {
type: ASTType.Multi, content: {
content: [ type: ASTType.Multi,
{ content: [
type: ASTType.DomNode, {
tag: "span", type: ASTType.DomNode,
dynamicTag: null, tag: "span",
attrs: {}, dynamicTag: null,
content: [], attrs: {},
ref: null, content: [],
model: null, ref: null,
on: {}, model: null,
ns: null, on: {},
}, ns: null,
{ },
type: ASTType.DomNode, {
tag: "div", type: ASTType.DomNode,
dynamicTag: null, tag: "div",
attrs: {}, dynamicTag: null,
content: [], attrs: {},
ref: null, content: [],
model: null, ref: null,
on: {}, model: null,
ns: null, on: {},
}, ns: null,
], },
],
},
}, },
}, },
}); });
@@ -1130,10 +1171,27 @@ describe("qweb parser", () => {
isDynamic: false, isDynamic: false,
dynamicProps: null, dynamicProps: null,
props: {}, props: {},
slots: { name: { type: ASTType.Text, value: "foo" } }, slots: { name: { content: { type: ASTType.Text, value: "foo" } } },
}); });
}); });
test("a component with a named slot with attributes", async () => {
expect(parse(`<MyComponent><t t-set-slot="name" param="param">foo</t></MyComponent>`)).toEqual({
type: ASTType.TComponent,
name: "MyComponent",
isDynamic: false,
dynamicProps: null,
props: {},
slots: { name: { content: { type: ASTType.Text, value: "foo" }, attrs: { param: "param" } } },
});
});
test("a component with a named slot with div tag", async () => {
expect(() =>
parse(`<MyComponent><div t-set-slot="name">foo</div></MyComponent>`)
).toThrowError();
});
test("a component with a named slot and some white space", async () => { test("a component with a named slot and some white space", async () => {
expect(parse(`<MyComponent><t t-set-slot="name">foo</t> </MyComponent>`)).toEqual({ expect(parse(`<MyComponent><t t-set-slot="name">foo</t> </MyComponent>`)).toEqual({
type: ASTType.TComponent, type: ASTType.TComponent,
@@ -1142,8 +1200,8 @@ describe("qweb parser", () => {
props: {}, props: {},
isDynamic: false, isDynamic: false,
slots: { slots: {
default: { type: ASTType.Text, value: " " }, default: { content: { type: ASTType.Text, value: " " } },
name: { type: ASTType.Text, value: "foo" }, name: { content: { type: ASTType.Text, value: "foo" } },
}, },
}); });
}); });
@@ -1161,8 +1219,8 @@ describe("qweb parser", () => {
props: {}, props: {},
isDynamic: false, isDynamic: false,
slots: { slots: {
a: { type: ASTType.Text, value: "foo" }, a: { content: { type: ASTType.Text, value: "foo" } },
b: { type: ASTType.Text, value: "bar" }, b: { content: { type: ASTType.Text, value: "bar" } },
}, },
}); });
}); });
@@ -1201,8 +1259,14 @@ describe("qweb parser", () => {
}); });
test("component with t-esc", async () => { test("component with t-esc", async () => {
expect(() => parse(`<MyComponent t-esc="someValue"/>`)).toThrow( expect(parse(`<MyComponent t-esc="someValue"/>`)).toEqual(
"t-esc is not supported on Component nodes" parse(`<MyComponent><t t-esc="someValue"/></MyComponent>`)
);
});
test("component with t-esc and content", async () => {
expect(() => parse(`<MyComponent t-esc="someValue">Some content</MyComponent>`)).toThrow(
"Cannot have t-esc on a component that already has content"
); );
}); });
@@ -1213,7 +1277,7 @@ describe("qweb parser", () => {
dynamicProps: null, dynamicProps: null,
props: {}, props: {},
isDynamic: false, isDynamic: false,
slots: { default: { body: null, name: "subTemplate", type: ASTType.TCall } }, slots: { default: { content: { body: null, name: "subTemplate", type: ASTType.TCall } } },
}); });
}); });
@@ -1233,12 +1297,14 @@ describe("qweb parser", () => {
isDynamic: false, isDynamic: false,
slots: { slots: {
default: { default: {
type: ASTType.TComponent, content: {
isDynamic: false, type: ASTType.TComponent,
name: "Child", isDynamic: false,
dynamicProps: null, name: "Child",
props: {}, dynamicProps: null,
slots: { brol: { type: ASTType.Text, value: "coucou" } }, props: {},
slots: { brol: { content: { type: ASTType.Text, value: "coucou" } } },
},
}, },
}, },
}); });
@@ -1248,7 +1314,7 @@ describe("qweb parser", () => {
const template = ` const template = `
<MyComponent> <MyComponent>
<Child> <Child>
<t><t t-set-slot="brol">coucou</t></t> <t t-set-slot="brol">coucou</t>
</Child> </Child>
</MyComponent> </MyComponent>
`; `;
@@ -1260,12 +1326,14 @@ describe("qweb parser", () => {
isDynamic: false, isDynamic: false,
slots: { slots: {
default: { default: {
type: ASTType.TComponent, content: {
isDynamic: false, type: ASTType.TComponent,
name: "Child", isDynamic: false,
dynamicProps: null, name: "Child",
props: {}, dynamicProps: null,
slots: { brol: { type: ASTType.Text, value: "coucou" } }, props: {},
slots: { brol: { content: { type: ASTType.Text, value: "coucou" } } },
},
}, },
}, },
}); });
@@ -1279,6 +1347,7 @@ describe("qweb parser", () => {
expect(parse(`<t t-slot="default"/>`)).toEqual({ expect(parse(`<t t-slot="default"/>`)).toEqual({
type: ASTType.TSlot, type: ASTType.TSlot,
name: "default", name: "default",
attrs: {},
defaultContent: null, defaultContent: null,
}); });
}); });
@@ -1287,6 +1356,7 @@ describe("qweb parser", () => {
expect(parse(`<t t-slot="header">default content</t>`)).toEqual({ expect(parse(`<t t-slot="header">default content</t>`)).toEqual({
type: ASTType.TSlot, type: ASTType.TSlot,
name: "header", name: "header",
attrs: {},
defaultContent: { type: ASTType.Text, value: "default content" }, defaultContent: { type: ASTType.Text, value: "default content" },
}); });
}); });
@@ -1020,7 +1020,7 @@ exports[`basics update props of component without concrete own node 3`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['childProps'].key; const tKey_1 = ctx['childProps'].key;
let b2 = toggler(tKey_1, component(\`Child\`, Object.assign({}, ctx['childProps']), tKey_1 + key + \`__2\`, node, ctx)); let b2 = toggler(tKey_1, component(\`Child\`, ctx['childProps'], tKey_1 + key + \`__2\`, node, ctx));
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -1044,6 +1044,30 @@ exports[`destroying/recreating a subwidget with different props (if start is not
}" }"
`; `;
exports[`parent and child rendered at exact same time 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].value);
}
}"
`;
exports[`parent and child rendered at exact same time 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`properly behave when destroyed/unmounted while rendering 1`] = ` exports[`properly behave when destroyed/unmounted while rendering 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -1067,7 +1091,7 @@ exports[`properly behave when destroyed/unmounted while rendering 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = component(\`SubChild\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx); let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -1130,10 +1154,9 @@ exports[`rendering component again in next microtick 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2; let b2;
const v1 = ctx['onClick']; let d1 = [ctx['onClick'], ctx];
let d1 = [v1, ctx];
if (ctx['env'].config.flag) { if (ctx['env'].config.flag) {
b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx); b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
} }
return block1([d1], [b2]); return block1([d1], [b2]);
} }
@@ -105,7 +105,7 @@ exports[`can catch errors can catch an error in a component render function 2`]
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -117,16 +117,15 @@ exports[`can catch errors can catch an error in a component render function 3`]
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -159,7 +158,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -207,7 +206,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -219,18 +218,17 @@ exports[`can catch errors can catch an error in the constructor call of a compon
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b3 = component(\`ClassicCompoent\`, {}, key + \`__3\`, node, ctx); let b3 = component(\`ClassicCompoent\`, {}, key + \`__2\`, node, ctx);
let b4 = component(\`ErrorComponent\`, {}, key + \`__4\`, node, ctx); let b4 = component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
return multi([b3, b4]); return multi([b3, b4]);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__4\`, node, ctx);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -241,16 +239,15 @@ exports[`can catch errors can catch an error in the constructor call of a compon
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -284,7 +281,7 @@ exports[`can catch errors can catch an error in the initial call of a component
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -296,16 +293,15 @@ exports[`can catch errors can catch an error in the initial call of a component
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -339,7 +335,7 @@ exports[`can catch errors can catch an error in the initial call of a component
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -351,18 +347,17 @@ exports[`can catch errors can catch an error in the initial call of a component
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3; let b3;
if (ctx['state'].flag) { if (ctx['state'].flag) {
b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
} }
return block1([], [b3]); return block1([], [b3]);
} }
@@ -396,7 +391,7 @@ exports[`can catch errors can catch an error in the mounted call 2`] = `
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -408,16 +403,15 @@ exports[`can catch errors can catch an error in the mounted call 3`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -451,7 +445,7 @@ exports[`can catch errors can catch an error in the willPatch call 2`] = `
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -463,17 +457,16 @@ exports[`can catch errors can catch an error in the willPatch call 3`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].message; let d1 = ctx['state'].message;
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([d1], [b3]); return block1([d1], [b3]);
} }
}" }"
@@ -506,7 +499,7 @@ exports[`can catch errors can catch an error in the willStart call 2`] = `
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -518,16 +511,15 @@ exports[`can catch errors can catch an error in the willStart call 3`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx); return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -574,7 +566,7 @@ exports[`can catch errors can catch an error origination from a child's willStar
if (ctx['state'].error) { if (ctx['state'].error) {
b2 = text(\`Error handled\`); b2 = text(\`Error handled\`);
} else { } else {
b3 = callSlot(ctx, node, key, 'default'); b3 = callSlot(ctx, node, key, 'default', false, {});
} }
return block1([], [b2, b3]); return block1([], [b2, b3]);
} }
@@ -586,18 +578,17 @@ exports[`can catch errors can catch an error origination from a child's willStar
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b3 = component(\`ClassicCompoent\`, {}, key + \`__3\`, node, ctx); let b3 = component(\`ClassicCompoent\`, {}, key + \`__2\`, node, ctx);
let b4 = component(\`ErrorComponent\`, {}, key + \`__4\`, node, ctx); let b4 = component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
return multi([b3, b4]); return multi([b3, b4]);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__4\`, node, ctx);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -23,15 +23,40 @@ exports[`event handling handler receive the event as argument 2`] = `
let block1 = createBlock(\`<span block-handler-0=\\"click\\"><block-child-0/><block-text-1/></span>\`); let block1 = createBlock(\`<span block-handler-0=\\"click\\"><block-child-0/><block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['inc']; let d1 = [ctx['inc'], ctx];
let d1 = [v1, ctx]; let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
let b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
let d2 = ctx['state'].value; let d2 = ctx['state'].value;
return block1([d1, d2], [b2]); return block1([d1, d2], [b2]);
} }
}" }"
`; `;
exports[`event handling objects from scope are properly captured by t-on 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div class=\\"item\\" block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item'];
const v1 = ctx['onClick'];
const v2 = ctx['item'];
let d1 = [ev=>v1(v2.val,ev), ctx];
c_block2[i1] = withKey(block3([d1]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
exports[`event handling support for callable expression in event handler 1`] = ` exports[`event handling support for callable expression in event handler 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -42,8 +67,7 @@ exports[`event handling support for callable expression in event handler 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].value; let d1 = ctx['state'].value;
const v1 = ctx['obj']; let d2 = [ctx['obj'].onInput, ctx];
let d2 = [v1.onInput, ctx];
return block1([d1, d2]); return block1([d1, d2]);
} }
}" }"
@@ -130,8 +130,7 @@ exports[`basics sub widget is interactive 1`] = `
let block1 = createBlock(\`<span><button block-handler-0=\\"click\\">click</button>child<block-text-1/></span>\`); let block1 = createBlock(\`<span><button block-handler-0=\\"click\\">click</button>child<block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['inc']; let d1 = [ctx['inc'], ctx];
let d1 = [v1, ctx];
let d2 = ctx['state'].val; let d2 = ctx['state'].val;
return block1([d1, d2]); return block1([d1, d2]);
} }
@@ -540,8 +540,7 @@ exports[`lifecycle hooks onWillRender 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['increment']; let d1 = [ctx['increment'], ctx];
let d1 = [v1, ctx];
let d2 = ctx['state'].value; let d2 = ctx['state'].value;
return block1([d1, d2]); return block1([d1, d2]);
} }
@@ -555,7 +554,7 @@ exports[`lifecycle hooks onWillRender 2`] = `
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx); return component(\`Child\`, {}, key + \`__1\`, node, ctx);
} }
}" }"
`; `;
@@ -30,6 +30,42 @@ exports[`basics accept ES6-like syntax for props (with getters) 2`] = `
}" }"
`; `;
exports[`basics arrow functions as prop correctly capture their scope 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['props'].onClick, ctx];
return block1([d1]);
}
}"
`;
exports[`basics arrow functions as prop correctly capture their scope 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['items']);
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1];
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 + \`__3__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
}
}"
`;
exports[`basics explicit object prop 1`] = ` exports[`basics explicit object prop 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -60,6 +96,60 @@ exports[`basics explicit object prop 2`] = `
}" }"
`; `;
exports[`basics prop names can contain - 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['props']['prop-name'];
return block1([d1]);
}
}"
`;
exports[`basics prop names can contain - 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {'prop-name': 7}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`basics support prop names that aren't valid bare object property names 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['props'].onClick, ctx];
return block1([d1]);
}
}"
`;
exports[`basics support prop names that aren't valid bare object property names 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {'some-dashed-prop': 5,'a.b': 'keyword prop'}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`basics t-set with a body expression can be passed in props, and then t-out 1`] = ` exports[`basics t-set with a body expression can be passed in props, and then t-out 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -161,3 +251,27 @@ exports[`basics t-set works 2`] = `
} }
}" }"
`; `;
exports[`basics template string in prop 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return text(\`\`);
}
}"
`;
exports[`basics template string in prop 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {propName: \`1\${ctx['someVal']}3\`}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -9,9 +9,9 @@ exports[`default props can set default required boolean values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {} const props1 = {}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -26,9 +26,9 @@ exports[`default props can set default values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {} const props1 = {}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -43,9 +43,9 @@ exports[`default props default values are also set whenever component is updated
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['state'].p} const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -60,9 +60,9 @@ exports[`props validation can validate a prop with multiple types 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -77,9 +77,9 @@ exports[`props validation can validate a prop with multiple types 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -94,9 +94,9 @@ exports[`props validation can validate an array with given primitive type 1`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -111,9 +111,9 @@ exports[`props validation can validate an array with given primitive type 2`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -128,9 +128,9 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -145,9 +145,9 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -162,9 +162,9 @@ exports[`props validation can validate an array with multiple sub element types
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -179,9 +179,9 @@ exports[`props validation can validate an object with simple shape 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -196,9 +196,9 @@ exports[`props validation can validate an optional props 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -213,9 +213,9 @@ exports[`props validation can validate an optional props 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -230,9 +230,9 @@ exports[`props validation can validate recursively complicated prop def 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -247,9 +247,9 @@ exports[`props validation can validate recursively complicated prop def 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -264,9 +264,9 @@ exports[`props validation default values are applied before validating props at
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['state'].p} const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -281,9 +281,9 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {message: 1} const props1 = {message: 1}
helpers.validateProps(\`Child\`, props2, ctx) helpers.validateProps(\`Child\`, props1, ctx)
let b2 = component(\`Child\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`Child\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -298,9 +298,9 @@ exports[`props validation props are validated whenever component is updated 1`]
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['state'].p} const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -315,9 +315,9 @@ exports[`props validation validate simple types 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -332,9 +332,9 @@ exports[`props validation validate simple types 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -349,9 +349,9 @@ exports[`props validation validate simple types 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -366,9 +366,9 @@ exports[`props validation validate simple types 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -383,9 +383,9 @@ exports[`props validation validate simple types 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -400,9 +400,9 @@ exports[`props validation validate simple types 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -417,9 +417,9 @@ exports[`props validation validate simple types, alternate form 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -434,9 +434,9 @@ exports[`props validation validate simple types, alternate form 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -451,9 +451,9 @@ exports[`props validation validate simple types, alternate form 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -468,9 +468,9 @@ exports[`props validation validate simple types, alternate form 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -485,9 +485,9 @@ exports[`props validation validate simple types, alternate form 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -502,9 +502,9 @@ exports[`props validation validate simple types, alternate form 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props2 = {p: ctx['p']} const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx) helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx); let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -25,7 +25,7 @@ exports[`refs refs are properly bound in slots 1`] = `
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'footer'); let b2 = callSlot(ctx, node, key, 'footer', false, {});
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -36,15 +36,13 @@ exports[`refs refs are properly bound in slots 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
let block2 = createBlock(\`<button block-handler-0=\\"click\\" block-ref=\\"1\\">do something</button>\`); let block2 = createBlock(\`<button block-handler-0=\\"click\\" block-ref=\\"1\\">do something</button>\`);
const slot3 = ctx => (node, key) => { function slot2(ctx, node, key) {
const refs = ctx.__owl__.refs const refs = ctx.__owl__.refs
const v4 = ctx['doSomething']; let d2 = [ctx['doSomething'], ctx];
let d2 = [v4, ctx];
let d3 = (el) => refs[\`myButton\`] = el; let d3 = (el) => refs[\`myButton\`] = el;
return block2([d2, d3]); return block2([d2, d3]);
} }
@@ -52,8 +50,8 @@ exports[`refs refs are properly bound in slots 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
let d1 = ctx['state'].val; let d1 = ctx['state'].val;
const ctx2 = capture(ctx); const ctx1 = capture(ctx);
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'footer': slot3(ctx2)}}); let b3 = component(\`Dialog\`, {slots: {'footer': {__render: slot2, __ctx: ctx1}}}, key + \`__3\`, node, ctx);
return block1([d1], [b3]); return block1([d1], [b3]);
} }
}" }"
@@ -1,77 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rendering semantics blabla 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].a.b);
}
}"
`;
exports[`rendering semantics blabla 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {a: ctx['state']}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`rendering semantics can force a render to update sub tree 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
exports[`rendering semantics can force a render to update sub tree 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
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 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
exports[`rendering semantics can render a parent without rendering child 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].value);
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return multi([b2, b3]);
}
}"
`;
File diff suppressed because it is too large Load Diff
@@ -52,8 +52,7 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`); let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['update']; let d1 = [ctx['update'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -39,6 +39,61 @@ exports[`list of components components in a node in a t-foreach 2`] = `
}" }"
`; `;
exports[`list of components crash on duplicate key in dev mode 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1,2]);
const keys1 = new Set();
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1];
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)
c_block1[i1] = withKey(component(\`Child\`, props1, key + \`__2__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
}
}"
`;
exports[`list of components crash on duplicate key in dev mode 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return text(\`\`);
}
}"
`;
exports[`list of components crash on duplicate key in dev mode 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1,2]);
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1];
let key1 = 'child';
c_block1[i1] = withKey(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
}
}"
`;
exports[`list of components list of sub components inside other nodes 1`] = ` exports[`list of components list of sub components inside other nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
@@ -117,11 +117,10 @@ exports[`t-model directive can also define t-on directive on same event, part 1
let block1 = createBlock(\`<div><input block-handler-0=\\"input\\" block-attribute-1=\\"value\\" block-handler-2=\\"input\\"/></div>\`); let block1 = createBlock(\`<div><input block-handler-0=\\"input\\" block-attribute-1=\\"value\\" block-handler-2=\\"input\\"/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onInput']; let d1 = [ctx['onInput'], ctx];
let d1 = [v1, ctx]; const bExpr1 = ctx['state'];
const bExpr2 = ctx['state'];
let d2 = ctx['state']['text']; let d2 = ctx['state']['text'];
let d3 = [(ev) => { bExpr2['text'] = ev.target.value; }]; let d3 = [(ev) => { bExpr1['text'] = ev.target.value; }];
return block1([d1, d2, d3]); return block1([d1, d2, d3]);
} }
}" }"
@@ -136,21 +135,18 @@ exports[`t-model directive can also define t-on directive on same event, part 2
let block1 = createBlock(\`<div><input type=\\"radio\\" id=\\"one\\" value=\\"One\\" block-handler-0=\\"click\\" block-attribute-1=\\"checked\\" block-handler-2=\\"click\\"/><input type=\\"radio\\" id=\\"two\\" value=\\"Two\\" block-handler-3=\\"click\\" block-attribute-4=\\"checked\\" block-handler-5=\\"click\\"/><input type=\\"radio\\" id=\\"three\\" value=\\"Three\\" block-handler-6=\\"click\\" block-attribute-7=\\"checked\\" block-handler-8=\\"click\\"/></div>\`); let block1 = createBlock(\`<div><input type=\\"radio\\" id=\\"one\\" value=\\"One\\" block-handler-0=\\"click\\" block-attribute-1=\\"checked\\" block-handler-2=\\"click\\"/><input type=\\"radio\\" id=\\"two\\" value=\\"Two\\" block-handler-3=\\"click\\" block-attribute-4=\\"checked\\" block-handler-5=\\"click\\"/><input type=\\"radio\\" id=\\"three\\" value=\\"Three\\" block-handler-6=\\"click\\" block-attribute-7=\\"checked\\" block-handler-8=\\"click\\"/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [ctx['onClick'], ctx];
let d1 = [v1, ctx]; const bExpr1 = ctx['state'];
const bExpr2 = ctx['state'];
let d2 = ctx['state']['choice'] === 'One'; let d2 = ctx['state']['choice'] === 'One';
let d3 = [(ev) => { bExpr2['choice'] = ev.target.value; }]; let d3 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
const v3 = ctx['onClick']; let d4 = [ctx['onClick'], ctx];
let d4 = [v3, ctx]; const bExpr2 = ctx['state'];
const bExpr4 = ctx['state'];
let d5 = ctx['state']['choice'] === 'Two'; let d5 = ctx['state']['choice'] === 'Two';
let d6 = [(ev) => { bExpr4['choice'] = ev.target.value; }]; let d6 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
const v5 = ctx['onClick']; let d7 = [ctx['onClick'], ctx];
let d7 = [v5, ctx]; const bExpr3 = ctx['state'];
const bExpr6 = ctx['state'];
let d8 = ctx['state']['choice'] === 'Three'; let d8 = ctx['state']['choice'] === 'Three';
let d9 = [(ev) => { bExpr6['choice'] = ev.target.value; }]; let d9 = [(ev) => { bExpr3['choice'] = ev.target.value; }];
return block1([d1, d2, d3, d4, d5, d6, d7, d8, d9]); return block1([d1, d2, d3, d4, d5, d6, d7, d8, d9]);
} }
}" }"
@@ -130,8 +130,7 @@ exports[`t-on t-on on destroyed components 1`] = `
let block1 = createBlock(\`<div block-handler-0=\\"click\\"/>\`); let block1 = createBlock(\`<div block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick']; let d1 = [ctx['onClick'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -24,7 +24,7 @@ exports[`t-props basic use 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, Object.assign({}, ctx['some'].obj), key + \`__1\`, node, ctx); let b2 = component(\`Child\`, ctx['some'].obj, key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -85,7 +85,7 @@ exports[`t-props t-props only 2`] = `
let block1 = createBlock(\`<div><div><block-child-0/></div></div>\`); let block1 = createBlock(\`<div><div><block-child-0/></div></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Comp\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx); let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -24,11 +24,10 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`); let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`);
const slot3 = ctx => (node, key) => { function slot2(ctx, node, key) {
setContextValue(ctx, \\"iter\\", 'inCall'); setContextValue(ctx, \\"iter\\", 'inCall');
} }
@@ -37,8 +36,8 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 2`] = `
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"iter\\", 'source'); setContextValue(ctx, \\"iter\\", 'source');
let d1 = ctx['iter']; let d1 = ctx['iter'];
const ctx2 = capture(ctx); const ctx1 = capture(ctx);
let b2 = assign(component(\`Childcomp\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}}); let b2 = component(\`Childcomp\`, {slots: {'default': {__render: slot2, __ctx: ctx1}}}, key + \`__3\`, node, ctx);
let d2 = ctx['iter']; let d2 = ctx['iter'];
return block1([d1, d2], [b2]); return block1([d1, d2], [b2]);
} }
+1 -1
View File
@@ -419,7 +419,7 @@ describe("basics", () => {
expect(fixture.innerHTML).toBe("<div><span></span></div>"); expect(fixture.innerHTML).toBe("<div><span></span></div>");
}); });
test.only("child can be updated", async () => { test("child can be updated", async () => {
class Child extends Component { class Child extends Component {
static template = xml`<t t-esc="props.value"/>`; static template = xml`<t t-esc="props.value"/>`;
} }
+59 -1
View File
@@ -543,7 +543,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
} }
class Child extends Component { class Child extends Component {
static template = xml`<div><SubChild val="props.val"/></div>`; static template = xml`<div><SubChild /></div>`;
static components = { SubChild }; static components = { SubChild };
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle(steps);
@@ -1900,13 +1900,18 @@ test("concurrent renderings scenario 13", async () => {
"Child:willPatch", "Child:willPatch",
"Child:patched", "Child:patched",
"Parent:willRender", "Parent:willRender",
"Child:willUpdateProps",
"Child:setup", "Child:setup",
"Child:willStart", "Child:willStart",
"Parent:rendered", "Parent:rendered",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
"Child:willRender",
"Child:rendered",
"Parent:willPatch", "Parent:willPatch",
"Child:willPatch",
"Child:mounted", "Child:mounted",
"Child:patched",
"Parent:patched", "Parent:patched",
"Child:willRender", "Child:willRender",
"Child:rendered", "Child:rendered",
@@ -2557,6 +2562,59 @@ test("two renderings initiated between willPatch and patched", async () => {
Object.freeze(steps); Object.freeze(steps);
}); });
test("parent and child rendered at exact same time", async () => {
let child: any = null;
let steps: any[] = [];
class Child extends Component {
static template = xml`<t t-esc="props.value"/>`;
setup() {
child = this;
useLogLifecycle(steps);
}
}
class Parent extends Component {
static template = xml`<Child value="state.value"/>`;
static components = { Child };
state = { value: 0 };
setup() {
useLogLifecycle(steps);
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("0");
parent.state.value = 1;
parent.render();
child.render();
await nextTick();
expect(fixture.innerHTML).toBe("1");
expect(steps).toEqual([
"Parent:setup",
"Parent:willStart",
"Parent:willRender",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:mounted",
"Parent:mounted",
"Parent:willRender",
"Child:willUpdateProps",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Parent:willPatch",
"Child:willPatch",
"Child:patched",
"Parent:patched",
]);
});
// test.skip("components with shouldUpdate=false", async () => { // test.skip("components with shouldUpdate=false", async () => {
// const state = { p: 1, cc: 10 }; // const state = { p: 1, cc: 10 };
+21
View File
@@ -72,4 +72,25 @@ describe("event handling", () => {
expect(onClickArgs![0]).toBe(1); expect(onClickArgs![0]).toBe(1);
expect(onClickArgs![1]).toBeInstanceOf(MouseEvent); expect(onClickArgs![1]).toBeInstanceOf(MouseEvent);
}); });
test("objects from scope are properly captured by t-on", async () => {
let onClickArgs: [number, MouseEvent] | null = null;
class Parent extends Component {
static template = xml`
<div>
<t t-foreach="items" t-as="item" t-key="item">
<div class="item" t-on-click="ev => onClick(item.val, ev)"/>
</t>
</div>`;
items = [{ val: 1 }, { val: 2 }, { val: 3 }, { val: 4 }];
onClick(n: number, ev: MouseEvent) {
onClickArgs = [n, ev];
}
}
await mount(Parent, fixture);
expect(onClickArgs).toBeNull();
(<HTMLElement>fixture.querySelector(".item")).click();
expect(onClickArgs![0]).toBe(1);
expect(onClickArgs![1]).toBeInstanceOf(MouseEvent);
});
}); });
+7 -4
View File
@@ -899,9 +899,8 @@ describe("lifecycle hooks", () => {
class Parent extends Component { class Parent extends Component {
static template = xml` static template = xml`
<Child someValue="state.value" />`; <Child />`;
static components = { Child }; static components = { Child };
state = useState({ value: 1 });
setup() { setup() {
useLogLifecycle(steps); useLogLifecycle(steps);
} }
@@ -911,7 +910,7 @@ describe("lifecycle hooks", () => {
expect(fixture.innerHTML).toBe("<button>1</button>"); expect(fixture.innerHTML).toBe("<button>1</button>");
parent.state.value++; // to block child render parent.render(); // to block child render
await nextTick(); await nextTick();
fixture.querySelector("button")!.click(); fixture.querySelector("button")!.click();
@@ -1064,18 +1063,22 @@ describe("lifecycle hooks", () => {
steps.splice(0); steps.splice(0);
c!.state.flag = false; c!.state.flag = false;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>F</div></div></div>`);
expect(steps).toEqual([ expect(steps).toEqual([
"C:willRender", "C:willRender",
"D:willUpdateProps",
"F:setup", "F:setup",
"F:willStart", "F:willStart",
"C:rendered", "C:rendered",
"D:willRender",
"D:rendered",
"F:willRender", "F:willRender",
"F:rendered", "F:rendered",
"C:willPatch", "C:willPatch",
"D:willPatch",
"E:willUnmount", "E:willUnmount",
"E:destroyed", "E:destroyed",
"F:mounted", "F:mounted",
"D:patched",
"C:patched", "C:patched",
]); ]);
}); });
+73
View File
@@ -30,6 +30,20 @@ describe("basics", () => {
expect(fixture.innerHTML).toBe("<div><span>42</span></div>"); expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
}); });
test("prop names can contain -", async () => {
class Child extends Component {
static template = xml`<div><t t-esc="props['prop-name']"/></div>`;
}
class Parent extends Component {
static template = xml`<Child prop-name="7"/>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div>7</div>");
});
test("accept ES6-like syntax for props (with getters)", async () => { test("accept ES6-like syntax for props (with getters)", async () => {
class Child extends Component { class Child extends Component {
static template = xml`<span><t t-esc="props.greetings"/></span>`; static template = xml`<span><t t-esc="props.greetings"/></span>`;
@@ -102,4 +116,63 @@ describe("basics", () => {
await mount(Parent, fixture); await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><span>&lt;p&gt;43&lt;/p&gt;<p>43</p></span></div>"); expect(fixture.innerHTML).toBe("<div><span>&lt;p&gt;43&lt;/p&gt;<p>43</p></span></div>");
}); });
test("arrow functions as prop correctly capture their scope", async () => {
class Child extends Component {
static template = xml`<button t-on-click="props.onClick"/>`;
}
let onClickArgs: [number, MouseEvent] | null = null;
class Parent extends Component {
static template = xml`
<t t-foreach="items" t-as="item" t-key="item.val">
<Child onClick="ev => onClick(item.val, ev)"/>
</t>
`;
static components = { Child };
items = [{ val: 1 }, { val: 2 }, { val: 3 }, { val: 4 }];
onClick(n: number, ev: MouseEvent) {
onClickArgs = [n, ev];
}
}
await mount(Parent, fixture);
expect(onClickArgs).toBeNull();
(<HTMLElement>fixture.querySelector("button")).click();
expect(onClickArgs![0]).toBe(1);
expect(onClickArgs![1]).toBeInstanceOf(MouseEvent);
});
test("support prop names that aren't valid bare object property names", async () => {
expect.assertions(4);
class Child extends Component {
static template = xml`<button t-on-click="props.onClick"/>`;
setup() {
expect(this.props["some-dashed-prop"]).toBe(5);
expect(this.props["a.b"]).toBe("keyword prop");
}
}
class Parent extends Component {
static template = xml`<Child some-dashed-prop="5" a.b="'keyword prop'"/>`;
static components = { Child };
}
await mount(Parent, fixture);
});
test("template string in prop", async () => {
expect.assertions(3);
class Child extends Component {
static template = xml``;
setup() {
expect(this.props.propName).toBe("123");
}
}
class Parent extends Component {
static template = xml({ raw: ['<Child propName="`1${someVal}3`"/>'] });
static components = { Child };
someVal = 2;
}
await mount(Parent, fixture);
});
}); });
-110
View File
@@ -1,110 +0,0 @@
import { Component, mount, onRendered, useState } from "../../src";
import { xml } from "../../src/tags";
import { makeTestFixture, snapshotEverything, nextTick } from "../helpers";
let fixture: HTMLElement;
snapshotEverything();
beforeEach(() => {
fixture = makeTestFixture();
});
describe("rendering semantics", () => {
test("can render a parent without rendering child", 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 = useState({ 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";
await nextTick();
expect(fixture.innerHTML).toBe("Bchild");
expect(parentN).toBe(2);
expect(childN).toBe(1);
});
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("blabla", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.a.b"/>`;
}
class Parent extends Component {
static template = xml`
<Child a="state"/>
`;
static components = { Child };
state = useState({ b: 1 });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("1");
parent.state.b = 3;
await nextTick();
expect(fixture.innerHTML).toBe("3");
});
});
+175 -1
View File
@@ -37,6 +37,59 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some text"); expect(fixture.innerHTML).toBe("some text");
}); });
test("simple default slot with params", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
state = useState({ bool: true });
setup() {
child = this;
}
}
class Parent extends Component {
static template = xml`
<Child>
<t t-set-slot="default" t-slot-scope="slotScope">
<t t-if="slotScope.bool">some text</t>
<t t-else="slotScope.bool">other text</t>
</t>
</Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span>some text</span>");
child.state.bool = false;
await nextTick();
expect(fixture.innerHTML).toBe("<span>other text</span>");
});
test("simple default slot with params", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
state = useState({ bool: true });
}
class Parent extends Component {
static template = xml`
<Child>
<t t-if="slotScope.bool">some text</t>
<t t-else="slotScope.bool">other text</t>
</Child>`;
static components = { Child };
}
let error = null;
try {
await mount(Parent, fixture);
} catch (e) {
error = e;
}
expect(error).not.toBeNull();
});
test("fun: two calls to the same slot", async () => { test("fun: two calls to the same slot", async () => {
class Child extends Component { class Child extends Component {
static template = xml`<t t-slot="default"/><t t-slot="default"/>`; static template = xml`<t t-slot="default"/><t t-slot="default"/>`;
@@ -71,6 +124,33 @@ describe("slots", () => {
expect(parent.state.value).toBe(1); expect(parent.state.value).toBe(1);
}); });
test("slot content is bound to caller (variation)", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
class Parent extends Component {
// t-set t-value in template is to force compiler to protect the scope
// which in turns means that the ctx propagated to the slot is a sub object
static template = xml`
<Child>
<t t-set="var" t-value="1"/>
<button t-on-click="() => this.inc()">some text</button>
</Child>`;
static components = { Child };
state = useState({ value: 0 });
inc() {
expect(this).toBe(parent);
this.state.value++;
}
}
const parent = await mount(Parent, fixture);
expect(parent.state.value).toBe(0);
fixture.querySelector("button")!.click();
expect(parent.state.value).toBe(1);
});
test("can define and call slots", async () => { test("can define and call slots", async () => {
class Dialog extends Component { class Dialog extends Component {
static template = xml` static template = xml`
@@ -97,6 +177,35 @@ describe("slots", () => {
); );
}); });
test("can define and call slots with params", async () => {
class Dialog extends Component {
static template = xml`
<div>
<t t-esc="props.slots['header'].param"/>
<div><t t-slot="header"/></div>
<t t-esc="props.slots['footer'].param"/>
<div><t t-slot="footer"/></div>
</div>`;
}
class Parent extends Component {
static components = { Dialog };
static template = xml`
<div>
<Dialog>
<t t-set-slot="header" param="var"><span>header</span></t>
<t t-set-slot="footer" param="'5'"><span>footer</span></t>
</Dialog>
</div>`;
var = 3;
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(
"<div><div>3<div><span>header</span></div>5<div><span>footer</span></div></div></div>"
);
});
test("no named slot content => just no children", async () => { test("no named slot content => just no children", async () => {
class Dialog extends Component { class Dialog extends Component {
static template = xml`<span><t t-slot="header"/></span>`; static template = xml`<span><t t-slot="header"/></span>`;
@@ -126,7 +235,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>"); expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
}); });
test("dafault slots can define a default content", async () => { test("can define a default content", async () => {
class Dialog extends Component { class Dialog extends Component {
static template = xml` static template = xml`
<span> <span>
@@ -174,6 +283,42 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>"); expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
}); });
test("slots are properly bound to correct component", async () => {
let child: any = null;
class Child extends Component {
// t-set t-value in template is to force compiler to protect the scope
// which in turns means that the ctx propagated to the slot is a sub object
static template = xml`
<t t-slot="default">
<t t-set="var" t-value="1"/>
<button t-on-click="() => this.increment()">
<t t-esc="state.value"/>
</button>
</t>`;
state = useState({ value: 1 });
setup() {
child = this;
}
increment() {
expect(this).toBe(child);
this.state.value++;
}
}
class Parent extends Component {
static template = xml`<Child/>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<button>1</button>");
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe("<button>2</button>");
});
test("slots are rendered with proper context", async () => { test("slots are rendered with proper context", async () => {
class Dialog extends Component { class Dialog extends Component {
static template = xml`<span><t t-slot="footer"/></span>`; static template = xml`<span><t t-slot="footer"/></span>`;
@@ -1352,4 +1497,33 @@ describe("slots", () => {
} }
expect(error).toBeNull(); expect(error).toBeNull();
}); });
test("can use t-call in default-content of t-slot", async () => {
const template = xml``;
class Child extends Component {
static template = xml`<t t-slot="default"><t t-call="${template}"/></t>`;
}
class Parent extends Component {
static template = xml`<Child/>`;
static components = { Child };
}
await mount(Parent, fixture);
});
test("can use component in default-content of t-slot", async () => {
class GrandChild extends Component {
static template = xml``;
}
class Child extends Component {
static template = xml`<t t-slot="default"><GrandChild/></t>`;
static components = { GrandChild };
}
class Parent extends Component {
static template = xml`<Child/>`;
static components = { Child };
}
await mount(Parent, fixture);
});
}); });
+32 -2
View File
@@ -1,5 +1,11 @@
import { Component, mount, onMounted, useState, xml } from "../../src/index"; import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers"; import {
makeTestFixture,
nextTick,
snapshotApp,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
snapshotEverything(); snapshotEverything();
@@ -291,4 +297,28 @@ describe("list of components", () => {
expect((parent.el as HTMLElement).innerHTML).toBe("<div>2</div><div>1</div>"); expect((parent.el as HTMLElement).innerHTML).toBe("<div>2</div><div>1</div>");
expect(childInstances.length).toBe(2); expect(childInstances.length).toBe(2);
}); });
test("crash on duplicate key in dev mode", async () => {
const consoleInfo = console.info;
console.info = jest.fn();
class Child extends Component {
static template = xml``;
}
class Parent extends Component {
static template = xml`
<t t-foreach="[1, 2]" t-as="item" t-key="'child'">
<Child/>
</t>
`;
static components = { Child };
}
const app = new App(Parent);
app.configure({ dev: true });
await expect(async () => {
await app.mount(fixture);
}).rejects.toThrowError("Got duplicate key in t-foreach: child");
snapshotApp(app);
console.info = consoleInfo;
});
}); });
+1 -1
View File
@@ -65,7 +65,7 @@ describe("t-props", () => {
`; `;
setup() { setup() {
expect(this.props).toEqual({ a: 1, b: 2 }); expect(this.props).toEqual({ a: 1, b: 2 });
expect(this.props).not.toBe(props); expect(this.props).toBe(props);
} }
} }
class Parent extends Component { class Parent extends Component {
+7 -10
View File
@@ -17,9 +17,8 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b6 = text(ctx['state'].a); let b6 = text(ctx['state'].a);
let b7 = text(ctx['state'].b); let b7 = text(ctx['state'].b);
let b8 = text(ctx['state'].c); let b8 = text(ctx['state'].c);
@@ -30,7 +29,7 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
let b2 = text(ctx['state'].a); let b2 = text(ctx['state'].a);
let b3 = text(ctx['state'].b); let b3 = text(ctx['state'].b);
let b4 = text(ctx['state'].c); let b4 = text(ctx['state'].c);
let b9 = assign(component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b9 = component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return multi([b2, b3, b4, b9]); return multi([b2, b3, b4, b9]);
} }
}" }"
@@ -53,15 +52,14 @@ exports[`Memo if no props, prevent renderings from above 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
const slot3 = ctx => (node, key) => { function slot2(ctx, node, key) {
return component(\`Child\`, {value: ctx['state'].value}, key + \`__4\`, node, ctx); return component(\`Child\`, {value: ctx['state'].value}, key + \`__3\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx); let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
let b4 = assign(component(\`Memo\`, {}, key + \`__2\`, node, ctx, true), {slots: {'default': slot3(ctx)}}); let b4 = component(\`Memo\`, {slots: {'default': {__render: slot2, __ctx: ctx}}}, key + \`__4\`, node, ctx);
return multi([b2, b4]); return multi([b2, b4]);
} }
}" }"
@@ -72,15 +70,14 @@ exports[`Memo if no props, prevent renderings from above (work with simple html)
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return text(ctx['state'].value); return text(ctx['state'].value);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].value); let b2 = text(ctx['state'].value);
let b4 = assign(component(\`Memo\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b4 = component(\`Memo\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return multi([b2, b4]); return multi([b2, b4]);
} }
}" }"
+49 -70
View File
@@ -9,8 +9,7 @@ exports[`Portal Portal composed with t-slot 1`] = `
let block1 = createBlock(\`<div block-handler-0=\\"custom\\"><span id=\\"childSpan\\">child2</span></div>\`); let block1 = createBlock(\`<div block-handler-0=\\"custom\\"><span id=\\"childSpan\\">child2</span></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onCustom']; let d1 = [ctx['onCustom'], ctx];
let d1 = [v1, ctx];
return block1([d1]); return block1([d1]);
} }
}" }"
@@ -21,14 +20,13 @@ exports[`Portal Portal composed with t-slot 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return callSlot(ctx, node, key, 'default'); return callSlot(ctx, node, key, 'default', false, {});
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); return component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
} }
}" }"
`; `;
@@ -38,16 +36,15 @@ exports[`Portal Portal composed with t-slot 3`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child2\`, {customHandler: ctx['_handled']}, key + \`__3\`, node, ctx); return component(\`Child2\`, {customHandler: ctx['_handled']}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -58,17 +55,16 @@ exports[`Portal basic use of portal 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -94,19 +90,18 @@ exports[`Portal conditional use of Portal (with sub Component) 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block2 = createBlock(\`<span>1</span>\`); let block2 = createBlock(\`<span>1</span>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b4; let b2,b4;
b2 = block2(); b2 = block2();
if (ctx['state'].hasPortal) { if (ctx['state'].hasPortal) {
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
} }
return multi([b2, b4]); return multi([b2, b4]);
} }
@@ -118,12 +113,11 @@ exports[`Portal conditional use of Portal 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block2 = createBlock(\`<span>1</span>\`); let block2 = createBlock(\`<span>1</span>\`);
let block3 = createBlock(\`<p>2</p>\`); let block3 = createBlock(\`<p>2</p>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block3(); return block3();
} }
@@ -131,7 +125,7 @@ exports[`Portal conditional use of Portal 1`] = `
let b2,b4; let b2,b4;
b2 = block2(); b2 = block2();
if (ctx['state'].hasPortal) { if (ctx['state'].hasPortal) {
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
} }
return multi([b2, b4]); return multi([b2, b4]);
} }
@@ -158,18 +152,17 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 2`]
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3; let b3;
if (ctx['state'].hasChild) { if (ctx['state'].hasChild) {
b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
} }
return block1([], [b3]); return block1([], [b3]);
} }
@@ -181,12 +174,11 @@ exports[`Portal portal could have dynamically no content 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`); let block3 = createBlock(\`<span><block-text-0/></span>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b3; let b3;
if (ctx['state'].val) { if (ctx['state'].val) {
let d1 = ctx['state'].val; let d1 = ctx['state'].val;
@@ -196,7 +188,7 @@ exports[`Portal portal could have dynamically no content 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b4]); return block1([], [b4]);
} }
}" }"
@@ -222,16 +214,15 @@ exports[`Portal portal destroys on crash 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child\`, {error: ctx['state'].error}, key + \`__3\`, node, ctx); return component(\`Child\`, {error: ctx['state'].error}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -257,16 +248,15 @@ exports[`Portal portal with child and props 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -277,13 +267,12 @@ exports[`Portal portal with dynamic body 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`); let block3 = createBlock(\`<span><block-text-0/></span>\`);
let block4 = createBlock(\`<div/>\`); let block4 = createBlock(\`<div/>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b3,b4; let b3,b4;
if (ctx['state'].val) { if (ctx['state'].val) {
let d1 = ctx['state'].val; let d1 = ctx['state'].val;
@@ -295,7 +284,7 @@ exports[`Portal portal with dynamic body 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -306,20 +295,19 @@ exports[`Portal portal with many children 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div>1</div>\`); let block3 = createBlock(\`<div>1</div>\`);
let block4 = createBlock(\`<p>2</p>\`); let block4 = createBlock(\`<p>2</p>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b3 = block3(); let b3 = block3();
let b4 = block4(); let b4 = block4();
return multi([b3, b4]); return multi([b3, b4]);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -330,11 +318,10 @@ exports[`Portal portal with no content 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
let b3; let b3;
if (false) { if (false) {
b3 = text('ABC'); b3 = text('ABC');
@@ -343,7 +330,7 @@ exports[`Portal portal with no content 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b4]); return block1([], [b4]);
} }
}" }"
@@ -354,16 +341,15 @@ exports[`Portal portal with only text as content 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return text('only text'); return text('only text');
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -374,17 +360,16 @@ exports[`Portal portal with target not in dom 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`); let block2 = createBlock(\`<div>2</div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#does-not-exist'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#does-not-exist',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -409,16 +394,15 @@ exports[`Portal portal's parent's env is not polluted 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child\`, {}, key + \`__3\`, node, ctx); return component(\`Child\`, {}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -429,17 +413,16 @@ exports[`Portal with target in template (after portal) 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`); let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -450,17 +433,16 @@ exports[`Portal with target in template (before portal) 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`); let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -471,17 +453,16 @@ exports[`Portal: Props validation target is mandatory 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`); let block2 = createBlock(\`<div>2</div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -492,17 +473,16 @@ exports[`Portal: Props validation target is not list 1`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`); let block2 = createBlock(\`<div>2</div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: ['body']}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: ['body'],slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -528,16 +508,15 @@ exports[`Portal: UI/UX focus is kept across re-renders 2`] = `
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => { function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}}); let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
+2 -3
View File
@@ -1,7 +1,6 @@
import { Component, mount, onWillRender, onWillStart, onWillUpdateProps, useState } from "../src"; import { Component, mount, onWillRender, onWillStart, onWillUpdateProps, useState } from "../src";
import { reactive } from "../src/reactivity"; import { batched, reactive } from "../src/reactivity";
import { xml } from "../src/tags"; import { xml } from "../src/tags";
import { batched } from "../src/utils";
import { import {
makeDeferred, makeDeferred,
makeTestFixture, makeTestFixture,
@@ -1510,7 +1509,7 @@ describe("Reactivity: useState", () => {
expect([...steps]).toEqual(["list"]); expect([...steps]).toEqual(["list"]);
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>"); expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>");
expect([...steps]).toEqual(["list"]); expect([...steps]).toEqual(["list", "quantity1"]);
steps.clear(); steps.clear();
secondQuantity.quantity = 2; secondQuantity.quantity = 2;
+1 -19
View File
@@ -1,5 +1,4 @@
import { batched, EventBus } from "../src/utils"; import { EventBus } from "../src/utils";
import { nextMicroTick } from "./helpers";
describe("event bus behaviour", () => { describe("event bus behaviour", () => {
test("can subscribe and be notified", () => { test("can subscribe and be notified", () => {
@@ -34,20 +33,3 @@ describe("event bus behaviour", () => {
bus.trigger("event", "hello world"); bus.trigger("event", "hello world");
}); });
}); });
describe("batched", () => {
test("callback is called only once after operations", async () => {
let n = 0;
let fn = batched(() => n++);
expect(n).toBe(0);
fn();
fn();
expect(n).toBe(0);
await nextMicroTick();
expect(n).toBe(1);
await nextMicroTick();
expect(n).toBe(1);
});
});
-301
View File
@@ -1,301 +0,0 @@
import {
buildData,
startMeasure,
stopMeasure,
formatNumber
} from "../shared/utils.js";
odoo.define("app", function(require) {
const Widget = require("web.Widget");
var core = require("web.core");
var dom = require("web.dom");
//----------------------------------------------------------------------------
// Likes Counter Widget
//----------------------------------------------------------------------------
var Counter = Widget.extend({
template: "counter",
events: {
"click .o_increment": "_onIncrement"
},
init: function(parent) {
this._super(parent);
this.value = 0;
},
start: function() {
this.updateCounter();
},
on_attach_callback: function() {},
on_detach_callback: function() {},
updateCounter: function() {
this.$(".o_increment").html("Value: " + this.value);
},
_onIncrement: function(ev) {
ev.stopPropagation();
this.value += 1;
this.updateCounter();
}
});
//----------------------------------------------------------------------------
// Message Widget
//----------------------------------------------------------------------------
var Message = Widget.extend({
template: "message",
events: {
"click .remove": "_onRemove"
},
init: function(parent, message) {
this._super(parent);
this.author = message.author;
this.msg = message.msg;
this.id = message.id;
},
willStart: function() {
this.counter = new Counter(this);
return this.counter.appendTo($("<div>"));
},
start: function() {
this.$msg = this.$(".msg");
dom.append(this.$el, this.counter.$el, {
in_DOM: this.isInDom,
callbacks: [{ widget: this.counter }]
});
},
on_attach_callback: function() {
this.isInDom = true;
if (this.counter) {
this.counter.on_attach_callback();
}
},
on_detach_callback: function() {
this.isInDom = true;
if (this.counter) {
this.counter.on_detach_callback();
}
},
_onRemove: function() {
this.trigger_up("remove_message", { id: this.id });
},
update: function() {
this.msg += "!!!";
this.$msg.text(this.msg);
}
});
//----------------------------------------------------------------------------
// Root Widget
//----------------------------------------------------------------------------
var App = Widget.extend({
template: "root",
events: {
"click .o_btn_msg.100": function() {
this.addMessages(100);
},
"click .o_btn_msg.1000": function() {
this.addMessages(1000);
},
"click .o_btn_msg.10000": function() {
this.addMessages(10000);
},
"click .o_btn_msg.30000": function() {
this.addMessages(30000);
},
"click .updateSomeMessages": function() {
this.updateSomeMessages();
},
"click .clear": function() {
this.clear();
},
"click .o_multiple": function() {
this.multipleFlag = !this.multipleFlag;
},
"click .o_clear": function() {
this.clearFlag = !this.clearFlag;
},
"click .clear-log": function() {
this.$log[0].innerHTML = "";
}
},
custom_events: {
remove_message: "_onRemoveMessage"
},
init: function(parent) {
this._super(parent);
this.widgets = {};
this.isInDom = false;
this.messageCount = 0;
this.multipleFlag = false;
this.clearFlag = false;
},
start: function() {
this.$content = this.$(".content");
this.$msgCount = this.$(".message_count");
this.$log = this.$(".log-content");
this.log("Benchmarking odoo widgets, 12.0");
},
on_attach_callback: function() {
this.isInDom = true;
for (let widget of Object.values(this.widgets)) {
if (widget.on_attach_callback) {
widget.on_attach_callback();
}
}
},
on_detach_callback: function() {
this.isInDom = true;
for (let widget of Object.values(this.widgets)) {
if (widget.on_detach_callback) {
widget.on_detach_callback();
}
}
},
updateMessageCount() {
this.$msgCount.text("Number of msg: " + this.messageCount);
},
addMessages: function(n) {
const self = this;
this.benchmark("add " + n, () => {
const defs = [];
const messages = buildData(n);
for (let message of messages) {
const widget = new Message(this, message);
this.widgets[message.id] = widget;
defs.push(widget.appendTo("<div>"));
}
return $.when
.apply($, defs)
.then(function() {
for (let message of messages) {
let widget = self.widgets[message.id];
dom.append(self.$content, widget.$el, {
in_DOM: this.isInDom,
callbacks: [{ widget: widget }]
});
}
})
.then(function() {
self.messageCount += n;
self.updateMessageCount();
});
});
},
clear: function() {
startMeasure("clear");
this._clear();
stopMeasure(info => {
this.log(info.msg);
});
},
_clear: function() {
this.$content.empty();
for (let key in this.widgets) {
this.widgets[key].destroy();
delete this.widgets[key];
}
this.messageCount = 0;
this.updateMessageCount();
},
updateSomeMessages: function() {
this.benchmark("update every 10th", () => {
const widgets = Object.values(this.widgets);
for (let i = 0; i < widgets.length; i += 10) {
widgets[i].update();
}
});
},
_onRemoveMessage: function(ev) {
startMeasure("remove message");
ev.target.destroy();
delete this.widgets[ev.data.id];
this.messageCount--;
this.updateMessageCount();
stopMeasure();
},
benchmark: function(message, fn, callback) {
if (this.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.clearFlag) {
this._benchmark("clear", this._clear.bind(this), finalize, false);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
},
_benchmark: function(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
const benchmark = fn();
(benchmark && benchmark.then ? benchmark : $.when()).then(() => {
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
});
},
log: function(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.$log[0].appendChild(div);
this.$log[0].scrollTop = this.$log[0].scrollHeight;
}
});
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
// prepare QWeb
const templates = await fetch("templates.xml");
let strTemplates = await templates.text();
strTemplates = strTemplates.replace(/<!--[\s\S]*?-->/g, "");
core.qweb.add_template(strTemplates);
// prepare app
const app = new App();
await app.appendTo(document.body);
}
start();
});
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Odoo Widget Benchmark (12.0)</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='web.assets_common.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
@@ -1,47 +0,0 @@
<templates>
<div class="main" t-name="root">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button class="o_btn_msg 100">Add 100 messages</button>
<button class="o_btn_msg 1000">Add 1k messages</button>
<button class="o_btn_msg 10000">Add 10k messages</button>
<button class="o_btn_msg 30000">Add 30k messages</button>
<button class="updateSomeMessages">Update every 10th message</button>
<button class="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" class="o_multiple" id="multipleflag" />
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" class="o_clear" id="clearFlag" />
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info message_count">
Number of messages: 0
</div>
<div class="title">Log <span class="clear-log">(clear)</span></div>
<div class="log">
<div class="log-content"/>
</div>
</div>
<div class="right-thing">
<div class="content">
</div>
</div>
</div>
<div t-name="message" class="message">
<span class="author"><t t-esc="widget.author"/></span>
<span class="msg"><t t-esc="widget.msg"/></span>
<button class="remove">Remove</button>
</div>
<div t-name="counter">
<button class="o_increment"></button>
</div>
</templates>
File diff suppressed because one or more lines are too long
-301
View File
@@ -1,301 +0,0 @@
import {
buildData,
startMeasure,
stopMeasure,
formatNumber
} from "../shared/utils.js";
odoo.define("app", function(require) {
const Widget = require("web.Widget");
var core = require("web.core");
var dom = require("web.dom");
//----------------------------------------------------------------------------
// Likes Counter Widget
//----------------------------------------------------------------------------
var Counter = Widget.extend({
template: "counter",
events: {
"click .o_increment": "_onIncrement"
},
init: function(parent) {
this._super(parent);
this.value = 0;
},
start: function() {
this.updateCounter();
},
on_attach_callback: function() {},
on_detach_callback: function() {},
updateCounter: function() {
this.$(".o_increment").html("Value: " + this.value);
},
_onIncrement: function(ev) {
ev.stopPropagation();
this.value += 1;
this.updateCounter();
}
});
//----------------------------------------------------------------------------
// Message Widget
//----------------------------------------------------------------------------
var Message = Widget.extend({
template: "message",
events: {
"click .remove": "_onRemove"
},
init: function(parent, message) {
this._super(parent);
this.author = message.author;
this.msg = message.msg;
this.id = message.id;
},
willStart: function() {
this.counter = new Counter(this);
return this.counter.appendTo($("<div>"));
},
start: function() {
this.$msg = this.$(".msg");
dom.append(this.$el, this.counter.$el, {
in_DOM: this.isInDom,
callbacks: [{ widget: this.counter }]
});
},
on_attach_callback: function() {
this.isInDom = true;
if (this.counter) {
this.counter.on_attach_callback();
}
},
on_detach_callback: function() {
this.isInDom = true;
if (this.counter) {
this.counter.on_detach_callback();
}
},
_onRemove: function() {
this.trigger_up("remove_message", { id: this.id });
},
update: function() {
this.msg += "!!!";
this.$msg.text(this.msg);
}
});
//----------------------------------------------------------------------------
// Root Widget
//----------------------------------------------------------------------------
var App = Widget.extend({
template: "root",
events: {
"click .o_btn_msg.100": function() {
this.addMessages(100);
},
"click .o_btn_msg.1000": function() {
this.addMessages(1000);
},
"click .o_btn_msg.10000": function() {
this.addMessages(10000);
},
"click .o_btn_msg.30000": function() {
this.addMessages(30000);
},
"click .updateSomeMessages": function() {
this.updateSomeMessages();
},
"click .clear": function() {
this.clear();
},
"click .o_multiple": function() {
this.multipleFlag = !this.multipleFlag;
},
"click .o_clear": function() {
this.clearFlag = !this.clearFlag;
},
"click .clear-log": function() {
this.$log[0].innerHTML = "";
}
},
custom_events: {
remove_message: "_onRemoveMessage"
},
init: function(parent) {
this._super(parent);
this.widgets = {};
this.isInDom = false;
this.messageCount = 0;
this.multipleFlag = false;
this.clearFlag = false;
},
start: function() {
this.$content = this.$(".content");
this.$msgCount = this.$(".message_count");
this.$log = this.$(".log-content");
this.log("Benchmarking odoo widgets, 12.3");
},
on_attach_callback: function() {
this.isInDom = true;
for (let widget of Object.values(this.widgets)) {
if (widget.on_attach_callback) {
widget.on_attach_callback();
}
}
},
on_detach_callback: function() {
this.isInDom = true;
for (let widget of Object.values(this.widgets)) {
if (widget.on_detach_callback) {
widget.on_detach_callback();
}
}
},
updateMessageCount() {
this.$msgCount.text("Number of msg: " + this.messageCount);
},
addMessages: function(n) {
const self = this;
this.benchmark("add " + n, () => {
const defs = [];
const messages = buildData(n);
for (let message of messages) {
const widget = new Message(this, message);
this.widgets[message.id] = widget;
defs.push(widget.appendTo("<div>"));
}
return $.when
.apply($, defs)
.then(function() {
for (let message of messages) {
let widget = self.widgets[message.id];
dom.append(self.$content, widget.$el, {
in_DOM: this.isInDom,
callbacks: [{ widget: widget }]
});
}
})
.then(function() {
self.messageCount += n;
self.updateMessageCount();
});
});
},
clear: function() {
startMeasure("clear");
this._clear();
stopMeasure(info => {
this.log(info.msg);
});
},
_clear: function() {
this.$content.empty();
for (let key in this.widgets) {
this.widgets[key].destroy();
delete this.widgets[key];
}
this.messageCount = 0;
this.updateMessageCount();
},
updateSomeMessages: function() {
this.benchmark("update every 10th", () => {
const widgets = Object.values(this.widgets);
for (let i = 0; i < widgets.length; i += 10) {
widgets[i].update();
}
});
},
_onRemoveMessage: function(ev) {
startMeasure("remove message");
ev.target.destroy();
delete this.widgets[ev.data.id];
this.messageCount--;
this.updateMessageCount();
stopMeasure();
},
benchmark: function(message, fn, callback) {
if (this.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.clearFlag) {
this._benchmark("clear", this._clear.bind(this), finalize, false);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
},
_benchmark: function(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
const benchmark = fn();
(benchmark && benchmark.then ? benchmark : $.when()).then(() => {
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
});
},
log: function(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.$log[0].appendChild(div);
this.$log[0].scrollTop = this.$log[0].scrollHeight;
}
});
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
// prepare QWeb
const templates = await fetch("templates.xml");
let strTemplates = await templates.text();
strTemplates = strTemplates.replace(/<!--[\s\S]*?-->/g, "");
core.qweb.add_template(strTemplates);
// prepare app
const app = new App();
await app.appendTo(document.body);
}
start();
});
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Odoo Widget Benchmark (12.3)</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='web.assets_common.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
@@ -1,47 +0,0 @@
<templates>
<div class="main" t-name="root">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button class="o_btn_msg 100">Add 100 messages</button>
<button class="o_btn_msg 1000">Add 1k messages</button>
<button class="o_btn_msg 10000">Add 10k messages</button>
<button class="o_btn_msg 30000">Add 30k messages</button>
<button class="updateSomeMessages">Update every 10th message</button>
<button class="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" class="o_multiple" id="multipleflag" />
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" class="o_clear" id="clearFlag" />
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info message_count">
Number of messages: 0
</div>
<div class="title">Log <span class="clear-log">(clear)</span></div>
<div class="log">
<div class="log-content"/>
</div>
</div>
<div class="right-thing">
<div class="content">
</div>
</div>
</div>
<div t-name="message" class="message">
<span class="author"><t t-esc="widget.author"/></span>
<span class="msg"><t t-esc="widget.msg"/></span>
<button class="remove">Remove</button>
</div>
<div t-name="counter">
<button class="o_increment"></button>
</div>
</templates>
File diff suppressed because one or more lines are too long
-170
View File
@@ -1,170 +0,0 @@
import {
buildData,
startMeasure,
stopMeasure,
formatNumber
} from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
widgets = { Counter };
shouldUpdate(nextProps) {
return nextProps !== this.props;
}
removeMessage() {
this.trigger("remove_message", {
id: this.props.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
widgets = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
mounted() {
this.log(
`Benchmarking Owl v${owl._version} (build date: ${
owl._date
})`
);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
this.set(messages, i, msg);
}
});
}
removeMessage(data) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === data.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.11.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="'log'"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.author"/></span>
<span class="msg"><t t-esc="props.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<t t-widget="Counter"/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-161
View File
@@ -1,161 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
widgets = { Counter };
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
widgets = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
this.set(messages, i, msg);
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.13.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<t t-widget="Message" t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<t t-widget="Counter"/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-161
View File
@@ -1,161 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
widgets = { Counter };
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
widgets = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
this.set(messages, i, msg);
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.15.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<t t-widget="Message" t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<t t-widget="Counter"/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-161
View File
@@ -1,161 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
components = { Counter };
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
components = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
owl.Observer.set(messages, i, msg);
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.17.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<Message t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<Counter/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-161
View File
@@ -1,161 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
components = { Counter };
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
components = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
messages[i] = msg;
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.18.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<Message t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<Counter/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-151
View File
@@ -1,151 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
Message.components = { Counter };
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
messages[i] = msg;
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
}
App.components = { Message };
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.21.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-model="multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-model="clearAfterFlag" />
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<Message t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<Counter/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-155
View File
@@ -1,155 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
const { useState, useRef } = owl.hooks;
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = useState({ counter: 0 });
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
static components = { Counter };
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
static components = { Message };
state = useState({ messages: [], multipleFlag: false, clearAfterFlag: false });
logRef = useRef("log");
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
messages[i] = msg;
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.logRef.el.appendChild(div);
this.logRef.el.scrollTop = this.logRef.el.scrollHeight;
}
clearLog() {
this.logRef.el.innerHTML = "";
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadFile("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL v0.24.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-model="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-model="state.clearAfterFlag" />
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<Message t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<Counter/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-173
View File
@@ -1,173 +0,0 @@
import {
buildData,
startMeasure,
stopMeasure,
formatNumber
} from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
template = "Counter";
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
widgets = { Counter };
template = "Message";
shouldUpdate(nextProps) {
return nextProps !== this.props;
}
removeMessage() {
this.trigger("remove_message", {
id: this.props.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
widgets = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
template = "App";
mounted() {
this.log(
`Benchmarking Owl v${owl._version} (build date: ${
owl._date
})`
);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
this.set(messages, i, msg);
}
});
}
removeMessage(data) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === data.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.7.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="'log'"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.author"/></span>
<span class="msg"><t t-esc="props.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<t t-widget="Counter"/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-173
View File
@@ -1,173 +0,0 @@
import {
buildData,
startMeasure,
stopMeasure,
formatNumber
} from "../shared/utils.js";
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = { counter: 0 };
template = "Counter";
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
widgets = { Counter };
template = "Message";
shouldUpdate(nextProps) {
return nextProps !== this.props;
}
removeMessage() {
this.trigger("remove_message", {
id: this.props.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
widgets = { Message };
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
template = "App";
mounted() {
this.log(
`Benchmarking Owl v${owl._version} (build date: ${
owl._date
})`
);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < this.state.messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
this.set(messages, i, msg);
}
});
}
removeMessage(data) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === data.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.refs.log.appendChild(div);
this.refs.log.scrollTop = this.refs.log.scrollHeight;
}
clearLog() {
this.refs.log.innerHTML = "";
}
toggleMultiple() {
this.state.multipleFlag = !this.state.multipleFlag;
}
toggleClear() {
this.state.clearAfterFlag = !this.state.clearAfterFlag;
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadTemplates("templates.xml");
const env = {
qweb: new owl.QWeb(templates)
};
const app = new App(env);
app.mount(document.body);
}
start();
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL 0.9.0 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<div id='main'></div>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="'log'"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.author"/></span>
<span class="msg"><t t-esc="props.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<t t-widget="Counter"/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-154
View File
@@ -1,154 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
const { useState, useRef } = owl.hooks;
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = useState({ counter: 0 });
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
Message.components = { Counter };
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
state = useState({ messages: [], multipleFlag: false, clearAfterFlag: false });
logRef = useRef("log");
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
messages[i] = msg;
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.logRef.el.appendChild(div);
this.logRef.el.scrollTop = this.logRef.el.scrollHeight;
}
clearLog() {
this.logRef.el.innerHTML = "";
}
}
App.components = { Message };
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadFile("templates.xml");
App.env = {
qweb: new owl.QWeb({ templates })
};
const app = new App();
app.mount(document.body);
}
start();
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL v1.0.0-beta1 Benchmark</title>
<link href="../shared/main.css" rel="stylesheet"/>
<script src='owl.js'></script>
</head>
<body>
<script src='app.js' type="module"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -1,50 +0,0 @@
<templates>
<div t-name="App" class="main">
<div class="left-thing">
<div class="title">Actions</div>
<div class="panel">
<button t-on-click="addMessages(100)">Add 100 messages</button>
<button t-on-click="addMessages(1000)">Add 1k messages</button>
<button t-on-click="addMessages(10000)">Add 10k messages</button>
<button t-on-click="addMessages(30000)">Add 30k messages</button>
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
<button t-on-click="clear">Clear</button>
</div>
<div class="flags">
<div>
<input type="checkbox" id="multipleflag" t-model="state.multipleFlag"/>
<label for="multipleflag">Do it 20x</label>
</div>
<div>
<input type="checkbox" id="clearFlag" t-model="state.clearAfterFlag" />
<label for="clearFlag">Clear after</label>
</div>
</div>
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
<hr/>
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
<div class="log">
<div class="log-content" t-ref="log"/>
</div>
</div>
<div class="right-thing">
<div class="content" t-on-remove-message="removeMessage">
<t t-foreach="state.messages" t-as="message">
<Message t-key="message.id" message="message"/>
</t>
</div>
</div>
</div>
<div t-name="Message" class="message">
<span class="author"><t t-esc="props.message.author"/></span>
<span class="msg"><t t-esc="props.message.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<Counter/>
</div>
<div t-name="Counter">
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
</div>
</templates>
-154
View File
@@ -1,154 +0,0 @@
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
const { useState, useRef } = owl.hooks;
//------------------------------------------------------------------------------
// Likes Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
state = useState({ counter: 0 });
increment() {
this.state.counter++;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
shouldUpdate(nextProps) {
return nextProps.message !== this.props.message;
}
removeMessage() {
this.trigger("remove-message", {
id: this.props.message.id
});
}
}
Message.components = { Counter };
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
state = useState({ messages: [], multipleFlag: false, clearAfterFlag: false });
logRef = useRef("log");
mounted() {
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
}
benchmark(message, fn, callback) {
if (this.state.multipleFlag) {
const N = 20;
let n = N;
let total = 0;
let cb = info => {
let finalize = () => {
n--;
total += info.delta;
if (n === 0) {
const avg = total / N;
this.log(`Average: ${formatNumber(avg)}ms`, true);
if (callback) {
callback();
}
} else {
this._benchmark(message, fn, cb);
}
};
if (this.state.clearAfterFlag) {
this._benchmark(
"clear",
() => {
this.state.messages = [];
},
finalize,
false
);
} else {
finalize();
}
};
this._benchmark(message, fn, cb);
} else {
this._benchmark(message, fn, callback);
}
}
_benchmark(message, fn, cb, log = true) {
setTimeout(() => {
startMeasure(message);
fn();
stopMeasure(info => {
if (log) {
this.log(info.msg);
}
if (cb) {
cb(info);
}
});
}, 10);
}
addMessages(n) {
this.benchmark("add " + n, () => {
const newMessages = buildData(n);
this.state.messages.push.apply(this.state.messages, newMessages);
});
}
clear() {
this._benchmark("clear", () => {
this.state.messages = [];
});
}
updateSomeMessages() {
this.benchmark("update every 10th", () => {
const messages = this.state.messages;
for (let i = 0; i < messages.length; i += 10) {
const msg = Object.assign({}, messages[i]);
msg.author += "!!!";
messages[i] = msg;
}
});
}
removeMessage(event) {
this.benchmark("remove message", () => {
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
this.state.messages.splice(index, 1);
});
}
log(str, isBold) {
const div = document.createElement("div");
if (isBold) {
div.classList.add("bold");
}
div.textContent = `> ${str}`;
this.logRef.el.appendChild(div);
this.logRef.el.scrollTop = this.logRef.el.scrollHeight;
}
clearLog() {
this.logRef.el.innerHTML = "";
}
}
App.components = { Message };
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
async function start() {
const templates = await owl.utils.loadFile("templates.xml");
App.env = {
qweb: new owl.QWeb({ templates })
};
const app = new App();
app.mount(document.body);
}
start();

Some files were not shown because too many files have changed in this diff Show More