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