diff --git a/packages/runtime-core/__tests__/__snapshots__/rendererPortal.spec.ts.snap b/packages/runtime-core/__tests__/__snapshots__/rendererPortal.spec.ts.snap
new file mode 100644
index 000000000..77d48c3cc
--- /dev/null
+++ b/packages/runtime-core/__tests__/__snapshots__/rendererPortal.spec.ts.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renderer: portal should update children 1`] = `"
teleported
"`;
+
+exports[`renderer: portal should update children 2`] = `""`;
+
+exports[`renderer: portal should update children 3`] = `"teleported"`;
+
+exports[`renderer: portal should update target 1`] = `"root
"`;
+
+exports[`renderer: portal should update target 2`] = `"teleported
"`;
+
+exports[`renderer: portal should update target 3`] = `""`;
+
+exports[`renderer: portal should update target 4`] = `"root
"`;
+
+exports[`renderer: portal should update target 5`] = `""`;
+
+exports[`renderer: portal should update target 6`] = `"teleported
"`;
+
+exports[`renderer: portal should work 1`] = `"root
"`;
+
+exports[`renderer: portal should work 2`] = `"teleported
"`;
diff --git a/packages/runtime-core/__tests__/rendererPortal.spec.ts b/packages/runtime-core/__tests__/rendererPortal.spec.ts
index 0f8a6911b..b6fb23b03 100644
--- a/packages/runtime-core/__tests__/rendererPortal.spec.ts
+++ b/packages/runtime-core/__tests__/rendererPortal.spec.ts
@@ -1,3 +1,84 @@
+import {
+ nodeOps,
+ serializeInner,
+ render,
+ h,
+ createComponent,
+ Portal,
+ Text,
+ Fragment,
+ ref,
+ nextTick,
+ TestElement,
+ TestNode
+} from '@vue/runtime-test'
+import { VNodeChildren } from '../src/vnode'
+
describe('renderer: portal', () => {
- test.todo('should work')
+ test('should work', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+
+ const Comp = createComponent(() => () =>
+ h(Fragment, [
+ h(Portal, { target }, h('div', 'teleported')),
+ h('div', 'root')
+ ])
+ )
+ render(h(Comp), root)
+
+ expect(serializeInner(root)).toMatchSnapshot()
+ expect(serializeInner(target)).toMatchSnapshot()
+ })
+
+ test('should update target', async () => {
+ const targetA = nodeOps.createElement('div')
+ const targetB = nodeOps.createElement('div')
+ const target = ref(targetA)
+ const root = nodeOps.createElement('div')
+
+ const Comp = createComponent(() => () =>
+ h(Fragment, [
+ h(Portal, { target }, h('div', 'teleported')),
+ h('div', 'root')
+ ])
+ )
+ render(h(Comp), root)
+
+ expect(serializeInner(root)).toMatchSnapshot()
+ expect(serializeInner(targetA)).toMatchSnapshot()
+ expect(serializeInner(targetB)).toMatchSnapshot()
+
+ target.value = targetB
+ await nextTick()
+
+ expect(serializeInner(root)).toMatchSnapshot()
+ expect(serializeInner(targetA)).toMatchSnapshot()
+ expect(serializeInner(targetB)).toMatchSnapshot()
+ })
+
+ test('should update children', async () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+ const children = ref>([
+ h('div', 'teleported')
+ ])
+
+ const Comp = createComponent(() => () =>
+ h(Portal, { target }, children.value)
+ )
+ render(h(Comp), root)
+
+ expect(serializeInner(target)).toMatchSnapshot()
+
+ children.value = []
+ await nextTick()
+
+ expect(serializeInner(target)).toMatchSnapshot()
+
+ children.value = [h(Text, 'teleported')]
+ await nextTick()
+
+ expect(serializeInner(target)).toMatchSnapshot()
+ })
})
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 1d73fd480..a6999e437 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -680,7 +680,11 @@ export function createRenderer<
isSVG: boolean,
optimized: boolean
) {
- const targetSelector = n2.props && n2.props.target
+ let targetSelector = n2.props && n2.props.target
+ if (isRef(targetSelector)) {
+ targetSelector = targetSelector.value
+ }
+
const { patchFlag, shapeFlag, children } = n2
if (n1 == null) {
const target = (n2.target = isString(targetSelector)
@@ -733,7 +737,7 @@ export function createRenderer<
if (targetSelector !== (n1.props && n1.props.target)) {
const nextTarget = (n2.target = isString(targetSelector)
? hostQuerySelector(targetSelector)
- : null)
+ : targetSelector)
if (nextTarget != null) {
// move content
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {