with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("p", null, [
_createElementVNode("span"),
_createElementVNode("span")
_createElementVNode("span"),
_createElementVNode("span")
], -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", null, [
_createCommentVNode("comment")
], -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
with (_ctx) {
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */),
_createTextVNode("foo", -1 /* CACHED */),
_createElementVNode("div", null, null, -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
const _directive_foo = _resolveDirective("foo")
return (_openBlock(), _createElementBlock("div", null, [
- _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
+ _withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
- ]))), [
+ ]))])), [
[_directive_foo]
])
]))
return (_openBlock(), _createElementBlock("div", null, [
ok
- ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ ? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */)
- ])))
+ ]))]))
: _createCommentVNode("v-if", true)
]))
}
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createCommentVNode("comment"),
- _createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", { id: "b" }, [
_createElementVNode("div", { id: "c" }, [
_createElementVNode("div", { id: "d" }, [
])
])
], -1 /* CACHED */)
- ]))
+ ]))])
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
}
}"
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
- return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */)
- ])))
+ ]))]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
const cachedChildrenArrayMatcher = (
tags: string[],
- needArraySpread = false,
+ needArraySpread = true,
) => ({
type: NodeTypes.JS_CACHE_EXPRESSION,
needArraySpread,
{
/* _ slot flag */
},
- {
- type: NodeTypes.JS_PROPERTY,
- key: { content: '__' },
- value: { content: '[0]' },
- },
],
})
})
{
/* _ slot flag */
},
- {
- type: NodeTypes.JS_PROPERTY,
- key: { content: '__' },
- value: { content: '[0]' },
- },
],
})
})
type RootNode,
type SimpleExpressionNode,
type SlotFunctionExpression,
- type SlotsObjectProperty,
type TemplateChildNode,
type TemplateNode,
type TextCallNode,
type VNodeCall,
createArrayExpression,
- createObjectProperty,
- createSimpleExpression,
getVNodeBlockHelper,
getVNodeHelper,
} from '../ast'
}
let cachedAsArray = false
- const slotCacheKeys = []
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
if (
node.tagType === ElementTypes.ELEMENT &&
// default slot
const slot = getSlotNode(node.codegenNode, 'default')
if (slot) {
- slotCacheKeys.push(context.cached.length)
slot.returns = getCacheExpression(
createArrayExpression(slot.returns as TemplateChildNode[]),
)
slotName.arg &&
getSlotNode(parent.codegenNode, slotName.arg)
if (slot) {
- slotCacheKeys.push(context.cached.length)
slot.returns = getCacheExpression(
createArrayExpression(slot.returns as TemplateChildNode[]),
)
if (!cachedAsArray) {
for (const child of toCache) {
- slotCacheKeys.push(context.cached.length)
child.codegenNode = context.cache(child.codegenNode!)
}
}
- // put the slot cached keys on the slot object, so that the cache
- // can be removed when component unmounting to prevent memory leaks
- if (
- slotCacheKeys.length &&
- node.type === NodeTypes.ELEMENT &&
- node.tagType === ElementTypes.COMPONENT &&
- node.codegenNode &&
- node.codegenNode.type === NodeTypes.VNODE_CALL &&
- node.codegenNode.children &&
- !isArray(node.codegenNode.children) &&
- node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
- ) {
- node.codegenNode.children.properties.push(
- createObjectProperty(
- `__`,
- createSimpleExpression(JSON.stringify(slotCacheKeys), false),
- ) as SlotsObjectProperty,
- )
- }
-
function getCacheExpression(value: JSChildNode): CacheExpression {
const exp = context.cache(value)
// #6978, #7138, #7114
// a cached children array inside v-for can caused HMR errors since
// it might be mutated when mounting the first item
- if (inFor && context.hmr) {
- exp.needArraySpread = true
- }
+ // #13221
+ // fix memory leak in cached array:
+ // cached vnodes get replaced by cloned ones during mountChildren,
+ // which bind DOM elements. These DOM references persist after unmount,
+ // preventing garbage collection. Array spread avoids mutating cached
+ // array, preventing memory leaks.
+ exp.needArraySpread = true
return exp
}
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
- ])))
+ ]))]))
}"
`;
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
- ])))
+ ]))]))
}"
`;
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
- ])))
+ ]))]))
}"
`;
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("select", null, [
_createElementVNode("option", { value: null }),
_createElementVNode("option", { value: "1" }),
_createElementVNode("option", { value: "1" }),
_createElementVNode("option", { value: "1" })
], -1 /* CACHED */)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("select", null, [
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 })
], -1 /* CACHED */)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", null, [
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("img", { src: _imports_0_ })
], -1 /* CACHED */)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
- ])))
+ ]))]))
}"
`;
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
- ])))
+ ]))]))
}"
`;
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
- ])))
+ ]))]))
}"
`;
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
- ])))
+ ]))]))
}"
`;
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
false,
)
- expect(slots).toHaveProperty('__')
- expect(Object.getOwnPropertyDescriptor(slots, '__')!.enumerable).toBe(
- false,
- )
return h('div')
},
}
- const slots = { foo: () => {}, _: 1, __: [1] }
+ const slots = { foo: () => {}, _: 1 }
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
})
* @internal
*/
_?: SlotFlags
- /**
- * cache indexes for slot content
- * @internal
- */
- __?: number[]
}
const isInternalKey = (key: string) =>
- key === '_' || key === '__' || key === '_ctx' || key === '$stable'
+ key === '_' || key === '_ctx' || key === '$stable'
const normalizeSlotValue = (value: unknown): VNode[] =>
isArray(value)
): void => {
const slots = (instance.slots = createInternalObject())
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
- const cacheIndexes = (children as RawSlots).__
- // make cache indexes marker non-enumerable
- if (cacheIndexes) def(slots, '__', cacheIndexes, true)
-
const type = (children as RawSlots)._
if (type) {
assignSlots(slots, children as Slots, optimized)
unregisterHMR(instance)
}
- const {
- bum,
- scope,
- job,
- subTree,
- um,
- m,
- a,
- parent,
- slots: { __: slotCacheKeys },
- } = instance
+ const { bum, scope, job, subTree, um, m, a } = instance
invalidateMount(m)
invalidateMount(a)
invokeArrayFns(bum)
}
- // remove slots content from parent renderCache
- if (parent && isArray(slotCacheKeys)) {
- slotCacheKeys.forEach(v => {
- parent.renderCache[v] = undefined
- })
- }
-
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
traverseStaticChildren(c1, c2)
}
// #6852 also inherit for text nodes
- if (c2.type === Text) {
+ if (
+ c2.type === Text &&
+ // avoid cached text nodes retaining detached dom nodes
+ c2.patchFlag !== PatchFlags.CACHED
+ ) {
c2.el = c1.el
}
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
},
E2E_TIMEOUT,
)
+
+ // #13211
+ test(
+ 'cached array vnodes should not retaining detached DOM nodes',
+ async () => {
+ const client = await page().createCDPSession()
+ await page().evaluate(async () => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ components: {
+ Comp1: {
+ template: `
+ <h1><slot></slot></h1>
+ <div>{{ test.length }}</div>
+ `,
+ setup() {
+ const test = ref([...Array(3000)].map((_, i) => ({ i })))
+ // @ts-expect-error
+ window.__REF__ = new WeakRef(test)
+
+ return { test }
+ },
+ },
+ },
+ template: `
+ <button id="toggleBtn" @click="click">button</button>
+ <Comp1 v-if="toggle">slot content</Comp1>
+ `,
+ setup() {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ },
+ }).mount('#app')
+ })
+
+ expect(await html('#app')).toBe(
+ `<button id="toggleBtn">button</button>` +
+ `<h1>` +
+ `slot content` +
+ `</h1>` +
+ `<div>3000</div>`,
+ )
+
+ await click('#toggleBtn')
+ expect(await html('#app')).toBe(
+ `<button id="toggleBtn">button</button><!--v-if-->`,
+ )
+
+ const isCollected = async () =>
+ // @ts-expect-error
+ await page().evaluate(() => window.__REF__.deref() === undefined)
+
+ while ((await isCollected()) === false) {
+ await client.send('HeapProfiler.collectGarbage')
+ }
+
+ expect(await isCollected()).toBe(true)
+ },
+ E2E_TIMEOUT,
+ )
+
+ // https://github.com/element-plus/element-plus/issues/21408
+ test(
+ 'cached text nodes in Fragment should not retaining detached DOM nodes',
+ async () => {
+ const client = await page().createCDPSession()
+ await page().evaluate(async () => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ components: {
+ Comp: {
+ template: `<div>{{ test.length }}</div>`,
+ setup() {
+ const test = ref([...Array(3000)].map((_, i) => ({ i })))
+ // @ts-expect-error
+ window.__REF__ = new WeakRef(test)
+
+ return { test }
+ },
+ },
+ },
+ template: `
+ <button id="addBtn" @click="add">add</button>
+ <button id="toggleBtn" @click="click">button</button>
+ <div v-if="toggle">
+ <template v-for="item in items" :key="item">
+ text
+ <div>{{ item }}</div>
+ </template>
+ <Comp/>
+ </div>
+ `,
+ setup() {
+ const toggle = ref(true)
+ const items = ref([1])
+ const click = () => (toggle.value = !toggle.value)
+ const add = () => items.value.push(2)
+ return { toggle, click, items, add }
+ },
+ }).mount('#app')
+ })
+
+ expect(await html('#app')).toBe(
+ `<button id="addBtn">add</button>` +
+ `<button id="toggleBtn">button</button>` +
+ `<div>` +
+ ` text ` +
+ `<div>1</div>` +
+ `<div>3000</div></div>`,
+ )
+
+ await click('#addBtn')
+ expect(await html('#app')).toBe(
+ `<button id="addBtn">add</button>` +
+ `<button id="toggleBtn">button</button>` +
+ `<div>` +
+ ` text ` +
+ `<div>1</div>` +
+ ` text ` +
+ `<div>2</div>` +
+ `<div>3000</div></div>`,
+ )
+
+ await click('#toggleBtn')
+ expect(await html('#app')).toBe(
+ `<button id="addBtn">add</button>` +
+ `<button id="toggleBtn">button</button><!--v-if-->`,
+ )
+
+ const isCollected = async () =>
+ // @ts-expect-error
+ await page().evaluate(() => window.__REF__.deref() === undefined)
+
+ while ((await isCollected()) === false) {
+ await client.send('HeapProfiler.collectGarbage')
+ }
+
+ expect(await isCollected()).toBe(true)
+ },
+ E2E_TIMEOUT,
+ )
})