Compare commits

..

1 Commits

Author SHA1 Message Date
Géry Debongnie 1253b3922b [IMP] hooks: add useRoots hook
closes #1260
2022-09-24 16:35:33 +02:00
38 changed files with 519 additions and 552 deletions
+6 -1
View File
@@ -261,7 +261,12 @@ This comes from the fact that Owl 2 supports fragments (arbitrary content).
Migration: if one need a reference to the root htmlelement of a template, it is
suggested to simply add a `ref` on it, and access the reference as needed.
Documentation: [Refs](doc/reference/refs.md)
Another way to get access to a root node or html element is the hook `useRoots`
Documentation:
- [Refs](doc/reference/refs.md)
- [useRoots](doc/reference/hooks.md#useroots)
### 10. style/class on components are now regular props
+1
View File
@@ -38,6 +38,7 @@ Other hooks:
- [`useRef`](reference/hooks.md#useref): get an object representing a reference (`t-ref`)
- [`useChildSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for child components)
- [`useSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for current component and child components)
- [`useRoots`](reference/hooks.md#useroots): returns an object that provides access to all root nodes or htmlelements
Utility/helpers:
+37
View File
@@ -13,6 +13,7 @@
- [`useComponent`](#usecomponent)
- [`useEnv`](#useenv)
- [`useEffect`](#useeffect)
- [`useRoots`](#useroots)
- [Example: Mouse Position](#example-mouse-position)
## Overview
@@ -288,6 +289,42 @@ class SomeComponent extends Component {
}
```
### `useRoots`
`useRoots` is an alternative way to get a reference to the root notes or elements
of a component. It may be useful in some cases where `useRef` cannot be applied,
such as a higher order component, or a component with only text as content.
The return value of `useRoots` is an object with the following key/values:
- `node`: getter that evaluates to the first node of the component content (or null)
- `elem`: getter that evaluates to the first HTMLElement of the component content (or null)
- `nodes`: iterator that returns all content nodes
- `elems`: iterator that returns all HTMLElement nodes
```js
class Child extends Component {
static template = xml`<p>some content</p>`;
}
class Parent extends Component {
static template = xml`<Child/>`;
static components = { Child };
setup() {
this.roots = useRoots();
console.log(this.roots.elem); // null
onMounted(() => {
console.log(this.roots.elem); // log the `p` element from the child
for (let elem of this.roots.elems) {
console.log(elem); // log the `p` element from the child
}
});
}
}
```
## Example: mouse position
Here is the classical example of a non trivial hook to track the mouse position.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.0",
"version": "2.0.0-beta-20",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
+74 -83
View File
@@ -214,14 +214,6 @@ 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"];
@@ -373,15 +365,19 @@ export class CodeGenerator {
insertBlock(expression: string, block: BlockDescription, ctx: Context): void {
let blockExpr = block.generateExpr(expression);
const tKeyExpr = ctx.tKeyExpr;
if (block.parentVar) {
let key = this.target.currentKey(ctx);
let keyArg = `key${this.target.loopLevel}`;
if (tKeyExpr) {
keyArg = `${tKeyExpr} + ${keyArg}`;
}
this.helpers.add("withKey");
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
return;
}
if (ctx.tKeyExpr) {
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
if (tKeyExpr) {
blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
}
if (block.isRoot && !ctx.preventRoot) {
@@ -428,66 +424,78 @@ export class CodeGenerator {
.join("");
}
/**
* @returns the newly created block name, if any
*/
compileAST(ast: AST, ctx: Context): string | null {
compileAST(ast: AST, ctx: Context) {
switch (ast.type) {
case ASTType.Comment:
return this.compileComment(ast, ctx);
this.compileComment(ast, ctx);
break;
case ASTType.Text:
return this.compileText(ast, ctx);
this.compileText(ast, ctx);
break;
case ASTType.DomNode:
return this.compileTDomNode(ast, ctx);
this.compileTDomNode(ast, ctx);
break;
case ASTType.TEsc:
return this.compileTEsc(ast, ctx);
this.compileTEsc(ast, ctx);
break;
case ASTType.TOut:
return this.compileTOut(ast, ctx);
this.compileTOut(ast, ctx);
break;
case ASTType.TIf:
return this.compileTIf(ast, ctx);
this.compileTIf(ast, ctx);
break;
case ASTType.TForEach:
return this.compileTForeach(ast, ctx);
this.compileTForeach(ast, ctx);
break;
case ASTType.TKey:
return this.compileTKey(ast, ctx);
this.compileTKey(ast, ctx);
break;
case ASTType.Multi:
return this.compileMulti(ast, ctx);
this.compileMulti(ast, ctx);
break;
case ASTType.TCall:
return this.compileTCall(ast, ctx);
this.compileTCall(ast, ctx);
break;
case ASTType.TCallBlock:
return this.compileTCallBlock(ast, ctx);
this.compileTCallBlock(ast, ctx);
break;
case ASTType.TSet:
return this.compileTSet(ast, ctx);
this.compileTSet(ast, ctx);
break;
case ASTType.TComponent:
return this.compileComponent(ast, ctx);
this.compileComponent(ast, ctx);
break;
case ASTType.TDebug:
return this.compileDebug(ast, ctx);
this.compileDebug(ast, ctx);
break;
case ASTType.TLog:
return this.compileLog(ast, ctx);
this.compileLog(ast, ctx);
break;
case ASTType.TSlot:
return this.compileTSlot(ast, ctx);
this.compileTSlot(ast, ctx);
break;
case ASTType.TTranslation:
return this.compileTTranslation(ast, ctx);
this.compileTTranslation(ast, ctx);
break;
case ASTType.TPortal:
return this.compileTPortal(ast, ctx);
this.compileTPortal(ast, ctx);
}
}
compileDebug(ast: ASTDebug, ctx: Context): string | null {
compileDebug(ast: ASTDebug, ctx: Context) {
this.addLine(`debugger;`);
if (ast.content) {
return this.compileAST(ast.content, ctx);
this.compileAST(ast.content, ctx);
}
return null;
}
compileLog(ast: ASTLog, ctx: Context): string | null {
compileLog(ast: ASTLog, ctx: Context) {
this.addLine(`console.log(${compileExpr(ast.expr)});`);
if (ast.content) {
return this.compileAST(ast.content, ctx);
this.compileAST(ast.content, ctx);
}
return null;
}
compileComment(ast: ASTComment, ctx: Context): string {
compileComment(ast: ASTComment, ctx: Context) {
let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock;
if (isNewBlock) {
@@ -500,10 +508,9 @@ export class CodeGenerator {
const text = xmlDoc.createComment(ast.value);
block!.insert(text);
}
return block!.varName;
}
compileText(ast: ASTText, ctx: Context): string {
compileText(ast: ASTText, ctx: Context) {
let { block, forceNewBlock } = ctx;
let value = ast.value;
@@ -522,7 +529,6 @@ 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 {
@@ -542,7 +548,7 @@ export class CodeGenerator {
return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
}
compileTDomNode(ast: ASTDomNode, ctx: Context): string {
compileTDomNode(ast: ASTDomNode, ctx: Context) {
let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
let codeIdx = this.target.code.length;
@@ -729,10 +735,9 @@ export class CodeGenerator {
this.addLine(`let ${block!.children.map((c) => c.varName)};`, codeIdx);
}
}
return block!.varName;
}
compileTEsc(ast: ASTTEsc, ctx: Context): string {
compileTEsc(ast: ASTTEsc, ctx: Context) {
let { block, forceNewBlock } = ctx;
let expr: string;
if (ast.expr === "0") {
@@ -753,10 +758,9 @@ export class CodeGenerator {
const text = xmlDoc.createElement(`block-text-${idx}`);
block.insert(text);
}
return block.varName;
}
compileTOut(ast: ASTTOut, ctx: Context): string {
compileTOut(ast: ASTTOut, ctx: Context) {
let { block } = ctx;
if (block) {
this.insertAnchor(block);
@@ -778,7 +782,6 @@ export class CodeGenerator {
blockStr = `safeOutput(${compileExpr(ast.expr)})`;
}
this.insertBlock(blockStr, block, ctx);
return block.varName;
}
compileTIfBranch(content: AST, block: BlockDescription, ctx: Context) {
@@ -792,7 +795,7 @@ export class CodeGenerator {
this.target.indentLevel--;
}
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode): string {
compileTIf(ast: ASTTif, ctx: Context, nextNode?: ASTDomNode) {
let { block, forceNewBlock } = ctx;
const codeIdx = this.target.code.length;
const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
@@ -835,10 +838,9 @@ 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): string {
compileTForeach(ast: ASTTForEach, ctx: Context) {
let { block } = ctx;
if (block) {
this.insertAnchor(block);
@@ -916,10 +918,9 @@ export class CodeGenerator {
this.addLine(`ctx = ctx.__proto__;`);
}
this.insertBlock("l", block, ctx);
return block.varName;
}
compileTKey(ast: ASTTKey, ctx: Context): string | null {
compileTKey(ast: ASTTKey, ctx: Context) {
const tKeyExpr = generateId("tKey_");
this.define(tKeyExpr, compileExpr(ast.expr));
ctx = createContext(ctx, {
@@ -927,22 +928,20 @@ export class CodeGenerator {
block: ctx.block,
index: ctx.index,
});
return this.compileAST(ast.content, ctx);
this.compileAST(ast.content, ctx);
}
compileMulti(ast: ASTMulti, ctx: Context): string | null {
compileMulti(ast: ASTMulti, ctx: Context) {
let { block, forceNewBlock } = ctx;
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) {
const blockName = this.compileAST(child, ctx);
result = result || blockName;
this.compileAST(child, ctx);
}
return result;
return;
}
block = this.createBlock(block, "multi", ctx);
}
@@ -982,10 +981,9 @@ 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): string {
compileTCall(ast: ASTTCall, ctx: Context) {
let { block, forceNewBlock } = ctx;
let ctxVar = ctx.ctxVar || "ctx";
if (ast.context) {
@@ -996,11 +994,12 @@ 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 });
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
if (bl) {
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
if (nextId !== BlockDescription.nextBlockId) {
this.helpers.add("zero");
this.addLine(`${ctxVar}[zero] = ${bl};`);
this.addLine(`${ctxVar}[zero] = b${nextId};`);
}
}
const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -1034,10 +1033,9 @@ export class CodeGenerator {
if (ast.body && !ctx.isLast) {
this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
}
return block.varName;
}
compileTCallBlock(ast: ASTTCallBlock, ctx: Context): string {
compileTCallBlock(ast: ASTTCallBlock, ctx: Context) {
let { block, forceNewBlock } = ctx;
if (block) {
if (!forceNewBlock) {
@@ -1046,10 +1044,9 @@ 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): null {
compileTSet(ast: ASTTSet, ctx: Context) {
this.target.shouldProtectScope = true;
this.helpers.add("isBoundary").add("withDefault");
const expr = ast.value ? compileExpr(ast.value || "") : "null";
@@ -1057,8 +1054,7 @@ export class CodeGenerator {
this.helpers.add("LazyValue");
const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
const name = this.compileInNewTarget("value", bodyAst, ctx);
let key = this.target.currentKey(ctx);
let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
let value = `new LazyValue(${name}, ctx, this, node)`;
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
} else {
@@ -1075,7 +1071,6 @@ export class CodeGenerator {
this.helpers.add("setContextValue");
this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
}
return null;
}
generateComponentKey() {
@@ -1127,7 +1122,7 @@ export class CodeGenerator {
return propString;
}
compileComponent(ast: ASTComponent, ctx: Context): string {
compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx;
// props
const hasSlotsProp = "slots" in (ast.props || {});
@@ -1193,7 +1188,7 @@ export class CodeGenerator {
}
if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, this);`);
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, node);`);
}
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1227,7 +1222,6 @@ export class CodeGenerator {
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockExpr, block, ctx);
return block.varName;
}
wrapWithEventCatcher(expr: string, on: EventHandlers): string {
@@ -1246,7 +1240,7 @@ export class CodeGenerator {
return `${name}(${expr}, [${handlers.join(",")}])`;
}
compileTSlot(ast: ASTSlot, ctx: Context): string {
compileTSlot(ast: ASTSlot, ctx: Context) {
this.helpers.add("callSlot");
let { block } = ctx;
let blockString: string;
@@ -1295,16 +1289,14 @@ export class CodeGenerator {
}
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
}
compileTTranslation(ast: ASTTranslation, ctx: Context): string | null {
compileTTranslation(ast: ASTTranslation, ctx: Context) {
if (ast.content) {
return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
}
return null;
}
compileTPortal(ast: ASTTPortal, ctx: Context): string {
compileTPortal(ast: ASTTPortal, ctx: Context) {
if (!this.staticDefs.find((d) => d.id === "Portal")) {
this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
}
@@ -1331,6 +1323,5 @@ export class CodeGenerator {
}
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
return block.varName;
}
}
+1 -1
View File
@@ -66,7 +66,7 @@ export class App<
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
App.validateTarget(target);
if (this.dev) {
validateProps(this.Root, this.props, { __owl__: { app: this } });
validateProps(this.Root, this.props, { app: this });
}
const node = this.makeNode(this.Root, this.props);
const prom = this.mountNode(node, target, options);
+6
View File
@@ -517,6 +517,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
return this.el!;
}
*nodes() {
if (this.el) {
yield this.el;
}
}
moveBefore(other: Block | null, afterNode: Node | null) {
const target = other ? other.el! : afterNode;
nodeInsertBefore.call(this.parentEl, this.el!, target);
+6 -6
View File
@@ -12,7 +12,6 @@ export function createCatcher(eventsSpec: EventsSpec): Catcher {
child: VNode;
handlerData: any[];
handlerFns: any[] = [];
parentEl?: HTMLElement | undefined;
afterNode: Text | null = null;
@@ -44,13 +43,10 @@ export function createCatcher(eventsSpec: EventsSpec): Catcher {
const self = this;
handler[idx] = function (ev: any) {
const target = ev.target;
let currentNode: any = self.child.firstNode();
const afterNode = self.afterNode;
while (currentNode !== afterNode) {
if (currentNode.contains(target)) {
for (let node of self.nodes()) {
if (node.contains(target)) {
return origFn.call(this, ev);
}
currentNode = currentNode.nextSibling;
}
};
}
@@ -90,6 +86,10 @@ export function createCatcher(eventsSpec: EventsSpec): Catcher {
return this.child.firstNode();
}
nodes(): Generator<Node> {
return this.child.nodes();
}
toString(): string {
return this.child.toString();
}
+6
View File
@@ -78,6 +78,12 @@ class VHtml {
return this.content[0]!;
}
*nodes() {
for (let elem of this.content) {
yield elem;
}
}
toString() {
return this.html;
}
+2
View File
@@ -16,6 +16,8 @@ export interface VNode<T = any> {
remove(): void;
firstNode(): Node | undefined;
nodes(): Generator<Node>;
el?: undefined | HTMLElement | Text;
parentEl?: undefined | HTMLElement;
isOnlyChild?: boolean | undefined;
+6
View File
@@ -221,6 +221,12 @@ class VList {
return child ? child.firstNode() : undefined;
}
*nodes() {
for (let child of this.children) {
yield* child.nodes();
}
}
toString(): string {
return this.children.map((c) => c!.toString()).join("");
}
+8
View File
@@ -124,6 +124,14 @@ export class VMulti {
return child ? child.firstNode() : this.anchors![0];
}
*nodes() {
for (let child of this.children) {
if (child) {
yield* child.nodes();
}
}
}
toString(): string {
return this.children.map((c) => (c ? c!.toString() : "")).join("");
}
+6
View File
@@ -38,6 +38,12 @@ abstract class VSimpleNode {
return this.el!;
}
*nodes(): Generator<Node> {
if (this.el) {
yield this.el;
}
}
toString() {
return this.text;
}
+4
View File
@@ -55,6 +55,10 @@ class VToggler {
return this.child.firstNode();
}
nodes() {
return this.child.nodes();
}
toString(): string {
return this.child.toString();
}
+6
View File
@@ -296,6 +296,12 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
return bdom ? bdom.firstNode() : undefined;
}
*nodes(): Generator<Node> {
if (this.bdom) {
yield* this.bdom.nodes();
}
}
mount(parent: HTMLElement, anchor: ChildNode) {
const bdom = this.fiber!.bdom!;
this.bdom = bdom;
+36
View File
@@ -127,3 +127,39 @@ export function useExternalListener(
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
}
// -----------------------------------------------------------------------------
// useRoots
// -----------------------------------------------------------------------------
interface DomRangeObj {
node: Node | null;
elem: HTMLElement | null;
elems: Iterable<HTMLElement>;
nodes: Iterable<Node>;
}
export function useRoots(): DomRangeObj {
const cnode = getCurrent();
function* _elems(): Generator<HTMLElement> {
for (let node of cnode.nodes()) {
if (node.nodeType === 1) {
yield node as HTMLElement;
}
}
}
return {
get node() {
return cnode.nodes().next().value || null;
},
get elem() {
return _elems().next().value || null;
},
get nodes() {
return cnode.nodes();
},
get elems() {
return _elems();
},
};
}
+9 -1
View File
@@ -40,7 +40,15 @@ export type { ComponentConstructor } from "./component";
export { useComponent, useState } from "./component_node";
export { status } from "./status";
export { reactive, markRaw, toRaw } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export {
useEffect,
useEnv,
useExternalListener,
useRef,
useChildSubEnv,
useSubEnv,
useRoots,
} from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils";
export {
onWillStart,
+32 -37
View File
@@ -1,4 +1,4 @@
import { onMounted, onWillUnmount } from "./lifecycle_hooks";
import { onWillUnmount } from "./lifecycle_hooks";
import { BDom, text, VNode } from "./blockdom";
import { Component } from "./component";
import { OwlError } from "./error_handling";
@@ -6,55 +6,60 @@ import { OwlError } from "./error_handling";
const VText: any = text("").constructor;
class VPortal extends VText implements Partial<VNode<VPortal>> {
content: BDom | null;
selector: string;
// selector: string;
realBDom: BDom | null;
target: HTMLElement | null = null;
constructor(selector: string, content: BDom) {
constructor(selector: string, realBDom: BDom) {
super("");
this.selector = selector;
this.content = content;
this.realBDom = realBDom;
}
mount(parent: HTMLElement, anchor: ChildNode) {
super.mount(parent, anchor);
this.target = document.querySelector(this.selector) as any;
if (this.target) {
this.content!.mount(this.target!, null);
} else {
this.content!.mount(parent, anchor);
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");
}
}
this.realBDom!.mount(this.target!, null);
}
beforeRemove() {
this.content!.beforeRemove();
this.realBDom!.beforeRemove();
}
remove() {
if (this.content) {
if (this.realBDom) {
super.remove();
this.content!.remove();
this.content = null;
this.realBDom!.remove();
this.realBDom = null;
}
}
patch(other: VPortal) {
super.patch(other);
if (this.content) {
this.content.patch(other.content!, true);
if (this.realBDom) {
this.realBDom.patch(other.realBDom!, true);
} else {
this.content = other.content;
this.content!.mount(this.target!, null);
this.realBDom = other.realBDom;
this.realBDom!.mount(this.target!, null);
}
}
}
/**
* kind of similar to <t t-slot="default"/>, but it wraps it around a VPortal
* <t t-slot="default"/>
*/
export function portalTemplate(app: any, bdom: any, helpers: any) {
let { callSlot } = helpers;
return function template(ctx: any, node: any, key = ""): any {
return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null));
return function template(ctx: any, node: any, key = "") {
return callSlot(ctx, node, key, "default", false, null);
};
}
@@ -68,23 +73,13 @@ export class Portal extends Component {
};
setup() {
const node: any = this.__owl__;
onMounted(() => {
const portal: VPortal = node.bdom;
if (!portal.target) {
const target: HTMLElement = document.querySelector(this.props.target);
if (target) {
portal.content!.moveBefore(target, null);
} else {
throw new OwlError("invalid portal target");
}
}
});
const node = this.__owl__;
const renderFn = node.renderFn;
node.renderFn = () => new VPortal(this.props.target, renderFn());
onWillUnmount(() => {
const portal: VPortal = node.bdom;
portal.remove();
if (node.bdom) {
node.bdom.remove();
}
});
}
}
+5 -8
View File
@@ -111,18 +111,15 @@ class LazyValue {
ctx: any;
component: any;
node: any;
key: any;
constructor(fn: any, ctx: any, component: any, node: any, key: any) {
constructor(fn: any, ctx: any, component: any, node: 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, this.key);
return this.fn.call(this.component, this.ctx, this.node);
}
toString() {
@@ -210,11 +207,11 @@ function multiRefSetter(refs: RefMap, name: string): RefSetter {
* visit recursively the props and all the children to check if they are valid.
* This is why it is only done in 'dev' mode.
*/
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, comp?: any) {
export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, node?: any) {
const ComponentClass =
typeof name !== "string"
? name
: (comp.constructor.components[name] as ComponentConstructor<P> | undefined);
: (node.component.constructor.components[name] as ComponentConstructor<P> | undefined);
if (!ComponentClass) {
// this is an error, wrong component. We silently return here instead so the
@@ -224,7 +221,7 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
const schema = ComponentClass.props;
if (!schema) {
if (comp.__owl__.app.warnIfNoStaticProps) {
if (node.app.warnIfNoStaticProps) {
console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
}
return;
@@ -334,57 +334,6 @@ exports[`t-call (template calling) inherit context 2`] = `
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`grandchild\`);
const callTemplate_2 = app.getTemplate(\`child\`);
let block1 = createBlock(\`<p>Some content...</p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = block1();
ctx[zero] = b1;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
ctx = ctx.__proto__;
ctx[zero] = b2;
return callTemplate_2.call(this, ctx, node, key + \`__2\`);
}
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero } = helpers;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`grandchild\`);
const b3 = ctx[zero];
return multi([b2, b3]);
}
}"
`;
exports[`t-call (template calling) nested t-calls with magic variable 0 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero } = helpers;
return function template(ctx, node, key = \\"\\") {
return ctx[zero];
}
}"
`;
exports[`t-call (template calling) recursive template, part 1 1`] = `
"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, key);
ctx[\`var\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`var\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`bdom\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`value\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`stuff\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`v2\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`setvar\`] = new LazyValue(value1, ctx, this, node);
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, key));
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node));
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, key));
ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, this, node));
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, key));
ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, this, node));
let txt1 = ctx['value'];
return block1([txt1]);
}
-17
View File
@@ -479,21 +479,4 @@ 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, this);
helpers.validateProps(\`SomeMispelledComponent\`, props1, node);
return comp1(props1, key + \`__1\`, node, this, null);
}
}"
@@ -405,3 +405,69 @@ exports[`hooks useSubEnv supports arbitrary descriptor 2`] = `
}
}"
`;
exports[`useRoots hook return a list of nodes and elems 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<span>hey</span>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`useRoots hook return a list of nodes and elems 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block3 = createBlock(\`<span>hey</span>\`);
let block5 = createBlock(\`<p>bla</p>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = text(\` some text \`);
const b3 = block3();
const b4 = text(\` coucou \`);
const b5 = block5();
return multi([b2, b3, b4, b5]);
}
}"
`;
exports[`useRoots hook useRoots and lifecycle 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<span>hey</span>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`useRoots hook useRoots is up to date 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block2 = createBlock(\`<span>hey</span>\`);
let block5 = createBlock(\`<p>paragraph</p>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['state'].flag) {
b2 = block2();
} else {
const b4 = text(\` coucou \`);
const b5 = block5();
b3 = multi([b4, b5]);
}
return multi([b2, b3]);
}
}"
`;
@@ -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, this);
helpers.validateProps(\`Child\`, props1, node);
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, key);
ctx[\`abc\`] = new LazyValue(value1, ctx, this, node);
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, this);
helpers.validateProps(\`Child\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`Child\`, props1, node);
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, this);
helpers.validateProps(\`Child\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -673,47 +673,13 @@ 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, this);
helpers.validateProps(\`SubComp\`, props1, node);
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
) {
@@ -724,7 +690,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -755,7 +721,7 @@ exports[`props validation missing required boolean prop causes an error 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -772,7 +738,7 @@ exports[`props validation mix of optional and mandatory 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`Child\`, props1, this);
helpers.validateProps(\`Child\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -789,7 +755,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
return function template(ctx, node, key = \\"\\") {
const props1 = {message: 1};
helpers.validateProps(\`Child\`, props1, this);
helpers.validateProps(\`Child\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -820,7 +786,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -851,7 +817,7 @@ exports[`props validation props: list of strings 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -868,7 +834,7 @@ exports[`props validation validate simple types 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -885,7 +851,7 @@ exports[`props validation validate simple types 2`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -915,7 +881,7 @@ exports[`props validation validate simple types 4`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -932,7 +898,7 @@ exports[`props validation validate simple types 5`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -949,7 +915,7 @@ exports[`props validation validate simple types 6`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -979,7 +945,7 @@ exports[`props validation validate simple types 8`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -996,7 +962,7 @@ exports[`props validation validate simple types 9`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1013,7 +979,7 @@ exports[`props validation validate simple types 10`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1043,7 +1009,7 @@ exports[`props validation validate simple types 12`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1060,7 +1026,7 @@ exports[`props validation validate simple types 13`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1077,7 +1043,7 @@ exports[`props validation validate simple types 14`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1107,7 +1073,7 @@ exports[`props validation validate simple types 16`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1124,7 +1090,7 @@ exports[`props validation validate simple types 17`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1141,7 +1107,7 @@ exports[`props validation validate simple types 18`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1171,7 +1137,7 @@ exports[`props validation validate simple types 20`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1188,7 +1154,7 @@ exports[`props validation validate simple types 21`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1205,7 +1171,7 @@ exports[`props validation validate simple types 22`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1235,7 +1201,7 @@ exports[`props validation validate simple types 24`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {p: ctx['p']};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1252,7 +1218,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1269,7 +1235,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1299,7 +1265,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1316,7 +1282,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1333,7 +1299,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1363,7 +1329,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1380,7 +1346,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1397,7 +1363,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1427,7 +1393,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1444,7 +1410,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1461,7 +1427,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1491,7 +1457,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1508,7 +1474,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1525,7 +1491,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1555,7 +1521,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1572,7 +1538,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1589,7 +1555,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1619,7 +1585,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, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -1636,7 +1602,7 @@ exports[`props validation validation is only done in dev mode 1`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {};
helpers.validateProps(\`SubComp\`, props1, this);
helpers.validateProps(\`SubComp\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
return block1([], [b2]);
}
@@ -466,10 +466,10 @@ exports[`t-call t-call with t-call-context and subcomponent, in dev mode 2`] = `
return function template(ctx, node, key = \\"\\") {
const props1 = {name: ctx['aab']};
helpers.validateProps(\`Child\`, props1, this);
helpers.validateProps(\`Child\`, props1, node);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
const props2 = {name: ctx['lpe']};
helpers.validateProps(\`Child\`, props2, this);
helpers.validateProps(\`Child\`, props2, node);
const b3 = comp2(props2, key + \`__2\`, node, this, null);
return multi([b2, b3]);
}
@@ -56,7 +56,7 @@ exports[`list of components crash on duplicate key in dev mode 1`] = `
if (keys1.has(key1)) { throw new OwlError(\`Got duplicate key in t-foreach: \${key1}\`)}
keys1.add(key1);
const props1 = {};
helpers.validateProps(\`Child\`, props1, this);
helpers.validateProps(\`Child\`, props1, node);
c_block1[i1] = withKey(comp1(props1, key + \`__1__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
@@ -1,39 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components in t-out simple list 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, isBoundary, withDefault, LazyValue, safeOutput, withKey } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
function value1(ctx, node, key = \\"\\") {
return comp1({}, key + \`__1\`, node, this, null);
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList([1,2]);;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`n\`] = v_block1[i1];
const key1 = ctx['n'];
ctx[\`blabla\`] = new LazyValue(value1, ctx, this, node, key1);
c_block1[i1] = withKey(safeOutput(ctx['blabla']), key1);
}
return list(c_block1);
}
}"
`;
exports[`components in t-out simple list 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
@@ -59,7 +59,7 @@ exports[`t-set slots with a t-set with a component in body 1`] = `
function slot1(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`v\`] = new LazyValue(value1, ctx, this, node, key);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
const b3 = text(\` in slot \`);
const 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, key);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
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, key);
ctx[\`v\`] = new LazyValue(value1, ctx, this, node);
const b3 = safeOutput(ctx['v']);
return block1([], [b3]);
}
+109 -1
View File
@@ -19,6 +19,7 @@ import {
xml,
OwlError,
} from "../../src/index";
import { useRoots } from "../../src/runtime/hooks";
import {
elem,
logStep,
@@ -518,7 +519,7 @@ describe("hooks", () => {
});
test("dependencies prevent effects from rerunning when unchanged", async () => {
let steps = [];
let steps: string[] = [];
class MyComponent extends Component {
state = useState({
a: 0,
@@ -673,3 +674,110 @@ describe("hooks", () => {
});
});
});
describe("useRoots hook", () => {
test("return a list of nodes and elems", async () => {
class MyComponent extends Component {
static template = xml`<span>hey</span>`;
roots: any;
setup() {
this.roots = useRoots();
}
}
const comp = await mount(MyComponent, fixture);
expect(comp.roots.node).toBeInstanceOf(HTMLSpanElement);
expect(comp.roots.elem).toBeInstanceOf(HTMLSpanElement);
expect([...comp.roots.nodes].map((n: any) => n.tagName)).toEqual(["SPAN"]);
expect([...comp.roots.elems].map((n: any) => n.tagName)).toEqual(["SPAN"]);
});
test("return a list of nodes and elems", async () => {
class MyComponent extends Component {
static template = xml`
some text
<span>hey</span>
coucou
<p>bla</p>`;
roots: any;
setup() {
this.roots = useRoots();
}
}
const comp = await mount(MyComponent, fixture);
expect(comp.roots.node).toBeInstanceOf(Text);
expect(comp.roots.elem).toBeInstanceOf(HTMLSpanElement);
expect([...comp.roots.elems].map((n: any) => n.tagName)).toEqual(["SPAN", "P"]);
expect([...comp.roots.nodes].map((n: any) => n.nodeType)).toEqual([3, 1, 3, 1]);
});
test("useRoots and lifecycle", async () => {
expect.assertions(13);
class MyComponent extends Component {
static template = xml`<span>hey</span>`;
roots: any;
setup() {
this.roots = useRoots();
expect(this.roots.elem).toBe(null);
expect(this.roots.node).toBe(null);
expect([...this.roots.nodes]).toEqual([]);
expect([...this.roots.elems]).toEqual([]);
onWillStart(() => {
expect(this.roots.elem).toBe(null);
expect(this.roots.node).toBe(null);
expect([...this.roots.nodes]).toEqual([]);
expect([...this.roots.elems]).toEqual([]);
});
onMounted(() => {
expect(this.roots.elem).toBeInstanceOf(HTMLSpanElement);
expect(this.roots.node).toBeInstanceOf(HTMLSpanElement);
expect([...this.roots.elems].map((n: any) => n.tagName)).toEqual(["SPAN"]);
expect([...this.roots.nodes].map((n: any) => n.nodeType)).toEqual([1]);
});
}
}
const comp = await mount(MyComponent, fixture);
comp.__owl__.app.destroy();
});
test("useRoots is up to date", async () => {
class MyComponent extends Component {
static template = xml`
<t t-if="state.flag">
<span>hey</span>
</t>
<t t-else="">
coucou
<p>paragraph</p>
</t>`;
roots: any;
state: any;
setup() {
this.roots = useRoots();
this.state = useState({ flag: true });
onMounted(() => {
expect(this.roots.elem).toBeInstanceOf(HTMLSpanElement);
expect(this.roots.node).toBeInstanceOf(HTMLSpanElement);
expect([...this.roots.elems].map((n: any) => n.tagName)).toEqual(["SPAN"]);
expect([...this.roots.nodes].map((n: any) => n.nodeType)).toEqual([1]);
});
}
}
const comp = await mount(MyComponent, fixture);
comp.state.flag = false;
await nextTick();
expect(comp.roots.node.nodeType).toBe(3); // text node
expect(comp.roots.node.textContent).toBe(" coucou "); // text node
expect(comp.roots.elem).toBeInstanceOf(HTMLParagraphElement);
});
});
-26
View File
@@ -780,32 +780,6 @@ 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");
});
});
//------------------------------------------------------------------------------
-36
View File
@@ -1,36 +0,0 @@
import { Component, mount, xml } from "../../src/index";
import { makeTestFixture, snapshotEverything } from "../helpers";
snapshotEverything();
// -----------------------------------------------------------------------------
// t-out
// -----------------------------------------------------------------------------
describe("components in t-out", () => {
let fixture: HTMLElement;
beforeEach(() => {
fixture = makeTestFixture();
});
test("simple list", async () => {
class Child extends Component {
static template = xml`child`;
}
class Parent extends Component {
static template = xml`
<t t-foreach="[1,2]" t-as="n" t-key="n">
<t t-set="blabla">
<Child />
</t>
<t t-out="blabla"/>
</t>`;
static components = { Child };
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("childchild");
});
});
@@ -155,44 +155,6 @@ exports[`Portal Add and remove portals with t-foreach inside div 1`] = `
}"
`;
exports[`Portal Child and Portal 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
let block3 = createBlock(\`<div class=\\"portal\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = comp1({}, key + \`__1\`, node, this, null);
const b3 = block3();
return multi([b2, b3]);
}
}"
`;
exports[`Portal Child and Portal 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const Portal = app.Portal;
const comp1 = app.createComponent(null, false, true, false, false);
let block2 = createBlock(\`<span>child</span>\`);
let block3 = createBlock(\`<span>portal</span>\`);
function slot1(ctx, node, key = \\"\\") {
return block3();
}
return function template(ctx, node, key = \\"\\") {
const b2 = block2();
const b4 = comp1({target: '.portal',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal);
return multi([b2, b4]);
}
}"
`;
exports[`Portal Portal composed with t-slot 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -553,44 +515,6 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 2`]
}"
`;
exports[`Portal portal and Child 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
let block2 = createBlock(\`<div class=\\"portal\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = block2();
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
}"
`;
exports[`Portal portal and Child 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const Portal = app.Portal;
const comp1 = app.createComponent(null, false, true, false, false);
let block2 = createBlock(\`<span>child</span>\`);
let block3 = createBlock(\`<span>portal</span>\`);
function slot1(ctx, node, key = \\"\\") {
return block3();
}
return function template(ctx, node, key = \\"\\") {
const b2 = block2();
const b4 = comp1({target: '.portal',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal);
return multi([b2, b4]);
}
}"
`;
exports[`Portal portal could have dynamically no content 1`] = `
"function anonymous(app, bdom, helpers
) {
+1 -43
View File
@@ -276,7 +276,7 @@ describe("Portal", () => {
expect(error!).toBeDefined();
expect(error!.message).toBe("invalid portal target");
expect(fixture.innerHTML).toBe(``);
expect(fixture.innerHTML).toBe(`<div></div>`);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
@@ -874,48 +874,6 @@ 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"></div><span>portal</span>'
);
});
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", () => {