@@ -354,3 +354,169 @@ test('can render the same item multiple times', () => {
354354 root . render ( [ item , item ] )
355355 assert_eq ( el . innerHTML , '<p>Item</p><p>Item</p>' )
356356} )
357+
358+ test ( 'keyed list insertion at beginning preserves existing elements' , ( ) => {
359+ const { root, el } = setup ( )
360+
361+ const items = [ keyed ( html `< p > Item B</ p > ` , 'b' ) , keyed ( html `< p > Item C</ p > ` , 'c' ) ]
362+ root . render ( items )
363+ assert_eq ( el . innerHTML , '<p>Item B</p><p>Item C</p>' )
364+
365+ const [ elemB , elemC ] = el . children
366+
367+ // Insert at beginning
368+ items . unshift ( keyed ( html `< p > Item A</ p > ` , 'a' ) )
369+ root . render ( items )
370+ assert_eq ( el . innerHTML , '<p>Item A</p><p>Item B</p><p>Item C</p>' )
371+
372+ // Existing elements should be preserved
373+ assert_eq ( el . children [ 1 ] , elemB )
374+ assert_eq ( el . children [ 2 ] , elemC )
375+ } )
376+
377+ test ( 'keyed list insertion at middle preserves existing elements' , ( ) => {
378+ const { root, el } = setup ( )
379+
380+ const items = [ keyed ( html `< p > Item A</ p > ` , 'a' ) , keyed ( html `< p > Item C</ p > ` , 'c' ) ]
381+ root . render ( items )
382+ assert_eq ( el . innerHTML , '<p>Item A</p><p>Item C</p>' )
383+
384+ const [ elemA , elemC ] = el . children
385+
386+ // Insert in middle
387+ items . splice ( 1 , 0 , keyed ( html `< p > Item B</ p > ` , 'b' ) )
388+ root . render ( items )
389+ assert_eq ( el . innerHTML , '<p>Item A</p><p>Item B</p><p>Item C</p>' )
390+
391+ // Existing elements should be preserved
392+ assert_eq ( el . children [ 0 ] , elemA )
393+ assert_eq ( el . children [ 2 ] , elemC )
394+ } )
395+
396+ test ( 'keyed list deletion preserves remaining elements' , ( ) => {
397+ const { root, el } = setup ( )
398+
399+ const items = [ keyed ( html `< p > Item A</ p > ` , 'a' ) , keyed ( html `< p > Item B</ p > ` , 'b' ) , keyed ( html `< p > Item C</ p > ` , 'c' ) ]
400+ root . render ( items )
401+ assert_eq ( el . innerHTML , '<p>Item A</p><p>Item B</p><p>Item C</p>' )
402+
403+ const [ elemA , , elemC ] = el . children
404+
405+ // Delete middle item
406+ items . splice ( 1 , 1 )
407+ root . render ( items )
408+ assert_eq ( el . innerHTML , '<p>Item A</p><p>Item C</p>' )
409+
410+ // Remaining elements should be preserved
411+ assert_eq ( el . children [ 0 ] , elemA )
412+ assert_eq ( el . children [ 1 ] , elemC )
413+ } )
414+
415+ test ( 'large keyed list reordering minimizes DOM moves' , ( ) => {
416+ const { root, el } = setup ( )
417+
418+ // Create 20 items in order
419+ const items = Array . from ( { length : 20 } , ( _ , i ) => keyed ( html `< div > Item ${ i } </ div > ` , i ) )
420+
421+ root . render ( items )
422+ const elements = [ ...el . children ]
423+
424+ // Move only item 0 to the end (should be minimal moves due to LIS)
425+ const first = items . shift ( ) !
426+ items . push ( first )
427+ root . render ( items )
428+
429+ // Elements 1-19 should stay in place (part of LIS)
430+ for ( let i = 1 ; i < 20 ; i ++ ) {
431+ assert_eq ( el . children [ i - 1 ] , elements [ i ] )
432+ }
433+ // Item 0 should be at the end
434+ assert_eq ( el . children [ 19 ] , elements [ 0 ] )
435+ } )
436+
437+ test ( 'reverse large keyed list preserves all elements' , ( ) => {
438+ const { root, el } = setup ( )
439+
440+ // Create 10 items
441+ const items = Array . from ( { length : 10 } , ( _ , i ) => keyed ( html `< div > Item ${ i } </ div > ` , i ) )
442+
443+ root . render ( items )
444+ const elements = [ ...el . children ]
445+
446+ // Reverse the array
447+ items . reverse ( )
448+ root . render ( items )
449+
450+ // All elements should be preserved, just reordered
451+ for ( let i = 0 ; i < 10 ; i ++ ) {
452+ assert_eq ( el . children [ i ] , elements [ 9 - i ] )
453+ }
454+ } )
455+
456+ test ( 'mixed keyed and unkeyed items work correctly' , ( ) => {
457+ const { root, el } = setup ( )
458+
459+ // Mix keyed and unkeyed items
460+ const items = [
461+ keyed ( html `< p > Keyed A</ p > ` , 'a' ) ,
462+ html `< p > Unkeyed 1</ p > ` ,
463+ keyed ( html `< p > Keyed B</ p > ` , 'b' ) ,
464+ html `< p > Unkeyed 2</ p > ` ,
465+ ]
466+
467+ root . render ( items )
468+ assert_eq ( el . innerHTML , '<p>Keyed A</p><p>Unkeyed 1</p><p>Keyed B</p><p>Unkeyed 2</p>' )
469+
470+ const keyedA = el . children [ 0 ]
471+ const keyedB = el . children [ 2 ]
472+
473+ // Reorder: move keyed items but keep unkeyed in new positions
474+ const newItems = [ keyed ( html `< p > Keyed B</ p > ` , 'b' ) , html `< p > New Unkeyed</ p > ` , keyed ( html `< p > Keyed A</ p > ` , 'a' ) ]
475+
476+ root . render ( newItems )
477+ assert_eq ( el . innerHTML , '<p>Keyed B</p><p>New Unkeyed</p><p>Keyed A</p>' )
478+
479+ // Keyed elements should be preserved
480+ assert_eq ( el . children [ 0 ] , keyedB )
481+ assert_eq ( el . children [ 2 ] , keyedA )
482+ } )
483+
484+ test ( 'reversing a list keeps identity' , ( ) => {
485+ const { root, el } = setup ( )
486+
487+ // All unkeyed items
488+ const items = [ html `< p > Item 1</ p > ` , html `< p > Item 2</ p > ` , html `< p > Item 3</ p > ` ]
489+
490+ root . render ( items )
491+ assert_eq ( el . innerHTML , '<p>Item 1</p><p>Item 2</p><p>Item 3</p>' )
492+
493+ const [ elem1 , elem2 , elem3 ] = el . children
494+
495+ // Reorder unkeyed items (should recreate elements)
496+ items . reverse ( )
497+ root . render ( items )
498+ assert_eq ( el . innerHTML , '<p>Item 3</p><p>Item 2</p><p>Item 1</p>' )
499+
500+ // Elements should be moved
501+ assert ( el . children [ 0 ] === elem3 )
502+ assert ( el . children [ 1 ] === elem2 )
503+ assert ( el . children [ 2 ] === elem1 )
504+ } )
505+
506+ test ( 'keyed list with duplicate keys handles gracefully' , ( ) => {
507+ const { root, el } = setup ( )
508+
509+ // First occurrence of duplicate key should win
510+ const items = [ keyed ( html `< p > First A</ p > ` , 'a' ) , keyed ( html `< p > Second A</ p > ` , 'a' ) , keyed ( html `< p > Item B</ p > ` , 'b' ) ]
511+
512+ root . render ( items )
513+ const firstA = el . children [ 0 ]
514+
515+ // Reorder with duplicate keys
516+ const newItems = [ keyed ( html `< p > Item B</ p > ` , 'b' ) , keyed ( html `< p > First A</ p > ` , 'a' ) , keyed ( html `< p > Third A</ p > ` , 'a' ) ]
517+
518+ root . render ( newItems )
519+
520+ // First keyed 'a' element should be preserved and moved
521+ assert_eq ( el . children [ 1 ] , firstA )
522+ } )
0 commit comments