| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936 |
- import {
- h,
- Fragment,
- Teleport,
- createVNode,
- createCommentVNode,
- openBlock,
- createBlock,
- render,
- nodeOps,
- TestElement,
- serialize,
- serializeInner as inner,
- VNode,
- ref,
- nextTick,
- defineComponent,
- withCtx,
- renderSlot,
- onBeforeUnmount,
- createTextVNode,
- SetupContext,
- createApp,
- FunctionalComponent,
- renderList,
- onUnmounted
- } from '@vue/runtime-test'
- import { PatchFlags, SlotFlags } from '@vue/shared'
- import { SuspenseImpl } from '../src/components/Suspense'
- describe('renderer: optimized mode', () => {
- let root: TestElement
- let block: VNode | null = null
- beforeEach(() => {
- root = nodeOps.createElement('div')
- block = null
- })
- const renderWithBlock = (renderChildren: () => VNode[]) => {
- render(
- (openBlock(), (block = createBlock('div', null, renderChildren()))),
- root
- )
- }
- test('basic use of block', () => {
- render((openBlock(), (block = createBlock('p', null, 'foo'))), root)
- expect(block.dynamicChildren!.length).toBe(0)
- expect(inner(root)).toBe('<p>foo</p>')
- })
- test('block can appear anywhere in the vdom tree', () => {
- render(
- h('div', (openBlock(), (block = createBlock('p', null, 'foo')))),
- root
- )
- expect(block.dynamicChildren!.length).toBe(0)
- expect(inner(root)).toBe('<div><p>foo</p></div>')
- })
- test('block should collect dynamic vnodes', () => {
- renderWithBlock(() => [
- createVNode('p', null, 'foo', PatchFlags.TEXT),
- createVNode('i')
- ])
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foo</p>'
- )
- })
- test('block can disable tracking', () => {
- render(
- // disable tracking
- (openBlock(true),
- (block = createBlock('div', null, [
- createVNode('p', null, 'foo', PatchFlags.TEXT)
- ]))),
- root
- )
- expect(block.dynamicChildren!.length).toBe(0)
- })
- test('block as dynamic children', () => {
- renderWithBlock(() => [
- (openBlock(), createBlock('div', { key: 0 }, [h('p')]))
- ])
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(0)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<div><p></p></div>'
- )
- renderWithBlock(() => [
- (openBlock(), createBlock('div', { key: 1 }, [h('i')]))
- ])
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(0)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<div><i></i></div>'
- )
- })
- test('PatchFlags: PatchFlags.TEXT', async () => {
- renderWithBlock(() => [createVNode('p', null, 'foo', PatchFlags.TEXT)])
- expect(inner(root)).toBe('<div><p>foo</p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foo</p>'
- )
- renderWithBlock(() => [createVNode('p', null, 'bar', PatchFlags.TEXT)])
- expect(inner(root)).toBe('<div><p>bar</p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p>bar</p>'
- )
- })
- test('PatchFlags: PatchFlags.CLASS', async () => {
- renderWithBlock(() => [
- createVNode('p', { class: 'foo' }, '', PatchFlags.CLASS)
- ])
- expect(inner(root)).toBe('<div><p class="foo"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p class="foo"></p>'
- )
- renderWithBlock(() => [
- createVNode('p', { class: 'bar' }, '', PatchFlags.CLASS)
- ])
- expect(inner(root)).toBe('<div><p class="bar"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p class="bar"></p>'
- )
- })
- test('PatchFlags: PatchFlags.STYLE', async () => {
- renderWithBlock(() => [
- createVNode('p', { style: 'color: red' }, '', PatchFlags.STYLE)
- ])
- expect(inner(root)).toBe('<div><p style="color: red"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p style="color: red"></p>'
- )
- renderWithBlock(() => [
- createVNode('p', { style: 'color: green' }, '', PatchFlags.STYLE)
- ])
- expect(inner(root)).toBe('<div><p style="color: green"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p style="color: green"></p>'
- )
- })
- test('PatchFlags: PatchFlags.PROPS', async () => {
- renderWithBlock(() => [
- createVNode('p', { id: 'foo' }, '', PatchFlags.PROPS, ['id'])
- ])
- expect(inner(root)).toBe('<div><p id="foo"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p id="foo"></p>'
- )
- renderWithBlock(() => [
- createVNode('p', { id: 'bar' }, '', PatchFlags.PROPS, ['id'])
- ])
- expect(inner(root)).toBe('<div><p id="bar"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p id="bar"></p>'
- )
- })
- test('PatchFlags: PatchFlags.FULL_PROPS', async () => {
- let propName = 'foo'
- renderWithBlock(() => [
- createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS)
- ])
- expect(inner(root)).toBe('<div><p foo="dynamic"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p foo="dynamic"></p>'
- )
- propName = 'bar'
- renderWithBlock(() => [
- createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS)
- ])
- expect(inner(root)).toBe('<div><p bar="dynamic"></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p bar="dynamic"></p>'
- )
- })
- // the order and length of the list will not change
- test('PatchFlags: PatchFlags.STABLE_FRAGMENT', async () => {
- let list = ['foo', 'bar']
- render(
- (openBlock(),
- (block = createBlock(
- Fragment,
- null,
- list.map(item => {
- return createVNode('p', null, item, PatchFlags.TEXT)
- }),
- PatchFlags.STABLE_FRAGMENT
- ))),
- root
- )
- expect(inner(root)).toBe('<p>foo</p><p>bar</p>')
- expect(block.dynamicChildren!.length).toBe(2)
- expect(serialize(block.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foo</p>'
- )
- expect(serialize(block.dynamicChildren![1].el as TestElement)).toBe(
- '<p>bar</p>'
- )
- list = list.map(item => item.repeat(2))
- render(
- (openBlock(),
- createBlock(
- Fragment,
- null,
- list.map(item => {
- return createVNode('p', null, item, PatchFlags.TEXT)
- }),
- PatchFlags.STABLE_FRAGMENT
- )),
- root
- )
- expect(inner(root)).toBe('<p>foofoo</p><p>barbar</p>')
- expect(block.dynamicChildren!.length).toBe(2)
- expect(serialize(block.dynamicChildren![0].el as TestElement)).toBe(
- '<p>foofoo</p>'
- )
- expect(serialize(block.dynamicChildren![1].el as TestElement)).toBe(
- '<p>barbar</p>'
- )
- })
- // A Fragment with `UNKEYED_FRAGMENT` flag will always patch its children,
- // so there's no need for tracking dynamicChildren.
- test('PatchFlags: PatchFlags.UNKEYED_FRAGMENT', async () => {
- const list = [{ tag: 'p', text: 'foo' }]
- render(
- (openBlock(true),
- (block = createBlock(
- Fragment,
- null,
- list.map(item => {
- return createVNode(item.tag, null, item.text)
- }),
- PatchFlags.UNKEYED_FRAGMENT
- ))),
- root
- )
- expect(inner(root)).toBe('<p>foo</p>')
- expect(block.dynamicChildren!.length).toBe(0)
- list.unshift({ tag: 'i', text: 'bar' })
- render(
- (openBlock(true),
- createBlock(
- Fragment,
- null,
- list.map(item => {
- return createVNode(item.tag, null, item.text)
- }),
- PatchFlags.UNKEYED_FRAGMENT
- )),
- root
- )
- expect(inner(root)).toBe('<i>bar</i><p>foo</p>')
- expect(block.dynamicChildren!.length).toBe(0)
- })
- // A Fragment with `KEYED_FRAGMENT` will always patch its children,
- // so there's no need for tracking dynamicChildren.
- test('PatchFlags: PatchFlags.KEYED_FRAGMENT', async () => {
- const list = [{ tag: 'p', text: 'foo' }]
- render(
- (openBlock(true),
- (block = createBlock(
- Fragment,
- null,
- list.map(item => {
- return createVNode(item.tag, { key: item.tag }, item.text)
- }),
- PatchFlags.KEYED_FRAGMENT
- ))),
- root
- )
- expect(inner(root)).toBe('<p>foo</p>')
- expect(block.dynamicChildren!.length).toBe(0)
- list.unshift({ tag: 'i', text: 'bar' })
- render(
- (openBlock(true),
- createBlock(
- Fragment,
- null,
- list.map(item => {
- return createVNode(item.tag, { key: item.tag }, item.text)
- }),
- PatchFlags.KEYED_FRAGMENT
- )),
- root
- )
- expect(inner(root)).toBe('<i>bar</i><p>foo</p>')
- expect(block.dynamicChildren!.length).toBe(0)
- })
- test('PatchFlags: PatchFlags.NEED_PATCH', async () => {
- const spyMounted = vi.fn()
- const spyUpdated = vi.fn()
- const count = ref(0)
- const Comp = {
- setup() {
- return () => {
- count.value
- return (
- openBlock(),
- (block = createBlock('div', null, [
- createVNode(
- 'p',
- { onVnodeMounted: spyMounted, onVnodeBeforeUpdate: spyUpdated },
- '',
- PatchFlags.NEED_PATCH
- )
- ]))
- )
- }
- }
- }
- render(h(Comp), root)
- expect(inner(root)).toBe('<div><p></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p></p>'
- )
- expect(spyMounted).toHaveBeenCalledTimes(1)
- expect(spyUpdated).toHaveBeenCalledTimes(0)
- count.value++
- await nextTick()
- expect(inner(root)).toBe('<div><p></p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
- '<p></p>'
- )
- expect(spyMounted).toHaveBeenCalledTimes(1)
- expect(spyUpdated).toHaveBeenCalledTimes(1)
- })
- test('PatchFlags: PatchFlags.BAIL', async () => {
- render(
- (openBlock(),
- (block = createBlock('div', null, [createVNode('p', null, 'foo')]))),
- root
- )
- expect(inner(root)).toBe('<div><p>foo</p></div>')
- expect(block!.dynamicChildren!.length).toBe(0)
- render(
- (openBlock(),
- (block = createBlock(
- 'div',
- null,
- [createVNode('i', null, 'bar')],
- PatchFlags.BAIL
- ))),
- root
- )
- expect(inner(root)).toBe('<div><i>bar</i></div>')
- expect(block!.dynamicChildren).toBe(null)
- })
- // #1980
- test('dynamicChildren should be tracked correctly when normalizing slots to plain children', async () => {
- let block: VNode
- const Comp = defineComponent({
- setup(_props, { slots }) {
- return () => {
- const vnode =
- (openBlock(),
- (block = createBlock('div', null, {
- default: withCtx(() => [renderSlot(slots, 'default')]),
- _: SlotFlags.FORWARDED
- })))
- return vnode
- }
- }
- })
- const foo = ref(0)
- const App = {
- setup() {
- return () => {
- return createVNode(Comp, null, {
- default: withCtx(() => [
- createVNode('p', null, foo.value, PatchFlags.TEXT)
- ]),
- // Indicates that this is a stable slot to avoid bail out
- _: SlotFlags.STABLE
- })
- }
- }
- }
- render(h(App), root)
- expect(inner(root)).toBe('<div><p>0</p></div>')
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(block!.dynamicChildren![0].type).toBe(Fragment)
- expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
- expect(
- serialize(
- block!.dynamicChildren![0].dynamicChildren![0].el as TestElement
- )
- ).toBe('<p>0</p>')
- foo.value++
- await nextTick()
- expect(inner(root)).toBe('<div><p>1</p></div>')
- })
- // #2169
- // block
- // - dynamic child (1)
- // - component (2)
- // When unmounting (1), we know we are in optimized mode so no need to further
- // traverse unmount its children
- test('should not perform unnecessary unmount traversals', () => {
- const spy = vi.fn()
- const Child = {
- setup() {
- onBeforeUnmount(spy)
- return () => 'child'
- }
- }
- const Parent = () => (
- openBlock(),
- createBlock('div', null, [
- createVNode('div', { style: {} }, [createVNode(Child)], 4 /* STYLE */)
- ])
- )
- render(h(Parent), root)
- render(null, root)
- expect(spy).toHaveBeenCalledTimes(1)
- })
- // #2444
- // `KEYED_FRAGMENT` and `UNKEYED_FRAGMENT` always need to diff its children
- test('non-stable Fragment always need to diff its children', () => {
- const spyA = vi.fn()
- const spyB = vi.fn()
- const ChildA = {
- setup() {
- onBeforeUnmount(spyA)
- return () => 'child'
- }
- }
- const ChildB = {
- setup() {
- onBeforeUnmount(spyB)
- return () => 'child'
- }
- }
- const Parent = () => (
- openBlock(),
- createBlock('div', null, [
- (openBlock(true),
- createBlock(
- Fragment,
- null,
- [createVNode(ChildA, { key: 0 })],
- 128 /* KEYED_FRAGMENT */
- )),
- (openBlock(true),
- createBlock(
- Fragment,
- null,
- [createVNode(ChildB)],
- 256 /* UNKEYED_FRAGMENT */
- ))
- ])
- )
- render(h(Parent), root)
- render(null, root)
- expect(spyA).toHaveBeenCalledTimes(1)
- expect(spyB).toHaveBeenCalledTimes(1)
- })
- // #2893
- test('manually rendering the optimized slots should allow subsequent updates to exit the optimized mode correctly', async () => {
- const state = ref(0)
- const CompA = {
- setup(props: any, { slots }: SetupContext) {
- return () => {
- return (
- openBlock(),
- createBlock('div', null, [renderSlot(slots, 'default')])
- )
- }
- }
- }
- const Wrapper = {
- setup(props: any, { slots }: SetupContext) {
- // use the manually written render function to rendering the optimized slots,
- // which should make subsequent updates exit the optimized mode correctly
- return () => {
- return slots.default!()[state.value]
- }
- }
- }
- const app = createApp({
- setup() {
- return () => {
- return (
- openBlock(),
- createBlock(Wrapper, null, {
- default: withCtx(() => [
- createVNode(CompA, null, {
- default: withCtx(() => [createTextVNode('Hello')]),
- _: 1 /* STABLE */
- }),
- createVNode(CompA, null, {
- default: withCtx(() => [createTextVNode('World')]),
- _: 1 /* STABLE */
- })
- ]),
- _: 1 /* STABLE */
- })
- )
- }
- }
- })
- app.mount(root)
- expect(inner(root)).toBe('<div>Hello</div>')
- state.value = 1
- await nextTick()
- expect(inner(root)).toBe('<div>World</div>')
- })
- //#3623
- test('nested teleport unmount need exit the optimization mode', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
- render(
- (openBlock(),
- createBlock('div', null, [
- (openBlock(),
- createBlock(
- Teleport as any,
- {
- to: target
- },
- [
- createVNode('div', null, [
- (openBlock(),
- createBlock(
- Teleport as any,
- {
- to: target
- },
- [createVNode('div', null, 'foo')]
- ))
- ])
- ]
- ))
- ])),
- root
- )
- expect(inner(target)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--></div><div>foo</div>"`
- )
- expect(inner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--></div>"`
- )
- render(null, root)
- expect(inner(target)).toBe('')
- })
- // #3548
- test('should not track dynamic children when the user calls a compiled slot inside template expression', () => {
- const Comp = {
- setup(props: any, { slots }: SetupContext) {
- return () => {
- return (
- openBlock(),
- (block = createBlock('section', null, [
- renderSlot(slots, 'default')
- ]))
- )
- }
- }
- }
- let dynamicVNode: VNode
- const Wrapper = {
- setup(props: any, { slots }: SetupContext) {
- return () => {
- return (
- openBlock(),
- createBlock(Comp, null, {
- default: withCtx(() => {
- return [
- (dynamicVNode = createVNode(
- 'div',
- {
- class: {
- foo: !!slots.default!()
- }
- },
- null,
- PatchFlags.CLASS
- ))
- ]
- }),
- _: 1
- })
- )
- }
- }
- }
- const app = createApp({
- render() {
- return (
- openBlock(),
- createBlock(Wrapper, null, {
- default: withCtx(() => {
- return [createVNode({}) /* component */]
- }),
- _: 1
- })
- )
- }
- })
- app.mount(root)
- expect(inner(root)).toBe('<section><div class="foo"></div></section>')
- /**
- * Block Tree:
- * - block(div)
- * - block(Fragment): renderSlots()
- * - dynamicVNode
- */
- expect(block!.dynamicChildren!.length).toBe(1)
- expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
- expect(block!.dynamicChildren![0].dynamicChildren![0]).toEqual(
- dynamicVNode!
- )
- })
- // 3569
- test('should force bailout when the user manually calls the slot function', async () => {
- const index = ref(0)
- const Foo = {
- setup(props: any, { slots }: SetupContext) {
- return () => {
- return slots.default!()[index.value]
- }
- }
- }
- const app = createApp({
- setup() {
- return () => {
- return (
- openBlock(),
- createBlock(Foo, null, {
- default: withCtx(() => [
- true
- ? (openBlock(), createBlock('p', { key: 0 }, '1'))
- : createCommentVNode('v-if', true),
- true
- ? (openBlock(), createBlock('p', { key: 0 }, '2'))
- : createCommentVNode('v-if', true)
- ]),
- _: 1 /* STABLE */
- })
- )
- }
- }
- })
- app.mount(root)
- expect(inner(root)).toBe('<p>1</p>')
- index.value = 1
- await nextTick()
- expect(inner(root)).toBe('<p>2</p>')
- index.value = 0
- await nextTick()
- expect(inner(root)).toBe('<p>1</p>')
- })
- // #3779
- test('treat slots manually written by the user as dynamic', async () => {
- const Middle = {
- setup(props: any, { slots }: any) {
- return slots.default!
- }
- }
- const Comp = {
- setup(props: any, { slots }: any) {
- return () => {
- return (
- openBlock(),
- createBlock('div', null, [
- createVNode(Middle, null, {
- default: withCtx(
- () => [
- createVNode('div', null, [renderSlot(slots, 'default')])
- ],
- undefined
- ),
- _: 3 /* FORWARDED */
- })
- ])
- )
- }
- }
- }
- const loading = ref(false)
- const app = createApp({
- setup() {
- return () => {
- // important: write the slot content here
- const content = h('span', loading.value ? 'loading' : 'loaded')
- return h(Comp, null, {
- default: () => content
- })
- }
- }
- })
- app.mount(root)
- expect(inner(root)).toBe('<div><div><span>loaded</span></div></div>')
- loading.value = true
- await nextTick()
- expect(inner(root)).toBe('<div><div><span>loading</span></div></div>')
- })
- // #3828
- test('patch Suspense in optimized mode w/ nested dynamic nodes', async () => {
- const show = ref(false)
- const app = createApp({
- render() {
- return (
- openBlock(),
- createBlock(
- Fragment,
- null,
- [
- (openBlock(),
- createBlock(SuspenseImpl, null, {
- default: withCtx(() => [
- createVNode('div', null, [
- createVNode('div', null, show.value, PatchFlags.TEXT)
- ])
- ]),
- _: SlotFlags.STABLE
- }))
- ],
- PatchFlags.STABLE_FRAGMENT
- )
- )
- }
- })
- app.mount(root)
- expect(inner(root)).toBe('<div><div>false</div></div>')
- show.value = true
- await nextTick()
- expect(inner(root)).toBe('<div><div>true</div></div>')
- })
- // #4183
- test('should not take unmount children fast path /w Suspense', async () => {
- const show = ref(true)
- const spyUnmounted = vi.fn()
- const Parent = {
- setup(props: any, { slots }: SetupContext) {
- return () => (
- openBlock(),
- createBlock(SuspenseImpl, null, {
- default: withCtx(() => [renderSlot(slots, 'default')]),
- _: SlotFlags.FORWARDED
- })
- )
- }
- }
- const Child = {
- setup() {
- onUnmounted(spyUnmounted)
- return () => createVNode('div', null, show.value, PatchFlags.TEXT)
- }
- }
- const app = createApp({
- render() {
- return show.value
- ? (openBlock(),
- createBlock(
- Parent,
- { key: 0 },
- {
- default: withCtx(() => [createVNode(Child)]),
- _: SlotFlags.STABLE
- }
- ))
- : createCommentVNode('v-if', true)
- }
- })
- app.mount(root)
- expect(inner(root)).toBe('<div>true</div>')
- show.value = false
- await nextTick()
- expect(inner(root)).toBe('<!--v-if-->')
- expect(spyUnmounted).toHaveBeenCalledTimes(1)
- })
- // #3881
- // root cause: fragment inside a compiled slot passed to component which
- // programmatically invokes the slot. The entire slot should de-opt but
- // the fragment was incorrectly put in optimized mode which causes it to skip
- // updates for its inner components.
- test('fragments inside programmatically invoked compiled slot should de-opt properly', async () => {
- const Parent: FunctionalComponent = (_, { slots }) => slots.default!()
- const Dummy = () => 'dummy'
- const toggle = ref(true)
- const force = ref(0)
- const app = createApp({
- render() {
- if (!toggle.value) {
- return null
- }
- return h(
- Parent,
- { n: force.value },
- {
- default: withCtx(
- () => [
- createVNode('ul', null, [
- (openBlock(),
- createBlock(
- Fragment,
- null,
- renderList(1, item => {
- return createVNode('li', null, [createVNode(Dummy)])
- }),
- 64 /* STABLE_FRAGMENT */
- ))
- ])
- ],
- undefined,
- true
- ),
- _: 1 /* STABLE */
- }
- )
- }
- })
- app.mount(root)
- // force a patch
- force.value++
- await nextTick()
- expect(inner(root)).toBe(`<ul><li>dummy</li></ul>`)
- // unmount
- toggle.value = false
- await nextTick()
- // should successfully unmount without error
- expect(inner(root)).toBe(`<!---->`)
- })
- })
|