Compare commits

..

6 Commits

Author SHA1 Message Date
Géry Debongnie b1e79677cb wip 2021-11-20 10:45:30 +01:00
Géry Debongnie 89d6b8b7dd wip 2021-11-20 10:25:37 +01:00
Géry Debongnie 9c2523e3ce wip 2021-11-20 10:25:37 +01:00
Géry Debongnie d8201b8955 add possibility to deep rendering 2021-11-20 10:25:37 +01:00
Géry Debongnie dfead2836e big change: shallow render
With this commit, component only render child
components if they have different props (shallow
equality). Otherwise, we trust the reactivity
system to make sure that all impacted components
are updated
2021-11-20 10:25:37 +01:00
Géry Debongnie 0e0ac5329a [REF] utils: move batched from reactivity to utils 2021-11-20 10:25:36 +01:00
121 changed files with 67217 additions and 2110 deletions
+2 -4
View File
@@ -447,10 +447,8 @@ bus.addEventListener('event-name', callback);
Rationale: it makes it easier to have just one interface to remember, it makes
the code simpler
Migration: most bus methods need to be adapted. So, `bus.on("event-type", owner, (info) => {...})` has to be
rewritten like this: `bus.addEventListener("event-type", (({detail: info}) => {...}).bind(owner))`.
Do not forget to similarly replace `bus.off(...)` by `bus.removeEventListener(...)`
Migration: most bus methods need to be adapted. So, `bus.on(...)` has to be
rewritten like this: `bus.addEventListener(...)`.
### 22. `Store` is removed
+43
View File
@@ -0,0 +1,43 @@
# 🦉 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,6 +9,7 @@ Are you new to Owl? This is the place to start!
- [How to start an Owl project](learning/quick_start.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 debug Owl applications](learning/how_to_debug.md)
## Reference
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.0-alpha1",
"version": "1.4.7",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
@@ -18,10 +18,10 @@
"test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch",
"test:watch": "jest --watch",
"playground:serve": "python3 tools/server.py || python tools/server.py",
"playground": "npm run build && npm run playground:serve",
"preplayground:watch": "npm run build",
"playground:watch": "npm-run-all --parallel playground:serve \"build:* -- --watch\"",
"tools:serve": "python3 tools/server.py || python tools/server.py",
"tools": "npm run build && npm run tools:serve",
"pretools:watch": "npm run build",
"tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\"",
"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",
"publish": "npm run build && npm publish",
@@ -52,7 +52,7 @@
"prettier": "2.4.1",
"rollup": "^2.56.3",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.1",
"rollup-plugin-typescript2": "^0.30.0",
"sass": "^1.16.1",
"source-map-support": "^0.5.10",
"ts-jest": "^27.0.5",
-4
View File
@@ -15,7 +15,6 @@ export interface AppConfig {
env?: Env;
translatableAttributes?: string[];
translateFn?: (s: string) => string;
templates?: string | Document;
}
export const DEV_MSG = `Owl is running in 'dev' mode.
@@ -50,9 +49,6 @@ export class App<T extends typeof Component = any> extends TemplateSet {
if (config.translatableAttributes) {
this.translatableAttributes = config.translatableAttributes;
}
if (config.templates) {
this.addTemplates(config.templates);
}
return this;
}
+7 -12
View File
@@ -17,24 +17,19 @@ function callSlot(
parent: any,
key: string,
name: string,
dynamic: boolean,
extra: any,
defaultContent?: (ctx: any, node: any, key: string) => BDom
defaultSlot?: (ctx: any, key: string) => BDom,
dynamic?: boolean
): BDom {
const slots = (ctx.props && ctx.props.slots) || {};
const { __render, __ctx, __scope } = slots[name] || {};
const slotScope = Object.create(__ctx || {});
if (__scope) {
slotScope[__scope] = extra || {};
}
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
if (defaultContent) {
const slots = ctx.__owl__.slots;
const slotFn = slots[name];
const slotBDom = slotFn ? slotFn(parent, key) : null;
if (defaultSlot) {
let child1: BDom | undefined = undefined;
let child2: BDom | undefined = undefined;
if (slotBDom) {
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
} else {
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
child2 = defaultSlot(parent, key);
}
return multi([child1, child2]);
}
+1 -35
View File
@@ -7,36 +7,6 @@ const bdom = { text, createBlock, list, multi, html, toggler, component };
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 {
rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
templates: { [name: string]: Template } = {};
@@ -63,11 +33,7 @@ export class TemplateSet {
}
addTemplates(xml: string | Document, options: { allowDuplicate?: boolean } = {}) {
if (!xml) {
// empty string
return;
}
xml = xml instanceof Document ? xml : parseXML(xml);
xml = xml instanceof Document ? xml : new DOMParser().parseFromString(xml, "text/xml");
for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name")!;
template.removeAttribute("t-name");
+54 -109
View File
@@ -137,6 +137,7 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
class CodeTarget {
name: string;
signature: string = "";
indentLevel = 0;
loopLevel = 0;
code: string[] = [];
@@ -333,7 +334,7 @@ export class CodeGenerator {
generateFunctions(fn: CodeTarget) {
this.addLine("");
this.addLine(`function ${fn.name}(ctx, node, key) {`);
this.addLine(`const ${fn.name} = ${fn.signature}`);
if (fn.hasCache) {
this.addLine(`let cache = ctx.cache || {};`);
this.addLine(`let nextCache = ctx.cache = {};`);
@@ -343,23 +344,8 @@ export class CodeGenerator {
}
this.addLine(`}`);
}
/**
* 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);
}
captureExpression(expr: string): string {
const tokens = compileExprToArray(expr);
const mapping = new Map<string, string>();
return tokens
@@ -549,7 +535,7 @@ export class CodeGenerator {
if (isDynamic) {
const str = ast.ref.replace(
INTERP_REGEXP,
(expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}"
(expr) => "${" + this.captureExpression(expr.slice(2, -2)) + "}"
);
const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`);
attrs["block-ref"] = String(idx);
@@ -748,10 +734,6 @@ export class CodeGenerator {
this.addLine(
`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.target.indentLevel++;
this.addLine(`ctx[\`${ast.elem}\`] = ${vals}[${loopVar}];`);
@@ -768,13 +750,6 @@ export class CodeGenerator {
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${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;
if (ast.memo) {
this.target.hasCache = true;
@@ -961,84 +936,24 @@ export class CodeGenerator {
compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx;
let extraArgs: { [key: string]: string } = {};
// props
const props: string[] = [];
let hasSlotsProp = false;
for (let p in ast.props) {
const propName = /^[a-z_]+$/i.test(p) ? p : `'${p}'`;
props.push(`${propName}: ${this.captureExpression(ast.props[p]) || undefined}`);
if (p === "slots") {
hasSlotsProp = true;
}
props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`);
}
// 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(",")}}`;
let propString = propStr;
if (ast.dynamicProps) {
if (!props.length) {
propString = `${compileExpr(ast.dynamicProps)}`;
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
} else {
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
}
}
let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = this.generateId("props");
this.addLine(`const ${propVar!} = ${propString}`);
propString = propVar!;
}
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
this.addLine(`${propVar!}.slots = Object.assign(${slotDef}, ${propVar!}.slots)`);
}
// cmap key
const key = this.generateComponentKey();
let expr: string;
@@ -1050,7 +965,41 @@ export class CodeGenerator {
}
if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
const propVar = this.generateId("props");
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)) {
@@ -1063,7 +1012,12 @@ export class CodeGenerator {
keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
}
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
let blockExpr = `component(${blockArgs})`;
let blockExpr = `component(${blockArgs}${hasSlot ? ", true" : ""})`;
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) {
blockExpr = `toggler(${expr}, ${blockExpr})`;
}
@@ -1082,33 +1036,24 @@ export class CodeGenerator {
} else {
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) {
let name = this.generateId("defaultContent");
let name = this.generateId("defaultSlot");
const slot = new CodeTarget(name);
slot.signature = "ctx => {";
this.functions.push(slot);
const initialTarget = this.target;
const subCtx: Context = createContext(ctx);
this.target = slot;
this.compileAST(ast.defaultContent, subCtx);
this.target = initialTarget;
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
blockString = `callSlot(ctx, node, key, ${slotName}, ${name}, ${dynamic})`;
} else {
if (dynamic) {
let name = this.generateId("slot");
this.addLine(`const ${name} = ${slotName};`);
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}))`;
} else {
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
blockString = `callSlot(ctx, node, key, ${slotName})`;
}
}
if (block) {
+37 -133
View File
@@ -118,13 +118,12 @@ export interface ASTComponent {
isDynamic: boolean;
dynamicProps: string | null;
props: { [name: string]: string };
slots: { [name: string]: { content: AST; attrs?: { [key: string]: string }; scope?: string } };
slots: { [name: string]: AST };
}
export interface ASTSlot {
type: ASTType.TSlot;
name: string;
attrs: { [key: string]: string };
defaultContent: AST | null;
}
@@ -177,8 +176,7 @@ interface ParsingContext {
}
export function parse(xml: string | Node): AST {
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
normalizeXML(node);
const node = xml instanceof Element ? xml : parseXML(`<t>${xml}</t>`).firstChild!;
const ctx = { inPreTag: false, inSVG: false };
const ast = parseNode(node, ctx);
if (!ast) {
@@ -595,7 +593,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
if (ast && ast.type === ASTType.TComponent) {
return {
...ast,
slots: { default: { content: tcall } },
slots: { default: tcall },
};
}
}
@@ -704,20 +702,6 @@ function parseTSetNode(node: Element, ctx: ParsingContext): AST | null {
// 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 {
let name = node.tagName;
const firstLetter = name[0];
@@ -741,9 +725,10 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const props: ASTComponent["props"] = {};
for (let name of node.getAttributeNames()) {
const value = node.getAttribute(name)!;
if (name.startsWith("t-")) {
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
throw new Error(message || `unsupported directive on Component: ${name}`);
if (name.startsWith("t-on-")) {
throw new Error(
"t-on is no longer supported on Component node. Consider passing a callback in props."
);
} else {
props[name] = value;
}
@@ -756,11 +741,6 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
// named slots
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
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")!;
// check if this is defined in a sub component (in which case it should
@@ -782,27 +762,14 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
slotNode.remove();
const slotAst = parseNode(slotNode, ctx);
if (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;
slots[name] = slotAst;
}
}
// default slot
const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) {
slots.default = { content: defaultContent };
slots.default = defaultContent;
}
}
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
@@ -816,17 +783,9 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-slot")) {
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 {
type: ASTType.TSlot,
name,
attrs,
name: node.getAttribute("t-slot")!,
defaultContent: parseChildNodes(node, ctx),
};
}
@@ -863,17 +822,34 @@ function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
return { type: ASTType.Multi, content: children };
}
}
function parseXML(xml: string): Document {
const parser = new DOMParser();
/**
* Normalizes the content of an Element so that t-if/t-elif/t-else directives
* immediately follow one another (by removing empty text nodes or comments).
* Throws an error when a conditional branching statement is malformed. This
* function modifies the Element in place.
*
* @param el the element containing the tree that should be normalized
*/
function normalizeTIf(el: Element) {
let tbranch = el.querySelectorAll("[t-elif], [t-else]");
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);
}
let tbranch = doc.querySelectorAll("[t-elif], [t-else]");
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
let node = tbranch[i];
let prevElem = node.previousElementSibling!;
@@ -907,78 +883,6 @@ function normalizeTIf(el: Element) {
);
}
}
}
/**
* 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;
}
+4 -2
View File
@@ -1,6 +1,8 @@
import type { Env } from "../app/app";
import type { ComponentNode } from "./component_node";
export type Props = { [key: string]: any };
// -----------------------------------------------------------------------------
// Component Class
// -----------------------------------------------------------------------------
@@ -26,7 +28,7 @@ export class Component {
setup() {}
render(): Promise<void> {
return this.__owl__.render();
render(deep: boolean = false): Promise<void> {
return this.__owl__.render(deep);
}
}
+77 -33
View File
@@ -1,6 +1,9 @@
import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom";
import { Component } from "./component";
import { clearReactivesForCallback, Reactive, reactive } from "../reactivity";
import { batched, Callback } from "../utils";
import { Component, Props } from "./component";
import { fibersInError, handleError } from "./error_handling";
import {
Fiber,
makeChildFiber,
@@ -8,20 +11,68 @@ import {
MountFiber,
MountOptions,
RootFiber,
__internal__destroyed,
__internal__destroyed
} from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { applyDefaultProps } from "./props_validation";
import { STATUS } from "./status";
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(
name: string | typeof Component,
props: any,
key: string,
ctx: ComponentNode,
parent: any
parent: any,
hasSlots: boolean = false
): ComponentNode {
console.warn('asdf')
let node: any = ctx.children[key];
let isDynamic = typeof name !== "string";
@@ -39,7 +90,11 @@ export function component(
const parentFiber = ctx.fiber!;
if (node) {
node.updateAndRender(props, parentFiber);
console.warn('coucou');
if (hasSlots || parentFiber.deep || arePropsDifferent(node.component.props, props)) {
console.warn('coucou3');
node.updateAndRender(props, parentFiber);
}
} else {
// new component
let C;
@@ -61,15 +116,9 @@ export function component(
}
// -----------------------------------------------------------------------------
// Component VNode
// Component VNode class
// -----------------------------------------------------------------------------
let currentNode: ComponentNode | null = null;
export function getCurrent(): ComponentNode | null {
return currentNode;
}
type LifecycleHook = Function;
export class ComponentNode<T extends typeof Component = typeof Component>
@@ -87,6 +136,7 @@ export class ComponentNode<T extends typeof Component = typeof Component>
level: number;
childEnv: Env;
children: { [key: string]: ComponentNode } = Object.create(null);
slots: any = {};
refs: any = {};
willStart: LifecycleHook[] = [];
@@ -105,6 +155,9 @@ export class ComponentNode<T extends typeof Component = typeof Component>
applyDefaultProps(props, C);
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
// if (props) {
// props = useState(props);
// }
this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
if (C.style) {
@@ -121,7 +174,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
}
async initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber;
if (this.mounted.length) {
fiber.root.mounted.push(fiber);
}
@@ -137,34 +189,23 @@ export class ComponentNode<T extends typeof Component = typeof Component>
}
}
async render() {
const current = this.fiber;
if (current && !current.bdom && !fibersInError.has(current)) {
return current.root.promise;
async render(deep: boolean = false) {
let fiber = this.fiber;
if (fiber && !fiber.bdom && !fibersInError.has(fiber)) {
return fiber.root.promise;
}
if (!this.bdom && !current) {
if (!this.bdom && !fiber) {
// should find a way to return the future mounting promise
return;
}
const fiber = makeRootFiber(this);
this.fiber = fiber;
fiber = makeRootFiber(this);
fiber.deep = deep;
this.app.scheduler.addFiber(fiber);
await Promise.resolve();
if (this.status === STATUS.DESTROYED) {
return;
}
// 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)) {
if (this.fiber === fiber) {
this._render(fiber);
}
return fiber.root.promise;
@@ -213,7 +254,6 @@ export class ComponentNode<T extends typeof Component = typeof Component>
async updateAndRender(props: any, parentFiber: Fiber) {
// update
const fiber = makeChildFiber(this, parentFiber);
this.fiber = fiber;
if (this.willPatch.length) {
parentFiber.root.willPatch.push(fiber);
}
@@ -254,6 +294,10 @@ export class ComponentNode<T extends typeof Component = typeof Component>
}
patch() {
if (!this.fiber) {
// component was not rendered => no need to do anything
return;
}
this.bdom!.patch(this!.fiber!.bdom!, false);
this.fiber!.appliedToDom = true;
this.fiber = null;
+2
View File
@@ -67,9 +67,11 @@ export class Fiber {
parent: Fiber | null;
children: Fiber[] = [];
appliedToDom = false;
deep: boolean = false;
constructor(node: ComponentNode, parent: Fiber | null) {
this.node = node;
node.fiber = this;
this.parent = parent;
if (parent) {
const root = parent.root;
+2 -1
View File
@@ -34,6 +34,7 @@ import type { AppConfig } from "./app/app";
import { App } from "./app/app";
import { Component } from "./component/component";
import { getCurrent } from "./component/component_node";
export { useState } from "./component/component_node";
export { App, Component };
@@ -55,7 +56,7 @@ export { status } from "./component/status";
export { Portal } from "./portal";
export { Memo } from "./memo";
export { css, xml } from "./tags";
export { useState, reactive } from "./reactivity";
export { reactive } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
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 {
for (let k in p1) {
if (k !== "slots" && p1[k] !== p2[k]) {
if (p1[k] !== p2[k]) {
return false;
}
}
+3 -53
View File
@@ -1,5 +1,4 @@
import { onWillUnmount } from "./component/lifecycle_hooks";
import { ComponentNode, getCurrent } from "./component/component_node";
import { Callback } from "./utils";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
const TARGET = Symbol("Target");
@@ -8,8 +7,7 @@ const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol;
type Target = object;
type Callback = () => void;
type Reactive<T extends Target = Target> = T & {
export type Reactive<T extends Target = Target> = T & {
[TARGET]: any;
};
@@ -81,7 +79,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
*
* @param callback the callback for which the reactives need to be cleared
*/
function clearReactivesForCallback(callback: Callback): void {
export function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) {
return;
@@ -190,51 +188,3 @@ export function reactive<T extends Target>(target: T, callback: Callback): React
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
// -----------------------------------------------------------------------------
export function xml(...args: Parameters<typeof String.raw>) {
export function xml(strings: TemplateStringsArray, ...args: any[]) {
const name = `__template__${xml.nextId++}`;
const value = String.raw(...args);
const value = String.raw(strings, ...args);
globalTemplates[name] = value;
return name;
}
+26
View File
@@ -1,3 +1,29 @@
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 {
trigger(name: string, payload?: any) {
this.dispatchEvent(new CustomEvent(name, { detail: payload }));
@@ -9,7 +9,8 @@ exports[`t-on can bind event handler 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx];
const v1 = ctx['add'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -115,8 +116,10 @@ exports[`t-on can bind two event handlers 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['handleClick'], ctx];
let d2 = [ctx['handleDblClick'], ctx];
const v1 = ctx['handleClick'];
let d1 = [v1, ctx];
const v2 = ctx['handleDblClick'];
let d2 = [v2, ctx];
return block1([d1, d2]);
}
}"
@@ -131,7 +134,8 @@ exports[`t-on handler is bound to proper owner 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx];
const v1 = ctx['add'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -151,7 +155,8 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`value\`] = v_block1[i1];
let key1 = ctx['value'];
let d1 = [ctx['add'], ctx];
const v1 = ctx['add'];
let d1 = [v1, ctx];
c_block1[i1] = withKey(block2([d1]), key1);
}
return list(c_block1);
@@ -168,7 +173,8 @@ exports[`t-on handler is bound to proper owner, part 3 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx];
const v1 = ctx['add'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -196,7 +202,8 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx];
const v1 = ctx['add'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -235,7 +242,8 @@ exports[`t-on receive event in first argument 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx];
const v1 = ctx['add'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -250,8 +258,10 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['divClicked'], ctx];
let d2 = [ctx['btnClicked'], ctx];
const v1 = ctx['divClicked'];
let d1 = [v1, ctx];
const v2 = ctx['btnClicked'];
let d2 = [v2, ctx];
return block1([d1, d2]);
}
}"
@@ -266,7 +276,8 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
let d2 = ctx['text'];
return block1([d1, d2]);
}
@@ -282,7 +293,8 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
let b2 = safeOutput(ctx['html']);
return block1([d1], [b2]);
}
@@ -298,8 +310,10 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [\\"capture\\", ctx['onCapture'], ctx];
let d2 = [ctx['doSomething'], ctx];
const v1 = ctx['onCapture'];
let d1 = [\\"capture\\", v1, ctx];
const v2 = ctx['doSomething'];
let d2 = [v2, ctx];
return block1([d1, d2]);
}
}"
@@ -329,7 +343,8 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [\\"prevent\\",\\"self\\", v1, ctx];
return block1([d1]);
}
}"
@@ -344,9 +359,12 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [\\"prevent\\", ctx['onClickPrevented'], ctx];
let d2 = [\\"stop\\", ctx['onClickStopped'], ctx];
let d3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx];
const v1 = ctx['onClickPrevented'];
let d1 = [\\"prevent\\", v1, ctx];
const v2 = ctx['onClickStopped'];
let d2 = [\\"stop\\", v2, ctx];
const v3 = ctx['onClickPreventedAndStopped'];
let d3 = [\\"prevent\\",\\"stop\\", v3, ctx];
return block1([d1, d2, d3]);
}
}"
@@ -388,7 +406,8 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [\\"self\\",\\"prevent\\", v1, ctx];
return block1([d1]);
}
}"
@@ -403,8 +422,10 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx];
let d2 = [\\"self\\", ctx['onClickSelf'], ctx];
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
const v2 = ctx['onClickSelf'];
let d2 = [\\"self\\", v2, ctx];
return block1([d1, d2]);
}
}"
@@ -419,8 +440,10 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [\\"synthetic\\", ctx['divClicked'], ctx];
let d2 = [\\"synthetic\\", ctx['btnClicked'], ctx];
const v1 = ctx['divClicked'];
let d1 = [\\"synthetic\\", v1, ctx];
const v2 = ctx['btnClicked'];
let d2 = [\\"synthetic\\", v2, ctx];
return block1([d1, d2]);
}
}"
@@ -500,7 +523,8 @@ exports[`t-on t-on with t-call 1`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['update'], ctx];
const v1 = ctx['update'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
+27 -15
View File
@@ -182,7 +182,7 @@ exports[`misc other complex template 1`] = `
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
const callTemplate_14 = 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\\">
<h5>Search options</h5>
@@ -223,7 +223,9 @@ exports[`misc other complex template 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`project\`] = v_block2[i1];
let key1 = ctx['project'].id;
let d3 = [ctx['selectProject'](ctx['project']), ctx];
const v1 = ctx['selectProject'];
const v2 = ctx['project'];
let d3 = [v1(v2), ctx];
let d4 = ctx['project'].name;
c_block2[i1] = withKey(block3([d3, d4]), key1);
}
@@ -255,8 +257,10 @@ exports[`misc other complex template 1`] = `
}
b4 = multi([b5, b6]);
}
let d11 = [ctx['toggleSettingsMenu'], ctx];
let d12 = [ctx['toggleMore'], ctx];
const v3 = ctx['toggleSettingsMenu'];
let d11 = [v3, ctx];
const v4 = ctx['toggleMore'];
let d12 = [v4, ctx];
if (ctx['categories']&&ctx['categories'].length>1) {
ctx = Object.create(ctx);
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);
@@ -273,10 +277,13 @@ exports[`misc other complex template 1`] = `
b14 = block14([], [b15]);
}
let d16 = ctx['search'].value;
let d17 = [ctx['updateFilter'], ctx];
let d18 = [ctx['updateFilter'], ctx];
const v5 = ctx['updateFilter'];
let d17 = [v5, ctx];
const v6 = ctx['updateFilter'];
let d18 = [v6, ctx];
let d19 = (el) => refs[\`search_input\`] = el;
let d20 = [ctx['clearSearch'], ctx];
const v7 = ctx['clearSearch'];
let d20 = [v7, ctx];
let d21 = (el) => refs[\`settings_menu\`] = el;
if (ctx['triggers']) {
ctx = Object.create(ctx);
@@ -290,7 +297,8 @@ exports[`misc other complex template 1`] = `
let d23 = \`trigger_\${ctx['trigger'].id}\`;
let d24 = ctx['options'].trigger_display[ctx['trigger'].id];
let d25 = ctx['trigger'].id;
let d26 = [ctx['updateTriggerDisplay'], ctx];
const v8 = ctx['updateTriggerDisplay'];
let d26 = [v8, ctx];
let d27 = \`trigger_\${ctx['trigger'].id}\`;
let d28 = ctx['trigger'].name;
b20 = block20([d22, d23, d24, d25, d26, d27, d28]);
@@ -299,15 +307,19 @@ exports[`misc other complex template 1`] = `
}
ctx = ctx.__proto__;
let b18 = list(c_block18);
let d29 = [ctx['triggerAll'], ctx];
let d30 = [ctx['triggerNone'], ctx];
let d31 = [ctx['triggerDefault'], ctx];
let d32 = [ctx['toggleSettingsMenu'], ctx];
const v9 = ctx['triggerAll'];
let d29 = [v9, ctx];
const v10 = ctx['triggerNone'];
let d30 = [v10, ctx];
const v11 = ctx['triggerDefault'];
let d31 = [v11, ctx];
const v12 = ctx['toggleSettingsMenu'];
let d32 = [v12, ctx];
let b21 = block21([d29, d30, d31, d32]);
b17 = multi([b18, b21]);
}
if (ctx['load_infos']) {
b22 = callTemplate_2.call(this, ctx, node, key + \`__1\`);
b22 = callTemplate_14.call(this, ctx, node, key + \`__13\`);
}
if (ctx['message']) {
let d33 = ctx['message'];
@@ -316,8 +328,8 @@ exports[`misc other complex template 1`] = `
if (!ctx['project']) {
b24 = block24();
} else {
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 + \`__4\`, node, ctx);
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__15\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__16\`, node, ctx);
b25 = block25([], [b26, b27]);
}
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");
});
test("addTemplates throw if parser error", () => {
const context = new TestContext();
expect(() => {
context.addTemplates("<templates><abc>></templates>");
}).toThrow("Invalid XML in template");
});
// test("addTemplates throw if parser error", () => {
// const context = new TestContext();
// expect(() => {
// context.addTemplates("<templates><abc>></templates>");
// }).toThrow("Invalid XML in template");
// });
test("nice error when t-on is evaluated with a missing event", () => {
expect(() => renderToString(`<div t-on="somemethod"></div>`)).toThrow(
+48 -118
View File
@@ -1069,31 +1069,7 @@ describe("qweb parser", () => {
test("component with event handler", async () => {
expect(() => parse(`<MyComponent t-on-click="someMethod"/>`)).toThrow(
"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"
"t-on is no longer supported on Component node. Consider passing a callback in props."
);
});
@@ -1104,22 +1080,7 @@ describe("qweb parser", () => {
dynamicProps: null,
props: {},
isDynamic: false,
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" } },
},
slots: { default: { type: ASTType.Text, value: "foo" } },
});
});
@@ -1132,33 +1093,31 @@ describe("qweb parser", () => {
props: {},
slots: {
default: {
content: {
type: ASTType.Multi,
content: [
{
type: ASTType.DomNode,
tag: "span",
dynamicTag: null,
attrs: {},
content: [],
ref: null,
model: null,
on: {},
ns: null,
},
{
type: ASTType.DomNode,
tag: "div",
dynamicTag: null,
attrs: {},
content: [],
ref: null,
model: null,
on: {},
ns: null,
},
],
},
type: ASTType.Multi,
content: [
{
type: ASTType.DomNode,
tag: "span",
dynamicTag: null,
attrs: {},
content: [],
ref: null,
model: null,
on: {},
ns: null,
},
{
type: ASTType.DomNode,
tag: "div",
dynamicTag: null,
attrs: {},
content: [],
ref: null,
model: null,
on: {},
ns: null,
},
],
},
},
});
@@ -1171,27 +1130,10 @@ describe("qweb parser", () => {
isDynamic: false,
dynamicProps: null,
props: {},
slots: { name: { content: { type: ASTType.Text, value: "foo" } } },
slots: { name: { 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 () => {
expect(parse(`<MyComponent><t t-set-slot="name">foo</t> </MyComponent>`)).toEqual({
type: ASTType.TComponent,
@@ -1200,8 +1142,8 @@ describe("qweb parser", () => {
props: {},
isDynamic: false,
slots: {
default: { content: { type: ASTType.Text, value: " " } },
name: { content: { type: ASTType.Text, value: "foo" } },
default: { type: ASTType.Text, value: " " },
name: { type: ASTType.Text, value: "foo" },
},
});
});
@@ -1219,8 +1161,8 @@ describe("qweb parser", () => {
props: {},
isDynamic: false,
slots: {
a: { content: { type: ASTType.Text, value: "foo" } },
b: { content: { type: ASTType.Text, value: "bar" } },
a: { type: ASTType.Text, value: "foo" },
b: { type: ASTType.Text, value: "bar" },
},
});
});
@@ -1259,14 +1201,8 @@ describe("qweb parser", () => {
});
test("component with t-esc", async () => {
expect(parse(`<MyComponent t-esc="someValue"/>`)).toEqual(
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"
expect(() => parse(`<MyComponent t-esc="someValue"/>`)).toThrow(
"t-esc is not supported on Component nodes"
);
});
@@ -1277,7 +1213,7 @@ describe("qweb parser", () => {
dynamicProps: null,
props: {},
isDynamic: false,
slots: { default: { content: { body: null, name: "subTemplate", type: ASTType.TCall } } },
slots: { default: { body: null, name: "subTemplate", type: ASTType.TCall } },
});
});
@@ -1297,14 +1233,12 @@ describe("qweb parser", () => {
isDynamic: false,
slots: {
default: {
content: {
type: ASTType.TComponent,
isDynamic: false,
name: "Child",
dynamicProps: null,
props: {},
slots: { brol: { content: { type: ASTType.Text, value: "coucou" } } },
},
type: ASTType.TComponent,
isDynamic: false,
name: "Child",
dynamicProps: null,
props: {},
slots: { brol: { type: ASTType.Text, value: "coucou" } },
},
},
});
@@ -1314,7 +1248,7 @@ describe("qweb parser", () => {
const template = `
<MyComponent>
<Child>
<t t-set-slot="brol">coucou</t>
<t><t t-set-slot="brol">coucou</t></t>
</Child>
</MyComponent>
`;
@@ -1326,14 +1260,12 @@ describe("qweb parser", () => {
isDynamic: false,
slots: {
default: {
content: {
type: ASTType.TComponent,
isDynamic: false,
name: "Child",
dynamicProps: null,
props: {},
slots: { brol: { content: { type: ASTType.Text, value: "coucou" } } },
},
type: ASTType.TComponent,
isDynamic: false,
name: "Child",
dynamicProps: null,
props: {},
slots: { brol: { type: ASTType.Text, value: "coucou" } },
},
},
});
@@ -1347,7 +1279,6 @@ describe("qweb parser", () => {
expect(parse(`<t t-slot="default"/>`)).toEqual({
type: ASTType.TSlot,
name: "default",
attrs: {},
defaultContent: null,
});
});
@@ -1356,7 +1287,6 @@ describe("qweb parser", () => {
expect(parse(`<t t-slot="header">default content</t>`)).toEqual({
type: ASTType.TSlot,
name: "header",
attrs: {},
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 = \\"\\") {
const tKey_1 = ctx['childProps'].key;
let b2 = toggler(tKey_1, component(\`Child\`, ctx['childProps'], tKey_1 + key + \`__2\`, node, ctx));
let b2 = toggler(tKey_1, component(\`Child\`, Object.assign({}, ctx['childProps']), tKey_1 + key + \`__2\`, node, ctx));
return block1([], [b2]);
}
}"
@@ -1044,30 +1044,6 @@ 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`] = `
"function anonymous(bdom, helpers
) {
@@ -1091,7 +1067,7 @@ exports[`properly behave when destroyed/unmounted while rendering 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`SubChild\`, {}, key + \`__1\`, node, ctx);
let b2 = component(\`SubChild\`, {val: ctx['props'].val}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -1154,9 +1130,10 @@ exports[`rendering component again in next microtick 2`] = `
return function template(ctx, node, key = \\"\\") {
let b2;
let d1 = [ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
if (ctx['env'].config.flag) {
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
}
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) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -117,15 +117,16 @@ exports[`can catch errors can catch an error in a component render function 3`]
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -158,7 +159,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -206,7 +207,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -218,17 +219,18 @@ 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 { 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>\`);
function slot1(ctx, node, key) {
let b3 = component(\`ClassicCompoent\`, {}, key + \`__2\`, node, ctx);
let b4 = component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
const slot2 = ctx => (node, key) => {
let b3 = component(\`ClassicCompoent\`, {}, key + \`__3\`, node, ctx);
let b4 = component(\`ErrorComponent\`, {}, key + \`__4\`, node, ctx);
return multi([b3, b4]);
}
return function template(ctx, node, key = \\"\\") {
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__4\`, node, ctx);
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b5]);
}
}"
@@ -239,15 +241,16 @@ 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 { 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>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -281,7 +284,7 @@ exports[`can catch errors can catch an error in the initial call of a component
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -293,15 +296,16 @@ 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 { 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>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -335,7 +339,7 @@ exports[`can catch errors can catch an error in the initial call of a component
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -347,17 +351,18 @@ 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 { 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>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3;
if (ctx['state'].flag) {
b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
}
return block1([], [b3]);
}
@@ -391,7 +396,7 @@ exports[`can catch errors can catch an error in the mounted call 2`] = `
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -403,15 +408,16 @@ exports[`can catch errors can catch an error in the mounted call 3`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -445,7 +451,7 @@ exports[`can catch errors can catch an error in the willPatch call 2`] = `
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -457,16 +463,17 @@ exports[`can catch errors can catch an error in the willPatch call 3`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><span><block-text-0/></span><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].message;
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([d1], [b3]);
}
}"
@@ -499,7 +506,7 @@ exports[`can catch errors can catch an error in the willStart call 2`] = `
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -511,15 +518,16 @@ exports[`can catch errors can catch an error in the willStart call 3`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -566,7 +574,7 @@ exports[`can catch errors can catch an error origination from a child's willStar
if (ctx['state'].error) {
b2 = text(\`Error handled\`);
} else {
b3 = callSlot(ctx, node, key, 'default', false, {});
b3 = callSlot(ctx, node, key, 'default');
}
return block1([], [b2, b3]);
}
@@ -578,17 +586,18 @@ exports[`can catch errors can catch an error origination from a child's willStar
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
let b3 = component(\`ClassicCompoent\`, {}, key + \`__2\`, node, ctx);
let b4 = component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
const slot2 = ctx => (node, key) => {
let b3 = component(\`ClassicCompoent\`, {}, key + \`__3\`, node, ctx);
let b4 = component(\`ErrorComponent\`, {}, key + \`__4\`, node, ctx);
return multi([b3, b4]);
}
return function template(ctx, node, key = \\"\\") {
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__4\`, node, ctx);
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b5]);
}
}"
@@ -23,40 +23,15 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['inc'], ctx];
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
const v1 = ctx['inc'];
let d1 = [v1, ctx];
let b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
let d2 = ctx['state'].value;
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`] = `
"function anonymous(bdom, helpers
) {
@@ -67,7 +42,8 @@ exports[`event handling support for callable expression in event handler 1`] = `
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].value;
let d2 = [ctx['obj'].onInput, ctx];
const v1 = ctx['obj'];
let d2 = [v1.onInput, ctx];
return block1([d1, d2]);
}
}"
@@ -130,7 +130,8 @@ exports[`basics sub widget is interactive 1`] = `
let block1 = createBlock(\`<span><button block-handler-0=\\"click\\">click</button>child<block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['inc'], ctx];
const v1 = ctx['inc'];
let d1 = [v1, ctx];
let d2 = ctx['state'].val;
return block1([d1, d2]);
}
@@ -540,7 +540,8 @@ exports[`lifecycle hooks onWillRender 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['increment'], ctx];
const v1 = ctx['increment'];
let d1 = [v1, ctx];
let d2 = ctx['state'].value;
return block1([d1, d2]);
}
@@ -554,7 +555,7 @@ exports[`lifecycle hooks onWillRender 2`] = `
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {}, key + \`__1\`, node, ctx);
return component(\`Child\`, {someValue: ctx['state'].value}, key + \`__1\`, node, ctx);
}
}"
`;
@@ -30,42 +30,6 @@ 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`] = `
"function anonymous(bdom, helpers
) {
@@ -96,60 +60,6 @@ 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`] = `
"function anonymous(bdom, helpers
) {
@@ -251,27 +161,3 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -26,9 +26,9 @@ exports[`default props can set default values 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -196,9 +196,9 @@ exports[`props validation can validate an optional props 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -213,9 +213,9 @@ exports[`props validation can validate an optional props 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 1}
helpers.validateProps(\`Child\`, props1, ctx)
let b2 = component(\`Child\`, props1, key + \`__2\`, node, ctx);
const props2 = {message: 1}
helpers.validateProps(\`Child\`, props2, ctx)
let b2 = component(\`Child\`, props2, key + \`__1\`, node, ctx);
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>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['state'].p}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -315,9 +315,9 @@ exports[`props validation validate simple types 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -332,9 +332,9 @@ exports[`props validation validate simple types 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -349,9 +349,9 @@ exports[`props validation validate simple types 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -366,9 +366,9 @@ exports[`props validation validate simple types 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -383,9 +383,9 @@ exports[`props validation validate simple types 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -400,9 +400,9 @@ exports[`props validation validate simple types 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -417,9 +417,9 @@ exports[`props validation validate simple types, alternate form 1`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -434,9 +434,9 @@ exports[`props validation validate simple types, alternate form 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -451,9 +451,9 @@ exports[`props validation validate simple types, alternate form 3`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -468,9 +468,9 @@ exports[`props validation validate simple types, alternate form 4`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -485,9 +485,9 @@ exports[`props validation validate simple types, alternate form 5`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -502,9 +502,9 @@ exports[`props validation validate simple types, alternate form 6`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props1, ctx)
let b2 = component(\`SubComp\`, props1, key + \`__2\`, node, ctx);
const props2 = {p: ctx['p']}
helpers.validateProps(\`SubComp\`, props2, ctx)
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -25,7 +25,7 @@ exports[`refs refs are properly bound in slots 1`] = `
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callSlot(ctx, node, key, 'footer', false, {});
let b2 = callSlot(ctx, node, key, 'footer');
return block1([], [b2]);
}
}"
@@ -36,13 +36,15 @@ exports[`refs refs are properly bound in slots 2`] = `
) {
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 assign = Object.assign;
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>\`);
function slot2(ctx, node, key) {
const slot3 = ctx => (node, key) => {
const refs = ctx.__owl__.refs
let d2 = [ctx['doSomething'], ctx];
const v4 = ctx['doSomething'];
let d2 = [v4, ctx];
let d3 = (el) => refs[\`myButton\`] = el;
return block2([d2, d3]);
}
@@ -50,8 +52,8 @@ exports[`refs refs are properly bound in slots 2`] = `
return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs;
let d1 = ctx['state'].val;
const ctx1 = capture(ctx);
let b3 = component(\`Dialog\`, {slots: {'footer': {__render: slot2, __ctx: ctx1}}}, key + \`__3\`, node, ctx);
const ctx2 = capture(ctx);
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx, true), {slots: {'footer': slot3(ctx2)}});
return block1([d1], [b3]);
}
}"
@@ -0,0 +1,77 @@
// 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,7 +52,8 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['update'], ctx];
const v1 = ctx['update'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -39,61 +39,6 @@ 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`] = `
"function anonymous(bdom, helpers
) {
@@ -117,10 +117,11 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onInput'], ctx];
const bExpr1 = ctx['state'];
const v1 = ctx['onInput'];
let d1 = [v1, ctx];
const bExpr2 = ctx['state'];
let d2 = ctx['state']['text'];
let d3 = [(ev) => { bExpr1['text'] = ev.target.value; }];
let d3 = [(ev) => { bExpr2['text'] = ev.target.value; }];
return block1([d1, d2, d3]);
}
}"
@@ -135,18 +136,21 @@ 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>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx];
const bExpr1 = ctx['state'];
let d2 = ctx['state']['choice'] === 'One';
let d3 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
let d4 = [ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
const bExpr2 = ctx['state'];
let d2 = ctx['state']['choice'] === 'One';
let d3 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
const v3 = ctx['onClick'];
let d4 = [v3, ctx];
const bExpr4 = ctx['state'];
let d5 = ctx['state']['choice'] === 'Two';
let d6 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
let d7 = [ctx['onClick'], ctx];
const bExpr3 = ctx['state'];
let d6 = [(ev) => { bExpr4['choice'] = ev.target.value; }];
const v5 = ctx['onClick'];
let d7 = [v5, ctx];
const bExpr6 = ctx['state'];
let d8 = ctx['state']['choice'] === 'Three';
let d9 = [(ev) => { bExpr3['choice'] = ev.target.value; }];
let d9 = [(ev) => { bExpr6['choice'] = ev.target.value; }];
return block1([d1, d2, d3, d4, d5, d6, d7, d8, d9]);
}
}"
@@ -130,7 +130,8 @@ exports[`t-on t-on on destroyed components 1`] = `
let block1 = createBlock(\`<div block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx];
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -24,7 +24,7 @@ exports[`t-props basic use 2`] = `
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, ctx['some'].obj, key + \`__1\`, node, ctx);
let b2 = component(\`Child\`, Object.assign({}, ctx['some'].obj), key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -85,7 +85,7 @@ exports[`t-props t-props only 2`] = `
let block1 = createBlock(\`<div><div><block-child-0/></div></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Comp\`, ctx['state'], key + \`__1\`, node, ctx);
let b2 = component(\`Comp\`, Object.assign({}, ctx['state']), key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
@@ -24,10 +24,11 @@ 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 { 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>\`);
function slot2(ctx, node, key) {
const slot3 = ctx => (node, key) => {
setContextValue(ctx, \\"iter\\", 'inCall');
}
@@ -36,8 +37,8 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 2`] = `
ctx[isBoundary] = 1
setContextValue(ctx, \\"iter\\", 'source');
let d1 = ctx['iter'];
const ctx1 = capture(ctx);
let b2 = component(\`Childcomp\`, {slots: {'default': {__render: slot2, __ctx: ctx1}}}, key + \`__3\`, node, ctx);
const ctx2 = capture(ctx);
let b2 = assign(component(\`Childcomp\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot3(ctx2)}});
let d2 = ctx['iter'];
return block1([d1, d2], [b2]);
}
+1 -1
View File
@@ -419,7 +419,7 @@ describe("basics", () => {
expect(fixture.innerHTML).toBe("<div><span></span></div>");
});
test("child can be updated", async () => {
test.only("child can be updated", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.value"/>`;
}
+1 -59
View File
@@ -543,7 +543,7 @@ test("properly behave when destroyed/unmounted while rendering ", async () => {
}
class Child extends Component {
static template = xml`<div><SubChild /></div>`;
static template = xml`<div><SubChild val="props.val"/></div>`;
static components = { SubChild };
setup() {
useLogLifecycle(steps);
@@ -1900,18 +1900,13 @@ test("concurrent renderings scenario 13", async () => {
"Child:willPatch",
"Child:patched",
"Parent:willRender",
"Child:willUpdateProps",
"Child:setup",
"Child:willStart",
"Parent:rendered",
"Child:willRender",
"Child:rendered",
"Child:willRender",
"Child:rendered",
"Parent:willPatch",
"Child:willPatch",
"Child:mounted",
"Child:patched",
"Parent:patched",
"Child:willRender",
"Child:rendered",
@@ -2562,59 +2557,6 @@ test("two renderings initiated between willPatch and patched", async () => {
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 () => {
// const state = { p: 1, cc: 10 };
-21
View File
@@ -72,25 +72,4 @@ describe("event handling", () => {
expect(onClickArgs![0]).toBe(1);
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);
});
});
+4 -7
View File
@@ -899,8 +899,9 @@ describe("lifecycle hooks", () => {
class Parent extends Component {
static template = xml`
<Child />`;
<Child someValue="state.value" />`;
static components = { Child };
state = useState({ value: 1 });
setup() {
useLogLifecycle(steps);
}
@@ -910,7 +911,7 @@ describe("lifecycle hooks", () => {
expect(fixture.innerHTML).toBe("<button>1</button>");
parent.render(); // to block child render
parent.state.value++; // to block child render
await nextTick();
fixture.querySelector("button")!.click();
@@ -1063,22 +1064,18 @@ describe("lifecycle hooks", () => {
steps.splice(0);
c!.state.flag = false;
await nextTick();
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>F</div></div></div>`);
expect(steps).toEqual([
"C:willRender",
"D:willUpdateProps",
"F:setup",
"F:willStart",
"C:rendered",
"D:willRender",
"D:rendered",
"F:willRender",
"F:rendered",
"C:willPatch",
"D:willPatch",
"E:willUnmount",
"E:destroyed",
"F:mounted",
"D:patched",
"C:patched",
]);
});
-73
View File
@@ -30,20 +30,6 @@ describe("basics", () => {
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 () => {
class Child extends Component {
static template = xml`<span><t t-esc="props.greetings"/></span>`;
@@ -116,63 +102,4 @@ describe("basics", () => {
await mount(Parent, fixture);
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
@@ -0,0 +1,110 @@
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");
});
});
+1 -175
View File
@@ -37,59 +37,6 @@ describe("slots", () => {
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 () => {
class Child extends Component {
static template = xml`<t t-slot="default"/><t t-slot="default"/>`;
@@ -124,33 +71,6 @@ describe("slots", () => {
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 () => {
class Dialog extends Component {
static template = xml`
@@ -177,35 +97,6 @@ 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 () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="header"/></span>`;
@@ -235,7 +126,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
});
test("can define a default content", async () => {
test("dafault slots can define a default content", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -283,42 +174,6 @@ describe("slots", () => {
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 () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="footer"/></span>`;
@@ -1497,33 +1352,4 @@ describe("slots", () => {
}
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);
});
});
+2 -32
View File
@@ -1,11 +1,5 @@
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
import {
makeTestFixture,
nextTick,
snapshotApp,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
import { Component, mount, onMounted, useState, xml } from "../../src/index";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
snapshotEverything();
@@ -297,28 +291,4 @@ describe("list of components", () => {
expect((parent.el as HTMLElement).innerHTML).toBe("<div>2</div><div>1</div>");
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() {
expect(this.props).toEqual({ a: 1, b: 2 });
expect(this.props).toBe(props);
expect(this.props).not.toBe(props);
}
}
class Parent extends Component {
+10 -7
View File
@@ -17,8 +17,9 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
) {
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 assign = Object.assign;
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
let b6 = text(ctx['state'].a);
let b7 = text(ctx['state'].b);
let b8 = text(ctx['state'].c);
@@ -29,7 +30,7 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
let b2 = text(ctx['state'].a);
let b3 = text(ctx['state'].b);
let b4 = text(ctx['state'].c);
let b9 = component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b9 = assign(component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return multi([b2, b3, b4, b9]);
}
}"
@@ -52,14 +53,15 @@ exports[`Memo if no props, prevent renderings from above 2`] = `
) {
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 assign = Object.assign;
function slot2(ctx, node, key) {
return component(\`Child\`, {value: ctx['state'].value}, key + \`__3\`, node, ctx);
const slot3 = ctx => (node, key) => {
return component(\`Child\`, {value: ctx['state'].value}, key + \`__4\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
let b4 = component(\`Memo\`, {slots: {'default': {__render: slot2, __ctx: ctx}}}, key + \`__4\`, node, ctx);
let b4 = assign(component(\`Memo\`, {}, key + \`__2\`, node, ctx, true), {slots: {'default': slot3(ctx)}});
return multi([b2, b4]);
}
}"
@@ -70,14 +72,15 @@ exports[`Memo if no props, prevent renderings from above (work with simple html)
) {
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 assign = Object.assign;
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return text(ctx['state'].value);
}
return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['state'].value);
let b4 = component(\`Memo\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b4 = assign(component(\`Memo\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return multi([b2, b4]);
}
}"
+70 -49
View File
@@ -9,7 +9,8 @@ exports[`Portal Portal composed with t-slot 1`] = `
let block1 = createBlock(\`<div block-handler-0=\\"custom\\"><span id=\\"childSpan\\">child2</span></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onCustom'], ctx];
const v1 = ctx['onCustom'];
let d1 = [v1, ctx];
return block1([d1]);
}
}"
@@ -20,13 +21,14 @@ exports[`Portal Portal composed with t-slot 2`] = `
) {
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 assign = Object.assign;
function slot1(ctx, node, key) {
return callSlot(ctx, node, key, 'default', false, {});
const slot2 = ctx => (node, key) => {
return callSlot(ctx, node, key, 'default');
}
return function template(ctx, node, key = \\"\\") {
return component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
}
}"
`;
@@ -36,15 +38,16 @@ exports[`Portal Portal composed with t-slot 3`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`Child2\`, {customHandler: ctx['_handled']}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child2\`, {customHandler: ctx['_handled']}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -55,16 +58,17 @@ exports[`Portal basic use of portal 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -90,18 +94,19 @@ exports[`Portal conditional use of Portal (with sub Component) 2`] = `
) {
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 assign = Object.assign;
let block2 = createBlock(\`<span>1</span>\`);
function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b2,b4;
b2 = block2();
if (ctx['state'].hasPortal) {
b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
}
return multi([b2, b4]);
}
@@ -113,11 +118,12 @@ exports[`Portal conditional use of Portal 1`] = `
) {
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 assign = Object.assign;
let block2 = createBlock(\`<span>1</span>\`);
let block3 = createBlock(\`<p>2</p>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block3();
}
@@ -125,7 +131,7 @@ exports[`Portal conditional use of Portal 1`] = `
let b2,b4;
b2 = block2();
if (ctx['state'].hasPortal) {
b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
}
return multi([b2, b4]);
}
@@ -152,17 +158,18 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 2`]
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3;
if (ctx['state'].hasChild) {
b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
}
return block1([], [b3]);
}
@@ -174,11 +181,12 @@ exports[`Portal portal could have dynamically no content 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
let b3;
if (ctx['state'].val) {
let d1 = ctx['state'].val;
@@ -188,7 +196,7 @@ exports[`Portal portal could have dynamically no content 1`] = `
}
return function template(ctx, node, key = \\"\\") {
let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b4]);
}
}"
@@ -214,15 +222,16 @@ exports[`Portal portal destroys on crash 2`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`Child\`, {error: ctx['state'].error}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child\`, {error: ctx['state'].error}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -248,15 +257,16 @@ exports[`Portal portal with child and props 2`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -267,12 +277,13 @@ exports[`Portal portal with dynamic body 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`);
let block4 = createBlock(\`<div/>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
let b3,b4;
if (ctx['state'].val) {
let d1 = ctx['state'].val;
@@ -284,7 +295,7 @@ exports[`Portal portal with dynamic body 1`] = `
}
return function template(ctx, node, key = \\"\\") {
let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b5]);
}
}"
@@ -295,19 +306,20 @@ exports[`Portal portal with many children 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div>1</div>\`);
let block4 = createBlock(\`<p>2</p>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
let b3 = block3();
let b4 = block4();
return multi([b3, b4]);
}
return function template(ctx, node, key = \\"\\") {
let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b5]);
}
}"
@@ -318,10 +330,11 @@ exports[`Portal portal with no content 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
let b3;
if (false) {
b3 = text('ABC');
@@ -330,7 +343,7 @@ exports[`Portal portal with no content 1`] = `
}
return function template(ctx, node, key = \\"\\") {
let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b4]);
}
}"
@@ -341,15 +354,16 @@ exports[`Portal portal with only text as content 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return text('only text');
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -360,16 +374,17 @@ exports[`Portal portal with target not in dom 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#does-not-exist',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#does-not-exist'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -394,15 +409,16 @@ exports[`Portal portal's parent's env is not polluted 2`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`Child\`, {}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child\`, {}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -413,16 +429,17 @@ exports[`Portal with target in template (after portal) 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`);
let block2 = createBlock(\`<p>2</p>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -433,16 +450,17 @@ exports[`Portal with target in template (before portal) 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -453,16 +471,17 @@ exports[`Portal: Props validation target is mandatory 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -473,16 +492,17 @@ exports[`Portal: Props validation target is not list 1`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`);
function slot1(ctx, node, key) {
const slot2 = ctx => (node, key) => {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: ['body'],slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: ['body']}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
@@ -508,15 +528,16 @@ exports[`Portal: UI/UX focus is kept across re-renders 2`] = `
) {
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 assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
function slot1(ctx, node, key) {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx, true), {slots: {'default': slot2(ctx)}});
return block1([], [b3]);
}
}"
+3 -2
View File
@@ -1,6 +1,7 @@
import { Component, mount, onWillRender, onWillStart, onWillUpdateProps, useState } from "../src";
import { batched, reactive } from "../src/reactivity";
import { reactive } from "../src/reactivity";
import { xml } from "../src/tags";
import { batched } from "../src/utils";
import {
makeDeferred,
makeTestFixture,
@@ -1509,7 +1510,7 @@ describe("Reactivity: useState", () => {
expect([...steps]).toEqual(["list"]);
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>3</div> Total: 3 Count: 1</div>");
expect([...steps]).toEqual(["list", "quantity1"]);
expect([...steps]).toEqual(["list"]);
steps.clear();
secondQuantity.quantity = 2;
+19 -1
View File
@@ -1,4 +1,5 @@
import { EventBus } from "../src/utils";
import { batched, EventBus } from "../src/utils";
import { nextMicroTick } from "./helpers";
describe("event bus behaviour", () => {
test("can subscribe and be notified", () => {
@@ -33,3 +34,20 @@ describe("event bus behaviour", () => {
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
@@ -0,0 +1,301 @@
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();
});
@@ -0,0 +1,13 @@
<!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>
@@ -0,0 +1,47 @@
<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
@@ -0,0 +1,301 @@
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();
});
@@ -0,0 +1,13 @@
<!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>
@@ -0,0 +1,47 @@
<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
@@ -0,0 +1,170 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,161 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,161 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,161 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,161 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,151 @@
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
@@ -0,0 +1,12 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,155 @@
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
@@ -0,0 +1,12 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,173 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,173 @@
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
@@ -0,0 +1,13 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,154 @@
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();
@@ -0,0 +1,12 @@
<!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
@@ -0,0 +1,50 @@
<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
@@ -0,0 +1,154 @@
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