Compare commits

...

22 Commits

Author SHA1 Message Date
Samuel Degueldre b2cfefd008 [FIX] portal: correctly mount portal content in target created by mount
Previously, blah blah blah
2022-10-19 14:39:44 +02:00
Samuel Degueldre 30d6994836 [FIX] blockdom: correctly reorder children in heterogeneous t-foreach
Currently, the `moveBefore` method on VNodes assumes that the `other`
VNode it receives is of the same type, and that the entire VNode tree
below that other VNode has the exact same structure. While this is
correct in most cases, it breaks down when there is a VToggler somewhere
in the VNode tree, as the structure below a VToggler can be very
different from the structure below another VToggler that was created
from the same compiled code. For example, two iterations of a t-foreach
that contains a <t t-component="..."/> may spawn different components,
and different components obviously have different structures.

One way to fix this is to remove the assumption that the structure of
the `this` block tree in moveBefore is the same as the structure
of the `other` block tree, and instead, always give the concrete DOM
node before which we want to move the current VNode instead of giving it
a VNode and an afterNode as a fallback. One problem with this solution
is that it degrades performance in the "standard" case, where a
t-foreach contains no VToggler anywhere in its block tree, as retrieving
the first concrete DOM node requires calling firstNode() which
recursively traverses the entire tree.

To avoid this performance penalty in the standard case, we opt to only
go down this route whenever we encounter a VToggler when calling
`moveBefore`. This requires that we maintain two separate methods, one
to move a VNode before another VNode of assumed similar structure, which
is basically the current implementation of `moveBefore` for all VNode
types except VToggler, and one implementation that moves a VNode before
a concrete DOM node. This method needs to be implemented for all VNode
types, as all VNode types can be descendants of a VToggler. This method
will only be called from one place: the `moveBeforeVNode` method of the
toggler, which is the point where we realize that the assumption of
identical structure breaks down.

Co-authored-by: Bruno Boi <boi@odoo.com>
2022-10-19 13:11:44 +02:00
Samuel Degueldre ba1a270c93 [FIX] parser: give t-set-slot="default" priority over the content
Currently, if a component has a default slot defined with t-set-slot,
and also content that compiles to something (eg, text or even a comment
node), the content takes priority over the t-set-slot. As t-set-slot is
more explicity, it should have priority.
2022-10-10 20:33:18 +02:00
Samuel Degueldre d546244fc3 [FIX] runtime: correctly throw an error for duplicate object keys
Currently when checking for duplicate keys, we insert the value of the
key as is in a set then check for unicity against those. When the key is
an object, we check for duplicates based on object identity, whereas the
keys are used by owl as strings, and so using objects can cause
duplicate key errors that do not throw correctly but crash in the owl
internals.

This commit fixes that by making the duplicate checking code serialize
the key to string before insertion and when comparing against existing
keys.
2022-10-10 13:53:11 +02:00
Géry Debongnie a1f22829c1 [REL] v2.0.0
# v2.0.0

Finally the official v2.0.0 release is ready. There are no feature nor fixes since
last beta release, because it is stable.

Thank you to everyone who contributed.

## Changelog

Owl 2.0 is a large improvement over 1.0. It brings a lot of new features, improvements,
and better APIs.  The most important changes are:

- a completely overhauled slot API (in particular slot scopes, ...)
- a new reactivity system, similar to Vue. In particular, if props are equals, then
  a sub component is not updated.
- new rendering engine, based on blockdom. This makes Owl much faster
- support for fragments: a template can have an arbitrary number of roots

A detailed changelog can be found [here](CHANGELOG.md).
2022-10-07 15:27:58 +02:00
Géry Debongnie 64bad25762 [REL] v2.0.0-beta-22
# v2.0.0-beta-22

- fix: t-call: nested t-call with magic variable 0
- fix: prevent crash in case with t-foreach, t-out and components
2022-09-29 09:17:06 +02:00
Géry Debongnie 7ab34c5ca5 [FIX] prevent crash in case with t-foreach and t-out with components
The t-out directive is compiled internally into a LazyValue, which
represents a value that may or may not be created sometimes in the
future.  It can also be reused more than once, and this is where there
may be an issue: if a component is contained in the lazyvalue, it needs
a unique key (coming from the t-foreach) to be properly indexed in the
parent children map.  However, the LazyValue does not keep the key
information, so it is not able to provide it to its content.

The fix is then quite clear: the LazyValue class should store the key
information, and provides it to its content.  This allows the LazyValue
to be used multiple times, in any place in a template.

