test/unit/modules/vdom/patch/children.spec.ts TYPESCRIPT 578 lines View on github.com → Search inside
1import { patch } from 'web/runtime/patch'2import VNode, { createEmptyVNode } from 'core/vdom/vnode'34function prop(name) {5  return obj => {6    return obj[name]7  }8}910function map(fn, list) {11  const ret: any[] = []12  for (let i = 0; i < list.length; i++) {13    ret[i] = fn(list[i])14  }15  return ret16}1718function spanNum(n) {19  if (typeof n === 'string') {20    return new VNode('span', {}, undefined, n)21  } else {22    return new VNode('span', { key: n }, undefined, n.toString())23  }24}2526function shuffle(array) {27  let currentIndex = array.length28  let temporaryValue29  let randomIndex3031  // while there remain elements to shuffle...32  while (currentIndex !== 0) {33    // pick a remaining element...34    randomIndex = Math.floor(Math.random() * currentIndex)35    currentIndex -= 136    // and swap it with the current element.37    temporaryValue = array[currentIndex]38    array[currentIndex] = array[randomIndex]39    array[randomIndex] = temporaryValue40  }41  return array42}4344const inner = prop('innerHTML')45const tag = prop('tagName')4647describe('vdom patch: children', () => {48  let vnode049  beforeEach(() => {50    vnode0 = new VNode('p', { attrs: { id: '1' } }, [51      createTextVNode('hello world')52    ])53    patch(null, vnode0)54  })5556  it('should appends elements', () => {57    const vnode1 = new VNode('p', {}, [1].map(spanNum))58    const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))59    let elm = patch(vnode0, vnode1)60    expect(elm.children.length).toBe(1)61    elm = patch(vnode1, vnode2)62    expect(elm.children.length).toBe(3)63    expect(elm.children[1].innerHTML).toBe('2')64    expect(elm.children[2].innerHTML).toBe('3')65  })6667  it('should prepends elements', () => {68    const vnode1 = new VNode('p', {}, [4, 5].map(spanNum))69    const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))70    let elm = patch(vnode0, vnode1)71    expect(elm.children.length).toBe(2)72    elm = patch(vnode1, vnode2)73    expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])74  })7576  it('should add elements in the middle', () => {77    const vnode1 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))78    const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))79    let elm = patch(vnode0, vnode1)80    expect(elm.children.length).toBe(4)81    elm = patch(vnode1, vnode2)82    expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])83  })8485  it('should add elements at begin and end', () => {86    const vnode1 = new VNode('p', {}, [2, 3, 4].map(spanNum))87    const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))88    let elm = patch(vnode0, vnode1)89    expect(elm.children.length).toBe(3)90    elm = patch(vnode1, vnode2)91    expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])92  })9394  it('should add children to parent with no children', () => {95    const vnode1 = new VNode('p', { key: 'p' })96    const vnode2 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))97    let elm = patch(vnode0, vnode1)98    expect(elm.children.length).toBe(0)99    elm = patch(vnode1, vnode2)100    expect(map(inner, elm.children)).toEqual(['1', '2', '3'])101  })102103  it('should remove all children from parent', () => {104    const vnode1 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))105    const vnode2 = new VNode('p', { key: 'p' })106    let elm = patch(vnode0, vnode1)107    expect(map(inner, elm.children)).toEqual(['1', '2', '3'])108    elm = patch(vnode1, vnode2)109    expect(elm.children.length).toBe(0)110  })111112  it('should remove elements from the beginning', () => {113    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))114    const vnode2 = new VNode('p', {}, [3, 4, 5].map(spanNum))115    let elm = patch(vnode0, vnode1)116    expect(elm.children.length).toBe(5)117    elm = patch(vnode1, vnode2)118    expect(map(inner, elm.children)).toEqual(['3', '4', '5'])119  })120121  it('should removes elements from end', () => {122    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))123    const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))124    let elm = patch(vnode0, vnode1)125    expect(elm.children.length).toBe(5)126    elm = patch(vnode1, vnode2)127    expect(elm.children.length).toBe(3)128    expect(map(inner, elm.children)).toEqual(['1', '2', '3'])129  })130131  it('should remove elements from the middle', () => {132    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))133    const vnode2 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))134    let elm = patch(vnode0, vnode1)135    expect(elm.children.length).toBe(5)136    elm = patch(vnode1, vnode2)137    expect(elm.children.length).toBe(4)138    expect(map(inner, elm.children)).toEqual(['1', '2', '4', '5'])139  })140141  it('should moves element forward', () => {142    const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))143    const vnode2 = new VNode('p', {}, [2, 3, 1, 4].map(spanNum))144    let elm = patch(vnode0, vnode1)145    expect(elm.children.length).toBe(4)146    elm = patch(vnode1, vnode2)147    expect(elm.children.length).toBe(4)148    expect(map(inner, elm.children)).toEqual(['2', '3', '1', '4'])149  })150151  it('should move elements to end', () => {152    const vnode1 = new VNode('p', {}, [1, 2, 3].map(spanNum))153    const vnode2 = new VNode('p', {}, [2, 3, 1].map(spanNum))154    let elm = patch(vnode0, vnode1)155    expect(elm.children.length).toBe(3)156    elm = patch(vnode1, vnode2)157    expect(elm.children.length).toBe(3)158    expect(map(inner, elm.children)).toEqual(['2', '3', '1'])159  })160161  it('should move element backwards', () => {162    const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))163    const vnode2 = new VNode('p', {}, [1, 4, 2, 3].map(spanNum))164    let elm = patch(vnode0, vnode1)165    expect(elm.children.length).toBe(4)166    elm = patch(vnode1, vnode2)167    expect(elm.children.length).toBe(4)168    expect(map(inner, elm.children)).toEqual(['1', '4', '2', '3'])169  })170171  it('should swap first and last', () => {172    const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))173    const vnode2 = new VNode('p', {}, [4, 2, 3, 1].map(spanNum))174    let elm = patch(vnode0, vnode1)175    expect(elm.children.length).toBe(4)176    elm = patch(vnode1, vnode2)177    expect(elm.children.length).toBe(4)178    expect(map(inner, elm.children)).toEqual(['4', '2', '3', '1'])179  })180181  it('should move to left and replace', () => {182    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))183    const vnode2 = new VNode('p', {}, [4, 1, 2, 3, 6].map(spanNum))184    let elm = patch(vnode0, vnode1)185    expect(elm.children.length).toBe(5)186    elm = patch(vnode1, vnode2)187    expect(elm.children.length).toBe(5)188    expect(map(inner, elm.children)).toEqual(['4', '1', '2', '3', '6'])189  })190191  it('should move to left and leaves hold', () => {192    const vnode1 = new VNode('p', {}, [1, 4, 5].map(spanNum))193    const vnode2 = new VNode('p', {}, [4, 6].map(spanNum))194    let elm = patch(vnode0, vnode1)195    expect(elm.children.length).toBe(3)196    elm = patch(vnode1, vnode2)197    expect(map(inner, elm.children)).toEqual(['4', '6'])198  })199200  it('should handle moved and set to undefined element ending at the end', () => {201    const vnode1 = new VNode('p', {}, [2, 4, 5].map(spanNum))202    const vnode2 = new VNode('p', {}, [4, 5, 3].map(spanNum))203    let elm = patch(vnode0, vnode1)204    expect(elm.children.length).toBe(3)205    elm = patch(vnode1, vnode2)206    expect(elm.children.length).toBe(3)207    expect(map(inner, elm.children)).toEqual(['4', '5', '3'])208  })209210  it('should move a key in non-keyed nodes with a size up', () => {211    const vnode1 = new VNode('p', {}, [1, 'a', 'b', 'c'].map(spanNum))212    const vnode2 = new VNode('p', {}, ['d', 'a', 'b', 'c', 1, 'e'].map(spanNum))213    let elm = patch(vnode0, vnode1)214    expect(elm.children.length).toBe(4)215    expect(elm.textContent, '1abc')216    elm = patch(vnode1, vnode2)217    expect(elm.children.length).toBe(6)218    expect(elm.textContent, 'dabc1e')219  })220221  it('should reverse element', () => {222    const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5, 6, 7, 8].map(spanNum))223    const vnode2 = new VNode('p', {}, [8, 7, 6, 5, 4, 3, 2, 1].map(spanNum))224    let elm = patch(vnode0, vnode1)225    expect(elm.children.length).toBe(8)226    elm = patch(vnode1, vnode2)227    expect(map(inner, elm.children)).toEqual([228      '8',229      '7',230      '6',231      '5',232      '4',233      '3',234      '2',235      '1'236    ])237  })238239  it('something', () => {240    const vnode1 = new VNode('p', {}, [0, 1, 2, 3, 4, 5].map(spanNum))241    const vnode2 = new VNode('p', {}, [4, 3, 2, 1, 5, 0].map(spanNum))242    let elm = patch(vnode0, vnode1)243    expect(elm.children.length).toBe(6)244    elm = patch(vnode1, vnode2)245    expect(map(inner, elm.children)).toEqual(['4', '3', '2', '1', '5', '0'])246  })247248  it('should handle random shuffle', () => {249    let n250    let i251    const arr: any[] = []252    const opacities: any[] = []253    const elms = 14254    const samples = 5255    function spanNumWithOpacity(n, o) {256      return new VNode(257        'span',258        { key: n, style: { opacity: o } },259        undefined,260        n.toString()261      )262    }263264    for (n = 0; n < elms; ++n) {265      arr[n] = n266    }267    for (n = 0; n < samples; ++n) {268      const vnode1 = new VNode(269        'span',270        {},271        arr.map(n => {272          return spanNumWithOpacity(n, '1')273        })274      )275      const shufArr = shuffle(arr.slice(0))276      let elm = patch(vnode0, vnode1)277      for (i = 0; i < elms; ++i) {278        expect(elm.children[i].innerHTML).toBe(i.toString())279        opacities[i] = Math.random().toFixed(5).toString()280      }281      const vnode2 = new VNode(282        'span',283        {},284        arr.map(n => {285          return spanNumWithOpacity(shufArr[n], opacities[n])286        })287      )288      elm = patch(vnode1, vnode2)289      for (i = 0; i < elms; ++i) {290        expect(elm.children[i].innerHTML).toBe(shufArr[i].toString())291        expect(opacities[i].indexOf(elm.children[i].style.opacity)).toBe(0)292      }293    }294  })295296  it('should append elements with updating children without keys', () => {297    const vnode1 = new VNode('div', {}, [298      new VNode('span', {}, undefined, 'hello')299    ])300    const vnode2 = new VNode('div', {}, [301      new VNode('span', {}, undefined, 'hello'),302      new VNode('span', {}, undefined, 'world')303    ])304    let elm = patch(vnode0, vnode1)305    expect(map(inner, elm.children)).toEqual(['hello'])306    elm = patch(vnode1, vnode2)307    expect(map(inner, elm.children)).toEqual(['hello', 'world'])308  })309310  it('should handle unmoved text nodes with updating children without keys', () => {311    const vnode1 = new VNode('div', {}, [312      createTextVNode('text'),313      new VNode('span', {}, undefined, 'hello')314    ])315    const vnode2 = new VNode('div', {}, [316      createTextVNode('text'),317      new VNode('span', {}, undefined, 'hello')318    ])319    let elm = patch(vnode0, vnode1)320    expect(elm.childNodes[0].textContent).toBe('text')321    elm = patch(vnode1, vnode2)322    expect(elm.childNodes[0].textContent).toBe('text')323  })324325  it('should handle changing text children with updating children without keys', () => {326    const vnode1 = new VNode('div', {}, [327      createTextVNode('text'),328      new VNode('span', {}, undefined, 'hello')329    ])330    const vnode2 = new VNode('div', {}, [331      createTextVNode('text2'),332      new VNode('span', {}, undefined, 'hello')333    ])334    let elm = patch(vnode0, vnode1)335    expect(elm.childNodes[0].textContent).toBe('text')336    elm = patch(vnode1, vnode2)337    expect(elm.childNodes[0].textContent).toBe('text2')338  })339340  it('should prepend element with updating children without keys', () => {341    const vnode1 = new VNode('div', {}, [342      new VNode('span', {}, undefined, 'world')343    ])344    const vnode2 = new VNode('div', {}, [345      new VNode('span', {}, undefined, 'hello'),346      new VNode('span', {}, undefined, 'world')347    ])348    let elm = patch(vnode0, vnode1)349    expect(map(inner, elm.children)).toEqual(['world'])350    elm = patch(vnode1, vnode2)351    expect(map(inner, elm.children)).toEqual(['hello', 'world'])352  })353354  it('should prepend element of different tag type with updating children without keys', () => {355    const vnode1 = new VNode('div', {}, [356      new VNode('span', {}, undefined, 'world')357    ])358    const vnode2 = new VNode('div', {}, [359      new VNode('div', {}, undefined, 'hello'),360      new VNode('span', {}, undefined, 'world')361    ])362    let elm = patch(vnode0, vnode1)363    expect(map(inner, elm.children)).toEqual(['world'])364    elm = patch(vnode1, vnode2)365    expect(map(prop('tagName'), elm.children)).toEqual(['DIV', 'SPAN'])366    expect(map(inner, elm.children)).toEqual(['hello', 'world'])367  })368369  it('should remove elements with updating children without keys', () => {370    const vnode1 = new VNode('div', {}, [371      new VNode('span', {}, undefined, 'one'),372      new VNode('span', {}, undefined, 'two'),373      new VNode('span', {}, undefined, 'three')374    ])375    const vnode2 = new VNode('div', {}, [376      new VNode('span', {}, undefined, 'one'),377      new VNode('span', {}, undefined, 'three')378    ])379    let elm = patch(vnode0, vnode1)380    expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])381    elm = patch(vnode1, vnode2)382    expect(map(inner, elm.children)).toEqual(['one', 'three'])383  })384385  it('should remove a single text node with updating children without keys', () => {386    const vnode1 = new VNode('div', {}, undefined, 'one')387    const vnode2 = new VNode('div', {})388    let elm = patch(vnode0, vnode1)389    expect(elm.textContent).toBe('one')390    elm = patch(vnode1, vnode2)391    expect(elm.textContent).toBe('')392  })393394  it('should remove a single text node when children are updated', () => {395    const vnode1 = new VNode('div', {}, undefined, 'one')396    const vnode2 = new VNode('div', {}, [397      new VNode('div', {}, undefined, 'two'),398      new VNode('span', {}, undefined, 'three')399    ])400    let elm = patch(vnode0, vnode1)401    expect(elm.textContent).toBe('one')402    elm = patch(vnode1, vnode2)403    expect(map(prop('textContent'), elm.childNodes)).toEqual(['two', 'three'])404  })405406  it('should remove a text node among other elements', () => {407    const vnode1 = new VNode('div', {}, [408      createTextVNode('one'),409      new VNode('span', {}, undefined, 'two')410    ])411    const vnode2 = new VNode('div', {}, [412      new VNode('div', {}, undefined, 'three')413    ])414    let elm = patch(vnode0, vnode1)415    expect(map(prop('textContent'), elm.childNodes)).toEqual(['one', 'two'])416    elm = patch(vnode1, vnode2)417    expect(elm.childNodes.length).toBe(1)418    expect(elm.childNodes[0].tagName).toBe('DIV')419    expect(elm.childNodes[0].textContent).toBe('three')420  })421422  it('should reorder elements', () => {423    const vnode1 = new VNode('div', {}, [424      new VNode('span', {}, undefined, 'one'),425      new VNode('div', {}, undefined, 'two'),426      new VNode('b', {}, undefined, 'three')427    ])428    const vnode2 = new VNode('div', {}, [429      new VNode('b', {}, undefined, 'three'),430      new VNode('span', {}, undefined, 'two'),431      new VNode('div', {}, undefined, 'one')432    ])433    let elm = patch(vnode0, vnode1)434    expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])435    elm = patch(vnode1, vnode2)436    expect(map(inner, elm.children)).toEqual(['three', 'two', 'one'])437  })438439  it('should handle children with the same key but with different tag', () => {440    const vnode1 = new VNode('div', {}, [441      new VNode('div', { key: 1 }, undefined, 'one'),442      new VNode('div', { key: 2 }, undefined, 'two'),443      new VNode('div', { key: 3 }, undefined, 'three'),444      new VNode('div', { key: 4 }, undefined, 'four')445    ])446    const vnode2 = new VNode('div', {}, [447      new VNode('div', { key: 4 }, undefined, 'four'),448      new VNode('span', { key: 3 }, undefined, 'three'),449      new VNode('span', { key: 2 }, undefined, 'two'),450      new VNode('div', { key: 1 }, undefined, 'one')451    ])452    let elm = patch(vnode0, vnode1)453    expect(map(tag, elm.children)).toEqual(['DIV', 'DIV', 'DIV', 'DIV'])454    expect(map(inner, elm.children)).toEqual(['one', 'two', 'three', 'four'])455    elm = patch(vnode1, vnode2)456    expect(map(tag, elm.children)).toEqual(['DIV', 'SPAN', 'SPAN', 'DIV'])457    expect(map(inner, elm.children)).toEqual(['four', 'three', 'two', 'one'])458  })459460  it('should handle children with the same tag, same key, but one with data and one without data', () => {461    const vnode1 = new VNode('div', {}, [462      new VNode('div', { class: 'hi' }, undefined, 'one')463    ])464    const vnode2 = new VNode('div', {}, [465      new VNode('div', undefined, undefined, 'four')466    ])467    let elm = patch(vnode0, vnode1)468    const child1 = elm.children[0]469    expect(child1.className).toBe('hi')470    elm = patch(vnode1, vnode2)471    const child2 = elm.children[0]472    expect(child1).not.toBe(child2)473    expect(child2.className).toBe('')474  })475476  it('should handle static vnodes properly', () => {477    function makeNode(text) {478      return new VNode('div', undefined, [479        new VNode(undefined, undefined, undefined, text)480      ])481    }482    const b = makeNode('B')483    b.isStatic = true484    b.key = `__static__1`485    const vnode1 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])486    const vnode2 = new VNode('div', {}, [b])487    const vnode3 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])488489    let elm = patch(vnode0, vnode1)490    expect(elm.textContent).toBe('ABC')491    elm = patch(vnode1, vnode2)492    expect(elm.textContent).toBe('B')493    elm = patch(vnode2, vnode3)494    expect(elm.textContent).toBe('ABC')495  })496497  it('should handle static vnodes inside ', () => {498    function makeNode(text) {499      return new VNode('div', undefined, [500        new VNode(undefined, undefined, undefined, text)501      ])502    }503    const b = makeNode('B')504    b.isStatic = true505    b.key = `__static__1`506    const vnode1 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])507    const vnode2 = new VNode('div', {}, [b])508    const vnode3 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])509510    let elm = patch(vnode0, vnode1)511    expect(elm.textContent).toBe('ABC')512    elm = patch(vnode1, vnode2)513    expect(elm.textContent).toBe('B')514    elm = patch(vnode2, vnode3)515    expect(elm.textContent).toBe('ABC')516  })517518  // #6502519  it('should not de-opt when both head and tail are changed', () => {520    const vnode1 = new VNode('div', {}, [521      createEmptyVNode(),522      new VNode('div'),523      createEmptyVNode()524    ])525    const vnode2 = new VNode('div', {}, [526      new VNode('p'),527      new VNode('div'),528      new VNode('p')529    ])530    let root = patch(null, vnode1)531    const original = root.childNodes[1]532533    root = patch(vnode1, vnode2)534    const postPatch = root.childNodes[1]535536    expect(postPatch).toBe(original)537  })538539  it('should warn with duplicate keys: createChildren', () => {540    function makeNode(key) {541      return new VNode('div', { key: key })542    }543544    const vnode = new VNode('p', {}, ['b', 'a', 'c', 'b'].map(makeNode))545    patch(null, vnode)546    expect(`Duplicate keys detected: 'b'`).toHaveBeenWarned()547  })548549  it('should warn with duplicate keys: updateChildren', () => {550    function makeNode(key) {551      return new VNode('div', { key: key })552    }553554    const vnode2 = new VNode('p', {}, ['b', 'a', 'c', 'b'].map(makeNode))555    const vnode3 = new VNode('p', {}, ['b', 'x', 'd', 'b'].map(makeNode))556    patch(vnode0, vnode2)557    expect(`Duplicate keys detected: 'b'`).toHaveBeenWarned()558    patch(vnode2, vnode3)559    expect(`Duplicate keys detected: 'b'`).toHaveBeenWarned()560  })561562  it('should warn with duplicate keys: patchVnode with empty oldVnode', () => {563    function makeNode(key) {564      return new VNode('li', { key: key })565    }566567    const vnode1 = new VNode('div')568    const vnode2 = new VNode(569      'div',570      undefined,571      ['1', '2', '3', '4', '4'].map(makeNode)572    )573574    patch(vnode1, vnode2)575    expect(`Duplicate keys detected: '4'`).toHaveBeenWarned()576  })577})

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.