+import {
+ h,
+ Fragment,
+ createVNode,
+ openBlock,
+ createBlock,
+ render,
+ nodeOps,
+ TestElement,
+ serialize,
+ serializeInner as inner,
+ VNode,
+ ref,
+ nextTick
+} from '@vue/runtime-test'
+import { PatchFlags } from '@vue/shared'
+
describe('renderer: optimized mode', () => {
- test.todo('should work')
+ 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 = jest.fn()
+ const spyUpdated = jest.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)
+ })
})