@@ -401,6 +401,153 @@ describe('keys', () => {
401401 ] ) ;
402402 } ) ;
403403
404+ it ( 'should not displace when the suffix after the match is shorter than the jump' , ( ) => {
405+ const values = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ] ;
406+
407+ render ( < List values = { values } /> , scratch ) ;
408+ expect ( scratch . textContent ) . to . equal ( 'abcdefghij' ) ;
409+
410+ // Swap two far apart children; only the two swapped children are out of
411+ // order, so only those two may move.
412+ [ values [ 1 ] , values [ 8 ] ] = [ values [ 8 ] , values [ 1 ] ] ;
413+ clearLog ( ) ;
414+
415+ render ( < List values = { values } /> , scratch ) ;
416+ expect ( scratch . textContent ) . to . equal ( 'aicdefghbj' ) ;
417+ expect ( getLog ( ) ) . to . deep . equal ( [
418+ '<ol>abcdefghij.insertBefore(<li>i, <li>b)' ,
419+ '<ol>aibcdefghj.insertBefore(<li>b, <li>j)'
420+ ] ) ;
421+ } ) ;
422+
423+ it ( 'should move the shorter suffix when more than half the list is displaced' , ( ) => {
424+ const values = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' ] ;
425+
426+ render ( < List values = { values } /> , scratch ) ;
427+ expect ( scratch . textContent ) . to . equal ( 'abcdef' ) ;
428+
429+ // Displacing 4 of 6 children: moving the two-child suffix is the
430+ // minimal set of moves.
431+ values . push ( ...values . splice ( 0 , 4 ) ) ;
432+ clearLog ( ) ;
433+
434+ render ( < List values = { values } /> , scratch ) ;
435+ expect ( scratch . textContent ) . to . equal ( 'efabcd' ) ;
436+ expect ( getLog ( ) ) . to . deep . equal ( [
437+ '<ol>abcdef.insertBefore(<li>e, <li>a)' ,
438+ '<ol>eabcdf.insertBefore(<li>f, <li>a)'
439+ ] ) ;
440+ } ) ;
441+
442+ it ( 'should displace multiple keyed children to the end while the list grows' , ( ) => {
443+ const values = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ] ;
444+
445+ render ( < List values = { values } /> , scratch ) ;
446+ expect ( scratch . textContent ) . to . equal ( 'abcdefghij' ) ;
447+
448+ values . push ( ...values . splice ( 0 , 3 ) ) ;
449+ values . push ( 'k' ) ;
450+ clearLog ( ) ;
451+
452+ render ( < List values = { values } /> , scratch ) ;
453+ expect ( scratch . textContent ) . to . equal ( 'defghijabck' ) ;
454+ expect ( getLog ( ) ) . to . deep . equal ( [
455+ '<ol>abcdefghij.appendChild(<li>a)' ,
456+ '<ol>bcdefghija.appendChild(<li>b)' ,
457+ '<ol>cdefghijab.appendChild(<li>c)' ,
458+ '<li>.appendChild(#text)' ,
459+ '<ol>defghijabc.appendChild(<li>k)'
460+ ] ) ;
461+ } ) ;
462+
463+ it ( 'should displace multiple keyed children to the end while another is removed' , ( ) => {
464+ const values = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ] ;
465+
466+ render ( < List values = { values } /> , scratch ) ;
467+ expect ( scratch . textContent ) . to . equal ( 'abcdefghij' ) ;
468+
469+ values . push ( ...values . splice ( 0 , 3 ) ) ;
470+ values . splice ( values . indexOf ( 'j' ) , 1 ) ;
471+ clearLog ( ) ;
472+
473+ render ( < List values = { values } /> , scratch ) ;
474+ expect ( scratch . textContent ) . to . equal ( 'defghiabc' ) ;
475+ expect ( getLog ( ) ) . to . deep . equal ( [
476+ '<li>j.remove()' ,
477+ '<ol>abcdefghi.appendChild(<li>a)' ,
478+ '<ol>bcdefghia.appendChild(<li>b)' ,
479+ '<ol>cdefghiab.appendChild(<li>c)'
480+ ] ) ;
481+ } ) ;
482+
483+ it ( 'should displace keyed children to the end repeatedly' , ( ) => {
484+ const values = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ] ;
485+ const expectedDisplaceLogs = [
486+ [
487+ '<ol>abcdefghij.appendChild(<li>a)' ,
488+ '<ol>bcdefghija.appendChild(<li>b)' ,
489+ '<ol>cdefghijab.appendChild(<li>c)'
490+ ] ,
491+ [
492+ '<ol>defghijabc.appendChild(<li>d)' ,
493+ '<ol>efghijabcd.appendChild(<li>e)' ,
494+ '<ol>fghijabcde.appendChild(<li>f)'
495+ ] ,
496+ [
497+ '<ol>ghijabcdef.appendChild(<li>g)' ,
498+ '<ol>hijabcdefg.appendChild(<li>h)' ,
499+ '<ol>ijabcdefgh.appendChild(<li>i)'
500+ ]
501+ ] ;
502+
503+ render ( < List values = { values } /> , scratch ) ;
504+ expect ( scratch . textContent ) . to . equal ( 'abcdefghij' ) ;
505+
506+ for ( let n = 0 ; n < 3 ; n ++ ) {
507+ values . push ( ...values . splice ( 0 , 3 ) ) ;
508+ clearLog ( ) ;
509+
510+ render ( < List values = { values } /> , scratch ) ;
511+ expect ( scratch . textContent ) . to . equal ( values . join ( '' ) ) ;
512+ expect ( getLog ( ) ) . to . deep . equal ( expectedDisplaceLogs [ n ] , `round ${ n } ` ) ;
513+ }
514+ } ) ;
515+
516+ it ( 'should keep text siblings correct around displaced keyed children' , ( ) => {
517+ const content = condition => (
518+ < ol >
519+ { condition
520+ ? [
521+ < li key = "a" > a</ li > ,
522+ < li key = "b" > b</ li > ,
523+ < li key = "c" > c</ li > ,
524+ 'mid' ,
525+ < li key = "d" > d</ li > ,
526+ < li key = "e" > e</ li >
527+ ]
528+ : [
529+ < li key = "c" > c</ li > ,
530+ 'mid' ,
531+ < li key = "a" > a</ li > ,
532+ < li key = "b" > b</ li > ,
533+ < li key = "d" > d</ li > ,
534+ < li key = "e" > e</ li >
535+ ] }
536+ </ ol >
537+ ) ;
538+
539+ render ( content ( true ) , scratch ) ;
540+ expect ( scratch . innerHTML ) . to . equal (
541+ '<ol><li>a</li><li>b</li><li>c</li>mid<li>d</li><li>e</li></ol>'
542+ ) ;
543+
544+ clearLog ( ) ;
545+ render ( content ( false ) , scratch ) ;
546+ expect ( scratch . innerHTML ) . to . equal (
547+ '<ol><li>c</li>mid<li>a</li><li>b</li><li>d</li><li>e</li></ol>'
548+ ) ;
549+ } ) ;
550+
404551 it ( 'should move keyed children to the end of the list' , ( ) => {
405552 const values = [ 'a' , 'b' , 'c' , 'd' ] ;
406553
0 commit comments