OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
| 5 import 'dart:async'; |
5 import 'package:observe/observe.dart'; | 6 import 'package:observe/observe.dart'; |
6 import 'package:unittest/unittest.dart'; | 7 import 'package:unittest/unittest.dart'; |
7 import 'utils.dart'; | 8 import 'observe_test_utils.dart'; |
8 | 9 |
9 // This file contains code ported from: | 10 // This file contains code ported from: |
10 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js | 11 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js |
11 | 12 |
12 main() { | 13 main() { |
13 // TODO(jmesserly): rename this? Is summarizeListChanges coming back? | 14 // TODO(jmesserly): rename this? Is summarizeListChanges coming back? |
14 group('summarizeListChanges', listChangeTests); | 15 group('summarizeListChanges', listChangeTests); |
15 } | 16 } |
16 | 17 |
17 // TODO(jmesserly): port or write array fuzzer tests | 18 // TODO(jmesserly): port or write array fuzzer tests |
18 listChangeTests() { | 19 listChangeTests() { |
| 20 StreamSubscription sub; |
19 | 21 |
20 test('sequential adds', () { | 22 tearDown(() { sub.cancel(); }); |
| 23 |
| 24 observeTest('sequential adds', () { |
21 var model = toObservable([]); | 25 var model = toObservable([]); |
22 model.add(0); | 26 model.add(0); |
23 | 27 |
24 var summary; | 28 var summary; |
25 var sub = model.changes.listen((r) { summary = _filter(r); }); | 29 sub = model.changes.listen((r) { summary = _filter(r); }); |
26 | 30 |
27 model.add(1); | 31 model.add(1); |
28 model.add(2); | 32 model.add(2); |
29 | 33 |
30 expect(summary, null); | 34 expect(summary, null); |
31 deliverChangeRecords(); | 35 performMicrotaskCheckpoint(); |
32 | 36 |
33 expectChanges(summary, [_delta(1, 0, 2)]); | 37 expectChanges(summary, [_delta(1, 0, 2)]); |
34 }); | 38 }); |
35 | 39 |
36 test('List Splice Truncate And Expand With Length', () { | 40 observeTest('List Splice Truncate And Expand With Length', () { |
37 var model = toObservable(['a', 'b', 'c', 'd', 'e']); | 41 var model = toObservable(['a', 'b', 'c', 'd', 'e']); |
38 | 42 |
39 var summary; | 43 var summary; |
40 var sub = model.changes.listen((r) { summary = _filter(r); }); | 44 sub = model.changes.listen((r) { summary = _filter(r); }); |
41 | 45 |
42 model.length = 2; | 46 model.length = 2; |
43 | 47 |
44 deliverChangeRecords(); | 48 performMicrotaskCheckpoint(); |
45 expectChanges(summary, [_delta(2, 3, 0)]); | 49 expectChanges(summary, [_delta(2, 3, 0)]); |
46 summary = null; | 50 summary = null; |
47 | 51 |
48 model.length = 5; | 52 model.length = 5; |
49 | 53 |
50 deliverChangeRecords(); | 54 performMicrotaskCheckpoint(); |
51 expectChanges(summary, [_delta(2, 0, 3)]); | 55 expectChanges(summary, [_delta(2, 0, 3)]); |
52 }); | 56 }); |
53 | 57 |
54 group('List deltas can be applied', () { | 58 group('List deltas can be applied', () { |
55 | 59 |
56 var summary = null; | 60 var summary = null; |
57 | 61 |
58 observeArray(model) { | 62 observeArray(model) { |
59 model.changes.listen((records) { summary = _filter(records); }); | 63 sub = model.changes.listen((records) { summary = _filter(records); }); |
60 } | 64 } |
61 | 65 |
62 applyAndCheckDeltas(model, copy) { | 66 applyAndCheckDeltas(model, copy) { |
63 summary = null; | 67 summary = null; |
64 deliverChangeRecords(); | 68 performMicrotaskCheckpoint(); |
65 | 69 |
66 // apply deltas to the copy | 70 // apply deltas to the copy |
67 for (var delta in summary) { | 71 for (var delta in summary) { |
68 copy.removeRange(delta.index, delta.index + delta.removedCount); | 72 copy.removeRange(delta.index, delta.index + delta.removedCount); |
69 for (int i = delta.addedCount - 1; i >= 0; i--) { | 73 for (int i = delta.addedCount - 1; i >= 0; i--) { |
70 copy.insert(delta.index, model[delta.index + i]); | 74 copy.insert(delta.index, model[delta.index + i]); |
71 } | 75 } |
72 } | 76 } |
73 | 77 |
74 // Note: compare strings for easier debugging. | 78 // Note: compare strings for easier debugging. |
75 expect('$copy', '$model', reason: '!!! summary $summary'); | 79 expect('$copy', '$model', reason: '!!! summary $summary'); |
76 } | 80 } |
77 | 81 |
78 test('Contained', () { | 82 observeTest('Contained', () { |
79 var model = toObservable(['a', 'b']); | 83 var model = toObservable(['a', 'b']); |
80 var copy = model.toList(); | 84 var copy = model.toList(); |
81 observeArray(model); | 85 observeArray(model); |
82 | 86 |
83 model.removeAt(1); | 87 model.removeAt(1); |
84 model.insertAll(0, ['c', 'd', 'e']); | 88 model.insertAll(0, ['c', 'd', 'e']); |
85 model.removeRange(1, 3); | 89 model.removeRange(1, 3); |
86 model.insert(1, 'f'); | 90 model.insert(1, 'f'); |
87 | 91 |
88 applyAndCheckDeltas(model, copy); | 92 applyAndCheckDeltas(model, copy); |
89 }); | 93 }); |
90 | 94 |
91 test('Delete Empty', () { | 95 observeTest('Delete Empty', () { |
92 var model = toObservable([1]); | 96 var model = toObservable([1]); |
93 var copy = model.toList(); | 97 var copy = model.toList(); |
94 observeArray(model); | 98 observeArray(model); |
95 | 99 |
96 model.removeAt(0); | 100 model.removeAt(0); |
97 model.insertAll(0, ['a', 'b', 'c']); | 101 model.insertAll(0, ['a', 'b', 'c']); |
98 | 102 |
99 applyAndCheckDeltas(model, copy); | 103 applyAndCheckDeltas(model, copy); |
100 }); | 104 }); |
101 | 105 |
102 test('Right Non Overlap', () { | 106 observeTest('Right Non Overlap', () { |
103 var model = toObservable(['a', 'b', 'c', 'd']); | 107 var model = toObservable(['a', 'b', 'c', 'd']); |
104 var copy = model.toList(); | 108 var copy = model.toList(); |
105 observeArray(model); | 109 observeArray(model); |
106 | 110 |
107 model.removeRange(0, 1); | 111 model.removeRange(0, 1); |
108 model.insert(0, 'e'); | 112 model.insert(0, 'e'); |
109 model.removeRange(2, 3); | 113 model.removeRange(2, 3); |
110 model.insertAll(2, ['f', 'g']); | 114 model.insertAll(2, ['f', 'g']); |
111 | 115 |
112 applyAndCheckDeltas(model, copy); | 116 applyAndCheckDeltas(model, copy); |
113 }); | 117 }); |
114 | 118 |
115 test('Left Non Overlap', () { | 119 observeTest('Left Non Overlap', () { |
116 var model = toObservable(['a', 'b', 'c', 'd']); | 120 var model = toObservable(['a', 'b', 'c', 'd']); |
117 var copy = model.toList(); | 121 var copy = model.toList(); |
118 observeArray(model); | 122 observeArray(model); |
119 | 123 |
120 model.removeRange(3, 4); | 124 model.removeRange(3, 4); |
121 model.insertAll(3, ['f', 'g']); | 125 model.insertAll(3, ['f', 'g']); |
122 model.removeRange(0, 1); | 126 model.removeRange(0, 1); |
123 model.insert(0, 'e'); | 127 model.insert(0, 'e'); |
124 | 128 |
125 applyAndCheckDeltas(model, copy); | 129 applyAndCheckDeltas(model, copy); |
126 }); | 130 }); |
127 | 131 |
128 test('Right Adjacent', () { | 132 observeTest('Right Adjacent', () { |
129 var model = toObservable(['a', 'b', 'c', 'd']); | 133 var model = toObservable(['a', 'b', 'c', 'd']); |
130 var copy = model.toList(); | 134 var copy = model.toList(); |
131 observeArray(model); | 135 observeArray(model); |
132 | 136 |
133 model.removeRange(1, 2); | 137 model.removeRange(1, 2); |
134 model.insert(3, 'e'); | 138 model.insert(3, 'e'); |
135 model.removeRange(2, 3); | 139 model.removeRange(2, 3); |
136 model.insertAll(0, ['f', 'g']); | 140 model.insertAll(0, ['f', 'g']); |
137 | 141 |
138 applyAndCheckDeltas(model, copy); | 142 applyAndCheckDeltas(model, copy); |
139 }); | 143 }); |
140 | 144 |
141 test('Left Adjacent', () { | 145 observeTest('Left Adjacent', () { |
142 var model = toObservable(['a', 'b', 'c', 'd']); | 146 var model = toObservable(['a', 'b', 'c', 'd']); |
143 var copy = model.toList(); | 147 var copy = model.toList(); |
144 observeArray(model); | 148 observeArray(model); |
145 | 149 |
146 model.removeRange(2, 4); | 150 model.removeRange(2, 4); |
147 model.insert(2, 'e'); | 151 model.insert(2, 'e'); |
148 | 152 |
149 model.removeAt(1); | 153 model.removeAt(1); |
150 model.insertAll(1, ['f', 'g']); | 154 model.insertAll(1, ['f', 'g']); |
151 | 155 |
152 applyAndCheckDeltas(model, copy); | 156 applyAndCheckDeltas(model, copy); |
153 }); | 157 }); |
154 | 158 |
155 test('Right Overlap', () { | 159 observeTest('Right Overlap', () { |
156 var model = toObservable(['a', 'b', 'c', 'd']); | 160 var model = toObservable(['a', 'b', 'c', 'd']); |
157 var copy = model.toList(); | 161 var copy = model.toList(); |
158 observeArray(model); | 162 observeArray(model); |
159 | 163 |
160 model.removeAt(1); | 164 model.removeAt(1); |
161 model.insert(1, 'e'); | 165 model.insert(1, 'e'); |
162 model.removeAt(1); | 166 model.removeAt(1); |
163 model.insertAll(1, ['f', 'g']); | 167 model.insertAll(1, ['f', 'g']); |
164 | 168 |
165 applyAndCheckDeltas(model, copy); | 169 applyAndCheckDeltas(model, copy); |
166 }); | 170 }); |
167 | 171 |
168 test('Left Overlap', () { | 172 observeTest('Left Overlap', () { |
169 var model = toObservable(['a', 'b', 'c', 'd']); | 173 var model = toObservable(['a', 'b', 'c', 'd']); |
170 var copy = model.toList(); | 174 var copy = model.toList(); |
171 observeArray(model); | 175 observeArray(model); |
172 | 176 |
173 model.removeAt(2); | 177 model.removeAt(2); |
174 model.insertAll(2, ['e', 'f', 'g']); | 178 model.insertAll(2, ['e', 'f', 'g']); |
175 // a b [e f g] d | 179 // a b [e f g] d |
176 model.removeRange(1, 3); | 180 model.removeRange(1, 3); |
177 model.insertAll(1, ['h', 'i', 'j']); | 181 model.insertAll(1, ['h', 'i', 'j']); |
178 // a [h i j] f g d | 182 // a [h i j] f g d |
179 | 183 |
180 applyAndCheckDeltas(model, copy); | 184 applyAndCheckDeltas(model, copy); |
181 }); | 185 }); |
182 | 186 |
183 test('Prefix And Suffix One In', () { | 187 observeTest('Prefix And Suffix One In', () { |
184 var model = toObservable(['a', 'b', 'c', 'd']); | 188 var model = toObservable(['a', 'b', 'c', 'd']); |
185 var copy = model.toList(); | 189 var copy = model.toList(); |
186 observeArray(model); | 190 observeArray(model); |
187 | 191 |
188 model.insert(0, 'z'); | 192 model.insert(0, 'z'); |
189 model.add('z'); | 193 model.add('z'); |
190 | 194 |
191 applyAndCheckDeltas(model, copy); | 195 applyAndCheckDeltas(model, copy); |
192 }); | 196 }); |
193 | 197 |
194 test('Remove First', () { | 198 observeTest('Remove First', () { |
195 var model = toObservable([16, 15, 15]); | 199 var model = toObservable([16, 15, 15]); |
196 var copy = model.toList(); | 200 var copy = model.toList(); |
197 observeArray(model); | 201 observeArray(model); |
198 | 202 |
199 model.removeAt(0); | 203 model.removeAt(0); |
200 | 204 |
201 applyAndCheckDeltas(model, copy); | 205 applyAndCheckDeltas(model, copy); |
202 }); | 206 }); |
203 | 207 |
204 test('Update Remove', () { | 208 observeTest('Update Remove', () { |
205 var model = toObservable(['a', 'b', 'c', 'd']); | 209 var model = toObservable(['a', 'b', 'c', 'd']); |
206 var copy = model.toList(); | 210 var copy = model.toList(); |
207 observeArray(model); | 211 observeArray(model); |
208 | 212 |
209 model.removeAt(2); | 213 model.removeAt(2); |
210 model.insertAll(2, ['e', 'f', 'g']); // a b [e f g] d | 214 model.insertAll(2, ['e', 'f', 'g']); // a b [e f g] d |
211 model[0] = 'h'; | 215 model[0] = 'h'; |
212 model.removeAt(1); | 216 model.removeAt(1); |
213 | 217 |
214 applyAndCheckDeltas(model, copy); | 218 applyAndCheckDeltas(model, copy); |
215 }); | 219 }); |
216 | 220 |
217 test('Remove Mid List', () { | 221 observeTest('Remove Mid List', () { |
218 var model = toObservable(['a', 'b', 'c', 'd']); | 222 var model = toObservable(['a', 'b', 'c', 'd']); |
219 var copy = model.toList(); | 223 var copy = model.toList(); |
220 observeArray(model); | 224 observeArray(model); |
221 | 225 |
222 model.removeAt(2); | 226 model.removeAt(2); |
223 | 227 |
224 applyAndCheckDeltas(model, copy); | 228 applyAndCheckDeltas(model, copy); |
225 }); | 229 }); |
226 }); | 230 }); |
227 | 231 |
228 group('edit distance', () { | 232 group('edit distance', () { |
229 var summary = null; | 233 var summary = null; |
230 | 234 |
231 observeArray(model) { | 235 observeArray(model) { |
232 model.changes.listen((records) { summary = _filter(records); }); | 236 sub = model.changes.listen((records) { summary = _filter(records); }); |
233 } | 237 } |
234 | 238 |
235 assertEditDistance(orig, expectDistance) { | 239 assertEditDistance(orig, expectDistance) { |
236 summary = null; | 240 summary = null; |
237 deliverChangeRecords(); | 241 performMicrotaskCheckpoint(); |
238 var actualDistance = 0; | 242 var actualDistance = 0; |
239 | 243 |
240 if (summary != null) { | 244 if (summary != null) { |
241 for (var delta in summary) { | 245 for (var delta in summary) { |
242 actualDistance += delta.addedCount + delta.removedCount; | 246 actualDistance += delta.addedCount + delta.removedCount; |
243 } | 247 } |
244 } | 248 } |
245 | 249 |
246 expect(actualDistance, expectDistance); | 250 expect(actualDistance, expectDistance); |
247 } | 251 } |
248 | 252 |
249 test('add items', () { | 253 observeTest('add items', () { |
250 var model = toObservable([]); | 254 var model = toObservable([]); |
251 observeArray(model); | 255 observeArray(model); |
252 model.addAll([1, 2, 3]); | 256 model.addAll([1, 2, 3]); |
253 assertEditDistance(model, 3); | 257 assertEditDistance(model, 3); |
254 }); | 258 }); |
255 | 259 |
256 test('trunacte and add, sharing a contiguous block', () { | 260 observeTest('trunacte and add, sharing a contiguous block', () { |
257 var model = toObservable(['x', 'x', 'x', 'x', '1', '2', '3']); | 261 var model = toObservable(['x', 'x', 'x', 'x', '1', '2', '3']); |
258 observeArray(model); | 262 observeArray(model); |
259 model.length = 0; | 263 model.length = 0; |
260 model.addAll(['1', '2', '3', 'y', 'y', 'y', 'y']); | 264 model.addAll(['1', '2', '3', 'y', 'y', 'y', 'y']); |
261 // Note: unlike the JS implementation, we don't perform a full diff. | 265 // Note: unlike the JS implementation, we don't perform a full diff. |
262 // The change records are computed with no regards to the *contents* of | 266 // The change records are computed with no regards to the *contents* of |
263 // the array. Thus, we get 14 instead of 8. | 267 // the array. Thus, we get 14 instead of 8. |
264 assertEditDistance(model, 14); | 268 assertEditDistance(model, 14); |
265 }); | 269 }); |
266 | 270 |
267 test('truncate and add, sharing a discontiguous block', () { | 271 observeTest('truncate and add, sharing a discontiguous block', () { |
268 var model = toObservable(['1', '2', '3', '4', '5']); | 272 var model = toObservable(['1', '2', '3', '4', '5']); |
269 observeArray(model); | 273 observeArray(model); |
270 model.length = 0; | 274 model.length = 0; |
271 model.addAll(['a', '2', 'y', 'y', '4', '5', 'z', 'z']); | 275 model.addAll(['a', '2', 'y', 'y', '4', '5', 'z', 'z']); |
272 // Note: unlike the JS implementation, we don't perform a full diff. | 276 // Note: unlike the JS implementation, we don't perform a full diff. |
273 // The change records are computed with no regards to the *contents* of | 277 // The change records are computed with no regards to the *contents* of |
274 // the array. Thus, we get 13 instead of 7. | 278 // the array. Thus, we get 13 instead of 7. |
275 assertEditDistance(model, 13); | 279 assertEditDistance(model, 13); |
276 }); | 280 }); |
277 | 281 |
278 test('insert at beginning and end', () { | 282 observeTest('insert at beginning and end', () { |
279 var model = toObservable([2, 3, 4]); | 283 var model = toObservable([2, 3, 4]); |
280 observeArray(model); | 284 observeArray(model); |
281 model.insert(0, 5); | 285 model.insert(0, 5); |
282 model[2] = 6; | 286 model[2] = 6; |
283 model.add(7); | 287 model.add(7); |
284 assertEditDistance(model, 4); | 288 assertEditDistance(model, 4); |
285 }); | 289 }); |
286 }); | 290 }); |
287 } | 291 } |
288 | 292 |
289 _delta(i, r, a) => new ListChangeRecord(i, removedCount: r, addedCount: a); | 293 _delta(i, r, a) => new ListChangeRecord(i, removedCount: r, addedCount: a); |
290 _filter(records) => records.where((r) => r is ListChangeRecord).toList(); | 294 _filter(records) => records.where((r) => r is ListChangeRecord).toList(); |
OLD | NEW |