diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts index 93a02e751..7ab0d99ae 100644 --- a/src/compiler/code_generator.ts +++ b/src/compiler/code_generator.ts @@ -17,7 +17,6 @@ import { ASTTif, ASTTKey, ASTTOut, - ASTTPortal, ASTTranslation, ASTTranslationContext, ASTTSet, @@ -461,8 +460,6 @@ export class CodeGenerator { return this.compileTTranslation(ast, ctx); case ASTType.TTranslationContext: return this.compileTTranslationContext(ast, ctx); - case ASTType.TPortal: - return this.compileTPortal(ast, ctx); } } @@ -1332,26 +1329,4 @@ export class CodeGenerator { } return null; } - compileTPortal(ast: ASTTPortal, ctx: Context): string { - this.helpers.add("Portal"); - - let { block } = ctx; - const name = this.compileInNewTarget("slot", ast.content, ctx); - let id = generateId("comp"); - this.helpers.add("createComponent"); - this.staticDefs.push({ - id, - expr: `createComponent(app, null, false, true, false, false)`, - }); - - const target = compileExpr(ast.target); - const key = this.generateComponentKey(); - const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ctx}}}, ${key}, node, ctx, Portal)`; - if (block) { - this.insertAnchor(block); - } - block = this.createBlock(block, "multi", ctx); - this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false }); - return block.varName; - } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a4c5e9482..d56fceedd 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -27,7 +27,6 @@ export const enum ASTType { TCallBlock, TTranslation, TTranslationContext, - TPortal, } export interface BaseAST { @@ -182,12 +181,6 @@ export interface ASTTranslationContext extends BaseAST { translationCtx: string; } -export interface ASTTPortal extends BaseAST { - type: ASTType.TPortal; - target: string; - content: AST; -} - export type AST = | ASTText | ASTComment @@ -205,8 +198,7 @@ export type AST = | ASTLog | ASTDebug | ASTTranslation - | ASTTranslationContext - | ASTTPortal; + | ASTTranslationContext; // ----------------------------------------------------------------------------- // Parser @@ -252,7 +244,6 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null { parseTDebugLog(node, ctx) || parseTForEach(node, ctx) || parseTIf(node, ctx) || - parseTPortal(node, ctx) || parseTTranslation(node, ctx) || parseTTranslationContext(node, ctx) || parseTCall(node, ctx) || @@ -934,30 +925,6 @@ function parseTTranslationContext(node: Element, ctx: ParsingContext): AST | nul return wrapInTTranslationContextAST(result, translationCtx); } -// ----------------------------------------------------------------------------- -// Portal -// ----------------------------------------------------------------------------- - -function parseTPortal(node: Element, ctx: ParsingContext): AST | null { - if (!node.hasAttribute("t-portal")) { - return null; - } - const target = node.getAttribute("t-portal")!; - node.removeAttribute("t-portal"); - const content = parseNode(node, ctx); - if (!content) { - return { - type: ASTType.Text, - value: "", - }; - } - return { - type: ASTType.TPortal, - target, - content, - }; -} - // ----------------------------------------------------------------------------- // helpers // ----------------------------------------------------------------------------- diff --git a/src/runtime/portal.ts b/src/runtime/portal.ts deleted file mode 100644 index b0e3b5ca1..000000000 --- a/src/runtime/portal.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { OwlError } from "../common/owl_error"; -import { BDom, text, VNode } from "./blockdom"; -import { Component } from "./component"; -import { onMounted, onWillUnmount } from "./lifecycle_hooks"; -import { props } from "./props"; -import { types } from "./types"; - -const VText: any = text("").constructor; - -class VPortal extends VText implements Partial> { - content: BDom | null; - selector: string; - target: HTMLElement | null = null; - - constructor(selector: string, content: BDom) { - super(""); - this.selector = selector; - this.content = content; - } - - mount(parent: HTMLElement, anchor: ChildNode) { - super.mount(parent, anchor); - this.target = document.querySelector(this.selector) as any; - if (this.target) { - this.content!.mount(this.target!, null); - } else { - this.content!.mount(parent, anchor); - } - } - - beforeRemove() { - this.content!.beforeRemove(); - } - remove() { - if (this.content) { - super.remove(); - this.content!.remove(); - this.content = null; - } - } - - patch(other: VPortal) { - super.patch(other); - if (this.content) { - this.content.patch(other.content!, true); - } else { - this.content = other.content; - this.content!.mount(this.target!, null); - } - } -} - -/** - * kind of similar to , but it wraps it around a VPortal - */ -export function portalTemplate(app: any, bdom: any, helpers: any) { - let { callSlot } = helpers; - return function template(ctx: any, node: any, key = ""): any { - return new VPortal(ctx.this.props.target, callSlot(ctx, node, key, "default", false, null)); - }; -} - -export class Portal extends Component { - static template = "__portal__"; - - props = props({ - target: types.string, - }); - - setup() { - const node: any = this.__owl__; - - onMounted(() => { - const portal: VPortal = node.bdom; - if (!portal.target) { - const target: HTMLElement = document.querySelector(node.props.target); - if (target) { - portal.content!.moveBeforeDOMNode(target.firstChild, target); - } else { - throw new OwlError("invalid portal target"); - } - } - }); - - onWillUnmount(() => { - const portal: VPortal = node.bdom; - portal.remove(); - }); - } -} diff --git a/src/runtime/rendering/template_helpers.ts b/src/runtime/rendering/template_helpers.ts index a5bdea5f5..48e906ba0 100644 --- a/src/runtime/rendering/template_helpers.ts +++ b/src/runtime/rendering/template_helpers.ts @@ -4,7 +4,6 @@ import { BDom, createCatcher, multi, text, toggler } from "../blockdom"; import { html } from "../blockdom/index"; import { Component } from "../component"; import { ComponentNode } from "../component_node"; -import { Portal } from "../portal"; import { markRaw } from "../reactivity/proxy"; import { Markup } from "../utils"; import { Fiber } from "./fibers"; @@ -281,6 +280,5 @@ export const helpers = { createRef, modelExpr, createComponent, - Portal, callTemplate, }; diff --git a/src/runtime/template_set.ts b/src/runtime/template_set.ts index 8af09ba6e..7cf13f60c 100644 --- a/src/runtime/template_set.ts +++ b/src/runtime/template_set.ts @@ -3,7 +3,6 @@ import { parseXML } from "../common/utils"; import { compile, CustomDirectives, Template, TemplateFunction } from "../compiler"; import { comment, createBlock, html, list, multi, text, toggler } from "./blockdom"; import { getContext } from "./context"; -import { portalTemplate } from "./portal"; import { helpers } from "./rendering/template_helpers"; const bdom = { text, createBlock, list, multi, html, toggler, comment }; @@ -129,5 +128,3 @@ export function xml(...args: Parameters) { } xml.nextId = 1; - -TemplateSet.registerTemplate("__portal__", portalTemplate); diff --git a/tests/compiler/parser.test.ts b/tests/compiler/parser.test.ts index 64dc09f54..c9ad59246 100644 --- a/tests/compiler/parser.test.ts +++ b/tests/compiler/parser.test.ts @@ -2107,29 +2107,4 @@ describe("qweb parser", () => { ns: null, }); }); - - // --------------------------------------------------------------------------- - // t-portal - // --------------------------------------------------------------------------- - test("t-portal", async () => { - expect(parse(`Content`)).toEqual({ - type: ASTType.TPortal, - target: "target", - content: { type: ASTType.Text, value: "Content" }, - }); - }); - - test("t-portal with t-if", async () => { - expect(parse(`Content`)).toEqual({ - condition: "condition", - content: { - content: { type: ASTType.Text, value: "Content" }, - target: "target", - type: ASTType.TPortal, - }, - tElif: null, - tElse: null, - type: ASTType.TIf, - }); - }); }); diff --git a/tests/misc/__snapshots__/portal.test.ts.snap b/tests/misc/__snapshots__/portal.test.ts.snap deleted file mode 100644 index 928086776..000000000 --- a/tests/misc/__snapshots__/portal.test.ts.snap +++ /dev/null @@ -1,969 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`Portal Add and remove portals 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, Portal, safeOutput, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - function slot1(ctx, node, key = "") { - const b3 = text(\` Portal\`); - const b4 = safeOutput(ctx['portalId']); - return multi([b3, b4]); - } - - return function template(ctx, node, key = "") { - const ctx1 = ctx; - const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['this'].portalIds);; - for (let i1 = 0; i1 < l_block1; i1++) { - let ctx = Object.create(ctx1); - ctx[\`portalId\`] = k_block1[i1]; - const key1 = ctx['portalId']; - c_block1[i1] = withKey(comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal), key1); - } - return list(c_block1); - } -}" -`; - -exports[`Portal Add and remove portals on div 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, Portal, safeOutput, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`
Portal
\`); - - function slot1(ctx, node, key = "") { - const b3 = safeOutput(ctx['portalId']); - return block2([], [b3]); - } - - return function template(ctx, node, key = "") { - const ctx1 = ctx; - const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['this'].portalIds);; - for (let i1 = 0; i1 < l_block1; i1++) { - let ctx = Object.create(ctx1); - ctx[\`portalId\`] = k_block1[i1]; - const key1 = ctx['portalId']; - c_block1[i1] = withKey(comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal), key1); - } - return list(c_block1); - } -}" -`; - -exports[`Portal Add and remove portals with t-foreach 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, safeOutput, Portal, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - const b5 = text(\` Portal\`); - const b6 = safeOutput(ctx['portalId']); - return multi([b5, b6]); - } - - return function template(ctx, node, key = "") { - const ctx1 = ctx; - const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['this'].portalIds);; - for (let i1 = 0; i1 < l_block1; i1++) { - let ctx = Object.create(ctx1); - ctx[\`portalId\`] = k_block1[i1]; - const key1 = ctx['portalId']; - const b3 = safeOutput(ctx['portalId']); - const b7 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal); - c_block1[i1] = withKey(block2([], [b3, b7]), key1); - } - return list(c_block1); - } -}" -`; - -exports[`Portal Add and remove portals with t-foreach and destroy 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, safeOutput, Portal, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - const b5 = text(\` Portal\`); - const b6 = safeOutput(ctx['portalId']); - return multi([b5, b6]); - } - - return function template(ctx, node, key = "") { - const ctx1 = ctx; - const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['this'].portalIds);; - for (let i1 = 0; i1 < l_block1; i1++) { - let ctx = Object.create(ctx1); - ctx[\`portalId\`] = k_block1[i1]; - const key1 = ctx['portalId']; - const b3 = safeOutput(ctx['portalId']); - const b7 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal); - c_block1[i1] = withKey(block2([], [b3, b7]), key1); - } - return list(c_block1); - } -}" -`; - -exports[`Portal Add and remove portals with t-foreach inside div 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, safeOutput, Portal, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block3 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - const b6 = text(\` Portal\`); - const b7 = safeOutput(ctx['portalId']); - return multi([b6, b7]); - } - - return function template(ctx, node, key = "") { - const ctx1 = ctx; - const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['this'].portalIds);; - for (let i1 = 0; i1 < l_block2; i1++) { - let ctx = Object.create(ctx1); - ctx[\`portalId\`] = k_block2[i1]; - const key1 = ctx['portalId']; - const b4 = safeOutput(ctx['portalId']); - const b8 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal); - c_block2[i1] = withKey(block3([], [b4, b8]), key1); - } - const b2 = list(c_block2); - return block1([], [b2]); - } -}" -`; - -exports[`Portal Child and Portal 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, []); - - let block3 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - const b2 = comp1({}, key + \`__1\`, node, this, null); - const b3 = block3(); - return multi([b2, b3]); - } -}" -`; - -exports[`Portal Child and Portal 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`child\`); - let block3 = createBlock(\`portal\`); - - function slot1(ctx, node, key = "") { - return block3(); - } - - return function template(ctx, node, key = "") { - const b2 = block2(); - const b4 = comp1({target: '.portal',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return multi([b2, b4]); - } -}" -`; - -exports[`Portal Portal composed with t-slot 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { createComponent, markRaw } = helpers; - const comp1 = createComponent(app, \`Child2\`, true, false, false, ["customHandler"]); - const comp2 = createComponent(app, \`Child\`, true, true, false, []); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return comp1({customHandler: ctx['this']._handled}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - const b3 = comp2({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__2\`, node, this, null); - return block1([], [b3]); - } -}" -`; - -exports[`Portal Portal composed with t-slot 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, callSlot, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - function slot1(ctx, node, key = "") { - return callSlot(ctx, node, key, 'default', false, {}); - } - - return function template(ctx, node, key = "") { - return comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - } -}" -`; - -exports[`Portal Portal composed with t-slot 3`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const hdlr_fn1 = (ctx, ev) => (ctx['this'].onCustom).call(ctx['this'], ev); - - let block1 = createBlock(\`
child2
\`); - - return function template(ctx, node, key = "") { - let hdlr1 = [hdlr_fn1, ctx]; - return block1([hdlr1]); - } -}" -`; - -exports[`Portal basic use of portal 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal basic use of portal in dev mode 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal basic use of portal on div 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal basic use of portal, variation 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: ctx['this'].target,slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal conditional use of Portal (with sub Component) 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, ["val"]); - const comp2 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`1\`); - - function slot1(ctx, node, key = "") { - return comp1({val: ctx['this'].state.val}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - let b2, b4; - b2 = block2(); - if (ctx['this'].state.hasPortal) { - b4 = comp2({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__2\`, node, ctx, Portal); - } - return multi([b2, b4]); - } -}" -`; - -exports[`Portal conditional use of Portal (with sub Component) 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { safeOutput } = helpers; - - let block1 = createBlock(\`

\`); - - return function template(ctx, node, key = "") { - const b2 = safeOutput(ctx['this'].props.val); - return block1([], [b2]); - } -}" -`; - -exports[`Portal conditional use of Portal 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`1\`); - let block3 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block3(); - } - - return function template(ctx, node, key = "") { - let b2, b4; - b2 = block2(); - if (ctx['this'].state.hasPortal) { - b4 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - } - return multi([b2, b4]); - } -}" -`; - -exports[`Portal conditional use of Portal with child and div 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, []); - - return function template(ctx, node, key = "") { - let b2; - if (ctx['this'].state.hasPortal) { - b2 = comp1({}, key + \`__1\`, node, this, null); - } - return multi([b2]); - } -}" -`; - -exports[`Portal conditional use of Portal with child and div 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, Portal, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
hasPortal
\`); - let block3 = createBlock(\`

thePortal

\`); - - function slot1(ctx, node, key = "") { - return block3(); - } - - return function template(ctx, node, key = "") { - const ctx1 = ctx; - const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);; - for (let i1 = 0; i1 < l_block2; i1++) { - let ctx = Object.create(ctx1); - ctx[\`elem\`] = k_block2[i1]; - const key1 = ctx['elem']; - c_block2[i1] = withKey(comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal), key1); - } - const b2 = list(c_block2); - return block1([], [b2]); - } -}" -`; - -exports[`Portal conditional use of Portal with child and div, variation 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, []); - - let block2 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let b2; - if (ctx['this'].state.hasPortal) { - const b3 = comp1({}, key + \`__1\`, node, this, null); - b2 = block2([], [b3]); - } - return multi([b2]); - } -}" -`; - -exports[`Portal conditional use of Portal with child and div, variation 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { prepareList, Portal, createComponent, withKey } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`hasPortal\`); - let block4 = createBlock(\`

thePortal

\`); - - function slot1(ctx, node, key = "") { - return block4(); - } - - return function template(ctx, node, key = "") { - const b2 = block2(); - const ctx1 = ctx; - const [k_block3, v_block3, l_block3, c_block3] = prepareList([1]);; - for (let i1 = 0; i1 < l_block3; i1++) { - let ctx = Object.create(ctx1); - ctx[\`elem\`] = k_block3[i1]; - const key1 = ctx['elem']; - c_block3[i1] = withKey(comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1__\${key1}\`, node, ctx, Portal), key1); - } - const b3 = list(c_block3); - return multi([b2, b3]); - } -}" -`; - -exports[`Portal conditional use of Portal with div 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`
hasPortal
\`); - let block3 = createBlock(\`

thePortal

\`); - - function slot1(ctx, node, key = "") { - return block3(); - } - - return function template(ctx, node, key = "") { - let b2; - if (ctx['this'].state.hasPortal) { - const b4 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - b2 = block2([], [b4]); - } - return multi([b2]); - } -}" -`; - -exports[`Portal lifecycle hooks of portal sub component are properly called 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, ["val"]); - const comp2 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return comp1({val: ctx['this'].state.val}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - let b3; - if (ctx['this'].state.hasChild) { - b3 = comp2({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__2\`, node, ctx, Portal); - } - return block1([], [b3]); - } -}" -`; - -exports[`Portal lifecycle hooks of portal sub component are properly called 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { safeOutput } = helpers; - - let block1 = createBlock(\`\`); - - return function template(ctx, node, key = "") { - const b2 = safeOutput(ctx['this'].props.val); - return block1([], [b2]); - } -}" -`; - -exports[`Portal portal and Child 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, []); - - let block2 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - const b2 = block2(); - const b3 = comp1({}, key + \`__1\`, node, this, null); - return multi([b2, b3]); - } -}" -`; - -exports[`Portal portal and Child 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block2 = createBlock(\`child\`); - let block3 = createBlock(\`portal\`); - - function slot1(ctx, node, key = "") { - return block3(); - } - - return function template(ctx, node, key = "") { - const b2 = block2(); - const b4 = comp1({target: '.portal',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return multi([b2, b4]); - } -}" -`; - -exports[`Portal portal could have dynamically no content 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, safeOutput, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block3 = createBlock(\`\`); - - function slot1(ctx, node, key = "") { - let b3; - if (ctx['this'].state.val) { - const b4 = safeOutput(ctx['this'].state.val); - b3 = block3([], [b4]); - } - return multi([b3]); - } - - return function template(ctx, node, key = "") { - const b5 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b5]); - } -}" -`; - -exports[`Portal portal destroys on crash 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, ["error"]); - const comp2 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return comp1({error: ctx['this'].state.error}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - const b3 = comp2({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__2\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal portal destroys on crash 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { safeOutput } = helpers; - - let block1 = createBlock(\`\`); - - return function template(ctx, node, key = "") { - const b2 = safeOutput(ctx['this'].props.error&&ctx['this'].will.crash); - return block1([], [b2]); - } -}" -`; - -exports[`Portal portal with child and props 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, ["val"]); - const comp2 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return comp1({val: ctx['this'].state.val}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - const b3 = comp2({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__2\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal portal with child and props 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { safeOutput } = helpers; - - let block1 = createBlock(\`\`); - - return function template(ctx, node, key = "") { - const b2 = safeOutput(ctx['this'].props.val); - return block1([], [b2]); - } -}" -`; - -exports[`Portal portal with dynamic body 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, safeOutput, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block3 = createBlock(\`\`); - let block5 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - let b3, b5; - if (ctx['this'].state.val) { - const b4 = safeOutput(ctx['this'].state.val); - b3 = block3([], [b4]); - } else { - b5 = block5(); - } - return multi([b3, b5]); - } - - return function template(ctx, node, key = "") { - const b6 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b6]); - } -}" -`; - -exports[`Portal portal with many children 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block3 = createBlock(\`
1
\`); - let block4 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - const b3 = block3(); - const b4 = block4(); - return multi([b3, b4]); - } - - return function template(ctx, node, key = "") { - const b5 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b5]); - } -}" -`; - -exports[`Portal portal with no content 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, safeOutput, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - let b3; - if (false) { - b3 = safeOutput('ABC'); - } - return multi([b3]); - } - - return function template(ctx, node, key = "") { - const b4 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b4]); - } -}" -`; - -exports[`Portal portal with only text as content 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, safeOutput, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return safeOutput('only text'); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal portal with target not in dom 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block2 = createBlock(\`
2
\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#does-not-exist',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal simple catchError with portal 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { createComponent } = helpers; - const comp1 = createComponent(app, \`Boom\`, true, false, false, []); - - let block1 = createBlock(\`
\`); - - return function template(ctx, node, key = "") { - let b2, b3; - if (ctx['this'].error) { - b2 = text(\`Error\`); - } else { - b3 = comp1({}, key + \`__1\`, node, this, null); - } - return block1([], [b2, b3]); - } -}" -`; - -exports[`Portal simple catchError with portal 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, safeOutput, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

\`); - - function slot1(ctx, node, key = "") { - const b3 = safeOutput(ctx['a'].b.c); - return block2([], [b3]); - } - - return function template(ctx, node, key = "") { - const b4 = comp1({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b4]); - } -}" -`; - -exports[`Portal with target in template (after portal) 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#local-target',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal with target in template (before portal) 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
1
\`); - let block2 = createBlock(\`

2

\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: '#local-target',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal: Props validation target must be a valid selector 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - const Portal = app.Portal; - const comp1 = app.createComponent(null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block2 = createBlock(\`
2
\`); - - function slot1(ctx, node, key = \\"\\") { - return block2(); - } - - return function template(ctx, node, key = \\"\\") { - const b3 = comp1({target: ' ',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal: Props validation target must be a valid selector 2 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - let block2 = createBlock(\`
2
\`); - - function slot1(ctx, node, key = "") { - return block2(); - } - - return function template(ctx, node, key = "") { - const b3 = comp1({target: 'aa',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__1\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal: UI/UX focus is kept across re-renders 1`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - let { Portal, createComponent } = helpers; - const comp1 = createComponent(app, \`Child\`, true, false, false, ["val"]); - const comp2 = createComponent(app, null, false, true, false, false); - - let block1 = createBlock(\`
\`); - - function slot1(ctx, node, key = "") { - return comp1({val: ctx['this'].state.val}, key + \`__1\`, node, this, null); - } - - return function template(ctx, node, key = "") { - const b3 = comp2({target: '#outside',slots: {'default': {__render: slot1.bind(this), __ctx: ctx}}}, key + \`__2\`, node, ctx, Portal); - return block1([], [b3]); - } -}" -`; - -exports[`Portal: UI/UX focus is kept across re-renders 2`] = ` -"function anonymous(app, bdom, helpers -) { - let { text, createBlock, list, multi, html, toggler, comment } = bdom; - - let block1 = createBlock(\`\`); - - return function template(ctx, node, key = "") { - let attr1 = ctx['this'].props.val; - return block1([attr1]); - } -}" -`; diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts deleted file mode 100644 index aaf674452..000000000 --- a/tests/misc/portal.test.ts +++ /dev/null @@ -1,1024 +0,0 @@ -import { OwlError } from "../../src/common/owl_error"; -import { - App, - Component, - mount, - onError, - onMounted, - onPatched, - onWillPatch, - onWillUnmount, - props, - proxy, -} from "../../src"; -import { xml } from "../../src/"; -import { - elem, - makeTestFixture, - nextAppError, - nextTick, - snapshotEverything, - render, -} from "../helpers"; - -let fixture: HTMLElement; -let originalconsoleWarn = console.warn; -let mockConsoleWarn: any; -const info = console.info; - -function addOutsideDiv(fixture: HTMLElement): HTMLElement { - let outside = document.createElement("div"); - outside.setAttribute("id", "outside"); - fixture.appendChild(outside); - return outside; -} - -snapshotEverything(); - -beforeAll(() => { - console.info = (message: any) => { - if (message === `Owl is running in 'dev' mode.`) { - return; - } - info(message); - }; -}); - -afterAll(() => { - console.info = info; -}); - -beforeEach(() => { - fixture = makeTestFixture(); - mockConsoleWarn = jest.fn(() => {}); - console.warn = mockConsoleWarn; -}); - -afterEach(() => { - console.warn = originalconsoleWarn; -}); - -describe("Portal", () => { - test("basic use of portal", async () => { - class Parent extends Component { - static template = xml` -
- 1 - -

2

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

2

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

2

-
-
`; - target = "#outside"; - } - - addOutsideDiv(fixture); - await mount(Parent, fixture); - - 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` -
- 1 - -

-
-
`; - } - - class Parent extends Component { - static template = xml` -
- Error - - - -
`; - static components = { Boom }; - - error: any = false; - - setup() { - onError((err) => { - this.error = err; - render(this); - }); - } - } - addOutsideDiv(fixture); - - await mount(Parent, fixture); - expect(fixture.innerHTML).toBe('
Error
'); - }); - - test("basic use of portal in dev mode", async () => { - class Parent extends Component { - static template = xml` -
- 1 - -

2

-
-
`; - } - - addOutsideDiv(fixture); - await mount(Parent, fixture, { dev: true }); - - expect(fixture.innerHTML).toBe('

2

1
'); - }); - - test("conditional use of Portal", async () => { - class Parent extends Component { - static template = xml` - 1 - -

2

-
`; - - state = proxy({ hasPortal: false }); - } - - addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); - expect(fixture.innerHTML).toBe('
1'); - - parent.state.hasPortal = true; - await nextTick(); - expect(fixture.innerHTML).toBe('

2

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

2

1'); - }); - - test("conditional use of Portal (with sub Component)", async () => { - class Child extends Component { - static template = xml`

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

1

1'); - - parent.state.hasPortal = false; - await nextTick(); - expect(fixture.innerHTML).toBe('
1'); - - parent.state.val = 2; - await nextTick(); - expect(fixture.innerHTML).toBe('
1'); - - parent.state.hasPortal = true; - await nextTick(); - expect(fixture.innerHTML).toBe('

2

1'); - }); - - test("with target in template (before portal)", async () => { - class Parent extends Component { - static template = xml` -
-
- 1 - -

2

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

2

1
' - ); - }); - - test("with target in template (after portal)", async () => { - class Parent extends Component { - static template = xml` -
- 1 - -

2

-
-
-
`; - } - - await mount(Parent, fixture); - expect(fixture.innerHTML).toBe( - '
1

2

' - ); - }); - - test("portal with target not in dom", async () => { - class Parent extends Component { - static template = xml` -
- -
2
-
-
`; - } - - let error: any; - try { - await mount(Parent, fixture); - } catch (e) { - error = e; - } - expect(error!).toBeDefined(); - expect(error!.cause.message).toBe("invalid portal target"); - expect(fixture.innerHTML).toBe(``); - expect(mockConsoleWarn).toHaveBeenCalledTimes(0); - }); - - test("portal with child and props", async () => { - const steps: string[] = []; - const outside = addOutsideDiv(fixture); - - class Child extends Component { - static template = xml``; - props = props(); - - setup() { - onMounted(() => { - steps.push("mounted"); - expect(outside.innerHTML).toBe("1"); - }); - onPatched(() => { - steps.push("patched"); - expect(outside.innerHTML).toBe("2"); - }); - } - } - class Parent extends Component { - static components = { Child }; - static template = xml` -
- - - -
`; - state = proxy({ val: 1 }); - } - - const parent = await mount(Parent, fixture); - expect(outside.innerHTML).toBe("1"); - expect(fixture.innerHTML).toBe('
1
'); - - parent.state.val = 2; - await nextTick(); - expect(outside.innerHTML).toBe("2"); - expect(fixture.innerHTML).toBe('
2
'); - expect(steps).toEqual(["mounted", "patched"]); - }); - - test("portal with only text as content", async () => { - class Parent extends Component { - static template = xml` -
- - - -
`; - } - - addOutsideDiv(fixture); - await mount(Parent, fixture); - expect(fixture.innerHTML).toBe('
only text
'); - }); - - test("portal with no content", async () => { - class Parent extends Component { - static template = xml` -
- - - -
`; - } - - addOutsideDiv(fixture); - await mount(Parent, fixture); - expect(fixture.innerHTML).toBe(`
`); - }); - - test("portal with many children", async () => { - class Parent extends Component { - static template = xml` -
- -
1
-

2

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

2

'); - }); - - test("portal with dynamic body", async () => { - class Parent extends Component { - static template = xml` -
- - -
- -
`; - state = proxy({ val: "ab" }); - } - - const outside = addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); - - expect(outside.innerHTML).toBe(`ab`); - - parent.state.val = ""; - await nextTick(); - expect(outside.innerHTML).toBe(`
`); - }); - - test("portal could have dynamically no content", async () => { - class Parent extends Component { - static template = xml` -
- - - -
`; - state = proxy({ val: "ab" }); - } - const outside = addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); - - expect(outside.innerHTML).toBe(`ab`); - - parent.state.val = ""; - await nextTick(); - expect(outside.innerHTML).toBe(``); - }); - - test("lifecycle hooks of portal sub component are properly called", async () => { - const steps: any[] = []; - - class Child extends Component { - static template = xml``; - props = props(); - setup() { - onMounted(() => steps.push("child:mounted")); - onWillPatch(() => steps.push("child:willPatch")); - onPatched(() => steps.push("child:patched")); - onWillUnmount(() => steps.push("child:willUnmount")); - } - } - - class Parent extends Component { - static components = { Child }; - static template = xml` -
- - - -
`; - state = proxy({ hasChild: false, val: 1 }); - setup() { - onMounted(() => steps.push("parent:mounted")); - onWillPatch(() => steps.push("parent:willPatch")); - onPatched(() => steps.push("parent:patched")); - onWillUnmount(() => steps.push("parent:willUnmount")); - } - } - - addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); - expect(steps).toEqual(["parent:mounted"]); - expect(fixture.innerHTML).toBe('
'); - - parent.state.hasChild = true; - await nextTick(); - expect(steps).toEqual([ - "parent:mounted", - "parent:willPatch", - "child:mounted", - "parent:patched", - ]); - expect(fixture.innerHTML).toBe('
1
'); - - parent.state.val = 2; - await nextTick(); - expect(steps).toEqual([ - "parent:mounted", - "parent:willPatch", - "child:mounted", - "parent:patched", - "child:willPatch", - "child:patched", - ]); - expect(fixture.innerHTML).toBe('
2
'); - - parent.state.hasChild = false; - await nextTick(); - expect(steps).toEqual([ - "parent:mounted", - "parent:willPatch", - "child:mounted", - "parent:patched", - "child:willPatch", - "child:patched", - "parent:willPatch", - "child:willUnmount", - "parent:patched", - ]); - expect(fixture.innerHTML).toBe('
'); - }); - - test("portal destroys on crash", async () => { - class Child extends Component { - static template = xml``; - props = props(); - state = {}; - } - class Parent extends Component { - static components = { Child }; - static template = xml` -
- - - -
`; - state = { error: false }; - setup() { - onError((_error) => (error = _error)); - } - } - addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); - - let error: Error; - parent.state.error = true; - render(parent); - await nextTick(); - expect(error!).toBeDefined(); - const regexp = - /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g; - expect(error!.message).toMatch(regexp); - }); - - test("Portal composed with t-slot", async () => { - const steps: Array = []; - let childInst: Component | null = null; - class Child2 extends Component { - static template = xml`
child2
`; - props = props(); - setup() { - childInst = this; - } - onCustom(ev: Event) { - this.props.customHandler(ev); - } - } - class Child extends Component { - static components = { Child2 }; - static template = xml` - - - `; - props = props(); - } - class Parent extends Component { - static components = { Child, Child2 }; - static template = xml` -
- - - -
`; - - _handled(ev: Event) { - steps.push(ev.type as string); - } - } - - addOutsideDiv(fixture); - await mount(Parent, fixture); - - elem(childInst!).dispatchEvent(new CustomEvent("custom")); - expect(steps).toEqual(["custom"]); - }); - - test("Add and remove portals", async () => { - class Parent extends Component { - static template = xml` - - Portal - `; - portalIds = proxy([] 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 on div", async () => { - class Parent extends Component { - static template = xml` -
- Portal -
`; - portalIds = proxy([] 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` - -
- - - Portal - -
-
`; - portalIds = proxy([] 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('
'); - }); - - test("Add and remove portals with t-foreach and destroy", async () => { - class Parent extends Component { - static template = xml` - -
- - - Portal - -
-
`; - portalIds = proxy([] as any); - } - - addOutsideDiv(fixture); - const app = new App(); - const parent = await app.createRoot(Parent).mount(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
' - ); - - app.destroy(); - //This will test explicitly that we don't use an await nextTick(); after the destroy. - expect(fixture.innerHTML).toBe('
'); - }); - - test("conditional use of Portal with div", async () => { - class Parent extends Component { - static template = xml` - -
- hasPortal - -

thePortal

-
-
-
`; - - state = proxy({ 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", async () => { - class Child extends Component { - static template = xml` -
- hasPortal - - -

thePortal

-
-
-
`; - } - class Parent extends Component { - static template = xml` - - - `; - - static components = { Child }; - - state = proxy({ 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 = proxy({ 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 = proxy([] 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('
'); - }); - - test("Child and Portal", async () => { - class Child extends Component { - static template = xml` - child - portal`; - } - class Parent extends Component { - static template = xml` - - -
-
`; - - static components = { Child }; - } - await mount(Parent, fixture); - expect(fixture.innerHTML).toBe( - 'child
portal
' - ); - }); - - test("portal and Child", async () => { - class Child extends Component { - static template = xml` - child - portal`; - } - class Parent extends Component { - static template = xml` - -
- -
`; - - static components = { Child }; - } - await mount(Parent, fixture); - expect(fixture.innerHTML).toBe( - '
portal
child' - ); - }); -}); - -describe("Portal: UI/UX", () => { - test("focus is kept across re-renders", async () => { - class Child extends Component { - static template = xml` - `; - props = props(); - } - class Parent extends Component { - static components = { Child }; - static template = xml` -
- - - -
`; - state = proxy({ val: "ab" }); - } - addOutsideDiv(fixture); - const parent = await mount(Parent, fixture); - const input = document.querySelector("#target-me"); - expect(input!.nodeName).toBe("INPUT"); - expect((input as HTMLInputElement).placeholder).toBe("ab"); - - (input as HTMLInputElement).focus(); - expect(document.activeElement === input).toBeTruthy(); - - parent.state.val = "bc"; - await nextTick(); - const inputReRendered = document.querySelector("#target-me"); - expect(inputReRendered!.nodeName).toBe("INPUT"); - expect((inputReRendered as HTMLInputElement).placeholder).toBe("bc"); - expect(document.activeElement === inputReRendered).toBeTruthy(); - }); -}); - -describe("Portal: Props validation", () => { - test("target is mandatory 1", async () => { - class Parent extends Component { - static template = xml` -
- -
2
-
-
`; - } - let error: Error; - try { - await mount(Parent, fixture, { dev: true }); - } catch (e) { - error = e as Error; - } - expect(error!).toBeDefined(); - expect(error!.message).toContain(`attribute without value.`); - }); - - test("target is mandatory 2", async () => { - class Parent extends Component { - static template = xml` -
- -
2
-
-
`; - } - let error: Error; - try { - await mount(Parent, fixture, { dev: true }); - } catch (e) { - error = e as Error; - } - expect(error!).toBeDefined(); - expect(error!.message).toContain(`Unexpected token ','`); - }); - - // why does it fail? - test.skip("target must be a valid selector", async () => { - class Parent extends Component { - static template = xml` -
- -
2
-
-
`; - } - let error: OwlError; - const app = new App(); - const mountProm = app - .createRoot(Parent) - .mount(fixture) - .catch((e: Error) => (error = e)); - await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle"); - await mountProm; - expect(error!).toBeDefined(); - expect(error!.cause).toBeDefined(); - expect(error!.cause.message).toBe(`' ' is not a valid selector`); - }); - - test("target must be a valid selector 2", async () => { - class Parent extends Component { - static template = xml` -
- -
2
-
-
`; - } - let error: any; - try { - await mount(Parent, fixture); - } catch (e) { - error = e; - } - expect(error!.cause.message).toBe(`invalid portal target`); - }); -});