@@ -246,6 +246,30 @@ describe("binding", () => {
246246 elem . remove ( ) ;
247247 } ) ;
248248
249+ it ( "should still refresh tuple key bindings when the first observed array item is removed" , async ( ) => {
250+ let elem = createElementFromHTML (
251+ "<ul>" +
252+ '<li ng-repeat="(key, item) in items"><span class="index" ng-bind="key"></span>:<span class="label" ng-bind="item.a"></span></li>' +
253+ "</ul>" ,
254+ ) ;
255+ document . getElementById ( "app" ) . insertAdjacentElement ( "afterend" , elem ) ;
256+ $injector = window . angular . bootstrap ( elem , [ "myModule" ] ) ;
257+ $rootScope = $injector . get ( "$rootScope" ) ;
258+
259+ $rootScope . items = [ { a : "A" } , { a : "B" } , { a : "C" } ] ;
260+ await wait ( ) ;
261+
262+ $rootScope . items . splice ( 0 , 1 ) ;
263+ await wait ( ) ;
264+
265+ const rows = Array . from ( elem . querySelectorAll ( "li" ) ) . map (
266+ ( row ) => row . textContent ,
267+ ) ;
268+
269+ expect ( rows ) . toEqual ( [ "0:B" , "1:C" ] ) ;
270+ elem . remove ( ) ;
271+ } ) ;
272+
249273 it ( "should preserve surviving repeated DOM nodes when leading observed array items are removed in bulk" , async ( ) => {
250274 let elem = createElementFromHTML (
251275 "<ul>" +
@@ -283,6 +307,152 @@ describe("binding", () => {
283307 elem . remove ( ) ;
284308 } ) ;
285309
310+ it ( "should preserve existing repeated DOM nodes when observed array items are appended via array replacement" , async ( ) => {
311+ let elem = createElementFromHTML (
312+ "<ul>" +
313+ '<li ng-repeat="item in items" ng-bind="item.a"></li>' +
314+ "</ul>" ,
315+ ) ;
316+ document . getElementById ( "app" ) . insertAdjacentElement ( "afterend" , elem ) ;
317+ $injector = window . angular . bootstrap ( elem , [ "myModule" ] ) ;
318+ $rootScope = $injector . get ( "$rootScope" ) ;
319+
320+ $rootScope . items = [ { a : "A" } , { a : "B" } ] ;
321+ await wait ( ) ;
322+
323+ const originalRows = elem . querySelectorAll ( "li" ) ;
324+ const firstRow = originalRows [ 0 ] ;
325+ const secondRow = originalRows [ 1 ] ;
326+
327+ firstRow . setAttribute ( "data-row" , "survivor-a" ) ;
328+ secondRow . setAttribute ( "data-row" , "survivor-b" ) ;
329+
330+ $rootScope . items = $rootScope . items . concat ( [ { a : "C" } , { a : "D" } ] ) ;
331+ await wait ( ) ;
332+
333+ const currentRows = elem . querySelectorAll ( "li" ) ;
334+
335+ expect ( currentRows . length ) . toBe ( 4 ) ;
336+ expect ( currentRows [ 0 ] ) . toBe ( firstRow ) ;
337+ expect ( currentRows [ 1 ] ) . toBe ( secondRow ) ;
338+ expect ( elem . outerHTML ) . toBe (
339+ "<ul><!---->" +
340+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-a">A</li>' +
341+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-b">B</li>' +
342+ '<li ng-repeat="item in items" ng-bind="item.a">C</li>' +
343+ '<li ng-repeat="item in items" ng-bind="item.a">D</li>' +
344+ "</ul>" ,
345+ ) ;
346+ elem . remove ( ) ;
347+ } ) ;
348+
349+ it ( "should preserve existing repeated DOM nodes when observed array items are appended via push" , async ( ) => {
350+ let elem = createElementFromHTML (
351+ "<ul>" +
352+ '<li ng-repeat="item in items" ng-bind="item.a"></li>' +
353+ "</ul>" ,
354+ ) ;
355+ document . getElementById ( "app" ) . insertAdjacentElement ( "afterend" , elem ) ;
356+ $injector = window . angular . bootstrap ( elem , [ "myModule" ] ) ;
357+ $rootScope = $injector . get ( "$rootScope" ) ;
358+
359+ $rootScope . items = [ { a : "A" } , { a : "B" } ] ;
360+ await wait ( ) ;
361+
362+ const originalRows = elem . querySelectorAll ( "li" ) ;
363+ const firstRow = originalRows [ 0 ] ;
364+ const secondRow = originalRows [ 1 ] ;
365+
366+ firstRow . setAttribute ( "data-row" , "survivor-a" ) ;
367+ secondRow . setAttribute ( "data-row" , "survivor-b" ) ;
368+
369+ $rootScope . items . push ( { a : "C" } , { a : "D" } ) ;
370+ await wait ( ) ;
371+
372+ const currentRows = elem . querySelectorAll ( "li" ) ;
373+
374+ expect ( currentRows . length ) . toBe ( 4 ) ;
375+ expect ( currentRows [ 0 ] ) . toBe ( firstRow ) ;
376+ expect ( currentRows [ 1 ] ) . toBe ( secondRow ) ;
377+ expect ( elem . outerHTML ) . toBe (
378+ "<ul><!---->" +
379+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-a">A</li>' +
380+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-b">B</li>' +
381+ '<li ng-repeat="item in items" ng-bind="item.a">C</li>' +
382+ '<li ng-repeat="item in items" ng-bind="item.a">D</li>' +
383+ "</ul>" ,
384+ ) ;
385+ elem . remove ( ) ;
386+ } ) ;
387+
388+ it ( "should preserve repeated DOM nodes when observed array items are swapped by index" , async ( ) => {
389+ let elem = createElementFromHTML (
390+ "<ul>" +
391+ '<li ng-repeat="item in items" ng-bind="item.a"></li>' +
392+ "</ul>" ,
393+ ) ;
394+ document . getElementById ( "app" ) . insertAdjacentElement ( "afterend" , elem ) ;
395+ $injector = window . angular . bootstrap ( elem , [ "myModule" ] ) ;
396+ $rootScope = $injector . get ( "$rootScope" ) ;
397+
398+ $rootScope . items = [ { a : "A" } , { a : "B" } , { a : "C" } , { a : "D" } ] ;
399+ await wait ( ) ;
400+
401+ const originalRows = elem . querySelectorAll ( "li" ) ;
402+ const secondRow = originalRows [ 1 ] ;
403+ const fourthRow = originalRows [ 3 ] ;
404+
405+ secondRow . setAttribute ( "data-row" , "survivor-b" ) ;
406+ fourthRow . setAttribute ( "data-row" , "survivor-d" ) ;
407+
408+ const tmp = $rootScope . items [ 1 ] ;
409+
410+ $rootScope . items [ 1 ] = $rootScope . items [ 3 ] ;
411+ $rootScope . items [ 3 ] = tmp ;
412+ await wait ( ) ;
413+
414+ const currentRows = elem . querySelectorAll ( "li" ) ;
415+
416+ expect ( currentRows [ 1 ] ) . toBe ( fourthRow ) ;
417+ expect ( currentRows [ 3 ] ) . toBe ( secondRow ) ;
418+ expect ( elem . outerHTML ) . toBe (
419+ "<ul><!---->" +
420+ '<li ng-repeat="item in items" ng-bind="item.a">A</li>' +
421+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-d">D</li>' +
422+ '<li ng-repeat="item in items" ng-bind="item.a">C</li>' +
423+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-b">B</li>' +
424+ "</ul>" ,
425+ ) ;
426+ elem . remove ( ) ;
427+ } ) ;
428+
429+ it ( "should still refresh $index bindings when observed array items are swapped by index" , async ( ) => {
430+ let elem = createElementFromHTML (
431+ "<ul>" +
432+ '<li ng-repeat="item in items"><span class="index" ng-bind="$index"></span>:<span class="label" ng-bind="item.a"></span></li>' +
433+ "</ul>" ,
434+ ) ;
435+ document . getElementById ( "app" ) . insertAdjacentElement ( "afterend" , elem ) ;
436+ $injector = window . angular . bootstrap ( elem , [ "myModule" ] ) ;
437+ $rootScope = $injector . get ( "$rootScope" ) ;
438+
439+ $rootScope . items = [ { a : "A" } , { a : "B" } , { a : "C" } ] ;
440+ await wait ( ) ;
441+
442+ const tmp = $rootScope . items [ 0 ] ;
443+
444+ $rootScope . items [ 0 ] = $rootScope . items [ 2 ] ;
445+ $rootScope . items [ 2 ] = tmp ;
446+ await wait ( ) ;
447+
448+ const rows = Array . from ( elem . querySelectorAll ( "li" ) ) . map (
449+ ( row ) => row . textContent ,
450+ ) ;
451+
452+ expect ( rows ) . toEqual ( [ "0:C" , "1:B" , "2:A" ] ) ;
453+ elem . remove ( ) ;
454+ } ) ;
455+
286456 it ( "should still refresh $index bindings when leading observed array items are removed in bulk" , async ( ) => {
287457 let elem = createElementFromHTML (
288458 "<ul>" +
@@ -368,6 +538,47 @@ describe("binding", () => {
368538 elem . remove ( ) ;
369539 } ) ;
370540
541+ it ( "should preserve surviving repeated DOM nodes when observed array items are removed from the tail" , async ( ) => {
542+ let elem = createElementFromHTML (
543+ "<ul>" +
544+ '<li ng-repeat="item in items" ng-bind="item.a"></li>' +
545+ "</ul>" ,
546+ ) ;
547+ document . getElementById ( "app" ) . insertAdjacentElement ( "afterend" , elem ) ;
548+ $injector = window . angular . bootstrap ( elem , [ "myModule" ] ) ;
549+ $rootScope = $injector . get ( "$rootScope" ) ;
550+
551+ $rootScope . items = [ { a : "A" } , { a : "B" } , { a : "C" } , { a : "D" } ] ;
552+ await wait ( ) ;
553+
554+ const originalRows = elem . querySelectorAll ( "li" ) ;
555+ const survivingFirstRow = originalRows [ 0 ] ;
556+ const survivingSecondRow = originalRows [ 1 ] ;
557+ const survivingThirdRow = originalRows [ 2 ] ;
558+
559+ survivingFirstRow . setAttribute ( "data-row" , "survivor-a" ) ;
560+ survivingSecondRow . setAttribute ( "data-row" , "survivor-b" ) ;
561+ survivingThirdRow . setAttribute ( "data-row" , "survivor-c" ) ;
562+
563+ $rootScope . items . pop ( ) ;
564+ await wait ( ) ;
565+
566+ const currentRows = elem . querySelectorAll ( "li" ) ;
567+
568+ expect ( currentRows . length ) . toBe ( 3 ) ;
569+ expect ( currentRows [ 0 ] ) . toBe ( survivingFirstRow ) ;
570+ expect ( currentRows [ 1 ] ) . toBe ( survivingSecondRow ) ;
571+ expect ( currentRows [ 2 ] ) . toBe ( survivingThirdRow ) ;
572+ expect ( elem . outerHTML ) . toBe (
573+ "<ul><!---->" +
574+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-a">A</li>' +
575+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-b">B</li>' +
576+ '<li ng-repeat="item in items" ng-bind="item.a" data-row="survivor-c">C</li>' +
577+ "</ul>" ,
578+ ) ;
579+ elem . remove ( ) ;
580+ } ) ;
581+
371582 it ( "should clear repeated DOM when an observed array is truncated via length assignment" , async ( ) => {
372583 let elem = createElementFromHTML (
373584 "<ul>" +
0 commit comments