mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21b07afb60 | |||
| 13c3178760 | |||
| 2b565ce1b4 | |||
| cfcf2c6714 | |||
| 92174559a1 | |||
| 840348892f | |||
| b988a68b6f | |||
| 7869a1f2c6 | |||
| 9e81d14d50 | |||
| 0e4a55ba09 | |||
| 6e9b68dafa | |||
| ba7c9063c0 | |||
| 3a361876ad | |||
| 58b8572f0a | |||
| c23637e6d8 | |||
| 4a4b1fbba5 | |||
| 536d9e1762 | |||
| 6ed68372a6 | |||
| efd934d2b1 | |||
| dabc24cee3 | |||
| 7f49796a07 | |||
| 757dffefac | |||
| bdfe058279 | |||
| 345c44b952 | |||
| ea74739d46 | |||
| 93b53d8017 |
+5
-3
@@ -14,7 +14,7 @@ removed after.
|
||||
|
||||
- components can now have empty content or multiple root nodes (htmlelement or text) ([details](#31-components-can-now-have-arbitrary-content))
|
||||
- new `useEffect` hook
|
||||
- new `onDestroyed` and `onRender` hooks
|
||||
- new `onDestroyed`, `onWillRender` and `onRendered` hooks
|
||||
- breaking: lifecycle methods are removed ([details](#1-component-lifecycle-methods-are-removed))
|
||||
- breaking: can no longer be mounted on detached DOM ([details](#2-components-can-no-longer-be-mounted-in-a-detached-dom-element))
|
||||
- breaking: standalone `mount` method API is simpler ([details](#4-mount-method-api-is-simpler))
|
||||
@@ -447,8 +447,10 @@ 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(...)` has to be
|
||||
rewritten like this: `bus.addEventListener(...)`.
|
||||
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(...)`
|
||||
|
||||
### 22. `Store` is removed
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# 🦉 How to debug Owl applications 🦉
|
||||
|
||||
Non trivial applications become quickly more difficult to understand. It is then
|
||||
useful to have a solid understanding of what is going on. To help with that,
|
||||
logging useful information is extremely valuable. There is a [javascript file](../../tools/debug.js) which can be evaluated in an application.
|
||||
|
||||
Once it is executed, it will log a lot of information on each component main hooks. The following code is a minified version to make it easier to copy/paste:
|
||||
|
||||
```
|
||||
function debugOwl(t,e){let n,o="[OWL_DEBUG]";function r(t){let e;try{e=JSON.stringify(t||{})}catch(t){e="<JSON error>"}return e.length>200&&(e=e.slice(0,200)+"..."),e}if(Object.defineProperty(t.Component,"current",{get:()=>n,set(s){n=s;const i=s.constructor.name;if(e.componentBlackList&&e.componentBlackList.test(i))return;if(e.componentWhiteList&&!e.componentWhiteList.test(i))return;let l;Object.defineProperty(n,"__owl__",{get:()=>l,set(n){!function(n,s,i){let l=`${s}<id=${i}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!e.methodBlackList||!e.methodBlackList.includes(t))&&!(e.methodWhiteList&&!e.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=${r(n.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounted")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpdateProps(t=>{c(`willUpdateProps, nextprops=${r(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=>{c("willUnmount")});const d=n.__render.bind(n);n.__render=function(...t){c("rendering template"),d(...t)};const h=n.render.bind(n);n.render=function(...t){const e=n.__owl__;let o="render";return e.isMounted||e.currentFiber||(o+=" (warning: component is not mounted, this render has no effect)"),c(o),h(...t)};const p=n.mount.bind(n);n.mount=function(...t){return c("mount"),p(...t)}}(s,i,(l=n).id)}})}}),e.logScheduler){let e=t.Component.scheduler.start,n=t.Component.scheduler.stop;t.Component.scheduler.start=function(){this.isRunning||console.log(`${o} scheduler: start running tasks queue`),e.call(this)},t.Component.scheduler.stop=function(){this.isRunning&&console.log(`${o} scheduler: stop running tasks queue`),n.call(this)}}if(e.logStore){let e=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...n){return console.log(`${o} store: action '${t}' dispatched. Payload: '${r(n)}'`),e.call(this,t,...n)}}}
|
||||
debugOwl(owl, {
|
||||
// componentBlackList: /App/, // regexp
|
||||
// componentWhiteList: /SomeComponent/, // regexp
|
||||
// methodBlackList: ["mounted"], // list of method names
|
||||
// methodWhiteList: ["willStart"], // list of method names
|
||||
logScheduler: false, // display/mute scheduler logs
|
||||
logStore: true, // display/mute store logs
|
||||
});
|
||||
```
|
||||
|
||||
The above code, once pasted somewhere in the main javascript file of an owl
|
||||
application, will log information looking like this:
|
||||
|
||||
```
|
||||
[OWL_DEBUG] TodoApp<id=1> constructor, props={}
|
||||
[OWL_DEBUG] TodoApp<id=1> mount
|
||||
[OWL_DEBUG] TodoApp<id=1> willStart
|
||||
[OWL_DEBUG] TodoApp<id=1> rendering template
|
||||
[OWL_DEBUG] TodoItem<id=2> constructor, props={"id":2,"completed":false,"title":"hey"}
|
||||
[OWL_DEBUG] TodoItem<id=2> willStart
|
||||
[OWL_DEBUG] TodoItem<id=3> constructor, props={"id":4,"completed":false,"title":"aaa"}
|
||||
[OWL_DEBUG] TodoItem<id=3> willStart
|
||||
[OWL_DEBUG] TodoItem<id=2> rendering template
|
||||
[OWL_DEBUG] TodoItem<id=3> rendering template
|
||||
[OWL_DEBUG] TodoItem<id=3> mounted
|
||||
[OWL_DEBUG] TodoItem<id=2> mounted
|
||||
[OWL_DEBUG] TodoApp<id=1> mounted
|
||||
```
|
||||
|
||||
Each component has an internal `id`, which is very useful when debugging.
|
||||
|
||||
Note that it is certainly useful to run this code at some point in an application,
|
||||
just to get a feel of what each user action implies, for the framework.
|
||||
@@ -9,7 +9,6 @@ Are you new to Owl? This is the place to start!
|
||||
- [How to start an Owl project](learning/quick_start.md)
|
||||
- [How to 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
|
||||
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.4.7",
|
||||
"version": "2.0.0-alpha1",
|
||||
"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",
|
||||
"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\"",
|
||||
"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\"",
|
||||
"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,11 +52,11 @@
|
||||
"prettier": "2.4.1",
|
||||
"rollup": "^2.56.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
"rollup-plugin-typescript2": "^0.31.1",
|
||||
"sass": "^1.16.1",
|
||||
"source-map-support": "^0.5.10",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "3.9.6",
|
||||
"typescript": "4.5.2",
|
||||
"uglify-es": "^3.3.9"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface AppConfig {
|
||||
env?: Env;
|
||||
translatableAttributes?: string[];
|
||||
translateFn?: (s: string) => string;
|
||||
templates?: string | Document;
|
||||
}
|
||||
|
||||
export const DEV_MSG = `Owl is running in 'dev' mode.
|
||||
@@ -49,6 +50,9 @@ export class App<T extends typeof Component = any> extends TemplateSet {
|
||||
if (config.translatableAttributes) {
|
||||
this.translatableAttributes = config.translatableAttributes;
|
||||
}
|
||||
if (config.templates) {
|
||||
this.addTemplates(config.templates);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,24 @@ function callSlot(
|
||||
parent: any,
|
||||
key: string,
|
||||
name: string,
|
||||
defaultSlot?: (ctx: any, key: string) => BDom,
|
||||
dynamic?: boolean
|
||||
dynamic: boolean,
|
||||
extra: any,
|
||||
defaultContent?: (ctx: any, node: any, key: string) => BDom
|
||||
): BDom {
|
||||
const slots = ctx.__owl__.slots;
|
||||
const slotFn = slots[name];
|
||||
const slotBDom = slotFn ? slotFn(parent, key) : null;
|
||||
if (defaultSlot) {
|
||||
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) {
|
||||
let child1: BDom | undefined = undefined;
|
||||
let child2: BDom | undefined = undefined;
|
||||
if (slotBDom) {
|
||||
child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
|
||||
} else {
|
||||
child2 = defaultSlot(parent, key);
|
||||
child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
|
||||
}
|
||||
return multi([child1, child2]);
|
||||
}
|
||||
|
||||
+35
-1
@@ -7,6 +7,36 @@ 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 } = {};
|
||||
@@ -33,7 +63,11 @@ export class TemplateSet {
|
||||
}
|
||||
|
||||
addTemplates(xml: string | Document, options: { allowDuplicate?: boolean } = {}) {
|
||||
xml = xml instanceof Document ? xml : new DOMParser().parseFromString(xml, "text/xml");
|
||||
if (!xml) {
|
||||
// empty string
|
||||
return;
|
||||
}
|
||||
xml = xml instanceof Document ? xml : parseXML(xml);
|
||||
for (const template of xml.querySelectorAll("[t-name]")) {
|
||||
const name = template.getAttribute("t-name")!;
|
||||
template.removeAttribute("t-name");
|
||||
|
||||
@@ -116,6 +116,7 @@ interface IntermediateTree {
|
||||
forceRef?: boolean;
|
||||
refIdx?: number;
|
||||
refN: number;
|
||||
currentNS: string | null;
|
||||
}
|
||||
|
||||
function buildTree(
|
||||
@@ -124,9 +125,10 @@ function buildTree(
|
||||
domParentTree: IntermediateTree | null = null
|
||||
): IntermediateTree {
|
||||
switch (node.nodeType) {
|
||||
case 1: {
|
||||
case Node.ELEMENT_NODE: {
|
||||
// HTMLElement
|
||||
let isActive = false;
|
||||
let currentNS = parent && parent.currentNS;
|
||||
const tagName = (node as Element).tagName;
|
||||
let el: Node | undefined = undefined;
|
||||
const info: DynamicInfo[] = [];
|
||||
@@ -143,11 +145,18 @@ function buildTree(
|
||||
el = document.createTextNode("");
|
||||
isActive = true;
|
||||
}
|
||||
if (!el) {
|
||||
el = document.createElement(tagName);
|
||||
const attrs = (node as Element).attributes;
|
||||
const ns = attrs.getNamedItem("block-ns");
|
||||
if (ns) {
|
||||
attrs.removeNamedItem("block-ns");
|
||||
currentNS = ns.value;
|
||||
}
|
||||
if (el instanceof HTMLElement) {
|
||||
const attrs = (node as Element).attributes;
|
||||
if (!el) {
|
||||
el = currentNS
|
||||
? document.createElementNS(currentNS, tagName)
|
||||
: document.createElement(tagName);
|
||||
}
|
||||
if (el instanceof Element) {
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attrName = attrs[i].name;
|
||||
const attrValue = attrs[i].value;
|
||||
@@ -193,13 +202,14 @@ function buildTree(
|
||||
el,
|
||||
info,
|
||||
refN: isActive ? 1 : 0,
|
||||
currentNS,
|
||||
};
|
||||
|
||||
if (node.firstChild) {
|
||||
const childNode = node.childNodes[0];
|
||||
if (
|
||||
node.childNodes.length === 1 &&
|
||||
childNode.nodeType === 1 &&
|
||||
childNode.nodeType === Node.ELEMENT_NODE &&
|
||||
(childNode as Element).tagName.startsWith("block-child-")
|
||||
) {
|
||||
const tagName = (childNode as Element).tagName;
|
||||
@@ -227,11 +237,11 @@ function buildTree(
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
case 3:
|
||||
case 8: {
|
||||
case Node.TEXT_NODE:
|
||||
case Node.COMMENT_NODE: {
|
||||
// text node or comment node
|
||||
const el =
|
||||
node.nodeType === 3
|
||||
node.nodeType === Node.TEXT_NODE
|
||||
? document.createTextNode(node.textContent!)
|
||||
: document.createComment(node.textContent!);
|
||||
return {
|
||||
@@ -241,6 +251,7 @@ function buildTree(
|
||||
el,
|
||||
info: [],
|
||||
refN: 0,
|
||||
currentNS: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+111
-52
@@ -137,7 +137,6 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
|
||||
|
||||
class CodeTarget {
|
||||
name: string;
|
||||
signature: string = "";
|
||||
indentLevel = 0;
|
||||
loopLevel = 0;
|
||||
code: string[] = [];
|
||||
@@ -334,7 +333,7 @@ export class CodeGenerator {
|
||||
|
||||
generateFunctions(fn: CodeTarget) {
|
||||
this.addLine("");
|
||||
this.addLine(`const ${fn.name} = ${fn.signature}`);
|
||||
this.addLine(`function ${fn.name}(ctx, node, key) {`);
|
||||
if (fn.hasCache) {
|
||||
this.addLine(`let cache = ctx.cache || {};`);
|
||||
this.addLine(`let nextCache = ctx.cache = {};`);
|
||||
@@ -344,8 +343,23 @@ export class CodeGenerator {
|
||||
}
|
||||
this.addLine(`}`);
|
||||
}
|
||||
|
||||
captureExpression(expr: string): string {
|
||||
/**
|
||||
* Captures variables that are used inside of an expression. This is useful
|
||||
* because in compiled code, almost all variables are accessed through the ctx
|
||||
* object. In the case of functions, that lookup in the context can be delayed
|
||||
* which can cause issues if the value has changed since the function was
|
||||
* defined.
|
||||
*
|
||||
* @param expr the expression to capture
|
||||
* @param forceCapture whether the expression should capture its scope even if
|
||||
* it doesn't contain a function. Useful when the expression will be used as
|
||||
* a function body.
|
||||
* @returns a new expression that uses the captured values
|
||||
*/
|
||||
captureExpression(expr: string, forceCapture: boolean = false): string {
|
||||
if (!forceCapture && !expr.includes("=>")) {
|
||||
return compileExpr(expr);
|
||||
}
|
||||
const tokens = compileExprToArray(expr);
|
||||
const mapping = new Map<string, string>();
|
||||
return tokens
|
||||
@@ -497,6 +511,10 @@ export class CodeGenerator {
|
||||
}
|
||||
// attributes
|
||||
const attrs: { [key: string]: string } = {};
|
||||
if (ast.ns) {
|
||||
// specific namespace uri
|
||||
attrs["block-ns"] = ast.ns;
|
||||
}
|
||||
for (let key in ast.attrs) {
|
||||
if (key.startsWith("t-attf")) {
|
||||
let expr = interpolate(ast.attrs[key]);
|
||||
@@ -531,7 +549,7 @@ export class CodeGenerator {
|
||||
if (isDynamic) {
|
||||
const str = ast.ref.replace(
|
||||
INTERP_REGEXP,
|
||||
(expr) => "${" + this.captureExpression(expr.slice(2, -2)) + "}"
|
||||
(expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}"
|
||||
);
|
||||
const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`);
|
||||
attrs["block-ref"] = String(idx);
|
||||
@@ -730,6 +748,10 @@ 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}];`);
|
||||
@@ -746,6 +768,13 @@ 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;
|
||||
@@ -932,13 +961,62 @@ 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) {
|
||||
props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`);
|
||||
const propName = /^[a-z_]+$/i.test(p) ? p : `'${p}'`;
|
||||
props.push(`${propName}: ${this.captureExpression(ast.props[p]) || undefined}`);
|
||||
if (p === "slots") {
|
||||
hasSlotsProp = true;
|
||||
}
|
||||
}
|
||||
|
||||
// slots
|
||||
const hasSlot = !!Object.keys(ast.slots).length;
|
||||
let slotDef: string = "";
|
||||
if (hasSlot) {
|
||||
let ctxStr = "ctx";
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = this.generateId("ctx");
|
||||
this.addLine(`const ${ctxStr} = capture(ctx);`);
|
||||
}
|
||||
let slotStr: string[] = [];
|
||||
const initialTarget = this.target;
|
||||
for (let slotName in ast.slots) {
|
||||
let name = this.generateId("slot");
|
||||
const slot = new CodeTarget(name);
|
||||
this.functions.push(slot);
|
||||
this.target = slot;
|
||||
const subCtx: Context = createContext(ctx);
|
||||
this.compileAST(ast.slots[slotName].content, subCtx);
|
||||
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
|
||||
const scope = ast.slots[slotName].scope;
|
||||
if (scope) {
|
||||
params.push(`__scope: "${scope}"`);
|
||||
}
|
||||
if (ast.slots[slotName].attrs) {
|
||||
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) {
|
||||
params.push(`${n}: ${compileExpr(v) || undefined}`);
|
||||
}
|
||||
}
|
||||
const slotInfo = `{${params.join(", ")}}`;
|
||||
if (this.hasRef) {
|
||||
slot.code.unshift(` const refs = ctx.__owl__.refs`);
|
||||
slotStr.push(`'${slotName}': ${slotInfo}`);
|
||||
} else {
|
||||
slotStr.push(`'${slotName}': ${slotInfo}`);
|
||||
}
|
||||
}
|
||||
this.target = initialTarget;
|
||||
slotDef = `{${slotStr.join(", ")}}`;
|
||||
}
|
||||
|
||||
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
|
||||
props.push(`slots: ${slotDef}`);
|
||||
}
|
||||
|
||||
const propStr = `{${props.join(",")}}`;
|
||||
|
||||
let propString = propStr;
|
||||
@@ -950,6 +1028,17 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -961,41 +1050,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
if (this.dev) {
|
||||
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;
|
||||
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`);
|
||||
}
|
||||
|
||||
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
|
||||
@@ -1009,11 +1064,6 @@ export class CodeGenerator {
|
||||
}
|
||||
const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
|
||||
let blockExpr = `component(${blockArgs})`;
|
||||
if (Object.keys(extraArgs).length) {
|
||||
this.shouldDefineAssign = true;
|
||||
const content = Object.keys(extraArgs).map((k) => `${k}: ${extraArgs[k]}`);
|
||||
blockExpr = `assign(${blockExpr}, {${content.join(", ")}})`;
|
||||
}
|
||||
if (ast.isDynamic) {
|
||||
blockExpr = `toggler(${expr}, ${blockExpr})`;
|
||||
}
|
||||
@@ -1032,24 +1082,33 @@ 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("defaultSlot");
|
||||
let name = this.generateId("defaultContent");
|
||||
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}, ${name}, ${dynamic})`;
|
||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
||||
} else {
|
||||
if (dynamic) {
|
||||
let name = this.generateId("slot");
|
||||
this.addLine(`const ${name} = ${slotName};`);
|
||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}))`;
|
||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
|
||||
} else {
|
||||
blockString = `callSlot(ctx, node, key, ${slotName})`;
|
||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
||||
}
|
||||
}
|
||||
if (block) {
|
||||
|
||||
+145
-46
@@ -49,6 +49,7 @@ export interface ASTDomNode {
|
||||
shouldTrim: boolean;
|
||||
shouldNumberize: boolean;
|
||||
} | null;
|
||||
ns: string | null;
|
||||
}
|
||||
|
||||
export interface ASTMulti {
|
||||
@@ -117,12 +118,13 @@ export interface ASTComponent {
|
||||
isDynamic: boolean;
|
||||
dynamicProps: string | null;
|
||||
props: { [name: string]: string };
|
||||
slots: { [name: string]: AST };
|
||||
slots: { [name: string]: { content: AST; attrs?: { [key: string]: string }; scope?: string } };
|
||||
}
|
||||
|
||||
export interface ASTSlot {
|
||||
type: ASTType.TSlot;
|
||||
name: string;
|
||||
attrs: { [key: string]: string };
|
||||
defaultContent: AST | null;
|
||||
}
|
||||
|
||||
@@ -171,11 +173,13 @@ export type AST =
|
||||
// -----------------------------------------------------------------------------
|
||||
interface ParsingContext {
|
||||
inPreTag: boolean;
|
||||
inSVG: boolean;
|
||||
}
|
||||
|
||||
export function parse(xml: string | Node): AST {
|
||||
const node = xml instanceof Element ? xml : parseXML(`<t>${xml}</t>`).firstChild!;
|
||||
const ctx = { inPreTag: false };
|
||||
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element);
|
||||
normalizeXML(node);
|
||||
const ctx = { inPreTag: false, inSVG: false };
|
||||
const ast = parseNode(node, ctx);
|
||||
if (!ast) {
|
||||
return { type: ASTType.Text, value: "" };
|
||||
@@ -240,7 +244,7 @@ const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
|
||||
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null {
|
||||
if (node.nodeType === 3) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
let value = node.textContent || "";
|
||||
if (!ctx.inPreTag) {
|
||||
if (lineBreakRE.test(value) && !value.trim()) {
|
||||
@@ -250,7 +254,7 @@ function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null
|
||||
}
|
||||
|
||||
return { type: ASTType.Text, value };
|
||||
} else if (node.nodeType === 8) {
|
||||
} else if (node.nodeType === Node.COMMENT_NODE) {
|
||||
return { type: ASTType.Comment, value: node.textContent || "" };
|
||||
}
|
||||
return null;
|
||||
@@ -289,18 +293,18 @@ const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
||||
|
||||
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
const { tagName } = node;
|
||||
let dynamicTag = null;
|
||||
if (node.hasAttribute("t-tag")) {
|
||||
dynamicTag = node.getAttribute("t-tag");
|
||||
node.removeAttribute("t-tag");
|
||||
}
|
||||
const dynamicTag = node.getAttribute("t-tag");
|
||||
node.removeAttribute("t-tag");
|
||||
if (tagName === "t" && !dynamicTag) {
|
||||
return null;
|
||||
}
|
||||
const children: AST[] = [];
|
||||
if (tagName === "pre") {
|
||||
ctx = { inPreTag: true };
|
||||
ctx.inPreTag = true;
|
||||
}
|
||||
const shouldAddSVGNS = tagName === "svg" || (tagName === "g" && !ctx.inSVG);
|
||||
ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
|
||||
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
|
||||
const ref = node.getAttribute("t-ref");
|
||||
node.removeAttribute("t-ref");
|
||||
|
||||
@@ -381,6 +385,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
ref,
|
||||
content: children,
|
||||
model,
|
||||
ns,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -590,7 +595,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
|
||||
if (ast && ast.type === ASTType.TComponent) {
|
||||
return {
|
||||
...ast,
|
||||
slots: { default: tcall },
|
||||
slots: { default: { content: tcall } },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -699,6 +704,20 @@ 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];
|
||||
@@ -722,10 +741,9 @@ 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-on-")) {
|
||||
throw new Error(
|
||||
"t-on is no longer supported on Component node. Consider passing a callback in props."
|
||||
);
|
||||
if (name.startsWith("t-")) {
|
||||
const message = directiveErrorMap.get(name.split("-").slice(0, 2).join("-"));
|
||||
throw new Error(message || `unsupported directive on Component: ${name}`);
|
||||
} else {
|
||||
props[name] = value;
|
||||
}
|
||||
@@ -738,6 +756,11 @@ 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
|
||||
@@ -759,14 +782,27 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
slotNode.remove();
|
||||
const slotAst = parseNode(slotNode, ctx);
|
||||
if (slotAst) {
|
||||
slots[name] = slotAst;
|
||||
const slotInfo: any = { content: slotAst };
|
||||
const attrs: { [key: string]: string } = {};
|
||||
for (let attributeName of slotNode.getAttributeNames()) {
|
||||
const value = slotNode.getAttribute(attributeName)!;
|
||||
if (attributeName === "t-slot-scope") {
|
||||
slotInfo.scope = value;
|
||||
continue;
|
||||
}
|
||||
attrs[attributeName] = value;
|
||||
}
|
||||
if (Object.keys(attrs).length) {
|
||||
slotInfo.attrs = attrs;
|
||||
}
|
||||
slots[name] = slotInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// default slot
|
||||
const defaultContent = parseChildNodes(clone, ctx);
|
||||
if (defaultContent) {
|
||||
slots.default = defaultContent;
|
||||
slots.default = { content: defaultContent };
|
||||
}
|
||||
}
|
||||
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
|
||||
@@ -780,9 +816,17 @@ 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: node.getAttribute("t-slot")!,
|
||||
name,
|
||||
attrs,
|
||||
defaultContent: parseChildNodes(node, ctx),
|
||||
};
|
||||
}
|
||||
@@ -819,34 +863,17 @@ function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
|
||||
return { type: ASTType.Multi, content: children };
|
||||
}
|
||||
}
|
||||
function parseXML(xml: string): Document {
|
||||
const parser = new DOMParser();
|
||||
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
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]");
|
||||
/**
|
||||
* 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]");
|
||||
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
||||
let node = tbranch[i];
|
||||
let prevElem = node.previousElementSibling!;
|
||||
@@ -880,6 +907,78 @@ function parseXML(xml: string): Document {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the content of an Element so that t-esc directives on components
|
||||
* are removed and instead places a <t t-esc=""> as the default slot of the
|
||||
* component. Also throws if the component already has content. This function
|
||||
* modifies the Element in place.
|
||||
*
|
||||
* @param el the element containing the tree that should be normalized
|
||||
*/
|
||||
function normalizeTEsc(el: Element) {
|
||||
const elements = [...el.querySelectorAll("[t-esc]")].filter(
|
||||
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
|
||||
);
|
||||
for (const el of elements) {
|
||||
if (el.childNodes.length) {
|
||||
throw new Error("Cannot have t-esc on a component that already has content");
|
||||
}
|
||||
const value = el.getAttribute("t-esc");
|
||||
el.removeAttribute("t-esc");
|
||||
const t = el.ownerDocument.createElement("t");
|
||||
if (value != null) {
|
||||
t.setAttribute("t-esc", value);
|
||||
}
|
||||
el.appendChild(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the tree inside a given element and do some preliminary validation
|
||||
* on it. This function modifies the Element in place.
|
||||
*
|
||||
* @param el the element containing the tree that should be normalized
|
||||
*/
|
||||
function normalizeXML(el: Element) {
|
||||
normalizeTIf(el);
|
||||
normalizeTEsc(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an XML string into an XML document, throwing errors on parser errors
|
||||
* instead of returning an XML document containing the parseerror.
|
||||
*
|
||||
* @param xml the string to parse
|
||||
* @returns an XML document corresponding to the content of the string
|
||||
*/
|
||||
function parseXML(xml: string): XMLDocument {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(xml, "text/xml");
|
||||
if (doc.getElementsByTagName("parsererror").length) {
|
||||
let msg = "Invalid XML in template.";
|
||||
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
||||
if (parsererrorText) {
|
||||
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
||||
const re = /\d+/g;
|
||||
const firstMatch = re.exec(parsererrorText);
|
||||
if (firstMatch) {
|
||||
const lineNumber = Number(firstMatch[0]);
|
||||
const line = xml.split("\n")[lineNumber - 1];
|
||||
const secondMatch = re.exec(parsererrorText);
|
||||
if (line && secondMatch) {
|
||||
const columnIndex = Number(secondMatch[0]) - 1;
|
||||
if (line[columnIndex]) {
|
||||
msg +=
|
||||
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ComponentNode } from "./component_node";
|
||||
export class Component {
|
||||
static template: string = "";
|
||||
static style: string = "";
|
||||
static props?: any;
|
||||
|
||||
props: any;
|
||||
env: Env;
|
||||
|
||||
@@ -72,7 +72,9 @@ export function getCurrent(): ComponentNode | null {
|
||||
|
||||
type LifecycleHook = Function;
|
||||
|
||||
export class ComponentNode<T extends typeof Component = any> implements VNode<ComponentNode> {
|
||||
export class ComponentNode<T extends typeof Component = typeof Component>
|
||||
implements VNode<ComponentNode>
|
||||
{
|
||||
el?: HTMLElement | Text | undefined;
|
||||
app: App;
|
||||
fiber: Fiber | null = null;
|
||||
@@ -85,7 +87,6 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
level: number;
|
||||
childEnv: Env;
|
||||
children: { [key: string]: ComponentNode } = Object.create(null);
|
||||
slots: any = {};
|
||||
refs: any = {};
|
||||
|
||||
willStart: LifecycleHook[] = [];
|
||||
@@ -120,6 +121,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
}
|
||||
|
||||
async initiateRender(fiber: Fiber | MountFiber) {
|
||||
this.fiber = fiber;
|
||||
if (this.mounted.length) {
|
||||
fiber.root.mounted.push(fiber);
|
||||
}
|
||||
@@ -127,7 +129,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
try {
|
||||
await Promise.all(this.willStart.map((f) => f.call(component)));
|
||||
} catch (e) {
|
||||
handleError(this, e as Error);
|
||||
handleError({ node: this, error: e });
|
||||
return;
|
||||
}
|
||||
if (this.status === STATUS.NEW && this.fiber === fiber) {
|
||||
@@ -136,21 +138,33 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
}
|
||||
|
||||
async render() {
|
||||
let fiber = this.fiber;
|
||||
if (fiber && !fiber.bdom && !fibersInError.has(fiber)) {
|
||||
return fiber.root.promise;
|
||||
const current = this.fiber;
|
||||
if (current && !current.bdom && !fibersInError.has(current)) {
|
||||
return current.root.promise;
|
||||
}
|
||||
if (!this.bdom && !fiber) {
|
||||
if (!this.bdom && !current) {
|
||||
// should find a way to return the future mounting promise
|
||||
return;
|
||||
}
|
||||
fiber = makeRootFiber(this);
|
||||
const fiber = makeRootFiber(this);
|
||||
this.fiber = fiber;
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status === STATUS.DESTROYED) {
|
||||
return;
|
||||
}
|
||||
if (this.fiber === fiber) {
|
||||
// We only want to actually render the component if the following two
|
||||
// conditions are true:
|
||||
// * this.fiber: it could be null, in which case the render has been cancelled
|
||||
// * (current || !fiber.parent): if current is not null, this means that the
|
||||
// render function was called when a render was already occurring. In this
|
||||
// case, the pending rendering was cancelled, and the fiber needs to be
|
||||
// rendered to complete the work. If current is null, we check that the
|
||||
// fiber has no parent. If that is the case, the fiber was downgraded from
|
||||
// a root fiber to a child fiber in the previous microtick, because it was
|
||||
// embedded in a rendering coming from above, so the fiber will be rendered
|
||||
// in the next microtick anyway, so we should not render it again.
|
||||
if (this.fiber && (current || !fiber.parent)) {
|
||||
this._render(fiber);
|
||||
}
|
||||
return fiber.root.promise;
|
||||
@@ -161,7 +175,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
fiber.bdom = this.renderFn();
|
||||
fiber.root.counter--;
|
||||
} catch (e) {
|
||||
handleError(this, e as Error);
|
||||
handleError({ node: this, error: e });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +213,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
|
||||
async updateAndRender(props: any, parentFiber: Fiber) {
|
||||
// update
|
||||
const fiber = makeChildFiber(this, parentFiber);
|
||||
this.fiber = fiber;
|
||||
if (this.willPatch.length) {
|
||||
parentFiber.root.willPatch.push(fiber);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import type { Fiber } from "./fibers";
|
||||
|
||||
export const fibersInError: WeakMap<Fiber, Error> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: Error) => void)[]> = new WeakMap();
|
||||
// Maps fibers to thrown errors
|
||||
export const fibersInError: WeakMap<Fiber, any> = new WeakMap();
|
||||
export const nodeErrorHandlers: WeakMap<ComponentNode, ((error: any) => void)[]> = new WeakMap();
|
||||
|
||||
function _handleError(node: ComponentNode | null, error: Error, isFirstRound = false): boolean {
|
||||
function _handleError(node: ComponentNode | null, error: any, isFirstRound = false): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
@@ -25,7 +26,7 @@ function _handleError(node: ComponentNode | null, error: Error, isFirstRound = f
|
||||
h(error);
|
||||
propagate = false;
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,17 +39,12 @@ function _handleError(node: ComponentNode | null, error: Error, isFirstRound = f
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(entity: ComponentNode | Fiber, error: Error) {
|
||||
let node: ComponentNode;
|
||||
let fiber: Fiber;
|
||||
// soft type check on Fiber
|
||||
if ("node" in entity) {
|
||||
fiber = entity;
|
||||
node = entity.node;
|
||||
} else {
|
||||
node = entity;
|
||||
fiber = entity.fiber!;
|
||||
}
|
||||
type ErrorParams = { error: any } & ({ node: ComponentNode } | { fiber: Fiber });
|
||||
export function handleError(params: ErrorParams) {
|
||||
const error = params.error;
|
||||
const node = "node" in params ? params.node : params.fiber.node;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber!;
|
||||
|
||||
fibersInError.set(fiber.root, error);
|
||||
|
||||
const handled = _handleError(node, error, true);
|
||||
|
||||
@@ -70,7 +70,6 @@ export class Fiber {
|
||||
|
||||
constructor(node: ComponentNode, parent: Fiber | null) {
|
||||
this.node = node;
|
||||
node.fiber = this;
|
||||
this.parent = parent;
|
||||
if (parent) {
|
||||
const root = parent.root;
|
||||
@@ -159,7 +158,7 @@ export class RootFiber extends Fiber {
|
||||
// unregistering the fiber
|
||||
node.fiber = null;
|
||||
} catch (e) {
|
||||
if (!handleError(current || this, e)) {
|
||||
if (!handleError({ fiber: current || this, error: e })) {
|
||||
this.reject(e);
|
||||
}
|
||||
}
|
||||
@@ -206,7 +205,7 @@ export class MountFiber extends RootFiber {
|
||||
}
|
||||
node.fiber = null;
|
||||
} catch (e) {
|
||||
if (!handleError(current as Fiber, e)) {
|
||||
if (!handleError({ fiber: current as Fiber, error: e })) {
|
||||
this.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function onDestroyed(fn: () => Promise<void> | void | any) {
|
||||
node.destroyed.push(fn);
|
||||
}
|
||||
|
||||
export function onRender(fn: () => void | any) {
|
||||
export function onWillRender(fn: () => void | any) {
|
||||
const node = getCurrent()!;
|
||||
const renderFn = node.renderFn;
|
||||
node.renderFn = () => {
|
||||
@@ -49,6 +49,16 @@ export function onRender(fn: () => void | any) {
|
||||
};
|
||||
}
|
||||
|
||||
export function onRendered(fn: () => void | any) {
|
||||
const node = getCurrent()!;
|
||||
const renderFn = node.renderFn;
|
||||
node.renderFn = () => {
|
||||
const result = renderFn();
|
||||
fn();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function onError(fn: (error: Error) => void | any) {
|
||||
const node = getCurrent()!;
|
||||
let handlers = nodeErrorHandlers.get(node);
|
||||
|
||||
@@ -26,26 +26,23 @@ export function applyDefaultProps(props: { [key: string]: any }, ComponentClass:
|
||||
* visit recursively the props and all the children to check if they are valid.
|
||||
* This is why it is only done in 'dev' mode.
|
||||
*/
|
||||
|
||||
// message: { type: String, default: "hello" };
|
||||
|
||||
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) {
|
||||
const ComponentClass =
|
||||
typeof name !== "string" ? name : parent.constructor.components[name as string];
|
||||
const ComponentClass = (
|
||||
typeof name !== "string" ? name : parent.constructor.components[name]
|
||||
) as typeof Component;
|
||||
|
||||
applyDefaultProps(props, ComponentClass);
|
||||
|
||||
const propsDef = (<any>ComponentClass).props;
|
||||
const propsDef = ComponentClass.props;
|
||||
if (propsDef instanceof Array) {
|
||||
// list of strings (prop names)
|
||||
for (let i = 0, l = propsDef.length; i < l; i++) {
|
||||
const propName = propsDef[i];
|
||||
for (const propName of propsDef) {
|
||||
if (propName[propName.length - 1] === "?") {
|
||||
// optional prop
|
||||
break;
|
||||
}
|
||||
if (!(propName in props)) {
|
||||
throw new Error(`Missing props '${propsDef[i]}' (component '${ComponentClass.name}')`);
|
||||
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
|
||||
}
|
||||
}
|
||||
for (let key in props) {
|
||||
@@ -67,7 +64,9 @@ export const validateProps = function (name: string | typeof Component, props: a
|
||||
try {
|
||||
isValid = isValidProp(props[propName], propsDef[propName]);
|
||||
} catch (e) {
|
||||
e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
|
||||
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
|
||||
(e as Error).message
|
||||
})`;
|
||||
throw e;
|
||||
}
|
||||
if (!isValid) {
|
||||
|
||||
+3
-2
@@ -55,7 +55,7 @@ export { status } from "./component/status";
|
||||
export { Portal } from "./portal";
|
||||
export { Memo } from "./memo";
|
||||
export { css, xml } from "./tags";
|
||||
export { useState } from "./reactivity";
|
||||
export { useState, reactive } from "./reactivity";
|
||||
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
|
||||
export { EventBus, whenReady, loadFile, markup } from "./utils";
|
||||
|
||||
@@ -66,7 +66,8 @@ export {
|
||||
onWillUpdateProps,
|
||||
onWillPatch,
|
||||
onPatched,
|
||||
onRender,
|
||||
onWillRender,
|
||||
onRendered,
|
||||
onDestroyed,
|
||||
onError,
|
||||
} from "./component/lifecycle_hooks";
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ export class Memo extends Component {
|
||||
*/
|
||||
function shallowEqual(p1: any, p2: any): boolean {
|
||||
for (let k in p1) {
|
||||
if (p1[k] !== p2[k]) {
|
||||
if (k !== "slots" && p1[k] !== p2[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+221
-258
@@ -1,132 +1,28 @@
|
||||
import { ComponentNode, getCurrent } from "./component/component_node";
|
||||
import { onWillUnmount } from "./component/lifecycle_hooks";
|
||||
import { ComponentNode, getCurrent } from "./component/component_node";
|
||||
|
||||
type Observer = ComponentNode | Function;
|
||||
type Atom = any; // proxy linked to a unique observer and source
|
||||
type Source = any; // trackable that is not an atom
|
||||
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
|
||||
const TARGET = Symbol("Target");
|
||||
// Special key to subscribe to, to be notified of key creation/deletion
|
||||
const KEYCHANGES = Symbol("Key changes");
|
||||
|
||||
const sourceAtoms: WeakMap<Source, Map<any, ObserverSet>> = new WeakMap();
|
||||
const observerSourceAtom: WeakMap<Observer, Map<any, Atom>> = new WeakMap();
|
||||
type ObjectKey = string | number | symbol;
|
||||
type Target = object;
|
||||
type Callback = () => void;
|
||||
type Reactive<T extends Target = Target> = T & {
|
||||
[TARGET]: any;
|
||||
};
|
||||
|
||||
const SOURCE = Symbol("source");
|
||||
const OBSERVER = Symbol("observer");
|
||||
const KEYS = Symbol("keys");
|
||||
const ROOT = Symbol("root");
|
||||
const SEED = Symbol("Seed");
|
||||
|
||||
export function observe(source: any, observer: Observer): [Atom, Function] {
|
||||
if (!isTrackable(source)) {
|
||||
throw new Error("First argument is not trackable");
|
||||
}
|
||||
const unregisterObserver = registerObserver(observer);
|
||||
const newAtom = atom(source, observer);
|
||||
return [newAtom, unregisterObserver];
|
||||
}
|
||||
|
||||
function atom(source: any, observer: Observer) {
|
||||
return _atom(source, observer, true);
|
||||
}
|
||||
|
||||
function _atom(source: any, observer: Observer, seed = false) {
|
||||
if (isTrackable(source) && observerSourceAtom.has(observer)) {
|
||||
source = (source as any)[SOURCE] || source;
|
||||
const oldAtom = observerSourceAtom.get(observer)!.get(source);
|
||||
if (oldAtom) {
|
||||
if (seed) {
|
||||
oldAtom[SEED] = true;
|
||||
}
|
||||
return oldAtom;
|
||||
}
|
||||
if (!sourceAtoms.get(source)) {
|
||||
sourceAtoms.set(source, new Map([[ROOT, new ObserverSet()]]));
|
||||
}
|
||||
const newAtom = createAtom(source, observer);
|
||||
if (seed) {
|
||||
newAtom[SEED] = true;
|
||||
}
|
||||
observerSourceAtom.get(observer)!.set(source, newAtom);
|
||||
sourceAtoms.get(source)!.get(ROOT)!.add(newAtom);
|
||||
return newAtom;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
function createAtom(source: Source, observer: Observer): Atom {
|
||||
const keys: Set<any> = new Set();
|
||||
let seed: boolean = false;
|
||||
const newAtom: Atom = new Proxy(source as any, {
|
||||
set(target: any, key: any, value: any): boolean {
|
||||
if (key === SEED) {
|
||||
seed = value;
|
||||
return true;
|
||||
}
|
||||
if (!(key in target)) {
|
||||
target[key] = value;
|
||||
notify(sourceAtoms.get(source)!.get(ROOT)!);
|
||||
return true;
|
||||
}
|
||||
const current = target[key];
|
||||
if (current !== value) {
|
||||
target[key] = value;
|
||||
const observerSet = sourceAtoms.get(source)!.get(key);
|
||||
if (observerSet) {
|
||||
const clean = isTrackable(current);
|
||||
notify(observerSet, clean);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target: any, key: string): boolean {
|
||||
if (key in target) {
|
||||
const current = target[key];
|
||||
delete target[key];
|
||||
// notify source observers
|
||||
const clean = isTrackable(current);
|
||||
notify(sourceAtoms.get(source)!.get(ROOT)!, clean);
|
||||
const atoms = sourceAtoms.get(source)!;
|
||||
if (atoms.has(key)) {
|
||||
// clear source-key observers
|
||||
for (const atom of atoms.get(key)!) {
|
||||
atom[KEYS].delete(key);
|
||||
}
|
||||
atoms.delete(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get(target: any, key: any, proxy: any): any {
|
||||
switch (key) {
|
||||
case OBSERVER:
|
||||
return observer;
|
||||
case SOURCE:
|
||||
return source;
|
||||
case KEYS:
|
||||
return keys;
|
||||
case SEED:
|
||||
return seed;
|
||||
default:
|
||||
const value = target[key];
|
||||
// register observer to source-key
|
||||
if (!keys.has(key) && observerSourceAtom.has(observer)) {
|
||||
const atoms = sourceAtoms.get(source)!;
|
||||
if (!atoms.has(key)) {
|
||||
atoms.set(key, new ObserverSet());
|
||||
}
|
||||
atoms.get(key)!.add(newAtom);
|
||||
keys.add(key);
|
||||
}
|
||||
//
|
||||
return _atom(value, observer);
|
||||
}
|
||||
},
|
||||
});
|
||||
return newAtom;
|
||||
}
|
||||
|
||||
function isTrackable(value: any): boolean {
|
||||
/**
|
||||
* Checks whether a given value can be made into a reactive object.
|
||||
*
|
||||
* @param value the value to check
|
||||
* @returns whether the value can be made reactive
|
||||
*/
|
||||
function canBeMadeReactive(value: any): boolean {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!(value instanceof Date) &&
|
||||
!(value instanceof Promise) &&
|
||||
!(value instanceof String) &&
|
||||
@@ -134,144 +30,211 @@ function isTrackable(value: any): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function registerObserver(observer: Observer) {
|
||||
if (!observerSourceAtom.get(observer)) {
|
||||
observerSourceAtom.set(observer, new Map());
|
||||
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
|
||||
/**
|
||||
* Observes a given key on a target with an callback. The callback will be
|
||||
* called when the given key changes on the target.
|
||||
*
|
||||
* @param target the target whose key should be observed
|
||||
* @param key the key to observe (or Symbol(KEYCHANGES) for key creation
|
||||
* or deletion)
|
||||
* @param callback the function to call when the key changes
|
||||
*/
|
||||
function observeTargetKey(target: Target, key: ObjectKey, callback: Callback): void {
|
||||
if (!targetToKeysToCallbacks.get(target)) {
|
||||
targetToKeysToCallbacks.set(target, new Map());
|
||||
}
|
||||
return unregisterObserverAtoms.bind(null, observer, false);
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target)!;
|
||||
if (!keyToCallbacks.get(key)) {
|
||||
keyToCallbacks.set(key, new Set());
|
||||
}
|
||||
keyToCallbacks.get(key)!.add(callback);
|
||||
}
|
||||
|
||||
function unregisterObserverAtoms(observer: Observer, keepSeeds: boolean) {
|
||||
const observerAtoms = observerSourceAtom.get(observer)!;
|
||||
for (const [source, atom] of observerAtoms) {
|
||||
if (keepSeeds && atom[SEED]) {
|
||||
continue;
|
||||
}
|
||||
observerAtoms.delete(source);
|
||||
const atoms = sourceAtoms.get(source)!;
|
||||
atoms.get(ROOT)!.delete(atom);
|
||||
for (const key of atom[KEYS]) {
|
||||
atoms.get(key)!.delete(atom);
|
||||
}
|
||||
}
|
||||
if (!keepSeeds) {
|
||||
observerSourceAtom.delete(observer);
|
||||
}
|
||||
}
|
||||
|
||||
export function useState(state: any): Atom {
|
||||
const node = getCurrent()!;
|
||||
const [newAtom, unregisterObserver] = observe(state, node);
|
||||
onWillUnmount(() => unregisterObserver());
|
||||
return newAtom;
|
||||
}
|
||||
|
||||
class ObserverSet {
|
||||
nodeAntichain: Antichain = new Antichain();
|
||||
callbackSet: Set<Atom> = new Set();
|
||||
add(atom: Atom) {
|
||||
if (atom[OBSERVER] instanceof ComponentNode) {
|
||||
this.nodeAntichain.add(atom);
|
||||
} else {
|
||||
this.callbackSet.add(atom);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
delete(atom: Atom) {
|
||||
if (atom[OBSERVER] instanceof ComponentNode) {
|
||||
return this.nodeAntichain.delete(atom);
|
||||
} else {
|
||||
return this.callbackSet.delete(atom);
|
||||
}
|
||||
}
|
||||
union(other: ObserverSet) {
|
||||
for (const atom of other.nodeAntichain) {
|
||||
this.nodeAntichain.add(atom);
|
||||
}
|
||||
for (const callback of other.callbackSet) {
|
||||
this.callbackSet.add(callback);
|
||||
}
|
||||
}
|
||||
notify() {
|
||||
for (const atom of this.nodeAntichain) {
|
||||
// an observer cannot be found twice here: <= in add is based on observers
|
||||
atom[OBSERVER].render();
|
||||
}
|
||||
const called = new Set();
|
||||
for (const atom of this.callbackSet) {
|
||||
const observer = atom[OBSERVER];
|
||||
if (!called.has(observer)) {
|
||||
observer();
|
||||
}
|
||||
called.add(observer);
|
||||
}
|
||||
}
|
||||
*[Symbol.iterator]() {
|
||||
yield* this.nodeAntichain;
|
||||
yield* this.callbackSet;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to optimize this!
|
||||
function isLessOrEqual(node1: ComponentNode, node2: ComponentNode) {
|
||||
let current: any = node1;
|
||||
if (current.level <= node2.level) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
if (current === node2) {
|
||||
return true;
|
||||
}
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set of atoms linked to observers of type ComponentNode
|
||||
class Antichain extends Set<Atom> {
|
||||
level?: number;
|
||||
add(atom: Atom) {
|
||||
const node = atom[OBSERVER];
|
||||
if (this.level === node.level || this.level === undefined) {
|
||||
super.add(atom);
|
||||
this.level = node.level;
|
||||
return this;
|
||||
}
|
||||
let willAdd = false;
|
||||
for (const atom2 of this) {
|
||||
const node2 = atom2[OBSERVER];
|
||||
if (!willAdd && isLessOrEqual(node, node2)) {
|
||||
return this;
|
||||
} else if (isLessOrEqual(node2, node)) {
|
||||
super.delete(atom2);
|
||||
willAdd = true;
|
||||
}
|
||||
}
|
||||
super.add(atom);
|
||||
this.level = NaN;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
let toNotify: ObserverSet | null = null;
|
||||
let toClean: Set<Observer> = new Set();
|
||||
async function notify(observers: ObserverSet, clean = false) {
|
||||
if (clean) {
|
||||
for (const atom of observers) {
|
||||
toClean.add(atom[OBSERVER]);
|
||||
}
|
||||
}
|
||||
if (toNotify) {
|
||||
toNotify.union(observers);
|
||||
/**
|
||||
* Notify Reactives that are observing a given target that a key has changed on
|
||||
* the target.
|
||||
*
|
||||
* @param target target whose Reactives should be notified that the target was
|
||||
* changed.
|
||||
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
|
||||
* or deleted)
|
||||
*/
|
||||
function notifyReactives(target: Target, key: ObjectKey): void {
|
||||
const keyToCallbacks = targetToKeysToCallbacks.get(target);
|
||||
if (!keyToCallbacks) {
|
||||
return;
|
||||
}
|
||||
toNotify = new ObserverSet();
|
||||
toNotify.union(observers);
|
||||
await Promise.resolve();
|
||||
for (const observer of toClean) {
|
||||
unregisterObserverAtoms(observer, true);
|
||||
const callbacks = keyToCallbacks.get(key);
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
// Loop on copy because clearReactivesForCallback will modify the set in place
|
||||
for (const callback of [...callbacks]) {
|
||||
clearReactivesForCallback(callback);
|
||||
callback();
|
||||
}
|
||||
toClean.clear();
|
||||
toNotify.notify();
|
||||
toNotify = null;
|
||||
}
|
||||
|
||||
const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
|
||||
/**
|
||||
* Clears all subscriptions of the Reactives associated with a given callback.
|
||||
*
|
||||
* @param callback the callback for which the reactives need to be cleared
|
||||
*/
|
||||
function clearReactivesForCallback(callback: Callback): void {
|
||||
const targetsToClear = callbacksToTargets.get(callback);
|
||||
if (!targetsToClear) {
|
||||
return;
|
||||
}
|
||||
for (const target of targetsToClear) {
|
||||
const observedKeys = targetToKeysToCallbacks.get(target);
|
||||
if (!observedKeys) {
|
||||
continue;
|
||||
}
|
||||
for (const callbacks of observedKeys.values()) {
|
||||
callbacks.delete(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reactiveCache = new WeakMap<Target, Map<Callback, Reactive>>();
|
||||
/**
|
||||
* Creates a reactive proxy for an object. Reading data on the reactive object
|
||||
* subscribes to changes to the data. Writing data on the object will cause the
|
||||
* notify callback to be called if there are suscriptions to that data. Nested
|
||||
* objects and arrays are automatically made reactive as well.
|
||||
*
|
||||
* Whenever you are notified of a change, all subscriptions are cleared, and if
|
||||
* you would like to be notified of any further changes, you should go read
|
||||
* the underlying data again. We assume that if you don't go read it again after
|
||||
* being notified, it means that you are no longer interested in that data.
|
||||
*
|
||||
* Subscriptions:
|
||||
* + Reading a property on an object will subscribe you to changes in the value
|
||||
* of that property.
|
||||
* + Accessing an object keys (eg with Object.keys or with `for..in`) will
|
||||
* subscribe you to the creation/deletion of keys. Checking the presence of a
|
||||
* key on the object with 'in' has the same effect.
|
||||
* - getOwnPropertyDescriptor does not currently subscribe you to the property.
|
||||
* This is a choice that was made because changing a key's value will trigger
|
||||
* this trap and we do not want to subscribe by writes. This also means that
|
||||
* Object.hasOwnProperty doesn't subscribe as it goes through this trap.
|
||||
*
|
||||
* @param target the object for which to create a reactive proxy
|
||||
* @param callback the function to call when an observed property of the
|
||||
* reactive has changed
|
||||
* @returns a proxy that tracks changes to it
|
||||
*/
|
||||
export function reactive<T extends Target>(target: T, callback: Callback): Reactive<T> {
|
||||
if (!canBeMadeReactive(target)) {
|
||||
throw new Error(`Cannot make the given value reactive`);
|
||||
}
|
||||
const originalTarget = (target as Reactive)[TARGET];
|
||||
if (originalTarget) {
|
||||
return reactive(originalTarget, callback);
|
||||
}
|
||||
if (!reactiveCache.has(target)) {
|
||||
reactiveCache.set(target, new Map());
|
||||
}
|
||||
const reactivesForTarget = reactiveCache.get(target)!;
|
||||
if (!reactivesForTarget.has(callback)) {
|
||||
const proxy = new Proxy(target, {
|
||||
get(target: any, key: ObjectKey, proxy: Reactive<T>) {
|
||||
if (key === TARGET) {
|
||||
return target;
|
||||
}
|
||||
observeTargetKey(target, key, callback);
|
||||
const value = Reflect.get(target, key, proxy);
|
||||
if (!canBeMadeReactive(value)) {
|
||||
return value;
|
||||
}
|
||||
return reactive(value, callback);
|
||||
},
|
||||
set(target, key, value, proxy) {
|
||||
const isNewKey = !Object.hasOwnProperty.call(target, key);
|
||||
const originalValue = Reflect.get(target, key, proxy);
|
||||
const ret = Reflect.set(target, key, value, proxy);
|
||||
if (isNewKey) {
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
}
|
||||
// While Array length may trigger the set trap, it's not actually set by this
|
||||
// method but is updated behind the scenes, and the trap is not called with the
|
||||
// new value. We disable the "same-value-optimization" for it because of that.
|
||||
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
|
||||
notifyReactives(target, key);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
const ret = Reflect.deleteProperty(target, key);
|
||||
notifyReactives(target, KEYCHANGES);
|
||||
notifyReactives(target, key);
|
||||
return ret;
|
||||
},
|
||||
ownKeys(target) {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.ownKeys(target);
|
||||
},
|
||||
has(target, key) {
|
||||
// TODO: this observes all key changes instead of only the presence of the argument key
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
return Reflect.has(target, key);
|
||||
},
|
||||
});
|
||||
reactivesForTarget.set(callback, proxy);
|
||||
if (!callbacksToTargets.has(callback)) {
|
||||
callbacksToTargets.set(callback, new Set());
|
||||
}
|
||||
callbacksToTargets.get(callback)!.add(target);
|
||||
}
|
||||
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
@@ -5,9 +5,9 @@ import { globalTemplates } from "./app/template_set";
|
||||
// Global templates
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export function xml(strings: TemplateStringsArray, ...args: any[]) {
|
||||
export function xml(...args: Parameters<typeof String.raw>) {
|
||||
const name = `__template__${xml.nextId++}`;
|
||||
const value = String.raw(strings, ...args);
|
||||
const value = String.raw(...args);
|
||||
globalTemplates[name] = value;
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 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(\`<span><block-text-0/><block-text-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['context'][ctx['props'].key].n;
|
||||
let d2 = ctx['state'].x;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 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;
|
||||
|
||||
let block1 = createBlock(\`<p><block-child-0/></p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`ComponentC\`, {key: ctx['props'].key}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState concurrent renderings 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;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`ComponentB\`, {key: ctx['context'].key}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Reactivity: useState destroyed component before being mounted is inactive 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { createBlock, mount } from "../../src/blockdom";
|
||||
import { makeTestFixture } from "./helpers";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Setup and helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const XHTML_URI = "http://www.w3.org/1999/xhtml";
|
||||
const SVG_URI = "http://www.w3.org/2000/svg";
|
||||
let fixture: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.remove();
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
describe("namespace", () => {
|
||||
test("default namespace is xhtml", () => {
|
||||
const block = createBlock(`<tag/>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<tag></tag>");
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(XHTML_URI);
|
||||
});
|
||||
|
||||
test("namespace can be changed with block-ns", () => {
|
||||
const block = createBlock(`<tag block-ns="${SVG_URI}"/>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<tag></tag>");
|
||||
expect(fixture.firstElementChild!.namespaceURI).toBe(SVG_URI);
|
||||
});
|
||||
|
||||
test("namespace is kept for children", () => {
|
||||
const block = createBlock(
|
||||
`<parent block-ns="${SVG_URI}"><child><subchild/></child><child/></parent>`
|
||||
);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<parent><child><subchild></subchild></child><child></child></parent>"
|
||||
);
|
||||
const parent = fixture.firstElementChild!;
|
||||
const child1 = parent.firstElementChild!;
|
||||
const subchild = child1.firstElementChild!;
|
||||
const child2 = child1.nextElementSibling!;
|
||||
expect(parent.namespaceURI).toBe(SVG_URI);
|
||||
expect(child1.namespaceURI).toBe(SVG_URI);
|
||||
expect(child2.namespaceURI).toBe(SVG_URI);
|
||||
expect(subchild.namespaceURI).toBe(SVG_URI);
|
||||
});
|
||||
|
||||
test("various namespaces in same block", () => {
|
||||
const block = createBlock(`<none><one block-ns="one"/><two block-ns="two"/></none>`);
|
||||
const tree = block();
|
||||
mount(tree, fixture);
|
||||
expect(fixture.innerHTML).toBe("<none><one></one><two></two></none>");
|
||||
const none = fixture.firstElementChild!;
|
||||
const one = none.firstElementChild!;
|
||||
const two = one.nextElementSibling!;
|
||||
expect(none.namespaceURI).toBe(XHTML_URI);
|
||||
expect(one.namespaceURI).toBe("one");
|
||||
expect(two.namespaceURI).toBe("two");
|
||||
});
|
||||
});
|
||||
@@ -9,8 +9,7 @@ exports[`t-on can bind event handler 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['add'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['add'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -116,10 +115,8 @@ exports[`t-on can bind two event handlers 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['handleClick'];
|
||||
let d1 = [v1, ctx];
|
||||
const v2 = ctx['handleDblClick'];
|
||||
let d2 = [v2, ctx];
|
||||
let d1 = [ctx['handleClick'], ctx];
|
||||
let d2 = [ctx['handleDblClick'], ctx];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
@@ -134,8 +131,7 @@ exports[`t-on handler is bound to proper owner 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['add'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['add'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -155,8 +151,7 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`value\`] = v_block1[i1];
|
||||
let key1 = ctx['value'];
|
||||
const v1 = ctx['add'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['add'], ctx];
|
||||
c_block1[i1] = withKey(block2([d1]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
@@ -173,8 +168,7 @@ exports[`t-on handler is bound to proper owner, part 3 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['add'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['add'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -202,8 +196,7 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['add'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['add'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -242,8 +235,7 @@ exports[`t-on receive event in first argument 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['add'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['add'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -258,10 +250,8 @@ exports[`t-on t-on modifiers (native listener) basic support for native listener
|
||||
let block1 = createBlock(\`<div class=\\"myClass\\" block-handler-0=\\"click\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['divClicked'];
|
||||
let d1 = [v1, ctx];
|
||||
const v2 = ctx['btnClicked'];
|
||||
let d2 = [v2, ctx];
|
||||
let d1 = [ctx['divClicked'], ctx];
|
||||
let d2 = [ctx['btnClicked'], ctx];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
@@ -276,8 +266,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = `
|
||||
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-text-1/></button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['onClick'], ctx];
|
||||
let d2 = ctx['text'];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
@@ -293,8 +282,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = `
|
||||
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-child-0/></button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['onClick'], ctx];
|
||||
let b2 = safeOutput(ctx['html']);
|
||||
return block1([d1], [b2]);
|
||||
}
|
||||
@@ -310,10 +298,8 @@ exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] =
|
||||
let block1 = createBlock(\`<div block-handler-0=\\"click.capture\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onCapture'];
|
||||
let d1 = [\\"capture\\", v1, ctx];
|
||||
const v2 = ctx['doSomething'];
|
||||
let d2 = [v2, ctx];
|
||||
let d1 = [\\"capture\\", ctx['onCapture'], ctx];
|
||||
let d2 = [ctx['doSomething'], ctx];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
@@ -343,8 +329,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifi
|
||||
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent.self\\"><span>Button</span></button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [\\"prevent\\",\\"self\\", v1, ctx];
|
||||
let d1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -359,12 +344,9 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop mod
|
||||
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button 1</button><button block-handler-1=\\"click.stop\\">Button 2</button><button block-handler-2=\\"click.prevent.stop\\">Button 3</button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
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];
|
||||
let d1 = [\\"prevent\\", ctx['onClickPrevented'], ctx];
|
||||
let d2 = [\\"stop\\", ctx['onClickStopped'], ctx];
|
||||
let d3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx];
|
||||
return block1([d1, d2, d3]);
|
||||
}
|
||||
}"
|
||||
@@ -406,8 +388,7 @@ exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifi
|
||||
let block1 = createBlock(\`<div><button block-handler-0=\\"click.self.prevent\\"><span>Button</span></button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [\\"self\\",\\"prevent\\", v1, ctx];
|
||||
let d1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -422,10 +403,8 @@ exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = `
|
||||
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><span>Button</span></button><button block-handler-1=\\"click.self\\"><span>Button</span></button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [v1, ctx];
|
||||
const v2 = ctx['onClickSelf'];
|
||||
let d2 = [\\"self\\", v2, ctx];
|
||||
let d1 = [ctx['onClick'], ctx];
|
||||
let d2 = [\\"self\\", ctx['onClickSelf'], ctx];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
@@ -440,10 +419,8 @@ exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`
|
||||
let block1 = createBlock(\`<div block-handler-0=\\"click.synthetic\\"><button block-handler-1=\\"click.synthetic\\">Button</button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['divClicked'];
|
||||
let d1 = [\\"synthetic\\", v1, ctx];
|
||||
const v2 = ctx['btnClicked'];
|
||||
let d2 = [\\"synthetic\\", v2, ctx];
|
||||
let d1 = [\\"synthetic\\", ctx['divClicked'], ctx];
|
||||
let d2 = [\\"synthetic\\", ctx['btnClicked'], ctx];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
@@ -523,8 +500,7 @@ exports[`t-on t-on with t-call 1`] = `
|
||||
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['update'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['update'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -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_14 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
|
||||
const callTemplate_2 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
|
||||
|
||||
let block1 = createBlock(\`<div><header><nav class=\\"navbar navbar-expand-md navbar-light bg-light\\"><a block-attribute-0=\\"href\\"><b style=\\"color:#777;\\"><block-text-1/></b></a><button type=\\"button\\" class=\\"navbar-toggler\\" data-toggle=\\"collapse\\" data-target=\\"#top_menu_collapse\\"><span class=\\"navbar-toggler-icon\\"/></button><div class=\\"collapse navbar-collapse\\" id=\\"top_menu_collapse\\" aria-expanded=\\"false\\"><ul class=\\"nav navbar-nav ml-auto text-right\\" id=\\"top_menu\\"><block-child-0/><li class=\\"nav-item divider\\"/><block-child-1/></ul><div><div class=\\"input-group input-group-sm\\"><div class=\\"input-group-prepend input-group-sm\\"><button class=\\"btn btn-default fa fa-cog\\" title=\\"Settings\\" block-handler-2=\\"click\\"/><button class=\\"btn btn-default\\" block-handler-3=\\"click\\"> More </button><block-child-2/></div><input class=\\"form-control\\" type=\\"text\\" placeholder=\\"Search\\" aria-label=\\"Search\\" name=\\"search\\" block-attribute-4=\\"value\\" block-handler-5=\\"keyup\\" block-handler-6=\\"change\\" block-ref=\\"7\\"/><div class=\\"input-group-append\\"><button class=\\"btn btn-default fa fa-eraser\\" block-handler-8=\\"click\\"/></div></div></div></div></nav></header><div class=\\"container-fluid\\" block-ref=\\"9\\"><div class=\\"row\\"><!--div class=\\"form-group col-md-6\\">
|
||||
<h5>Search options</h5>
|
||||
@@ -223,9 +223,7 @@ exports[`misc other complex template 1`] = `
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`project\`] = v_block2[i1];
|
||||
let key1 = ctx['project'].id;
|
||||
const v1 = ctx['selectProject'];
|
||||
const v2 = ctx['project'];
|
||||
let d3 = [v1(v2), ctx];
|
||||
let d3 = [ctx['selectProject'](ctx['project']), ctx];
|
||||
let d4 = ctx['project'].name;
|
||||
c_block2[i1] = withKey(block3([d3, d4]), key1);
|
||||
}
|
||||
@@ -257,10 +255,8 @@ exports[`misc other complex template 1`] = `
|
||||
}
|
||||
b4 = multi([b5, b6]);
|
||||
}
|
||||
const v3 = ctx['toggleSettingsMenu'];
|
||||
let d11 = [v3, ctx];
|
||||
const v4 = ctx['toggleMore'];
|
||||
let d12 = [v4, ctx];
|
||||
let d11 = [ctx['toggleSettingsMenu'], ctx];
|
||||
let d12 = [ctx['toggleMore'], ctx];
|
||||
if (ctx['categories']&&ctx['categories'].length>1) {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);
|
||||
@@ -277,13 +273,10 @@ exports[`misc other complex template 1`] = `
|
||||
b14 = block14([], [b15]);
|
||||
}
|
||||
let d16 = ctx['search'].value;
|
||||
const v5 = ctx['updateFilter'];
|
||||
let d17 = [v5, ctx];
|
||||
const v6 = ctx['updateFilter'];
|
||||
let d18 = [v6, ctx];
|
||||
let d17 = [ctx['updateFilter'], ctx];
|
||||
let d18 = [ctx['updateFilter'], ctx];
|
||||
let d19 = (el) => refs[\`search_input\`] = el;
|
||||
const v7 = ctx['clearSearch'];
|
||||
let d20 = [v7, ctx];
|
||||
let d20 = [ctx['clearSearch'], ctx];
|
||||
let d21 = (el) => refs[\`settings_menu\`] = el;
|
||||
if (ctx['triggers']) {
|
||||
ctx = Object.create(ctx);
|
||||
@@ -297,8 +290,7 @@ 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;
|
||||
const v8 = ctx['updateTriggerDisplay'];
|
||||
let d26 = [v8, ctx];
|
||||
let d26 = [ctx['updateTriggerDisplay'], ctx];
|
||||
let d27 = \`trigger_\${ctx['trigger'].id}\`;
|
||||
let d28 = ctx['trigger'].name;
|
||||
b20 = block20([d22, d23, d24, d25, d26, d27, d28]);
|
||||
@@ -307,19 +299,15 @@ exports[`misc other complex template 1`] = `
|
||||
}
|
||||
ctx = ctx.__proto__;
|
||||
let b18 = list(c_block18);
|
||||
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 d29 = [ctx['triggerAll'], ctx];
|
||||
let d30 = [ctx['triggerNone'], ctx];
|
||||
let d31 = [ctx['triggerDefault'], ctx];
|
||||
let d32 = [ctx['toggleSettingsMenu'], ctx];
|
||||
let b21 = block21([d29, d30, d31, d32]);
|
||||
b17 = multi([b18, b21]);
|
||||
}
|
||||
if (ctx['load_infos']) {
|
||||
b22 = callTemplate_14.call(this, ctx, node, key + \`__13\`);
|
||||
b22 = callTemplate_2.call(this, ctx, node, key + \`__1\`);
|
||||
}
|
||||
if (ctx['message']) {
|
||||
let d33 = ctx['message'];
|
||||
@@ -328,8 +316,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 + \`__15\`, node, ctx);
|
||||
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__16\`, node, ctx);
|
||||
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__3\`, node, ctx);
|
||||
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__4\`, 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]);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`properly support svg add proper namespace to g tags 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(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg add proper namespace to svg 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(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg namespace to g tags not added if already in svg namespace 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(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly support svg namespace to svg tags added even if already in svg namespace 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(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg block-ns=\\"http://www.w3.org/2000/svg\\"/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -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(
|
||||
|
||||
+234
-47
@@ -46,6 +46,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,6 +74,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
content: [],
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,6 +88,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
content: [{ type: ASTType.Text, value: "some text" }],
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,6 +101,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{ type: ASTType.Text, value: "some text" },
|
||||
{
|
||||
@@ -108,6 +112,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "inside" }],
|
||||
},
|
||||
],
|
||||
@@ -126,6 +131,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [],
|
||||
},
|
||||
{
|
||||
@@ -136,6 +142,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [],
|
||||
},
|
||||
],
|
||||
@@ -155,6 +162,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [],
|
||||
},
|
||||
],
|
||||
@@ -170,10 +178,83 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "foo" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("svg dom node", async () => {
|
||||
expect(
|
||||
parse(
|
||||
`<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/></svg>`
|
||||
)
|
||||
).toEqual({
|
||||
attrs: {
|
||||
height: "90px",
|
||||
width: "100px",
|
||||
},
|
||||
content: [
|
||||
{
|
||||
attrs: {
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
fill: "yellow",
|
||||
r: "4",
|
||||
stroke: "green",
|
||||
"stroke-width": "1",
|
||||
},
|
||||
content: [],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "circle",
|
||||
type: 2,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: "http://www.w3.org/2000/svg",
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "svg",
|
||||
type: 2,
|
||||
});
|
||||
expect(
|
||||
parse(`<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/></g>`)
|
||||
).toEqual({
|
||||
attrs: {},
|
||||
content: [
|
||||
{
|
||||
attrs: {
|
||||
cx: "50",
|
||||
cy: "50",
|
||||
fill: "yellow",
|
||||
r: "4",
|
||||
stroke: "green",
|
||||
"stroke-width": "1",
|
||||
},
|
||||
content: [],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "circle",
|
||||
type: 2,
|
||||
},
|
||||
],
|
||||
dynamicTag: null,
|
||||
model: null,
|
||||
ns: "http://www.w3.org/2000/svg",
|
||||
on: {},
|
||||
ref: null,
|
||||
tag: "g",
|
||||
type: 2,
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// t-esc
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -200,6 +281,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "" }],
|
||||
});
|
||||
});
|
||||
@@ -221,6 +303,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "hey" }],
|
||||
});
|
||||
});
|
||||
@@ -262,6 +345,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TOut, expr: "text", body: null }],
|
||||
});
|
||||
});
|
||||
@@ -275,6 +359,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{ type: ASTType.TOut, expr: "text", body: [{ type: ASTType.Text, value: "body" }] },
|
||||
],
|
||||
@@ -294,6 +379,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TIf,
|
||||
@@ -321,6 +407,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
},
|
||||
tElif: null,
|
||||
@@ -397,6 +484,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.Text,
|
||||
@@ -415,6 +503,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "elif" }],
|
||||
},
|
||||
},
|
||||
@@ -427,6 +516,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.Text,
|
||||
@@ -474,6 +564,7 @@ describe("qweb parser", () => {
|
||||
dynamicTag: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "ok" }],
|
||||
},
|
||||
],
|
||||
@@ -493,6 +584,7 @@ describe("qweb parser", () => {
|
||||
dynamicTag: null,
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "ok" }],
|
||||
},
|
||||
{ type: ASTType.Text, value: "abc" },
|
||||
@@ -527,6 +619,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TIf,
|
||||
@@ -601,6 +694,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -646,6 +740,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -681,6 +776,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
},
|
||||
@@ -715,6 +811,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "category.name", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -736,6 +833,7 @@ describe("qweb parser", () => {
|
||||
model: null,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TForEach,
|
||||
@@ -781,6 +879,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
model: null,
|
||||
attrs: {},
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "item", defaultValue: "" }],
|
||||
},
|
||||
memo: "",
|
||||
@@ -888,6 +987,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.TCall,
|
||||
@@ -925,6 +1025,7 @@ describe("qweb parser", () => {
|
||||
on: { click: "add" },
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "Click" }],
|
||||
});
|
||||
});
|
||||
@@ -968,7 +1069,31 @@ describe("qweb parser", () => {
|
||||
|
||||
test("component with event handler", async () => {
|
||||
expect(() => parse(`<MyComponent t-on-click="someMethod"/>`)).toThrow(
|
||||
"t-on is no longer supported on Component node. Consider passing a callback in props."
|
||||
"t-on is no longer supported on components. Consider passing a callback in props."
|
||||
);
|
||||
});
|
||||
|
||||
test("component with t-ref", async () => {
|
||||
expect(() => parse(`<MyComponent t-ref="something"/>`)).toThrow(
|
||||
"t-ref is no longer supported on components. Consider exposing only the public part of the component's API through a callback prop."
|
||||
);
|
||||
});
|
||||
|
||||
test("component with t-att", async () => {
|
||||
expect(() => parse(`<MyComponent t-att="something"/>`)).toThrow(
|
||||
"t-att makes no sense on component: props are already treated as expressions"
|
||||
);
|
||||
});
|
||||
|
||||
test("component with t-attf", async () => {
|
||||
expect(() => parse(`<MyComponent t-attf="something"/>`)).toThrow(
|
||||
"t-attf is not supported on components: use template strings for string interpolation in props"
|
||||
);
|
||||
});
|
||||
|
||||
test("component with other unsupported directive", async () => {
|
||||
expect(() => parse(`<MyComponent t-something="5"/>`)).toThrow(
|
||||
"unsupported directive on Component: t-something"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -979,7 +1104,22 @@ describe("qweb parser", () => {
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
isDynamic: false,
|
||||
slots: { default: { type: ASTType.Text, value: "foo" } },
|
||||
slots: { default: { content: { type: ASTType.Text, value: "foo" } } },
|
||||
});
|
||||
});
|
||||
|
||||
test("a component with a default slot with attributes", async () => {
|
||||
expect(
|
||||
parse(`<MyComponent><t t-set-slot="default" param="param">foo</t></MyComponent>`)
|
||||
).toEqual({
|
||||
type: ASTType.TComponent,
|
||||
name: "MyComponent",
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
isDynamic: false,
|
||||
slots: {
|
||||
default: { content: { type: ASTType.Text, value: "foo" }, attrs: { param: "param" } },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -992,29 +1132,33 @@ describe("qweb parser", () => {
|
||||
props: {},
|
||||
slots: {
|
||||
default: {
|
||||
type: ASTType.Multi,
|
||||
content: [
|
||||
{
|
||||
type: ASTType.DomNode,
|
||||
tag: "span",
|
||||
dynamicTag: null,
|
||||
attrs: {},
|
||||
content: [],
|
||||
ref: null,
|
||||
model: null,
|
||||
on: {},
|
||||
},
|
||||
{
|
||||
type: ASTType.DomNode,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
attrs: {},
|
||||
content: [],
|
||||
ref: null,
|
||||
model: null,
|
||||
on: {},
|
||||
},
|
||||
],
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1027,10 +1171,27 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
slots: { name: { type: ASTType.Text, value: "foo" } },
|
||||
slots: { name: { content: { type: ASTType.Text, value: "foo" } } },
|
||||
});
|
||||
});
|
||||
|
||||
test("a component with a named slot with attributes", async () => {
|
||||
expect(parse(`<MyComponent><t t-set-slot="name" param="param">foo</t></MyComponent>`)).toEqual({
|
||||
type: ASTType.TComponent,
|
||||
name: "MyComponent",
|
||||
isDynamic: false,
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
slots: { name: { content: { type: ASTType.Text, value: "foo" }, attrs: { param: "param" } } },
|
||||
});
|
||||
});
|
||||
|
||||
test("a component with a named slot with div tag", async () => {
|
||||
expect(() =>
|
||||
parse(`<MyComponent><div t-set-slot="name">foo</div></MyComponent>`)
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test("a component with a named slot and some white space", async () => {
|
||||
expect(parse(`<MyComponent><t t-set-slot="name">foo</t> </MyComponent>`)).toEqual({
|
||||
type: ASTType.TComponent,
|
||||
@@ -1039,8 +1200,8 @@ describe("qweb parser", () => {
|
||||
props: {},
|
||||
isDynamic: false,
|
||||
slots: {
|
||||
default: { type: ASTType.Text, value: " " },
|
||||
name: { type: ASTType.Text, value: "foo" },
|
||||
default: { content: { type: ASTType.Text, value: " " } },
|
||||
name: { content: { type: ASTType.Text, value: "foo" } },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1058,8 +1219,8 @@ describe("qweb parser", () => {
|
||||
props: {},
|
||||
isDynamic: false,
|
||||
slots: {
|
||||
a: { type: ASTType.Text, value: "foo" },
|
||||
b: { type: ASTType.Text, value: "bar" },
|
||||
a: { content: { type: ASTType.Text, value: "foo" } },
|
||||
b: { content: { type: ASTType.Text, value: "bar" } },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1098,8 +1259,14 @@ describe("qweb parser", () => {
|
||||
});
|
||||
|
||||
test("component with t-esc", async () => {
|
||||
expect(() => parse(`<MyComponent t-esc="someValue"/>`)).toThrow(
|
||||
"t-esc is not supported on Component nodes"
|
||||
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"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1110,7 +1277,7 @@ describe("qweb parser", () => {
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
isDynamic: false,
|
||||
slots: { default: { body: null, name: "subTemplate", type: ASTType.TCall } },
|
||||
slots: { default: { content: { body: null, name: "subTemplate", type: ASTType.TCall } } },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1130,12 +1297,14 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
slots: {
|
||||
default: {
|
||||
type: ASTType.TComponent,
|
||||
isDynamic: false,
|
||||
name: "Child",
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
slots: { brol: { type: ASTType.Text, value: "coucou" } },
|
||||
content: {
|
||||
type: ASTType.TComponent,
|
||||
isDynamic: false,
|
||||
name: "Child",
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
slots: { brol: { content: { type: ASTType.Text, value: "coucou" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1145,7 +1314,7 @@ describe("qweb parser", () => {
|
||||
const template = `
|
||||
<MyComponent>
|
||||
<Child>
|
||||
<t><t t-set-slot="brol">coucou</t></t>
|
||||
<t t-set-slot="brol">coucou</t>
|
||||
</Child>
|
||||
</MyComponent>
|
||||
`;
|
||||
@@ -1157,12 +1326,14 @@ describe("qweb parser", () => {
|
||||
isDynamic: false,
|
||||
slots: {
|
||||
default: {
|
||||
type: ASTType.TComponent,
|
||||
isDynamic: false,
|
||||
name: "Child",
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
slots: { brol: { type: ASTType.Text, value: "coucou" } },
|
||||
content: {
|
||||
type: ASTType.TComponent,
|
||||
isDynamic: false,
|
||||
name: "Child",
|
||||
dynamicProps: null,
|
||||
props: {},
|
||||
slots: { brol: { content: { type: ASTType.Text, value: "coucou" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1176,6 +1347,7 @@ describe("qweb parser", () => {
|
||||
expect(parse(`<t t-slot="default"/>`)).toEqual({
|
||||
type: ASTType.TSlot,
|
||||
name: "default",
|
||||
attrs: {},
|
||||
defaultContent: null,
|
||||
});
|
||||
});
|
||||
@@ -1184,6 +1356,7 @@ 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" },
|
||||
});
|
||||
});
|
||||
@@ -1203,6 +1376,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
},
|
||||
});
|
||||
@@ -1220,6 +1394,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: null,
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
},
|
||||
});
|
||||
@@ -1238,6 +1413,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: "name",
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.Text, value: "hey" }],
|
||||
});
|
||||
});
|
||||
@@ -1251,6 +1427,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: "name",
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [
|
||||
{ type: ASTType.TOut, expr: "text", body: [{ type: ASTType.Text, value: "body" }] },
|
||||
],
|
||||
@@ -1266,6 +1443,7 @@ describe("qweb parser", () => {
|
||||
on: {},
|
||||
ref: "name",
|
||||
model: null,
|
||||
ns: null,
|
||||
content: [{ type: ASTType.TEsc, expr: "text", defaultValue: "body" }],
|
||||
});
|
||||
});
|
||||
@@ -1309,7 +1487,8 @@ describe("qweb parser", () => {
|
||||
model: null,
|
||||
tag: "div",
|
||||
dynamicTag: null,
|
||||
type: 2,
|
||||
type: ASTType.DomNode,
|
||||
ns: null,
|
||||
},
|
||||
type: 16,
|
||||
},
|
||||
@@ -1339,6 +1518,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1357,6 +1537,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1375,6 +1556,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1394,6 +1576,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "textarea",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1412,6 +1595,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1430,6 +1614,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1448,6 +1633,7 @@ describe("qweb parser", () => {
|
||||
ref: null,
|
||||
tag: "input",
|
||||
dynamicTag: null,
|
||||
ns: null,
|
||||
model: {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
@@ -1472,6 +1658,7 @@ describe("qweb parser", () => {
|
||||
tag: "div",
|
||||
dynamicTag: "theTag",
|
||||
model: null,
|
||||
ns: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+25
-17
@@ -1,22 +1,30 @@
|
||||
import { renderToString, snapshotEverything } from "../helpers";
|
||||
|
||||
// NB: check the snapshots to see where the SVG namespaces are added
|
||||
snapshotEverything();
|
||||
|
||||
describe("properly support svg", () => {
|
||||
test.skip("add proper namespace to svg", () => {
|
||||
// qweb.addTemplate(
|
||||
// "test",
|
||||
// `<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </svg>`
|
||||
// );
|
||||
// expect(renderToString(qweb, "test")).toBe(
|
||||
// `<svg width=\"100px\" height=\"90px\"><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </svg>`
|
||||
// );
|
||||
test("add proper namespace to svg", () => {
|
||||
const template = `<svg width="100px" height="90px"><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </svg>`;
|
||||
expect(renderToString(template)).toBe(
|
||||
`<svg width=\"100px\" height=\"90px\"><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </svg>`
|
||||
);
|
||||
});
|
||||
|
||||
test.skip("add proper namespace to g tags", () => {
|
||||
// this is necessary if one wants to use components in a svg
|
||||
// qweb.addTemplate(
|
||||
// "test",
|
||||
// `<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </g>`
|
||||
// );
|
||||
// expect(renderToString(qweb, "test")).toBe(
|
||||
// `<g><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </g>`
|
||||
// );
|
||||
test("add proper namespace to g tags", () => {
|
||||
const template = `<g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/> </g>`;
|
||||
expect(renderToString(template)).toBe(
|
||||
`<g><circle cx=\"50\" cy=\"50\" r=\"4\" stroke=\"green\" stroke-width=\"1\" fill=\"yellow\"></circle> </g>`
|
||||
);
|
||||
});
|
||||
|
||||
test("namespace to g tags not added if already in svg namespace", () => {
|
||||
const template = `<svg><g/></svg>`;
|
||||
expect(renderToString(template)).toBe(`<svg><g></g></svg>`);
|
||||
});
|
||||
|
||||
test("namespace to svg tags added even if already in svg namespace", () => {
|
||||
const template = `<svg><svg/></svg>`;
|
||||
expect(renderToString(template)).toBe(`<svg><svg></svg></svg>`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1192,7 +1192,7 @@ exports[`support svg components add proper namespace to svg 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 block1 = createBlock(\`<g><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/></g>\`);
|
||||
let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/></g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
@@ -1206,7 +1206,7 @@ exports[`support svg components add proper namespace to svg 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 block1 = createBlock(\`<svg><block-child-0/></svg>\`);
|
||||
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`GComp\`, {}, key + \`__1\`, node, ctx);
|
||||
|
||||
@@ -1044,6 +1044,30 @@ exports[`destroying/recreating a subwidget with different props (if start is not
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`parent and child rendered at exact same time 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(ctx['props'].value);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`parent and child rendered at exact same time 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`properly behave when destroyed/unmounted while rendering 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
@@ -1130,10 +1154,9 @@ exports[`rendering component again in next microtick 2`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['onClick'], ctx];
|
||||
if (ctx['env'].config.flag) {
|
||||
b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||
b2 = component(\`Child\`, {}, key + \`__1\`, 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');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -117,16 +117,15 @@ exports[`can catch errors can catch an error in a component render function 3`]
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {flag: ctx['state'].flag}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -159,7 +158,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
||||
if (ctx['state'].error) {
|
||||
b2 = text(\`Error handled\`);
|
||||
} else {
|
||||
b3 = callSlot(ctx, node, key, 'default');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -207,7 +206,7 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
||||
if (ctx['state'].error) {
|
||||
b2 = text(\`Error handled\`);
|
||||
} else {
|
||||
b3 = callSlot(ctx, node, key, 'default');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -219,18 +218,17 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
let b3 = component(\`ClassicCompoent\`, {}, key + \`__3\`, node, ctx);
|
||||
let b4 = component(\`ErrorComponent\`, {}, key + \`__4\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
let b3 = component(\`ClassicCompoent\`, {}, key + \`__2\`, node, ctx);
|
||||
let b4 = component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
return multi([b3, b4]);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__4\`, node, ctx);
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -241,16 +239,15 @@ exports[`can catch errors can catch an error in the constructor call of a compon
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -284,7 +281,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
||||
if (ctx['state'].error) {
|
||||
b2 = text(\`Error handled\`);
|
||||
} else {
|
||||
b3 = callSlot(ctx, node, key, 'default');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -296,16 +293,15 @@ exports[`can catch errors can catch an error in the initial call of a component
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -339,7 +335,7 @@ exports[`can catch errors can catch an error in the initial call of a component
|
||||
if (ctx['state'].error) {
|
||||
b2 = text(\`Error handled\`);
|
||||
} else {
|
||||
b3 = callSlot(ctx, node, key, 'default');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -351,18 +347,17 @@ exports[`can catch errors can catch an error in the initial call of a component
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3;
|
||||
if (ctx['state'].flag) {
|
||||
b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
return block1([], [b3]);
|
||||
}
|
||||
@@ -396,7 +391,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');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -408,16 +403,15 @@ exports[`can catch errors can catch an error in the mounted call 3`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -451,7 +445,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');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -463,17 +457,16 @@ exports[`can catch errors can catch an error in the willPatch call 3`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {message: ctx['state'].message}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].message;
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([d1], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -506,7 +499,7 @@ exports[`can catch errors can catch an error in the willStart call 2`] = `
|
||||
if (ctx['state'].error) {
|
||||
b2 = text(\`Error handled\`);
|
||||
} else {
|
||||
b3 = callSlot(ctx, node, key, 'default');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -518,16 +511,15 @@ exports[`can catch errors can catch an error in the willStart call 3`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`ErrorComponent\`, {}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -574,7 +566,7 @@ exports[`can catch errors can catch an error origination from a child's willStar
|
||||
if (ctx['state'].error) {
|
||||
b2 = text(\`Error handled\`);
|
||||
} else {
|
||||
b3 = callSlot(ctx, node, key, 'default');
|
||||
b3 = callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
@@ -586,18 +578,17 @@ exports[`can catch errors can catch an error origination from a child's willStar
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
let b3 = component(\`ClassicCompoent\`, {}, key + \`__3\`, node, ctx);
|
||||
let b4 = component(\`ErrorComponent\`, {}, key + \`__4\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
let b3 = component(\`ClassicCompoent\`, {}, key + \`__2\`, node, ctx);
|
||||
let b4 = component(\`ErrorComponent\`, {}, key + \`__3\`, node, ctx);
|
||||
return multi([b3, b4]);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`ErrorBoundary\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = component(\`ErrorBoundary\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__4\`, node, ctx);
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -23,15 +23,40 @@ exports[`event handling handler receive the event as argument 2`] = `
|
||||
let block1 = createBlock(\`<span block-handler-0=\\"click\\"><block-child-0/><block-text-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['inc'];
|
||||
let d1 = [v1, ctx];
|
||||
let b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||
let d1 = [ctx['inc'], ctx];
|
||||
let b2 = component(\`Child\`, {}, key + \`__1\`, 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
|
||||
) {
|
||||
@@ -42,8 +67,7 @@ exports[`event handling support for callable expression in event handler 1`] = `
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].value;
|
||||
const v1 = ctx['obj'];
|
||||
let d2 = [v1.onInput, ctx];
|
||||
let d2 = [ctx['obj'].onInput, ctx];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -130,8 +130,7 @@ exports[`basics sub widget is interactive 1`] = `
|
||||
let block1 = createBlock(\`<span><button block-handler-0=\\"click\\">click</button>child<block-text-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['inc'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['inc'], ctx];
|
||||
let d2 = ctx['state'].val;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ exports[`lifecycle hooks mounted hook is called on subsubcomponents, in proper o
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks onRender 1`] = `
|
||||
exports[`lifecycle hooks onWillRender 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
@@ -540,15 +540,14 @@ exports[`lifecycle hooks onRender 1`] = `
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['increment'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['increment'], ctx];
|
||||
let d2 = ctx['state'].value;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks onRender 2`] = `
|
||||
exports[`lifecycle hooks onWillRender 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
|
||||
@@ -30,6 +30,42 @@ exports[`basics accept ES6-like syntax for props (with getters) 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics arrow functions as prop correctly capture their scope 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = [ctx['props'].onClick, ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics arrow functions as prop correctly capture their scope 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['items']);
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
let key1 = ctx['item'].val;
|
||||
const v1 = ctx['onClick'];
|
||||
const v2 = ctx['item'];
|
||||
c_block1[i1] = withKey(component(\`Child\`, {onClick: ev=>v1(v2.val,ev)}, key + \`__3__\${key1}\`, node, ctx), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics explicit object prop 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
@@ -60,6 +96,60 @@ exports[`basics explicit object prop 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics prop names can contain - 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['props']['prop-name'];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics prop names can contain - 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {'prop-name': 7}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics support prop names that aren't valid bare object property names 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<button block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = [ctx['props'].onClick, ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics support prop names that aren't valid bare object property names 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {'some-dashed-prop': 5,'a.b': 'keyword prop'}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics t-set with a body expression can be passed in props, and then t-out 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
@@ -161,3 +251,27 @@ exports[`basics t-set works 2`] = `
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics template string in prop 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics template string in prop 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {propName: \`1\${ctx['someVal']}3\`}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -9,9 +9,9 @@ exports[`default props can set default required boolean values 1`] = `
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const props2 = {}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['state'].p}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['state'].p}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['state'].p}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['state'].p}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {message: 1}
|
||||
helpers.validateProps(\`Child\`, props2, ctx)
|
||||
let b2 = component(\`Child\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {message: 1}
|
||||
helpers.validateProps(\`Child\`, props1, ctx)
|
||||
let b2 = component(\`Child\`, props1, key + \`__2\`, 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 props2 = {p: ctx['state'].p}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['state'].p}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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 props2 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props2, ctx)
|
||||
let b2 = component(\`SubComp\`, props2, key + \`__1\`, node, ctx);
|
||||
const props1 = {p: ctx['p']}
|
||||
helpers.validateProps(\`SubComp\`, props1, ctx)
|
||||
let b2 = component(\`SubComp\`, props1, key + \`__2\`, 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');
|
||||
let b2 = callSlot(ctx, node, key, 'footer', false, {});
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
@@ -36,15 +36,13 @@ exports[`refs refs are properly bound in slots 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot3 = ctx => (node, key) => {
|
||||
function slot2(ctx, node, key) {
|
||||
const refs = ctx.__owl__.refs
|
||||
const v4 = ctx['doSomething'];
|
||||
let d2 = [v4, ctx];
|
||||
let d2 = [ctx['doSomething'], ctx];
|
||||
let d3 = (el) => refs[\`myButton\`] = el;
|
||||
return block2([d2, d3]);
|
||||
}
|
||||
@@ -52,8 +50,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 ctx2 = capture(ctx);
|
||||
let b3 = assign(component(\`Dialog\`, {}, key + \`__1\`, node, ctx), {slots: {'footer': slot3(ctx2)}});
|
||||
const ctx1 = capture(ctx);
|
||||
let b3 = component(\`Dialog\`, {slots: {'footer': {__render: slot2, __ctx: ctx1}}}, key + \`__3\`, node, ctx);
|
||||
return block1([d1], [b3]);
|
||||
}
|
||||
}"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,8 +52,7 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
|
||||
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['update'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['update'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -39,6 +39,61 @@ exports[`list of components components in a node in a t-foreach 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash on duplicate key in dev mode 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1,2]);
|
||||
const keys1 = new Set();
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
let key1 = 'child';
|
||||
if (keys1.has(key1)) { throw new Error(\`Got duplicate key in t-foreach: \${key1}\`)}
|
||||
keys1.add(key1);
|
||||
const props1 = {}
|
||||
helpers.validateProps(\`Child\`, props1, ctx)
|
||||
c_block1[i1] = withKey(component(\`Child\`, props1, key + \`__2__\${key1}\`, node, ctx), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash on duplicate key in dev mode 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components crash on duplicate key in dev mode 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1,2]);
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
let key1 = 'child';
|
||||
c_block1[i1] = withKey(component(\`Child\`, {}, key + \`__1__\${key1}\`, node, ctx), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`list of components list of sub components inside other nodes 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -117,11 +117,10 @@ exports[`t-model directive can also define t-on directive on same event, part 1
|
||||
let block1 = createBlock(\`<div><input block-handler-0=\\"input\\" block-attribute-1=\\"value\\" block-handler-2=\\"input\\"/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onInput'];
|
||||
let d1 = [v1, ctx];
|
||||
const bExpr2 = ctx['state'];
|
||||
let d1 = [ctx['onInput'], ctx];
|
||||
const bExpr1 = ctx['state'];
|
||||
let d2 = ctx['state']['text'];
|
||||
let d3 = [(ev) => { bExpr2['text'] = ev.target.value; }];
|
||||
let d3 = [(ev) => { bExpr1['text'] = ev.target.value; }];
|
||||
return block1([d1, d2, d3]);
|
||||
}
|
||||
}"
|
||||
@@ -136,21 +135,18 @@ exports[`t-model directive can also define t-on directive on same event, part 2
|
||||
let block1 = createBlock(\`<div><input type=\\"radio\\" id=\\"one\\" value=\\"One\\" block-handler-0=\\"click\\" block-attribute-1=\\"checked\\" block-handler-2=\\"click\\"/><input type=\\"radio\\" id=\\"two\\" value=\\"Two\\" block-handler-3=\\"click\\" block-attribute-4=\\"checked\\" block-handler-5=\\"click\\"/><input type=\\"radio\\" id=\\"three\\" value=\\"Three\\" block-handler-6=\\"click\\" block-attribute-7=\\"checked\\" block-handler-8=\\"click\\"/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [v1, ctx];
|
||||
const bExpr2 = ctx['state'];
|
||||
let d1 = [ctx['onClick'], ctx];
|
||||
const bExpr1 = 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 d3 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
|
||||
let d4 = [ctx['onClick'], ctx];
|
||||
const bExpr2 = ctx['state'];
|
||||
let d5 = ctx['state']['choice'] === 'Two';
|
||||
let d6 = [(ev) => { bExpr4['choice'] = ev.target.value; }];
|
||||
const v5 = ctx['onClick'];
|
||||
let d7 = [v5, ctx];
|
||||
const bExpr6 = ctx['state'];
|
||||
let d6 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
|
||||
let d7 = [ctx['onClick'], ctx];
|
||||
const bExpr3 = ctx['state'];
|
||||
let d8 = ctx['state']['choice'] === 'Three';
|
||||
let d9 = [(ev) => { bExpr6['choice'] = ev.target.value; }];
|
||||
let d9 = [(ev) => { bExpr3['choice'] = ev.target.value; }];
|
||||
return block1([d1, d2, d3, d4, d5, d6, d7, d8, d9]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -130,8 +130,7 @@ exports[`t-on t-on on destroyed components 1`] = `
|
||||
let block1 = createBlock(\`<div block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onClick'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['onClick'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -24,11 +24,10 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot3 = ctx => (node, key) => {
|
||||
function slot2(ctx, node, key) {
|
||||
setContextValue(ctx, \\"iter\\", 'inCall');
|
||||
}
|
||||
|
||||
@@ -37,8 +36,8 @@ exports[`t-set slot setted value (with t-set) not accessible with t-esc 2`] = `
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"iter\\", 'source');
|
||||
let d1 = ctx['iter'];
|
||||
const ctx2 = capture(ctx);
|
||||
let b2 = assign(component(\`Childcomp\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot3(ctx2)}});
|
||||
const ctx1 = capture(ctx);
|
||||
let b2 = component(\`Childcomp\`, {slots: {'default': {__render: slot2, __ctx: ctx1}}}, key + \`__3\`, node, ctx);
|
||||
let d2 = ctx['iter'];
|
||||
return block1([d1, d2], [b2]);
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ describe("basics", () => {
|
||||
class SomeWidget extends Component {
|
||||
static template = xml`<div>content</div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(SomeWidget, document.createDocumentFragment() as any);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
});
|
||||
|
||||
test("can mount a simple component with props", async () => {
|
||||
@@ -160,28 +160,28 @@ describe("basics", () => {
|
||||
static template = xml`<span>simple vnode</span>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Test, null as any);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Cannot mount component: the target is not a valid DOM element");
|
||||
});
|
||||
|
||||
test("a component cannot be mounted in a detached node", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`<div/>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Test, document.createElement("div"));
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Cannot mount a component on a detached dom node");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
|
||||
});
|
||||
|
||||
test("crashes if it cannot find a template", async () => {
|
||||
@@ -189,14 +189,14 @@ describe("basics", () => {
|
||||
static template = "wrongtemplate";
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Test, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe('Missing template: "wrongtemplate"');
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe('Missing template: "wrongtemplate"');
|
||||
});
|
||||
|
||||
test("class component with dynamic text", async () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,18 +35,18 @@ describe("basics", () => {
|
||||
expect(fixture.innerHTML).toBe("<div><div>heyfalse</div></div>");
|
||||
parent.state.flag = true;
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await parent.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(status(parent)).toBe("destroyed");
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("display a nice error if it cannot find component", async () => {
|
||||
@@ -58,14 +58,14 @@ describe("basics", () => {
|
||||
static template = xml`<SomeMispelledComponent />`;
|
||||
static components = { SomeComponent };
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
});
|
||||
@@ -108,16 +108,16 @@ describe("errors and promises", () => {
|
||||
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -136,14 +136,14 @@ describe("errors and promises", () => {
|
||||
}
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("boom");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -166,14 +166,14 @@ describe("errors and promises", () => {
|
||||
|
||||
const app = await mount(App, fixture);
|
||||
app.val = 4;
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await app.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("boom");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -196,14 +196,14 @@ describe("errors and promises", () => {
|
||||
|
||||
const app = await mount(App, fixture);
|
||||
app.val = 4;
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await app.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("boom");
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -222,16 +222,16 @@ describe("errors and promises", () => {
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -249,16 +249,16 @@ describe("errors and promises", () => {
|
||||
const app = await mount(App, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div></div>");
|
||||
app.flag = true;
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await app.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
@@ -273,20 +273,20 @@ describe("errors and promises", () => {
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("errors in mounted and in willUnmount", async () => {
|
||||
expect.assertions(2); // apparently this expect count in assertions...
|
||||
expect.assertions(2);
|
||||
class Example extends Component {
|
||||
static template = xml`<div/>`;
|
||||
val: any;
|
||||
@@ -305,7 +305,7 @@ describe("errors and promises", () => {
|
||||
try {
|
||||
await mount(Example, fixture);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Error in mounted");
|
||||
expect((e as Error).message).toBe("Error in mounted");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,4 +72,25 @@ 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 +4,7 @@ import {
|
||||
onWillUnmount,
|
||||
onPatched,
|
||||
onWillUpdateProps,
|
||||
onRender,
|
||||
onWillRender,
|
||||
} from "../../src/component/lifecycle_hooks";
|
||||
import { status } from "../../src/component/status";
|
||||
import { xml } from "../../src/tags";
|
||||
@@ -385,20 +385,25 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
"Parent:patched",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:destroyed",
|
||||
@@ -561,10 +566,12 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -609,20 +616,29 @@ describe("lifecycle hooks", () => {
|
||||
const app = new App(Parent);
|
||||
const parent = await app.mount(fixture);
|
||||
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
|
||||
parent.state.hasChild = true;
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"GrandChild:setup",
|
||||
"GrandChild:willStart",
|
||||
"GrandChild:render",
|
||||
"Child:rendered",
|
||||
"GrandChild:willRender",
|
||||
"GrandChild:rendered",
|
||||
"Parent:willPatch",
|
||||
"GrandChild:mounted",
|
||||
"Child:mounted",
|
||||
@@ -672,7 +688,13 @@ describe("lifecycle hooks", () => {
|
||||
const app = new App(Parent);
|
||||
const parent = await app.mount(fixture);
|
||||
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
|
||||
@@ -717,7 +739,13 @@ describe("lifecycle hooks", () => {
|
||||
const app = new App(Parent);
|
||||
const parent = await app.mount(fixture);
|
||||
|
||||
expect(steps).toEqual(["Parent:setup", "Parent:willStart", "Parent:render", "Parent:mounted"]);
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
|
||||
@@ -725,12 +753,14 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"GrandChild:setup",
|
||||
"GrandChild:willStart",
|
||||
"Child:rendered",
|
||||
]);
|
||||
|
||||
steps.splice(0);
|
||||
@@ -769,10 +799,12 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -783,7 +815,8 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:destroyed",
|
||||
@@ -816,10 +849,12 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -830,9 +865,11 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
@@ -841,7 +878,7 @@ describe("lifecycle hooks", () => {
|
||||
Object.freeze(steps);
|
||||
});
|
||||
|
||||
test("onRender", async () => {
|
||||
test("onWillRender", async () => {
|
||||
let steps: string[] = [];
|
||||
const def = makeDeferred();
|
||||
|
||||
@@ -852,7 +889,7 @@ describe("lifecycle hooks", () => {
|
||||
setup() {
|
||||
useLogLifecycle(steps);
|
||||
onWillUpdateProps(() => def);
|
||||
onRender(() => (this.visibleState = this.state.value));
|
||||
onWillRender(() => (this.visibleState = this.state.value));
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
@@ -891,17 +928,21 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:willUpdateProps",
|
||||
"Parent:rendered",
|
||||
"inc:1",
|
||||
"inc:1",
|
||||
"Child:render",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willPatch",
|
||||
"Child:patched",
|
||||
@@ -993,19 +1034,24 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:render",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"B:render",
|
||||
"C:render",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"E:setup",
|
||||
"E:willStart",
|
||||
"D:render",
|
||||
"E:render",
|
||||
"C:rendered",
|
||||
"D:willRender",
|
||||
"D:rendered",
|
||||
"E:willRender",
|
||||
"E:rendered",
|
||||
"E:mounted",
|
||||
"D:mounted",
|
||||
"C:mounted",
|
||||
@@ -1018,12 +1064,15 @@ describe("lifecycle hooks", () => {
|
||||
c!.state.flag = false;
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"C:render",
|
||||
"C:willRender",
|
||||
"D:willUpdateProps",
|
||||
"F:setup",
|
||||
"F:willStart",
|
||||
"D:render",
|
||||
"F:render",
|
||||
"C:rendered",
|
||||
"D:willRender",
|
||||
"D:rendered",
|
||||
"F:willRender",
|
||||
"F:rendered",
|
||||
"C:willPatch",
|
||||
"D:willPatch",
|
||||
"E:willUnmount",
|
||||
@@ -1062,21 +1111,26 @@ describe("lifecycle hooks", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:destroyed",
|
||||
"Parent:patched",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Parent:willPatch",
|
||||
"Child:mounted",
|
||||
"Parent:patched",
|
||||
|
||||
@@ -30,6 +30,20 @@ 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>`;
|
||||
@@ -102,4 +116,63 @@ describe("basics", () => {
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span><p>43</p><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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,23 +46,23 @@ describe("props validation", () => {
|
||||
static components = { SubComp };
|
||||
static template = xml`<div><SubComp /></div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
error = undefined;
|
||||
|
||||
try {
|
||||
await mountApp(Parent, false);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
});
|
||||
|
||||
test("props: list of strings", async () => {
|
||||
@@ -75,14 +75,14 @@ describe("props validation", () => {
|
||||
static template = xml`<div><SubComp /></div>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'message' (component 'SubComp')`);
|
||||
});
|
||||
|
||||
test("validate simple types", async () => {
|
||||
@@ -108,32 +108,32 @@ describe("props validation", () => {
|
||||
};
|
||||
(Parent as any).components = { SubComp };
|
||||
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
props = {};
|
||||
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
error = undefined;
|
||||
props = { p: test.ok };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
props = { p: test.ko };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -159,31 +159,31 @@ describe("props validation", () => {
|
||||
static template = xml`<div>hey</div>`;
|
||||
};
|
||||
(Parent as any).components = { SubComp };
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
props = {};
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'p' (component '_a')`);
|
||||
error = undefined;
|
||||
props = { p: test.ok };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
props = { p: test.ko };
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -199,30 +199,30 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: "string" };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: true };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: 1 };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate an optional props", async () => {
|
||||
@@ -237,30 +237,30 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: "key" };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = {};
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: 1 };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate an array with given primitive type", async () => {
|
||||
@@ -275,35 +275,35 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: [] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: ["string"] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: [1] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
error = undefined;
|
||||
try {
|
||||
props = { p: ["string", 1] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -319,37 +319,37 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: [] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: ["string"] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: [false, true, "string"] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: [true, 1] };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate an object with simple shape", async () => {
|
||||
@@ -366,40 +366,40 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: { id: 1, url: "url" } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: { id: 1, url: "url", extra: true } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid prop 'p' in component SubComp (unknown prop 'extra')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid prop 'p' in component SubComp (unknown prop 'extra')");
|
||||
try {
|
||||
props = { p: { id: "1", url: "url" } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
error = undefined;
|
||||
try {
|
||||
props = { p: { id: 1 } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate recursively complicated prop def", async () => {
|
||||
@@ -422,30 +422,30 @@ describe("props validation", () => {
|
||||
return props.p;
|
||||
}
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let props: { p?: any };
|
||||
try {
|
||||
props = { p: { id: 1, url: true } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: { id: 1, url: [12] } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
props = { p: { id: 1, url: [12, true] } };
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'p' in component 'SubComp'");
|
||||
});
|
||||
|
||||
test("can validate optional attributes in nested sub props", () => {
|
||||
@@ -462,20 +462,20 @@ describe("props validation", () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
validateProps(TestComponent as any, { myprop: [{}] });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
validateProps(TestComponent as any, { myprop: [{ a: 1 }] });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(
|
||||
"Invalid prop 'myprop' in component TestComponent (unknown prop 'a')"
|
||||
);
|
||||
});
|
||||
@@ -488,20 +488,20 @@ describe("props validation", () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
validateProps(TestComponent as any, { size: "small" });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
try {
|
||||
validateProps(TestComponent as any, { size: "abcdef" });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'size' in component 'TestComponent'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'size' in component 'TestComponent'");
|
||||
});
|
||||
|
||||
test("can validate with a custom validator, and a type", () => {
|
||||
@@ -514,30 +514,30 @@ describe("props validation", () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
let error;
|
||||
let error: Error | undefined;
|
||||
try {
|
||||
validateProps(TestComponent as any, { n: 3 });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeUndefined();
|
||||
expect(error!).toBeUndefined();
|
||||
expect(validator).toBeCalledTimes(1);
|
||||
try {
|
||||
validateProps(TestComponent as any, { n: "str" });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(validator).toBeCalledTimes(1);
|
||||
error = null;
|
||||
error = undefined;
|
||||
try {
|
||||
validateProps(TestComponent as any, { n: 100 });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(validator).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -647,18 +647,18 @@ describe("props validation", () => {
|
||||
static template = xml`<div><SubComp/></div>`;
|
||||
static components = { SubComp };
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
});
|
||||
|
||||
test("props are validated whenever component is updated", async () => {
|
||||
let error;
|
||||
let error: Error;
|
||||
class SubComp extends Component {
|
||||
static props = { p: { type: Number } };
|
||||
static template = xml`<div><t t-esc="props.p"/></div>`;
|
||||
@@ -674,10 +674,10 @@ describe("props validation", () => {
|
||||
(app as any).root.component.state.p = undefined;
|
||||
await (app as any).root.component.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Missing props 'p' (component 'SubComp')");
|
||||
});
|
||||
|
||||
test("default values are applied before validating props at update", async () => {
|
||||
@@ -712,14 +712,14 @@ describe("props validation", () => {
|
||||
static components = { Child };
|
||||
static template = xml`<div><Child/></div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mountApp(Parent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Missing props 'mandatory' (component 'Child')");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("Missing props 'mandatory' (component 'Child')");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
Component,
|
||||
mount,
|
||||
onPatched,
|
||||
onRender,
|
||||
onWillRender,
|
||||
onWillPatch,
|
||||
onWillUnmount,
|
||||
useState,
|
||||
@@ -37,7 +37,7 @@ describe("reactivity in lifecycle", () => {
|
||||
static template = xml`<div><t t-esc="state.a"/></div>`;
|
||||
state = useState({ a: 5, b: 7 });
|
||||
setup() {
|
||||
onRender(() => n++);
|
||||
onWillRender(() => n++);
|
||||
}
|
||||
}
|
||||
const comp = await mount(Comp, fixture);
|
||||
@@ -61,7 +61,7 @@ describe("reactivity in lifecycle", () => {
|
||||
`;
|
||||
state = useState({ n: 2 });
|
||||
setup() {
|
||||
onRender(() => {
|
||||
onWillRender(() => {
|
||||
steps.push("render");
|
||||
});
|
||||
onWillPatch(() => {
|
||||
@@ -132,7 +132,7 @@ describe("reactivity in lifecycle", () => {
|
||||
state = useState({ val: 1 });
|
||||
setup() {
|
||||
STATE = this.state;
|
||||
onRender(() => {
|
||||
onWillRender(() => {
|
||||
steps.push(this.state.val);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,59 @@ 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"/>`;
|
||||
@@ -71,6 +124,33 @@ 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`
|
||||
@@ -97,6 +177,35 @@ describe("slots", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("can define and call slots with params", async () => {
|
||||
class Dialog extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-esc="props.slots['header'].param"/>
|
||||
<div><t t-slot="header"/></div>
|
||||
<t t-esc="props.slots['footer'].param"/>
|
||||
<div><t t-slot="footer"/></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Dialog };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Dialog>
|
||||
<t t-set-slot="header" param="var"><span>header</span></t>
|
||||
<t t-set-slot="footer" param="'5'"><span>footer</span></t>
|
||||
</Dialog>
|
||||
</div>`;
|
||||
var = 3;
|
||||
}
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><div>3<div><span>header</span></div>5<div><span>footer</span></div></div></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("no named slot content => just no children", async () => {
|
||||
class Dialog extends Component {
|
||||
static template = xml`<span><t t-slot="header"/></span>`;
|
||||
@@ -126,7 +235,7 @@ describe("slots", () => {
|
||||
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
|
||||
});
|
||||
|
||||
test("dafault slots can define a default content", async () => {
|
||||
test("can define a default content", async () => {
|
||||
class Dialog extends Component {
|
||||
static template = xml`
|
||||
<span>
|
||||
@@ -174,6 +283,42 @@ 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>`;
|
||||
@@ -1352,4 +1497,33 @@ 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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -338,16 +338,16 @@ describe("style and class handling", () => {
|
||||
static template = xml`<Child class="'a'"/>`;
|
||||
static components = { Child };
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(ParentWidget, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,14 +87,14 @@ describe("styles and component", () => {
|
||||
static template = xml`<div class="app">text</div>`;
|
||||
static style = `.app {color: red;}`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(App, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(
|
||||
"Invalid css stylesheet for component 'App'. Did you forget to use the 'css' tag helper?"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -35,10 +35,12 @@ describe("t-component", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
]);
|
||||
@@ -80,16 +82,20 @@ describe("t-component", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildA:render",
|
||||
"Parent:rendered",
|
||||
"ChildA:willRender",
|
||||
"ChildA:rendered",
|
||||
"ChildA:mounted",
|
||||
"Parent:mounted",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"ChildB:setup",
|
||||
"ChildB:willStart",
|
||||
"ChildB:render",
|
||||
"Parent:rendered",
|
||||
"ChildB:willRender",
|
||||
"ChildB:rendered",
|
||||
"Parent:willPatch",
|
||||
"ChildA:willUnmount",
|
||||
"ChildA:destroyed",
|
||||
@@ -199,14 +205,14 @@ describe("t-component", () => {
|
||||
static components = { Child };
|
||||
static template = xml`<div><div t-component="Child"/></div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(
|
||||
`Directive 't-component' can only be used on <t> nodes (used on a <div>)`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Component, mount, onMounted, useState, xml } from "../../src/index";
|
||||
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
|
||||
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
|
||||
import {
|
||||
makeTestFixture,
|
||||
nextTick,
|
||||
snapshotApp,
|
||||
snapshotEverything,
|
||||
useLogLifecycle,
|
||||
} from "../helpers";
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
@@ -81,13 +87,16 @@ describe("list of components", () => {
|
||||
expect(steps).toEqual([
|
||||
"Parent:setup",
|
||||
"Parent:willStart",
|
||||
"Parent:render",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Child:render",
|
||||
"Child:render",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:mounted",
|
||||
"Child:mounted",
|
||||
"Parent:mounted",
|
||||
@@ -288,4 +297,28 @@ 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;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,14 +57,14 @@ describe("t-model directive", () => {
|
||||
</div>`;
|
||||
state = useState({ text: "" });
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(SomeComponent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
|
||||
});
|
||||
|
||||
test("basic use, on another key in component", async () => {
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("t-props", () => {
|
||||
a: "first",
|
||||
b: "second",
|
||||
});
|
||||
a = "third";
|
||||
a? = "third";
|
||||
}
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.textContent).toBe("thirdsecond");
|
||||
|
||||
+8
-3
@@ -4,13 +4,14 @@ import {
|
||||
onDestroyed,
|
||||
onMounted,
|
||||
onPatched,
|
||||
onRender,
|
||||
onWillRender,
|
||||
onWillPatch,
|
||||
onWillStart,
|
||||
onWillUnmount,
|
||||
onWillUpdateProps,
|
||||
status,
|
||||
useComponent,
|
||||
onRendered,
|
||||
} from "../src";
|
||||
import { BDom } from "../src/blockdom";
|
||||
import { blockDom } from "../src";
|
||||
@@ -181,8 +182,12 @@ export function useLogLifecycle(steps: string[]) {
|
||||
steps.push(`${name}:willUpdateProps`);
|
||||
});
|
||||
|
||||
onRender(() => {
|
||||
steps.push(`${name}:render`);
|
||||
onWillRender(() => {
|
||||
steps.push(`${name}:willRender`);
|
||||
});
|
||||
|
||||
onRendered(() => {
|
||||
steps.push(`${name}:rendered`);
|
||||
});
|
||||
|
||||
onWillPatch(() => {
|
||||
|
||||
@@ -17,9 +17,8 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
let assign = Object.assign;
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
let b6 = text(ctx['state'].a);
|
||||
let b7 = text(ctx['state'].b);
|
||||
let b8 = text(ctx['state'].c);
|
||||
@@ -30,7 +29,7 @@ exports[`Memo if no prop change, prevent renderings from above 2`] = `
|
||||
let b2 = text(ctx['state'].a);
|
||||
let b3 = text(ctx['state'].b);
|
||||
let b4 = text(ctx['state'].c);
|
||||
let b9 = assign(component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b9 = component(\`Memo\`, {a: ctx['state'].a,b: ctx['state'].b,slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return multi([b2, b3, b4, b9]);
|
||||
}
|
||||
}"
|
||||
@@ -53,15 +52,14 @@ 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;
|
||||
|
||||
const slot3 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {value: ctx['state'].value}, key + \`__4\`, node, ctx);
|
||||
function slot2(ctx, node, key) {
|
||||
return component(\`Child\`, {value: ctx['state'].value}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||
let b4 = assign(component(\`Memo\`, {}, key + \`__2\`, node, ctx), {slots: {'default': slot3(ctx)}});
|
||||
let b4 = component(\`Memo\`, {slots: {'default': {__render: slot2, __ctx: ctx}}}, key + \`__4\`, node, ctx);
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
}"
|
||||
@@ -72,15 +70,14 @@ exports[`Memo if no props, prevent renderings from above (work with simple html)
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
let assign = Object.assign;
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return text(ctx['state'].value);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = text(ctx['state'].value);
|
||||
let b4 = assign(component(\`Memo\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = component(\`Memo\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -9,8 +9,7 @@ exports[`Portal Portal composed with t-slot 1`] = `
|
||||
let block1 = createBlock(\`<div block-handler-0=\\"custom\\"><span id=\\"childSpan\\">child2</span></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const v1 = ctx['onCustom'];
|
||||
let d1 = [v1, ctx];
|
||||
let d1 = [ctx['onCustom'], ctx];
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
@@ -21,14 +20,13 @@ exports[`Portal Portal composed with t-slot 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
let assign = Object.assign;
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return callSlot(ctx, node, key, 'default');
|
||||
function slot1(ctx, node, key) {
|
||||
return callSlot(ctx, node, key, 'default', false, {});
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -38,16 +36,15 @@ exports[`Portal Portal composed with t-slot 3`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child2\`, {customHandler: ctx['_handled']}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child2\`, {customHandler: ctx['_handled']}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Child\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Child\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -58,17 +55,16 @@ 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block2();
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -94,19 +90,18 @@ exports[`Portal conditional use of Portal (with sub Component) 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
|
||||
let assign = Object.assign;
|
||||
|
||||
let block2 = createBlock(\`<span>1</span>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2,b4;
|
||||
b2 = block2();
|
||||
if (ctx['state'].hasPortal) {
|
||||
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
@@ -118,12 +113,11 @@ exports[`Portal conditional use of Portal 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block3();
|
||||
}
|
||||
|
||||
@@ -131,7 +125,7 @@ exports[`Portal conditional use of Portal 1`] = `
|
||||
let b2,b4;
|
||||
b2 = block2();
|
||||
if (ctx['state'].hasPortal) {
|
||||
b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
return multi([b2, b4]);
|
||||
}
|
||||
@@ -158,18 +152,17 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 2`]
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3;
|
||||
if (ctx['state'].hasChild) {
|
||||
b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
return block1([], [b3]);
|
||||
}
|
||||
@@ -181,12 +174,11 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
let b3;
|
||||
if (ctx['state'].val) {
|
||||
let d1 = ctx['state'].val;
|
||||
@@ -196,7 +188,7 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -222,16 +214,15 @@ 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {error: ctx['state'].error}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child\`, {error: ctx['state'].error}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -257,16 +248,15 @@ exports[`Portal portal with child and props 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -277,13 +267,12 @@ 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/>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
let b3,b4;
|
||||
if (ctx['state'].val) {
|
||||
let d1 = ctx['state'].val;
|
||||
@@ -295,7 +284,7 @@ exports[`Portal portal with dynamic body 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -306,20 +295,19 @@ 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
let b3 = block3();
|
||||
let b4 = block4();
|
||||
return multi([b3, b4]);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b5 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b5 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b5]);
|
||||
}
|
||||
}"
|
||||
@@ -330,11 +318,10 @@ 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
let b3;
|
||||
if (false) {
|
||||
b3 = text('ABC');
|
||||
@@ -343,7 +330,7 @@ exports[`Portal portal with no content 1`] = `
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b4 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b4 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b4]);
|
||||
}
|
||||
}"
|
||||
@@ -354,16 +341,15 @@ exports[`Portal portal with only text as content 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return text('only text');
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -374,17 +360,16 @@ exports[`Portal portal with target not in dom 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block2();
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#does-not-exist'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#does-not-exist',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -409,16 +394,15 @@ exports[`Portal portal's parent's env is not polluted 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child\`, {}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -429,17 +413,16 @@ exports[`Portal with target in template (after portal) 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block2();
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -450,17 +433,16 @@ exports[`Portal with target in template (before portal) 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block2();
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#local-target'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -471,17 +453,16 @@ exports[`Portal: Props validation target is mandatory 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block2();
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -492,17 +473,16 @@ exports[`Portal: Props validation target is not list 1`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
function slot1(ctx, node, key) {
|
||||
return block2();
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: ['body']}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: ['body'],slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
@@ -528,16 +508,15 @@ exports[`Portal: UI/UX focus is kept across re-renders 2`] = `
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { 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>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
|
||||
function slot1(ctx, node, key) {
|
||||
return component(\`Child\`, {val: ctx['state'].val}, key + \`__2\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
let b3 = component(\`Portal\`, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__3\`, node, ctx);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
|
||||
+16
-16
@@ -160,15 +160,15 @@ describe("Portal", () => {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("invalid portal target");
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe("invalid portal target");
|
||||
expect(fixture.innerHTML).toBe(`<div></div>`);
|
||||
});
|
||||
|
||||
@@ -396,16 +396,16 @@ describe("Portal", () => {
|
||||
const parent = await mount(Parent, fixture);
|
||||
parent.state.error = true;
|
||||
|
||||
let error;
|
||||
let error: Error;
|
||||
try {
|
||||
await parent.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!).toBeDefined();
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(error!.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("portal's parent's env is not polluted", async () => {
|
||||
@@ -519,16 +519,16 @@ describe("Portal: Props validation", () => {
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let app = new App(Parent);
|
||||
app.configure({ dev: true });
|
||||
try {
|
||||
await app.mount(fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'target' (component 'Portal')`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Missing props 'target' (component 'Portal')`);
|
||||
console.info = consoleInfo;
|
||||
});
|
||||
|
||||
@@ -544,16 +544,16 @@ describe("Portal: Props validation", () => {
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
let error: Error;
|
||||
let app = new App(Parent);
|
||||
app.configure({ dev: true });
|
||||
try {
|
||||
await app.mount(fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
expect(error!).toBeDefined();
|
||||
expect(error!.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
console.info = consoleInfo;
|
||||
});
|
||||
});
|
||||
|
||||
+496
-459
File diff suppressed because it is too large
Load Diff
@@ -1,301 +0,0 @@
|
||||
import {
|
||||
buildData,
|
||||
startMeasure,
|
||||
stopMeasure,
|
||||
formatNumber
|
||||
} from "../shared/utils.js";
|
||||
|
||||
odoo.define("app", function(require) {
|
||||
const Widget = require("web.Widget");
|
||||
var core = require("web.core");
|
||||
var dom = require("web.dom");
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//----------------------------------------------------------------------------
|
||||
var Counter = Widget.extend({
|
||||
template: "counter",
|
||||
events: {
|
||||
"click .o_increment": "_onIncrement"
|
||||
},
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
this.value = 0;
|
||||
},
|
||||
start: function() {
|
||||
this.updateCounter();
|
||||
},
|
||||
on_attach_callback: function() {},
|
||||
on_detach_callback: function() {},
|
||||
updateCounter: function() {
|
||||
this.$(".o_increment").html("Value: " + this.value);
|
||||
},
|
||||
_onIncrement: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.value += 1;
|
||||
this.updateCounter();
|
||||
}
|
||||
});
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//----------------------------------------------------------------------------
|
||||
var Message = Widget.extend({
|
||||
template: "message",
|
||||
events: {
|
||||
"click .remove": "_onRemove"
|
||||
},
|
||||
|
||||
init: function(parent, message) {
|
||||
this._super(parent);
|
||||
this.author = message.author;
|
||||
this.msg = message.msg;
|
||||
this.id = message.id;
|
||||
},
|
||||
willStart: function() {
|
||||
this.counter = new Counter(this);
|
||||
return this.counter.appendTo($("<div>"));
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this.$msg = this.$(".msg");
|
||||
dom.append(this.$el, this.counter.$el, {
|
||||
in_DOM: this.isInDom,
|
||||
callbacks: [{ widget: this.counter }]
|
||||
});
|
||||
},
|
||||
on_attach_callback: function() {
|
||||
this.isInDom = true;
|
||||
if (this.counter) {
|
||||
this.counter.on_attach_callback();
|
||||
}
|
||||
},
|
||||
on_detach_callback: function() {
|
||||
this.isInDom = true;
|
||||
if (this.counter) {
|
||||
this.counter.on_detach_callback();
|
||||
}
|
||||
},
|
||||
|
||||
_onRemove: function() {
|
||||
this.trigger_up("remove_message", { id: this.id });
|
||||
},
|
||||
|
||||
update: function() {
|
||||
this.msg += "!!!";
|
||||
this.$msg.text(this.msg);
|
||||
}
|
||||
});
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//----------------------------------------------------------------------------
|
||||
var App = Widget.extend({
|
||||
template: "root",
|
||||
events: {
|
||||
"click .o_btn_msg.100": function() {
|
||||
this.addMessages(100);
|
||||
},
|
||||
"click .o_btn_msg.1000": function() {
|
||||
this.addMessages(1000);
|
||||
},
|
||||
"click .o_btn_msg.10000": function() {
|
||||
this.addMessages(10000);
|
||||
},
|
||||
"click .o_btn_msg.30000": function() {
|
||||
this.addMessages(30000);
|
||||
},
|
||||
"click .updateSomeMessages": function() {
|
||||
this.updateSomeMessages();
|
||||
},
|
||||
"click .clear": function() {
|
||||
this.clear();
|
||||
},
|
||||
"click .o_multiple": function() {
|
||||
this.multipleFlag = !this.multipleFlag;
|
||||
},
|
||||
"click .o_clear": function() {
|
||||
this.clearFlag = !this.clearFlag;
|
||||
},
|
||||
"click .clear-log": function() {
|
||||
this.$log[0].innerHTML = "";
|
||||
}
|
||||
},
|
||||
custom_events: {
|
||||
remove_message: "_onRemoveMessage"
|
||||
},
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
this.widgets = {};
|
||||
this.isInDom = false;
|
||||
this.messageCount = 0;
|
||||
this.multipleFlag = false;
|
||||
this.clearFlag = false;
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this.$content = this.$(".content");
|
||||
this.$msgCount = this.$(".message_count");
|
||||
this.$log = this.$(".log-content");
|
||||
this.log("Benchmarking odoo widgets, 12.0");
|
||||
},
|
||||
on_attach_callback: function() {
|
||||
this.isInDom = true;
|
||||
for (let widget of Object.values(this.widgets)) {
|
||||
if (widget.on_attach_callback) {
|
||||
widget.on_attach_callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
on_detach_callback: function() {
|
||||
this.isInDom = true;
|
||||
for (let widget of Object.values(this.widgets)) {
|
||||
if (widget.on_detach_callback) {
|
||||
widget.on_detach_callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateMessageCount() {
|
||||
this.$msgCount.text("Number of msg: " + this.messageCount);
|
||||
},
|
||||
|
||||
addMessages: function(n) {
|
||||
const self = this;
|
||||
this.benchmark("add " + n, () => {
|
||||
const defs = [];
|
||||
const messages = buildData(n);
|
||||
for (let message of messages) {
|
||||
const widget = new Message(this, message);
|
||||
this.widgets[message.id] = widget;
|
||||
defs.push(widget.appendTo("<div>"));
|
||||
}
|
||||
return $.when
|
||||
.apply($, defs)
|
||||
.then(function() {
|
||||
for (let message of messages) {
|
||||
let widget = self.widgets[message.id];
|
||||
dom.append(self.$content, widget.$el, {
|
||||
in_DOM: this.isInDom,
|
||||
callbacks: [{ widget: widget }]
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
self.messageCount += n;
|
||||
self.updateMessageCount();
|
||||
});
|
||||
});
|
||||
},
|
||||
clear: function() {
|
||||
startMeasure("clear");
|
||||
this._clear();
|
||||
stopMeasure(info => {
|
||||
this.log(info.msg);
|
||||
});
|
||||
},
|
||||
|
||||
_clear: function() {
|
||||
this.$content.empty();
|
||||
for (let key in this.widgets) {
|
||||
this.widgets[key].destroy();
|
||||
delete this.widgets[key];
|
||||
}
|
||||
this.messageCount = 0;
|
||||
this.updateMessageCount();
|
||||
},
|
||||
updateSomeMessages: function() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const widgets = Object.values(this.widgets);
|
||||
for (let i = 0; i < widgets.length; i += 10) {
|
||||
widgets[i].update();
|
||||
}
|
||||
});
|
||||
},
|
||||
_onRemoveMessage: function(ev) {
|
||||
startMeasure("remove message");
|
||||
ev.target.destroy();
|
||||
delete this.widgets[ev.data.id];
|
||||
this.messageCount--;
|
||||
this.updateMessageCount();
|
||||
stopMeasure();
|
||||
},
|
||||
|
||||
benchmark: function(message, fn, callback) {
|
||||
if (this.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.clearFlag) {
|
||||
this._benchmark("clear", this._clear.bind(this), finalize, false);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
},
|
||||
|
||||
_benchmark: function(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
const benchmark = fn();
|
||||
(benchmark && benchmark.then ? benchmark : $.when()).then(() => {
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
},
|
||||
|
||||
log: function(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.$log[0].appendChild(div);
|
||||
this.$log[0].scrollTop = this.$log[0].scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
// prepare QWeb
|
||||
const templates = await fetch("templates.xml");
|
||||
let strTemplates = await templates.text();
|
||||
strTemplates = strTemplates.replace(/<!--[\s\S]*?-->/g, "");
|
||||
core.qweb.add_template(strTemplates);
|
||||
|
||||
// prepare app
|
||||
const app = new App();
|
||||
await app.appendTo(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Odoo Widget Benchmark (12.0)</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='web.assets_common.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,47 +0,0 @@
|
||||
<templates>
|
||||
<div class="main" t-name="root">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button class="o_btn_msg 100">Add 100 messages</button>
|
||||
<button class="o_btn_msg 1000">Add 1k messages</button>
|
||||
<button class="o_btn_msg 10000">Add 10k messages</button>
|
||||
<button class="o_btn_msg 30000">Add 30k messages</button>
|
||||
<button class="updateSomeMessages">Update every 10th message</button>
|
||||
<button class="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" class="o_multiple" id="multipleflag" />
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" class="o_clear" id="clearFlag" />
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info message_count">
|
||||
Number of messages: 0
|
||||
</div>
|
||||
<div class="title">Log <span class="clear-log">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="message" class="message">
|
||||
<span class="author"><t t-esc="widget.author"/></span>
|
||||
<span class="msg"><t t-esc="widget.msg"/></span>
|
||||
<button class="remove">Remove</button>
|
||||
</div>
|
||||
|
||||
<div t-name="counter">
|
||||
<button class="o_increment"></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,301 +0,0 @@
|
||||
import {
|
||||
buildData,
|
||||
startMeasure,
|
||||
stopMeasure,
|
||||
formatNumber
|
||||
} from "../shared/utils.js";
|
||||
|
||||
odoo.define("app", function(require) {
|
||||
const Widget = require("web.Widget");
|
||||
var core = require("web.core");
|
||||
var dom = require("web.dom");
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//----------------------------------------------------------------------------
|
||||
var Counter = Widget.extend({
|
||||
template: "counter",
|
||||
events: {
|
||||
"click .o_increment": "_onIncrement"
|
||||
},
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
this.value = 0;
|
||||
},
|
||||
start: function() {
|
||||
this.updateCounter();
|
||||
},
|
||||
on_attach_callback: function() {},
|
||||
on_detach_callback: function() {},
|
||||
updateCounter: function() {
|
||||
this.$(".o_increment").html("Value: " + this.value);
|
||||
},
|
||||
_onIncrement: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.value += 1;
|
||||
this.updateCounter();
|
||||
}
|
||||
});
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//----------------------------------------------------------------------------
|
||||
var Message = Widget.extend({
|
||||
template: "message",
|
||||
events: {
|
||||
"click .remove": "_onRemove"
|
||||
},
|
||||
|
||||
init: function(parent, message) {
|
||||
this._super(parent);
|
||||
this.author = message.author;
|
||||
this.msg = message.msg;
|
||||
this.id = message.id;
|
||||
},
|
||||
willStart: function() {
|
||||
this.counter = new Counter(this);
|
||||
return this.counter.appendTo($("<div>"));
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this.$msg = this.$(".msg");
|
||||
dom.append(this.$el, this.counter.$el, {
|
||||
in_DOM: this.isInDom,
|
||||
callbacks: [{ widget: this.counter }]
|
||||
});
|
||||
},
|
||||
on_attach_callback: function() {
|
||||
this.isInDom = true;
|
||||
if (this.counter) {
|
||||
this.counter.on_attach_callback();
|
||||
}
|
||||
},
|
||||
on_detach_callback: function() {
|
||||
this.isInDom = true;
|
||||
if (this.counter) {
|
||||
this.counter.on_detach_callback();
|
||||
}
|
||||
},
|
||||
|
||||
_onRemove: function() {
|
||||
this.trigger_up("remove_message", { id: this.id });
|
||||
},
|
||||
|
||||
update: function() {
|
||||
this.msg += "!!!";
|
||||
this.$msg.text(this.msg);
|
||||
}
|
||||
});
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//----------------------------------------------------------------------------
|
||||
var App = Widget.extend({
|
||||
template: "root",
|
||||
events: {
|
||||
"click .o_btn_msg.100": function() {
|
||||
this.addMessages(100);
|
||||
},
|
||||
"click .o_btn_msg.1000": function() {
|
||||
this.addMessages(1000);
|
||||
},
|
||||
"click .o_btn_msg.10000": function() {
|
||||
this.addMessages(10000);
|
||||
},
|
||||
"click .o_btn_msg.30000": function() {
|
||||
this.addMessages(30000);
|
||||
},
|
||||
"click .updateSomeMessages": function() {
|
||||
this.updateSomeMessages();
|
||||
},
|
||||
"click .clear": function() {
|
||||
this.clear();
|
||||
},
|
||||
"click .o_multiple": function() {
|
||||
this.multipleFlag = !this.multipleFlag;
|
||||
},
|
||||
"click .o_clear": function() {
|
||||
this.clearFlag = !this.clearFlag;
|
||||
},
|
||||
"click .clear-log": function() {
|
||||
this.$log[0].innerHTML = "";
|
||||
}
|
||||
},
|
||||
custom_events: {
|
||||
remove_message: "_onRemoveMessage"
|
||||
},
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
this.widgets = {};
|
||||
this.isInDom = false;
|
||||
this.messageCount = 0;
|
||||
this.multipleFlag = false;
|
||||
this.clearFlag = false;
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this.$content = this.$(".content");
|
||||
this.$msgCount = this.$(".message_count");
|
||||
this.$log = this.$(".log-content");
|
||||
this.log("Benchmarking odoo widgets, 12.3");
|
||||
},
|
||||
on_attach_callback: function() {
|
||||
this.isInDom = true;
|
||||
for (let widget of Object.values(this.widgets)) {
|
||||
if (widget.on_attach_callback) {
|
||||
widget.on_attach_callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
on_detach_callback: function() {
|
||||
this.isInDom = true;
|
||||
for (let widget of Object.values(this.widgets)) {
|
||||
if (widget.on_detach_callback) {
|
||||
widget.on_detach_callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateMessageCount() {
|
||||
this.$msgCount.text("Number of msg: " + this.messageCount);
|
||||
},
|
||||
|
||||
addMessages: function(n) {
|
||||
const self = this;
|
||||
this.benchmark("add " + n, () => {
|
||||
const defs = [];
|
||||
const messages = buildData(n);
|
||||
for (let message of messages) {
|
||||
const widget = new Message(this, message);
|
||||
this.widgets[message.id] = widget;
|
||||
defs.push(widget.appendTo("<div>"));
|
||||
}
|
||||
return $.when
|
||||
.apply($, defs)
|
||||
.then(function() {
|
||||
for (let message of messages) {
|
||||
let widget = self.widgets[message.id];
|
||||
dom.append(self.$content, widget.$el, {
|
||||
in_DOM: this.isInDom,
|
||||
callbacks: [{ widget: widget }]
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
self.messageCount += n;
|
||||
self.updateMessageCount();
|
||||
});
|
||||
});
|
||||
},
|
||||
clear: function() {
|
||||
startMeasure("clear");
|
||||
this._clear();
|
||||
stopMeasure(info => {
|
||||
this.log(info.msg);
|
||||
});
|
||||
},
|
||||
|
||||
_clear: function() {
|
||||
this.$content.empty();
|
||||
for (let key in this.widgets) {
|
||||
this.widgets[key].destroy();
|
||||
delete this.widgets[key];
|
||||
}
|
||||
this.messageCount = 0;
|
||||
this.updateMessageCount();
|
||||
},
|
||||
updateSomeMessages: function() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const widgets = Object.values(this.widgets);
|
||||
for (let i = 0; i < widgets.length; i += 10) {
|
||||
widgets[i].update();
|
||||
}
|
||||
});
|
||||
},
|
||||
_onRemoveMessage: function(ev) {
|
||||
startMeasure("remove message");
|
||||
ev.target.destroy();
|
||||
delete this.widgets[ev.data.id];
|
||||
this.messageCount--;
|
||||
this.updateMessageCount();
|
||||
stopMeasure();
|
||||
},
|
||||
|
||||
benchmark: function(message, fn, callback) {
|
||||
if (this.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.clearFlag) {
|
||||
this._benchmark("clear", this._clear.bind(this), finalize, false);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
},
|
||||
|
||||
_benchmark: function(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
const benchmark = fn();
|
||||
(benchmark && benchmark.then ? benchmark : $.when()).then(() => {
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
},
|
||||
|
||||
log: function(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.$log[0].appendChild(div);
|
||||
this.$log[0].scrollTop = this.$log[0].scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
// prepare QWeb
|
||||
const templates = await fetch("templates.xml");
|
||||
let strTemplates = await templates.text();
|
||||
strTemplates = strTemplates.replace(/<!--[\s\S]*?-->/g, "");
|
||||
core.qweb.add_template(strTemplates);
|
||||
|
||||
// prepare app
|
||||
const app = new App();
|
||||
await app.appendTo(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Odoo Widget Benchmark (12.3)</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='web.assets_common.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,47 +0,0 @@
|
||||
<templates>
|
||||
<div class="main" t-name="root">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button class="o_btn_msg 100">Add 100 messages</button>
|
||||
<button class="o_btn_msg 1000">Add 1k messages</button>
|
||||
<button class="o_btn_msg 10000">Add 10k messages</button>
|
||||
<button class="o_btn_msg 30000">Add 30k messages</button>
|
||||
<button class="updateSomeMessages">Update every 10th message</button>
|
||||
<button class="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" class="o_multiple" id="multipleflag" />
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" class="o_clear" id="clearFlag" />
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info message_count">
|
||||
Number of messages: 0
|
||||
</div>
|
||||
<div class="title">Log <span class="clear-log">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="message" class="message">
|
||||
<span class="author"><t t-esc="widget.author"/></span>
|
||||
<span class="msg"><t t-esc="widget.msg"/></span>
|
||||
<button class="remove">Remove</button>
|
||||
</div>
|
||||
|
||||
<div t-name="counter">
|
||||
<button class="o_increment"></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,170 +0,0 @@
|
||||
import {
|
||||
buildData,
|
||||
startMeasure,
|
||||
stopMeasure,
|
||||
formatNumber
|
||||
} from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
widgets = { Counter };
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps !== this.props;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove_message", {
|
||||
id: this.props.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
widgets = { Message };
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
|
||||
mounted() {
|
||||
this.log(
|
||||
`Benchmarking Owl v${owl._version} (build date: ${
|
||||
owl._date
|
||||
})`
|
||||
);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
this.set(messages, i, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(data) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
|
||||
toggleMultiple() {
|
||||
this.state.multipleFlag = !this.state.multipleFlag;
|
||||
}
|
||||
|
||||
toggleClear() {
|
||||
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL 0.11.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="'log'"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.author"/></span>
|
||||
<span class="msg"><t t-esc="props.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<t t-widget="Counter"/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,161 +0,0 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
widgets = { Counter };
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps.message !== this.props.message;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove-message", {
|
||||
id: this.props.message.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
widgets = { Message };
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
this.set(messages, i, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(event) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
|
||||
toggleMultiple() {
|
||||
this.state.multipleFlag = !this.state.multipleFlag;
|
||||
}
|
||||
|
||||
toggleClear() {
|
||||
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL 0.13.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="log"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<t t-widget="Message" t-key="message.id" message="message"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.message.author"/></span>
|
||||
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<t t-widget="Counter"/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,161 +0,0 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
widgets = { Counter };
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps.message !== this.props.message;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove-message", {
|
||||
id: this.props.message.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
widgets = { Message };
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
this.set(messages, i, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(event) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
|
||||
toggleMultiple() {
|
||||
this.state.multipleFlag = !this.state.multipleFlag;
|
||||
}
|
||||
|
||||
toggleClear() {
|
||||
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL 0.15.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="log"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<t t-widget="Message" t-key="message.id" message="message"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.message.author"/></span>
|
||||
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<t t-widget="Counter"/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,161 +0,0 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
components = { Counter };
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps.message !== this.props.message;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove-message", {
|
||||
id: this.props.message.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
components = { Message };
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
owl.Observer.set(messages, i, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(event) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
|
||||
toggleMultiple() {
|
||||
this.state.multipleFlag = !this.state.multipleFlag;
|
||||
}
|
||||
|
||||
toggleClear() {
|
||||
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL 0.17.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="log"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<Message t-key="message.id" message="message"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.message.author"/></span>
|
||||
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<Counter/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,161 +0,0 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
components = { Counter };
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps.message !== this.props.message;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove-message", {
|
||||
id: this.props.message.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
components = { Message };
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
messages[i] = msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(event) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
|
||||
toggleMultiple() {
|
||||
this.state.multipleFlag = !this.state.multipleFlag;
|
||||
}
|
||||
|
||||
toggleClear() {
|
||||
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL 0.18.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='main'></div>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="log"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<Message t-key="message.id" message="message"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.message.author"/></span>
|
||||
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<Counter/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,151 +0,0 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps.message !== this.props.message;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove-message", {
|
||||
id: this.props.message.id
|
||||
});
|
||||
}
|
||||
}
|
||||
Message.components = { Counter };
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
messages[i] = msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(event) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
}
|
||||
App.components = { Message };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL 0.21.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-model="multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-model="clearAfterFlag" />
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="log"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<Message t-key="message.id" message="message"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.message.author"/></span>
|
||||
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<Counter/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,155 +0,0 @@
|
||||
import { buildData, startMeasure, stopMeasure, formatNumber } from "../shared/utils.js";
|
||||
|
||||
const { useState, useRef } = owl.hooks;
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = useState({ counter: 0 });
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
static components = { Counter };
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps.message !== this.props.message;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove-message", {
|
||||
id: this.props.message.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
static components = { Message };
|
||||
state = useState({ messages: [], multipleFlag: false, clearAfterFlag: false });
|
||||
logRef = useRef("log");
|
||||
|
||||
mounted() {
|
||||
this.log(`Benchmarking Owl v${owl.__info__.version} (build date: ${owl.__info__.date})`);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
messages[i] = msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(event) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.logRef.el.appendChild(div);
|
||||
this.logRef.el.scrollTop = this.logRef.el.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.logRef.el.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadFile("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OWL v0.24.0 Benchmark</title>
|
||||
<link href="../shared/main.css" rel="stylesheet"/>
|
||||
<script src='owl.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src='app.js' type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
<templates>
|
||||
<div t-name="App" class="main">
|
||||
<div class="left-thing">
|
||||
<div class="title">Actions</div>
|
||||
<div class="panel">
|
||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||
<button t-on-click="clear">Clear</button>
|
||||
</div>
|
||||
<div class="flags">
|
||||
<div>
|
||||
<input type="checkbox" id="multipleflag" t-model="state.multipleFlag"/>
|
||||
<label for="multipleflag">Do it 20x</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="clearFlag" t-model="state.clearAfterFlag" />
|
||||
<label for="clearFlag">Clear after</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||
<hr/>
|
||||
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||
<div class="log">
|
||||
<div class="log-content" t-ref="log"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-thing">
|
||||
<div class="content" t-on-remove-message="removeMessage">
|
||||
<t t-foreach="state.messages" t-as="message">
|
||||
<Message t-key="message.id" message="message"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-name="Message" class="message">
|
||||
<span class="author"><t t-esc="props.message.author"/></span>
|
||||
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||
<Counter/>
|
||||
</div>
|
||||
|
||||
<div t-name="Counter">
|
||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,173 +0,0 @@
|
||||
import {
|
||||
buildData,
|
||||
startMeasure,
|
||||
stopMeasure,
|
||||
formatNumber
|
||||
} from "../shared/utils.js";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Likes Counter Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Counter extends owl.Component {
|
||||
state = { counter: 0 };
|
||||
template = "Counter";
|
||||
|
||||
increment() {
|
||||
this.state.counter++;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Message Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class Message extends owl.Component {
|
||||
widgets = { Counter };
|
||||
template = "Message";
|
||||
|
||||
shouldUpdate(nextProps) {
|
||||
return nextProps !== this.props;
|
||||
}
|
||||
removeMessage() {
|
||||
this.trigger("remove_message", {
|
||||
id: this.props.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Root Widget
|
||||
//------------------------------------------------------------------------------
|
||||
class App extends owl.Component {
|
||||
widgets = { Message };
|
||||
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||
template = "App";
|
||||
|
||||
mounted() {
|
||||
this.log(
|
||||
`Benchmarking Owl v${owl._version} (build date: ${
|
||||
owl._date
|
||||
})`
|
||||
);
|
||||
}
|
||||
|
||||
benchmark(message, fn, callback) {
|
||||
if (this.state.multipleFlag) {
|
||||
const N = 20;
|
||||
let n = N;
|
||||
let total = 0;
|
||||
let cb = info => {
|
||||
let finalize = () => {
|
||||
n--;
|
||||
total += info.delta;
|
||||
if (n === 0) {
|
||||
const avg = total / N;
|
||||
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
this._benchmark(message, fn, cb);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.clearAfterFlag) {
|
||||
this._benchmark(
|
||||
"clear",
|
||||
() => {
|
||||
this.state.messages = [];
|
||||
},
|
||||
finalize,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
};
|
||||
this._benchmark(message, fn, cb);
|
||||
} else {
|
||||
this._benchmark(message, fn, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_benchmark(message, fn, cb, log = true) {
|
||||
setTimeout(() => {
|
||||
startMeasure(message);
|
||||
fn();
|
||||
stopMeasure(info => {
|
||||
if (log) {
|
||||
this.log(info.msg);
|
||||
}
|
||||
if (cb) {
|
||||
cb(info);
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
addMessages(n) {
|
||||
this.benchmark("add " + n, () => {
|
||||
const newMessages = buildData(n);
|
||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._benchmark("clear", () => {
|
||||
this.state.messages = [];
|
||||
});
|
||||
}
|
||||
|
||||
updateSomeMessages() {
|
||||
this.benchmark("update every 10th", () => {
|
||||
const messages = this.state.messages;
|
||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||
const msg = Object.assign({}, messages[i]);
|
||||
msg.author += "!!!";
|
||||
this.set(messages, i, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMessage(data) {
|
||||
this.benchmark("remove message", () => {
|
||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||
this.state.messages.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
log(str, isBold) {
|
||||
const div = document.createElement("div");
|
||||
if (isBold) {
|
||||
div.classList.add("bold");
|
||||
}
|
||||
div.textContent = `> ${str}`;
|
||||
this.refs.log.appendChild(div);
|
||||
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||
}
|
||||
|
||||
clearLog() {
|
||||
this.refs.log.innerHTML = "";
|
||||
}
|
||||
|
||||
toggleMultiple() {
|
||||
this.state.multipleFlag = !this.state.multipleFlag;
|
||||
}
|
||||
|
||||
toggleClear() {
|
||||
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Application initialization
|
||||
//------------------------------------------------------------------------------
|
||||
async function start() {
|
||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||
const env = {
|
||||
qweb: new owl.QWeb(templates)
|
||||
};
|
||||
const app = new App(env);
|
||||
app.mount(document.body);
|
||||
}
|
||||
|
||||
start();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user