closes #1270
2022-09-29 08:28:38 +02:00
Géry Debongnie 669fd622ec [FIX] t-call: nested t-call with magic variable 0
Before this commit, the template compiler would guess the next block id
that will be generated when compiling the body of a tcall.  This is
correct IF there are not nested t-call, but otherwise wrong, because the
next block id could be mixed up: the first t-call would save the next
block id (let's say n), then the inner t-call would also save the same
block id (so, n), will then generate its own block (n+1), then the outer
t-call would use the block n index instead of n+1

The best fix, in my opinion, is to make sure we get the next block var
name, so we do not have to guess. To do that, each compile block type
function needs to properly return the information.

closes #1267
2022-09-28 09:31:41 +02:00
Géry Debongnie ab72cdddde [REL] v2.0.0-beta-21
# v2.0.0-beta-21

- fix: prevent side effects at template compilation
- fix: props validation: does not crash with t-call-context
- fix: make t-portal work in all cases
- fix: make props validation work through slots
2022-09-26 15:44:11 +02:00
Géry Debongnie 17fb33475c [FIX] props validation: make it work through slots
A recent commit fixes the props validation code to make it work
regardless of the rendering context (important with the recent
t-call-context directive). Unfortunately, it then breaks props
validation through slots, because it assumed that the parent node in the
virtual node was the parent of the component, but it is not necessarily
true.

To fix this, we can use a simple property of the template functions:
they are bound to the current instance of the component, so we can
simply use "this"
2022-09-26 15:17:58 +02:00
Géry Debongnie ab29b896eb [FIX] portal: make it work in all cases
Before this commit, the portal wouldn't work when its target is created
after the portal content, since it wouldn't be able to mount the dom at
the correct location.

With this commit, we work around the issue by mounting the portal
content at the portal location, then when the Portal component is
mounted, moving it to its correct location.

The big downside with that approach is that the portal content is
(sometimes) rendered and mounted at a location, THEN mounted in another
location. I think that it is most of the time not an issue, but one
could argue that it is inconsistent: some specific code could work at
one point, then fail in a different very similar situation (for example,
iframes don't support very well being moved around).  On the flip side,
having the portal work as expected is very useful, and may be worth the
tradeoff.

closes #1250
2022-09-26 12:01:12 +02:00
Géry Debongnie d5ed25cd19 [FIX] props validation: does not crash with t-call-context
The code for props validation assumed that the rendering context was a
component.  This was actually true when it was written, but is no longer
true since t-call-context was introduced.

Because of that, it would crash when trying to access the internals of
the component, such as the static components object.

The fix is simple: instead of passing the context to the props
validation code, which can now be anything, we pass the component node,
which is guaranteed to give a reference to the component (and also to
the app).  This also make the code slightly simpler.

closes #1261
2022-09-24 08:34:22 +02:00
Géry Debongnie c4f0f17b9b [FIX] blockdom: prevent side effects at block compilation
When creating the template node for a block, we create htmlelements and
set their (static) attributes.  But this can have side effects. For
example, setting the src attribute for an img element will trigger a
request to fetch the image.

We avoid that issue by simply setting the html element template node
inside a <template/> element.

Note that I don't really see how to test this fix in jest: we don't have
a real browser, and no real way to check for this side effect.

closes #1257
2022-09-21 13:59:13 +02:00
Florent Dardenne - dafl@odoo d27455e9f2 [IMP] doc: explicit useEffect first parameter
The `useEffect` has two parameters:
* The `effect` function
* The `computeDependencies` function

The `effect` function always take as parameters the result
 of the `computeDependencies` function.

Expliciting this allows to better understand the `useEffect`
behaviour and the following example in the doc:

```
useEffect(
    (el) => el && el.focus(),
    () => [ref.el]
  );
```
2022-09-09 20:24:56 +02:00
Géry Debongnie 6ef38676c4 [DOC] doc: fix broken link and update roadmap 2022-09-09 09:45:05 +02:00
Géry Debongnie b51756f356 [REL] v2.0.0-beta-20
# v2.0.0-beta-20

- app: properly rethrow unhandled errors
2022-09-09 09:26:12 +02:00
Samuel Degueldre cfdf7caa50 [IMP] app: rethrow errors that were not handled
This commit makes it so that when an error occurs in an owl app and none
of the registered error handlers are able to handle it, we rethrow the
error instead of just logging it to the console and swallowing it. This
allows users of owl to handle errors that happen in owl applications by
using event listeners for error and unhandledrejection events on the
window.
2022-09-09 09:23:32 +02:00
Florent Dardenne - dafl@odoo a5a6a592c1 [FIX] tutorial_todoapp: fix the final code mount issue
In app.js, `mount(Root, document.body, { dev: true, env });`  crash because `body` is not available yet.
Therefore, moving the script into the body fix the issue.
2022-09-08 13:30:38 +02:00
Géry Debongnie d0d7482b0f [REL] v2.0.0-beta-19
# v2.0.0-beta-19

- fix: events: correctly call handlers in iframes
2022-09-06 12:13:25 +02:00
Samuel Degueldre 8fe4c0c76e [FIX] events: correctly call handlers in iframes
Previously, event handlers would not work when an app was mounted in an
iframe, this is caused by a guard in the event handler that checks that
the target element is still in the document, but it doesn't check
against the correct document in the case of an iframe.

This commit changes the check to check against the target's
ownerDocument.
2022-09-06 12:06:59 +02:00
Géry Debongnie c1afaeb92a [REL] v2.0.0-beta-18
# v2.0.0-beta-18

- fix: allow multiple occurrences of same slot in different locations
2022-09-02 14:57:18 +02:00
Géry Debongnie 3883cec079 [FIX] slots: prevent crash when using same slot in different locations
Before this commit, a crash could occur when a component with no props
is defined in a slot, and that slot is conditionally displayed in
multiple locations.

The reason for that is that the key provided to the callSlot function
was identical, so from the perspective of the component function, it was
not possible to make the difference between a component located in
either places.  With this commit, we make sure that a unique key is used
when a slot is reused in a template (or if it is dynamic, because in
that case, we have no idea at compile time if it will be unique or not)

closes #1246
2022-09-02 12:12:00 +02:00
52 changed files with 1549 additions and 575 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:
- [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
- [owl](https://github.com/odoo/owl/releases/latest)
+3 -2
View File
@@ -770,10 +770,11 @@ For reference, here is the final code:
<meta charset="UTF-8" />
<title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" />
</head>
<body>
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</body>
</html>
```
+2 -1
View File
@@ -234,7 +234,8 @@ are defined by a function instead of just the dependencies.
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 dependency function returns a list of dependencies. If any of these
function. The dependency function returns a list of dependencies, these dependencies
are passed as parameters in the effect function . If any of these
dependencies changes, then the current effect will be cleaned up and reexecuted.
Here is an example without any dependencies:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.0-beta-17",
"version": "2.0.0",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
+4 -23
View File
@@ -1,28 +1,9 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.4.10
- Current version: 2.X
- Status: stable
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.
Owl is currently stable. No (large) improvements is expected in the near future.
Note that we intend to keep maintaining owl, and as such, improvements and/or
breaking changes may require a version bump in the future.
+98 -79
View File
@@ -214,6 +214,14 @@ class CodeTarget {
result.push(`}`);
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"];
@@ -232,6 +240,7 @@ export class CodeGenerator {
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
ast: AST;
staticDefs: { id: string; expr: string }[] = [];
slotNames: Set<String> = new Set();
helpers: Set<string> = new Set();
constructor(ast: AST, options: CodeGenOptions) {
@@ -364,19 +373,15 @@ export class CodeGenerator {
insertBlock(expression: string, block: BlockDescription, ctx: Context): void {
let blockExpr = block.generateExpr(expression);
const tKeyExpr = ctx.tKeyExpr;
if (block.parentVar) {
let keyArg = `key${this.target.loopLevel}`;
if (tKeyExpr) {
keyArg = `${tKeyExpr} + ${keyArg}`;
}
let key = this.target.currentKey(ctx);
this.helpers.add("withKey");
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
return;
}
if (tKeyExpr) {
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
if (ctx.tKeyExpr) {
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
}
if (block.isRoot && !ctx.preventRoot) {
@@ -423,78 +428,66 @@ export class CodeGenerator {
.join("");
}
compileAST(ast: AST, ctx: Context) {
/**
* @returns the newly created block name, if any
*/
compileAST(ast: AST, ctx: Context): string | null {
switch (ast.type) {
case ASTType.Comment:
this.compileComment(ast, ctx);
break;
return this.compileComment(ast, ctx);
case ASTType.Text:
this.compileText(ast, ctx);
break;
return this.compileText(ast, ctx);
case ASTType.DomNode:
this.compileTDomNode(ast, ctx);
break;
return this.compileTDomNode(ast, ctx);
case ASTType.TEsc:
this.compileTEsc(ast, ctx);
break;
return this.compileTEsc(ast, ctx);
case ASTType.TOut:
this.compileTOut(ast, ctx);
break;
return this.compileTOut(ast, ctx);
case ASTType.TIf:
this.compileTIf(ast, ctx);
break;
return this.compileTIf(ast, ctx);
case ASTType.TForEach:
this.compileTForeach(ast, ctx);
break;
return this.compileTForeach(ast, ctx);
case ASTType.TKey:
this.compileTKey(ast, ctx);
break;
return this.compileTKey(ast, ctx);
case ASTType.Multi:
this.compileMulti(ast, ctx);
break;
return this.compileMulti(ast, ctx);
case ASTType.TCall:
this.compileTCall(ast, ctx);
break;
return this.compileTCall(ast, ctx);
case ASTType.TCallBlock:
this.compileTCallBlock(ast, ctx);
break;
return this.compileTCallBlock(ast, ctx);
case ASTType.TSet:
this.compileTSet(ast, ctx);
break;
return this.compileTSet(ast, ctx);
case ASTType.TComponent:
this.compileComponent(ast, ctx);
break;
return this.compileComponent(ast, ctx);
case ASTType.TDebug:
this.compileDebug(ast, ctx);
break;
return this.compileDebug(ast, ctx);
case ASTType.TLog:
this.compileLog(ast, ctx);
break;
return this.compileLog(ast, ctx);
case ASTType.TSlot:
this.compileTSlot(ast, ctx);
break;
return this.compileTSlot(ast, ctx);
case ASTType.TTranslation:
this.compileTTranslation(ast, ctx);
break;
return this.compileTTranslation(ast, ctx);
case ASTType.TPortal:
this.compileTPortal(ast, ctx);
return this.compileTPortal(ast, ctx);
}
}
compileDebug(ast: ASTDebug, ctx: Context) {
compileDebug(ast: ASTDebug, ctx: Context): string | null {
this.addLine(`debugger;`);
if (ast.content) {
this.compileAST(ast.content, ctx);
return this.compileAST(ast.content, ctx);
}
return null;
}
compileLog(ast: ASTLog, ctx: Context) {
compileLog(ast: ASTLog, ctx: Context): string | null {
this.addLine(`console.log(${compileExpr(ast.expr)});`);
if (ast.content) {
this.compileAST(ast.content, ctx);
return this.compileAST(ast.content, ctx);
}
return null;
}
compileComment(ast: ASTComment, ctx: Context) {
compileComment(ast: ASTComment, ctx: Context): string {
let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock;
if (isNewBlock) {
@@ -507,9 +500,10 @@ export class CodeGenerator {
const text = xmlDoc.createComment(ast.value);
block!.insert(text);
}
return block!.varName;
}
compileText(ast: ASTText, ctx: Context) {
compileText(ast: ASTText, ctx: Context): string {
let { block, forceNewBlock } = ctx;
let value = ast.value;
@@ -528,6 +522,7 @@ export class CodeGenerator {
const createFn = ast.type === ASTType.Text ? xmlDoc.createTextNode : xmlDoc.createComment;
block.insert(createFn.call(xmlDoc, value));
}
return block.varName;
}
generateHandlerCode(rawEvent: string, handler: string): string {
@@ -547,7 +542,7 @@ export class CodeGenerator {
return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
}
compileTDomNode(ast: ASTDomNode, ctx: Context) {
compileTDomNode(ast: ASTDomNode, ctx: Context): string {
let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
let codeIdx = this.target.code.length;
@@ -734,9 +729,10 @@ export class CodeGenerator {
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
}
}
return block!.varName;
}
compileTEsc(ast: ASTTEsc, ctx: Context) {
compileTEsc(ast: ASTTEsc, ctx: Context): string {
let { block, forceNewBlock } = ctx;
let expr: string;
if (ast.expr === "0") {
@@ -757,9 +753,10 @@ export class CodeGenerator {
const text = xmlDoc.createElement(`block-text-${idx}`);
block.insert(text);
}
return block.varName;
}
compileTOut(ast: ASTTOut, ctx: Context) {
compileTOut(ast: ASTTOut, ctx: Context): string {
let { block } = ctx;
if (block) {
this.insertAnchor(block);
@@ -781,6 +778,7 @@ export class CodeGenerator {
blockStr = `safeOutput(${compileExpr(ast.expr)})`;
}
this.insertBlock(blockStr, block, ctx);
return block.varName;
}
compileTIfBranch(content: AST, block: BlockDescription, ctx: Context) {
@@ -794,7 +792,7 @@ export class CodeGenerator {
this.target.indentLevel--;
}
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode) {
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode): string {
let { block, forceNewBlock } = ctx;
const codeIdx = this.target.code.length;
const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
@@ -837,9 +835,10 @@ export class CodeGenerator {
const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}])`, block!, ctx)!;
}
return block.varName;
}
compileTForeach(ast: ASTTForEach, ctx: Context) {
compileTForeach(ast: ASTTForEach, ctx: Context): string {
let { block } = ctx;
if (block) {
this.insertAnchor(block);
@@ -878,9 +877,9 @@ export class CodeGenerator {
// Throw error on duplicate keys in dev mode
this.helpers.add("OwlError");
this.addLine(
`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`
`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`
);
this.addLine(`keys${block.id}.add(key${this.target.loopLevel});`);
this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
}
let id: string;
if (ast.memo) {
@@ -917,9 +916,10 @@ export class CodeGenerator {
this.addLine(`ctx = ctx.__proto__;`);
}
this.insertBlock("l", block, ctx);
return block.varName;
}
compileTKey(ast: ASTTKey, ctx: Context) {
compileTKey(ast: ASTTKey, ctx: Context): string | null {
const tKeyExpr = generateId("tKey_");
this.define(tKeyExpr, compileExpr(ast.expr));
ctx = createContext(ctx, {
@@ -927,20 +927,22 @@ export class CodeGenerator {
block: ctx.block,
index: ctx.index,
});
this.compileAST(ast.content, ctx);
return this.compileAST(ast.content, ctx);
}
compileMulti(ast: ASTMulti, ctx: Context) {
compileMulti(ast: ASTMulti, ctx: Context): string | null {
let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock;
let codeIdx = this.target.code.length;
if (isNewBlock) {
const n = ast.content.filter((c) => c.type !== ASTType.TSet).length;
let result: string | null = null;
if (n <= 1) {
for (let child of ast.content) {
this.compileAST(child, ctx);
const blockName = this.compileAST(child, ctx);
result = result || blockName;
}
return;
return result;
}
block = this.createBlock(block, "multi", ctx);
}
@@ -980,9 +982,10 @@ export class CodeGenerator {
const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}])`, block!, ctx)!;
}
return block!.varName;
}
compileTCall(ast: ASTTCall, ctx: Context) {
compileTCall(ast: ASTTCall, ctx: Context): string {
let { block, forceNewBlock } = ctx;
let ctxVar = ctx.ctxVar || "ctx";
if (ast.context) {
@@ -993,12 +996,11 @@ export class CodeGenerator {
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
this.addLine(`${ctxVar}[isBoundary] = 1;`);
this.helpers.add("isBoundary");
const nextId = BlockDescription.nextBlockId;
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
if (nextId !== BlockDescription.nextBlockId) {
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
if (bl) {
this.helpers.add("zero");
this.addLine(`${ctxVar}[zero] = b${nextId};`);
this.addLine(`${ctxVar}[zero] = ${bl};`);
}
}
const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -1032,9 +1034,10 @@ export class CodeGenerator {
if (ast.body && !ctx.isLast) {
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
}
return block.varName;
}
compileTCallBlock(ast: ASTTCallBlock, ctx: Context) {
compileTCallBlock(ast: ASTTCallBlock, ctx: Context): string {
let { block, forceNewBlock } = ctx;
if (block) {
if (!forceNewBlock) {
@@ -1043,9 +1046,10 @@ export class CodeGenerator {
}
block = this.createBlock(block, "multi", ctx);
this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
return block.varName;
}
compileTSet(ast: ASTTSet, ctx: Context) {
compileTSet(ast: ASTTSet, ctx: Context): null {
this.target.shouldProtectScope = true;
this.helpers.add("isBoundary").add("withDefault");
const expr = ast.value ? compileExpr(ast.value || "") : "null";
@@ -1053,7 +1057,8 @@ export class CodeGenerator {
this.helpers.add("LazyValue");
const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
const name = this.compileInNewTarget("value", bodyAst, ctx);
let value = `new LazyValue(${name}, ctx, this, node)`;
let key = this.target.currentKey(ctx);
let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
} else {
@@ -1070,6 +1075,7 @@ export class CodeGenerator {
this.helpers.add("setContextValue");
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
}
return null;
}
generateComponentKey() {
@@ -1121,7 +1127,7 @@ export class CodeGenerator {
return propString;
}
compileComponent(ast: ASTComponent, ctx: Context) {
compileComponent(ast: ASTComponent, ctx: Context): string {
let { block } = ctx;
// props
const hasSlotsProp = "slots" in (ast.props || {});
@@ -1187,7 +1193,7 @@ export class CodeGenerator {
}
if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, this);`);
}
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1221,6 +1227,7 @@ export class CodeGenerator {
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockExpr, block, ctx);
return block.varName;
}
wrapWithEventCatcher(expr: string, on: EventHandlers): string {
@@ -1239,34 +1246,43 @@ export class CodeGenerator {
return `${name}(${expr}, [${handlers.join(",")}])`;
}
compileTSlot(ast: ASTSlot, ctx: Context) {
compileTSlot(ast: ASTSlot, ctx: Context): string {
this.helpers.add("callSlot");
let { block } = ctx;
let blockString: string;
let slotName;
let dynamic = false;
let isMultiple = false;
if (ast.name.match(INTERP_REGEXP)) {
dynamic = true;
isMultiple = true;
slotName = interpolate(ast.name);
} else {
slotName = "'" + ast.name + "'";
isMultiple = isMultiple || this.slotNames.has(ast.name);
this.slotNames.add(ast.name);
}
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
if (ast.attrs) {
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 scope = this.getPropString(props, dynProps);
if (ast.defaultContent) {
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 {
if (dynamic) {
let name = generateId("slot");
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 {
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
}
}
// event handling
@@ -1279,14 +1295,16 @@ export class CodeGenerator {
}
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
}
compileTTranslation(ast: ASTTranslation, ctx: Context) {
compileTTranslation(ast: ASTTranslation, ctx: Context): string | null {
if (ast.content) {
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
}
return null;
}
compileTPortal(ast: ASTTPortal, ctx: Context) {
compileTPortal(ast: ASTTPortal, ctx: Context): string {
if (!this.staticDefs.find((d) => d.id === "Portal")) {
this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
}
@@ -1313,5 +1331,6 @@ export class CodeGenerator {
}
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
}
}
+3 -2
View File
@@ -781,8 +781,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
// default slot
const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) {
slots = slots || {};
slots = slots || {};
// t-set-slot="default" has priority over content
if (defaultContent && !slots.default) {
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
}
}
+6 -3
View File
@@ -6,6 +6,7 @@ import { Scheduler } from "./scheduler";
import { validateProps } from "./template_helpers";
import { TemplateSet, TemplateSetConfig } from "./template_set";
import { validateTarget } from "./utils";
import { handleError } from "./error_handling";
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
@@ -94,9 +95,7 @@ export class App<
nodeErrorHandlers.set(node, handlers);
}
handlers.unshift((e) => {
if (isResolved) {
console.error(e);
} else {
if (!isResolved) {
reject(e);
}
throw e;
@@ -169,6 +168,10 @@ export class App<
return node;
};
}
handleError(...args: Parameters<typeof handleError>) {
return handleError(...args);
}
}
export async function mount<
+15 -3
View File
@@ -157,6 +157,15 @@ function buildTree(
: document.createElement(tagName);
}
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++) {
const attrName = attrs[i].name;
const attrValue = attrs[i].value;
@@ -508,9 +517,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
return this.el!;
}
moveBefore(other: Block | null, afterNode: Node | null) {
const target = other ? other.el! : afterNode;
nodeInsertBefore.call(this.parentEl, this.el!, target);
moveBeforeDOMNode(node: Node | null) {
nodeInsertBefore.call(this.parentEl, this.el!, node);
}
moveBeforeVNode(other: Block | null, afterNode: Node | null) {
nodeInsertBefore.call(this.parentEl, this.el!, other ? other.el! : afterNode);
}
toString() {
+11 -2
View File
@@ -56,8 +56,17 @@ export function createCatcher(eventsSpec: EventsSpec): Catcher {
}
}
moveBefore(other: VCatcher | null, afterNode: Node | null) {
this.child.moveBefore(other ? other.child : null, afterNode);
moveBeforeDOMNode(node: Node | null) {
this.child.moveBeforeDOMNode(node);
this.parentEl!.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);
}
+2 -2
View File
@@ -27,8 +27,8 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
}
function listener(ev: Event) {
const currentTarget = ev.currentTarget;
if (!currentTarget || !document.contains(currentTarget as HTMLElement)) return;
const currentTarget = ev.currentTarget as HTMLElement;
if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) return;
const data = (currentTarget as any)[eventKey];
if (!data) return;
config.mainEventHandler(data, ev, currentTarget);
+7 -3
View File
@@ -29,14 +29,18 @@ class VHtml {
}
}
moveBefore(other: VHtml | null, afterNode: Node | null) {
const target = other ? other.content[0] : afterNode;
moveBeforeDOMNode(node: Node | null) {
const parent = this.parentEl;
for (let elem of this.content) {
nodeInsertBefore.call(parent, elem, target);
nodeInsertBefore.call(parent, elem, node);
}
}
moveBeforeVNode(other: VHtml | null, afterNode: Node | null) {
const target = other ? other.content[0] : afterNode;
this.moveBeforeDOMNode(target);
}
patch(other: VHtml) {
if (this === other) {
return;
+2 -1
View File
@@ -10,7 +10,8 @@ export { createCatcher } from "./event_catcher";
export interface VNode<T = any> {
mount(parent: HTMLElement, afterNode: Node | null): void;
moveBefore(other: T | null, afterNode: Node | null): void;
moveBeforeDOMNode(node: Node | null): void;
moveBeforeVNode(other: T | null, afterNode: Node | null): void;
patch(other: T, withBeforeRemove: boolean): void;
beforeRemove(): void;
remove(): void;
+11 -3
View File
@@ -38,14 +38,22 @@ class VList {
this.parentEl = parent;
}
moveBefore(other: VList | null, afterNode: Node | null) {
moveBeforeDOMNode(node: Node | null) {
const children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].moveBeforeDOMNode(node);
}
this.parentEl!.insertBefore(this.anchor!, node);
}
moveBeforeVNode(other: VList | null, afterNode: Node | null) {
if (other) {
const next = other!.children[0];
afterNode = (next ? next.firstNode() : other!.anchor) || null;
}
const children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].moveBefore(null, afterNode);
children[i].moveBeforeVNode(null, afterNode);
}
this.parentEl!.insertBefore(this.anchor!, afterNode);
}
@@ -66,7 +74,7 @@ class VList {
patch: cPatch,
remove: cRemove,
beforeRemove,
moveBefore: cMoveBefore,
moveBeforeVNode: cMoveBefore,
firstNode: cFirstNode,
} = proto;
+17 -2
View File
@@ -38,7 +38,22 @@ export class VMulti {
this.parentEl = parent;
}
moveBefore(other: VMulti | null, afterNode: Node | null) {
moveBeforeDOMNode(node: Node | null) {
const children = this.children;
const parent = this.parentEl;
const anchors = this.anchors;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
if (child) {
child.moveBeforeDOMNode(node);
} else {
const anchor = anchors![i];
nodeInsertBefore.call(parent, anchor, node);
}
}
}
moveBeforeVNode(other: VMulti | null, afterNode: Node | null) {
if (other) {
const next = other!.children[0];
afterNode = (next ? next.firstNode() : other!.anchors![0]) || null;
@@ -49,7 +64,7 @@ export class VMulti {
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
if (child) {
child.moveBefore(null, afterNode);
child.moveBeforeVNode(null, afterNode);
} else {
const anchor = anchors![i];
nodeInsertBefore.call(parent, anchor, afterNode);
+6 -3
View File
@@ -23,9 +23,12 @@ abstract class VSimpleNode {
this.el = node;
}
moveBefore(other: VText | null, afterNode: Node | null) {
const target = other ? other.el! : afterNode;
nodeInsertBefore.call(this.parentEl, this.el!, target);
moveBeforeDOMNode(node: Node | null) {
nodeInsertBefore.call(this.parentEl, this.el!, node);
}
moveBeforeVNode(other: VText | null, afterNode: Node | null) {
nodeInsertBefore.call(this.parentEl, this.el!, other ? other.el! : afterNode);
}
beforeRemove() {}
+6 -2
View File
@@ -20,8 +20,12 @@ class VToggler {
this.child.mount(parent, afterNode);
}
moveBefore(other: VToggler | null, afterNode: Node | null) {
this.child.moveBefore(other ? other.child : null, afterNode);
moveBeforeDOMNode(node: Node | null) {
this.child.moveBeforeDOMNode(node);
}
moveBeforeVNode(other: VToggler | null, afterNode: Node | null) {
this.moveBeforeDOMNode((other && other.firstNode()) || afterNode);
}
patch(other: VToggler, withBeforeRemove: boolean) {
+9 -5
View File
@@ -1,7 +1,7 @@
import type { App, Env } from "./app";
import { BDom, VNode } from "./blockdom";
import { Component, ComponentConstructor, Props } from "./component";
import { fibersInError, handleError, OwlError } from "./error_handling";
import { fibersInError, OwlError } from "./error_handling";
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
import {
clearReactivesForCallback,
@@ -141,7 +141,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
try {
await Promise.all(this.willStart.map((f) => f.call(component)));
} catch (e) {
handleError({ node: this, error: e });
this.app.handleError({ node: this, error: e });
return;
}
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);
}
} catch (e) {
handleError({ error: e, node: this });
this.app.handleError({ error: e, node: this });
}
}
this.status = STATUS.DESTROYED;
@@ -306,8 +306,12 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
this.fiber = null;
}
moveBefore(other: ComponentNode | null, afterNode: Node | null) {
this.bdom!.moveBefore(other ? other.bdom : null, afterNode);
moveBeforeDOMNode(node: Node | null): void {
this.bdom!.moveBeforeDOMNode(node);
}
moveBeforeVNode(other: ComponentNode<P, E> | null, afterNode: Node | null) {
this.bdom!.moveBeforeVNode(other ? other.bdom : null, afterNode);
}
patch() {
+1
View File
@@ -71,5 +71,6 @@ export function handleError(params: ErrorParams) {
} catch (e) {
console.error(e);
}
throw error;
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
import { BDom, mount } from "./blockdom";
import type { ComponentNode } from "./component_node";
import { fibersInError, handleError, OwlError } from "./error_handling";
import { fibersInError, OwlError } from "./error_handling";
import { STATUS } from "./status";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
@@ -130,7 +130,7 @@ export class Fiber {
(this.bdom as any) = true;
this.bdom = node.renderFn();
} catch (e) {
handleError({ node, error: e });
node.app.handleError({ node, error: e });
}
root.setCounter(root.counter - 1);
}
@@ -195,7 +195,7 @@ export class RootFiber extends Fiber {
}
} catch (e) {
this.locked = false;
handleError({ fiber: current || this, error: e });
node.app.handleError({ fiber: current || this, error: e });
}
}
@@ -259,7 +259,7 @@ export class MountFiber extends RootFiber {
}
}
} catch (e) {
handleError({ fiber: current as Fiber, error: e });
this.node.app.handleError({ fiber: current as Fiber, error: e });
}
}
}
+36 -35
View File
@@ -1,4 +1,4 @@
import { onWillUnmount } from "./lifecycle_hooks";
import { onMounted, onWillUnmount } from "./lifecycle_hooks";
import { BDom, text, VNode } from "./blockdom";
import { Component } from "./component";
import { OwlError } from "./error_handling";
@@ -6,60 +6,51 @@ import { OwlError } from "./error_handling";
const VText: any = text("").constructor;
class VPortal extends VText implements Partial<VNode<VPortal>> {
// selector: string;
realBDom: BDom | null;
content: BDom | null;
selector: string;
target: HTMLElement | null = null;
constructor(selector: string, realBDom: BDom) {
constructor(selector: string, content: BDom) {
super("");
this.selector = selector;
this.realBDom = realBDom;
this.content = content;
}
mount(parent: HTMLElement, anchor: ChildNode) {
super.mount(parent, anchor);
this.target = document.querySelector(this.selector) as any;
if (!this.target) {
let el: any = this.el;
while (el && el.parentElement instanceof HTMLElement) {
el = el.parentElement;
}
this.target = el && el.querySelector(this.selector);
if (!this.target) {
throw new OwlError("invalid portal target");
}
if (this.target) {
this.content!.mount(this.target!, null);
}
this.realBDom!.mount(this.target!, null);
}
beforeRemove() {
this.realBDom!.beforeRemove();
}
remove() {
if (this.realBDom) {
super.remove();
this.realBDom!.remove();
this.realBDom = null;
// this.target not being null means content is mounted
if (this.target) {
this.content!.beforeRemove();
this.content!.remove();
}
this.content = null;
}
patch(other: VPortal) {
super.patch(other);
if (this.realBDom) {
this.realBDom.patch(other.realBDom!, true);
if (this.content) {
this.content.patch(other.content!, true);
} else {
this.realBDom = other.realBDom;
this.realBDom!.mount(this.target!, null);
this.content = other.content;
this.content!.mount(this.target!, null);
}
}
}
/**
* <t t-slot="default"/>
* kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
*/
export function portalTemplate(app: any, bdom: any, helpers: any) {
let { callSlot } = helpers;
return function template(ctx: any, node: any, key = "") {
return callSlot(ctx, node, key, "default", false, null);
return function template(ctx: any, node: any, key = ""): any {
return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null));
};
}
@@ -73,13 +64,23 @@ export class Portal extends Component {
};
setup() {
const node = this.__owl__;
const renderFn = node.renderFn;
node.renderFn = () => new VPortal(this.props.target, renderFn());
onWillUnmount(() => {
if (node.bdom) {
node.bdom.remove();
const node: any = this.__owl__;
onMounted(() => {
const portal: VPortal = node.bdom;
if (!portal.target) {
portal.target = document.querySelector(this.props.target);
if (portal.target) {
portal.content!.mount(portal.target, null);
} else {
throw new OwlError("invalid portal target");
}
}
});
onWillUnmount(() => {
const portal: VPortal = node.bdom;
portal.beforeRemove();
});
}
}
+8 -5
View File
@@ -111,15 +111,18 @@ class LazyValue {
ctx: any;
component: any;
node: any;
constructor(fn: any, ctx: any, component: any, node: any) {
key: any;
constructor(fn: any, ctx: any, component: any, node: any, key: any) {
this.fn = fn;
this.ctx = capture(ctx);
this.component = component;
this.node = node;
this.key = key;
}
evaluate(): any {
return this.fn.call(this.component, this.ctx, this.node);
return this.fn.call(this.component, this.ctx, this.node, this.key);
}
toString() {
@@ -207,11 +210,11 @@ function multiRefSetter(refs: RefMap, name: string): RefSetter {
* visit recursively the props and all the children to check if they are valid.
* This is why it is only done in 'dev' mode.
*/
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, comp?: any) {
const ComponentClass =
typeof name !== "string"
? name
: (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
: (comp.constructor.components[name] as ComponentConstructor<P> | undefined);
if (!ComponentClass) {
// this is an error, wrong component. We silently return here instead so the
@@ -221,7 +224,7 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
const schema = ComponentClass.props;
if (!schema) {
if (parent.__owl__.app.warnIfNoStaticProps) {
if (comp.__owl__.app.warnIfNoStaticProps) {
console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
}
return;
@@ -334,6 +334,57 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -126,7 +126,7 @@ exports[`t-esc t-esc is escaped 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`var\`] = new LazyValue(value1, ctx, this, node);
ctx[\`var\`] = new LazyValue(value1, ctx, this, node, key);
let txt1 = ctx['var'];
return block1([txt1]);
}
@@ -161,7 +161,7 @@ exports[`t-out t-out bdom 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`var\`] = new LazyValue(value1, ctx, this, node);
ctx[\`var\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = safeOutput(ctx['var']);
return block1([], [b3]);
}
@@ -310,7 +310,7 @@ exports[`t-out t-out switch markup on bdom 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
let b3,b5;
ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node);
ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node, key);
if (ctx['hasBdom']) {
const b4 = safeOutput(ctx['bdom']);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`value\`] = new LazyValue(value1, ctx, this, node);
ctx[\`value\`] = new LazyValue(value1, ctx, this, node, key);
return text(ctx['value']);
}
}"
@@ -142,7 +142,7 @@ exports[`t-set set from body lookup 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node);
ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node, key);
let txt1 = ctx['stuff'];
return block1([txt1]);
}
@@ -206,7 +206,7 @@ exports[`t-set t-set body is evaluated immediately 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = new LazyValue(value1, ctx, this, node);
ctx[\`v2\`] = new LazyValue(value1, ctx, this, node, key);
setContextValue(ctx, \\"v1\\", 'after');
const b3 = safeOutput(ctx['v2']);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node);
ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node, key);
let txt1 = ctx['setvar'];
return block1([txt1]);
}
@@ -497,7 +497,7 @@ exports[`t-set t-set with t-value (falsy) and body 1`] = `
ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", false);
setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node));
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node, key));
setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", true);
const b3 = safeOutput(ctx['v2']);
@@ -525,7 +525,7 @@ exports[`t-set t-set with t-value (truthy) and body 1`] = `
ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", 'Truthy');
setContextValue(ctx, \\"v1\\", 'before');
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node));
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node, key));
setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", false);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node));
ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node, key));
let txt1 = ctx['value'];
return block1([txt1]);
}
+17
View File
@@ -479,4 +479,21 @@ describe("t-call (template calling)", () => {
"<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 = \\"\\") {
const props1 = {};
helpers.validateProps(\`SomeMispelledComponent\`, props1, ctx);
helpers.validateProps(\`SomeMispelledComponent\`, props1, this);
return comp1(props1, key + \`__1\`, node, this, null);
}
}"
@@ -58,6 +58,20 @@ 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`] = `
"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 = \\"\\") {
const props1 = {prop: ctx['state'].prop};
helpers.validateProps(\`Child\`, props1, ctx);
helpers.validateProps(\`Child\`, props1, this);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`abc\`] = new LazyValue(value1, ctx, this, node);
ctx[\`abc\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = comp1({val: ctx['abc']}, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
helpers.validateProps(\`Child\`, props1, this);
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 = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -61,7 +61,7 @@ exports[`default props can set default values 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -121,7 +121,7 @@ exports[`props validation can specify that additional props are allowed (array)
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, ctx);
helpers.validateProps(\`Child\`, props1, this);
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 = \\"\\") {
const props1 = {message: 'm',otherProp: 'o'};
helpers.validateProps(\`Child\`, props1, ctx);
helpers.validateProps(\`Child\`, props1, this);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -207,7 +207,7 @@ exports[`props validation can validate a prop with multiple types 3`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -237,7 +237,7 @@ exports[`props validation can validate a prop with multiple types 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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 = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -455,7 +455,7 @@ exports[`props validation can validate an object with simple shape 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -485,7 +485,7 @@ exports[`props validation can validate an object with simple shape 3`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -502,7 +502,7 @@ exports[`props validation can validate an object with simple shape 4`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -519,7 +519,7 @@ exports[`props validation can validate an object with simple shape 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -536,7 +536,7 @@ exports[`props validation can validate an optional props 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -566,7 +566,7 @@ exports[`props validation can validate an optional props 3`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -596,7 +596,7 @@ exports[`props validation can validate an optional props 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -613,7 +613,7 @@ exports[`props validation can validate recursively complicated prop def 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -643,7 +643,7 @@ exports[`props validation can validate recursively complicated prop def 3`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -673,13 +673,47 @@ exports[`props validation can validate recursively complicated prop def 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -690,7 +724,7 @@ exports[`props validation default values are applied before validating props at
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -721,7 +755,7 @@ exports[`props validation missing required boolean prop causes an error 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -738,7 +772,7 @@ exports[`props validation mix of optional and mandatory 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`Child\`, props1, ctx);
helpers.validateProps(\`Child\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -755,7 +789,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 1};
helpers.validateProps(\`Child\`, props1, ctx);
helpers.validateProps(\`Child\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -786,7 +820,7 @@ exports[`props validation props are validated whenever component is updated 1`]
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['state'].p};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -817,7 +851,7 @@ exports[`props validation props: list of strings 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -834,7 +868,7 @@ exports[`props validation validate simple types 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -851,7 +885,7 @@ exports[`props validation validate simple types 2`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -881,7 +915,7 @@ exports[`props validation validate simple types 4`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -898,7 +932,7 @@ exports[`props validation validate simple types 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -915,7 +949,7 @@ exports[`props validation validate simple types 6`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -945,7 +979,7 @@ exports[`props validation validate simple types 8`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -962,7 +996,7 @@ exports[`props validation validate simple types 9`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -979,7 +1013,7 @@ exports[`props validation validate simple types 10`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1009,7 +1043,7 @@ exports[`props validation validate simple types 12`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1026,7 +1060,7 @@ exports[`props validation validate simple types 13`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1043,7 +1077,7 @@ exports[`props validation validate simple types 14`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1073,7 +1107,7 @@ exports[`props validation validate simple types 16`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1090,7 +1124,7 @@ exports[`props validation validate simple types 17`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1107,7 +1141,7 @@ exports[`props validation validate simple types 18`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1137,7 +1171,7 @@ exports[`props validation validate simple types 20`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1154,7 +1188,7 @@ exports[`props validation validate simple types 21`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1171,7 +1205,7 @@ exports[`props validation validate simple types 22`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1201,7 +1235,7 @@ exports[`props validation validate simple types 24`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1218,7 +1252,7 @@ exports[`props validation validate simple types, alternate form 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1235,7 +1269,7 @@ exports[`props validation validate simple types, alternate form 2`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1265,7 +1299,7 @@ exports[`props validation validate simple types, alternate form 4`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1282,7 +1316,7 @@ exports[`props validation validate simple types, alternate form 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1299,7 +1333,7 @@ exports[`props validation validate simple types, alternate form 6`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1329,7 +1363,7 @@ exports[`props validation validate simple types, alternate form 8`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1346,7 +1380,7 @@ exports[`props validation validate simple types, alternate form 9`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1363,7 +1397,7 @@ exports[`props validation validate simple types, alternate form 10`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1393,7 +1427,7 @@ exports[`props validation validate simple types, alternate form 12`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1410,7 +1444,7 @@ exports[`props validation validate simple types, alternate form 13`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1427,7 +1461,7 @@ exports[`props validation validate simple types, alternate form 14`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1457,7 +1491,7 @@ exports[`props validation validate simple types, alternate form 16`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1474,7 +1508,7 @@ exports[`props validation validate simple types, alternate form 17`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1491,7 +1525,7 @@ exports[`props validation validate simple types, alternate form 18`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1521,7 +1555,7 @@ exports[`props validation validate simple types, alternate form 20`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1538,7 +1572,7 @@ exports[`props validation validate simple types, alternate form 21`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1555,7 +1589,7 @@ exports[`props validation validate simple types, alternate form 22`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1585,7 +1619,7 @@ exports[`props validation validate simple types, alternate form 24`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1602,7 +1636,7 @@ exports[`props validation validation is only done in dev mode 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, ctx);
helpers.validateProps(\`SubComp\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -604,6 +604,64 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -645,7 +703,7 @@ exports[`slots dynamic t-slot call 2`] = `
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['toggle'], ctx];
const slot1 = (ctx['current'].slot);
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {}));
const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {}));
return block1([hdlr1], [b2]);
}
}"
@@ -695,7 +753,7 @@ exports[`slots dynamic t-slot call with default 2`] = `
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['toggle'], ctx];
const b3 = callSlot(ctx, node, key, (ctx['current'].slot), true, {}, defaultContent1);
const b3 = callSlot(ctx, node, key + \`__1\`, (ctx['current'].slot), true, {}, defaultContent1);
return block1([hdlr1], [b3]);
}
}"
@@ -726,7 +784,7 @@ exports[`slots fun: two calls to the same slot 2`] = `
return function template(ctx, node, key = \\"\\") {
const b2 = callSlot(ctx, node, key, 'default', false, {});
const b3 = callSlot(ctx, node, key, 'default', false, {});
const b3 = callSlot(ctx, node, key + \`__1\`, 'default', false, {});
return multi([b2, b3]);
}
}"
@@ -1527,7 +1585,7 @@ exports[`slots simple dynamic slot with slot scope 2`] = `
return function template(ctx, node, key = \\"\\") {
const slot1 = ('slotName');
const b2 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {bool: ctx['state'].bool}));
const b2 = toggler(slot1, callSlot(ctx, node, key + \`__1\`, slot1, true, {bool: ctx['state'].bool}));
return block1([], [b2]);
}
}"
@@ -1854,7 +1912,7 @@ exports[`slots slot content has different key from other content -- dynamic slot
return function template(ctx, node, key = \\"\\") {
const b2 = comp1({parent: 'SlotDisplay'}, key + \`__1\`, node, this, null);
const slot1 = (ctx['slotName']);
const b3 = toggler(slot1, callSlot(ctx, node, key, slot1, true, {}));
const b3 = toggler(slot1, callSlot(ctx, node, key + \`__2\`, slot1, true, {}));
return multi([b2, b3]);
}
}"
@@ -1995,6 +2053,118 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -2665,6 +2835,36 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -444,6 +444,51 @@ 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`] = `
"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++) {
ctx[\`item\`] = v_block1[i1];
const key1 = 'child';
if (keys1.has(key1)) { throw new OwlError(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(key1);
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, ctx);
helpers.validateProps(\`Child\`, props1, this);
c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
@@ -75,6 +75,42 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -113,6 +149,58 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -0,0 +1,39 @@
// 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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = text(\` in slot \`);
const b4 = safeOutput(ctx['v']);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b5 = text(\` tea \`);
const b6 = safeOutput(ctx['v']);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
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 = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = safeOutput(ctx['v']);
return block1([], [b3]);
}
@@ -377,7 +377,7 @@ exports[`t-set t-set with something in body 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
const b3 = safeOutput(ctx['v']);
return block1([], [b3]);
}
+14 -7
View File
@@ -1,5 +1,12 @@
import { App, Component, mount, status, toRaw, useState, xml } from "../../src";
import { elem, makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
import {
elem,
makeTestFixture,
nextAppError,
nextTick,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
import { markup } from "../../src/runtime/utils";
let fixture: HTMLElement;
@@ -208,14 +215,14 @@ describe("basics", () => {
static template = xml`<div/>`;
}
let error: Error;
const prom = mount(Test, fixture);
const app = new App(Test);
const prom = app.mount(fixture);
await Promise.resolve();
fixture.remove();
try {
await prom;
} catch (e) {
error = e as Error;
}
prom.catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"Cannot mount a component on a detached dom node"
);
expect(error!).toBeDefined();
expect(error!.message).toBe("Cannot mount a component on a detached dom node");
expect(console.warn).toBeCalledTimes(1);
+117 -127
View File
@@ -1,4 +1,4 @@
import { Component, mount, onWillDestroy } from "../../src";
import { App, Component, mount, onWillDestroy } from "../../src";
import {
onError,
onMounted,
@@ -18,6 +18,7 @@ import {
nextMicroTick,
snapshotEverything,
useLogLifecycle,
nextAppError,
} from "../helpers";
import { OwlError } from "../../src/runtime/error_handling";
@@ -59,9 +60,10 @@ describe("basics", () => {
parent.state.flag = true;
parent.render();
await nextTick();
await expect(nextAppError(parent.__owl__.app)).resolves.toThrow(
"An error occured in the owl lifecycle"
);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
@@ -71,12 +73,13 @@ describe("basics", () => {
static template = xml`<SomeMispelledComponent />`;
static components = { SomeComponent };
}
const app = new App(Parent);
let error: Error;
try {
await mount(Parent, fixture);
} catch (e) {
error = e as Error;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
'Cannot find the definition of component "SomeMispelledComponent"'
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0);
@@ -90,12 +93,13 @@ describe("basics", () => {
static template = xml`<SomeMispelledComponent />`;
static components = { SomeComponent };
}
const app = new App(Parent, { test: true });
let error: Error;
try {
await mount(Parent, fixture, { test: true });
} catch (e) {
error = e as Error;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
'Cannot find the definition of component "SomeMispelledComponent"'
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0);
@@ -109,13 +113,13 @@ describe("basics", () => {
static template = xml`<SomeComponent />`;
static components = { SomeComponent: notAComponentConstructor };
}
const app = new App(Parent as typeof Component);
let error: Error;
try {
// @ts-expect-error
await mount(Parent, fixture);
} catch (e) {
error = e as Error;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
'"SomeComponent" is not a Component. It must inherit from the Component class'
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
'"SomeComponent" is not a Component. It must inherit from the Component class'
@@ -156,16 +160,15 @@ describe("basics", () => {
describe("errors and promises", () => {
test("a rendering error will reject the mount promise", async () => {
// we do not catch error in willPatch anymore
class App extends Component {
class Root extends Component {
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
}
const app = new App(Root);
let error: OwlError;
try {
await mount(App, fixture);
} catch (e) {
error = e as OwlError;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause).toBeDefined();
const regexp =
@@ -176,7 +179,7 @@ describe("errors and promises", () => {
});
test("an error in mounted call will reject the mount promise", async () => {
class App extends Component {
class Root extends Component {
static template = xml`<div>abc</div>`;
setup() {
onMounted(() => {
@@ -185,12 +188,11 @@ describe("errors and promises", () => {
}
}
const app = new App(Root);
let error: OwlError;
try {
await mount(App, fixture);
} catch (e) {
error = e as OwlError;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause).toBeDefined();
expect(error!.cause.message).toBe("boom");
@@ -200,7 +202,7 @@ describe("errors and promises", () => {
});
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
class App extends Component {
class Root extends Component {
static template = xml`<div>abc</div>`;
setup() {
onMounted(() => {
@@ -209,14 +211,13 @@ describe("errors and promises", () => {
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
const app = new App(Root, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
await mountProm;
expect(error!).toBeDefined();
expect(error!.stack).toContain("App.setup");
expect(error!.stack).toContain("Root.setup");
expect(error!.stack).toContain("error_handling.test.ts");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
@@ -224,7 +225,7 @@ describe("errors and promises", () => {
});
test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
class App extends Component {
class Root extends Component {
static template = xml`<div>abc</div>`;
setup() {
onWillRender(() => {
@@ -236,12 +237,11 @@ describe("errors and promises", () => {
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
const app = new App(Root, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillRender");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
`The following error occurred in onWillRender: "boom in onWillRender"`
@@ -278,12 +278,11 @@ describe("errors and promises", () => {
}
}
let error: any;
try {
await mount(Root, fixture, { test: true });
} catch (e) {
error = e;
}
const app = new App(Root, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
`The following error occurred in onWillStart: "boom in onWillStart"`
@@ -342,17 +341,16 @@ describe("errors and promises", () => {
class Child extends Component {
static template = xml`<div><t t-esc="this.will.crash"/></div>`;
}
class App extends Component {
class Parent extends Component {
static template = xml`<div><Child/></div>`;
static components = { Child };
}
const app = new App(Parent);
let error: OwlError;
try {
await mount(App, fixture);
} catch (e) {
error = e as OwlError;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause).toBeDefined();
const regexp =
@@ -394,12 +392,11 @@ describe("errors and promises", () => {
static components = { Child };
}
const app = new App(Parent);
let error: OwlError;
try {
await mount(Parent, fixture);
} catch (e) {
error = e as OwlError;
}
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause).toBeDefined();
const regexp =
@@ -425,13 +422,12 @@ describe("errors and promises", () => {
}
}
try {
await mount(Example, fixture, { test: true });
} catch (e) {
expect((e as Error).message).toBe(
`The following error occurred in onMounted: "Error in mounted"`
);
}
const app = new App(Example, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
await mountProm;
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
// the onWillUnmount hook to be called and to fail
expect(mockConsoleError).toBeCalledTimes(1);
@@ -448,9 +444,10 @@ describe("errors and promises", () => {
root.state = "boom";
root.render();
await nextTick();
await expect(nextAppError(root.__owl__.app)).resolves.toThrow(
"error occured in the owl lifecycle"
);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
});
@@ -500,13 +497,12 @@ describe("can catch errors", () => {
});
}
}
let e: Error;
try {
await mount(Root, fixture, { test: true });
} catch (error) {
e = error as Error;
}
expect(e!.message).toBe(
const app = new App(Root, { test: true });
let error: OwlError;
const crashProm = expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
await app.mount(fixture).catch((e: Error) => (error = e));
await crashProm;
expect(error!.message).toBe(
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
);
});
@@ -523,14 +519,13 @@ describe("can catch errors", () => {
});
}
}
let e: OwlError;
try {
await mount(Root, fixture, { test: true });
} catch (error) {
e = error as OwlError;
}
expect(e!.message).toBe(`The following error occurred in onMounted: "test error"`);
expect(e!.cause).toBe(err);
const app = new App(Root, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onMounted");
await mountProm;
expect(error!.message).toBe(`The following error occurred in onMounted: "test error"`);
expect(error!.cause).toBe(err);
});
test("Errors in owl lifecycle are wrapped in dev mode: async hook", async () => {
@@ -546,14 +541,13 @@ describe("can catch errors", () => {
});
}
}
let e: OwlError;
try {
await mount(Root, fixture, { test: true });
} catch (error) {
e = error as OwlError;
}
expect(e!.message).toBe(`The following error occurred in onWillStart: "test error"`);
expect(e!.cause).toBe(err);
const app = new App(Root, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occurred in onWillStart");
await mountProm;
expect(error!.message).toBe(`The following error occurred in onWillStart: "test error"`);
expect(error!.cause).toBe(err);
});
test("Errors in owl lifecycle are wrapped outside dev mode: sync hook", async () => {
@@ -568,16 +562,15 @@ describe("can catch errors", () => {
});
}
}
let e: OwlError;
try {
await mount(Root, fixture);
} catch (error) {
e = error as OwlError;
}
expect(e!.message).toBe(
const app = new App(Root);
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)`
);
expect(e!.cause).toBe(err);
expect(error!.cause).toBe(err);
});
test("Errors in owl lifecycle are wrapped out of dev mode: async hook", async () => {
@@ -593,16 +586,15 @@ describe("can catch errors", () => {
});
}
}
let e: OwlError;
try {
await mount(Root, fixture);
} catch (error) {
e = error as OwlError;
}
expect(e!.message).toBe(
const app = new App(Root);
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)`
);
expect(e!.cause).toBe(err);
expect(error!.cause).toBe(err);
});
test("Thrown values that are not errors are wrapped in dev mode", async () => {
@@ -616,16 +608,15 @@ describe("can catch errors", () => {
});
}
}
let e: OwlError;
try {
await mount(Root, fixture, { test: true });
} catch (error) {
e = error as OwlError;
}
expect(e!.message).toBe(
const app = new App(Root, { test: true });
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("not an Error was thrown in onMounted");
await mountProm;
expect(error!.message).toBe(
`Something that is not an Error was thrown in onMounted (see this Error's "cause" property)`
);
expect(e!.cause).toBe("This is not an error");
expect(error!.cause).toBe("This is not an error");
});
test("Thrown values that are not errors are wrapped outside dev mode", async () => {
@@ -639,16 +630,15 @@ describe("can catch errors", () => {
});
}
}
let e: OwlError;
try {
await mount(Root, fixture);
} catch (error) {
e = error as OwlError;
}
expect(e!.message).toBe(
const app = new App(Root);
let error: OwlError;
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!.message).toBe(
`An error occured in the owl lifecycle (see this Error's "cause" property)`
);
expect(e!.cause).toBe("This is not an error");
expect(error!.cause).toBe("This is not an error");
});
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
+18
View File
@@ -171,4 +171,22 @@ describe("event handling", () => {
// input is removed when component is destroyed => nothing should happen
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);
});
});
+15 -6
View File
@@ -17,8 +17,16 @@ import {
useChildSubEnv,
useSubEnv,
xml,
OwlError,
} from "../../src/index";
import { elem, logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import {
elem,
logStep,
makeTestFixture,
nextAppError,
nextTick,
snapshotEverything,
} from "../helpers";
let fixture: HTMLElement;
@@ -650,11 +658,12 @@ describe("hooks", () => {
}
}
try {
await mount(MyComponent, fixture);
} catch (e: any) {
expect(e.cause.message).toBe("Intentional error");
}
let error: OwlError;
const app = new App(MyComponent);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!.cause.message).toBe("Intentional error");
// no console.error because the error has been caught in this test
expect(console.error).toHaveBeenCalledTimes(0);
console.error = originalconsoleError;
+124 -111
View File
@@ -1,6 +1,6 @@
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { Component, onError, xml, mount } from "../../src";
import { DEV_MSG } from "../../src/runtime/app";
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { Component, onError, xml, mount, OwlError } from "../../src";
import { App, DEV_MSG } from "../../src/runtime/app";
import { validateProps } from "../../src/runtime/template_helpers";
import { Schema } from "../../src/runtime/validation";
@@ -48,13 +48,14 @@ describe("props validation", () => {
static components = { SubComp };
static template = xml`<div><SubComp /></div>`;
}
let error: Error | undefined;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
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 'SubComp': 'message' is missing"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
error = undefined;
@@ -77,12 +78,13 @@ describe("props validation", () => {
static template = xml`<div><SubComp /></div>`;
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
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 'SubComp': 'message' is missing"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'message' is missing");
});
@@ -126,14 +128,12 @@ describe("props validation", () => {
};
(Parent as any).components = { SubComp };
let error: Error | undefined;
props = {};
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
let app = new App(Parent, { test: true });
let error: OwlError | undefined;
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
@@ -147,11 +147,10 @@ describe("props validation", () => {
}
expect(error!).toBeUndefined();
props = { p: test.ko };
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
app = new App(Parent, { test: true });
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
@@ -181,13 +180,12 @@ describe("props validation", () => {
static template = xml`<div>hey</div>`;
};
(Parent as any).components = { SubComp };
let error: Error | undefined;
props = {};
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
let app = new App(Parent, { test: true });
let error: OwlError | undefined;
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is undefined (should be a ${test.type.name.toLowerCase()})`
@@ -201,11 +199,10 @@ describe("props validation", () => {
}
expect(error!).toBeUndefined();
props = { p: test.ko };
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
app = new App(Parent, { test: true });
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component '_a'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
`Invalid props for component '_a': 'p' is not a ${test.type.name.toLowerCase()}`
@@ -227,26 +224,25 @@ describe("props validation", () => {
}
let error: Error;
let props: { p?: any };
props = { p: "string" };
try {
props = { p: "string" };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeUndefined();
props = { p: true };
try {
props = { p: true };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeUndefined();
try {
props = { p: 1 };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: 1 };
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' is not a string or boolean"
@@ -267,26 +263,25 @@ describe("props validation", () => {
}
let error: Error;
let props: { p?: any };
props = { p: "key" };
try {
props = { p: "key" };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeUndefined();
props = {};
try {
props = {};
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeUndefined();
try {
props = { p: 1 };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: 1 };
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is not a string");
});
@@ -319,20 +314,18 @@ describe("props validation", () => {
error = e as Error;
}
expect(error!).toBeUndefined();
try {
props = { p: [1] };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: [1] };
let app = new App(Parent, { test: true });
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
error = undefined;
try {
props = { p: ["string", 1] };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
app = new App(Parent, { test: true });
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
});
test("can validate an array with multiple sub element types", async () => {
@@ -370,12 +363,11 @@ describe("props validation", () => {
error = e as Error;
}
expect(error!).toBeUndefined();
try {
props = { p: [true, 1] };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: [true, 1] };
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p[1]' is not a string or boolean"
@@ -405,33 +397,30 @@ describe("props validation", () => {
error = e as Error;
}
expect(error!).toBeUndefined();
try {
props = { p: { id: 1, url: "url", extra: true } };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: { id: 1, url: "url", extra: true } };
let app = new App(Parent, { test: true });
let mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape (unknown key 'extra')"
);
try {
props = { p: { id: "1", url: "url" } };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: { id: "1", url: "url" } };
app = new App(Parent, { test: true });
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape ('id' is not a number)"
);
error = undefined;
try {
props = { p: { id: 1 } };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: { id: 1 } };
app = new App(Parent, { test: true });
mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is missing (should be a string))"
@@ -474,12 +463,11 @@ describe("props validation", () => {
error = e as Error;
}
expect(error!).toBeUndefined();
try {
props = { p: { id: 1, url: [12, true] } };
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
props = { p: { id: 1, url: [12, true] } };
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'SubComp': 'p' has not the correct shape ('url' is not a boolean or list of numbers)"
@@ -686,11 +674,10 @@ describe("props validation", () => {
static components = { SubComp };
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'SubComp'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
});
@@ -754,11 +741,10 @@ describe("props validation", () => {
static template = xml`<div><Child/></div>`;
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("Invalid props for component 'Child'");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"Invalid props for component 'Child': 'mandatory' is missing (should be a number)"
@@ -794,6 +780,32 @@ describe("props validation", () => {
// we just check that it doesn't throw
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");
});
});
//------------------------------------------------------------------------------
@@ -859,11 +871,12 @@ describe("default props", () => {
static template = xml`<Child/>`;
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
const app = new App(Parent, { test: true });
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow(
"default value cannot be defined for a mandatory prop"
);
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(
"A default value cannot be defined for a mandatory prop (name: 'mandatory', component: Child)"
+10 -5
View File
@@ -1,5 +1,5 @@
import { Component, mount, onMounted, useRef, useState } from "../../src/index";
import { logStep, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { App, Component, mount, onMounted, useRef, useState } from "../../src/index";
import { logStep, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { xml } from "../../src/index";
snapshotEverything();
@@ -94,9 +94,14 @@ describe("refs", () => {
ref = useRef("coucou");
}
await expect(async () => {
await mount(Test, fixture);
}).rejects.toThrowError("Cannot have 2 elements with same ref name at the same time");
const app = new App(Test, { test: true });
const mountProm = expect(app.mount(fixture)).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);
console.warn = consoleWarn;
});
+122 -8
View File
@@ -1,5 +1,5 @@
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
import { children, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { children, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
snapshotEverything();
let originalconsoleWarn = console.warn;
@@ -45,6 +45,23 @@ describe("slots", () => {
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 () => {
let child: any;
class Child extends Component {
@@ -204,13 +221,12 @@ describe("slots", () => {
static components = { Child };
}
let error = null;
try {
await mount(Parent, fixture);
} catch (e) {
error = e;
}
expect(error).not.toBeNull();
let error: Error;
const app = new App(Parent);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).not.toBeNull();
expect(mockConsoleWarn).toBeCalledTimes(1);
});
@@ -1819,4 +1835,102 @@ describe("slots", () => {
await nextTick();
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>"
);
});
});
+7 -8
View File
@@ -1,6 +1,6 @@
import { OwlError } from "../../src/runtime/error_handling";
import { Component, mount, onMounted, useState, xml } from "../../src";
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { App, Component, mount, onMounted, useState, xml } from "../../src";
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
snapshotEverything();
let fixture: HTMLElement;
@@ -343,16 +343,15 @@ describe("style and class handling", () => {
class Child extends Component {
static template = xml`<div t-att-class="props.class" t-esc="this.will.crash"/>`;
}
class ParentWidget extends Component {
class Parent extends Component {
static template = xml`<Child class="'a'"/>`;
static components = { Child };
}
let error: OwlError;
try {
await mount(ParentWidget, fixture);
} catch (e) {
error = e as OwlError;
}
const app = new App(Parent);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause).toBeDefined();
const regexp =
+28
View File
@@ -287,4 +287,32 @@ describe("t-call", () => {
});
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");
});
});
+74 -5
View File
@@ -1,5 +1,11 @@
import { Component, mount, onMounted, useState, xml } from "../../src/index";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
import { App, Component, mount, onMounted, useState, xml } from "../../src/index";
import {
makeTestFixture,
nextAppError,
nextTick,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
snapshotEverything();
@@ -315,10 +321,73 @@ describe("list of components", () => {
`;
static components = { Child };
}
await expect(async () => {
await mount(Parent, fixture, { dev: true });
}).rejects.toThrowError("Got duplicate key in t-foreach: child");
const app = new App(Parent, { test: true });
const mountProm = expect(app.mount(fixture)).rejects.toThrow(
"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;
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");
});
});
+36
View File
@@ -0,0 +1,36 @@
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,6 +261,20 @@ 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 {
namespace jest {
interface Matchers<R> {
@@ -155,6 +155,44 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -515,6 +553,44 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
+56 -17
View File
@@ -12,7 +12,7 @@ import {
} from "../../src";
import { xml } from "../../src/";
import { DEV_MSG } from "../../src/runtime/app";
import { elem, makeTestFixture, nextTick, snapshotEverything } from "../helpers";
import { elem, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
let fixture: HTMLElement;
let originalconsoleWarn = console.warn;
@@ -269,15 +269,14 @@ describe("Portal", () => {
}
let error: Error;
try {
await mount(Parent, fixture);
} catch (e) {
error = e as Error;
}
const app = new App(Parent);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("invalid portal target");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe("invalid portal target");
expect(fixture.innerHTML).toBe(`<div></div>`);
expect(fixture.innerHTML).toBe(``);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
@@ -875,6 +874,48 @@ describe("Portal", () => {
await nextTick();
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", () => {
@@ -960,11 +1001,10 @@ describe("Portal: Props validation", () => {
</div>`;
}
let error: OwlError;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as OwlError;
}
const app = new App(Parent);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
await mountProm;
expect(error!).toBeDefined();
expect(error!.cause).toBeDefined();
expect(error!.cause.message).toBe(`' ' is not a valid selector`);
@@ -980,11 +1020,10 @@ describe("Portal: Props validation", () => {
</div>`;
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
} catch (e) {
error = e as Error;
}
const app = new App(Parent);
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
await expect(nextAppError(app)).resolves.toThrow("invalid portal target");
await mountProm;
expect(error!).toBeDefined();
expect(error!.message).toBe(`invalid portal target`);
});
+1 -3
View File
@@ -72,10 +72,8 @@ async function startRelease() {
}
// ---------------------------------------------------------------------------
log(`Step 3/${STEPS}: updating package.json, readme.md and roadmap.md...`);
log(`Step 3/${STEPS}: updating package.json...`);
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...`);