Compare commits

..

1 Commits

Author SHA1 Message Date
Géry Debongnie ba5365e9d9 [IMP] app: add a setting to share compiled templates between apps 2022-07-22 09:32:26 +02:00
63 changed files with 625 additions and 1730 deletions
+1 -1
View File
@@ -124,5 +124,5 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here: If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl](https://github.com/odoo/owl/releases/latest) - [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
+2 -3
View File
@@ -770,11 +770,10 @@ For reference, here is the final code:
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>OWL Todo App</title> <title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="app.css" />
</head>
<body>
<script src="owl.js"></script> <script src="owl.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
</body> </head>
<body></body>
</html> </html>
``` ```
+3
View File
@@ -55,6 +55,9 @@ The `config` object is an object with some of the following keys:
[`dev` mode](#dev-mode); [`dev` mode](#dev-mode);
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except - **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
that Owl will not log a message to warn that Owl is in `dev` mode. that Owl will not log a message to warn that Owl is in `dev` mode.
- **`shareTemplates (boolean, default=false)`**: if `true`, each compiled template
will be shared between instances of `App`. Useful for speeding test suites, because
it prevent recompiling the same templates again and again.
- **`translatableAttributes (string[])`**: a list of additional attributes that should - **`translatableAttributes (string[])`**: a list of additional attributes that should
be translated (see [translations](translations.md)) be translated (see [translations](translations.md))
- **`translateFn (function)`**: a function that will be called by owl to translate - **`translateFn (function)`**: a function that will be called by owl to translate
+1 -2
View File
@@ -234,8 +234,7 @@ are defined by a function instead of just the dependencies.
The `useEffect` hook takes two function: the effect function and the dependency The `useEffect` hook takes two function: the effect function and the dependency
function. The effect function perform some task and return (optionally) a cleanup function. The effect function perform some task and return (optionally) a cleanup
function. The dependency function returns a list of dependencies, these dependencies function. The dependency function returns a list of dependencies. If any of these
are passed as parameters in the effect function . If any of these
dependencies changes, then the current effect will be cleaned up and reexecuted. dependencies changes, then the current effect will be cleaned up and reexecuted.
Here is an example without any dependencies: Here is an example without any dependencies:
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.0.1", "version": "2.0.0-beta-15",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"module": "dist/owl.es.js", "module": "dist/owl.es.js",
+23 -4
View File
@@ -1,9 +1,28 @@
# 🦉 OWL Roadmap 🦉 # 🦉 OWL Roadmap 🦉
- Current version: 2.X - Current version: 1.4.10
- Status: stable - Status: stable
Owl is currently stable. No (large) improvements is expected in the near future. This roadmap is only an attempt at predicting Owl's future. Everything may
change!
### 1.x
- add chrome and firefox devtools,
- fix every bugs,
- improve documentation,
- small backward compatible improvements.
### 2.x (2020? 2021? 2022?)
- stop support for `t-set` directive to define the content of a slot
Maybe:
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
much faster
- refactor `QWeb` to use an intermediate representation (some kind of AST) to
allow additional optimisations.
Note that we intend to keep maintaining owl, and as such, improvements and/or
breaking changes may require a version bump in the future.
+81 -104
View File
@@ -214,14 +214,6 @@ class CodeTarget {
result.push(`}`); result.push(`}`);
return result.join("\n "); return result.join("\n ");
} }
currentKey(ctx: Context) {
let key = this.loopLevel ? `key${this.loopLevel}` : "key";
if (ctx.tKeyExpr) {
key = `${ctx.tKeyExpr} + ${key}`;
}
return key;
}
} }
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"]; const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
@@ -240,7 +232,6 @@ export class CodeGenerator {
translatableAttributes: string[] = TRANSLATABLE_ATTRS; translatableAttributes: string[] = TRANSLATABLE_ATTRS;
ast: AST; ast: AST;
staticDefs: { id: string; expr: string }[] = []; staticDefs: { id: string; expr: string }[] = [];
slotNames: Set<String> = new Set();
helpers: Set<string> = new Set(); helpers: Set<string> = new Set();
constructor(ast: AST, options: CodeGenOptions) { constructor(ast: AST, options: CodeGenOptions) {
@@ -373,15 +364,19 @@ export class CodeGenerator {
insertBlock(expression: string, block: BlockDescription, ctx: Context): void { insertBlock(expression: string, block: BlockDescription, ctx: Context): void {
let blockExpr = block.generateExpr(expression); let blockExpr = block.generateExpr(expression);
const tKeyExpr = ctx.tKeyExpr;
if (block.parentVar) { if (block.parentVar) {
let key = this.target.currentKey(ctx); let keyArg = `key${this.target.loopLevel}`;
if (tKeyExpr) {
keyArg = `${tKeyExpr} + ${keyArg}`;
}
this.helpers.add("withKey"); this.helpers.add("withKey");
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`); this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
return; return;
} }
if (ctx.tKeyExpr) { if (tKeyExpr) {
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`; blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
} }
if (block.isRoot && !ctx.preventRoot) { if (block.isRoot && !ctx.preventRoot) {
@@ -428,66 +423,78 @@ export class CodeGenerator {
.join(""); .join("");
} }
/** compileAST(ast: AST, ctx: Context) {
* @returns the newly created block name, if any
*/
compileAST(ast: AST, ctx: Context): string | null {
switch (ast.type) { switch (ast.type) {
case ASTType.Comment: case ASTType.Comment:
return this.compileComment(ast, ctx); this.compileComment(ast, ctx);
break;
case ASTType.Text: case ASTType.Text:
return this.compileText(ast, ctx); this.compileText(ast, ctx);
break;
case ASTType.DomNode: case ASTType.DomNode:
return this.compileTDomNode(ast, ctx); this.compileTDomNode(ast, ctx);
break;
case ASTType.TEsc: case ASTType.TEsc:
return this.compileTEsc(ast, ctx); this.compileTEsc(ast, ctx);
break;
case ASTType.TOut: case ASTType.TOut:
return this.compileTOut(ast, ctx); this.compileTOut(ast, ctx);
break;
case ASTType.TIf: case ASTType.TIf:
return this.compileTIf(ast, ctx); this.compileTIf(ast, ctx);
break;
case ASTType.TForEach: case ASTType.TForEach:
return this.compileTForeach(ast, ctx); this.compileTForeach(ast, ctx);
break;
case ASTType.TKey: case ASTType.TKey:
return this.compileTKey(ast, ctx); this.compileTKey(ast, ctx);
break;
case ASTType.Multi: case ASTType.Multi:
return this.compileMulti(ast, ctx); this.compileMulti(ast, ctx);
break;
case ASTType.TCall: case ASTType.TCall:
return this.compileTCall(ast, ctx); this.compileTCall(ast, ctx);
break;
case ASTType.TCallBlock: case ASTType.TCallBlock:
return this.compileTCallBlock(ast, ctx); this.compileTCallBlock(ast, ctx);
break;
case ASTType.TSet: case ASTType.TSet:
return this.compileTSet(ast, ctx); this.compileTSet(ast, ctx);
break;
case ASTType.TComponent: case ASTType.TComponent:
return this.compileComponent(ast, ctx); this.compileComponent(ast, ctx);
break;
case ASTType.TDebug: case ASTType.TDebug:
return this.compileDebug(ast, ctx); this.compileDebug(ast, ctx);
break;
case ASTType.TLog: case ASTType.TLog:
return this.compileLog(ast, ctx); this.compileLog(ast, ctx);
break;
case ASTType.TSlot: case ASTType.TSlot:
return this.compileTSlot(ast, ctx); this.compileTSlot(ast, ctx);
break;
case ASTType.TTranslation: case ASTType.TTranslation:
return this.compileTTranslation(ast, ctx); this.compileTTranslation(ast, ctx);
break;
case ASTType.TPortal: case ASTType.TPortal:
return this.compileTPortal(ast, ctx); this.compileTPortal(ast, ctx);
} }
} }
compileDebug(ast: ASTDebug, ctx: Context): string | null { compileDebug(ast: ASTDebug, ctx: Context) {
this.addLine(`debugger;`); this.addLine(`debugger;`);
if (ast.content) { if (ast.content) {
return this.compileAST(ast.content, ctx); this.compileAST(ast.content, ctx);
} }
return null;
} }
compileLog(ast: ASTLog, ctx: Context): string | null { compileLog(ast: ASTLog, ctx: Context) {
this.addLine(`console.log(${compileExpr(ast.expr)});`); this.addLine(`console.log(${compileExpr(ast.expr)});`);
if (ast.content) { if (ast.content) {
return this.compileAST(ast.content, ctx); this.compileAST(ast.content, ctx);
} }
return null;
} }
compileComment(ast: ASTComment, ctx: Context): string { compileComment(ast: ASTComment, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock; const isNewBlock = !block || forceNewBlock;
if (isNewBlock) { if (isNewBlock) {
@@ -500,10 +507,9 @@ export class CodeGenerator {
const text = xmlDoc.createComment(ast.value); const text = xmlDoc.createComment(ast.value);
block!.insert(text); block!.insert(text);
} }
return block!.varName;
} }
compileText(ast: ASTText, ctx: Context): string { compileText(ast: ASTText, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let value = ast.value; let value = ast.value;
@@ -522,7 +528,6 @@ export class CodeGenerator {
const createFn = ast.type === ASTType.Text ? xmlDoc.createTextNode : xmlDoc.createComment; const createFn = ast.type === ASTType.Text ? xmlDoc.createTextNode : xmlDoc.createComment;
block.insert(createFn.call(xmlDoc, value)); block.insert(createFn.call(xmlDoc, value));
} }
return block.varName;
} }
generateHandlerCode(rawEvent: string, handler: string): string { generateHandlerCode(rawEvent: string, handler: string): string {
@@ -542,7 +547,7 @@ export class CodeGenerator {
return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`; return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
} }
compileTDomNode(ast: ASTDomNode, ctx: Context): string { compileTDomNode(ast: ASTDomNode, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns; const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
let codeIdx = this.target.code.length; let codeIdx = this.target.code.length;
@@ -579,12 +584,8 @@ export class CodeGenerator {
expr = compileExpr(ast.attrs[key]); expr = compileExpr(ast.attrs[key]);
if (attrName && isProp(ast.tag, attrName)) { if (attrName && isProp(ast.tag, attrName)) {
// we force a new string or new boolean to bypass the equality check in blockdom when patching same value // we force a new string or new boolean to bypass the equality check in blockdom when patching same value
if (attrName === "value") { const C = attrName === "value" ? "String" : "Boolean";
// When the expression is falsy, fall back to an empty string expr = `new ${C}(${expr})`;
expr = `new String((${expr}) || "")`;
} else {
expr = `new Boolean(${expr})`;
}
} }
const idx = block!.insertData(expr, "attr"); const idx = block!.insertData(expr, "attr");
if (key === "t-att") { if (key === "t-att") {
@@ -729,10 +730,9 @@ export class CodeGenerator {
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx); this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
} }
} }
return block!.varName;
} }
compileTEsc(ast: ASTTEsc, ctx: Context): string { compileTEsc(ast: ASTTEsc, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let expr: string; let expr: string;
if (ast.expr === "0") { if (ast.expr === "0") {
@@ -753,10 +753,9 @@ export class CodeGenerator {
const text = xmlDoc.createElement(`block-text-${idx}`); const text = xmlDoc.createElement(`block-text-${idx}`);
block.insert(text); block.insert(text);
} }
return block.varName;
} }
compileTOut(ast: ASTTOut, ctx: Context): string { compileTOut(ast: ASTTOut, ctx: Context) {
let { block } = ctx; let { block } = ctx;
if (block) { if (block) {
this.insertAnchor(block); this.insertAnchor(block);
@@ -778,7 +777,6 @@ export class CodeGenerator {
blockStr = `safeOutput(${compileExpr(ast.expr)})`; blockStr = `safeOutput(${compileExpr(ast.expr)})`;
} }
this.insertBlock(blockStr, block, ctx); this.insertBlock(blockStr, block, ctx);
return block.varName;
} }
compileTIfBranch(content: AST, block: BlockDescription, ctx: Context) { compileTIfBranch(content: AST, block: BlockDescription, ctx: Context) {
@@ -792,7 +790,7 @@ export class CodeGenerator {
this.target.indentLevel--; this.target.indentLevel--;
} }
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode): string { compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const codeIdx = this.target.code.length; const codeIdx = this.target.code.length;
const isNewBlock = !block || (block.type !== "multi" && forceNewBlock); const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
@@ -835,10 +833,9 @@ export class CodeGenerator {
const args = block!.children.map((c) => c.varName).join(", "); const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}])`, block!, ctx)!; this.insertBlock(`multi([${args}])`, block!, ctx)!;
} }
return block.varName;
} }
compileTForeach(ast: ASTTForEach, ctx: Context): string { compileTForeach(ast: ASTTForEach, ctx: Context) {
let { block } = ctx; let { block } = ctx;
if (block) { if (block) {
this.insertAnchor(block); this.insertAnchor(block);
@@ -877,9 +874,9 @@ export class CodeGenerator {
// Throw error on duplicate keys in dev mode // Throw error on duplicate keys in dev mode
this.helpers.add("OwlError"); this.helpers.add("OwlError");
this.addLine( this.addLine(
`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}` `if (keys${block.id}.has(key${this.target.loopLevel})) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`
); );
this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`); this.addLine(`keys${block.id}.add(key${this.target.loopLevel});`);
} }
let id: string; let id: string;
if (ast.memo) { if (ast.memo) {
@@ -916,10 +913,9 @@ export class CodeGenerator {
this.addLine(`ctx = ctx.__proto__;`); this.addLine(`ctx = ctx.__proto__;`);
} }
this.insertBlock("l", block, ctx); this.insertBlock("l", block, ctx);
return block.varName;
} }
compileTKey(ast: ASTTKey, ctx: Context): string | null { compileTKey(ast: ASTTKey, ctx: Context) {
const tKeyExpr = generateId("tKey_"); const tKeyExpr = generateId("tKey_");
this.define(tKeyExpr, compileExpr(ast.expr)); this.define(tKeyExpr, compileExpr(ast.expr));
ctx = createContext(ctx, { ctx = createContext(ctx, {
@@ -927,22 +923,20 @@ export class CodeGenerator {
block: ctx.block, block: ctx.block,
index: ctx.index, index: ctx.index,
}); });
return this.compileAST(ast.content, ctx); this.compileAST(ast.content, ctx);
} }
compileMulti(ast: ASTMulti, ctx: Context): string | null { compileMulti(ast: ASTMulti, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock; const isNewBlock = !block || forceNewBlock;
let codeIdx = this.target.code.length; let codeIdx = this.target.code.length;
if (isNewBlock) { if (isNewBlock) {
const n = ast.content.filter((c) => c.type !== ASTType.TSet).length; const n = ast.content.filter((c) => c.type !== ASTType.TSet).length;
let result: string | null = null;
if (n <= 1) { if (n <= 1) {
for (let child of ast.content) { for (let child of ast.content) {
const blockName = this.compileAST(child, ctx); this.compileAST(child, ctx);
result = result || blockName;
} }
return result; return;
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
} }
@@ -982,10 +976,9 @@ export class CodeGenerator {
const args = block!.children.map((c) => c.varName).join(", "); const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}])`, block!, ctx)!; this.insertBlock(`multi([${args}])`, block!, ctx)!;
} }
return block!.varName;
} }
compileTCall(ast: ASTTCall, ctx: Context): string { compileTCall(ast: ASTTCall, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let ctxVar = ctx.ctxVar || "ctx"; let ctxVar = ctx.ctxVar || "ctx";
if (ast.context) { if (ast.context) {
@@ -996,11 +989,12 @@ export class CodeGenerator {
this.addLine(`${ctxVar} = Object.create(${ctxVar});`); this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
this.addLine(`${ctxVar}[isBoundary] = 1;`); this.addLine(`${ctxVar}[isBoundary] = 1;`);
this.helpers.add("isBoundary"); this.helpers.add("isBoundary");
const nextId = BlockDescription.nextBlockId;
const subCtx = createContext(ctx, { preventRoot: true, ctxVar }); const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx); this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
if (bl) { if (nextId !== BlockDescription.nextBlockId) {
this.helpers.add("zero"); this.helpers.add("zero");
this.addLine(`${ctxVar}[zero] = ${bl};`); this.addLine(`${ctxVar}[zero] = b${nextId};`);
} }
} }
const isDynamic = INTERP_REGEXP.test(ast.name); const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -1034,10 +1028,9 @@ export class CodeGenerator {
if (ast.body && !ctx.isLast) { if (ast.body && !ctx.isLast) {
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`); this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
} }
return block.varName;
} }
compileTCallBlock(ast: ASTTCallBlock, ctx: Context): string { compileTCallBlock(ast: ASTTCallBlock, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
if (block) { if (block) {
if (!forceNewBlock) { if (!forceNewBlock) {
@@ -1046,10 +1039,9 @@ export class CodeGenerator {
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block }); this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
return block.varName;
} }
compileTSet(ast: ASTTSet, ctx: Context): null { compileTSet(ast: ASTTSet, ctx: Context) {
this.target.shouldProtectScope = true; this.target.shouldProtectScope = true;
this.helpers.add("isBoundary").add("withDefault"); this.helpers.add("isBoundary").add("withDefault");
const expr = ast.value ? compileExpr(ast.value || "") : "null"; const expr = ast.value ? compileExpr(ast.value || "") : "null";
@@ -1057,8 +1049,7 @@ export class CodeGenerator {
this.helpers.add("LazyValue"); this.helpers.add("LazyValue");
const bodyAst: AST = { type: ASTType.Multi, content: ast.body }; const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
const name = this.compileInNewTarget("value", bodyAst, ctx); const name = this.compileInNewTarget("value", bodyAst, ctx);
let key = this.target.currentKey(ctx); let value = `new LazyValue(${name}, ctx, this, node)`;
let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value; value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
this.addLine(`ctx[\`${ast.name}\`] = ${value};`); this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
} else { } else {
@@ -1075,7 +1066,6 @@ export class CodeGenerator {
this.helpers.add("setContextValue"); this.helpers.add("setContextValue");
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`); this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
} }
return null;
} }
generateComponentKey() { generateComponentKey() {
@@ -1127,7 +1117,7 @@ export class CodeGenerator {
return propString; return propString;
} }
compileComponent(ast: ASTComponent, ctx: Context): string { compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx; let { block } = ctx;
// props // props
const hasSlotsProp = "slots" in (ast.props || {}); const hasSlotsProp = "slots" in (ast.props || {});
@@ -1193,7 +1183,7 @@ export class CodeGenerator {
} }
if (this.dev) { if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, this);`); this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
} }
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) { if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1227,7 +1217,6 @@ export class CodeGenerator {
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockExpr, block, ctx); this.insertBlock(blockExpr, block, ctx);
return block.varName;
} }
wrapWithEventCatcher(expr: string, on: EventHandlers): string { wrapWithEventCatcher(expr: string, on: EventHandlers): string {
@@ -1246,43 +1235,34 @@ export class CodeGenerator {
return `${name}(${expr}, [${handlers.join(",")}])`; return `${name}(${expr}, [${handlers.join(",")}])`;
} }
compileTSlot(ast: ASTSlot, ctx: Context): string { compileTSlot(ast: ASTSlot, ctx: Context) {
this.helpers.add("callSlot"); this.helpers.add("callSlot");
let { block } = ctx; let { block } = ctx;
let blockString: string; let blockString: string;
let slotName; let slotName;
let dynamic = false; let dynamic = false;
let isMultiple = false;
if (ast.name.match(INTERP_REGEXP)) { if (ast.name.match(INTERP_REGEXP)) {
dynamic = true; dynamic = true;
isMultiple = true;
slotName = interpolate(ast.name); slotName = interpolate(ast.name);
} else { } else {
slotName = "'" + ast.name + "'"; slotName = "'" + ast.name + "'";
isMultiple = isMultiple || this.slotNames.has(ast.name);
this.slotNames.add(ast.name);
} }
const dynProps = ast.attrs ? ast.attrs["t-props"] : null; const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
if (ast.attrs) { if (ast.attrs) {
delete ast.attrs["t-props"]; delete ast.attrs["t-props"];
} }
let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
if (isMultiple) {
key = `${key} + \`${this.generateComponentKey()}\``;
}
const props = ast.attrs ? this.formatPropObject(ast.attrs) : []; const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
const scope = this.getPropString(props, dynProps); const scope = this.getPropString(props, dynProps);
if (ast.defaultContent) { if (ast.defaultContent) {
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx); const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name})`; blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
} else { } else {
if (dynamic) { if (dynamic) {
let name = generateId("slot"); let name = generateId("slot");
this.define(name, slotName); this.define(name, slotName);
blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`; blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
} else { } else {
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`; blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
} }
} }
// event handling // event handling
@@ -1295,16 +1275,14 @@ export class CodeGenerator {
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false }); this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
} }
compileTTranslation(ast: ASTTranslation, ctx: Context): string | null { compileTTranslation(ast: ASTTranslation, ctx: Context) {
if (ast.content) { if (ast.content) {
return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false })); this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
} }
return null;
} }
compileTPortal(ast: ASTTPortal, ctx: Context): string { compileTPortal(ast: ASTTPortal, ctx: Context) {
if (!this.staticDefs.find((d) => d.id === "Portal")) { if (!this.staticDefs.find((d) => d.id === "Portal")) {
this.staticDefs.push({ id: "Portal", expr: `app.Portal` }); this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
} }
@@ -1331,6 +1309,5 @@ export class CodeGenerator {
} }
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false }); this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
} }
} }
+2 -3
View File
@@ -781,9 +781,8 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
// default slot // default slot
const defaultContent = parseChildNodes(clone, ctx); const defaultContent = parseChildNodes(clone, ctx);
slots = slots || {}; if (defaultContent) {
// t-set-slot="default" has priority over content slots = slots || {};
if (defaultContent && !slots.default) {
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope }; slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
} }
} }
+3 -6
View File
@@ -6,7 +6,6 @@ import { Scheduler } from "./scheduler";
import { validateProps } from "./template_helpers"; import { validateProps } from "./template_helpers";
import { TemplateSet, TemplateSetConfig } from "./template_set"; import { TemplateSet, TemplateSetConfig } from "./template_set";
import { validateTarget } from "./utils"; import { validateTarget } from "./utils";
import { handleError } from "./error_handling";
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f // reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
@@ -95,7 +94,9 @@ export class App<
nodeErrorHandlers.set(node, handlers); nodeErrorHandlers.set(node, handlers);
} }
handlers.unshift((e) => { handlers.unshift((e) => {
if (!isResolved) { if (isResolved) {
console.error(e);
} else {
reject(e); reject(e);
} }
throw e; throw e;
@@ -168,10 +169,6 @@ export class App<
return node; return node;
}; };
} }
handleError(...args: Parameters<typeof handleError>) {
return handleError(...args);
}
} }
export async function mount< export async function mount<
+3 -16
View File
@@ -157,15 +157,6 @@ function buildTree(
: document.createElement(tagName); : document.createElement(tagName);
} }
if (el instanceof Element) { if (el instanceof Element) {
if (!domParentTree) {
// some html elements may have side effects when setting their attributes.
// For example, setting the src attribute of an <img/> will trigger a
// request to get the corresponding image. This is something that we
// don't want at compile time. We avoid that by putting the content of
// the block in a <template/> element
const fragment = document.createElement("template").content;
fragment.appendChild(el);
}
for (let i = 0; i < attrs.length; i++) { for (let i = 0; i < attrs.length; i++) {
const attrName = attrs[i].name; const attrName = attrs[i].name;
const attrValue = attrs[i].value; const attrValue = attrs[i].value;
@@ -517,13 +508,9 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
return this.el!; return this.el!;
} }
moveBeforeDOMNode(node: Node | null, parent = this.parentEl) { moveBefore(other: Block | null, afterNode: Node | null) {
this.parentEl = parent; const target = other ? other.el! : afterNode;
nodeInsertBefore.call(parent, this.el!, node); nodeInsertBefore.call(this.parentEl, this.el!, target);
}
moveBeforeVNode(other: Block | null, afterNode: Node | null) {
nodeInsertBefore.call(this.parentEl, this.el!, other ? other.el! : afterNode);
} }
toString() { toString() {
+3 -13
View File
@@ -46,7 +46,7 @@ export function createCatcher(eventsSpec: EventsSpec): Catcher {
const target = ev.target; const target = ev.target;
let currentNode: any = self.child.firstNode(); let currentNode: any = self.child.firstNode();
const afterNode = self.afterNode; const afterNode = self.afterNode;
while (currentNode && currentNode !== afterNode) { while (currentNode !== afterNode) {
if (currentNode.contains(target)) { if (currentNode.contains(target)) {
return origFn.call(this, ev); return origFn.call(this, ev);
} }
@@ -56,18 +56,8 @@ export function createCatcher(eventsSpec: EventsSpec): Catcher {
} }
} }
moveBeforeDOMNode(node: Node | null, parent = this.parentEl) { moveBefore(other: VCatcher | null, afterNode: Node | null) {
this.parentEl = parent; this.child.moveBefore(other ? other.child : null, afterNode);
this.child.moveBeforeDOMNode(node, parent);
parent!.insertBefore(this.afterNode!, node);
}
moveBeforeVNode(other: VCatcher | null, afterNode: Node | null) {
if (other) {
// check this with @ged-odoo for use in foreach
afterNode = other.firstNode() || afterNode;
}
this.child.moveBeforeVNode(other ? other.child : null, afterNode);
this.parentEl!.insertBefore(this.afterNode!, afterNode); this.parentEl!.insertBefore(this.afterNode!, afterNode);
} }
+2 -2
View File
@@ -27,8 +27,8 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
} }
function listener(ev: Event) { function listener(ev: Event) {
const currentTarget = ev.currentTarget as HTMLElement; const currentTarget = ev.currentTarget;
if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) return; if (!currentTarget || !document.contains(currentTarget as HTMLElement)) return;
const data = (currentTarget as any)[eventKey]; const data = (currentTarget as any)[eventKey];
if (!data) return; if (!data) return;
config.mainEventHandler(data, ev, currentTarget); config.mainEventHandler(data, ev, currentTarget);
+5 -9
View File
@@ -29,16 +29,12 @@ class VHtml {
} }
} }
moveBeforeDOMNode(node: Node | null, parent = this.parentEl) { moveBefore(other: VHtml | null, afterNode: Node | null) {
this.parentEl = parent;
for (let elem of this.content) {
nodeInsertBefore.call(parent, elem, node);
}
}
moveBeforeVNode(other: VHtml | null, afterNode: Node | null) {
const target = other ? other.content[0] : afterNode; const target = other ? other.content[0] : afterNode;
this.moveBeforeDOMNode(target); const parent = this.parentEl;
for (let elem of this.content) {
nodeInsertBefore.call(parent, elem, target);
}
} }
patch(other: VHtml) { patch(other: VHtml) {
+1 -2
View File
@@ -10,8 +10,7 @@ export { createCatcher } from "./event_catcher";
export interface VNode<T = any> { export interface VNode<T = any> {
mount(parent: HTMLElement, afterNode: Node | null): void; mount(parent: HTMLElement, afterNode: Node | null): void;
moveBeforeDOMNode(node: Node | null, parent?: HTMLElement): void; moveBefore(other: T | null, afterNode: Node | null): void;
moveBeforeVNode(other: T | null, afterNode: Node | null): void;
patch(other: T, withBeforeRemove: boolean): void; patch(other: T, withBeforeRemove: boolean): void;
beforeRemove(): void; beforeRemove(): void;
remove(): void; remove(): void;
+3 -12
View File
@@ -38,23 +38,14 @@ class VList {
this.parentEl = parent; this.parentEl = parent;
} }
moveBeforeDOMNode(node: Node | null, parent = this.parentEl) { moveBefore(other: VList | null, afterNode: Node | null) {
this.parentEl = parent;
const children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].moveBeforeDOMNode(node, parent);
}
parent!.insertBefore(this.anchor!, node);
}
moveBeforeVNode(other: VList | null, afterNode: Node | null) {
if (other) { if (other) {
const next = other!.children[0]; const next = other!.children[0];
afterNode = (next ? next.firstNode() : other!.anchor) || null; afterNode = (next ? next.firstNode() : other!.anchor) || null;
} }
const children = this.children; const children = this.children;
for (let i = 0, l = children.length; i < l; i++) { for (let i = 0, l = children.length; i < l; i++) {
children[i].moveBeforeVNode(null, afterNode); children[i].moveBefore(null, afterNode);
} }
this.parentEl!.insertBefore(this.anchor!, afterNode); this.parentEl!.insertBefore(this.anchor!, afterNode);
} }
@@ -75,7 +66,7 @@ class VList {
patch: cPatch, patch: cPatch,
remove: cRemove, remove: cRemove,
beforeRemove, beforeRemove,
moveBeforeVNode: cMoveBefore, moveBefore: cMoveBefore,
firstNode: cFirstNode, firstNode: cFirstNode,
} = proto; } = proto;
+2 -17
View File
@@ -38,22 +38,7 @@ export class VMulti {
this.parentEl = parent; this.parentEl = parent;
} }
moveBeforeDOMNode(node: Node | null, parent = this.parentEl) { moveBefore(other: VMulti | null, afterNode: Node | null) {
this.parentEl = parent;
const children = this.children;
const anchors = this.anchors;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
if (child) {
child.moveBeforeDOMNode(node, parent);
} else {
const anchor = anchors![i];
nodeInsertBefore.call(parent, anchor, node);
}
}
}
moveBeforeVNode(other: VMulti | null, afterNode: Node | null) {
if (other) { if (other) {
const next = other!.children[0]; const next = other!.children[0];
afterNode = (next ? next.firstNode() : other!.anchors![0]) || null; afterNode = (next ? next.firstNode() : other!.anchors![0]) || null;
@@ -64,7 +49,7 @@ export class VMulti {
for (let i = 0, l = children.length; i < l; i++) { for (let i = 0, l = children.length; i < l; i++) {
let child = children[i]; let child = children[i];
if (child) { if (child) {
child.moveBeforeVNode(null, afterNode); child.moveBefore(null, afterNode);
} else { } else {
const anchor = anchors![i]; const anchor = anchors![i];
nodeInsertBefore.call(parent, anchor, afterNode); nodeInsertBefore.call(parent, anchor, afterNode);
+3 -7
View File
@@ -23,13 +23,9 @@ abstract class VSimpleNode {
this.el = node; this.el = node;
} }
moveBeforeDOMNode(node: Node | null, parent = this.parentEl) { moveBefore(other: VText | null, afterNode: Node | null) {
this.parentEl = parent; const target = other ? other.el! : afterNode;
nodeInsertBefore.call(parent, this.el!, node); nodeInsertBefore.call(this.parentEl, this.el!, target);
}
moveBeforeVNode(other: VText | null, afterNode: Node | null) {
nodeInsertBefore.call(this.parentEl, this.el!, other ? other.el! : afterNode);
} }
beforeRemove() {} beforeRemove() {}
+2 -6
View File
@@ -20,12 +20,8 @@ class VToggler {
this.child.mount(parent, afterNode); this.child.mount(parent, afterNode);
} }
moveBeforeDOMNode(node: Node | null, parent?: HTMLElement) { moveBefore(other: VToggler | null, afterNode: Node | null) {
this.child.moveBeforeDOMNode(node, parent); this.child.moveBefore(other ? other.child : null, afterNode);
}
moveBeforeVNode(other: VToggler | null, afterNode: Node | null) {
this.moveBeforeDOMNode((other && other.firstNode()) || afterNode);
} }
patch(other: VToggler, withBeforeRemove: boolean) { patch(other: VToggler, withBeforeRemove: boolean) {
+5 -9
View File
@@ -1,7 +1,7 @@
import type { App, Env } from "./app"; import type { App, Env } from "./app";
import { BDom, VNode } from "./blockdom"; import { BDom, VNode } from "./blockdom";
import { Component, ComponentConstructor, Props } from "./component"; import { Component, ComponentConstructor, Props } from "./component";
import { fibersInError, OwlError } from "./error_handling"; import { fibersInError, handleError, OwlError } from "./error_handling";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers"; import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import { import {
clearReactivesForCallback, clearReactivesForCallback,
@@ -141,7 +141,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
try { try {
await Promise.all(this.willStart.map((f) => f.call(component))); await Promise.all(this.willStart.map((f) => f.call(component)));
} catch (e) { } catch (e) {
this.app.handleError({ node: this, error: e }); handleError({ node: this, error: e });
return; return;
} }
if (this.status === STATUS.NEW && this.fiber === fiber) { if (this.status === STATUS.NEW && this.fiber === fiber) {
@@ -219,7 +219,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
cb.call(component); cb.call(component);
} }
} catch (e) { } catch (e) {
this.app.handleError({ error: e, node: this }); handleError({ error: e, node: this });
} }
} }
this.status = STATUS.DESTROYED; this.status = STATUS.DESTROYED;
@@ -306,12 +306,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.fiber = null; this.fiber = null;
} }
moveBeforeDOMNode(node: Node | null, parent?: HTMLElement): void { moveBefore(other: ComponentNode | null, afterNode: Node | null) {
this.bdom!.moveBeforeDOMNode(node, parent); this.bdom!.moveBefore(other ? other.bdom : null, afterNode);
}
moveBeforeVNode(other: ComponentNode<P, E> | null, afterNode: Node | null) {
this.bdom!.moveBeforeVNode(other ? other.bdom : null, afterNode);
} }
patch() { patch() {
-1
View File
@@ -71,6 +71,5 @@ export function handleError(params: ErrorParams) {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
throw error;
} }
} }
+4 -4
View File
@@ -1,6 +1,6 @@
import { BDom, mount } from "./blockdom"; import { BDom, mount } from "./blockdom";
import type { ComponentNode } from "./component_node"; import type { ComponentNode } from "./component_node";
import { fibersInError, OwlError } from "./error_handling"; import { fibersInError, handleError, OwlError } from "./error_handling";
import { STATUS } from "./status"; import { STATUS } from "./status";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber { export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
@@ -130,7 +130,7 @@ export class Fiber {
(this.bdom as any) = true; (this.bdom as any) = true;
this.bdom = node.renderFn(); this.bdom = node.renderFn();
} catch (e) { } catch (e) {
node.app.handleError({ node, error: e }); handleError({ node, error: e });
} }
root.setCounter(root.counter - 1); root.setCounter(root.counter - 1);
} }
@@ -195,7 +195,7 @@ export class RootFiber extends Fiber {
} }
} catch (e) { } catch (e) {
this.locked = false; this.locked = false;
node.app.handleError({ fiber: current || this, error: e }); handleError({ fiber: current || this, error: e });
} }
} }
@@ -259,7 +259,7 @@ export class MountFiber extends RootFiber {
} }
} }
} catch (e) { } catch (e) {
this.node.app.handleError({ fiber: current as Fiber, error: e }); handleError({ fiber: current as Fiber, error: e });
} }
} }
} }
-1
View File
@@ -36,7 +36,6 @@ export const blockDom = {
export { App, mount } from "./app"; export { App, mount } from "./app";
export { xml } from "./template_set"; export { xml } from "./template_set";
export { Component } from "./component"; export { Component } from "./component";
export type { ComponentConstructor } from "./component";
export { useComponent, useState } from "./component_node"; export { useComponent, useState } from "./component_node";
export { status } from "./status"; export { status } from "./status";
export { reactive, markRaw, toRaw } from "./reactivity"; export { reactive, markRaw, toRaw } from "./reactivity";
+32 -37
View File
@@ -1,4 +1,4 @@
import { onMounted, onWillUnmount } from "./lifecycle_hooks"; import { onWillUnmount } from "./lifecycle_hooks";
import { BDom, text, VNode } from "./blockdom"; import { BDom, text, VNode } from "./blockdom";
import { Component } from "./component"; import { Component } from "./component";
import { OwlError } from "./error_handling"; import { OwlError } from "./error_handling";
@@ -6,55 +6,60 @@ import { OwlError } from "./error_handling";
const VText: any = text("").constructor; const VText: any = text("").constructor;
class VPortal extends VText implements Partial<VNode<VPortal>> { class VPortal extends VText implements Partial<VNode<VPortal>> {
content: BDom | null; // selector: string;
selector: string; realBDom: BDom | null;
target: HTMLElement | null = null; target: HTMLElement | null = null;
constructor(selector: string, content: BDom) { constructor(selector: string, realBDom: BDom) {
super(""); super("");
this.selector = selector; this.selector = selector;
this.content = content; this.realBDom = realBDom;
} }
mount(parent: HTMLElement, anchor: ChildNode) { mount(parent: HTMLElement, anchor: ChildNode) {
super.mount(parent, anchor); super.mount(parent, anchor);
this.target = document.querySelector(this.selector) as any; this.target = document.querySelector(this.selector) as any;
if (this.target) { if (!this.target) {
this.content!.mount(this.target!, null); let el: any = this.el;
} else { while (el && el.parentElement instanceof HTMLElement) {
this.content!.mount(parent, anchor); el = el.parentElement;
}
this.target = el && el.querySelector(this.selector);
if (!this.target) {
throw new OwlError("invalid portal target");
}
} }
this.realBDom!.mount(this.target!, null);
} }
beforeRemove() { beforeRemove() {
this.content!.beforeRemove(); this.realBDom!.beforeRemove();
} }
remove() { remove() {
if (this.content) { if (this.realBDom) {
super.remove(); super.remove();
this.content!.remove(); this.realBDom!.remove();
this.content = null; this.realBDom = null;
} }
} }
patch(other: VPortal) { patch(other: VPortal) {
super.patch(other); super.patch(other);
if (this.content) { if (this.realBDom) {
this.content.patch(other.content!, true); this.realBDom.patch(other.realBDom!, true);
} else { } else {
this.content = other.content; this.realBDom = other.realBDom;
this.content!.mount(this.target!, null); this.realBDom!.mount(this.target!, null);
} }
} }
} }
/** /**
* kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal * <t t-slot="default"/>
*/ */
export function portalTemplate(app: any, bdom: any, helpers: any) { export function portalTemplate(app: any, bdom: any, helpers: any) {
let { callSlot } = helpers; let { callSlot } = helpers;
return function template(ctx: any, node: any, key = ""): any { return function template(ctx: any, node: any, key = "") {
return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null)); return callSlot(ctx, node, key, "default", false, null);
}; };
} }
@@ -68,23 +73,13 @@ export class Portal extends Component {
}; };
setup() { setup() {
const node: any = this.__owl__; const node = this.__owl__;
const renderFn = node.renderFn;
onMounted(() => { node.renderFn = () => new VPortal(this.props.target, renderFn());
const portal: VPortal = node.bdom;
if (!portal.target) {
const target: HTMLElement = document.querySelector(this.props.target);
if (target) {
portal.content!.moveBeforeDOMNode(target.firstChild, target);
} else {
throw new OwlError("invalid portal target");
}
}
});
onWillUnmount(() => { onWillUnmount(() => {
const portal: VPortal = node.bdom; if (node.bdom) {
portal.remove(); node.bdom.remove();
}
}); });
} }
} }
+5 -8
View File
@@ -111,18 +111,15 @@ class LazyValue {
ctx: any; ctx: any;
component: any; component: any;
node: any; node: any;
key: any; constructor(fn: any, ctx: any, component: any, node: any) {
constructor(fn: any, ctx: any, component: any, node: any, key: any) {
this.fn = fn; this.fn = fn;
this.ctx = capture(ctx); this.ctx = capture(ctx);
this.component = component; this.component = component;
this.node = node; this.node = node;
this.key = key;
} }
evaluate(): any { evaluate(): any {
return this.fn.call(this.component, this.ctx, this.node, this.key); return this.fn.call(this.component, this.ctx, this.node);
} }
toString() { toString() {
@@ -210,11 +207,11 @@ function multiRefSetter(refs: RefMap, name: string): RefSetter {
* visit recursively the props and all the children to check if they are valid. * visit recursively the props and all the children to check if they are valid.
* This is why it is only done in 'dev' mode. * This is why it is only done in 'dev' mode.
*/ */
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, comp?: any) { export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
const ComponentClass = const ComponentClass =
typeof name !== "string" typeof name !== "string"
? name ? name
: (comp.constructor.components[name] as ComponentConstructor<P> | undefined); : (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
if (!ComponentClass) { if (!ComponentClass) {
// this is an error, wrong component. We silently return here instead so the // this is an error, wrong component. We silently return here instead so the
@@ -224,7 +221,7 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
const schema = ComponentClass.props; const schema = ComponentClass.props;
if (!schema) { if (!schema) {
if (comp.__owl__.app.warnIfNoStaticProps) { if (parent.__owl__.app.warnIfNoStaticProps) {
console.warn(`Component '${ComponentClass.name}' does not have a static props description`); console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
} }
return; return;
+35 -10
View File
@@ -37,11 +37,17 @@ function parseXML(xml: string): Document {
return doc; return doc;
} }
const sharedTemplates: Map<
Function | undefined,
{ [key: string]: { [name: string]: TemplateFunction } }
> = new Map();
export interface TemplateSetConfig { export interface TemplateSetConfig {
dev?: boolean; dev?: boolean;
translatableAttributes?: string[]; translatableAttributes?: string[];
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
templates?: string | Document; templates?: string | Document;
shareTemplates?: boolean;
} }
export class TemplateSet { export class TemplateSet {
@@ -51,12 +57,27 @@ export class TemplateSet {
dev: boolean; dev: boolean;
rawTemplates: typeof globalTemplates = Object.create(globalTemplates); rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
templates: { [name: string]: Template } = {}; templates: { [name: string]: Template } = {};
templateFunctions: { [name: string]: TemplateFunction } = {};
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
translatableAttributes?: string[]; translatableAttributes?: string[];
Portal = Portal; Portal = Portal;
constructor(config: TemplateSetConfig = {}) { constructor(config: TemplateSetConfig = {}) {
this.dev = config.dev || false; this.dev = config.dev || false;
if (config.shareTemplates) {
let cache = sharedTemplates.get(this.translateFn);
if (!cache) {
cache = {};
sharedTemplates.set(this.translateFn, cache);
}
let key = `${this.dev ? "d" : "p"}${(this.translatableAttributes || []).toString()}`;
let templates = cache[key];
if (!templates) {
cache[key] = {};
templates = cache[key];
}
this.templateFunctions = templates;
}
this.translateFn = config.translateFn; this.translateFn = config.translateFn;
this.translatableAttributes = config.translatableAttributes; this.translatableAttributes = config.translatableAttributes;
if (config.templates) { if (config.templates) {
@@ -96,17 +117,21 @@ export class TemplateSet {
getTemplate(name: string): Template { getTemplate(name: string): Template {
if (!(name in this.templates)) { if (!(name in this.templates)) {
const rawTemplate = this.rawTemplates[name]; let templateFn = this.templateFunctions[name];
if (rawTemplate === undefined) { if (!templateFn) {
let extraInfo = ""; const rawTemplate = this.rawTemplates[name];
try { if (rawTemplate === undefined) {
const componentName = getCurrent().component.constructor.name; let extraInfo = "";
extraInfo = ` (for component "${componentName}")`; try {
} catch {} const componentName = getCurrent().component.constructor.name;
throw new OwlError(`Missing template: "${name}"${extraInfo}`); extraInfo = ` (for component "${componentName}")`;
} catch {}
throw new OwlError(`Missing template: "${name}"${extraInfo}`);
}
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
this.templateFunctions[name] = templateFn;
} }
const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
// first add a function to lazily get the template, in case there is a // first add a function to lazily get the template, in case there is a
// recursive call to the template name // recursive call to the template name
const templates = this.templates; const templates = this.templates;
+5 -11
View File
@@ -28,18 +28,12 @@ export function batched(callback: Callback): Callback {
} }
export function validateTarget(target: HTMLElement) { export function validateTarget(target: HTMLElement) {
// Get the document and HTMLElement corresponding to the target to allow mounting in iframes if (!(target instanceof HTMLElement)) {
const document = target && target.ownerDocument; throw new OwlError("Cannot mount component: the target is not a valid DOM element");
if (document) { }
const HTMLElement = document.defaultView!.HTMLElement; if (!document.body.contains(target)) {
if (target instanceof HTMLElement) { throw new OwlError("Cannot mount a component on a detached dom node");
if (!document.body.contains(target)) {
throw new OwlError("Cannot mount a component on a detached dom node");
}
return;
}
} }
throw new OwlError("Cannot mount component: the target is not a valid DOM element");
} }
export class EventBus extends EventTarget { export class EventBus extends EventTarget {
-13
View File
@@ -29,19 +29,6 @@ exports[`app can configure an app with props 1`] = `
}" }"
`; `;
exports[`app can mount app in an iframe 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div class=\\"my-div\\"/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`app destroy remove the widget from the DOM 1`] = ` exports[`app destroy remove the widget from the DOM 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
-18
View File
@@ -76,22 +76,4 @@ describe("app", () => {
"Component 'Root' does not have a static props description" "Component 'Root' does not have a static props description"
); );
}); });
test("can mount app in an iframe", async () => {
class SomeComponent extends Component {
static template = xml`<div class="my-div"/>`;
}
const iframe = document.createElement("iframe");
fixture.appendChild(iframe);
const app = new App(SomeComponent);
const iframeDoc = iframe.contentDocument!;
const comp = await app.mount(iframeDoc.body);
const div = iframeDoc.querySelector(".my-div");
expect(div).not.toBe(null);
expect(iframeDoc.contains(div)).toBe(true);
app.destroy();
expect(iframeDoc.contains(div)).toBe(false);
expect(status(comp)).toBe("destroyed");
});
}); });
@@ -708,20 +708,6 @@ exports[`attributes updating classes (with obj notation) 1`] = `
}" }"
`; `;
exports[`attributes updating property with falsy value 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = new String((ctx['v']) || \\"\\");
return block1([attr1]);
}
}"
`;
exports[`attributes various escapes 1`] = ` exports[`attributes various escapes 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -787,7 +773,7 @@ exports[`special cases for some specific html attributes/properties input with t
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`); let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let attr1 = new String((ctx['v']) || \\"\\"); let attr1 = new String(ctx['v']);
return block1([attr1]); return block1([attr1]);
} }
}" }"
@@ -801,7 +787,7 @@ exports[`special cases for some specific html attributes/properties input with t
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`); let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let attr1 = new String((ctx['v']) || \\"\\"); let attr1 = new String(ctx['v']);
return block1([attr1]); return block1([attr1]);
} }
}" }"
@@ -829,7 +815,7 @@ exports[`special cases for some specific html attributes/properties select with
let block1 = createBlock(\`<select block-attribute-0=\\"value\\"><option value=\\"potato\\">Potato</option><option value=\\"tomato\\">Tomato</option><option value=\\"onion\\">Onion</option></select>\`); let block1 = createBlock(\`<select block-attribute-0=\\"value\\"><option value=\\"potato\\">Potato</option><option value=\\"tomato\\">Tomato</option><option value=\\"onion\\">Onion</option></select>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let attr1 = new String((ctx['value']) || \\"\\"); let attr1 = new String(ctx['value']);
return block1([attr1]); return block1([attr1]);
} }
}" }"
@@ -843,7 +829,7 @@ exports[`special cases for some specific html attributes/properties textarea wit
let block1 = createBlock(\`<textarea block-attribute-0=\\"value\\"/>\`); let block1 = createBlock(\`<textarea block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let attr1 = new String((ctx['v']) || \\"\\"); let attr1 = new String(ctx['v']);
return block1([attr1]); return block1([attr1]);
} }
}" }"
@@ -277,7 +277,7 @@ exports[`misc other complex template 1`] = `
const b15 = list(c_block15); const b15 = list(c_block15);
b14 = block14([], [b15]); b14 = block14([], [b15]);
} }
let attr8 = new String((ctx['search'].value) || \\"\\"); let attr8 = new String(ctx['search'].value);
let hdlr4 = [ctx['updateFilter'], ctx]; let hdlr4 = [ctx['updateFilter'], ctx];
let hdlr5 = [ctx['updateFilter'], ctx]; let hdlr5 = [ctx['updateFilter'], ctx];
let hdlr6 = [ctx['clearSearch'], ctx]; let hdlr6 = [ctx['clearSearch'], ctx];
@@ -334,57 +334,6 @@ exports[`t-call (template calling) inherit context 2`] = `
}" }"
`; `;
exports[`t-call (template calling) nested t-calls with magic variable 0 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`grandchild\`);
const callTemplate_2 = app.getTemplate(\`child\`);
let block1 = createBlock(\`<p>Some content...</p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = block1();
ctx[zero] = b1;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
ctx = ctx.__proto__;
ctx[zero] = b2;
return callTemplate_2.call(this, ctx, node, key + \`__2\`);
}
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero } = helpers;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`grandchild\`);
const b3 = ctx[zero];
return multi([b2, b3]);
}
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero } = helpers;
return function template(ctx, node, key = \\"\\") {
return ctx[zero];
}
}"
`;
exports[`t-call (template calling) recursive template, part 1 1`] = ` exports[`t-call (template calling) recursive template, part 1 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -126,7 +126,7 @@ exports[`t-esc t-esc is escaped 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`var\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`var\`] = new LazyValue(value1, ctx, this, node);
let txt1 = ctx['var']; let txt1 = ctx['var'];
return block1([txt1]); return block1([txt1]);
} }
@@ -161,7 +161,7 @@ exports[`t-out t-out bdom 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`var\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`var\`] = new LazyValue(value1, ctx, this, node);
const b3 = safeOutput(ctx['var']); const b3 = safeOutput(ctx['var']);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -310,7 +310,7 @@ exports[`t-out t-out switch markup on bdom 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b3,b5; let b3,b5;
ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node);
if (ctx['hasBdom']) { if (ctx['hasBdom']) {
const b4 = safeOutput(ctx['bdom']); const b4 = safeOutput(ctx['bdom']);
b3 = block3([], [b4]); b3 = block3([], [b4]);
@@ -106,7 +106,7 @@ exports[`t-set set from body literal (with t-if/t-else 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`value\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`value\`] = new LazyValue(value1, ctx, this, node);
return text(ctx['value']); return text(ctx['value']);
} }
}" }"
@@ -142,7 +142,7 @@ exports[`t-set set from body lookup 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node);
let txt1 = ctx['stuff']; let txt1 = ctx['stuff'];
return block1([txt1]); return block1([txt1]);
} }
@@ -206,7 +206,7 @@ exports[`t-set t-set body is evaluated immediately 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`v2\`] = new LazyValue(value1, ctx, this, node);
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
const b3 = safeOutput(ctx['v2']); const b3 = safeOutput(ctx['v2']);
return block1([], [b3]); return block1([], [b3]);
@@ -471,7 +471,7 @@ exports[`t-set t-set with content and sub t-esc 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node);
let txt1 = ctx['setvar']; let txt1 = ctx['setvar'];
return block1([txt1]); return block1([txt1]);
} }
@@ -497,7 +497,7 @@ exports[`t-set t-set with t-value (falsy) and body 1`] = `
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", false); setContextValue(ctx, \\"v3\\", false);
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node, key)); ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node));
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", true); setContextValue(ctx, \\"v3\\", true);
const b3 = safeOutput(ctx['v2']); const b3 = safeOutput(ctx['v2']);
@@ -525,7 +525,7 @@ exports[`t-set t-set with t-value (truthy) and body 1`] = `
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", 'Truthy'); setContextValue(ctx, \\"v3\\", 'Truthy');
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node, key)); ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node));
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", false); setContextValue(ctx, \\"v3\\", false);
const b3 = safeOutput(ctx['v2']); const b3 = safeOutput(ctx['v2']);
@@ -638,7 +638,7 @@ exports[`t-set value priority (with non text body 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node, key)); ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node));
let txt1 = ctx['value']; let txt1 = ctx['value'];
return block1([txt1]); return block1([txt1]);
} }
-29
View File
@@ -329,35 +329,6 @@ describe("attributes", () => {
expect(fixture.innerHTML).toBe('<div value=""></div>'); expect(fixture.innerHTML).toBe('<div value=""></div>');
}); });
test("updating property with falsy value", async () => {
// render input with initial value
const template = `<input t-att-value="v"></input>`;
const bnode1 = renderToBdom(template, { v: false });
const fixture = makeTestFixture();
mount(bnode1, fixture);
const input = fixture.querySelector("input")!;
expect(input.value).toBe("");
patch(bnode1, renderToBdom(template, { v: "owl" }));
expect(input.value).toBe("owl");
patch(bnode1, renderToBdom(template, { v: false }));
expect(input.value).toBe("");
patch(bnode1, renderToBdom(template, { v: "owl" }));
expect(input.value).toBe("owl");
patch(bnode1, renderToBdom(template, { v: undefined }));
expect(input.value).toBe("");
patch(bnode1, renderToBdom(template, { v: "owl" }));
expect(input.value).toBe("owl");
patch(bnode1, renderToBdom(template, { v: null }));
expect(input.value).toBe("");
});
test("changing a class with t-att-class", () => { test("changing a class with t-att-class", () => {
// render input with initial value // render input with initial value
const template = `<div t-att-class="v"/>`; const template = `<div t-att-class="v"/>`;
-17
View File
@@ -479,21 +479,4 @@ describe("t-call (template calling)", () => {
"<span>123lucas</span>" "<span>123lucas</span>"
); );
}); });
test("nested t-calls with magic variable 0", () => {
const context = new TestContext();
context.addTemplate("grandchild", `grandchild<t t-out="0"/>`);
context.addTemplate("child", `<t t-out="0"/>`);
context.addTemplate(
"main",
`
<t t-call="child">
<t t-call="grandchild">
<p>Some content...</p>
</t>
</t>`
);
expect(context.renderToString("main")).toBe("grandchild<p>Some content...</p>");
});
}); });
@@ -20,7 +20,7 @@ exports[`basics display a nice error if it cannot find component (in dev mode) 1
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`SomeMispelledComponent\`, props1, this); helpers.validateProps(\`SomeMispelledComponent\`, props1, ctx);
return comp1(props1, key + \`__1\`, node, this, null); return comp1(props1, key + \`__1\`, node, this, null);
} }
}" }"
@@ -58,20 +58,6 @@ exports[`event handling handler receive the event as argument 2`] = `
}" }"
`; `;
exports[`event handling handler works when app is mounted in an iframe 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<span block-handler-0=\\"click\\">click me</span>\`);
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['inc'], ctx];
return block1([hdlr1]);
}
}"
`;
exports[`event handling input blur event is not called if component is destroyed 1`] = ` exports[`event handling input blur event is not called if component is destroyed 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -704,7 +704,7 @@ exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {prop: ctx['state'].prop}; const props1 = {prop: ctx['state'].prop};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
return comp1(props1, key + \`__1\`, node, this, null); return comp1(props1, key + \`__1\`, node, this, null);
} }
}" }"
@@ -163,7 +163,7 @@ exports[`basics t-set with a body expression can be passed in props, and then t-
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`abc\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`abc\`] = new LazyValue(value1, ctx, this, node);
const b3 = comp1({val: ctx['abc']}, key + \`__1\`, node, this, null); const b3 = comp1({val: ctx['abc']}, key + \`__1\`, node, this, null);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -8,7 +8,7 @@ exports[`default props a default prop cannot be defined on a mandatory prop 1`]
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
return comp1(props1, key + \`__1\`, node, this, null); return comp1(props1, key + \`__1\`, node, this, null);
} }
}" }"
@@ -24,7 +24,7 @@ exports[`default props can set default boolean values 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -61,7 +61,7 @@ exports[`default props can set default values 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -92,7 +92,7 @@ exports[`default props default values are also set whenever component is updated
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}; const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -121,7 +121,7 @@ exports[`props validation can specify that additional props are allowed (array)
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'}; const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
return comp1(props1, key + \`__1\`, node, this, null); return comp1(props1, key + \`__1\`, node, this, null);
} }
}" }"
@@ -148,7 +148,7 @@ exports[`props validation can specify that additional props are allowed (object)
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'}; const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
return comp1(props1, key + \`__1\`, node, this, null); return comp1(props1, key + \`__1\`, node, this, null);
} }
}" }"
@@ -177,7 +177,7 @@ exports[`props validation can validate a prop with multiple types 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -207,7 +207,7 @@ exports[`props validation can validate a prop with multiple types 3`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -237,7 +237,7 @@ exports[`props validation can validate a prop with multiple types 5`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -254,7 +254,7 @@ exports[`props validation can validate an array with given primitive type 1`] =
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -284,7 +284,7 @@ exports[`props validation can validate an array with given primitive type 3`] =
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -314,7 +314,7 @@ exports[`props validation can validate an array with given primitive type 5`] =
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -331,7 +331,7 @@ exports[`props validation can validate an array with given primitive type 6`] =
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -348,7 +348,7 @@ exports[`props validation can validate an array with multiple sub element types
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -378,7 +378,7 @@ exports[`props validation can validate an array with multiple sub element types
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -408,7 +408,7 @@ exports[`props validation can validate an array with multiple sub element types
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -438,7 +438,7 @@ exports[`props validation can validate an array with multiple sub element types
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -455,7 +455,7 @@ exports[`props validation can validate an object with simple shape 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -485,7 +485,7 @@ exports[`props validation can validate an object with simple shape 3`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -502,7 +502,7 @@ exports[`props validation can validate an object with simple shape 4`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -519,7 +519,7 @@ exports[`props validation can validate an object with simple shape 5`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -536,7 +536,7 @@ exports[`props validation can validate an optional props 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -566,7 +566,7 @@ exports[`props validation can validate an optional props 3`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -596,7 +596,7 @@ exports[`props validation can validate an optional props 5`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -613,7 +613,7 @@ exports[`props validation can validate recursively complicated prop def 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -643,7 +643,7 @@ exports[`props validation can validate recursively complicated prop def 3`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -673,47 +673,13 @@ exports[`props validation can validate recursively complicated prop def 5`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
`; `;
exports[`props validation can validate through slots 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
const comp2 = app.createComponent(\`Wrapper\`, true, true, false, true);
function slot1(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`Child\`, props1, this);
return comp1(props1, key + \`__1\`, node, this, null);
}
return function template(ctx, node, key = \\"\\") {
const props2 = {slots: markRaw({'default': {__render: slot1, __ctx: ctx}})};
helpers.validateProps(\`Wrapper\`, props2, this);
return comp2(props2, key + \`__2\`, node, this, null);
}
}"
`;
exports[`props validation can validate through slots 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {});
}
}"
`;
exports[`props validation default values are applied before validating props at update 1`] = ` exports[`props validation default values are applied before validating props at update 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -724,7 +690,7 @@ exports[`props validation default values are applied before validating props at
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}; const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -755,7 +721,7 @@ exports[`props validation missing required boolean prop causes an error 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -772,7 +738,7 @@ exports[`props validation mix of optional and mandatory 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -789,7 +755,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {message: 1}; const props1 = {message: 1};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -820,7 +786,7 @@ exports[`props validation props are validated whenever component is updated 1`]
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p}; const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -851,7 +817,7 @@ exports[`props validation props: list of strings 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -868,7 +834,7 @@ exports[`props validation validate simple types 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -885,7 +851,7 @@ exports[`props validation validate simple types 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -915,7 +881,7 @@ exports[`props validation validate simple types 4`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -932,7 +898,7 @@ exports[`props validation validate simple types 5`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -949,7 +915,7 @@ exports[`props validation validate simple types 6`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -979,7 +945,7 @@ exports[`props validation validate simple types 8`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -996,7 +962,7 @@ exports[`props validation validate simple types 9`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1013,7 +979,7 @@ exports[`props validation validate simple types 10`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1043,7 +1009,7 @@ exports[`props validation validate simple types 12`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1060,7 +1026,7 @@ exports[`props validation validate simple types 13`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1077,7 +1043,7 @@ exports[`props validation validate simple types 14`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1107,7 +1073,7 @@ exports[`props validation validate simple types 16`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1124,7 +1090,7 @@ exports[`props validation validate simple types 17`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1141,7 +1107,7 @@ exports[`props validation validate simple types 18`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1171,7 +1137,7 @@ exports[`props validation validate simple types 20`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1188,7 +1154,7 @@ exports[`props validation validate simple types 21`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1205,7 +1171,7 @@ exports[`props validation validate simple types 22`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1235,7 +1201,7 @@ exports[`props validation validate simple types 24`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1252,7 +1218,7 @@ exports[`props validation validate simple types, alternate form 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1269,7 +1235,7 @@ exports[`props validation validate simple types, alternate form 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1299,7 +1265,7 @@ exports[`props validation validate simple types, alternate form 4`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1316,7 +1282,7 @@ exports[`props validation validate simple types, alternate form 5`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1333,7 +1299,7 @@ exports[`props validation validate simple types, alternate form 6`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1363,7 +1329,7 @@ exports[`props validation validate simple types, alternate form 8`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1380,7 +1346,7 @@ exports[`props validation validate simple types, alternate form 9`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1397,7 +1363,7 @@ exports[`props validation validate simple types, alternate form 10`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1427,7 +1393,7 @@ exports[`props validation validate simple types, alternate form 12`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1444,7 +1410,7 @@ exports[`props validation validate simple types, alternate form 13`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1461,7 +1427,7 @@ exports[`props validation validate simple types, alternate form 14`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1491,7 +1457,7 @@ exports[`props validation validate simple types, alternate form 16`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1508,7 +1474,7 @@ exports[`props validation validate simple types, alternate form 17`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1525,7 +1491,7 @@ exports[`props validation validate simple types, alternate form 18`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1555,7 +1521,7 @@ exports[`props validation validate simple types, alternate form 20`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1572,7 +1538,7 @@ exports[`props validation validate simple types, alternate form 21`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1589,7 +1555,7 @@ exports[`props validation validate simple types, alternate form 22`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1619,7 +1585,7 @@ exports[`props validation validate simple types, alternate form 24`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']}; const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -1636,7 +1602,7 @@ exports[`props validation validation is only done in dev mode 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const props1 = {}; const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this); helpers.validateProps(\`SubComp\`, props1, ctx);
const b2 = comp1(props1, key + \`__1\`, node, this, null); const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]); return block1([], [b2]);
} }
@@ -604,64 +604,6 @@ exports[`slots default slot work with text nodes 2`] = `
}" }"
`; `;
exports[`slots dynamic slot in multiple locations 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
const comp2 = app.createComponent(\`Slotter\`, true, true, false, false);
function slot1(ctx, node, key = \\"\\") {
const b2 = text(\`hello \`);
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return comp2({location: ctx['state'].location,slots: markRaw({'coffee': {__render: slot1, __ctx: ctx1}})}, key + \`__2\`, node, this, null);
}
}"
`;
exports[`slots dynamic slot in multiple locations 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
let block2 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b4;
if (ctx['props'].location===1) {
const slot1 = ('coffee');
const b3 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {}));
b2 = block2([], [b3]);
}
if (ctx['props'].location===2) {
const slot2 = ('coffee');
b4 = toggler(slot2, callSlot(ctx, node, key + \`__2\`, slot2, true, {}));
}
return multi([b2, b4]);
}
}"
`;
exports[`slots dynamic slot in multiple locations 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>child</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`slots dynamic t-slot call 1`] = ` exports[`slots dynamic t-slot call 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -703,7 +645,7 @@ exports[`slots dynamic t-slot call 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['toggle'], ctx]; let hdlr1 = [ctx['toggle'], ctx];
const slot1 = (ctx['current'].slot); const slot1 = (ctx['current'].slot);
const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {})); const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {}));
return block1([hdlr1], [b2]); return block1([hdlr1], [b2]);
} }
}" }"
@@ -753,7 +695,7 @@ exports[`slots dynamic t-slot call with default 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['toggle'], ctx]; let hdlr1 = [ctx['toggle'], ctx];
const b3 = callSlot(ctx, node, key + \`__1\`, (ctx['current'].slot), true, {}, defaultContent1); const b3 = callSlot(ctx, node, key, (ctx['current'].slot), true, {}, defaultContent1);
return block1([hdlr1], [b3]); return block1([hdlr1], [b3]);
} }
}" }"
@@ -784,7 +726,7 @@ exports[`slots fun: two calls to the same slot 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b2 = callSlot(ctx, node, key, 'default', false, {}); const b2 = callSlot(ctx, node, key, 'default', false, {});
const b3 = callSlot(ctx, node, key + \`__1\`, 'default', false, {}); const b3 = callSlot(ctx, node, key, 'default', false, {});
return multi([b2, b3]); return multi([b2, b3]);
} }
}" }"
@@ -1585,7 +1527,7 @@ exports[`slots simple dynamic slot with slot scope 2`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const slot1 = ('slotName'); const slot1 = ('slotName');
const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {bool: ctx['state'].bool})); const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {bool: ctx['state'].bool}));
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -1912,7 +1854,7 @@ exports[`slots slot content has different key from other content -- dynamic slot
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const b2 = comp1({parent: 'SlotDisplay'}, key + \`__1\`, node, this, null); const b2 = comp1({parent: 'SlotDisplay'}, key + \`__1\`, node, this, null);
const slot1 = (ctx['slotName']); const slot1 = (ctx['slotName']);
const b3 = toggler(slot1, callSlot(ctx, node, key + \`__2\`, slot1, true, {})); const b3 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {}));
return multi([b2, b3]); return multi([b2, b3]);
} }
}" }"
@@ -2053,118 +1995,6 @@ exports[`slots slot content is bound to caller 2`] = `
}" }"
`; `;
exports[`slots slot in multiple locations 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
const comp2 = app.createComponent(\`Slotter\`, true, true, false, false);
function slot1(ctx, node, key = \\"\\") {
const b2 = text(\` hello \`);
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
return comp2({location: ctx['state'].location,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, this, null);
}
}"
`;
exports[`slots slot in multiple locations 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
let block2 = createBlock(\`<p><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b4;
if (ctx['props'].location===1) {
const b3 = callSlot(ctx, node, key, 'default', false, {});
b2 = block2([], [b3]);
}
if (ctx['props'].location===2) {
b4 = callSlot(ctx, node, key + \`__1\`, 'default', false, {});
}
return multi([b2, b4]);
}
}"
`;
exports[`slots slot in multiple locations 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>child</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`slots slot in t-foreach locations 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
const comp2 = app.createComponent(\`Slotter\`, true, true, false, false);
function slot1(ctx, node, key = \\"\\") {
const b2 = text(\` hello \`);
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
return comp2({list: ctx['state'].list,slots: markRaw({'default': {__render: slot1, __ctx: ctx}})}, key + \`__2\`, node, this, null);
}
}"
`;
exports[`slots slot in t-foreach locations 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, callSlot, withKey } = helpers;
let block2 = createBlock(\`<p><block-text-0/><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['props'].list);;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`elem\`] = v_block1[i1];
ctx[\`elem_index\`] = i1;
const key1 = ctx['elem_index'];
let txt1 = ctx['elem'];
const b3 = callSlot(ctx, node, key1, 'default', false, {});
c_block1[i1] = withKey(block2([txt1], [b3]), key1);
}
return list(c_block1);
}
}"
`;
exports[`slots slot in t-foreach locations 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<div>child</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`slots slot preserves properly parented relationship 1`] = ` exports[`slots slot preserves properly parented relationship 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -2835,36 +2665,6 @@ exports[`slots t-set t-value in a slot 2`] = `
}" }"
`; `;
exports[`slots t-set-slot=default has priority over rest of the content 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, true);
function slot1(ctx, node, key = \\"\\") {
return text(\`some other text\`);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return comp1({slots: markRaw({'default': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, this, null);
}
}"
`;
exports[`slots t-set-slot=default has priority over rest of the content 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {});
}
}"
`;
exports[`slots t-slot in recursive templates 1`] = ` exports[`slots t-slot in recursive templates 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -444,51 +444,6 @@ exports[`t-call t-call with t-call-context and subcomponent 3`] = `
}" }"
`; `;
exports[`t-call t-call with t-call-context and subcomponent, in dev mode 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`someTemplate\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = ctx['subctx'];
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call t-call with t-call-context and subcomponent, in dev mode 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
const comp2 = app.createComponent(\`Child\`, true, false, false, false);
return function template(ctx, node, key = \\"\\") {
const props1 = {name: ctx['aab']};
helpers.validateProps(\`Child\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
const props2 = {name: ctx['lpe']};
helpers.validateProps(\`Child\`, props2, this);
const b3 = comp2(props2, key + \`__2\`, node, this, null);
return multi([b2, b3]);
}
}"
`;
exports[`t-call t-call with t-call-context and subcomponent, in dev mode 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`child\`);
const b3 = text(ctx['props'].name);
return multi([b2, b3]);
}
}"
`;
exports[`t-call t-call with t-call-context, simple use 1`] = ` exports[`t-call t-call with t-call-context, simple use 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -53,10 +53,10 @@ exports[`list of components crash on duplicate key in dev mode 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) { for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1]; ctx[\`item\`] = v_block1[i1];
const key1 = 'child'; const key1 = 'child';
if (keys1.has(String(key1))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key1}\`)} if (keys1.has(key1)) { throw new OwlError(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(String(key1)); keys1.add(key1);
const props1 = {}; const props1 = {};
helpers.validateProps(\`Child\`, props1, this); helpers.validateProps(\`Child\`, props1, ctx);
c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1); c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1);
} }
return list(c_block1); return list(c_block1);
@@ -75,42 +75,6 @@ exports[`list of components crash on duplicate key in dev mode 2`] = `
}" }"
`; `;
exports[`list of components crash when using object as keys that serialize to the same string 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, OwlError, withKey } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList([{},{}]);;
const keys1 = new Set();
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1];
const key1 = ctx['item'];
if (keys1.has(String(key1))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(String(key1));
const props1 = {};
helpers.validateProps(\`Child\`, props1, this);
c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
}
}"
`;
exports[`list of components crash when using object as keys that serialize to the same string 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`\`);
}
}"
`;
exports[`list of components list of sub components inside other nodes 1`] = ` exports[`list of components list of sub components inside other nodes 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -149,58 +113,6 @@ exports[`list of components list of sub components inside other nodes 2`] = `
}" }"
`; `;
exports[`list of components order is correct when slots are not of same type 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, true);
let block2 = createBlock(\`<div>A</div>\`);
function slot1(ctx, node, key = \\"\\") {
let b2;
if (!ctx['state'].active) {
b2 = block2();
}
return multi([b2]);
}
function slot2(ctx, node, key = \\"\\") {
return text(\`B\`);
}
function slot3(ctx, node, key = \\"\\") {
return text(\`C\`);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
return comp1({slots: markRaw({'a': {__render: slot1, __ctx: ctx1, active: !ctx['state'].active}, 'b': {__render: slot2, __ctx: ctx1, active: true}, 'c': {__render: slot3, __ctx: ctx1, active: ctx['state'].active}})}, key + \`__1\`, node, this, null);
}
}"
`;
exports[`list of components order is correct when slots are not of same type 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, callSlot, withKey } = helpers;
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['slotNames']);;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`slotName\`] = v_block1[i1];
const key1 = ctx['slotName'];
const slot1 = (ctx['slotName']);
c_block1[i1] = withKey(toggler(slot1, callSlot(ctx, node, key1 + \`__1__\${key1}\`, slot1, true, {})), key1);
}
return list(c_block1);
}
}"
`;
exports[`list of components reconciliation alg works for t-foreach in t-foreach 1`] = ` exports[`list of components reconciliation alg works for t-foreach in t-foreach 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -456,45 +456,3 @@ exports[`t-on t-on on t-slots 2`] = `
} }
}" }"
`; `;
exports[`t-on t-on when first component child is an empty component 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { createCatcher } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
const catcher1 = createCatcher({\\"click\\":0});
let block1 = createBlock(\`<div block-handler-0=\\"click\\"><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['push'], ctx];
const hdlr2 = [()=>{}, ctx];
const b2 = catcher1(comp1({list: ctx['list']}, key + \`__1\`, node, this, null), [hdlr2]);
return block1([hdlr1], [b2]);
}
}"
`;
exports[`t-on t-on when first component child is an empty component 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, withKey } = helpers;
let block2 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['props'].list);;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`c\`] = v_block1[i1];
ctx[\`c_index\`] = i1;
const key1 = ctx['c_index'];
let txt1 = ctx['c'];
c_block1[i1] = withKey(block2([txt1]), key1);
}
return list(c_block1);
}
}"
`;
@@ -1,39 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components in t-out simple list 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, isBoundary, withDefault, LazyValue, safeOutput, withKey } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
function value1(ctx, node, key = \\"\\") {
return comp1({}, key + \`__1\`, node, this, null);
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
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[\`n\`] = v_block1[i1];
const key1 = ctx['n'];
ctx[\`blabla\`] = new LazyValue(value1, ctx, this, node, key1);
c_block1[i1] = withKey(safeOutput(ctx['blabla']), key1);
}
return list(c_block1);
}
}"
`;
exports[`components in t-out simple list 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
@@ -59,7 +59,7 @@ exports[`t-set slots with a t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
const b3 = text(\` in slot \`); const b3 = text(\` in slot \`);
const b4 = safeOutput(ctx['v']); const b4 = safeOutput(ctx['v']);
return multi([b3, b4]); return multi([b3, b4]);
@@ -114,7 +114,7 @@ exports[`t-set slots with an t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
const b5 = text(\` tea \`); const b5 = text(\` tea \`);
const b6 = safeOutput(ctx['v']); const b6 = safeOutput(ctx['v']);
return multi([b5, b6]); return multi([b5, b6]);
@@ -169,7 +169,7 @@ exports[`t-set slots with an unused t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
return text(\` in slot \`); return text(\` in slot \`);
} }
@@ -343,7 +343,7 @@ exports[`t-set t-set with a component in body 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
const b3 = safeOutput(ctx['v']); const b3 = safeOutput(ctx['v']);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -377,7 +377,7 @@ exports[`t-set t-set with something in body 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key); ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
const b3 = safeOutput(ctx['v']); const b3 = safeOutput(ctx['v']);
return block1([], [b3]); return block1([], [b3]);
} }
+7 -14
View File
@@ -1,12 +1,5 @@
import { App, Component, mount, status, toRaw, useState, xml } from "../../src"; import { App, Component, mount, status, toRaw, useState, xml } from "../../src";
import { import { elem, makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
elem,
makeTestFixture,
nextAppError,
nextTick,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
import { markup } from "../../src/runtime/utils"; import { markup } from "../../src/runtime/utils";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -215,14 +208,14 @@ describe("basics", () => {
static template = xml`<div/>`; static template = xml`<div/>`;
} }
let error: Error; let error: Error;
const app = new App(Test); const prom = mount(Test, fixture);
const prom = app.mount(fixture);
await Promise.resolve(); await Promise.resolve();
fixture.remove(); fixture.remove();
prom.catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow( await prom;
"Cannot mount a component on a detached dom node" } catch (e) {
); error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Cannot mount a component on a detached dom node"); expect(error!.message).toBe("Cannot mount a component on a detached dom node");
expect(console.warn).toBeCalledTimes(1); expect(console.warn).toBeCalledTimes(1);
+127 -117
View File
@@ -1,4 +1,4 @@
import { App, Component, mount, onWillDestroy } from "../../src"; import { Component, mount, onWillDestroy } from "../../src";
import { import {
onError, onError,
onMounted, onMounted,
@@ -18,7 +18,6 @@ import {
nextMicroTick, nextMicroTick,
snapshotEverything, snapshotEverything,
useLogLifecycle, useLogLifecycle,
nextAppError,
} from "../helpers"; } from "../helpers";
import { OwlError } from "../../src/runtime/error_handling"; import { OwlError } from "../../src/runtime/error_handling";
@@ -60,10 +59,9 @@ describe("basics", () => {
parent.state.flag = true; parent.state.flag = true;
parent.render(); parent.render();
await expect(nextAppError(parent.__owl__.app)).resolves.toThrow( await nextTick();
"An error occured in the owl lifecycle"
);
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
}); });
@@ -73,13 +71,12 @@ describe("basics", () => {
static template = xml`<SomeMispelledComponent />`; static template = xml`<SomeMispelledComponent />`;
static components = { SomeComponent }; static components = { SomeComponent };
} }
const app = new App(Parent);
let error: Error; let error: Error;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow( await mount(Parent, fixture);
'Cannot find the definition of component "SomeMispelledComponent"' } catch (e) {
); error = e as Error;
await mountProm; }
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"'); expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
@@ -93,13 +90,12 @@ describe("basics", () => {
static template = xml`<SomeMispelledComponent />`; static template = xml`<SomeMispelledComponent />`;
static components = { SomeComponent }; static components = { SomeComponent };
} }
const app = new App(Parent, { test: true });
let error: Error; let error: Error;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow( await mount(Parent, fixture, { test: true });
'Cannot find the definition of component "SomeMispelledComponent"' } catch (e) {
); error = e as Error;
await mountProm; }
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"'); expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
@@ -113,13 +109,13 @@ describe("basics", () => {
static template = xml`<SomeComponent />`; static template = xml`<SomeComponent />`;
static components = { SomeComponent: notAComponentConstructor }; static components = { SomeComponent: notAComponentConstructor };
} }
const app = new App(Parent as typeof Component);
let error: Error; let error: Error;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow( // @ts-expect-error
'"SomeComponent" is not a Component. It must inherit from the Component class' await mount(Parent, fixture);
); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
'"SomeComponent" is not a Component. It must inherit from the Component class' '"SomeComponent" is not a Component. It must inherit from the Component class'
@@ -160,15 +156,16 @@ describe("basics", () => {
describe("errors and promises", () => { describe("errors and promises", () => {
test("a rendering error will reject the mount promise", async () => { test("a rendering error will reject the mount promise", async () => {
// we do not catch error in willPatch anymore // we do not catch error in willPatch anymore
class Root extends Component { class App extends Component {
static template = xml`<div><t t-esc="this.will.crash"/></div>`; static template = xml`<div><t t-esc="this.will.crash"/></div>`;
} }
const app = new App(Root);
let error: OwlError; let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await mount(App, fixture);
await mountProm; } catch (e) {
error = e as OwlError;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
const regexp = const regexp =
@@ -179,7 +176,7 @@ describe("errors and promises", () => {
}); });
test("an error in mounted call will reject the mount promise", async () => { test("an error in mounted call will reject the mount promise", async () => {
class Root extends Component { class App extends Component {
static template = xml`<div>abc</div>`; static template = xml`<div>abc</div>`;
setup() { setup() {
onMounted(() => { onMounted(() => {
@@ -188,11 +185,12 @@ describe("errors and promises", () => {
} }
} }
const app = new App(Root);
let error: OwlError; let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await mount(App, fixture);
await mountProm; } catch (e) {
error = e as OwlError;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
expect(error!.cause.message).toBe("boom"); expect(error!.cause.message).toBe("boom");
@@ -202,7 +200,7 @@ describe("errors and promises", () => {
}); });
test("an error in onMounted callback will have the component's setup in its stack trace", async () => { test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
class Root extends Component { class App extends Component {
static template = xml`<div>abc</div>`; static template = xml`<div>abc</div>`;
setup() { setup() {
onMounted(() => { onMounted(() => {
@@ -211,13 +209,14 @@ describe("errors and promises", () => {
} }
} }
const app = new App(Root, { test: true }); let error: Error;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(App, fixture, { test: true });
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.stack).toContain("Root.setup"); expect(error!.stack).toContain("App.setup");
expect(error!.stack).toContain("error_handling.test.ts"); expect(error!.stack).toContain("error_handling.test.ts");
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0); expect(mockConsoleError).toBeCalledTimes(0);
@@ -225,7 +224,7 @@ describe("errors and promises", () => {
}); });
test("errors in onWillRender/onRender aren't wrapped more than once", async () => { test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
class Root extends Component { class App extends Component {
static template = xml`<div>abc</div>`; static template = xml`<div>abc</div>`;
setup() { setup() {
onWillRender(() => { onWillRender(() => {
@@ -237,11 +236,12 @@ describe("errors and promises", () => {
} }
} }
const app = new App(Root, { test: true }); let error: Error;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(App, fixture, { test: true });
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillRender"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
`The following error occurred in onWillRender: "boom in onWillRender"` `The following error occurred in onWillRender: "boom in onWillRender"`
@@ -278,11 +278,12 @@ describe("errors and promises", () => {
} }
} }
const app = new App(Root, { test: true }); let error: any;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture, { test: true });
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart"); } catch (e) {
await mountProm; error = e;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
`The following error occurred in onWillStart: "boom in onWillStart"` `The following error occurred in onWillStart: "boom in onWillStart"`
@@ -341,16 +342,17 @@ describe("errors and promises", () => {
class Child extends Component { class Child extends Component {
static template = xml`<div><t t-esc="this.will.crash"/></div>`; static template = xml`<div><t t-esc="this.will.crash"/></div>`;
} }
class Parent extends Component { class App extends Component {
static template = xml`<div><Child/></div>`; static template = xml`<div><Child/></div>`;
static components = { Child }; static components = { Child };
} }
const app = new App(Parent);
let error: OwlError; let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await mount(App, fixture);
await mountProm; } catch (e) {
error = e as OwlError;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
const regexp = const regexp =
@@ -392,11 +394,12 @@ describe("errors and promises", () => {
static components = { Child }; static components = { Child };
} }
const app = new App(Parent);
let error: OwlError; let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); try {
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); await mount(Parent, fixture);
await mountProm; } catch (e) {
error = e as OwlError;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
const regexp = const regexp =
@@ -422,12 +425,13 @@ describe("errors and promises", () => {
} }
} }
const app = new App(Example, { test: true }); try {
let error: OwlError; await mount(Example, fixture, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); } catch (e) {
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted"); expect((e as Error).message).toBe(
await mountProm; `The following error occurred in onMounted: "Error in mounted"`
expect(error!.message).toBe(`The following error occurred in onMounted: "Error in mounted"`); );
}
// 1 additional error is logged because the destruction of the app causes // 1 additional error is logged because the destruction of the app causes
// the onWillUnmount hook to be called and to fail // the onWillUnmount hook to be called and to fail
expect(mockConsoleError).toBeCalledTimes(1); expect(mockConsoleError).toBeCalledTimes(1);
@@ -444,10 +448,9 @@ describe("errors and promises", () => {
root.state = "boom"; root.state = "boom";
root.render(); root.render();
await expect(nextAppError(root.__owl__.app)).resolves.toThrow( await nextTick();
"error occured in the owl lifecycle"
);
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
}); });
}); });
@@ -497,12 +500,13 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root, { test: true }); let e: Error;
let error: OwlError; try {
const crashProm = expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart"); await mount(Root, fixture, { test: true });
await app.mount(fixture).catch((e: Error) => (error = e)); } catch (error) {
await crashProm; e = error as Error;
expect(error!.message).toBe( }
expect(e!.message).toBe(
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"` `The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
); );
}); });
@@ -519,13 +523,14 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root, { test: true }); let e: OwlError;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture, { test: true });
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted"); } catch (error) {
await mountProm; e = error as OwlError;
expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`); }
expect(error!.cause).toBe(err); expect(e!.message).toBe(`The following error occurred in onMounted: "test error"`);
expect(e!.cause).toBe(err);
}); });
test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => { test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => {
@@ -541,13 +546,14 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root, { test: true }); let e: OwlError;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture, { test: true });
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart"); } catch (error) {
await mountProm; e = error as OwlError;
expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`); }
expect(error!.cause).toBe(err); expect(e!.message).toBe(`The following error occurred in onWillStart: "test error"`);
expect(e!.cause).toBe(err);
}); });
test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => { test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => {
@@ -562,15 +568,16 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root); let e: OwlError;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture);
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); } catch (error) {
await mountProm; e = error as OwlError;
expect(error!.message).toBe( }
expect(e!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)` `An error occured in the owl lifecycle (see this Error's "cause" property)`
); );
expect(error!.cause).toBe(err); expect(e!.cause).toBe(err);
}); });
test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => { test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => {
@@ -586,15 +593,16 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root); let e: OwlError;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture);
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); } catch (error) {
await mountProm; e = error as OwlError;
expect(error!.message).toBe( }
expect(e!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)` `An error occured in the owl lifecycle (see this Error's "cause" property)`
); );
expect(error!.cause).toBe(err); expect(e!.cause).toBe(err);
}); });
test("Thrown values that are not errors are wrapped in dev mode", async () => { test("Thrown values that are not errors are wrapped in dev mode", async () => {
@@ -608,15 +616,16 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root, { test: true }); let e: OwlError;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture, { test: true });
await expect(nextAppError(app)).resolves.toThrow("not an Error was thrown in onMounted"); } catch (error) {
await mountProm; e = error as OwlError;
expect(error!.message).toBe( }
expect(e!.message).toBe(
`Something that is not an Error was thrown in onMounted (see this Error's "cause" property)` `Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
); );
expect(error!.cause).toBe("This is not an error"); expect(e!.cause).toBe("This is not an error");
}); });
test("Thrown values that are not errors are wrapped outside dev mode", async () => { test("Thrown values that are not errors are wrapped outside dev mode", async () => {
@@ -630,15 +639,16 @@ describe("can catch errors", () => {
}); });
} }
} }
const app = new App(Root); let e: OwlError;
let error: OwlError; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Root, fixture);
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); } catch (error) {
await mountProm; e = error as OwlError;
expect(error!.message).toBe( }
expect(e!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)` `An error occured in the owl lifecycle (see this Error's "cause" property)`
); );
expect(error!.cause).toBe("This is not an error"); expect(e!.cause).toBe("This is not an error");
}); });
test("can catch an error in the initial call of a component render function (parent mounted)", async () => { test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
-18
View File
@@ -171,22 +171,4 @@ describe("event handling", () => {
// input is removed when component is destroyed => nothing should happen // input is removed when component is destroyed => nothing should happen
expect([]).toBeLogged(); expect([]).toBeLogged();
}); });
test("handler works when app is mounted in an iframe", async () => {
let clickCount = 0;
class Parent extends Component {
static template = xml`<span t-on-click="inc">click me</span>`;
inc() {
clickCount++;
}
}
const iframe = document.createElement("iframe");
fixture.appendChild(iframe);
const iframeDoc = iframe.contentDocument!;
await mount(Parent, iframeDoc.body);
expect(clickCount).toBe(0);
iframeDoc.querySelector("span")!.click();
expect(clickCount).toBe(1);
});
}); });
+6 -15
View File
@@ -17,16 +17,8 @@ import {
useChildSubEnv, useChildSubEnv,
useSubEnv, useSubEnv,
xml, xml,
OwlError,
} from "../../src/index"; } from "../../src/index";
import { import { elem, logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
elem,
logStep,
makeTestFixture,
nextAppError,
nextTick,
snapshotEverything,
} from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -658,12 +650,11 @@ describe("hooks", () => {
} }
} }
let error: OwlError; try {
const app = new App(MyComponent); await mount(MyComponent, fixture);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); } catch (e: any) {
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); expect(e.cause.message).toBe("Intentional error");
await mountProm; }
expect(error!.cause.message).toBe("Intentional error");
// no console.error because the error has been caught in this test // no console.error because the error has been caught in this test
expect(console.error).toHaveBeenCalledTimes(0); expect(console.error).toHaveBeenCalledTimes(0);
console.error = originalconsoleError; console.error = originalconsoleError;
+111 -124
View File
@@ -1,6 +1,6 @@
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers"; import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { Component, onError, xml, mount, OwlError } from "../../src"; import { Component, onError, xml, mount } from "../../src";
import { App, DEV_MSG } from "../../src/runtime/app"; import { DEV_MSG } from "../../src/runtime/app";
import { validateProps } from "../../src/runtime/template_helpers"; import { validateProps } from "../../src/runtime/template_helpers";
import { Schema } from "../../src/runtime/validation"; import { Schema } from "../../src/runtime/validation";
@@ -48,14 +48,13 @@ describe("props validation", () => {
static components = { SubComp }; static components = { SubComp };
static template = xml`<div><SubComp /></div>`; static template = xml`<div><SubComp /></div>`;
} }
let error: Error | undefined;
const app = new App(Parent, { test: true }); try {
let error: OwlError | undefined; await mount(Parent, fixture, { dev: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); } catch (e) {
await expect(nextAppError(app)).resolves.toThrow( error = e as Error;
"Invalid props for component 'SubComp': 'message' is missing" }
);
await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing"); expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
error = undefined; error = undefined;
@@ -78,13 +77,12 @@ describe("props validation", () => {
static template = xml`<div><SubComp /></div>`; static template = xml`<div><SubComp /></div>`;
} }
const app = new App(Parent, { test: true }); let error: Error;
let error: OwlError | undefined; try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow( } catch (e) {
"Invalid props for component 'SubComp': 'message' is missing" error = e as Error;
); }
await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing"); expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
}); });
@@ -128,12 +126,14 @@ describe("props validation", () => {
}; };
(Parent as any).components = { SubComp }; (Parent as any).components = { SubComp };
let error: Error | undefined;
props = {}; props = {};
let app = new App(Parent, { test: true });
let error: OwlError | undefined; try {
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})` `Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
@@ -147,10 +147,11 @@ describe("props validation", () => {
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: test.ko }; props = { p: test.ko };
app = new App(Parent, { test: true }); try {
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}` `Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
@@ -180,12 +181,13 @@ describe("props validation", () => {
static template = xml`<div>hey</div>`; static template = xml`<div>hey</div>`;
}; };
(Parent as any).components = { SubComp }; (Parent as any).components = { SubComp };
let error: Error | undefined;
props = {}; props = {};
let app = new App(Parent, { test: true }); try {
let error: OwlError | undefined; await mount(Parent, fixture, { dev: true });
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); } catch (e) {
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); error = e as Error;
await mountProm; }
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})` `Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
@@ -199,10 +201,11 @@ describe("props validation", () => {
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: test.ko }; props = { p: test.ko };
app = new App(Parent, { test: true }); try {
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}` `Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
@@ -224,25 +227,26 @@ describe("props validation", () => {
} }
let error: Error; let error: Error;
let props: { p?: any }; let props: { p?: any };
props = { p: "string" };
try { try {
props = { p: "string" };
await mount(Parent, fixture, { dev: true }); await mount(Parent, fixture, { dev: true });
} catch (e) { } catch (e) {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: true };
try { try {
props = { p: true };
await mount(Parent, fixture, { dev: true }); await mount(Parent, fixture, { dev: true });
} catch (e) { } catch (e) {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: 1 }; try {
const app = new App(Parent, { test: true }); props = { p: 1 };
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' is not a string or boolean" "Invalid props for component 'SubComp': 'p' is not a string or boolean"
@@ -263,25 +267,26 @@ describe("props validation", () => {
} }
let error: Error; let error: Error;
let props: { p?: any }; let props: { p?: any };
props = { p: "key" };
try { try {
props = { p: "key" };
await mount(Parent, fixture, { dev: true }); await mount(Parent, fixture, { dev: true });
} catch (e) { } catch (e) {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = {};
try { try {
props = {};
await mount(Parent, fixture, { dev: true }); await mount(Parent, fixture, { dev: true });
} catch (e) { } catch (e) {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: 1 }; try {
const app = new App(Parent, { test: true }); props = { p: 1 };
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string"); expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string");
}); });
@@ -314,18 +319,20 @@ describe("props validation", () => {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: [1] }; try {
let app = new App(Parent, { test: true }); props = { p: [1] };
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
error = undefined; error = undefined;
app = new App(Parent, { test: true }); try {
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); props = { p: ["string", 1] };
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); await mount(Parent, fixture, { dev: true });
await mountProm; } catch (e) {
expect(error!).toBeDefined(); error = e as Error;
}
}); });
test("can validate an array with multiple sub element types", async () => { test("can validate an array with multiple sub element types", async () => {
@@ -363,11 +370,12 @@ describe("props validation", () => {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: [true, 1] }; try {
const app = new App(Parent, { test: true }); props = { p: [true, 1] };
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p[1]' is not a string or boolean" "Invalid props for component 'SubComp': 'p[1]' is not a string or boolean"
@@ -397,30 +405,33 @@ describe("props validation", () => {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: { id: 1, url: "url", extra: true } }; try {
let app = new App(Parent, { test: true }); props = { p: { id: 1, url: "url", extra: true } };
let mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape (unknown key 'extra')" "Invalid props for component 'SubComp': 'p' has not the correct shape (unknown key 'extra')"
); );
props = { p: { id: "1", url: "url" } }; try {
app = new App(Parent, { test: true }); props = { p: { id: "1", url: "url" } };
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape ('id' is not a number)" "Invalid props for component 'SubComp': 'p' has not the correct shape ('id' is not a number)"
); );
error = undefined; error = undefined;
props = { p: { id: 1 } }; try {
app = new App(Parent, { test: true }); props = { p: { id: 1 } };
mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is missing (should be a string))" "Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is missing (should be a string))"
@@ -463,11 +474,12 @@ describe("props validation", () => {
error = e as Error; error = e as Error;
} }
expect(error!).toBeUndefined(); expect(error!).toBeUndefined();
props = { p: { id: 1, url: [12, true] } }; try {
const app = new App(Parent, { test: true }); props = { p: { id: 1, url: [12, true] } };
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is not a boolean or list of numbers)" "Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is not a boolean or list of numbers)"
@@ -674,10 +686,11 @@ describe("props validation", () => {
static components = { SubComp }; static components = { SubComp };
} }
let error: Error; let error: Error;
const app = new App(Parent, { test: true }); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing"); expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
}); });
@@ -741,10 +754,11 @@ describe("props validation", () => {
static template = xml`<div><Child/></div>`; static template = xml`<div><Child/></div>`;
} }
let error: Error; let error: Error;
const app = new App(Parent, { test: true }); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'Child'"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"Invalid props for component 'Child': 'mandatory' is missing (should be a number)" "Invalid props for component 'Child': 'mandatory' is missing (should be a number)"
@@ -780,32 +794,6 @@ describe("props validation", () => {
// we just check that it doesn't throw // we just check that it doesn't throw
await expect(mount(Parent, fixture, { dev: true })).resolves.toEqual(expect.anything()); await expect(mount(Parent, fixture, { dev: true })).resolves.toEqual(expect.anything());
}); });
test("can validate through slots", async () => {
class Child extends Component {
static props = ["message"];
static template = xml`<div>hey</div>`;
}
class Wrapper extends Component {
static template = xml`<t t-slot="default"/>`;
}
class Parent extends Component {
static components = { Child, Wrapper };
static template = xml`<Wrapper><Child /></Wrapper>`;
}
const app = new App(Parent, { test: true });
let error: OwlError | undefined;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"Invalid props for component 'Child': 'message' is missing"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'Child': 'message' is missing");
});
}); });
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -871,12 +859,11 @@ describe("default props", () => {
static template = xml`<Child/>`; static template = xml`<Child/>`;
} }
let error: Error; let error: Error;
const app = new App(Parent, { test: true }); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow( } catch (e) {
"default value cannot be defined for a mandatory prop" error = e as Error;
); }
await mountProm;
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe( expect(error!.message).toBe(
"A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)" "A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)"
+5 -10
View File
@@ -1,5 +1,5 @@
import { App, Component, mount, onMounted, useRef, useState } from "../../src/index"; import { Component, mount, onMounted, useRef, useState } from "../../src/index";
import { logStep, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers"; import { logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { xml } from "../../src/index"; import { xml } from "../../src/index";
snapshotEverything(); snapshotEverything();
@@ -94,14 +94,9 @@ describe("refs", () => {
ref = useRef("coucou"); ref = useRef("coucou");
} }
const app = new App(Test, { test: true }); await expect(async () => {
const mountProm = expect(app.mount(fixture)).rejects.toThrowError( await mount(Test, fixture);
"Cannot have 2 elements with same ref name at the same time" }).rejects.toThrowError("Cannot have 2 elements with same ref name at the same time");
);
await expect(nextAppError(app)).resolves.toThrow(
"Cannot have 2 elements with same ref name at the same time"
);
await mountProm;
expect(console.warn).toBeCalledTimes(1); expect(console.warn).toBeCalledTimes(1);
console.warn = consoleWarn; console.warn = consoleWarn;
}); });
+8 -122
View File
@@ -1,5 +1,5 @@
import { App, Component, mount, onMounted, useState, xml } from "../../src/index"; import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
import { children, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers"; import { children, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
snapshotEverything(); snapshotEverything();
let originalconsoleWarn = console.warn; let originalconsoleWarn = console.warn;
@@ -45,23 +45,6 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some text"); expect(fixture.innerHTML).toBe("some text");
}); });
test("t-set-slot=default has priority over rest of the content", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/>`;
}
class Parent extends Component {
static template = xml`<Child>
some text
<t t-set-slot="default">some other text</t>
</Child>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("some other text");
});
test("simple slot with slot scope", async () => { test("simple slot with slot scope", async () => {
let child: any; let child: any;
class Child extends Component { class Child extends Component {
@@ -221,12 +204,13 @@ describe("slots", () => {
static components = { Child }; static components = { Child };
} }
let error: Error; let error = null;
const app = new App(Parent); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture);
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); } catch (e) {
await mountProm; error = e;
expect(error!).not.toBeNull(); }
expect(error).not.toBeNull();
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
}); });
@@ -1835,102 +1819,4 @@ describe("slots", () => {
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2334]"); expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2334]");
}); });
test("slot in multiple locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
class Slotter extends Component {
static components = { Child };
static template = xml`
<t t-if="props.location === 1">
<p><t t-slot="default"/></p>
</t>
<t t-if="props.location === 2">
<t t-slot="default"/>
</t>
`;
}
class Parent extends Component {
static components = { Child, Slotter };
static template = xml`
<Slotter location="state.location">
hello <Child/>
</Slotter>`;
state = useState({ location: 1 });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<p> hello <div>child</div></p>");
parent.state.location = 2;
await nextTick();
expect(fixture.innerHTML).toBe(" hello <div>child</div>");
});
test("dynamic slot in multiple locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
class Slotter extends Component {
static components = { Child };
static template = xml`
<t t-if="props.location === 1">
<p><t t-slot="{{'coffee'}}"/></p>
</t>
<t t-if="props.location === 2">
<t t-slot="{{'coffee'}}"/>
</t>
`;
}
class Parent extends Component {
static components = { Child, Slotter };
static template = xml`
<Slotter location="state.location">
<t t-set-slot="coffee">hello <Child/></t>
</Slotter>`;
state = useState({ location: 1 });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<p>hello <div>child</div></p>");
parent.state.location = 2;
await nextTick();
expect(fixture.innerHTML).toBe("hello <div>child</div>");
});
test("slot in t-foreach locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
class Slotter extends Component {
static components = { Child };
static template = xml`
<t t-foreach="props.list" t-as="elem" t-key="elem_index">
<p><t t-esc="elem"/><t t-slot="default"/></p>
</t>
`;
}
class Parent extends Component {
static components = { Child, Slotter };
static template = xml`
<Slotter list="state.list">
hello <Child/>
</Slotter>`;
state = useState({ list: [1] });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<p>1 hello <div>child</div></p>");
parent.state.list.push(2);
await nextTick();
expect(fixture.innerHTML).toBe(
"<p>1 hello <div>child</div></p><p>2 hello <div>child</div></p>"
);
});
}); });
+8 -7
View File
@@ -1,6 +1,6 @@
import { OwlError } from "../../src/runtime/error_handling"; import { OwlError } from "../../src/runtime/error_handling";
import { App, Component, mount, onMounted, useState, xml } from "../../src"; import { Component, mount, onMounted, useState, xml } from "../../src";
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers"; import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
snapshotEverything(); snapshotEverything();
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -343,15 +343,16 @@ describe("style and class handling", () => {
class Child extends Component { class Child extends Component {
static template = xml`<div t-att-class="props.class" t-esc="this.will.crash"/>`; static template = xml`<div t-att-class="props.class" t-esc="this.will.crash"/>`;
} }
class Parent extends Component { class ParentWidget extends Component {
static template = xml`<Child class="'a'"/>`; static template = xml`<Child class="'a'"/>`;
static components = { Child }; static components = { Child };
} }
let error: OwlError; let error: OwlError;
const app = new App(Parent); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(ParentWidget, fixture);
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); } catch (e) {
await mountProm; error = e as OwlError;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
const regexp = const regexp =
-28
View File
@@ -287,32 +287,4 @@ describe("t-call", () => {
}); });
expect(fixture.innerHTML).toBe("childaaronchildlucas"); expect(fixture.innerHTML).toBe("childaaronchildlucas");
}); });
test("t-call with t-call-context and subcomponent, in dev mode", async () => {
class Child extends Component {
static template = xml`child<t t-esc="props.name"/>`;
static props = ["name"];
}
class Root extends Component {
static template = xml`
<t t-call="someTemplate" t-call-context="subctx"/>`;
static components = { Child };
subctx = { aab: "aaron", lpe: "lucas" };
}
await mount(Root, fixture, {
dev: true,
templates: `
<templates>
<t t-name="someTemplate">
<Child name="aab"/>
<Child name="lpe"/>
</t>
</templates>`,
});
expect(fixture.innerHTML).toBe("childaaronchildlucas");
});
}); });
+5 -74
View File
@@ -1,11 +1,5 @@
import { App, Component, mount, onMounted, useState, xml } from "../../src/index"; import { Component, mount, onMounted, useState, xml } from "../../src/index";
import { import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
makeTestFixture,
nextAppError,
nextTick,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
snapshotEverything(); snapshotEverything();
@@ -321,73 +315,10 @@ describe("list of components", () => {
`; `;
static components = { Child }; static components = { Child };
} }
await expect(async () => {
const app = new App(Parent, { test: true }); await mount(Parent, fixture, { dev: true });
const mountProm = expect(app.mount(fixture)).rejects.toThrow( }).rejects.toThrowError("Got duplicate key in t-foreach: child");
"Got duplicate key in t-foreach: child"
);
await expect(nextAppError(app)).resolves.toThrow("Got duplicate key in t-foreach: child");
await mountProm;
console.info = consoleInfo; console.info = consoleInfo;
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
}); });
test("crash when using object as keys that serialize to the same string", 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="[{}, {}]" t-as="item" t-key="item">
<Child/>
</t>
`;
static components = { Child };
}
const app = new App(Parent, { test: true });
const mountProm = expect(app.mount(fixture)).rejects.toThrow(
"Got duplicate key in t-foreach: [object Object]"
);
await expect(nextAppError(app)).resolves.toThrow(
"Got duplicate key in t-foreach: [object Object]"
);
await mountProm;
console.info = consoleInfo;
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("order is correct when slots are not of same type", async () => {
class Child extends Component {
static template = xml`
<t t-slot="{{ slotName }}" t-foreach="slotNames" t-as="slotName" t-key="slotName"/>
`;
get slotNames() {
return Object.entries(this.props.slots)
.filter((entry: any) => entry[1].active)
.map((entry) => entry[0]);
}
}
class Parent extends Component {
static template = xml`
<Child>
<t t-set-slot="a" active="!state.active"><div t-if="!state.active">A</div></t>
<t t-set-slot="b" active="true">B</t>
<t t-set-slot="c" active="state.active">C</t>
</Child>
`;
static components = { Child };
state = useState({ active: false });
}
const parent = await mount(Parent, fixture);
expect(fixture.textContent).toBe("AB");
parent.state.active = true;
await nextTick();
expect(fixture.textContent).toBe("BC");
});
}); });
-24
View File
@@ -41,30 +41,6 @@ describe("t-on", () => {
expect(steps).toEqual(["click"]); expect(steps).toEqual(["click"]);
}); });
test("t-on when first component child is an empty component", async () => {
class Child extends Component {
static template = xml`
<span t-foreach="props.list" t-as="c" t-key="c_index" t-esc="c"/>
`;
}
class Parent extends Component {
static template = xml`
<div t-on-click="push"><Child list="list" t-on-click="() => {}"/></div>
`;
static components = { Child };
list = useState([] as string[]);
push() {
this.list.push("foo");
}
}
const parent = await mount(Parent, fixture);
const el = elem(parent);
expect(el.innerHTML).toBe("");
el.click();
await nextTick();
expect(el.innerHTML).toBe("<span>foo</span>");
});
test("t-on expression in t-foreach", async () => { test("t-on expression in t-foreach", async () => {
class Comp extends Component { class Comp extends Component {
static template = xml` static template = xml`
-36
View File
@@ -1,36 +0,0 @@
import { Component, mount, xml } from "../../src/index";
import { makeTestFixture, snapshotEverything } from "../helpers";
snapshotEverything();
// -----------------------------------------------------------------------------
// t-out
// -----------------------------------------------------------------------------
describe("components in t-out", () => {
let fixture: HTMLElement;
beforeEach(() => {
fixture = makeTestFixture();
});
test("simple list", async () => {
class Child extends Component {
static template = xml`child`;
}
class Parent extends Component {
static template = xml`
<t t-foreach="[1,2]" t-as="n" t-key="n">
<t t-set="blabla">
<Child />
</t>
<t t-out="blabla"/>
</t>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("childchild");
});
});
-14
View File
@@ -261,20 +261,6 @@ expect.extend({
}, },
}); });
export function nextAppError(app: any) {
const { handleError } = app;
return new Promise((resolve) => {
app.handleError = (...args: Parameters<typeof handleError>) => {
try {
handleError.call(app, ...args);
} catch (e: any) {
app.handleError = handleError;
resolve(e);
}
};
});
}
declare global { declare global {
namespace jest { namespace jest {
interface Matchers<R> { interface Matchers<R> {
@@ -155,44 +155,6 @@ exports[`Portal Add and remove portals with t-foreach inside div 1`] = `
}" }"
`; `;
exports[`Portal Child and Portal 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
let block3 = createBlock(\`<div class=\\"portal\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = comp1({}, key + \`__1\`, node, this, null);
const b3 = block3();
return multi([b2, b3]);
}
}"
`;
exports[`Portal Child and Portal 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const Portal = app.Portal;
const comp1 = app.createComponent(null, false, true, false, false);
let block2 = createBlock(\`<span>child</span>\`);
let block3 = createBlock(\`<span>portal</span>\`);
function slot1(ctx, node, key = \\"\\") {
return block3();
}
return function template(ctx, node, key = \\"\\") {
const b2 = block2();
const b4 = comp1({target: '.portal',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal);
return multi([b2, b4]);
}
}"
`;
exports[`Portal Portal composed with t-slot 1`] = ` exports[`Portal Portal composed with t-slot 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -553,44 +515,6 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 2`]
}" }"
`; `;
exports[`Portal portal and Child 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
let block2 = createBlock(\`<div class=\\"portal\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = block2();
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
}"
`;
exports[`Portal portal and Child 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const Portal = app.Portal;
const comp1 = app.createComponent(null, false, true, false, false);
let block2 = createBlock(\`<span>child</span>\`);
let block3 = createBlock(\`<span>portal</span>\`);
function slot1(ctx, node, key = \\"\\") {
return block3();
}
return function template(ctx, node, key = \\"\\") {
const b2 = block2();
const b4 = comp1({target: '.portal',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal);
return multi([b2, b4]);
}
}"
`;
exports[`Portal portal could have dynamically no content 1`] = ` exports[`Portal portal could have dynamically no content 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
+17 -56
View File
@@ -12,7 +12,7 @@ import {
} from "../../src"; } from "../../src";
import { xml } from "../../src/"; import { xml } from "../../src/";
import { DEV_MSG } from "../../src/runtime/app"; import { DEV_MSG } from "../../src/runtime/app";
import { elem, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers"; import { elem, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
let originalconsoleWarn = console.warn; let originalconsoleWarn = console.warn;
@@ -269,14 +269,15 @@ describe("Portal", () => {
} }
let error: Error; let error: Error;
const app = new App(Parent); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture);
await expect(nextAppError(app)).resolves.toThrow("invalid portal target"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe("invalid portal target"); expect(error!.message).toBe("invalid portal target");
expect(fixture.innerHTML).toBe(``); expect(fixture.innerHTML).toBe(`<div></div>`);
expect(mockConsoleWarn).toBeCalledTimes(1); expect(mockConsoleWarn).toBeCalledTimes(1);
}); });
@@ -874,48 +875,6 @@ describe("Portal", () => {
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"></div><div></div>'); expect(fixture.innerHTML).toBe('<div id="outside"></div><div></div>');
}); });
test("Child and Portal", async () => {
class Child extends Component {
static template = xml`
<span>child</span>
<t t-portal="'.portal'"><span>portal</span></t>`;
}
class Parent extends Component {
static template = xml`
<t>
<Child/>
<div class="portal"></div>
</t>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(
'<span>child</span><div class="portal"><span>portal</span></div>'
);
});
test("portal and Child", async () => {
class Child extends Component {
static template = xml`
<span>child</span>
<t t-portal="'.portal'"><span>portal</span></t>`;
}
class Parent extends Component {
static template = xml`
<t>
<div class="portal"></div>
<Child/>
</t>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(
'<div class="portal"><span>portal</span></div><span>child</span>'
);
});
}); });
describe("Portal: UI/UX", () => { describe("Portal: UI/UX", () => {
@@ -1001,10 +960,11 @@ describe("Portal: Props validation", () => {
</div>`; </div>`;
} }
let error: OwlError; let error: OwlError;
const app = new App(Parent); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); } catch (e) {
await mountProm; error = e as OwlError;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.cause).toBeDefined(); expect(error!.cause).toBeDefined();
expect(error!.cause.message).toBe(`' ' is not a valid selector`); expect(error!.cause.message).toBe(`' ' is not a valid selector`);
@@ -1020,10 +980,11 @@ describe("Portal: Props validation", () => {
</div>`; </div>`;
} }
let error: Error; let error: Error;
const app = new App(Parent); try {
const mountProm = app.mount(fixture).catch((e: Error) => (error = e)); await mount(Parent, fixture, { dev: true });
await expect(nextAppError(app)).resolves.toThrow("invalid portal target"); } catch (e) {
await mountProm; error = e as Error;
}
expect(error!).toBeDefined(); expect(error!).toBeDefined();
expect(error!.message).toBe(`invalid portal target`); expect(error!.message).toBe(`invalid portal target`);
}); });
+3 -1
View File
@@ -72,8 +72,10 @@ async function startRelease() {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
log(`Step 3/${STEPS}: updating package.json...`); log(`Step 3/${STEPS}: updating package.json, readme.md and roadmap.md...`);
await replaceInFile("./package.json", current, next); await replaceInFile("./package.json", current, next);
await replaceInFile("./README.md", current, next);
await replaceInFile("./roadmap.md", current, next);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
log(`Step 4/${STEPS}: creating git commit...`); log(`Step 4/${STEPS}: creating git commit...`);