From 9577d70b4bc67c2d195d8a907cc21f210810df01 Mon Sep 17 00:00:00 2001 From: Jorge Pinna Puissant Date: Mon, 17 Jan 2022 15:40:43 +0100 Subject: [PATCH] [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. --- src/app/template_helpers.ts | 13 - src/blockdom/block_compiler.ts | 14 +- src/blockdom/list.ts | 12 +- src/blockdom/multi.ts | 10 +- src/compiler/code_generator.ts | 49 ++- src/compiler/parser.ts | 62 +--- src/index.ts | 3 + src/portal.ts | 27 +- tests/compiler/parser.test.ts | 32 -- tests/misc/__snapshots__/portal.test.ts.snap | 337 ++++++++++++++----- tests/misc/portal.test.ts | 177 ++++++++++ 11 files changed, 496 insertions(+), 240 deletions(-) diff --git a/src/app/template_helpers.ts b/src/app/template_helpers.ts index 7928df60..a641950d 100644 --- a/src/app/template_helpers.ts +++ b/src/app/template_helpers.ts @@ -2,8 +2,6 @@ import { BDom, multi, text, toggler } from "../blockdom"; import { validateProps } from "../component/props_validation"; import { Markup } from "../utils"; import { html } from "../blockdom/index"; -import { VPortal } from "../portal"; - /** * This file contains utility functions that will be injected in each template, * 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; } -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( ctx: any, parent: any, @@ -198,7 +186,6 @@ export const UTILS = { zero: Symbol("zero"), isBoundary, callSlot, - callPortal, capture, withKey, prepareList, diff --git a/src/blockdom/block_compiler.ts b/src/blockdom/block_compiler.ts index 8345c19b..35cfe2b6 100644 --- a/src/blockdom/block_compiler.ts +++ b/src/blockdom/block_compiler.ts @@ -47,7 +47,7 @@ const cache: { [key: string]: BlockType } = {}; * @param str * @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) { return cache[str]; } @@ -67,7 +67,7 @@ export function createBlock(str: string, deepRemove: boolean = false): BlockType // step 3: build the final block class const template = tree.el as HTMLElement; - const Block = buildBlock(template, context, deepRemove); + const Block = buildBlock(template, context); cache[str] = Block; return Block; } @@ -422,7 +422,7 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) { // 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); if (ctx.cbRefs.length) { @@ -447,14 +447,6 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx, deepRemove: boolean): } }; 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); } diff --git a/src/blockdom/list.ts b/src/blockdom/list.ts index 58417b9f..08fd5749 100644 --- a/src/blockdom/list.ts +++ b/src/blockdom/list.ts @@ -17,11 +17,9 @@ class VList { anchor: Node | undefined; parentEl?: HTMLElement | undefined; isOnlyChild?: boolean | undefined; - deepRemove: boolean; - constructor(children: VNode[], deepRemove: boolean) { + constructor(children: VNode[]) { this.children = children; - this.deepRemove = deepRemove; } mount(parent: HTMLElement, afterNode: Node | null) { @@ -77,7 +75,7 @@ class VList { const parent = this.parentEl!; // fast path: no new child => only remove - if (ch2.length === 0 && isOnlyChild && !this.deepRemove) { + if (ch2.length === 0 && isOnlyChild) { if (withBeforeRemove) { for (let i = 0, l = ch1.length; i < l; i++) { beforeRemove.call(ch1[i]); @@ -203,7 +201,7 @@ class VList { remove() { const { parentEl, anchor } = this; - if (this.isOnlyChild && !this.deepRemove) { + if (this.isOnlyChild) { nodeSetTextContent.call(parentEl, ""); } else { const children = this.children; @@ -228,8 +226,8 @@ class VList { } } -export function list(children: VNode[], deepRemove = false): VNode { - return new VList(children, deepRemove); +export function list(children: VNode[]): VNode { + return new VList(children); } function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } { diff --git a/src/blockdom/multi.ts b/src/blockdom/multi.ts index a7cf12fa..c9278208 100644 --- a/src/blockdom/multi.ts +++ b/src/blockdom/multi.ts @@ -15,11 +15,9 @@ export class VMulti { anchors?: Node[] | undefined; parentEl?: HTMLElement | undefined; isOnlyChild?: boolean | undefined; - deepRemove: boolean; - constructor(children: (VNode | undefined)[], deepRemove: boolean) { + constructor(children: (VNode | undefined)[]) { this.children = children; - this.deepRemove = deepRemove; } mount(parent: HTMLElement, afterNode: Node | null) { @@ -105,7 +103,7 @@ export class VMulti { remove() { const parentEl = this.parentEl; - if (this.isOnlyChild && !this.deepRemove) { + if (this.isOnlyChild) { nodeSetTextContent.call(parentEl, ""); } else { const children = this.children; @@ -131,6 +129,6 @@ export class VMulti { } } -export function multi(children: (VNode | undefined)[], deepRemove = false): VNode { - return new VMulti(children, deepRemove); +export function multi(children: (VNode | undefined)[]): VNode { + return new VMulti(children); } diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index dcb8a6bf..673e3cdf 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -65,15 +65,13 @@ class BlockDescription { type: BlockType; parentVar: string = ""; id: number; - deepRemove: boolean; - constructor(target: CodeTarget, type: BlockType, deepRemove: boolean = false) { + constructor(target: CodeTarget, type: BlockType) { this.id = BlockDescription.nextBlockId++; this.varName = "b" + this.id; this.blockName = "block" + this.id; this.target = target; this.type = type; - this.deepRemove = deepRemove; } insertData(str: string, prefix: string = "d"): number { @@ -102,7 +100,7 @@ class BlockDescription { } return `${this.blockName}(${params})`; } else if (this.type === "list") { - return `list(c_block${this.id}${this.deepRemove ? ", true" : ""})`; + return `list(c_block${this.id})`; } return expr; } @@ -253,8 +251,6 @@ export class CodeGenerator { mainCode.push(`const ${id} = getTemplate(${template});`); } - const deepRemove = "deepRemove" in this.ast ? this.ast.deepRemove : false; - // define all blocks if (this.blocks.length) { mainCode.push(``); @@ -264,15 +260,9 @@ export class CodeGenerator { if (block.dynamicTagName) { xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`); xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`); - mainCode.push( - `let ${block.blockName} = tag => createBlock(\`${xmlString}\`${ - deepRemove ? ", true" : "" - });` - ); + mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`); } else { - mainCode.push( - `let ${block.blockName} = createBlock(\`${xmlString}\`${deepRemove ? ", true" : ""});` - ); + mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`); } } } @@ -328,11 +318,10 @@ export class CodeGenerator { createBlock( parentBlock: BlockDescription | null, type: BlockType, - ctx: Context, - deepRemove: boolean = false + ctx: Context ): BlockDescription { const hasRoot = this.target.hasRoot; - const block = new BlockDescription(this.target, type, deepRemove); + const block = new BlockDescription(this.target, type); if (!hasRoot && !ctx.preventRoot) { this.target.hasRoot = true; block.isRoot = true; @@ -718,7 +707,7 @@ export class CodeGenerator { if (ast.body) { const nextId = BlockDescription.nextBlockId; 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"); expr = `withDefault(${expr}, b${nextId})`; } @@ -779,7 +768,7 @@ export class CodeGenerator { // note: this part is duplicated from end of compilemulti: 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) { this.insertAnchor(block); } - block = this.createBlock(block, "list", ctx, ast.deepRemove); + block = this.createBlock(block, "list", ctx); this.target.loopLevel++; const loopVar = `i${this.target.loopLevel}`; this.addLine(`ctx = Object.create(ctx);`); @@ -923,7 +912,7 @@ export class CodeGenerator { } 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"); const nextId = BlockDescription.nextBlockId; 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) { this.helpers.add("zero"); this.addLine(`ctx[zero] = b${nextId};`); @@ -990,7 +979,7 @@ export class CodeGenerator { const expr = ast.value ? compileExpr(ast.value || "") : "null"; if (ast.body) { 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); let value = `new LazyValue(${name}, ctx, node)`; value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value; @@ -1177,10 +1166,18 @@ export class CodeGenerator { } } compileTPortal(ast: ASTTPortal, ctx: Context) { - this.helpers.add("callPortal"); + this.helpers.add("Portal"); + let { block } = ctx; - const name = this.compileInNewTarget("portalContent", ast.content, ctx); - const blockString = `callPortal(ctx, node, key, ${ast.target}, ${name})`; + const name = this.compileInNewTarget("slot", ast.content, ctx); + 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) { this.insertAnchor(block); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 221d26d2..c64fc9f8 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -56,7 +56,6 @@ export interface ASTDomNode { export interface ASTMulti { type: ASTType.Multi; content: AST[]; - deepRemove: boolean; } export interface ASTTEsc { @@ -77,7 +76,6 @@ export interface ASTTif { content: AST; tElif: { condition: string; content: AST }[] | null; tElse: AST | null; - deepRemove: boolean; } export interface ASTTSet { @@ -95,7 +93,6 @@ export interface ASTTForEach { key: string | null; body: AST; memo: string; - deepRemove: boolean; hasNoFirst: boolean; hasNoLast: boolean; hasNoIndex: boolean; @@ -484,7 +481,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null { elem, body, memo, - deepRemove: needDeepRemove(body), key, hasNoFirst, 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 { if (!node.hasAttribute("t-key")) { return null; @@ -628,21 +586,12 @@ function parseTIf(node: Element, ctx: ParsingContext): AST | null { nextElement.remove(); } - let deepRemove = needDeepRemove(content); - if (tElifs) { - deepRemove = deepRemove || tElifs.some((ast) => needDeepRemove(ast)); - } - if (tElse) { - deepRemove = deepRemove || needDeepRemove(tElse); - } - return { type: ASTType.TIf, condition, content, tElif: tElifs.length ? tElifs : null, tElse, - deepRemove, }; } @@ -814,11 +763,6 @@ function parseTPortal(node: Element, ctx: ParsingContext): AST | null { if (!node.hasAttribute("t-portal")) { return null; } - if (node.tagName !== "t") { - throw new Error( - `Directive 't-portal' can only be used on nodes (used on a <${node.tagName}>)` - ); - } const target = node.getAttribute("t-portal")!; node.removeAttribute("t-portal"); const content = parseNode(node, ctx); @@ -869,11 +813,7 @@ function parseChildNodes(node: Node, ctx: ParsingContext): AST | null { case 1: return children[0]; default: - return { - type: ASTType.Multi, - content: children, - deepRemove: children.some((ast) => needDeepRemove(ast)), - }; + return { type: ASTType.Multi, content: children }; } } diff --git a/src/index.ts b/src/index.ts index 741a4639..6bfd0c29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import { UTILS } from "./app/template_helpers"; import { config, createBlock, @@ -12,9 +13,11 @@ import { comment, } from "./blockdom"; import { mainEventHandler } from "./component/handler"; +import { Portal } from "./portal"; config.shouldNormalizeDom = false; config.mainEventHandler = mainEventHandler; +(UTILS as any).Portal = Portal; export const blockDom = { config, diff --git a/src/portal.ts b/src/portal.ts index 23fa759b..5b1de234 100644 --- a/src/portal.ts +++ b/src/portal.ts @@ -1,8 +1,11 @@ +import { onWillUnmount } from "."; +import { xml } from "./app/template_set"; import { BDom, text, VNode } from "./blockdom"; +import { Component } from "./component/component"; const VText: any = text("").constructor; -export class VPortal extends VText implements Partial> { +class VPortal extends VText implements Partial> { // selector: string; realBDom: BDom | null; target: HTMLElement | null = null; @@ -47,3 +50,25 @@ export class VPortal extends VText implements Partial> { } } } + +export class Portal extends Component { + static template = xml``; + 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(); + } + }); + } +} diff --git a/tests/compiler/parser.test.ts b/tests/compiler/parser.test.ts index c6d64474..9a9e4ab0 100644 --- a/tests/compiler/parser.test.ts +++ b/tests/compiler/parser.test.ts @@ -146,7 +146,6 @@ describe("qweb parser", () => { content: [], }, ], - deepRemove: false, }); }); @@ -254,7 +253,6 @@ describe("qweb parser", () => { content: [], }, ], - deepRemove: false, }); }); @@ -505,7 +503,6 @@ describe("qweb parser", () => { }, tElif: null, tElse: null, - deepRemove: false, }, ], }); @@ -521,7 +518,6 @@ describe("qweb parser", () => { }, tElif: null, tElse: null, - deepRemove: false, }); }); @@ -542,7 +538,6 @@ describe("qweb parser", () => { }, tElif: null, tElse: null, - deepRemove: false, }); }); @@ -559,7 +554,6 @@ describe("qweb parser", () => { type: ASTType.Text, value: "else", }, - deepRemove: false, }); }); @@ -578,7 +572,6 @@ describe("qweb parser", () => { }, ], tElse: null, - deepRemove: false, }); }); @@ -600,7 +593,6 @@ describe("qweb parser", () => { type: ASTType.Text, value: "else", }, - deepRemove: false, }); }); @@ -658,7 +650,6 @@ describe("qweb parser", () => { }, ], }, - deepRemove: false, }); }); @@ -740,7 +731,6 @@ describe("qweb parser", () => { }, tElif: null, tElse: null, - deepRemove: false, }); }); @@ -763,7 +753,6 @@ describe("qweb parser", () => { content: { type: ASTType.Text, value: "1" }, tElif: null, tElse: { type: ASTType.TSet, name: "ourvar", value: "0", defaultValue: null, body: null }, - deepRemove: false, }, ], }); @@ -793,7 +782,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -809,7 +797,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -835,7 +822,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -851,7 +837,6 @@ describe("qweb parser", () => { hasNoIndex: true, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -879,7 +864,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -898,7 +882,6 @@ describe("qweb parser", () => { condition: "condition", tElif: null, tElse: null, - deepRemove: false, content: { type: ASTType.DomNode, tag: "span", @@ -916,7 +899,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -949,7 +931,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -977,7 +958,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }, ], }); @@ -1015,7 +995,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -1038,7 +1017,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -1060,7 +1038,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: false, hasNoValue: false, - deepRemove: false, }); }); @@ -1080,7 +1057,6 @@ describe("qweb parser", () => { hasNoIndex: false, hasNoLast: true, hasNoValue: true, - deepRemove: false, }); }); @@ -1130,7 +1106,6 @@ describe("qweb parser", () => { condition: "condition", tElif: null, tElse: null, - deepRemove: false, content: { type: ASTType.TCall, name: "blabla", @@ -1285,7 +1260,6 @@ describe("qweb parser", () => { ns: null, }, ], - deepRemove: false, }, }, }, @@ -1628,7 +1602,6 @@ describe("qweb parser", () => { hasNoValue: true, key: "item_index", memo: "", - deepRemove: false, type: ASTType.TForEach, }); }); @@ -1800,10 +1773,6 @@ describe("qweb parser", () => { }); }); - test("t-portal must be in a node", async () => { - expect(() => parse(`
Content
`)).toThrowError(); - }); - test("t-portal with t-if", async () => { expect(parse(`Content`)).toEqual({ condition: "condition", @@ -1815,7 +1784,6 @@ describe("qweb parser", () => { tElif: null, tElse: null, type: ASTType.TIf, - deepRemove: true, }); }); }); diff --git a/tests/misc/__snapshots__/portal.test.ts.snap b/tests/misc/__snapshots__/portal.test.ts.snap index 8704aa52..53932b53 100644 --- a/tests/misc/__snapshots__/portal.test.ts.snap +++ b/tests/misc/__snapshots__/portal.test.ts.snap @@ -4,9 +4,9 @@ exports[`Portal Add and remove portals 1`] = ` "function anonymous(bdom, helpers ) { 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 b4 = text(ctx['portalId']); return multi([b3, b4]); @@ -18,9 +18,37 @@ exports[`Portal Add and remove portals 1`] = ` for (let i1 = 0; i1 < l_block1; i1++) { ctx[\`portalId\`] = v_block1[i1]; 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(\`
Portal
\`); + + 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 ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { prepareList, callPortal, withKey } = helpers; + let { prepareList, Portal, capture, withKey } = helpers; - let block2 = createBlock(\`
\`, true); + let block2 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { let b4 = text(\` Portal\`); let b5 = text(ctx['portalId']); return multi([b4, b5]); @@ -46,10 +74,43 @@ exports[`Portal Add and remove portals with t-foreach 1`] = ` ctx[\`portalId\`] = v_block1[i1]; let key1 = 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); } - 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(\`
\`); + let block3 = createBlock(\`
\`); + + 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 ) { 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 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 ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
1
\`); let block2 = createBlock(\`

2

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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]); } }" @@ -126,17 +187,37 @@ exports[`Portal basic use of portal in dev mode 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
1
\`); let block2 = createBlock(\`

2

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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(\`
1
\`); + let block2 = createBlock(\`

2

\`); + + 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]); } }" @@ -146,11 +227,11 @@ exports[`Portal conditional use of Portal (with sub Component) 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; - let block2 = createBlock(\`1\`, true); + let block2 = createBlock(\`1\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { 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; b2 = block2(); 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 ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; - let block2 = createBlock(\`1\`, true); - let block3 = createBlock(\`

2

\`, true); + let block2 = createBlock(\`1\`); + let block3 = createBlock(\`

2

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block3(); } @@ -196,9 +277,99 @@ exports[`Portal conditional use of Portal 1`] = ` let b2,b4; b2 = block2(); 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(\`
hasPortal
\`); + let block3 = createBlock(\`

thePortal

\`); + + 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(\`
\`); + + 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(\`hasPortal\`); + let block4 = createBlock(\`

thePortal

\`); + + 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 ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; - let block2 = createBlock(\`
hasPortal
\`, true); - let block3 = createBlock(\`

thePortal

\`, true); + let block2 = createBlock(\`
hasPortal
\`); + let block3 = createBlock(\`

thePortal

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block3(); } return function template(ctx, node, key = \\"\\") { let b2; 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]); } - 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 ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); } return function template(ctx, node, key = \\"\\") { let b3; 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]); } @@ -267,12 +438,12 @@ exports[`Portal portal could have dynamically no content 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); let block3 = createBlock(\`\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { let b3; if (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 = \\"\\") { - 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]); } }" @@ -292,16 +463,16 @@ exports[`Portal portal destroys on crash 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return component(\`Child\`, {error: ctx['state'].error}, key + \`__1\`, node, ctx); } 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]); } }" @@ -325,16 +496,16 @@ exports[`Portal portal with child and props 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); } 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]); } }" @@ -358,13 +529,13 @@ exports[`Portal portal with dynamic body 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); let block3 = createBlock(\`\`); let block4 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { let b3,b4; if (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 = \\"\\") { - 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]); } }" @@ -386,20 +557,20 @@ exports[`Portal portal with many children 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); let block3 = createBlock(\`
1
\`); let block4 = createBlock(\`

2

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { let b3 = block3(); let b4 = block4(); return multi([b3, b4]); } return function template(ctx, node, key = \\"\\") { - let b5 = 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]); } }" @@ -409,11 +580,11 @@ exports[`Portal portal with no content 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { let b3; if (false) { b3 = text('ABC'); @@ -422,7 +593,7 @@ exports[`Portal portal with no content 1`] = ` } 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]); } }" @@ -432,16 +603,16 @@ exports[`Portal portal with only text as content 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return text('only text'); } 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]); } }" @@ -451,17 +622,17 @@ exports[`Portal portal with target not in dom 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); let block2 = createBlock(\`
2
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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]); } }" @@ -471,16 +642,16 @@ exports[`Portal portal's parent's env is not polluted 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return component(\`Child\`, {}, key + \`__1\`, node, ctx); } 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]); } }" @@ -522,18 +693,18 @@ exports[`Portal simple catchError with portal 2`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
1
\`); let block2 = createBlock(\`

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { let txt1 = ctx['a'].b.c; return block2([txt1]); } 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]); } }" @@ -543,17 +714,17 @@ exports[`Portal with target in template (after portal) 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
1
\`); let block2 = createBlock(\`

2

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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]); } }" @@ -563,17 +734,17 @@ exports[`Portal with target in template (before portal) 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
1
\`); let block2 = createBlock(\`

2

\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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]); } }" @@ -583,17 +754,17 @@ exports[`Portal: Props validation target must be a valid selector 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); let block2 = createBlock(\`
2
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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]); } }" @@ -603,17 +774,17 @@ exports[`Portal: Props validation target must be a valid selector 2 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); let block2 = createBlock(\`
2
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return block2(); } 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]); } }" @@ -623,16 +794,16 @@ exports[`Portal: UI/UX focus is kept across re-renders 1`] = ` "function anonymous(bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, component, comment } = bdom; - let { callPortal } = helpers; + let { Portal } = helpers; let block1 = createBlock(\`
\`); - function portalContent1(ctx, node, key = \\"\\") { + function slot1(ctx, node, key = \\"\\") { return component(\`Child\`, {val: ctx['state'].val}, key + \`__1\`, node, ctx); } 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]); } }" diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts index 0abcb3a1..98f3d24e 100644 --- a/tests/misc/portal.test.ts +++ b/tests/misc/portal.test.ts @@ -66,7 +66,24 @@ describe("Portal", () => { expect(fixture.innerHTML).toBe('

2

1
'); }); + test("basic use of portal on div", async () => { + class Parent extends Component { + static template = xml` +
+ 1 +
+

2

+
+
`; + } + addOutsideDiv(fixture); + await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe( + '

2

1
' + ); + }); test("simple catchError with portal", async () => { class Boom extends Component { static template = xml` @@ -565,6 +582,39 @@ describe("Portal", () => { expect(fixture.innerHTML).toBe('
'); }); + test("Add and remove portals on div", async () => { + class Parent extends Component { + static template = xml` +
+ Portal +
`; + portalIds = useState([] as any); + } + + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe('
'); + + parent.portalIds.push(1); + await nextTick(); + expect(fixture.innerHTML).toBe('
Portal1
'); + + parent.portalIds.push(2); + await nextTick(); + expect(fixture.innerHTML).toBe( + '
Portal1
Portal2
' + ); + + parent.portalIds.pop(); + await nextTick(); + expect(fixture.innerHTML).toBe('
Portal1
'); + + parent.portalIds.pop(); + await nextTick(); + expect(fixture.innerHTML).toBe('
'); + }); + test("Add and remove portals with t-foreach", async () => { class Parent extends Component { static template = xml` @@ -638,6 +688,133 @@ describe("Portal", () => { '

thePortal

hasPortal
' ); }); + + test("conditional use of Portal with child and div", async () => { + class Child extends Component { + static template = xml` +
+ hasPortal + + +

thePortal

+
+
+
`; + } + class Parent extends Component { + static template = xml` + + + `; + + static components = { Child }; + + state = useState({ hasPortal: false }); + } + + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe('
'); + + parent.state.hasPortal = true; + await nextTick(); + expect(fixture.innerHTML).toBe( + '

thePortal

hasPortal
' + ); + + parent.state.hasPortal = false; + await nextTick(); + expect(fixture.innerHTML).toBe('
'); + + parent.state.hasPortal = true; + await nextTick(); + expect(fixture.innerHTML).toBe( + '

thePortal

hasPortal
' + ); + }); + + test("conditional use of Portal with child and div, variation", async () => { + class Child extends Component { + static template = xml` + hasPortal + + +

thePortal

+
+
`; + } + class Parent extends Component { + static template = xml` + +
+ +
+
`; + + static components = { Child }; + + state = useState({ hasPortal: false }); + } + + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + expect(fixture.innerHTML).toBe('
'); + + parent.state.hasPortal = true; + await nextTick(); + expect(fixture.innerHTML).toBe( + '

thePortal

hasPortal
' + ); + + parent.state.hasPortal = false; + await nextTick(); + expect(fixture.innerHTML).toBe('
'); + + parent.state.hasPortal = true; + await nextTick(); + expect(fixture.innerHTML).toBe( + '

thePortal

hasPortal
' + ); + }); + test("Add and remove portals with t-foreach inside div", async () => { + class Parent extends Component { + static template = xml` +
+ +
+ + + Portal + +
+
+
`; + portalIds = useState([] as any); + } + + addOutsideDiv(fixture); + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe('
'); + + parent.portalIds.push(1); + await nextTick(); + expect(fixture.innerHTML).toBe('
Portal1
1
'); + + parent.portalIds.push(2); + await nextTick(); + expect(fixture.innerHTML).toBe( + '
Portal1 Portal2
1
2
' + ); + + parent.portalIds.pop(); + await nextTick(); + expect(fixture.innerHTML).toBe('
Portal1
1
'); + + parent.portalIds.pop(); + await nextTick(); + expect(fixture.innerHTML).toBe('
'); + }); }); describe("Portal: UI/UX", () => {