[IMP] portal: compile t-portal in an internal Component Portal

This commit also clean-up the deepRemove for the Portal that is not
needed any more.
This commit is contained in:
Jorge Pinna Puissant
2022-01-17 15:40:43 +01:00
committed by Aaron Bohy
parent 42a140a8e3
commit 41ad5db2e3
11 changed files with 496 additions and 240 deletions
-13
View File
@@ -2,8 +2,6 @@ import { BDom, multi, text, toggler } from "../blockdom";
import { validateProps } from "../component/props_validation"; import { validateProps } from "../component/props_validation";
import { Markup } from "../utils"; import { Markup } from "../utils";
import { html } from "../blockdom/index"; import { html } from "../blockdom/index";
import { VPortal } from "../portal";
/** /**
* This file contains utility functions that will be injected in each template, * This file contains utility functions that will be injected in each template,
* to perform various useful tasks in the compiled code. * to perform various useful tasks in the compiled code.
@@ -13,16 +11,6 @@ function withDefault(value: any, defaultValue: any): any {
return value === undefined || value === null || value === false ? defaultValue : value; return value === undefined || value === null || value === false ? defaultValue : value;
} }
function callPortal(
ctx: any,
parent: any,
key: string,
target: string,
content: (ctx: any, node: any, key: string) => BDom
): BDom {
return new VPortal(target, content(ctx, parent, key)) as any;
}
function callSlot( function callSlot(
ctx: any, ctx: any,
parent: any, parent: any,
@@ -198,7 +186,6 @@ export const UTILS = {
zero: Symbol("zero"), zero: Symbol("zero"),
isBoundary, isBoundary,
callSlot, callSlot,
callPortal,
capture, capture,
withKey, withKey,
prepareList, prepareList,
+3 -11
View File
@@ -47,7 +47,7 @@ const cache: { [key: string]: BlockType } = {};
* @param str * @param str
* @returns a new block type, that can build concrete blocks * @returns a new block type, that can build concrete blocks
*/ */
export function createBlock(str: string, deepRemove: boolean = false): BlockType { export function createBlock(str: string): BlockType {
if (str in cache) { if (str in cache) {
return cache[str]; return cache[str];
} }
@@ -67,7 +67,7 @@ export function createBlock(str: string, deepRemove: boolean = false): BlockType
// step 3: build the final block class // step 3: build the final block class
const template = tree.el as HTMLElement; const template = tree.el as HTMLElement;
const Block = buildBlock(template, context, deepRemove); const Block = buildBlock(template, context);
cache[str] = Block; cache[str] = Block;
return Block; return Block;
} }
@@ -422,7 +422,7 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
// building the concrete block class // building the concrete block class
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function buildBlock(template: HTMLElement, ctx: BlockCtx, deepRemove: boolean): BlockType { function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
let B = createBlockClass(template, ctx); let B = createBlockClass(template, ctx);
if (ctx.cbRefs.length) { if (ctx.cbRefs.length) {
@@ -447,14 +447,6 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx, deepRemove: boolean):
} }
}; };
B.prototype.beforeRemove = VMulti.prototype.beforeRemove; B.prototype.beforeRemove = VMulti.prototype.beforeRemove;
if (deepRemove) {
const blockRemove = B.prototype.remove;
const vMultiRemove = VMulti.prototype.remove;
B.prototype.remove = function () {
blockRemove.call(this);
vMultiRemove.call(this);
};
}
return (data?: any[], children: (VNode | undefined)[] = []) => new B(data, children); return (data?: any[], children: (VNode | undefined)[] = []) => new B(data, children);
} }
+5 -7
View File
@@ -17,11 +17,9 @@ class VList {
anchor: Node | undefined; anchor: Node | undefined;
parentEl?: HTMLElement | undefined; parentEl?: HTMLElement | undefined;
isOnlyChild?: boolean | undefined; isOnlyChild?: boolean | undefined;
deepRemove: boolean;
constructor(children: VNode[], deepRemove: boolean) { constructor(children: VNode[]) {
this.children = children; this.children = children;
this.deepRemove = deepRemove;
} }
mount(parent: HTMLElement, afterNode: Node | null) { mount(parent: HTMLElement, afterNode: Node | null) {
@@ -77,7 +75,7 @@ class VList {
const parent = this.parentEl!; const parent = this.parentEl!;
// fast path: no new child => only remove // fast path: no new child => only remove
if (ch2.length === 0 && isOnlyChild && !this.deepRemove) { if (ch2.length === 0 && isOnlyChild) {
if (withBeforeRemove) { if (withBeforeRemove) {
for (let i = 0, l = ch1.length; i < l; i++) { for (let i = 0, l = ch1.length; i < l; i++) {
beforeRemove.call(ch1[i]); beforeRemove.call(ch1[i]);
@@ -203,7 +201,7 @@ class VList {
remove() { remove() {
const { parentEl, anchor } = this; const { parentEl, anchor } = this;
if (this.isOnlyChild && !this.deepRemove) { if (this.isOnlyChild) {
nodeSetTextContent.call(parentEl, ""); nodeSetTextContent.call(parentEl, "");
} else { } else {
const children = this.children; const children = this.children;
@@ -228,8 +226,8 @@ class VList {
} }
} }
export function list(children: VNode[], deepRemove = false): VNode<VList> { export function list(children: VNode[]): VNode<VList> {
return new VList(children, deepRemove); return new VList(children);
} }
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } { function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
+4 -6
View File
@@ -15,11 +15,9 @@ export class VMulti {
anchors?: Node[] | undefined; anchors?: Node[] | undefined;
parentEl?: HTMLElement | undefined; parentEl?: HTMLElement | undefined;
isOnlyChild?: boolean | undefined; isOnlyChild?: boolean | undefined;
deepRemove: boolean;
constructor(children: (VNode | undefined)[], deepRemove: boolean) { constructor(children: (VNode | undefined)[]) {
this.children = children; this.children = children;
this.deepRemove = deepRemove;
} }
mount(parent: HTMLElement, afterNode: Node | null) { mount(parent: HTMLElement, afterNode: Node | null) {
@@ -105,7 +103,7 @@ export class VMulti {
remove() { remove() {
const parentEl = this.parentEl; const parentEl = this.parentEl;
if (this.isOnlyChild && !this.deepRemove) { if (this.isOnlyChild) {
nodeSetTextContent.call(parentEl, ""); nodeSetTextContent.call(parentEl, "");
} else { } else {
const children = this.children; const children = this.children;
@@ -131,6 +129,6 @@ export class VMulti {
} }
} }
export function multi(children: (VNode | undefined)[], deepRemove = false): VNode<VMulti> { export function multi(children: (VNode | undefined)[]): VNode<VMulti> {
return new VMulti(children, deepRemove); return new VMulti(children);
} }
+23 -26
View File
@@ -65,15 +65,13 @@ class BlockDescription {
type: BlockType; type: BlockType;
parentVar: string = ""; parentVar: string = "";
id: number; id: number;
deepRemove: boolean;
constructor(target: CodeTarget, type: BlockType, deepRemove: boolean = false) { constructor(target: CodeTarget, type: BlockType) {
this.id = BlockDescription.nextBlockId++; this.id = BlockDescription.nextBlockId++;
this.varName = "b" + this.id; this.varName = "b" + this.id;
this.blockName = "block" + this.id; this.blockName = "block" + this.id;
this.target = target; this.target = target;
this.type = type; this.type = type;
this.deepRemove = deepRemove;
} }
insertData(str: string, prefix: string = "d"): number { insertData(str: string, prefix: string = "d"): number {
@@ -102,7 +100,7 @@ class BlockDescription {
} }
return `${this.blockName}(${params})`; return `${this.blockName}(${params})`;
} else if (this.type === "list") { } else if (this.type === "list") {
return `list(c_block${this.id}${this.deepRemove ? ", true" : ""})`; return `list(c_block${this.id})`;
} }
return expr; return expr;
} }
@@ -253,8 +251,6 @@ export class CodeGenerator {
mainCode.push(`const ${id} = getTemplate(${template});`); mainCode.push(`const ${id} = getTemplate(${template});`);
} }
const deepRemove = "deepRemove" in this.ast ? this.ast.deepRemove : false;
// define all blocks // define all blocks
if (this.blocks.length) { if (this.blocks.length) {
mainCode.push(``); mainCode.push(``);
@@ -264,15 +260,9 @@ export class CodeGenerator {
if (block.dynamicTagName) { if (block.dynamicTagName) {
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`); xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`); xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
mainCode.push( mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
`let ${block.blockName} = tag => createBlock(\`${xmlString}\`${
deepRemove ? ", true" : ""
});`
);
} else { } else {
mainCode.push( mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
`let ${block.blockName} = createBlock(\`${xmlString}\`${deepRemove ? ", true" : ""});`
);
} }
} }
} }
@@ -328,11 +318,10 @@ export class CodeGenerator {
createBlock( createBlock(
parentBlock: BlockDescription | null, parentBlock: BlockDescription | null,
type: BlockType, type: BlockType,
ctx: Context, ctx: Context
deepRemove: boolean = false
): BlockDescription { ): BlockDescription {
const hasRoot = this.target.hasRoot; const hasRoot = this.target.hasRoot;
const block = new BlockDescription(this.target, type, deepRemove); const block = new BlockDescription(this.target, type);
if (!hasRoot && !ctx.preventRoot) { if (!hasRoot && !ctx.preventRoot) {
this.target.hasRoot = true; this.target.hasRoot = true;
block.isRoot = true; block.isRoot = true;
@@ -718,7 +707,7 @@ export class CodeGenerator {
if (ast.body) { if (ast.body) {
const nextId = BlockDescription.nextBlockId; const nextId = BlockDescription.nextBlockId;
const subCtx: Context = createContext(ctx); const subCtx: Context = createContext(ctx);
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx); this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
this.helpers.add("withDefault"); this.helpers.add("withDefault");
expr = `withDefault(${expr}, b${nextId})`; expr = `withDefault(${expr}, b${nextId})`;
} }
@@ -779,7 +768,7 @@ export class CodeGenerator {
// note: this part is duplicated from end of compilemulti: // note: this part is duplicated from end of compilemulti:
const args = block!.children.map((c) => c.varName).join(", "); const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!; this.insertBlock(`multi([${args}])`, block!, ctx)!;
} }
} }
@@ -788,7 +777,7 @@ export class CodeGenerator {
if (block) { if (block) {
this.insertAnchor(block); this.insertAnchor(block);
} }
block = this.createBlock(block, "list", ctx, ast.deepRemove); block = this.createBlock(block, "list", ctx);
this.target.loopLevel++; this.target.loopLevel++;
const loopVar = `i${this.target.loopLevel}`; const loopVar = `i${this.target.loopLevel}`;
this.addLine(`ctx = Object.create(ctx);`); this.addLine(`ctx = Object.create(ctx);`);
@@ -923,7 +912,7 @@ export class CodeGenerator {
} }
const args = block!.children.map((c) => c.varName).join(", "); const args = block!.children.map((c) => c.varName).join(", ");
this.insertBlock(`multi([${args}]${ast.deepRemove ? ", true" : ""})`, block!, ctx)!; this.insertBlock(`multi([${args}])`, block!, ctx)!;
} }
} }
@@ -935,7 +924,7 @@ export class CodeGenerator {
this.helpers.add("isBoundary"); this.helpers.add("isBoundary");
const nextId = BlockDescription.nextBlockId; const nextId = BlockDescription.nextBlockId;
const subCtx: Context = createContext(ctx, { preventRoot: true }); const subCtx: Context = createContext(ctx, { preventRoot: true });
this.compileAST({ type: ASTType.Multi, content: ast.body, deepRemove: false }, subCtx); this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
if (nextId !== BlockDescription.nextBlockId) { if (nextId !== BlockDescription.nextBlockId) {
this.helpers.add("zero"); this.helpers.add("zero");
this.addLine(`ctx[zero] = b${nextId};`); this.addLine(`ctx[zero] = b${nextId};`);
@@ -990,7 +979,7 @@ export class CodeGenerator {
const expr = ast.value ? compileExpr(ast.value || "") : "null"; const expr = ast.value ? compileExpr(ast.value || "") : "null";
if (ast.body) { if (ast.body) {
this.helpers.add("LazyValue"); this.helpers.add("LazyValue");
const bodyAst: AST = { type: ASTType.Multi, content: ast.body, deepRemove: false }; const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
const name = this.compileInNewTarget("value", bodyAst, ctx); const name = this.compileInNewTarget("value", bodyAst, ctx);
let value = `new LazyValue(${name}, ctx, node)`; let value = `new LazyValue(${name}, ctx, node)`;
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value; value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
@@ -1177,10 +1166,18 @@ export class CodeGenerator {
} }
} }
compileTPortal(ast: ASTTPortal, ctx: Context) { compileTPortal(ast: ASTTPortal, ctx: Context) {
this.helpers.add("callPortal"); this.helpers.add("Portal");
let { block } = ctx; let { block } = ctx;
const name = this.compileInNewTarget("portalContent", ast.content, ctx); const name = this.compileInNewTarget("slot", ast.content, ctx);
const blockString = `callPortal(ctx, node, key, ${ast.target}, ${name})`; const key = this.generateComponentKey();
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
this.helpers.add("capture");
this.addLine(`const ${ctxStr} = capture(ctx);`);
}
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
if (block) { if (block) {
this.insertAnchor(block); this.insertAnchor(block);
} }
+1 -61
View File
@@ -56,7 +56,6 @@ export interface ASTDomNode {
export interface ASTMulti { export interface ASTMulti {
type: ASTType.Multi; type: ASTType.Multi;
content: AST[]; content: AST[];
deepRemove: boolean;
} }
export interface ASTTEsc { export interface ASTTEsc {
@@ -77,7 +76,6 @@ export interface ASTTif {
content: AST; content: AST;
tElif: { condition: string; content: AST }[] | null; tElif: { condition: string; content: AST }[] | null;
tElse: AST | null; tElse: AST | null;
deepRemove: boolean;
} }
export interface ASTTSet { export interface ASTTSet {
@@ -95,7 +93,6 @@ export interface ASTTForEach {
key: string | null; key: string | null;
body: AST; body: AST;
memo: string; memo: string;
deepRemove: boolean;
hasNoFirst: boolean; hasNoFirst: boolean;
hasNoLast: boolean; hasNoLast: boolean;
hasNoIndex: boolean; hasNoIndex: boolean;
@@ -484,7 +481,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
elem, elem,
body, body,
memo, memo,
deepRemove: needDeepRemove(body),
key, key,
hasNoFirst, hasNoFirst,
hasNoLast, hasNoLast,
@@ -493,44 +489,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
}; };
} }
/**
* @returns true if we are sure that a deep remove (without optimisation) is needed, for exemple
* if there is a portal.
*/
function needDeepRemove(ast: AST): boolean {
switch (ast.type) {
case ASTType.Multi:
case ASTType.TForEach:
case ASTType.TIf:
return ast.deepRemove;
case ASTType.TPortal:
return true;
case ASTType.TComponent:
case ASTType.TOut:
case ASTType.TCall:
case ASTType.TCallBlock:
case ASTType.TSlot:
case ASTType.Text:
case ASTType.Comment:
case ASTType.TEsc:
return false;
case ASTType.TKey:
return needDeepRemove(ast.content);
case ASTType.TDebug:
case ASTType.TLog:
case ASTType.TTranslation:
return ast.content ? needDeepRemove(ast.content) : false;
case ASTType.TSet:
return ast.body ? ast.body.some((ast) => needDeepRemove(ast)) : false;
case ASTType.DomNode:
return ast.content.some((ast) => needDeepRemove(ast));
}
}
function parseTKey(node: Element, ctx: ParsingContext): AST | null { function parseTKey(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-key")) { if (!node.hasAttribute("t-key")) {
return null; return null;
@@ -628,21 +586,12 @@ function parseTIf(node: Element, ctx: ParsingContext): AST | null {
nextElement.remove(); nextElement.remove();
} }
let deepRemove = needDeepRemove(content);
if (tElifs) {
deepRemove = deepRemove || tElifs.some((ast) => needDeepRemove(ast));
}
if (tElse) {
deepRemove = deepRemove || needDeepRemove(tElse);
}
return { return {
type: ASTType.TIf, type: ASTType.TIf,
condition, condition,
content, content,
tElif: tElifs.length ? tElifs : null, tElif: tElifs.length ? tElifs : null,
tElse, tElse,
deepRemove,
}; };
} }
@@ -814,11 +763,6 @@ function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-portal")) { if (!node.hasAttribute("t-portal")) {
return null; return null;
} }
if (node.tagName !== "t") {
throw new Error(
`Directive 't-portal' can only be used on <t> nodes (used on a <${node.tagName}>)`
);
}
const target = node.getAttribute("t-portal")!; const target = node.getAttribute("t-portal")!;
node.removeAttribute("t-portal"); node.removeAttribute("t-portal");
const content = parseNode(node, ctx); const content = parseNode(node, ctx);
@@ -869,11 +813,7 @@ function parseChildNodes(node: Node, ctx: ParsingContext): AST | null {
case 1: case 1:
return children[0]; return children[0];
default: default:
return { return { type: ASTType.Multi, content: children };
type: ASTType.Multi,
content: children,
deepRemove: children.some((ast) => needDeepRemove(ast)),
};
} }
} }
+3
View File
@@ -1,3 +1,4 @@
import { UTILS } from "./app/template_helpers";
import { import {
config, config,
createBlock, createBlock,
@@ -12,9 +13,11 @@ import {
comment, comment,
} from "./blockdom"; } from "./blockdom";
import { mainEventHandler } from "./component/handler"; import { mainEventHandler } from "./component/handler";
import { Portal } from "./portal";
config.shouldNormalizeDom = false; config.shouldNormalizeDom = false;
config.mainEventHandler = mainEventHandler; config.mainEventHandler = mainEventHandler;
(UTILS as any).Portal = Portal;
export const blockDom = { export const blockDom = {
config, config,
+26 -1
View File
@@ -1,8 +1,11 @@
import { onWillUnmount } from ".";
import { xml } from "./app/template_set";
import { BDom, text, VNode } from "./blockdom"; import { BDom, text, VNode } from "./blockdom";
import { Component } from "./component/component";
const VText: any = text("").constructor; const VText: any = text("").constructor;
export class VPortal extends VText implements Partial<VNode<VPortal>> { class VPortal extends VText implements Partial<VNode<VPortal>> {
// selector: string; // selector: string;
realBDom: BDom | null; realBDom: BDom | null;
target: HTMLElement | null = null; target: HTMLElement | null = null;
@@ -47,3 +50,25 @@ export class VPortal extends VText implements Partial<VNode<VPortal>> {
} }
} }
} }
export class Portal extends Component {
static template = xml`<t t-slot="default"/>`;
static props = {
target: {
type: String,
},
slots: true,
};
setup() {
const node = this.__owl__;
const renderFn = node.renderFn;
node.renderFn = () => new VPortal(this.props.target, renderFn());
onWillUnmount(async () => {
await Promise.resolve();
if (node.bdom && (node.bdom as any).realBDom) {
(node.bdom as any).realBDom.remove();
}
});
}
}
-32
View File
@@ -146,7 +146,6 @@ describe("qweb parser", () => {
content: [], content: [],
}, },
], ],
deepRemove: false,
}); });
}); });
@@ -254,7 +253,6 @@ describe("qweb parser", () => {
content: [], content: [],
}, },
], ],
deepRemove: false,
}); });
}); });
@@ -505,7 +503,6 @@ describe("qweb parser", () => {
}, },
tElif: null, tElif: null,
tElse: null, tElse: null,
deepRemove: false,
}, },
], ],
}); });
@@ -521,7 +518,6 @@ describe("qweb parser", () => {
}, },
tElif: null, tElif: null,
tElse: null, tElse: null,
deepRemove: false,
}); });
}); });
@@ -542,7 +538,6 @@ describe("qweb parser", () => {
}, },
tElif: null, tElif: null,
tElse: null, tElse: null,
deepRemove: false,
}); });
}); });
@@ -559,7 +554,6 @@ describe("qweb parser", () => {
type: ASTType.Text, type: ASTType.Text,
value: "else", value: "else",
}, },
deepRemove: false,
}); });
}); });
@@ -578,7 +572,6 @@ describe("qweb parser", () => {
}, },
], ],
tElse: null, tElse: null,
deepRemove: false,
}); });
}); });
@@ -600,7 +593,6 @@ describe("qweb parser", () => {
type: ASTType.Text, type: ASTType.Text,
value: "else", value: "else",
}, },
deepRemove: false,
}); });
}); });
@@ -658,7 +650,6 @@ describe("qweb parser", () => {
}, },
], ],
}, },
deepRemove: false,
}); });
}); });
@@ -740,7 +731,6 @@ describe("qweb parser", () => {
}, },
tElif: null, tElif: null,
tElse: null, tElse: null,
deepRemove: false,
}); });
}); });
@@ -763,7 +753,6 @@ describe("qweb parser", () => {
content: { type: ASTType.Text, value: "1" }, content: { type: ASTType.Text, value: "1" },
tElif: null, tElif: null,
tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null }, tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null },
deepRemove: false,
}, },
], ],
}); });
@@ -793,7 +782,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -809,7 +797,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -835,7 +822,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -851,7 +837,6 @@ describe("qweb parser", () => {
hasNoIndex: true, hasNoIndex: true,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -879,7 +864,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -898,7 +882,6 @@ describe("qweb parser", () => {
condition: "condition", condition: "condition",
tElif: null, tElif: null,
tElse: null, tElse: null,
deepRemove: false,
content: { content: {
type: ASTType.DomNode, type: ASTType.DomNode,
tag: "span", tag: "span",
@@ -916,7 +899,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -949,7 +931,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -977,7 +958,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}, },
], ],
}); });
@@ -1015,7 +995,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -1038,7 +1017,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -1060,7 +1038,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: false, hasNoLast: false,
hasNoValue: false, hasNoValue: false,
deepRemove: false,
}); });
}); });
@@ -1080,7 +1057,6 @@ describe("qweb parser", () => {
hasNoIndex: false, hasNoIndex: false,
hasNoLast: true, hasNoLast: true,
hasNoValue: true, hasNoValue: true,
deepRemove: false,
}); });
}); });
@@ -1130,7 +1106,6 @@ describe("qweb parser", () => {
condition: "condition", condition: "condition",
tElif: null, tElif: null,
tElse: null, tElse: null,
deepRemove: false,
content: { content: {
type: ASTType.TCall, type: ASTType.TCall,
name: "blabla", name: "blabla",
@@ -1285,7 +1260,6 @@ describe("qweb parser", () => {
ns: null, ns: null,
}, },
], ],
deepRemove: false,
}, },
}, },
}, },
@@ -1628,7 +1602,6 @@ describe("qweb parser", () => {
hasNoValue: true, hasNoValue: true,
key: "item_index", key: "item_index",
memo: "", memo: "",
deepRemove: false,
type: ASTType.TForEach, type: ASTType.TForEach,
}); });
}); });
@@ -1800,10 +1773,6 @@ describe("qweb parser", () => {
}); });
}); });
test("t-portal must be in a <t> node", async () => {
expect(() => parse(`<div t-portal="target">Content</div>`)).toThrowError();
});
test("t-portal with t-if", async () => { test("t-portal with t-if", async () => {
expect(parse(`<t t-portal="target" t-if="condition">Content</t>`)).toEqual({ expect(parse(`<t t-portal="target" t-if="condition">Content</t>`)).toEqual({
condition: "condition", condition: "condition",
@@ -1815,7 +1784,6 @@ describe("qweb parser", () => {
tElif: null, tElif: null,
tElse: null, tElse: null,
type: ASTType.TIf, type: ASTType.TIf,
deepRemove: true,
}); });
}); });
}); });
+254 -83
View File
@@ -4,9 +4,9 @@ exports[`Portal Add and remove portals 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, callPortal, withKey } = helpers; let { prepareList, Portal, capture, withKey } = helpers;
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let b3 = text(\` Portal\`); let b3 = text(\` Portal\`);
let b4 = text(ctx['portalId']); let b4 = text(ctx['portalId']);
return multi([b3, b4]); return multi([b3, b4]);
@@ -18,9 +18,37 @@ exports[`Portal Add and remove portals 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) { for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`portalId\`] = v_block1[i1]; ctx[\`portalId\`] = v_block1[i1];
let key1 = ctx['portalId']; let key1 = ctx['portalId'];
c_block1[i1] = withKey(callPortal(ctx, node, key, '#outside', portalContent1), key1); const ctx1 = capture(ctx);
c_block1[i1] = withKey(component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1__\${key1}\`, node, ctx), key1);
} }
return list(c_block1, true); return list(c_block1);
}
}"
`;
exports[`Portal Add and remove portals on div 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, Portal, capture, withKey } = helpers;
let block2 = createBlock(\`<div> Portal<block-text-0/></div>\`);
function slot1(ctx, node, key = \\"\\") {
let txt1 = ctx['portalId'];
return block2([txt1]);
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['portalIds']);
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`portalId\`] = v_block1[i1];
let key1 = ctx['portalId'];
const ctx1 = capture(ctx);
c_block1[i1] = withKey(component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
} }
}" }"
`; `;
@@ -29,11 +57,11 @@ exports[`Portal Add and remove portals with t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, callPortal, withKey } = helpers; let { prepareList, Portal, capture, withKey } = helpers;
let block2 = createBlock(\`<div><block-text-0/><block-child-0/></div>\`, true); let block2 = createBlock(\`<div><block-text-0/><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let b4 = text(\` Portal\`); let b4 = text(\` Portal\`);
let b5 = text(ctx['portalId']); let b5 = text(ctx['portalId']);
return multi([b4, b5]); return multi([b4, b5]);
@@ -46,10 +74,43 @@ exports[`Portal Add and remove portals with t-foreach 1`] = `
ctx[\`portalId\`] = v_block1[i1]; ctx[\`portalId\`] = v_block1[i1];
let key1 = ctx['portalId']; let key1 = ctx['portalId'];
let txt1 = ctx['portalId']; let txt1 = ctx['portalId'];
let b6 = callPortal(ctx, node, key, '#outside', portalContent1); const ctx1 = capture(ctx);
let b6 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1__\${key1}\`, node, ctx);
c_block1[i1] = withKey(block2([txt1], [b6]), key1); c_block1[i1] = withKey(block2([txt1], [b6]), key1);
} }
return list(c_block1, true); return list(c_block1);
}
}"
`;
exports[`Portal Add and remove portals with t-foreach inside div 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, Portal, capture, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div><block-text-0/><block-child-0/></div>\`);
function slot1(ctx, node, key = \\"\\") {
let b5 = text(\` Portal\`);
let b6 = text(ctx['portalId']);
return multi([b5, b6]);
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['portalIds']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`portalId\`] = v_block2[i1];
let key1 = ctx['portalId'];
let txt1 = ctx['portalId'];
const ctx1 = capture(ctx);
let b7 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1__\${key1}\`, node, ctx);
c_block2[i1] = withKey(block3([txt1], [b7]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
} }
}" }"
`; `;
@@ -76,14 +137,14 @@ exports[`Portal Portal composed with t-slot 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal, callSlot } = helpers; let { Portal, callSlot } = helpers;
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {}); return callSlot(ctx, node, key, 'default', false, {});
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return callPortal(ctx, node, key, '#outside', portalContent1); return component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
} }
}" }"
`; `;
@@ -106,17 +167,17 @@ exports[`Portal basic use of portal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -126,17 +187,37 @@ exports[`Portal basic use of portal in dev mode 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]);
}
}"
`;
exports[`Portal basic use of portal on div 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { Portal } = helpers;
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<div><p>2</p></div>\`);
function slot1(ctx, node, key = \\"\\") {
return block2();
}
return function template(ctx, node, key = \\"\\") {
let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -146,11 +227,11 @@ exports[`Portal conditional use of Portal (with sub Component) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block2 = createBlock(\`<span>1</span>\`, true); let block2 = createBlock(\`<span>1</span>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
} }
@@ -158,9 +239,9 @@ exports[`Portal conditional use of Portal (with sub Component) 1`] = `
let b2,b4; let b2,b4;
b2 = block2(); b2 = block2();
if (ctx['state'].hasPortal) { if (ctx['state'].hasPortal) {
b4 = callPortal(ctx, node, key, '#outside', portalContent1); b4 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
} }
return multi([b2, b4], true); return multi([b2, b4]);
} }
}" }"
`; `;
@@ -183,12 +264,12 @@ exports[`Portal conditional use of Portal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block2 = createBlock(\`<span>1</span>\`, true); let block2 = createBlock(\`<span>1</span>\`);
let block3 = createBlock(\`<p>2</p>\`, true); let block3 = createBlock(\`<p>2</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block3(); return block3();
} }
@@ -196,9 +277,99 @@ exports[`Portal conditional use of Portal 1`] = `
let b2,b4; let b2,b4;
b2 = block2(); b2 = block2();
if (ctx['state'].hasPortal) { if (ctx['state'].hasPortal) {
b4 = callPortal(ctx, node, key, '#outside', portalContent1); b4 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
} }
return multi([b2, b4], true); return multi([b2, b4]);
}
}"
`;
exports[`Portal conditional use of Portal with child and div 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['state'].hasPortal) {
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
return multi([b2]);
}
}"
`;
exports[`Portal conditional use of Portal with child and div 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, Portal, capture, withKey } = helpers;
let block1 = createBlock(\`<div><span>hasPortal</span><block-child-0/></div>\`);
let block3 = createBlock(\`<p>thePortal</p>\`);
function slot1(ctx, node, key = \\"\\") {
return block3();
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`elem\`] = v_block2[i1];
let key1 = ctx['elem'];
const ctx1 = capture(ctx);
c_block2[i1] = withKey(component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1__\${key1}\`, node, ctx), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
exports[`Portal conditional use of Portal with child and div, variation 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block2 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['state'].hasPortal) {
let b3 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
b2 = block2([], [b3]);
}
return multi([b2]);
}
}"
`;
exports[`Portal conditional use of Portal with child and div, variation 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { prepareList, Portal, capture, withKey } = helpers;
let block2 = createBlock(\`<span>hasPortal</span>\`);
let block4 = createBlock(\`<p>thePortal</p>\`);
function slot1(ctx, node, key = \\"\\") {
return block4();
}
return function template(ctx, node, key = \\"\\") {
let b2 = block2();
ctx = Object.create(ctx);
const [k_block3, v_block3, l_block3, c_block3] = prepareList([1]);
for (let i1 = 0; i1 < l_block3; i1++) {
ctx[\`elem\`] = v_block3[i1];
let key1 = ctx['elem'];
const ctx1 = capture(ctx);
c_block3[i1] = withKey(component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx1}}}, key + \`__1__\${key1}\`, node, ctx), key1);
}
let b3 = list(c_block3);
return multi([b2, b3]);
} }
}" }"
`; `;
@@ -207,22 +378,22 @@ exports[`Portal conditional use of Portal with div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block2 = createBlock(\`<div><span>hasPortal</span><block-child-0/></div>\`, true); let block2 = createBlock(\`<div><span>hasPortal</span><block-child-0/></div>\`);
let block3 = createBlock(\`<p>thePortal</p>\`, true); let block3 = createBlock(\`<p>thePortal</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block3(); return block3();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2; let b2;
if (ctx['state'].hasPortal) { if (ctx['state'].hasPortal) {
let b4 = callPortal(ctx, node, key, '#outside', portalContent1); let b4 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
b2 = block2([], [b4]); b2 = block2([], [b4]);
} }
return multi([b2], true); return multi([b2]);
} }
}" }"
`; `;
@@ -231,18 +402,18 @@ exports[`Portal lifecycle hooks of portal sub component are properly called 1`]
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3; let b3;
if (ctx['state'].hasChild) { if (ctx['state'].hasChild) {
b3 = callPortal(ctx, node, key, '#outside', portalContent1); b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
} }
return block1([], [b3]); return block1([], [b3]);
} }
@@ -267,12 +438,12 @@ exports[`Portal portal could have dynamically no content 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`); let block3 = createBlock(\`<span><block-text-0/></span>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let b3; let b3;
if (ctx['state'].val) { if (ctx['state'].val) {
let txt1 = ctx['state'].val; let txt1 = ctx['state'].val;
@@ -282,7 +453,7 @@ exports[`Portal portal could have dynamically no content 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b4 = callPortal(ctx, node, key, '#outside', portalContent1); let b4 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b4]); return block1([], [b4]);
} }
}" }"
@@ -292,16 +463,16 @@ exports[`Portal portal destroys on crash 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {error: ctx['state'].error}, key + \`__1\`, node, ctx); return component(\`Child\`, {error: ctx['state'].error}, key + \`__1\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -325,16 +496,16 @@ exports[`Portal portal with child and props 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -358,13 +529,13 @@ exports[`Portal portal with dynamic body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`); let block3 = createBlock(\`<span><block-text-0/></span>\`);
let block4 = createBlock(\`<div/>\`); let block4 = createBlock(\`<div/>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let b3,b4; let b3,b4;
if (ctx['state'].val) { if (ctx['state'].val) {
let txt1 = ctx['state'].val; let txt1 = ctx['state'].val;
@@ -376,7 +547,7 @@ exports[`Portal portal with dynamic body 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b5 = callPortal(ctx, node, key, '#outside', portalContent1); let b5 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -386,20 +557,20 @@ exports[`Portal portal with many children 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div>1</div>\`); let block3 = createBlock(\`<div>1</div>\`);
let block4 = createBlock(\`<p>2</p>\`); let block4 = createBlock(\`<p>2</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let b3 = block3(); let b3 = block3();
let b4 = block4(); let b4 = block4();
return multi([b3, b4]); return multi([b3, b4]);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b5 = callPortal(ctx, node, key, '#outside', portalContent1); let b5 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b5]); return block1([], [b5]);
} }
}" }"
@@ -409,11 +580,11 @@ exports[`Portal portal with no content 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let b3; let b3;
if (false) { if (false) {
b3 = text('ABC'); b3 = text('ABC');
@@ -422,7 +593,7 @@ exports[`Portal portal with no content 1`] = `
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b4 = callPortal(ctx, node, key, '#outside', portalContent1); let b4 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b4]); return block1([], [b4]);
} }
}" }"
@@ -432,16 +603,16 @@ exports[`Portal portal with only text as content 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return text('only text'); return text('only text');
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -451,17 +622,17 @@ exports[`Portal portal with target not in dom 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`); let block2 = createBlock(\`<div>2</div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#does-not-exist', portalContent1); let b3 = component(Portal, {target: '#does-not-exist',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -471,16 +642,16 @@ exports[`Portal portal's parent's env is not polluted 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {}, key + \`__1\`, node, ctx); return component(\`Child\`, {}, key + \`__1\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -522,18 +693,18 @@ exports[`Portal simple catchError with portal 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`); let block1 = createBlock(\`<div><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p><block-text-0/></p>\`); let block2 = createBlock(\`<p><block-text-0/></p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
let txt1 = ctx['a'].b.c; let txt1 = ctx['a'].b.c;
return block2([txt1]); return block2([txt1]);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -543,17 +714,17 @@ exports[`Portal with target in template (after portal) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`); let block1 = createBlock(\`<div><span>1</span><block-child-0/><div id=\\"local-target\\"/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#local-target', portalContent1); let b3 = component(Portal, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -563,17 +734,17 @@ exports[`Portal with target in template (before portal) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`); let block1 = createBlock(\`<div><div id=\\"local-target\\"/><span>1</span><block-child-0/></div>\`);
let block2 = createBlock(\`<p>2</p>\`); let block2 = createBlock(\`<p>2</p>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#local-target', portalContent1); let b3 = component(Portal, {target: '#local-target',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -583,17 +754,17 @@ exports[`Portal: Props validation target must be a valid selector 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`); let block2 = createBlock(\`<div>2</div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, ' ', portalContent1); let b3 = component(Portal, {target: ' ',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -603,17 +774,17 @@ exports[`Portal: Props validation target must be a valid selector 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>2</div>\`); let block2 = createBlock(\`<div>2</div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return block2(); return block2();
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, 'aa', portalContent1); let b3 = component(Portal, {target: 'aa',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__1\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
@@ -623,16 +794,16 @@ exports[`Portal: UI/UX focus is kept across re-renders 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { callPortal } = helpers; let { Portal } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
function portalContent1(ctx, node, key = \\"\\") { function slot1(ctx, node, key = \\"\\") {
return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx);
} }
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b3 = callPortal(ctx, node, key, '#outside', portalContent1); let b3 = component(Portal, {target: '#outside',slots: {'default': {__render: slot1, __ctx: ctx}}}, key + \`__2\`, node, ctx);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
+177
View File
@@ -66,7 +66,24 @@ describe("Portal", () => {
expect(fixture.innerHTML).toBe('<div id="outside"><p>2</p></div><div><span>1</span></div>'); expect(fixture.innerHTML).toBe('<div id="outside"><p>2</p></div><div><span>1</span></div>');
}); });
test("basic use of portal on div", async () => {
class Parent extends Component {
static template = xml`
<div>
<span>1</span>
<div t-portal="'#outside'">
<p>2</p>
</div>
</div>`;
}
addOutsideDiv(fixture);
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe(
'<div id="outside"><div><p>2</p></div></div><div><span>1</span></div>'
);
});
test("simple catchError with portal", async () => { test("simple catchError with portal", async () => {
class Boom extends Component { class Boom extends Component {
static template = xml` static template = xml`
@@ -565,6 +582,39 @@ describe("Portal", () => {
expect(fixture.innerHTML).toBe('<div id="outside"></div>'); expect(fixture.innerHTML).toBe('<div id="outside"></div>');
}); });
test("Add and remove portals on div", async () => {
class Parent extends Component {
static template = xml`
<div t-portal="'#outside'" t-foreach="portalIds" t-as="portalId" t-key="portalId">
Portal<t t-esc="portalId"/>
</div>`;
portalIds = useState([] as any);
}
addOutsideDiv(fixture);
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
parent.portalIds.push(1);
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"><div> Portal1</div></div>');
parent.portalIds.push(2);
await nextTick();
expect(fixture.innerHTML).toBe(
'<div id="outside"><div> Portal1</div><div> Portal2</div></div>'
);
parent.portalIds.pop();
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"><div> Portal1</div></div>');
parent.portalIds.pop();
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
});
test("Add and remove portals with t-foreach", async () => { test("Add and remove portals with t-foreach", async () => {
class Parent extends Component { class Parent extends Component {
static template = xml` static template = xml`
@@ -638,6 +688,133 @@ describe("Portal", () => {
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>' '<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
); );
}); });
test("conditional use of Portal with child and div", async () => {
class Child extends Component {
static template = xml`
<div>
<span>hasPortal</span>
<t t-foreach="[1]" t-as="elem" t-key="elem">
<t t-portal="'#outside'">
<p>thePortal</p>
</t>
</t>
</div>`;
}
class Parent extends Component {
static template = xml`
<t t-if="state.hasPortal">
<Child />
</t>`;
static components = { Child };
state = useState({ hasPortal: false });
}
addOutsideDiv(fixture);
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
parent.state.hasPortal = true;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
);
parent.state.hasPortal = false;
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
parent.state.hasPortal = true;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
);
});
test("conditional use of Portal with child and div, variation", async () => {
class Child extends Component {
static template = xml`
<span>hasPortal</span>
<t t-foreach="[1]" t-as="elem" t-key="elem">
<t t-portal="'#outside'">
<p>thePortal</p>
</t>
</t>`;
}
class Parent extends Component {
static template = xml`
<t t-if="state.hasPortal">
<div>
<Child />
</div>
</t>`;
static components = { Child };
state = useState({ hasPortal: false });
}
addOutsideDiv(fixture);
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
parent.state.hasPortal = true;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
);
parent.state.hasPortal = false;
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"></div>');
parent.state.hasPortal = true;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div id="outside"><p>thePortal</p></div><div><span>hasPortal</span></div>'
);
});
test("Add and remove portals with t-foreach inside div", async () => {
class Parent extends Component {
static template = xml`
<div>
<t t-foreach="portalIds" t-as="portalId" t-key="portalId">
<div>
<t t-esc="portalId"/>
<t t-portal="'#outside'">
Portal<t t-esc="portalId"/>
</t>
</div>
</t>
</div>`;
portalIds = useState([] as any);
}
addOutsideDiv(fixture);
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe('<div id="outside"></div><div></div>');
parent.portalIds.push(1);
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1</div><div><div>1</div></div>');
parent.portalIds.push(2);
await nextTick();
expect(fixture.innerHTML).toBe(
'<div id="outside"> Portal1 Portal2</div><div><div>1</div><div>2</div></div>'
);
parent.portalIds.pop();
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"> Portal1</div><div><div>1</div></div>');
parent.portalIds.pop();
await nextTick();
expect(fixture.innerHTML).toBe('<div id="outside"></div><div></div>');
});
}); });
describe("Portal: UI/UX", () => { describe("Portal: UI/UX", () => {