@@ -96,83 +96,161 @@ describe('control flow - for', () => {
9696 expect ( fixture . nativeElement . textContent ) . toBe ( 'Empty' ) ;
9797 } ) ;
9898
99- it ( 'should have access to the host context in the track function' , ( ) => {
100- let offsetReads = 0 ;
99+ describe ( 'trackBy' , ( ) => {
100+ it ( 'should have access to the host context in the track function' , ( ) => {
101+ let offsetReads = 0 ;
102+
103+ @Component ( { template : '@for ((item of items); track $index + offset) {{{item}}}' } )
104+ class TestComponent {
105+ items = [ 'a' , 'b' , 'c' ] ;
106+
107+ get offset ( ) {
108+ offsetReads ++ ;
109+ return 0 ;
110+ }
111+ }
101112
102- @Component ( { template : '@for ((item of items); track $index + offset) {{{item}}}' } )
103- class TestComponent {
104- items = [ 'a' , 'b' , 'c' ] ;
113+ const fixture = TestBed . createComponent ( TestComponent ) ;
114+ fixture . detectChanges ( ) ;
115+ expect ( fixture . nativeElement . textContent ) . toBe ( 'abc' ) ;
116+ expect ( offsetReads ) . toBeGreaterThan ( 0 ) ;
117+
118+ const prevReads = offsetReads ;
119+ // explicitly modify the DOM text node to make sure that the list reconciliation algorithm
120+ // based on tracking indices overrides it.
121+ fixture . debugElement . childNodes [ 1 ] . nativeNode . data = 'x' ;
122+ fixture . componentInstance . items . shift ( ) ;
123+ fixture . detectChanges ( ) ;
124+ expect ( fixture . nativeElement . textContent ) . toBe ( 'bc' ) ;
125+ expect ( offsetReads ) . toBeGreaterThan ( prevReads ) ;
126+ } ) ;
127+
128+ it ( 'should be able to access component properties in the tracking function from a loop at the root of the template' ,
129+ ( ) => {
130+ const calls = new Set ( ) ;
131+
132+ @Component ( {
133+ template : `@for ((item of items); track trackingFn(item, compProp)) {{{item}}}` ,
134+ } )
135+ class TestComponent {
136+ items = [ 'a' , 'b' ] ;
137+ compProp = 'hello' ;
138+
139+ trackingFn ( item : string , message : string ) {
140+ calls . add ( `${ item } :${ message } ` ) ;
141+ return item ;
142+ }
143+ }
105144
106- get offset ( ) {
107- offsetReads ++ ;
108- return 0 ;
109- }
110- }
145+ const fixture = TestBed . createComponent ( TestComponent ) ;
146+ fixture . detectChanges ( ) ;
147+ expect ( [ ...calls ] . sort ( ) ) . toEqual ( [ 'a:hello' , 'b:hello' ] ) ;
148+ } ) ;
149+
150+ it ( 'should be able to access component properties in the tracking function from a nested template' ,
151+ ( ) => {
152+ const calls = new Set ( ) ;
153+
154+ @Component ( {
155+ template : `
156+ @if (true) {
157+ @if (true) {
158+ @if (true) {
159+ @for ((item of items); track trackingFn(item, compProp)) {{{item}}}
160+ }
161+ }
162+ }
163+ ` ,
164+ } )
165+ class TestComponent {
166+ items = [ 'a' , 'b' ] ;
167+ compProp = 'hello' ;
168+
169+ trackingFn ( item : string , message : string ) {
170+ calls . add ( `${ item } :${ message } ` ) ;
171+ return item ;
172+ }
173+ }
111174
112- const fixture = TestBed . createComponent ( TestComponent ) ;
113- fixture . detectChanges ( ) ;
114- expect ( fixture . nativeElement . textContent ) . toBe ( 'abc' ) ;
115- expect ( offsetReads ) . toBeGreaterThan ( 0 ) ;
116-
117- const prevReads = offsetReads ;
118- // explicitly modify the DOM text node to make sure that the list reconciliation algorithm
119- // based on tracking indices overrides it.
120- fixture . debugElement . childNodes [ 1 ] . nativeNode . data = 'x' ;
121- fixture . componentInstance . items . shift ( ) ;
122- fixture . detectChanges ( ) ;
123- expect ( fixture . nativeElement . textContent ) . toBe ( 'bc' ) ;
124- expect ( offsetReads ) . toBeGreaterThan ( prevReads ) ;
175+ const fixture = TestBed . createComponent ( TestComponent ) ;
176+ fixture . detectChanges ( ) ;
177+ expect ( [ ...calls ] . sort ( ) ) . toEqual ( [ 'a:hello' , 'b:hello' ] ) ;
178+ } ) ;
125179 } ) ;
126180
127- it ( 'should be able to access component properties in the tracking function from a loop at the root of the template' ,
128- ( ) => {
129- const calls = new Set ( ) ;
181+ describe ( 'list diffing and view operations' , ( ) => {
182+ it ( 'should delete views in the middle' , ( ) => {
183+ @Component ( {
184+ template : '@for (item of items; track item; let idx = $index) {{{item}}({{idx}})|}' ,
185+ } )
186+ class TestComponent {
187+ items = [ 1 , 2 , 3 ] ;
188+ }
130189
131- @Component ( {
132- template : `@for ((item of items); track trackingFn(item, compProp)) {{{item}}}` ,
133- } )
134- class TestComponent {
135- items = [ 'a' , 'b' ] ;
136- compProp = 'hello' ;
190+ const fixture = TestBed . createComponent ( TestComponent ) ;
191+ fixture . detectChanges ( ) ;
192+ expect ( fixture . nativeElement . textContent ) . toBe ( '1(0)|2(1)|3(2)|' ) ;
193+
194+ // delete in the middle
195+ fixture . componentInstance . items . splice ( 1 , 1 ) ;
196+ fixture . detectChanges ( ) ;
197+ expect ( fixture . nativeElement . textContent ) . toBe ( '1(0)|3(1)|' ) ;
198+ } ) ;
199+
200+ it ( 'should insert views in the middle' , ( ) => {
201+ @Component ( {
202+ template : '@for (item of items; track item; let idx = $index) {{{item}}({{idx}})|}' ,
203+ } )
204+ class TestComponent {
205+ items = [ 1 , 3 ] ;
206+ }
137207
138- trackingFn ( item : string , message : string ) {
139- calls . add ( `${ item } :${ message } ` ) ;
140- return item ;
141- }
142- }
143-
144- const fixture = TestBed . createComponent ( TestComponent ) ;
145- fixture . detectChanges ( ) ;
146- expect ( [ ...calls ] . sort ( ) ) . toEqual ( [ 'a:hello' , 'b:hello' ] ) ;
147- } ) ;
148-
149- it ( 'should be able to access component properties in the tracking function from a nested template' ,
150- ( ) => {
151- const calls = new Set ( ) ;
152-
153- @Component ( {
154- template : `
155- @if (true) {
156- @if (true) {
157- @if (true) {
158- @for ((item of items); track trackingFn(item, compProp)) {{{item}}}
159- }
160- }
161- }
162- ` ,
163- } )
164- class TestComponent {
165- items = [ 'a' , 'b' ] ;
166- compProp = 'hello' ;
167-
168- trackingFn ( item : string , message : string ) {
169- calls . add ( `${ item } :${ message } ` ) ;
170- return item ;
171- }
172- }
208+ const fixture = TestBed . createComponent ( TestComponent ) ;
209+ fixture . detectChanges ( ) ;
210+ expect ( fixture . nativeElement . textContent ) . toBe ( '1(0)|3(1)|' ) ;
211+
212+
213+ // add in the middle
214+ fixture . componentInstance . items . splice ( 1 , 0 , 2 ) ;
215+ fixture . detectChanges ( ) ;
216+ expect ( fixture . nativeElement . textContent ) . toBe ( '1(0)|2(1)|3(2)|' ) ;
217+ } ) ;
218+
219+ it ( 'should replace different items' , ( ) => {
220+ @Component ( {
221+ template : '@for (item of items; track item; let idx = $index) {{{item}}({{idx}})|}' ,
222+ } )
223+ class TestComponent {
224+ items = [ 1 , 2 , 3 ] ;
225+ }
226+
227+ const fixture = TestBed . createComponent ( TestComponent ) ;
228+ fixture . detectChanges ( ) ;
229+ expect ( fixture . nativeElement . textContent ) . toBe ( '1(0)|2(1)|3(2)|' ) ;
173230
174- const fixture = TestBed . createComponent ( TestComponent ) ;
175- fixture . detectChanges ( ) ;
176- expect ( [ ...calls ] . sort ( ) ) . toEqual ( [ 'a:hello' , 'b:hello' ] ) ;
177- } ) ;
231+
232+ // an item in the middle stays the same, the rest gets replaced
233+ fixture . componentInstance . items = [ 5 , 2 , 7 ] ;
234+ fixture . detectChanges ( ) ;
235+ expect ( fixture . nativeElement . textContent ) . toBe ( '5(0)|2(1)|7(2)|' ) ;
236+ } ) ;
237+
238+ it ( 'should move and delete items' , ( ) => {
239+ @Component ( {
240+ template : '@for (item of items; track item; let idx = $index) {{{item}}({{idx}})|}' ,
241+ } )
242+ class TestComponent {
243+ items = [ 1 , 2 , 3 , 4 , 5 , 6 ] ;
244+ }
245+
246+ const fixture = TestBed . createComponent ( TestComponent ) ;
247+ fixture . detectChanges ( false ) ;
248+ expect ( fixture . nativeElement . textContent ) . toBe ( '1(0)|2(1)|3(2)|4(3)|5(4)|6(5)|' ) ;
249+
250+ // move 5 and do some other delete other operations
251+ fixture . componentInstance . items = [ 5 , 3 , 7 ] ;
252+ fixture . detectChanges ( ) ;
253+ expect ( fixture . nativeElement . textContent ) . toBe ( '5(0)|3(1)|7(2)|' ) ;
254+ } ) ;
255+ } ) ;
178256} ) ;
0 commit comments