test/unit/modules/vdom/patch/hooks.spec.ts TYPESCRIPT 379 lines View on github.com → Search inside
1import { patch } from 'web/runtime/patch'2import { createPatchFunction } from 'core/vdom/patch'3import baseModules from 'core/vdom/modules/index'4import * as nodeOps from 'web/runtime/node-ops'5import platformModules from 'web/runtime/modules/index'6import VNode from 'core/vdom/vnode'78const modules = baseModules.concat(platformModules) as any[]910describe('vdom patch: hooks', () => {11  let vnode012  beforeEach(() => {13    vnode0 = new VNode('p', { attrs: { id: '1' } }, [14      createTextVNode('hello world')15    ])16    patch(null, vnode0)17  })1819  it('should call `insert` listener after both parents, siblings and children have been inserted', () => {20    const result: any[] = []21    function insert(vnode) {22      expect(vnode.elm.children.length).toBe(2)23      expect(vnode.elm.parentNode.children.length).toBe(3)24      result.push(vnode)25    }26    const vnode1 = new VNode('div', {}, [27      new VNode('span', {}, undefined, 'first sibling'),28      new VNode('div', { hook: { insert } }, [29        new VNode('span', {}, undefined, 'child 1'),30        new VNode('span', {}, undefined, 'child 2')31      ]),32      new VNode('span', {}, undefined, 'can touch me')33    ])34    patch(vnode0, vnode1)35    expect(result.length).toBe(1)36  })3738  it('should call `prepatch` listener', () => {39    const result: any[] = []40    function prepatch(oldVnode, newVnode) {41      expect(oldVnode).toEqual(vnode1.children[1])42      expect(newVnode).toEqual(vnode2.children[1])43      result.push(newVnode)44    }45    const vnode1 = new VNode('div', {}, [46      new VNode('span', {}, undefined, 'first sibling'),47      new VNode('div', { hook: { prepatch } }, [48        new VNode('span', {}, undefined, 'child 1'),49        new VNode('span', {}, undefined, 'child 2')50      ])51    ])52    const vnode2 = new VNode('div', {}, [53      new VNode('span', {}, undefined, 'first sibling'),54      new VNode('div', { hook: { prepatch } }, [55        new VNode('span', {}, undefined, 'child 1'),56        new VNode('span', {}, undefined, 'child 2')57      ])58    ])59    patch(vnode0, vnode1)60    patch(vnode1, vnode2)61    expect(result.length).toBe(1)62  })6364  it('should call `postpatch` after `prepatch` listener', () => {65    const pre: any[] = []66    const post: any[] = []67    function prepatch(oldVnode, newVnode) {68      pre.push(pre)69    }70    function postpatch(oldVnode, newVnode) {71      expect(pre.length).toBe(post.length + 1)72      post.push(post)73    }74    const vnode1 = new VNode('div', {}, [75      new VNode('span', {}, undefined, 'first sibling'),76      new VNode('div', { hook: { prepatch, postpatch } }, [77        new VNode('span', {}, undefined, 'child 1'),78        new VNode('span', {}, undefined, 'child 2')79      ])80    ])81    const vnode2 = new VNode('div', {}, [82      new VNode('span', {}, undefined, 'first sibling'),83      new VNode('div', { hook: { prepatch, postpatch } }, [84        new VNode('span', {}, undefined, 'child 1'),85        new VNode('span', {}, undefined, 'child 2')86      ])87    ])88    patch(vnode0, vnode1)89    patch(vnode1, vnode2)90    expect(pre.length).toBe(1)91    expect(post.length).toBe(1)92  })9394  it('should call `update` listener', () => {95    const result1: any[] = []96    const result2: any[] = []97    function cb(result, oldVnode, newVnode) {98      if (result.length > 1) {99        expect(result[result.length - 1]).toEqual(oldVnode)100      }101      result.push(newVnode)102    }103    const vnode1 = new VNode('div', {}, [104      new VNode('span', {}, undefined, 'first sibling'),105      new VNode('div', { hook: { update: cb.bind(null, result1) } }, [106        new VNode('span', {}, undefined, 'child 1'),107        new VNode(108          'span',109          { hook: { update: cb.bind(null, result2) } },110          undefined,111          'child 2'112        )113      ])114    ])115    const vnode2 = new VNode('div', {}, [116      new VNode('span', {}, undefined, 'first sibling'),117      new VNode('div', { hook: { update: cb.bind(null, result1) } }, [118        new VNode('span', {}, undefined, 'child 1'),119        new VNode(120          'span',121          { hook: { update: cb.bind(null, result2) } },122          undefined,123          'child 2'124        )125      ])126    ])127    patch(vnode0, vnode1)128    patch(vnode1, vnode2)129    expect(result1.length).toBe(1)130    expect(result2.length).toBe(1)131  })132133  it('should call `remove` listener', () => {134    const result: any[] = []135    function remove(vnode, rm) {136      const parent = vnode.elm.parentNode137      expect(vnode.elm.children.length).toBe(2)138      expect(vnode.elm.children.length).toBe(2)139      result.push(vnode)140      rm()141      expect(parent.children.length).toBe(1)142    }143    const vnode1 = new VNode('div', {}, [144      new VNode('span', {}, undefined, 'first sibling'),145      new VNode('div', { hook: { remove } }, [146        new VNode('span', {}, undefined, 'child 1'),147        new VNode('span', {}, undefined, 'child 2')148      ])149    ])150    const vnode2 = new VNode('div', {}, [151      new VNode('span', {}, undefined, 'first sibling')152    ])153    patch(vnode0, vnode1)154    patch(vnode1, vnode2)155    expect(result.length).toBe(1)156  })157158  it('should call `init` and `prepatch` listeners on root', () => {159    let count = 0160    function init(vnode) {161      count++162    }163    function prepatch(oldVnode, newVnode) {164      count++165    }166    const vnode1 = new VNode('div', { hook: { init, prepatch } })167    patch(vnode0, vnode1)168    expect(count).toBe(1)169    const vnode2 = new VNode('span', { hook: { init, prepatch } })170    patch(vnode1, vnode2)171    expect(count).toBe(2)172  })173174  it('should remove element when all remove listeners are done', () => {175    let rm1, rm2, rm3176    const patch1 = createPatchFunction({177      nodeOps,178      // @ts-ignore - TODO dtw179      modules: modules.concat([180        {181          remove(_, rm) {182            rm1 = rm183          }184        },185        {186          remove(_, rm) {187            rm2 = rm188          }189        }190      ])191    })192    const vnode1 = new VNode('div', {}, [193      new VNode('a', {194        hook: {195          remove(_, rm) {196            rm3 = rm197          }198        }199      })200    ])201    const vnode2 = new VNode('div', {}, [])202    let elm = patch1(vnode0, vnode1)203    expect(elm.children.length).toBe(1)204    elm = patch1(vnode1, vnode2)205    expect(elm.children.length).toBe(1)206    rm1()207    expect(elm.children.length).toBe(1)208    rm3()209    expect(elm.children.length).toBe(1)210    rm2()211    expect(elm.children.length).toBe(0)212  })213214  it('should invoke the remove hook on replaced root', () => {215    const result: any[] = []216    const parent = nodeOps.createElement('div')217    vnode0 = nodeOps.createElement('div')218    parent.appendChild(vnode0)219    function remove(vnode, rm) {220      result.push(vnode)221      rm()222    }223    const vnode1 = new VNode('div', { hook: { remove } }, [224      new VNode('b', {}, undefined, 'child 1'),225      new VNode('i', {}, undefined, 'child 2')226    ])227    const vnode2 = new VNode('span', {}, [228      new VNode('b', {}, undefined, 'child 1'),229      new VNode('i', {}, undefined, 'child 2')230    ])231    patch(vnode0, vnode1)232    patch(vnode1, vnode2)233    expect(result.length).toBe(1)234  })235236  it('should invoke global `destroy` hook for all removed children', () => {237    const result: any[] = []238    function destroy(vnode) {239      result.push(vnode)240    }241    const vnode1 = new VNode('div', {}, [242      new VNode('span', {}, undefined, 'first sibling'),243      new VNode('div', {}, [244        new VNode('span', { hook: { destroy } }, undefined, 'child 1'),245        new VNode('span', {}, undefined, 'child 2')246      ])247    ])248    const vnode2 = new VNode('div')249    patch(vnode0, vnode1)250    patch(vnode1, vnode2)251    expect(result.length).toBe(1)252  })253254  it('should handle text vnodes with `undefined` `data` property', () => {255    const vnode1 = new VNode('div', {}, [createTextVNode(' ')])256    const vnode2 = new VNode('div', {}, [])257    patch(vnode0, vnode1)258    patch(vnode1, vnode2)259  })260261  it('should invoke `destroy` module hook for all removed children', () => {262    let created = 0263    let destroyed = 0264    const patch1 = createPatchFunction({265      nodeOps,266      modules: modules.concat([267        {268          create() {269            created++270          }271        },272        {273          destroy() {274            destroyed++275          }276        }277      ])278    })279    const vnode1 = new VNode('div', {}, [280      new VNode('span', {}, undefined, 'first sibling'),281      new VNode('div', {}, [282        new VNode('span', {}, undefined, 'child 1'),283        new VNode('span', {}, undefined, 'child 2')284      ])285    ])286    const vnode2 = new VNode('div', {})287    patch1(vnode0, vnode1)288    expect(destroyed).toBe(1) // should invoke for replaced root nodes too289    patch1(vnode1, vnode2)290    expect(created).toBe(5)291    expect(destroyed).toBe(5)292  })293294  it('should not invoke `create` and `remove` module hook for text nodes', () => {295    let created = 0296    let removed = 0297    const patch1 = createPatchFunction({298      nodeOps,299      modules: modules.concat([300        {301          create() {302            created++303          }304        },305        {306          remove() {307            removed++308          }309        }310      ])311    })312    const vnode1 = new VNode('div', {}, [313      new VNode('span', {}, undefined, 'first child'),314      createTextVNode(''),315      new VNode('span', {}, undefined, 'third child')316    ])317    const vnode2 = new VNode('div', {})318    patch1(vnode0, vnode1)319    patch1(vnode1, vnode2)320    expect(created).toBe(3)321    expect(removed).toBe(2)322  })323324  it('should not invoke `destroy` module hook for text nodes', () => {325    let created = 0326    let destroyed = 0327    const patch1 = createPatchFunction({328      nodeOps,329      modules: modules.concat([330        {331          create() {332            created++333          }334        },335        {336          destroy() {337            destroyed++338          }339        }340      ])341    })342    const vnode1 = new VNode('div', {}, [343      new VNode('span', {}, undefined, 'first sibling'),344      new VNode('div', {}, [345        new VNode('span', {}, undefined, 'child 1'),346        new VNode('span', {}, [347          createTextVNode('text1'),348          createTextVNode('text2')349        ])350      ])351    ])352    const vnode2 = new VNode('div', {})353    patch1(vnode0, vnode1)354    expect(destroyed).toBe(1) // should invoke for replaced root nodes too355    patch1(vnode1, vnode2)356    expect(created).toBe(5)357    expect(destroyed).toBe(5)358  })359360  it('should call `create` listener before inserted into parent but after children', () => {361    const result: any[] = []362    function create(empty, vnode) {363      expect(vnode.elm.children.length).toBe(2)364      expect(vnode.elm.parentNode).toBe(null)365      result.push(vnode)366    }367    const vnode1 = new VNode('div', {}, [368      new VNode('span', {}, undefined, 'first sibling'),369      new VNode('div', { hook: { create } }, [370        new VNode('span', {}, undefined, 'child 1'),371        new VNode('span', {}, undefined, 'child 2')372      ]),373      new VNode('span', {}, undefined, "can't touch me")374    ])375    patch(vnode0, vnode1)376    expect(result.length).toBe(1)377  })378})

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.