OLD | NEW |
1 library scope2_spec; | 1 library scope2_spec; |
2 | 2 |
3 import '../_specs.dart'; | 3 import '../_specs.dart'; |
4 import 'package:angular/change_detection/change_detection.dart' hide ExceptionHa
ndler; | 4 import 'package:angular/change_detection/change_detection.dart' hide ExceptionHa
ndler; |
5 import 'package:angular/change_detection/dirty_checking_change_detector.dart'; | 5 import 'package:angular/change_detection/dirty_checking_change_detector.dart'; |
6 import 'dart:async'; | 6 import 'dart:async'; |
7 import 'dart:math'; | 7 import 'dart:math'; |
8 | 8 |
9 main() => describe('scope', () { | 9 void main() { |
10 beforeEach(module((Module module) { | 10 describe('scope', () { |
11 Map context = {}; | 11 beforeEachModule((Module module) { |
12 module.value(GetterCache, new GetterCache({})); | 12 Map context = {}; |
13 module.type(ChangeDetector, implementedBy: DirtyCheckingChangeDetector); | 13 module |
14 module.value(Object, context); | 14 ..type(ChangeDetector, implementedBy: DirtyCheckingChangeDetector) |
15 module.value(Map, context); | 15 ..value(Object, context) |
16 module.type(RootScope); | 16 ..value(Map, context) |
17 module.type(_MultiplyFilter); | 17 ..type(RootScope) |
18 module.type(_ListHeadFilter); | 18 ..type(_MultiplyFilter) |
19 module.type(_ListTailFilter); | 19 ..type(_ListHeadFilter) |
20 module.type(_SortFilter); | 20 ..type(_ListTailFilter) |
21 })); | 21 ..type(_SortFilter) |
22 | 22 ..type(_IdentityFilter) |
23 describe('AST Bridge', () { | 23 ..type(_MapKeys) |
24 it('should watch field', inject((Logger logger, Map context, RootScope rootS
cope) { | 24 ..type(ScopeStatsEmitter, implementedBy: MockScopeStatsEmitter); |
25 context['field'] = 'Worked!'; | |
26 rootScope.watch('field', (value, previous) => logger([value, previous])); | |
27 expect(logger).toEqual([]); | |
28 rootScope.digest(); | |
29 expect(logger).toEqual([['Worked!', null]]); | |
30 rootScope.digest(); | |
31 expect(logger).toEqual([['Worked!', null]]); | |
32 })); | |
33 | |
34 it('should watch field path', inject((Logger logger, Map context, RootScope
rootScope) { | |
35 context['a'] = {'b': 'AB'}; | |
36 rootScope.watch('a.b', (value, previous) => logger(value)); | |
37 rootScope.digest(); | |
38 expect(logger).toEqual(['AB']); | |
39 context['a']['b'] = '123'; | |
40 rootScope.digest(); | |
41 expect(logger).toEqual(['AB', '123']); | |
42 context['a'] = {'b': 'XYZ'}; | |
43 rootScope.digest(); | |
44 expect(logger).toEqual(['AB', '123', 'XYZ']); | |
45 })); | |
46 | |
47 it('should watch math operations', inject((Logger logger, Map context, RootS
cope rootScope) { | |
48 context['a'] = 1; | |
49 context['b'] = 2; | |
50 rootScope.watch('a + b + 1', (value, previous) => logger(value)); | |
51 rootScope.digest(); | |
52 expect(logger).toEqual([4]); | |
53 context['a'] = 3; | |
54 rootScope.digest(); | |
55 expect(logger).toEqual([4, 6]); | |
56 context['b'] = 5; | |
57 rootScope.digest(); | |
58 expect(logger).toEqual([4, 6, 9]); | |
59 })); | |
60 | |
61 | |
62 it('should watch literals', inject((Logger logger, Map context, RootScope ro
otScope) { | |
63 context['a'] = 1; | |
64 rootScope.watch('1', (value, previous) => logger(value)); | |
65 rootScope.watch('"str"', (value, previous) => logger(value)); | |
66 rootScope.watch('[a, 2, 3]', (value, previous) => logger(value)); | |
67 rootScope.watch('{a:a, b:2}', (value, previous) => logger(value)); | |
68 rootScope.digest(); | |
69 expect(logger).toEqual([1, 'str', [1, 2, 3], {'a': 1, 'b': 2}]); | |
70 logger.clear(); | |
71 context['a'] = 3; | |
72 rootScope.digest(); | |
73 expect(logger).toEqual([[3, 2, 3], {'a': 3, 'b': 2}]); | |
74 })); | |
75 | |
76 it('should invoke closures', inject((Logger logger, Map context, RootScope r
ootScope) { | |
77 context['fn'] = () { | |
78 logger('fn'); | |
79 return 1; | |
80 }; | |
81 context['a'] = {'fn': () { | |
82 logger('a.fn'); | |
83 return 2; | |
84 }}; | |
85 rootScope.watch('fn()', (value, previous) => logger('=> $value')); | |
86 rootScope.watch('a.fn()', (value, previous) => logger('-> $value')); | |
87 rootScope.digest(); | |
88 expect(logger).toEqual(['fn', 'a.fn', '=> 1', '-> 2', | |
89 /* second loop*/ 'fn', 'a.fn']); | |
90 logger.clear(); | |
91 rootScope.digest(); | |
92 expect(logger).toEqual(['fn', 'a.fn']); | |
93 })); | |
94 | |
95 it('should perform conditionals', inject((Logger logger, Map context, RootSc
ope rootScope) { | |
96 context['a'] = 1; | |
97 context['b'] = 2; | |
98 context['c'] = 3; | |
99 rootScope.watch('a?b:c', (value, previous) => logger(value)); | |
100 rootScope.digest(); | |
101 expect(logger).toEqual([2]); | |
102 logger.clear(); | |
103 context['a'] = 0; | |
104 rootScope.digest(); | |
105 expect(logger).toEqual([3]); | |
106 })); | |
107 | |
108 | |
109 xit('should call function', inject((Logger logger, Map context, RootScope ro
otScope) { | |
110 context['a'] = () { | |
111 return () { return 123; }; | |
112 }; | |
113 rootScope.watch('a()()', (value, previous) => logger(value)); | |
114 rootScope.digest(); | |
115 expect(logger).toEqual([123]); | |
116 logger.clear(); | |
117 rootScope.digest(); | |
118 expect(logger).toEqual([]); | |
119 })); | |
120 | |
121 it('should access bracket', inject((Logger logger, Map context, RootScope ro
otScope) { | |
122 context['a'] = {'b': 123}; | |
123 rootScope.watch('a["b"]', (value, previous) => logger(value)); | |
124 rootScope.digest(); | |
125 expect(logger).toEqual([123]); | |
126 logger.clear(); | |
127 rootScope.digest(); | |
128 expect(logger).toEqual([]); | |
129 })); | |
130 | |
131 | |
132 it('should prefix', inject((Logger logger, Map context, RootScope rootScope)
{ | |
133 context['a'] = true; | |
134 rootScope.watch('!a', (value, previous) => logger(value)); | |
135 rootScope.digest(); | |
136 expect(logger).toEqual([false]); | |
137 logger.clear(); | |
138 context['a'] = false; | |
139 rootScope.digest(); | |
140 expect(logger).toEqual([true]); | |
141 })); | |
142 | |
143 it('should support filters', inject((Logger logger, Map context, | |
144 RootScope rootScope, AstParser parser, | |
145 FilterMap filters) { | |
146 context['a'] = 123; | |
147 context['b'] = 2; | |
148 rootScope.watch( | |
149 parser('a | multiply:b', filters: filters), | |
150 (value, previous) => logger(value)); | |
151 rootScope.digest(); | |
152 expect(logger).toEqual([246]); | |
153 logger.clear(); | |
154 rootScope.digest(); | |
155 expect(logger).toEqual([]); | |
156 logger.clear(); | |
157 })); | |
158 | |
159 it('should support arrays in filters', inject((Logger logger, Map context, | |
160 RootScope rootScope, | |
161 AstParser parser, | |
162 FilterMap filters) { | |
163 context['a'] = [1]; | |
164 rootScope.watch( | |
165 parser('a | sort | listHead:"A" | listTail:"B"', filters: filters), | |
166 (value, previous) => logger(value)); | |
167 rootScope.digest(); | |
168 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 'B']]); | |
169 logger.clear(); | |
170 | |
171 rootScope.digest(); | |
172 expect(logger).toEqual([]); | |
173 logger.clear(); | |
174 | |
175 context['a'].add(2); | |
176 rootScope.digest(); | |
177 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 2, 'B']])
; | |
178 logger.clear(); | |
179 | |
180 // We change the order, but sort should change it to same one and it shoul
d not | |
181 // call subsequent filters. | |
182 context['a'] = [2, 1]; | |
183 rootScope.digest(); | |
184 expect(logger).toEqual(['sort']); | |
185 logger.clear(); | |
186 })); | |
187 }); | |
188 | |
189 | |
190 describe('properties', () { | |
191 describe('root', () { | |
192 it('should point to itself', inject((RootScope rootScope) { | |
193 expect(rootScope.rootScope).toEqual(rootScope); | |
194 })); | |
195 | |
196 it('children should point to root', inject((RootScope rootScope) { | |
197 var child = rootScope.createChild(new PrototypeMap(rootScope.context)); | |
198 expect(child.rootScope).toEqual(rootScope); | |
199 expect(child.createChild(new PrototypeMap(rootScope.context)).rootScope)
.toEqual(rootScope); | |
200 })); | |
201 }); | 25 }); |
202 | 26 |
203 | 27 describe('AST Bridge', () { |
204 describe('parent', () { | 28 it('should watch field', (Logger logger, Map context, RootScope rootScope)
{ |
205 it('should not have parent', inject((RootScope rootScope) { | 29 context['field'] = 'Worked!'; |
206 expect(rootScope.parentScope).toEqual(null); | 30 rootScope.watch('field', (value, previous) => logger([value, previous]))
; |
207 })); | 31 expect(logger).toEqual([]); |
208 | 32 rootScope.digest(); |
209 | 33 expect(logger).toEqual([['Worked!', null]]); |
210 it('should point to parent', inject((RootScope rootScope) { | 34 rootScope.digest(); |
211 var child = rootScope.createChild(new PrototypeMap(rootScope.context)); | 35 expect(logger).toEqual([['Worked!', null]]); |
212 expect(rootScope.parentScope).toEqual(null); | 36 }); |
213 expect(child.parentScope).toEqual(rootScope); | 37 |
214 expect(child.createChild(new PrototypeMap(rootScope.context)).parentScop
e).toEqual(child); | 38 it('should watch field path', (Logger logger, Map context, RootScope rootS
cope) { |
215 })); | 39 context['a'] = {'b': 'AB'}; |
| 40 rootScope.watch('a.b', (value, previous) => logger(value)); |
| 41 rootScope.digest(); |
| 42 expect(logger).toEqual(['AB']); |
| 43 context['a']['b'] = '123'; |
| 44 rootScope.digest(); |
| 45 expect(logger).toEqual(['AB', '123']); |
| 46 context['a'] = {'b': 'XYZ'}; |
| 47 rootScope.digest(); |
| 48 expect(logger).toEqual(['AB', '123', 'XYZ']); |
| 49 }); |
| 50 |
| 51 it('should watch math operations', (Logger logger, Map context, RootScope
rootScope) { |
| 52 context['a'] = 1; |
| 53 context['b'] = 2; |
| 54 rootScope.watch('a + b + 1', (value, previous) => logger(value)); |
| 55 rootScope.digest(); |
| 56 expect(logger).toEqual([4]); |
| 57 context['a'] = 3; |
| 58 rootScope.digest(); |
| 59 expect(logger).toEqual([4, 6]); |
| 60 context['b'] = 5; |
| 61 rootScope.digest(); |
| 62 expect(logger).toEqual([4, 6, 9]); |
| 63 }); |
| 64 |
| 65 |
| 66 it('should watch literals', (Logger logger, Map context, RootScope rootSco
pe) { |
| 67 context['a'] = 1; |
| 68 rootScope |
| 69 ..watch('', (value, previous) => logger(value)) |
| 70 ..watch('""', (value, previous) => logger(value)) |
| 71 ..watch('1', (value, previous) => logger(value)) |
| 72 ..watch('"str"', (value, previous) => logger(value)) |
| 73 ..watch('[a, 2, 3]', (value, previous) => logger(value)) |
| 74 ..watch('{a:a, b:2}', (value, previous) => logger(value)) |
| 75 ..digest(); |
| 76 expect(logger).toEqual(['', '', 1, 'str', [1, 2, 3], {'a': 1, 'b': 2}]); |
| 77 logger.clear(); |
| 78 context['a'] = 3; |
| 79 rootScope.digest(); |
| 80 expect(logger).toEqual([[3, 2, 3], {'a': 3, 'b': 2}]); |
| 81 }); |
| 82 |
| 83 it('should watch nulls', (Logger logger, Map context, RootScope rootScope)
{ |
| 84 var r = (value, _) => logger(value); |
| 85 rootScope |
| 86 ..watch('null < 0',r) |
| 87 ..watch('null * 3', r) |
| 88 ..watch('null + 6', r) |
| 89 ..watch('5 + null', r) |
| 90 ..watch('null - 4', r) |
| 91 ..watch('3 - null', r) |
| 92 ..watch('null + null', r) |
| 93 ..watch('null - null', r) |
| 94 ..watch('null == null', r) |
| 95 ..watch('null != null', r) |
| 96 ..digest(); |
| 97 expect(logger).toEqual([null, null, 6, 5, -4, 3, 0, 0, true, false]); |
| 98 }); |
| 99 |
| 100 it('should invoke closures', (Logger logger, Map context, RootScope rootSc
ope) { |
| 101 context['fn'] = () { |
| 102 logger('fn'); |
| 103 return 1; |
| 104 }; |
| 105 context['a'] = {'fn': () { |
| 106 logger('a.fn'); |
| 107 return 2; |
| 108 }}; |
| 109 rootScope.watch('fn()', (value, previous) => logger('=> $value')); |
| 110 rootScope.watch('a.fn()', (value, previous) => logger('-> $value')); |
| 111 rootScope.digest(); |
| 112 expect(logger).toEqual(['fn', 'a.fn', '=> 1', '-> 2', |
| 113 /* second loop*/ 'fn', 'a.fn']); |
| 114 logger.clear(); |
| 115 rootScope.digest(); |
| 116 expect(logger).toEqual(['fn', 'a.fn']); |
| 117 }); |
| 118 |
| 119 it('should perform conditionals', (Logger logger, Map context, RootScope r
ootScope) { |
| 120 context['a'] = 1; |
| 121 context['b'] = 2; |
| 122 context['c'] = 3; |
| 123 rootScope.watch('a?b:c', (value, previous) => logger(value)); |
| 124 rootScope.digest(); |
| 125 expect(logger).toEqual([2]); |
| 126 logger.clear(); |
| 127 context['a'] = 0; |
| 128 rootScope.digest(); |
| 129 expect(logger).toEqual([3]); |
| 130 }); |
| 131 |
| 132 |
| 133 xit('should call function', (Logger logger, Map context, RootScope rootSco
pe) { |
| 134 context['a'] = () { |
| 135 return () { return 123; }; |
| 136 }; |
| 137 rootScope.watch('a()()', (value, previous) => logger(value)); |
| 138 rootScope.digest(); |
| 139 expect(logger).toEqual([123]); |
| 140 logger.clear(); |
| 141 rootScope.digest(); |
| 142 expect(logger).toEqual([]); |
| 143 }); |
| 144 |
| 145 it('should access bracket', (Logger logger, Map context, RootScope rootSco
pe) { |
| 146 context['a'] = {'b': 123}; |
| 147 rootScope.watch('a["b"]', (value, previous) => logger(value)); |
| 148 rootScope.digest(); |
| 149 expect(logger).toEqual([123]); |
| 150 logger.clear(); |
| 151 rootScope.digest(); |
| 152 expect(logger).toEqual([]); |
| 153 logger.clear(); |
| 154 |
| 155 context['a']['b'] = 234; |
| 156 rootScope.digest(); |
| 157 expect(logger).toEqual([234]); |
| 158 }); |
| 159 |
| 160 |
| 161 it('should prefix', (Logger logger, Map context, RootScope rootScope) { |
| 162 context['a'] = true; |
| 163 rootScope.watch('!a', (value, previous) => logger(value)); |
| 164 rootScope.digest(); |
| 165 expect(logger).toEqual([false]); |
| 166 logger.clear(); |
| 167 context['a'] = false; |
| 168 rootScope.digest(); |
| 169 expect(logger).toEqual([true]); |
| 170 }); |
| 171 |
| 172 it('should support formatters', (Logger logger, Map context, |
| 173 RootScope rootScope, FormatterMap formatters) { |
| 174 context['a'] = 123; |
| 175 context['b'] = 2; |
| 176 rootScope.watch('a | multiply:b', (value, previous) => logger(value), |
| 177 formatters: formatters); |
| 178 rootScope.digest(); |
| 179 expect(logger).toEqual([246]); |
| 180 logger.clear(); |
| 181 rootScope.digest(); |
| 182 expect(logger).toEqual([]); |
| 183 logger.clear(); |
| 184 }); |
| 185 |
| 186 it('should support arrays in formatters', (Logger logger, Map context, |
| 187 RootScope rootScope, FormatterMap formatters) { |
| 188 context['a'] = [1]; |
| 189 rootScope.watch('a | sort | listHead:"A" | listTail:"B"', |
| 190 (value, previous) => logger(value), formatters: formatters); |
| 191 rootScope.digest(); |
| 192 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 'B']]); |
| 193 logger.clear(); |
| 194 |
| 195 rootScope.digest(); |
| 196 expect(logger).toEqual([]); |
| 197 logger.clear(); |
| 198 |
| 199 context['a'].add(2); |
| 200 rootScope.digest(); |
| 201 expect(logger).toEqual(['sort', 'listHead', 'listTail', ['A', 1, 2, 'B']
]); |
| 202 logger.clear(); |
| 203 |
| 204 // We change the order, but sort should change it to same one and it sho
uld not |
| 205 // call subsequent formatters. |
| 206 context['a'] = [2, 1]; |
| 207 rootScope.digest(); |
| 208 expect(logger).toEqual(['sort']); |
| 209 logger.clear(); |
| 210 }); |
| 211 |
| 212 it('should support maps in formatters', (Logger logger, Map context, |
| 213 RootScope rootScope, FormatterMap formatters) { |
| 214 context['a'] = {'foo': 'bar'}; |
| 215 rootScope.watch('a | identity | keys', |
| 216 (value, previous) => logger(value), formatters: formatters); |
| 217 rootScope.digest(); |
| 218 expect(logger).toEqual(['identity', 'keys', ['foo']]); |
| 219 logger.clear(); |
| 220 |
| 221 rootScope.digest(); |
| 222 expect(logger).toEqual([]); |
| 223 logger.clear(); |
| 224 |
| 225 context['a']['bar'] = 'baz'; |
| 226 rootScope.digest(); |
| 227 expect(logger).toEqual(['identity', 'keys', ['foo', 'bar']]); |
| 228 logger.clear(); |
| 229 }); |
| 230 |
216 }); | 231 }); |
217 }); | 232 |
218 | 233 |
219 | 234 describe('properties', () { |
220 describe(r'events', () { | 235 describe('root', () { |
221 | 236 it('should point to itself', (RootScope rootScope) { |
222 describe('on', () { | 237 expect(rootScope.rootScope).toEqual(rootScope); |
223 it('should allow emit/broadcast when no listeners', inject((RootScope scop
e) { | 238 }); |
224 scope.emit('foo'); | 239 |
225 scope.broadcast('foo'); | 240 it('children should point to root', (RootScope rootScope) { |
226 })); | 241 var child = rootScope.createChild(new PrototypeMap(rootScope.context))
; |
227 | 242 expect(child.rootScope).toEqual(rootScope); |
228 | 243 expect(child.createChild(new PrototypeMap(rootScope.context)).rootScop
e).toEqual(rootScope); |
229 it(r'should add listener for both emit and broadcast events', inject((Root
Scope rootScope) { | 244 }); |
230 var log = '', | 245 }); |
231 child = rootScope.createChild(new PrototypeMap(rootScope.context)); | 246 |
232 | 247 |
233 eventFn(event) { | 248 describe('parent', () { |
234 expect(event).not.toEqual(null); | 249 it('should not have parent', (RootScope rootScope) { |
235 log += 'X'; | 250 expect(rootScope.parentScope).toEqual(null); |
236 } | 251 expect(rootScope.id).toEqual(''); |
237 | 252 }); |
238 child.on('abc').listen(eventFn); | 253 |
239 expect(log).toEqual(''); | 254 |
240 | 255 it('should point to parent', (RootScope rootScope) { |
241 child.emit('abc'); | 256 var child = rootScope.createChild(new PrototypeMap(rootScope.context))
; |
242 expect(log).toEqual('X'); | 257 expect(child.id).toEqual(':0'); |
243 | 258 expect(rootScope.parentScope).toEqual(null); |
244 child.broadcast('abc'); | 259 expect(child.parentScope).toEqual(rootScope); |
245 expect(log).toEqual('XX'); | 260 expect(child.createChild(new PrototypeMap(rootScope.context)).parentSc
ope).toEqual(child); |
246 })); | 261 }); |
247 | 262 }); |
248 | 263 }); |
249 it(r'should return a function that deregisters the listener', inject((Root
Scope rootScope) { | 264 |
250 var log = ''; | 265 |
251 var child = rootScope.createChild(new PrototypeMap(rootScope.context)); | 266 describe(r'events', () { |
252 var subscription; | 267 |
253 | 268 describe('on', () { |
254 eventFn(e) { | 269 it('should allow emit/broadcast when no listeners', (RootScope scope) { |
255 log += 'X'; | 270 scope.emit('foo'); |
256 } | 271 scope.broadcast('foo'); |
257 | 272 }); |
258 subscription = child.on('abc').listen(eventFn); | 273 |
259 expect(log).toEqual(''); | 274 |
260 expect(subscription).toBeDefined(); | 275 it(r'should add listener for both emit and broadcast events', (RootScope
rootScope) { |
261 | 276 var log = '', |
262 child.emit(r'abc'); | 277 child = rootScope.createChild(new PrototypeMap(rootScope.context)); |
263 child.broadcast('abc'); | 278 |
264 expect(log).toEqual('XX'); | 279 eventFn(event) { |
265 | 280 expect(event).not.toEqual(null); |
266 log = ''; | 281 log += 'X'; |
267 expect(subscription.cancel()).toBe(null); | |
268 child.emit(r'abc'); | |
269 child.broadcast('abc'); | |
270 expect(log).toEqual(''); | |
271 })); | |
272 | |
273 it('should not trigger assertions on scope fork', inject((RootScope root)
{ | |
274 var d1 = root.createChild({}); | |
275 var d2 = root.createChild({}); | |
276 var d3 = d2.createChild({}); | |
277 expect(root.apply).not.toThrow(); | |
278 d1.on(ScopeEvent.DESTROY).listen((_) => null); | |
279 expect(root.apply).not.toThrow(); | |
280 d3.on(ScopeEvent.DESTROY).listen((_) => null); | |
281 expect(root.apply).not.toThrow(); | |
282 d2.on(ScopeEvent.DESTROY).listen((_) => null); | |
283 expect(root.apply).not.toThrow(); | |
284 })); | |
285 | |
286 it('should not too eagerly create own streams', inject((RootScope root) { | |
287 var a = root.createChild({}); | |
288 var a2 = root.createChild({}); | |
289 var b = a.createChild({}); | |
290 var c = b.createChild({}); | |
291 var d = c.createChild({}); | |
292 var e = d.createChild({}); | |
293 | |
294 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStrea
ms, | |
295 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams, | |
296 e.hasOwnStreams]; | |
297 | |
298 expect(getStreamState()).toEqual([false, false, false, false, false, fal
se, false]); | |
299 expect(root.apply).not.toThrow(); | |
300 | |
301 e.on(ScopeEvent.DESTROY).listen((_) => null); | |
302 expect(getStreamState()).toEqual([false, false, false, false, false, fal
se, true]); | |
303 expect(root.apply).not.toThrow(); | |
304 | |
305 d.on(ScopeEvent.DESTROY).listen((_) => null); | |
306 expect(getStreamState()).toEqual([false, false, false, false, false, tru
e, true]); | |
307 expect(root.apply).not.toThrow(); | |
308 | |
309 b.on(ScopeEvent.DESTROY).listen((_) => null); | |
310 expect(getStreamState()).toEqual([false, false, false, true, false, true
, true]); | |
311 expect(root.apply).not.toThrow(); | |
312 | |
313 c.on(ScopeEvent.DESTROY).listen((_) => null); | |
314 expect(getStreamState()).toEqual([false, false, false, true, true, true,
true]); | |
315 expect(root.apply).not.toThrow(); | |
316 | |
317 a.on(ScopeEvent.DESTROY).listen((_) => null); | |
318 expect(getStreamState()).toEqual([false, true, false, true, true, true,
true]); | |
319 expect(root.apply).not.toThrow(); | |
320 | |
321 a2.on(ScopeEvent.DESTROY).listen((_) => null); | |
322 expect(getStreamState()).toEqual([true, true, true, true, true, true, tr
ue]); | |
323 expect(root.apply).not.toThrow(); | |
324 })); | |
325 | |
326 | |
327 it('should not properly merge streams', inject((RootScope root) { | |
328 var a = root.createChild({}); | |
329 var a2 = root.createChild({}); | |
330 var b = a.createChild({}); | |
331 var c = b.createChild({}); | |
332 var d = c.createChild({}); | |
333 var e = d.createChild({}); | |
334 | |
335 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStrea
ms, | |
336 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams, | |
337 e.hasOwnStreams]; | |
338 | |
339 expect(getStreamState()).toEqual([false, false, false, false, false, fal
se, false]); | |
340 expect(root.apply).not.toThrow(); | |
341 | |
342 a2.on(ScopeEvent.DESTROY).listen((_) => null); | |
343 expect(getStreamState()).toEqual([false, false, true, false, false, fals
e, false]); | |
344 expect(root.apply).not.toThrow(); | |
345 | |
346 e.on(ScopeEvent.DESTROY).listen((_) => null); | |
347 expect(getStreamState()).toEqual([true, false, true, false, false, false
, true]); | |
348 expect(root.apply).not.toThrow(); | |
349 })); | |
350 | |
351 | |
352 it('should clean up on cancel', inject((RootScope root) { | |
353 var child = root.createChild(null); | |
354 var cl = child.on("E").listen((e) => null); | |
355 var rl = root.on("E").listen((e) => null); | |
356 rl.cancel(); | |
357 expect(root.apply).not.toThrow(); | |
358 })); | |
359 | |
360 | |
361 it('should find random bugs', inject((RootScope root) { | |
362 List scopes; | |
363 List listeners; | |
364 List steps; | |
365 var random = new Random(); | |
366 for (var i = 0; i < 1000; i++) { | |
367 if (i % 10 == 0) { | |
368 scopes = [root.createChild(null)]; | |
369 listeners = []; | |
370 steps = []; | |
371 } | 282 } |
372 switch(random.nextInt(4)) { | 283 |
373 case 0: | 284 child.on('abc').listen(eventFn); |
374 if (scopes.length > 10) break; | 285 expect(log).toEqual(''); |
375 var index = random.nextInt(scopes.length); | 286 |
376 Scope scope = scopes[index]; | 287 child.emit('abc'); |
377 var child = scope.createChild(null); | 288 expect(log).toEqual('X'); |
378 scopes.add(child); | 289 |
379 steps.add('scopes[$index].createChild(null)'); | 290 child.broadcast('abc'); |
380 break; | 291 expect(log).toEqual('XX'); |
381 case 1: | 292 }); |
382 var index = random.nextInt(scopes.length); | 293 |
383 Scope scope = scopes[index]; | 294 |
384 listeners.add(scope.on('E').listen((e) => null)); | 295 it(r'should return a function that deregisters the listener', (RootScope
rootScope) { |
385 steps.add('scopes[$index].on("E").listen((e)=>null)'); | 296 var log = ''; |
386 break; | 297 var child = rootScope.createChild(new PrototypeMap(rootScope.context))
; |
387 case 2: | 298 var subscription; |
388 if (scopes.length < 3) break; | 299 |
389 var index = random.nextInt(scopes.length - 1) + 1; | 300 eventFn(e) { |
390 Scope scope = scopes[index]; | 301 log += 'X'; |
391 scope.destroy(); | |
392 scopes = scopes.where((Scope s) => s.isAttached).toList(); | |
393 steps.add('scopes[$index].destroy()'); | |
394 break; | |
395 case 3: | |
396 if (listeners.length == 0) break; | |
397 var index = random.nextInt(listeners.length); | |
398 var l = listeners[index]; | |
399 l.cancel(); | |
400 listeners.remove(l); | |
401 steps.add('listeners[$index].cancel()'); | |
402 break; | |
403 } | 302 } |
404 try { | 303 |
405 root.apply(); | 304 subscription = child.on('abc').listen(eventFn); |
406 } catch (e) { | 305 expect(log).toEqual(''); |
407 expect('').toEqual(steps.join(';\n')); | 306 expect(subscription).toBeDefined(); |
| 307 |
| 308 child.emit(r'abc'); |
| 309 child.broadcast('abc'); |
| 310 expect(log).toEqual('XX'); |
| 311 |
| 312 log = ''; |
| 313 expect(subscription.cancel()).toBe(null); |
| 314 child.emit(r'abc'); |
| 315 child.broadcast('abc'); |
| 316 expect(log).toEqual(''); |
| 317 }); |
| 318 |
| 319 it('should not trigger assertions on scope fork', (RootScope root) { |
| 320 var d1 = root.createChild({}); |
| 321 var d2 = root.createChild({}); |
| 322 var d3 = d2.createChild({}); |
| 323 expect(root.apply).not.toThrow(); |
| 324 d1.on(ScopeEvent.DESTROY).listen((_) => null); |
| 325 expect(root.apply).not.toThrow(); |
| 326 d3.on(ScopeEvent.DESTROY).listen((_) => null); |
| 327 expect(root.apply).not.toThrow(); |
| 328 d2.on(ScopeEvent.DESTROY).listen((_) => null); |
| 329 expect(root.apply).not.toThrow(); |
| 330 }); |
| 331 |
| 332 it('should not too eagerly create own streams', (RootScope root) { |
| 333 var a = root.createChild({}); |
| 334 var a2 = root.createChild({}); |
| 335 var b = a.createChild({}); |
| 336 var c = b.createChild({}); |
| 337 var d = c.createChild({}); |
| 338 var e = d.createChild({}); |
| 339 |
| 340 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStr
eams, |
| 341 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams, |
| 342 e.hasOwnStreams]; |
| 343 |
| 344 expect(getStreamState()).toEqual([false, false, false, false, false, f
alse, false]); |
| 345 expect(root.apply).not.toThrow(); |
| 346 |
| 347 e.on(ScopeEvent.DESTROY).listen((_) => null); |
| 348 expect(getStreamState()).toEqual([false, false, false, false, false, f
alse, true]); |
| 349 expect(root.apply).not.toThrow(); |
| 350 |
| 351 d.on(ScopeEvent.DESTROY).listen((_) => null); |
| 352 expect(getStreamState()).toEqual([false, false, false, false, false, t
rue, true]); |
| 353 expect(root.apply).not.toThrow(); |
| 354 |
| 355 b.on(ScopeEvent.DESTROY).listen((_) => null); |
| 356 expect(getStreamState()).toEqual([false, false, false, true, false, tr
ue, true]); |
| 357 expect(root.apply).not.toThrow(); |
| 358 |
| 359 c.on(ScopeEvent.DESTROY).listen((_) => null); |
| 360 expect(getStreamState()).toEqual([false, false, false, true, true, tru
e, true]); |
| 361 expect(root.apply).not.toThrow(); |
| 362 |
| 363 a.on(ScopeEvent.DESTROY).listen((_) => null); |
| 364 expect(getStreamState()).toEqual([false, true, false, true, true, true
, true]); |
| 365 expect(root.apply).not.toThrow(); |
| 366 |
| 367 a2.on(ScopeEvent.DESTROY).listen((_) => null); |
| 368 expect(getStreamState()).toEqual([true, true, true, true, true, true,
true]); |
| 369 expect(root.apply).not.toThrow(); |
| 370 }); |
| 371 |
| 372 |
| 373 it('should not properly merge streams', (RootScope root) { |
| 374 var a = root.createChild({}); |
| 375 var a2 = root.createChild({}); |
| 376 var b = a.createChild({}); |
| 377 var c = b.createChild({}); |
| 378 var d = c.createChild({}); |
| 379 var e = d.createChild({}); |
| 380 |
| 381 getStreamState() => [root.hasOwnStreams, a.hasOwnStreams, a2.hasOwnStr
eams, |
| 382 b.hasOwnStreams, c.hasOwnStreams, d.hasOwnStreams, |
| 383 e.hasOwnStreams]; |
| 384 |
| 385 expect(getStreamState()).toEqual([false, false, false, false, false, f
alse, false]); |
| 386 expect(root.apply).not.toThrow(); |
| 387 |
| 388 a2.on(ScopeEvent.DESTROY).listen((_) => null); |
| 389 expect(getStreamState()).toEqual([false, false, true, false, false, fa
lse, false]); |
| 390 expect(root.apply).not.toThrow(); |
| 391 |
| 392 e.on(ScopeEvent.DESTROY).listen((_) => null); |
| 393 expect(getStreamState()).toEqual([true, false, true, false, false, fal
se, true]); |
| 394 expect(root.apply).not.toThrow(); |
| 395 }); |
| 396 |
| 397 |
| 398 it('should clean up on cancel', (RootScope root) { |
| 399 var child = root.createChild(null); |
| 400 var cl = child.on("E").listen((e) => null); |
| 401 var rl = root.on("E").listen((e) => null); |
| 402 rl.cancel(); |
| 403 expect(root.apply).not.toThrow(); |
| 404 }); |
| 405 |
| 406 |
| 407 it('should find random bugs', (RootScope root) { |
| 408 List scopes; |
| 409 List listeners; |
| 410 List steps; |
| 411 var random = new Random(); |
| 412 for (var i = 0; i < 1000; i++) { |
| 413 if (i % 10 == 0) { |
| 414 scopes = [root.createChild(null)]; |
| 415 listeners = []; |
| 416 steps = []; |
| 417 } |
| 418 switch(random.nextInt(4)) { |
| 419 case 0: |
| 420 if (scopes.length > 10) break; |
| 421 var index = random.nextInt(scopes.length); |
| 422 Scope scope = scopes[index]; |
| 423 var child = scope.createChild(null); |
| 424 scopes.add(child); |
| 425 steps.add('scopes[$index].createChild(null)'); |
| 426 break; |
| 427 case 1: |
| 428 var index = random.nextInt(scopes.length); |
| 429 Scope scope = scopes[index]; |
| 430 listeners.add(scope.on('E').listen((e) => null)); |
| 431 steps.add('scopes[$index].on("E").listen((e)=>null)'); |
| 432 break; |
| 433 case 2: |
| 434 if (scopes.length < 3) break; |
| 435 var index = random.nextInt(scopes.length - 1) + 1; |
| 436 Scope scope = scopes[index]; |
| 437 scope.destroy(); |
| 438 scopes = scopes.where((Scope s) => s.isAttached).toList(); |
| 439 steps.add('scopes[$index].destroy()'); |
| 440 break; |
| 441 case 3: |
| 442 if (listeners.length == 0) break; |
| 443 var index = random.nextInt(listeners.length); |
| 444 var l = listeners[index]; |
| 445 l.cancel(); |
| 446 listeners.remove(l); |
| 447 steps.add('listeners[$index].cancel()'); |
| 448 break; |
| 449 } |
| 450 try { |
| 451 root.apply(); |
| 452 } catch (e) { |
| 453 expect('').toEqual(steps.join(';\n')); |
| 454 } |
408 } | 455 } |
409 } | 456 }); |
410 })); | 457 }); |
411 }); | 458 |
412 | 459 |
413 | 460 describe('emit', () { |
414 describe('emit', () { | 461 var log, child, grandChild, greatGrandChild; |
415 var log, child, grandChild, greatGrandChild; | |
416 | |
417 logger(event) { | |
418 log.add(event.currentScope.context['id']); | |
419 } | |
420 | |
421 beforeEach(module(() { | |
422 return (RootScope rootScope) { | |
423 log = []; | |
424 child = rootScope.createChild({'id': 1}); | |
425 grandChild = child.createChild({'id': 2}); | |
426 greatGrandChild = grandChild.createChild({'id': 3}); | |
427 | |
428 rootScope.context['id'] = 0; | |
429 | |
430 rootScope.on('myEvent').listen(logger); | |
431 child.on('myEvent').listen(logger); | |
432 grandChild.on('myEvent').listen(logger); | |
433 greatGrandChild.on('myEvent').listen(logger); | |
434 }; | |
435 })); | |
436 | |
437 it(r'should bubble event up to the root scope', inject((RootScope rootScop
e) { | |
438 grandChild.emit(r'myEvent'); | |
439 expect(log.join('>')).toEqual('2>1>0'); | |
440 })); | |
441 | |
442 | |
443 it(r'should dispatch exceptions to the exceptionHandler', () { | |
444 module((Module module) { | |
445 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); | |
446 }); | |
447 inject((ExceptionHandler e) { | |
448 LoggingExceptionHandler exceptionHandler = e; | |
449 child.on('myEvent').listen((e) { throw 'bubbleException'; }); | |
450 grandChild.emit(r'myEvent'); | |
451 expect(log.join('>')).toEqual('2>1>0'); | |
452 expect(exceptionHandler.errors[0].error).toEqual('bubbleException'); | |
453 }); | |
454 }); | |
455 | |
456 | |
457 it(r'should allow stopping event propagation', inject((RootScope rootScope
) { | |
458 child.on('myEvent').listen((event) { event.stopPropagation(); }); | |
459 grandChild.emit(r'myEvent'); | |
460 expect(log.join('>')).toEqual('2>1'); | |
461 })); | |
462 | |
463 | |
464 it(r'should forward method arguments', inject((RootScope rootScope) { | |
465 var eventName; | |
466 var eventData; | |
467 child.on('abc').listen((event) { | |
468 eventName = event.name; | |
469 eventData = event.data; | |
470 }); | |
471 child.emit('abc', ['arg1', 'arg2']); | |
472 expect(eventName).toEqual('abc'); | |
473 expect(eventData).toEqual(['arg1', 'arg2']); | |
474 })); | |
475 | |
476 | |
477 describe(r'event object', () { | |
478 it(r'should have methods/properties', inject((RootScope rootScope) { | |
479 var event; | |
480 child.on('myEvent').listen((e) { | |
481 expect(e.targetScope).toBe(grandChild); | |
482 expect(e.currentScope).toBe(child); | |
483 expect(e.name).toBe('myEvent'); | |
484 event = e; | |
485 }); | |
486 grandChild.emit(r'myEvent'); | |
487 expect(event).toBeDefined(); | |
488 })); | |
489 | |
490 | |
491 it(r'should have preventDefault method and defaultPrevented property', i
nject((RootScope rootScope) { | |
492 var event = grandChild.emit(r'myEvent'); | |
493 expect(event.defaultPrevented).toBe(false); | |
494 | |
495 child.on('myEvent').listen((event) { | |
496 event.preventDefault(); | |
497 }); | |
498 event = grandChild.emit(r'myEvent'); | |
499 expect(event.defaultPrevented).toBe(true); | |
500 })); | |
501 }); | |
502 }); | |
503 | |
504 | |
505 describe('broadcast', () { | |
506 describe(r'event propagation', () { | |
507 var log, child1, child2, child3, grandChild11, grandChild21, grandChild2
2, grandChild23, | |
508 greatGrandChild211; | |
509 | 462 |
510 logger(event) { | 463 logger(event) { |
511 log.add(event.currentScope.context['id']); | 464 log.add(event.currentScope.context['id']); |
512 } | 465 } |
513 | 466 |
514 beforeEach(inject((RootScope rootScope) { | 467 beforeEachModule(() { |
515 log = []; | 468 return (RootScope rootScope) { |
516 child1 = rootScope.createChild({}); | 469 log = []; |
517 child2 = rootScope.createChild({}); | 470 child = rootScope.createChild({'id': 1}); |
518 child3 = rootScope.createChild({}); | 471 grandChild = child.createChild({'id': 2}); |
519 grandChild11 = child1.createChild({}); | 472 greatGrandChild = grandChild.createChild({'id': 3}); |
520 grandChild21 = child2.createChild({}); | 473 |
521 grandChild22 = child2.createChild({}); | 474 rootScope.context['id'] = 0; |
522 grandChild23 = child2.createChild({}); | 475 |
523 greatGrandChild211 = grandChild21.createChild({}); | 476 rootScope.on('myEvent').listen(logger); |
524 | 477 child.on('myEvent').listen(logger); |
525 rootScope.context['id'] = 0; | 478 grandChild.on('myEvent').listen(logger); |
526 child1.context['id'] = 1; | 479 greatGrandChild.on('myEvent').listen(logger); |
527 child2.context['id'] = 2; | 480 }; |
528 child3.context['id'] = 3; | 481 }); |
529 grandChild11.context['id'] = 11; | 482 |
530 grandChild21.context['id'] = 21; | 483 it(r'should bubble event up to the root scope', (RootScope rootScope) { |
531 grandChild22.context['id'] = 22; | 484 grandChild.emit(r'myEvent'); |
532 grandChild23.context['id'] = 23; | 485 expect(log.join('>')).toEqual('2>1>0'); |
533 greatGrandChild211.context['id'] = 211; | 486 }); |
534 | 487 |
535 rootScope.on('myEvent').listen(logger); | 488 |
536 child1.on('myEvent').listen(logger); | 489 describe('exceptions', () { |
537 child2.on('myEvent').listen(logger); | 490 beforeEachModule((Module module) { |
538 child3.on('myEvent').listen(logger); | 491 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler
); |
539 grandChild11.on('myEvent').listen(logger); | 492 }); |
540 grandChild21.on('myEvent').listen(logger); | 493 |
541 grandChild22.on('myEvent').listen(logger); | 494 |
542 grandChild23.on('myEvent').listen(logger); | 495 it(r'should dispatch exceptions to the exceptionHandler', (ExceptionHa
ndler e) { |
543 greatGrandChild211.on('myEvent').listen(logger); | 496 LoggingExceptionHandler exceptionHandler = e; |
544 | 497 child.on('myEvent').listen((e) { throw 'bubbleException'; }); |
545 // R | 498 grandChild.emit(r'myEvent'); |
546 // / | \ | 499 expect(log.join('>')).toEqual('2>1>0'); |
547 // 1 2 3 | 500 expect(exceptionHandler.errors[0].error).toEqual('bubbleException'); |
548 // / / | \ | 501 }); |
549 // 11 21 22 23 | 502 |
550 // | | 503 |
551 // 211 | 504 it('should throw "model unstable" error when observer is present', (Ro
otScope rootScope, VmTurnZone zone, ExceptionHandler e) { |
552 })); | 505 // Generates a different, equal, list on each evaluation. |
553 | 506 rootScope.context['list'] = new UnstableList(); |
554 | 507 |
555 it(r'should broadcast an event from the root scope', inject((RootScope r
ootScope) { | 508 rootScope.watch('list.list', (n, v) => null, canChangeModel: true); |
556 rootScope.broadcast('myEvent'); | 509 try { |
557 expect(log.join('>')).toEqual('0>1>11>2>21>211>22>23>3'); | 510 zone.run(() => null); |
558 })); | 511 } catch(_) {} |
559 | 512 |
560 | 513 var errors = (e as LoggingExceptionHandler).errors; |
561 it(r'should broadcast an event from a child scope', inject((RootScope ro
otScope) { | 514 expect(errors.length).toEqual(1); |
562 child2.broadcast('myEvent'); | 515 expect(errors.first.error, startsWith('Model did not stabilize')); |
563 expect(log.join('>')).toEqual('2>21>211>22>23'); | 516 }); |
564 })); | 517 }); |
565 | 518 |
566 | 519 it(r'should allow stopping event propagation', (RootScope rootScope) { |
567 it(r'should broadcast an event from a leaf scope with a sibling', inject
((RootScope rootScope) { | 520 child.on('myEvent').listen((event) { event.stopPropagation(); }); |
568 grandChild22.broadcast('myEvent'); | 521 grandChild.emit(r'myEvent'); |
569 expect(log.join('>')).toEqual('22'); | 522 expect(log.join('>')).toEqual('2>1'); |
570 })); | 523 }); |
571 | 524 |
572 | 525 |
573 it(r'should broadcast an event from a leaf scope without a sibling', inj
ect((RootScope rootScope) { | 526 it(r'should forward method arguments', (RootScope rootScope) { |
574 grandChild23.broadcast('myEvent'); | 527 var eventName; |
575 expect(log.join('>')).toEqual('23'); | 528 var eventData; |
576 })); | 529 child.on('abc').listen((event) { |
577 | 530 eventName = event.name; |
578 | 531 eventData = event.data; |
579 it(r'should not not fire any listeners for other events', inject((RootSc
ope rootScope) { | 532 }); |
580 rootScope.broadcast('fooEvent'); | 533 child.emit('abc', ['arg1', 'arg2']); |
581 expect(log.join('>')).toEqual(''); | 534 expect(eventName).toEqual('abc'); |
582 })); | 535 expect(eventData).toEqual(['arg1', 'arg2']); |
583 | 536 }); |
584 | 537 |
585 it(r'should return event object', inject((RootScope rootScope) { | 538 |
586 var result = child1.broadcast('some'); | 539 describe(r'event object', () { |
587 | 540 it(r'should have methods/properties', (RootScope rootScope) { |
588 expect(result).toBeDefined(); | 541 var event; |
589 expect(result.name).toBe('some'); | 542 child.on('myEvent').listen((e) { |
590 expect(result.targetScope).toBe(child1); | 543 expect(e.targetScope).toBe(grandChild); |
591 })); | 544 expect(e.currentScope).toBe(child); |
592 | 545 expect(e.name).toBe('myEvent'); |
593 | 546 event = e; |
594 it('should skip scopes which dont have given event', | 547 }); |
595 inject((RootScope rootScope, Logger log) { | 548 grandChild.emit(r'myEvent'); |
596 var child1 = rootScope.createChild('A'); | 549 expect(event).toBeDefined(); |
597 rootScope.createChild('A1'); | 550 }); |
598 rootScope.createChild('A2'); | 551 |
599 rootScope.createChild('A3'); | 552 |
600 var child2 = rootScope.createChild('B'); | 553 it(r'should have preventDefault method and defaultPrevented property',
(RootScope rootScope) { |
601 child2.on('event').listen((e) => log(e.data)); | 554 var event = grandChild.emit(r'myEvent'); |
602 rootScope.broadcast('event', 'OK'); | 555 expect(event.defaultPrevented).toBe(false); |
603 expect(log).toEqual(['OK']); | 556 |
604 })); | 557 child.on('myEvent').listen((event) { |
605 }); | 558 event.preventDefault(); |
606 | 559 }); |
607 | 560 event = grandChild.emit(r'myEvent'); |
608 describe(r'listener', () { | 561 expect(event.defaultPrevented).toBe(true); |
609 it(r'should receive event object', inject((RootScope rootScope) { | 562 }); |
610 var scope = rootScope, | 563 }); |
611 child = scope.createChild({}), | 564 }); |
612 event; | 565 |
613 | 566 |
614 child.on('fooEvent').listen((e) { | 567 describe('broadcast', () { |
615 event = e; | 568 describe(r'event propagation', () { |
616 }); | 569 var log, child1, child2, child3, grandChild11, grandChild21, grandChil
d22, grandChild23, |
617 scope.broadcast('fooEvent'); | 570 greatGrandChild211; |
618 | 571 |
619 expect(event.name).toBe('fooEvent'); | 572 logger(event) { |
620 expect(event.targetScope).toBe(scope); | 573 log.add(event.currentScope.context['id']); |
621 expect(event.currentScope).toBe(child); | 574 } |
622 })); | 575 |
623 | 576 beforeEach((RootScope rootScope) { |
624 it(r'should support passing messages as varargs', inject((RootScope root
Scope) { | 577 log = []; |
625 var scope = rootScope, | 578 child1 = rootScope.createChild({}); |
626 child = scope.createChild({}), | 579 child2 = rootScope.createChild({}); |
627 args; | 580 child3 = rootScope.createChild({}); |
628 | 581 grandChild11 = child1.createChild({}); |
629 child.on('fooEvent').listen((e) { | 582 grandChild21 = child2.createChild({}); |
630 args = e.data; | 583 grandChild22 = child2.createChild({}); |
631 }); | 584 grandChild23 = child2.createChild({}); |
632 scope.broadcast('fooEvent', ['do', 're', 'me', 'fa']); | 585 greatGrandChild211 = grandChild21.createChild({}); |
633 | 586 |
634 expect(args.length).toBe(4); | 587 rootScope.context['id'] = 0; |
635 expect(args).toEqual(['do', 're', 'me', 'fa']); | 588 child1.context['id'] = 1; |
636 })); | 589 child2.context['id'] = 2; |
| 590 child3.context['id'] = 3; |
| 591 grandChild11.context['id'] = 11; |
| 592 grandChild21.context['id'] = 21; |
| 593 grandChild22.context['id'] = 22; |
| 594 grandChild23.context['id'] = 23; |
| 595 greatGrandChild211.context['id'] = 211; |
| 596 |
| 597 rootScope.on('myEvent').listen(logger); |
| 598 child1.on('myEvent').listen(logger); |
| 599 child2.on('myEvent').listen(logger); |
| 600 child3.on('myEvent').listen(logger); |
| 601 grandChild11.on('myEvent').listen(logger); |
| 602 grandChild21.on('myEvent').listen(logger); |
| 603 grandChild22.on('myEvent').listen(logger); |
| 604 grandChild23.on('myEvent').listen(logger); |
| 605 greatGrandChild211.on('myEvent').listen(logger); |
| 606 |
| 607 // R |
| 608 // / | \ |
| 609 // 1 2 3 |
| 610 // / / | \ |
| 611 // 11 21 22 23 |
| 612 // | |
| 613 // 211 |
| 614 }); |
| 615 |
| 616 |
| 617 it(r'should broadcast an event from the root scope', (RootScope rootSc
ope) { |
| 618 rootScope.broadcast('myEvent'); |
| 619 expect(log.join('>')).toEqual('0>1>11>2>21>211>22>23>3'); |
| 620 }); |
| 621 |
| 622 |
| 623 it(r'should broadcast an event from a child scope', (RootScope rootSco
pe) { |
| 624 child2.broadcast('myEvent'); |
| 625 expect(log.join('>')).toEqual('2>21>211>22>23'); |
| 626 }); |
| 627 |
| 628 |
| 629 it(r'should broadcast an event from a leaf scope with a sibling', (Roo
tScope rootScope) { |
| 630 grandChild22.broadcast('myEvent'); |
| 631 expect(log.join('>')).toEqual('22'); |
| 632 }); |
| 633 |
| 634 |
| 635 it(r'should broadcast an event from a leaf scope without a sibling', (
RootScope rootScope) { |
| 636 grandChild23.broadcast('myEvent'); |
| 637 expect(log.join('>')).toEqual('23'); |
| 638 }); |
| 639 |
| 640 |
| 641 it(r'should not not fire any listeners for other events', (RootScope r
ootScope) { |
| 642 rootScope.broadcast('fooEvent'); |
| 643 expect(log.join('>')).toEqual(''); |
| 644 }); |
| 645 |
| 646 |
| 647 it(r'should return event object', (RootScope rootScope) { |
| 648 var result = child1.broadcast('some'); |
| 649 |
| 650 expect(result).toBeDefined(); |
| 651 expect(result.name).toBe('some'); |
| 652 expect(result.targetScope).toBe(child1); |
| 653 }); |
| 654 |
| 655 |
| 656 it('should skip scopes which dont have given event', |
| 657 inject((RootScope rootScope, Logger log) { |
| 658 var child1 = rootScope.createChild('A'); |
| 659 rootScope.createChild('A1'); |
| 660 rootScope.createChild('A2'); |
| 661 rootScope.createChild('A3'); |
| 662 var child2 = rootScope.createChild('B'); |
| 663 child2.on('event').listen((e) => log(e.data)); |
| 664 rootScope.broadcast('event', 'OK'); |
| 665 expect(log).toEqual(['OK']); |
| 666 })); |
| 667 }); |
| 668 |
| 669 |
| 670 describe(r'listener', () { |
| 671 it(r'should receive event object', (RootScope rootScope) { |
| 672 var scope = rootScope, |
| 673 child = scope.createChild({}), |
| 674 event; |
| 675 |
| 676 child.on('fooEvent').listen((e) { |
| 677 event = e; |
| 678 }); |
| 679 scope.broadcast('fooEvent'); |
| 680 |
| 681 expect(event.name).toBe('fooEvent'); |
| 682 expect(event.targetScope).toBe(scope); |
| 683 expect(event.currentScope).toBe(child); |
| 684 }); |
| 685 |
| 686 it(r'should support passing messages as varargs', (RootScope rootScope
) { |
| 687 var scope = rootScope, |
| 688 child = scope.createChild({}), |
| 689 args; |
| 690 |
| 691 child.on('fooEvent').listen((e) { |
| 692 args = e.data; |
| 693 }); |
| 694 scope.broadcast('fooEvent', ['do', 're', 'me', 'fa']); |
| 695 |
| 696 expect(args.length).toBe(4); |
| 697 expect(args).toEqual(['do', 're', 'me', 'fa']); |
| 698 }); |
| 699 |
| 700 it('should allow removing/adding listener during an event', (RootScope
rootScope, Logger log) { |
| 701 StreamSubscription subscription; |
| 702 subscription = rootScope.on('foo').listen((_) { |
| 703 subscription.cancel(); |
| 704 rootScope.on('foo').listen((_) => log(3)); |
| 705 log(2); |
| 706 }); |
| 707 expect(() { |
| 708 log(1); |
| 709 rootScope.broadcast('foo'); |
| 710 }).not.toThrow(); |
| 711 rootScope.broadcast('foo'); |
| 712 expect(log).toEqual([1, 2, 3]); |
| 713 }); |
| 714 }); |
637 }); | 715 }); |
638 }); | 716 }); |
639 }); | 717 |
640 | 718 |
641 | 719 describe(r'destroy', () { |
642 describe(r'destroy', () { | 720 var first = null, middle = null, last = null, log = null; |
643 var first = null, middle = null, last = null, log = null; | 721 |
644 | 722 beforeEach((RootScope rootScope) { |
645 beforeEach(inject((RootScope rootScope) { | 723 log = ''; |
646 log = ''; | 724 |
647 | 725 first = rootScope.createChild({"check": (n) { log+= '$n'; return n;}}); |
648 first = rootScope.createChild({"check": (n) { log+= '$n'; return n;}}); | 726 middle = rootScope.createChild({"check": (n) { log+= '$n'; return n;}}); |
649 middle = rootScope.createChild({"check": (n) { log+= '$n'; return n;}}); | 727 last = rootScope.createChild({"check": (n) { log+= '$n'; return n;}}); |
650 last = rootScope.createChild({"check": (n) { log+= '$n'; return n;}}); | 728 |
651 | 729 first.watch('check(1)', (v, l) {}); |
652 first.watch('check(1)', (v, l) {}); | 730 middle.watch('check(2)', (v, l) {}); |
653 middle.watch('check(2)', (v, l) {}); | 731 last.watch('check(3)', (v, l) {}); |
654 last.watch('check(3)', (v, l) {}); | 732 |
655 | 733 first.on(ScopeEvent.DESTROY).listen((e) { log += 'destroy:first;'; }); |
656 first.on(ScopeEvent.DESTROY).listen((e) { log += 'destroy:first;'; }); | 734 |
657 | 735 rootScope.digest(); |
658 rootScope.digest(); | 736 log = ''; |
659 log = ''; | 737 }); |
660 })); | 738 |
661 | 739 |
662 | 740 it(r'should ignore remove on root', (RootScope rootScope) { |
663 it(r'should ignore remove on root', inject((RootScope rootScope) { | 741 rootScope.destroy(); |
664 rootScope.destroy(); | 742 rootScope.digest(); |
665 rootScope.digest(); | 743 expect(log).toEqual('123'); |
666 expect(log).toEqual('123'); | 744 }); |
667 })); | 745 |
668 | 746 |
669 | 747 it(r'should remove first', (RootScope rootScope) { |
670 it(r'should remove first', inject((RootScope rootScope) { | 748 first.destroy(); |
671 first.destroy(); | 749 rootScope.digest(); |
672 rootScope.digest(); | 750 expect(log).toEqual('destroy:first;23'); |
673 expect(log).toEqual('destroy:first;23'); | 751 }); |
674 })); | 752 |
675 | 753 |
676 | 754 it(r'should remove middle', (RootScope rootScope) { |
677 it(r'should remove middle', inject((RootScope rootScope) { | 755 middle.destroy(); |
678 middle.destroy(); | 756 rootScope.digest(); |
679 rootScope.digest(); | 757 expect(log).toEqual('13'); |
680 expect(log).toEqual('13'); | 758 }); |
681 })); | 759 |
682 | 760 |
683 | 761 it(r'should remove last', (RootScope rootScope) { |
684 it(r'should remove last', inject((RootScope rootScope) { | 762 last.destroy(); |
685 last.destroy(); | 763 rootScope.digest(); |
686 rootScope.digest(); | 764 expect(log).toEqual('12'); |
687 expect(log).toEqual('12'); | 765 }); |
688 })); | 766 |
689 | 767 |
690 | 768 it(r'should broadcast the destroy event', (RootScope rootScope) { |
691 it(r'should broadcast the destroy event', inject((RootScope rootScope) { | |
692 var log = []; | |
693 first.on(ScopeEvent.DESTROY).listen((s) => log.add('first')); | |
694 var child = first.createChild({}); | |
695 child.on(ScopeEvent.DESTROY).listen((s) => log.add('first-child')); | |
696 | |
697 first.destroy(); | |
698 expect(log).toEqual(['first', 'first-child']); | |
699 })); | |
700 | |
701 | |
702 it('should not call reaction function on destroyed scope', inject((RootScope
rootScope, Logger log) { | |
703 rootScope.context['name'] = 'misko'; | |
704 var child = rootScope.createChild(rootScope.context); | |
705 rootScope.watch('name', (v, _) { | |
706 log('root $v'); | |
707 if (v == 'destroy') { | |
708 child.destroy(); | |
709 } | |
710 }); | |
711 rootScope.watch('name', (v, _) => log('root2 $v')); | |
712 child.watch('name', (v, _) => log('child $v')); | |
713 rootScope.apply(); | |
714 expect(log).toEqual(['root misko', 'root2 misko', 'child misko']); | |
715 log.clear(); | |
716 | |
717 rootScope.context['name'] = 'destroy'; | |
718 rootScope.apply(); | |
719 expect(log).toEqual(['root destroy', 'root2 destroy']); | |
720 })); | |
721 }); | |
722 | |
723 | |
724 describe('digest lifecycle', () { | |
725 it(r'should apply expression with full lifecycle', inject((RootScope rootSco
pe) { | |
726 var log = ''; | |
727 var child = rootScope.createChild({"parent": rootScope.context}); | |
728 rootScope.watch('a', (a, _) { log += '1'; }); | |
729 child.apply('parent.a = 0'); | |
730 expect(log).toEqual('1'); | |
731 })); | |
732 | |
733 | |
734 it(r'should catch exceptions', () { | |
735 module((Module module) => module.type(ExceptionHandler, implementedBy: Log
gingExceptionHandler)); | |
736 inject((RootScope rootScope, ExceptionHandler e) { | |
737 LoggingExceptionHandler exceptionHandler = e; | |
738 var log = []; | 769 var log = []; |
739 var child = rootScope.createChild({}); | 770 first.on(ScopeEvent.DESTROY).listen((s) => log.add('first')); |
740 rootScope.watch('a', (a, _) => log.add('1')); | 771 var child = first.createChild({}); |
741 rootScope.context['a'] = 0; | 772 child.on(ScopeEvent.DESTROY).listen((s) => log.add('first-child')); |
742 child.apply(() { throw 'MyError'; }); | 773 |
743 expect(log.join(',')).toEqual('1'); | 774 first.destroy(); |
744 expect(exceptionHandler.errors[0].error).toEqual('MyError'); | 775 expect(log).toEqual(['first', 'first-child']); |
745 exceptionHandler.errors.removeAt(0); | 776 }); |
746 exceptionHandler.assertEmpty(); | 777 |
| 778 |
| 779 it('should not call reaction function on destroyed scope', (RootScope root
Scope, Logger log) { |
| 780 rootScope.context['name'] = 'misko'; |
| 781 var child = rootScope.createChild(rootScope.context); |
| 782 rootScope.watch('name', (v, _) { |
| 783 log('root $v'); |
| 784 if (v == 'destroy') { |
| 785 child.destroy(); |
| 786 } |
| 787 }); |
| 788 rootScope.watch('name', (v, _) => log('root2 $v')); |
| 789 child.watch('name', (v, _) => log('child $v')); |
| 790 rootScope.apply(); |
| 791 expect(log).toEqual(['root misko', 'root2 misko', 'child misko']); |
| 792 log.clear(); |
| 793 |
| 794 rootScope.context['name'] = 'destroy'; |
| 795 rootScope.apply(); |
| 796 expect(log).toEqual(['root destroy', 'root2 destroy']); |
| 797 }); |
| 798 |
| 799 |
| 800 it('should not call reaction fn when destroyed', (RootScope scope) { |
| 801 var testScope = scope.createChild({}); |
| 802 bool called = false; |
| 803 testScope.watch('items', (_, __) { |
| 804 called = true; |
| 805 }); |
| 806 testScope.destroy(); |
| 807 scope.apply(); |
| 808 expect(called).toBeFalsy(); |
747 }); | 809 }); |
748 }); | 810 }); |
749 | 811 |
750 | 812 |
751 describe(r'exceptions', () { | 813 describe('digest lifecycle', () { |
752 var log; | 814 it(r'should apply expression with full lifecycle', (RootScope rootScope) { |
753 beforeEach(module((Module module) { | 815 var log = ''; |
754 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHand
ler); | 816 var child = rootScope.createChild({"parent": rootScope.context}); |
| 817 rootScope.watch('a', (a, _) { log += '1'; }); |
| 818 child.apply('parent.a = 0'); |
| 819 expect(log).toEqual('1'); |
| 820 }); |
| 821 |
| 822 describe(r'exceptions', () { |
| 823 var log; |
| 824 beforeEachModule((Module module) { |
| 825 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHa
ndler); |
| 826 }); |
| 827 |
| 828 beforeEach((RootScope rootScope) { |
| 829 rootScope.context['log'] = () { log += 'digest;'; return null; }; |
| 830 log = ''; |
| 831 rootScope.watch('log()', (v, o) => null); |
| 832 rootScope.digest(); |
| 833 log = ''; |
| 834 }); |
| 835 |
| 836 it(r'should catch exceptions', (RootScope rootScope, ExceptionHandler e)
{ |
| 837 LoggingExceptionHandler exceptionHandler = e; |
| 838 var log = []; |
| 839 var child = rootScope.createChild({}); |
| 840 rootScope.watch('a', (a, _) => log.add('1')); |
| 841 rootScope.context['a'] = 0; |
| 842 child.apply(() { throw 'MyError'; }); |
| 843 expect(log.join(',')).toEqual('1'); |
| 844 expect(exceptionHandler.errors[0].error).toEqual('MyError'); |
| 845 exceptionHandler.errors.removeAt(0); |
| 846 exceptionHandler.assertEmpty(); |
| 847 }); |
| 848 |
| 849 |
| 850 it(r'should execute and return value and update', inject( |
| 851 (RootScope rootScope, ExceptionHandler e) { |
| 852 LoggingExceptionHandler exceptionHandler = e; |
| 853 rootScope.context['name'] = 'abc'; |
| 854 expect(rootScope.apply((context) => context['name'])).toEqual('abc
'); |
| 855 expect(log).toEqual('digest;digest;'); |
| 856 exceptionHandler.assertEmpty(); |
| 857 })); |
| 858 |
| 859 |
| 860 it(r'should execute and return value and update', (RootScope rootScope)
{ |
| 861 rootScope.context['name'] = 'abc'; |
| 862 expect(rootScope.apply('name', {'name': 123})).toEqual(123); |
| 863 }); |
| 864 |
| 865 |
| 866 it(r'should catch exception and update', (RootScope rootScope, Exception
Handler e) { |
| 867 LoggingExceptionHandler exceptionHandler = e; |
| 868 var error = 'MyError'; |
| 869 rootScope.apply(() { throw error; }); |
| 870 expect(log).toEqual('digest;digest;'); |
| 871 expect(exceptionHandler.errors[0].error).toEqual(error); |
| 872 }); |
| 873 }); |
| 874 |
| 875 it(r'should properly reset phase on exception', (RootScope rootScope) { |
| 876 var error = 'MyError'; |
| 877 expect(() => rootScope.apply(() { throw error; })).toThrow(error); |
| 878 expect(() => rootScope.apply(() { throw error; })).toThrow(error); |
| 879 }); |
| 880 }); |
| 881 |
| 882 |
| 883 describe('flush lifecycle', () { |
| 884 it(r'should apply expression with full lifecycle', (RootScope rootScope) { |
| 885 var log = ''; |
| 886 var child = rootScope.createChild({"parent": rootScope.context}); |
| 887 rootScope.watch('a', (a, _) { log += '1'; }, canChangeModel: false); |
| 888 child.apply('parent.a = 0'); |
| 889 expect(log).toEqual('1'); |
| 890 }); |
| 891 |
| 892 |
| 893 it(r'should schedule domWrites and domReads', (RootScope rootScope) { |
| 894 var log = ''; |
| 895 var child = rootScope.createChild({"parent": rootScope.context}); |
| 896 rootScope.watch('a', (a, _) { log += '1'; }, canChangeModel: false); |
| 897 child.apply('parent.a = 0'); |
| 898 expect(log).toEqual('1'); |
| 899 }); |
| 900 |
| 901 describe(r'exceptions', () { |
| 902 var log; |
| 903 beforeEachModule((Module module) { |
| 904 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHa
ndler); |
| 905 }); |
| 906 beforeEach((RootScope rootScope) { |
| 907 rootScope.context['log'] = () { log += 'digest;'; return null; }; |
| 908 log = ''; |
| 909 rootScope.watch('log()', (v, o) => null, canChangeModel: false); |
| 910 rootScope.digest(); |
| 911 log = ''; |
| 912 }); |
| 913 |
| 914 it(r'should catch exceptions', (RootScope rootScope, ExceptionHandler e)
{ |
| 915 LoggingExceptionHandler exceptionHandler = e; |
| 916 var log = []; |
| 917 var child = rootScope.createChild({}); |
| 918 rootScope.watch('a', (a, _) => log.add('1'), canChangeModel: false); |
| 919 rootScope.context['a'] = 0; |
| 920 child.apply(() { throw 'MyError'; }); |
| 921 expect(log.join(',')).toEqual('1'); |
| 922 expect(exceptionHandler.errors[0].error).toEqual('MyError'); |
| 923 exceptionHandler.errors.removeAt(0); |
| 924 exceptionHandler.assertEmpty(); |
| 925 }); |
| 926 |
| 927 it(r'should execute and return value and update', inject( |
| 928 (RootScope rootScope, ExceptionHandler e) { |
| 929 LoggingExceptionHandler exceptionHandler = e; |
| 930 rootScope.context['name'] = 'abc'; |
| 931 expect(rootScope.apply((context) => context['name'])).toEqual('abc
'); |
| 932 expect(log).toEqual('digest;digest;'); |
| 933 exceptionHandler.assertEmpty(); |
| 934 })); |
| 935 |
| 936 it(r'should execute and return value and update', (RootScope rootScope)
{ |
| 937 rootScope.context['name'] = 'abc'; |
| 938 expect(rootScope.apply('name', {'name': 123})).toEqual(123); |
| 939 }); |
| 940 |
| 941 it(r'should catch exception and update', (RootScope rootScope, Exception
Handler e) { |
| 942 LoggingExceptionHandler exceptionHandler = e; |
| 943 var error = 'MyError'; |
| 944 rootScope.apply(() { throw error; }); |
| 945 expect(log).toEqual('digest;digest;'); |
| 946 expect(exceptionHandler.errors[0].error).toEqual(error); |
| 947 }); |
| 948 |
| 949 it(r'should throw assertion when model changes in flush', (RootScope roo
tScope, Logger log) { |
| 950 var retValue = 1; |
| 951 rootScope.context['logger'] = (name) { log(name); return retValue; }; |
| 952 |
| 953 rootScope.watch('logger("watch")', (n, v) => null); |
| 954 rootScope.watch('logger("flush")', (n, v) => null, |
| 955 canChangeModel: false); |
| 956 |
| 957 // clear watches |
| 958 rootScope.digest(); |
| 959 log.clear(); |
| 960 |
| 961 rootScope.flush(); |
| 962 expect(log).toEqual(['flush', /*assertion*/ 'watch', 'flush']); |
| 963 |
| 964 retValue = 2; |
| 965 expect(rootScope.flush). |
| 966 toThrow('Observer reaction functions should not change model. \n' |
| 967 'These watch changes were detected: logger("watch"): 2 <= 1\n' |
| 968 'These observe changes were detected: '); |
| 969 }); |
| 970 }); |
| 971 |
| 972 }); |
| 973 |
| 974 |
| 975 describe('ScopeLocals', () { |
| 976 it('should read from locals', (RootScope scope) { |
| 977 scope.context['a'] = 'XXX'; |
| 978 scope.context['c'] = 'C'; |
| 979 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'}); |
| 980 expect(scopeLocal['a']).toEqual('A'); |
| 981 expect(scopeLocal['b']).toEqual('B'); |
| 982 expect(scopeLocal['c']).toEqual('C'); |
| 983 }); |
| 984 |
| 985 it('should write to Scope', (RootScope scope) { |
| 986 scope.context['a'] = 'XXX'; |
| 987 scope.context['c'] = 'C'; |
| 988 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'}); |
| 989 |
| 990 scopeLocal['a'] = 'aW'; |
| 991 scopeLocal['b'] = 'bW'; |
| 992 scopeLocal['c'] = 'cW'; |
| 993 |
| 994 expect(scope.context['a']).toEqual('aW'); |
| 995 expect(scope.context['b']).toEqual('bW'); |
| 996 expect(scope.context['c']).toEqual('cW'); |
| 997 |
| 998 expect(scopeLocal['a']).toEqual('A'); |
| 999 expect(scopeLocal['b']).toEqual('B'); |
| 1000 expect(scopeLocal['c']).toEqual('cW'); |
| 1001 }); |
| 1002 }); |
| 1003 |
| 1004 |
| 1005 describe(r'watch/digest', () { |
| 1006 it(r'should watch and fire on simple property change', (RootScope rootScop
e) { |
| 1007 var log; |
| 1008 |
| 1009 rootScope.watch('name', (a, b) { |
| 1010 log = [a, b]; |
| 1011 }); |
| 1012 rootScope.digest(); |
| 1013 log = null; |
| 1014 |
| 1015 expect(log).toEqual(null); |
| 1016 rootScope.digest(); |
| 1017 expect(log).toEqual(null); |
| 1018 rootScope.context['name'] = 'misko'; |
| 1019 rootScope.digest(); |
| 1020 expect(log).toEqual(['misko', null]); |
| 1021 }); |
| 1022 |
| 1023 |
| 1024 it('should watch/observe on objects other then contex', (RootScope rootSco
pe) { |
| 1025 var log = ''; |
| 1026 var map = {'a': 'A', 'b': 'B'}; |
| 1027 rootScope.watch('a', (a, b) => log += a, context: map); |
| 1028 rootScope.watch('b', (a, b) => log += a, context: map); |
| 1029 rootScope.apply(); |
| 1030 expect(log).toEqual('AB'); |
| 1031 }); |
| 1032 |
| 1033 |
| 1034 it(r'should watch and fire on expression change', (RootScope rootScope) { |
| 1035 var log; |
| 1036 |
| 1037 rootScope.watch('name.first', (a, b) => log = [a, b]); |
| 1038 rootScope.digest(); |
| 1039 log = null; |
| 1040 |
| 1041 rootScope.context['name'] = {}; |
| 1042 expect(log).toEqual(null); |
| 1043 rootScope.digest(); |
| 1044 expect(log).toEqual(null); |
| 1045 rootScope.context['name']['first'] = 'misko'; |
| 1046 rootScope.digest(); |
| 1047 expect(log).toEqual(['misko', null]); |
| 1048 }); |
| 1049 |
| 1050 |
| 1051 describe('exceptions', () { |
| 1052 beforeEachModule((Module module) { |
| 1053 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); |
| 1054 }); |
| 1055 it(r'should delegate exceptions', (RootScope rootScope, ExceptionHandler
e) { |
| 1056 LoggingExceptionHandler exceptionHandler = e; |
| 1057 rootScope.watch('a', (n, o) {throw 'abc';}); |
| 1058 rootScope.context['a'] = 1; |
| 1059 rootScope.digest(); |
| 1060 expect(exceptionHandler.errors.length).toEqual(1); |
| 1061 expect(exceptionHandler.errors[0].error).toEqual('abc'); |
| 1062 }); |
| 1063 }); |
| 1064 |
| 1065 |
| 1066 |
| 1067 it(r'should fire watches in order of addition', (RootScope rootScope) { |
| 1068 // this is not an external guarantee, just our own sanity |
| 1069 var log = ''; |
| 1070 rootScope |
| 1071 ..watch('a', (a, b) { log += 'a'; }) |
| 1072 ..watch('b', (a, b) { log += 'b'; }) |
| 1073 ..watch('c', (a, b) { log += 'c'; }) |
| 1074 ..context['a'] = rootScope.context['b'] = rootScope.context['c'] = 1 |
| 1075 ..digest(); |
| 1076 expect(log).toEqual('abc'); |
| 1077 }); |
| 1078 |
| 1079 |
| 1080 it(r'should call child watchers in addition order', (RootScope rootScope)
{ |
| 1081 // this is not an external guarantee, just our own sanity |
| 1082 var log = ''; |
| 1083 var childA = rootScope.createChild({}); |
| 1084 var childB = rootScope.createChild({}); |
| 1085 var childC = rootScope.createChild({}); |
| 1086 childA.watch('a', (a, b) { log += 'a'; }); |
| 1087 childB.watch('b', (a, b) { log += 'b'; }); |
| 1088 childC.watch('c', (a, b) { log += 'c'; }); |
| 1089 childA.context['a'] = childB.context['b'] = childC.context['c'] = 1; |
| 1090 rootScope.digest(); |
| 1091 expect(log).toEqual('abc'); |
| 1092 }); |
| 1093 |
| 1094 |
| 1095 it(r'should run digest multiple times', inject( |
| 1096 (RootScope rootScope) { |
| 1097 // tests a traversal edge case which we originally missed |
| 1098 var log = []; |
| 1099 var childA = rootScope.createChild({'log': log}); |
| 1100 var childB = rootScope.createChild({'log': log}); |
| 1101 |
| 1102 rootScope.context['log'] = log; |
| 1103 |
| 1104 rootScope.watch("log.add('r')", (_, __) => null); |
| 1105 childA.watch("log.add('a')", (_, __) => null); |
| 1106 childB.watch("log.add('b')", (_, __) => null); |
| 1107 |
| 1108 // init |
| 1109 rootScope.digest(); |
| 1110 expect(log.join('')).toEqual('rabrab'); |
| 1111 })); |
| 1112 |
| 1113 |
| 1114 it(r'should repeat watch cycle while model changes are identified', (RootS
cope rootScope) { |
| 1115 var log = ''; |
| 1116 rootScope |
| 1117 ..watch('c', (v, b) {rootScope.context['d'] = v; log+='c'; }) |
| 1118 ..watch('b', (v, b) {rootScope.context['c'] = v; log+='b'; }) |
| 1119 ..watch('a', (v, b) {rootScope.context['b'] = v; log+='a'; }) |
| 1120 ..digest(); |
| 1121 log = ''; |
| 1122 rootScope.context['a'] = 1; |
| 1123 rootScope.digest(); |
| 1124 expect(rootScope.context['b']).toEqual(1); |
| 1125 expect(rootScope.context['c']).toEqual(1); |
| 1126 expect(rootScope.context['d']).toEqual(1); |
| 1127 expect(log).toEqual('abc'); |
| 1128 }); |
| 1129 |
| 1130 |
| 1131 it(r'should repeat watch cycle from the root element', (RootScope rootScop
e) { |
| 1132 var log = []; |
| 1133 rootScope.context['log'] = log; |
| 1134 var child = rootScope.createChild({'log':log}); |
| 1135 rootScope.watch("log.add('a')", (_, __) => null); |
| 1136 child.watch("log.add('b')", (_, __) => null); |
| 1137 rootScope.digest(); |
| 1138 expect(log.join('')).toEqual('abab'); |
| 1139 }); |
| 1140 |
| 1141 |
| 1142 it(r'should not fire upon watch registration on initial digest', (RootScop
e rootScope) { |
| 1143 var log = ''; |
| 1144 rootScope.context['a'] = 1; |
| 1145 rootScope.watch('a', (a, b) { log += 'a'; }); |
| 1146 rootScope.watch('b', (a, b) { log += 'b'; }); |
| 1147 rootScope.digest(); |
| 1148 log = ''; |
| 1149 rootScope.digest(); |
| 1150 expect(log).toEqual(''); |
| 1151 }); |
| 1152 |
| 1153 |
| 1154 it(r'should prevent digest recursion', (RootScope rootScope) { |
| 1155 var callCount = 0; |
| 1156 rootScope.watch('name', (a, b) { |
| 1157 expect(() { |
| 1158 rootScope.digest(); |
| 1159 }).toThrow(r'digest already in progress'); |
| 1160 callCount++; |
| 1161 }); |
| 1162 rootScope.context['name'] = 'a'; |
| 1163 rootScope.digest(); |
| 1164 expect(callCount).toEqual(1); |
| 1165 }); |
| 1166 |
| 1167 |
| 1168 it(r'should return a function that allows listeners to be unregistered', i
nject( |
| 1169 (RootScope rootScope) { |
| 1170 var listener = jasmine.createSpy('watch listener'); |
| 1171 var watch; |
| 1172 |
| 1173 watch = rootScope.watch('foo', listener); |
| 1174 rootScope.digest(); //init |
| 1175 expect(listener).toHaveBeenCalled(); |
| 1176 expect(watch).toBeDefined(); |
| 1177 |
| 1178 listener.reset(); |
| 1179 rootScope.context['foo'] = 'bar'; |
| 1180 rootScope.digest(); //trigger |
| 1181 expect(listener).toHaveBeenCalledOnce(); |
| 1182 |
| 1183 listener.reset(); |
| 1184 rootScope.context['foo'] = 'baz'; |
| 1185 watch.remove(); |
| 1186 rootScope.digest(); //trigger |
| 1187 expect(listener).not.toHaveBeenCalled(); |
755 })); | 1188 })); |
756 beforeEach(inject((RootScope rootScope) { | 1189 |
757 rootScope.context['log'] = () { log += 'digest;'; return null; }; | 1190 |
758 log = ''; | 1191 it(r'should be possible to remove every watch', |
759 rootScope.watch('log()', (v, o) => null); | 1192 (RootScope rootScope, FormatterMap formatters) { |
760 rootScope.digest(); | 1193 rootScope.context['foo'] = 'bar'; |
761 log = ''; | 1194 var watch1 = rootScope.watch('(foo|json)+"bar"', (v, p) => null, |
762 })); | 1195 formatters: formatters); |
763 | 1196 var watch2 = rootScope.watch('(foo|json)+"bar"', (v, p) => null, |
764 | 1197 formatters: formatters); |
765 it(r'should execute and return value and update', inject( | 1198 |
766 (RootScope rootScope, ExceptionHandler e) { | 1199 expect(() => watch1.remove()).not.toThrow(); |
767 LoggingExceptionHandler exceptionHandler = e; | 1200 expect(() => watch2.remove()).not.toThrow(); |
768 rootScope.context['name'] = 'abc'; | 1201 }); |
769 expect(rootScope.apply((context) => context['name'])).toEqual('abc'); | 1202 |
770 expect(log).toEqual('digest;digest;'); | 1203 |
771 exceptionHandler.assertEmpty(); | 1204 it(r'should not infinitely digest when current value is NaN', (RootScope r
ootScope) { |
772 })); | 1205 rootScope.context['nan'] = double.NAN; |
773 | 1206 rootScope.watch('nan', (_, __) => null); |
774 | 1207 |
775 it(r'should execute and return value and update', inject((RootScope rootSc
ope) { | |
776 rootScope.context['name'] = 'abc'; | |
777 expect(rootScope.apply('name', {'name': 123})).toEqual(123); | |
778 })); | |
779 | |
780 | |
781 it(r'should catch exception and update', inject((RootScope rootScope, Exce
ptionHandler e) { | |
782 LoggingExceptionHandler exceptionHandler = e; | |
783 var error = 'MyError'; | |
784 rootScope.apply(() { throw error; }); | |
785 expect(log).toEqual('digest;digest;'); | |
786 expect(exceptionHandler.errors[0].error).toEqual(error); | |
787 })); | |
788 }); | |
789 | |
790 it(r'should proprely reset phase on exception', inject((RootScope rootScope)
{ | |
791 var error = 'MyError'; | |
792 expect(() => rootScope.apply(() { throw error; })).toThrow(error); | |
793 expect(() => rootScope.apply(() { throw error; })).toThrow(error); | |
794 })); | |
795 }); | |
796 | |
797 | |
798 describe('flush lifecycle', () { | |
799 it(r'should apply expression with full lifecycle', inject((RootScope rootSco
pe) { | |
800 var log = ''; | |
801 var child = rootScope.createChild({"parent": rootScope.context}); | |
802 rootScope.watch('a', (a, _) { log += '1'; }, readOnly: true); | |
803 child.apply('parent.a = 0'); | |
804 expect(log).toEqual('1'); | |
805 })); | |
806 | |
807 | |
808 it(r'should schedule domWrites and domReads', inject((RootScope rootScope) { | |
809 var log = ''; | |
810 var child = rootScope.createChild({"parent": rootScope.context}); | |
811 rootScope.watch('a', (a, _) { log += '1'; }, readOnly: true); | |
812 child.apply('parent.a = 0'); | |
813 expect(log).toEqual('1'); | |
814 })); | |
815 | |
816 | |
817 it(r'should catch exceptions', () { | |
818 module((Module module) => module.type(ExceptionHandler, implementedBy: Log
gingExceptionHandler)); | |
819 inject((RootScope rootScope, ExceptionHandler e) { | |
820 LoggingExceptionHandler exceptionHandler = e; | |
821 var log = []; | |
822 var child = rootScope.createChild({}); | |
823 rootScope.watch('a', (a, _) => log.add('1'), readOnly: true); | |
824 rootScope.context['a'] = 0; | |
825 child.apply(() { throw 'MyError'; }); | |
826 expect(log.join(',')).toEqual('1'); | |
827 expect(exceptionHandler.errors[0].error).toEqual('MyError'); | |
828 exceptionHandler.errors.removeAt(0); | |
829 exceptionHandler.assertEmpty(); | |
830 }); | |
831 }); | |
832 | |
833 | |
834 describe(r'exceptions', () { | |
835 var log; | |
836 beforeEach(module((Module module) { | |
837 return module.type(ExceptionHandler, implementedBy: LoggingExceptionHand
ler); | |
838 })); | |
839 beforeEach(inject((RootScope rootScope) { | |
840 rootScope.context['log'] = () { log += 'digest;'; return null; }; | |
841 log = ''; | |
842 rootScope.watch('log()', (v, o) => null, readOnly: true); | |
843 rootScope.digest(); | |
844 log = ''; | |
845 })); | |
846 | |
847 | |
848 it(r'should execute and return value and update', inject( | |
849 (RootScope rootScope, ExceptionHandler e) { | |
850 LoggingExceptionHandler exceptionHandler = e; | |
851 rootScope.context['name'] = 'abc'; | |
852 expect(rootScope.apply((context) => context['name'])).toEqual('abc'); | |
853 expect(log).toEqual('digest;digest;'); | |
854 exceptionHandler.assertEmpty(); | |
855 })); | |
856 | |
857 it(r'should execute and return value and update', inject((RootScope rootSc
ope) { | |
858 rootScope.context['name'] = 'abc'; | |
859 expect(rootScope.apply('name', {'name': 123})).toEqual(123); | |
860 })); | |
861 | |
862 it(r'should catch exception and update', inject((RootScope rootScope, Exce
ptionHandler e) { | |
863 LoggingExceptionHandler exceptionHandler = e; | |
864 var error = 'MyError'; | |
865 rootScope.apply(() { throw error; }); | |
866 expect(log).toEqual('digest;digest;'); | |
867 expect(exceptionHandler.errors[0].error).toEqual(error); | |
868 })); | |
869 | |
870 it(r'should throw assertion when model changes in flush', inject((RootScop
e rootScope, Logger log) { | |
871 var retValue = 1; | |
872 rootScope.context['logger'] = (name) { log(name); return retValue; }; | |
873 | |
874 rootScope.watch('logger("watch")', (n, v) => null); | |
875 rootScope.watch('logger("flush")', (n, v) => null, readOnly: true); | |
876 | |
877 // clear watches | |
878 rootScope.digest(); | |
879 log.clear(); | |
880 | |
881 rootScope.flush(); | |
882 expect(log).toEqual(['flush', /*assertion*/ 'watch', 'flush']); | |
883 | |
884 retValue = 2; | |
885 expect(rootScope.flush). | |
886 toThrow('Observer reaction functions should not change model. \n' | |
887 'These watch changes were detected: logger("watch"): 2 <= 1\n' | |
888 'These observe changes were detected: '); | |
889 })); | |
890 }); | |
891 | |
892 }); | |
893 | |
894 | |
895 describe('ScopeLocals', () { | |
896 it('should read from locals', inject((RootScope scope) { | |
897 scope.context['a'] = 'XXX'; | |
898 scope.context['c'] = 'C'; | |
899 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'}); | |
900 expect(scopeLocal['a']).toEqual('A'); | |
901 expect(scopeLocal['b']).toEqual('B'); | |
902 expect(scopeLocal['c']).toEqual('C'); | |
903 })); | |
904 | |
905 it('should write to Scope', inject((RootScope scope) { | |
906 scope.context['a'] = 'XXX'; | |
907 scope.context['c'] = 'C'; | |
908 var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'}); | |
909 | |
910 scopeLocal['a'] = 'aW'; | |
911 scopeLocal['b'] = 'bW'; | |
912 scopeLocal['c'] = 'cW'; | |
913 | |
914 expect(scope.context['a']).toEqual('aW'); | |
915 expect(scope.context['b']).toEqual('bW'); | |
916 expect(scope.context['c']).toEqual('cW'); | |
917 | |
918 expect(scopeLocal['a']).toEqual('A'); | |
919 expect(scopeLocal['b']).toEqual('B'); | |
920 expect(scopeLocal['c']).toEqual('cW'); | |
921 })); | |
922 }); | |
923 | |
924 | |
925 describe(r'watch/digest', () { | |
926 it(r'should watch and fire on simple property change', inject((RootScope roo
tScope) { | |
927 var log; | |
928 | |
929 rootScope.watch('name', (a, b) { | |
930 log = [a, b]; | |
931 }); | |
932 rootScope.digest(); | |
933 log = null; | |
934 | |
935 expect(log).toEqual(null); | |
936 rootScope.digest(); | |
937 expect(log).toEqual(null); | |
938 rootScope.context['name'] = 'misko'; | |
939 rootScope.digest(); | |
940 expect(log).toEqual(['misko', null]); | |
941 })); | |
942 | |
943 | |
944 it('should watch/observe on objects other then contex', inject((RootScope ro
otScope) { | |
945 var log = ''; | |
946 var map = {'a': 'A', 'b': 'B'}; | |
947 rootScope.watch('a', (a, b) => log += a, context: map); | |
948 rootScope.watch('b', (a, b) => log += a, context: map); | |
949 rootScope.apply(); | |
950 expect(log).toEqual('AB'); | |
951 })); | |
952 | |
953 | |
954 it(r'should watch and fire on expression change', inject((RootScope rootScop
e) { | |
955 var log; | |
956 | |
957 rootScope.watch('name.first', (a, b) => log = [a, b]); | |
958 rootScope.digest(); | |
959 log = null; | |
960 | |
961 rootScope.context['name'] = {}; | |
962 expect(log).toEqual(null); | |
963 rootScope.digest(); | |
964 expect(log).toEqual(null); | |
965 rootScope.context['name']['first'] = 'misko'; | |
966 rootScope.digest(); | |
967 expect(log).toEqual(['misko', null]); | |
968 })); | |
969 | |
970 | |
971 it(r'should delegate exceptions', () { | |
972 module((Module module) { | |
973 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); | |
974 }); | |
975 inject((RootScope rootScope, ExceptionHandler e) { | |
976 LoggingExceptionHandler exceptionHandler = e; | |
977 rootScope.watch('a', (n, o) {throw 'abc';}); | |
978 rootScope.context['a'] = 1; | |
979 rootScope.digest(); | |
980 expect(exceptionHandler.errors.length).toEqual(1); | |
981 expect(exceptionHandler.errors[0].error).toEqual('abc'); | |
982 }); | |
983 }); | |
984 | |
985 | |
986 it(r'should fire watches in order of addition', inject((RootScope rootScope)
{ | |
987 // this is not an external guarantee, just our own sanity | |
988 var log = ''; | |
989 rootScope.watch('a', (a, b) { log += 'a'; }); | |
990 rootScope.watch('b', (a, b) { log += 'b'; }); | |
991 rootScope.watch('c', (a, b) { log += 'c'; }); | |
992 rootScope.context['a'] = rootScope.context['b'] = rootScope.context['c'] =
1; | |
993 rootScope.digest(); | |
994 expect(log).toEqual('abc'); | |
995 })); | |
996 | |
997 | |
998 it(r'should call child watchers in addition order', inject((RootScope rootSc
ope) { | |
999 // this is not an external guarantee, just our own sanity | |
1000 var log = ''; | |
1001 var childA = rootScope.createChild({}); | |
1002 var childB = rootScope.createChild({}); | |
1003 var childC = rootScope.createChild({}); | |
1004 childA.watch('a', (a, b) { log += 'a'; }); | |
1005 childB.watch('b', (a, b) { log += 'b'; }); | |
1006 childC.watch('c', (a, b) { log += 'c'; }); | |
1007 childA.context['a'] = childB.context['b'] = childC.context['c'] = 1; | |
1008 rootScope.digest(); | |
1009 expect(log).toEqual('abc'); | |
1010 })); | |
1011 | |
1012 | |
1013 it(r'should run digest multiple times', inject( | |
1014 (RootScope rootScope) { | |
1015 // tests a traversal edge case which we originally missed | |
1016 var log = []; | |
1017 var childA = rootScope.createChild({'log': log}); | |
1018 var childB = rootScope.createChild({'log': log}); | |
1019 | |
1020 rootScope.context['log'] = log; | |
1021 | |
1022 rootScope.watch("log.add('r')", (_, __) => null); | |
1023 childA.watch("log.add('a')", (_, __) => null); | |
1024 childB.watch("log.add('b')", (_, __) => null); | |
1025 | |
1026 // init | |
1027 rootScope.digest(); | |
1028 expect(log.join('')).toEqual('rabrab'); | |
1029 })); | |
1030 | |
1031 | |
1032 it(r'should repeat watch cycle while model changes are identified', inject((
RootScope rootScope) { | |
1033 var log = ''; | |
1034 rootScope.watch('c', (v, b) {rootScope.context['d'] = v; log+='c'; }); | |
1035 rootScope.watch('b', (v, b) {rootScope.context['c'] = v; log+='b'; }); | |
1036 rootScope.watch('a', (v, b) {rootScope.context['b'] = v; log+='a'; }); | |
1037 rootScope.digest(); | |
1038 log = ''; | |
1039 rootScope.context['a'] = 1; | |
1040 rootScope.digest(); | |
1041 expect(rootScope.context['b']).toEqual(1); | |
1042 expect(rootScope.context['c']).toEqual(1); | |
1043 expect(rootScope.context['d']).toEqual(1); | |
1044 expect(log).toEqual('abc'); | |
1045 })); | |
1046 | |
1047 | |
1048 it(r'should repeat watch cycle from the root element', inject((RootScope roo
tScope) { | |
1049 var log = []; | |
1050 rootScope.context['log'] = log; | |
1051 var child = rootScope.createChild({'log':log}); | |
1052 rootScope.watch("log.add('a')", (_, __) => null); | |
1053 child.watch("log.add('b')", (_, __) => null); | |
1054 rootScope.digest(); | |
1055 expect(log.join('')).toEqual('abab'); | |
1056 })); | |
1057 | |
1058 | |
1059 it(r'should not fire upon watch registration on initial digest', inject((Roo
tScope rootScope) { | |
1060 var log = ''; | |
1061 rootScope.context['a'] = 1; | |
1062 rootScope.watch('a', (a, b) { log += 'a'; }); | |
1063 rootScope.watch('b', (a, b) { log += 'b'; }); | |
1064 rootScope.digest(); | |
1065 log = ''; | |
1066 rootScope.digest(); | |
1067 expect(log).toEqual(''); | |
1068 })); | |
1069 | |
1070 | |
1071 it(r'should prevent digest recursion', inject((RootScope rootScope) { | |
1072 var callCount = 0; | |
1073 rootScope.watch('name', (a, b) { | |
1074 expect(() { | 1208 expect(() { |
1075 rootScope.digest(); | 1209 rootScope.digest(); |
1076 }).toThrow(r'digest already in progress'); | 1210 }).not.toThrow(); |
1077 callCount++; | 1211 }); |
1078 }); | 1212 |
1079 rootScope.context['name'] = 'a'; | 1213 |
1080 rootScope.digest(); | 1214 it(r'should prevent infinite digest and should log firing expressions', (R
ootScope rootScope) { |
1081 expect(callCount).toEqual(1); | 1215 rootScope.context['a'] = 0; |
1082 })); | 1216 rootScope.context['b'] = 0; |
1083 | 1217 rootScope.watch('a', (a, __) => rootScope.context['a'] = a + 1); |
1084 | 1218 rootScope.watch('b', (b, __) => rootScope.context['b'] = b + 1); |
1085 it(r'should return a function that allows listeners to be unregistered', inj
ect( | 1219 |
1086 (RootScope rootScope) { | 1220 expect(() { |
1087 var listener = jasmine.createSpy('watch listener'); | 1221 rootScope.digest(); |
1088 var watch; | 1222 }).toThrow('Model did not stabilize in 5 digests. ' |
1089 | 1223 'Last 3 iterations:\n' |
1090 watch = rootScope.watch('foo', listener); | 1224 'a: 2 <= 1, b: 2 <= 1\n' |
1091 rootScope.digest(); //init | 1225 'a: 3 <= 2, b: 3 <= 2\n' |
1092 expect(listener).toHaveBeenCalled(); | 1226 'a: 4 <= 3, b: 4 <= 3'); |
1093 expect(watch).toBeDefined(); | 1227 }); |
1094 | 1228 |
1095 listener.reset(); | 1229 |
1096 rootScope.context['foo'] = 'bar'; | 1230 it(r'should always call the watchr with newVal and oldVal equal on the fir
st run', |
1097 rootScope.digest(); //triger | 1231 inject((RootScope rootScope) { |
1098 expect(listener).toHaveBeenCalledOnce(); | 1232 var log = []; |
1099 | 1233 var logger = (newVal, oldVal) { |
1100 listener.reset(); | 1234 var val = (newVal == oldVal || (newVal != oldVal && oldVal != newVal))
? newVal : 'xxx'; |
1101 rootScope.context['foo'] = 'baz'; | 1235 log.add(val); |
1102 watch.remove(); | 1236 }; |
1103 rootScope.digest(); //trigger | 1237 |
1104 expect(listener).not.toHaveBeenCalled(); | 1238 rootScope |
1105 })); | 1239 ..context['nanValue'] = double.NAN |
1106 | 1240 ..context['nullValue'] = null |
1107 | 1241 ..context['emptyString'] = '' |
1108 it(r'should not infinitely digest when current value is NaN', inject((RootSc
ope rootScope) { | 1242 ..context['falseValue'] = false |
1109 rootScope.context['nan'] = double.NAN; | 1243 ..context['numberValue'] = 23 |
1110 rootScope.watch('nan', (_, __) => null); | 1244 ..watch('nanValue', logger) |
1111 | 1245 ..watch('nullValue', logger) |
1112 expect(() { | 1246 ..watch('emptyString', logger) |
1113 rootScope.digest(); | 1247 ..watch('falseValue', logger) |
1114 }).not.toThrow(); | 1248 ..watch('numberValue', logger) |
1115 })); | 1249 ..digest(); |
1116 | 1250 |
1117 | 1251 expect(log.removeAt(0).isNaN).toEqual(true); //jasmine's toBe and toEqua
l don't work well with NaNs |
1118 it(r'should prevent infinite digest and should log firing expressions', inje
ct((RootScope rootScope) { | 1252 expect(log).toEqual([null, '', false, 23]); |
1119 rootScope.context['a'] = 0; | 1253 log = []; |
1120 rootScope.context['b'] = 0; | 1254 rootScope.digest(); |
1121 rootScope.watch('a', (a, __) => rootScope.context['a'] = a + 1); | 1255 expect(log).toEqual([]); |
1122 rootScope.watch('b', (b, __) => rootScope.context['b'] = b + 1); | 1256 })); |
1123 | 1257 |
1124 expect(() { | 1258 |
1125 rootScope.digest(); | 1259 it('should properly watch constants', (RootScope rootScope, Logger log) { |
1126 }).toThrow('Model did not stabilize in 5 digests. ' | 1260 rootScope.watch('[1, 2]', (v, o) => log([v, o])); |
1127 'Last 3 iterations:\n' | 1261 expect(log).toEqual([]); |
1128 'a: 2 <= 1, b: 2 <= 1\n' | 1262 rootScope.apply(); |
1129 'a: 3 <= 2, b: 3 <= 2\n' | 1263 expect(log).toEqual([[[1, 2], null]]); |
1130 'a: 4 <= 3, b: 4 <= 3'); | 1264 }); |
1131 })); | 1265 |
1132 | 1266 |
1133 | 1267 it('should properly watch array of fields 1', (RootScope rootScope, Logger
log) { |
1134 it(r'should always call the watchr with newVal and oldVal equal on the first
run', | 1268 rootScope.context['foo'] = 12; |
1135 inject((RootScope rootScope) { | 1269 rootScope.context['bar'] = 34; |
1136 var log = []; | 1270 rootScope.watch('[foo, bar]', (v, o) => log([v, o])); |
1137 var logger = (newVal, oldVal) { | 1271 expect(log).toEqual([]); |
1138 var val = (newVal == oldVal || (newVal != oldVal && oldVal != newVal)) ?
newVal : 'xxx'; | 1272 rootScope.apply(); |
1139 log.add(val); | 1273 expect(log).toEqual([[[12, 34], null]]); |
1140 }; | 1274 log.clear(); |
1141 | 1275 |
1142 rootScope.context['nanValue'] = double.NAN; | 1276 rootScope.context['foo'] = 56; |
1143 rootScope.context['nullValue'] = null; | 1277 rootScope.context['bar'] = 78; |
1144 rootScope.context['emptyString'] = ''; | 1278 rootScope.apply(); |
1145 rootScope.context['falseValue'] = false; | 1279 expect(log).toEqual([[[56, 78], [12, 34]]]); |
1146 rootScope.context['numberValue'] = 23; | 1280 }); |
1147 | 1281 |
1148 rootScope.watch('nanValue', logger); | 1282 |
1149 rootScope.watch('nullValue', logger); | 1283 it('should properly watch array of fields 2', (RootScope rootScope, Logger
log) { |
1150 rootScope.watch('emptyString', logger); | 1284 rootScope.context['foo'] = () => 12; |
1151 rootScope.watch('falseValue', logger); | 1285 rootScope.watch('foo()', (v, o) => log(v)); |
1152 rootScope.watch('numberValue', logger); | 1286 expect(log).toEqual([]); |
1153 | 1287 rootScope.apply(); |
1154 rootScope.digest(); | 1288 expect(log).toEqual([12]); |
1155 expect(log.removeAt(0).isNaN).toEqual(true); //jasmine's toBe and toEqual
don't work well with NaNs | 1289 }); |
1156 expect(log).toEqual([null, '', false, 23]); | 1290 |
1157 log = []; | 1291 |
1158 rootScope.digest(); | 1292 it('should properly watch array of fields 3', (RootScope rootScope, Logger
log) { |
1159 expect(log).toEqual([]); | 1293 rootScope.context['foo'] = 'abc'; |
1160 })); | 1294 rootScope.watch('foo.contains("b")', (v, o) => log([v, o])); |
1161 | 1295 expect(log).toEqual([]); |
1162 | 1296 rootScope.apply(); |
1163 it('should properly watch canstants', inject((RootScope rootScope, Logger lo
g) { | 1297 expect(log).toEqual([[true, null]]); |
1164 rootScope.watch('[1, 2]', (v, o) => log([v, o])); | 1298 log.clear(); |
1165 expect(log).toEqual([]); | 1299 }); |
1166 rootScope.apply(); | 1300 |
1167 expect(log).toEqual([[[1, 2], null]]); | 1301 |
1168 })); | 1302 it('should not trigger new watcher in the flush where it was added', (Scop
e scope) { |
1169 | 1303 var log = [] ; |
1170 | 1304 scope.context['foo'] = () => 'foo'; |
1171 it('should properly watch array of fields', inject((RootScope rootScope, Log
ger log) { | 1305 scope.context['name'] = 'misko'; |
1172 rootScope.context['foo'] = 12; | 1306 scope.context['list'] = [2, 3]; |
1173 rootScope.context['bar'] = 34; | 1307 scope.context['map'] = {'bar': 'chocolate'}; |
1174 rootScope.watch('[foo, bar]', (v, o) => log([v, o])); | 1308 scope.watch('1', (value, __) { |
1175 expect(log).toEqual([]); | 1309 expect(value).toEqual(1); |
1176 rootScope.apply(); | 1310 scope.watch('foo()', (value, __) => log.add(value)); |
1177 expect(log).toEqual([[[12, 34], null]]); | 1311 scope.watch('name', (value, __) => log.add(value)); |
1178 log.clear(); | 1312 scope.watch('(foo() + "-" + name).toUpperCase()', (value, __) => log.a
dd(value)); |
1179 | 1313 scope.watch('list', (value, __) => log.add(value)); |
1180 rootScope.context['foo'] = 56; | 1314 scope.watch('map', (value, __) => log.add(value)); |
1181 rootScope.context['bar'] = 78; | 1315 }); |
1182 rootScope.apply(); | 1316 scope.apply(); |
1183 expect(log).toEqual([[[56, 78], [12, 34]]]); | 1317 expect(log).toEqual(['foo', 'misko', 'FOO-MISKO', [2, 3], {'bar': 'choco
late'}]); |
1184 })); | 1318 }); |
1185 | 1319 |
1186 | 1320 |
1187 it('should properly watch array of fields2', inject((RootScope rootScope, Lo
gger log) { | 1321 it('should allow multiple nested watches', (RootScope scope) { |
1188 rootScope.watch('[ctrl.foo, ctrl.bar]', (v, o) => log([v, o])); | 1322 scope.watch('1', (_, __) { |
1189 expect(log).toEqual([]); | 1323 scope.watch('1', (_, __) { |
1190 rootScope.apply(); | 1324 scope.watch('1', (_, __) { |
1191 expect(log).toEqual([[[null, null], null]]); | 1325 scope.watch('1', (_, __) { |
1192 log.clear(); | 1326 scope.watch('1', (_, __) { |
1193 | 1327 scope.watch('1', (_, __) { |
1194 rootScope.context['ctrl'] = {'foo': 56, 'bar': 78}; | 1328 scope.watch('1', (_, __) { |
1195 rootScope.apply(); | 1329 scope.watch('1', (_, __) { |
1196 expect(log).toEqual([[[56, 78], [null, null]]]); | 1330 scope.watch('1', (_, __) { |
1197 })); | 1331 scope.watch('1', (_, __) { |
1198 }); | 1332 scope.watch('1', (_, __) { |
1199 | 1333 scope.watch('1', (_, __) { |
1200 | 1334 scope.watch('1', (_, __) { |
1201 describe('special binding modes', () { | 1335 scope.watch('1', (_, __) { |
1202 it('should bind one time', inject((RootScope rootScope, Logger log) { | 1336 scope.watch('1', (_, __) { |
1203 rootScope.watch('foo', (v, _) => log('foo:$v')); | 1337 scope.watch('1', (_, __) { |
1204 rootScope.watch(':foo', (v, _) => log(':foo:$v')); | 1338 // make this deeper then ScopeTTL; |
1205 rootScope.watch('::foo', (v, _) => log('::foo:$v')); | 1339 }); |
1206 | 1340 }); |
1207 rootScope.apply(); | 1341 }); |
1208 expect(log).toEqual(['foo:null']); | 1342 }); |
1209 log.clear(); | 1343 }); |
1210 | 1344 }); |
1211 rootScope.context['foo'] = true; | 1345 }); |
1212 rootScope.apply(); | 1346 }); |
1213 expect(log).toEqual(['foo:true', ':foo:true', '::foo:true']); | 1347 }); |
1214 log.clear(); | 1348 }); |
1215 | 1349 }); |
1216 rootScope.context['foo'] = 123; | 1350 }); |
1217 rootScope.apply(); | 1351 }); |
1218 expect(log).toEqual(['foo:123', ':foo:123']); | 1352 }); |
1219 log.clear(); | 1353 }); |
1220 | 1354 }); |
1221 rootScope.context['foo'] = null; | 1355 expect(scope.apply).not.toThrow(); |
1222 rootScope.apply(); | 1356 }); |
1223 expect(log).toEqual(['foo:null']); | 1357 |
1224 log.clear(); | 1358 |
1225 })); | 1359 it('should properly watch array of fields 4', (RootScope rootScope, Logger
log) { |
1226 }); | 1360 rootScope.watch('[ctrl.foo, ctrl.bar]', (v, o) => log([v, o])); |
1227 | 1361 expect(log).toEqual([]); |
1228 | 1362 rootScope.apply(); |
1229 describe('runAsync', () { | 1363 expect(log).toEqual([[[null, null], null]]); |
1230 it(r'should run callback before watch', inject((RootScope rootScope) { | 1364 log.clear(); |
1231 var log = ''; | 1365 |
1232 rootScope.runAsync(() { log += 'parent.async;'; }); | 1366 rootScope.context['ctrl'] = {'foo': 56, 'bar': 78}; |
1233 rootScope.watch('value', (_, __) { log += 'parent.digest;'; }); | 1367 rootScope.apply(); |
1234 rootScope.digest(); | 1368 expect(log).toEqual([[[56, 78], [null, null]]]); |
1235 expect(log).toEqual('parent.async;parent.digest;'); | 1369 }); |
1236 })); | 1370 }); |
1237 | 1371 |
1238 it(r'should cause a digest rerun', inject((RootScope rootScope) { | 1372 |
1239 rootScope.context['log'] = ''; | 1373 describe('special binding modes', () { |
1240 rootScope.context['value'] = 0; | 1374 it('should bind one time', (RootScope rootScope, Logger log) { |
1241 // NOTE(deboer): watch listener string functions not yet supported | 1375 rootScope.watch('foo', (v, _) => log('foo:$v')); |
1242 //rootScope.watch('value', 'log = log + ".";'); | 1376 rootScope.watch(':foo', (v, _) => log(':foo:$v')); |
1243 rootScope.watch('value', (_, __) { rootScope.context['log'] += "."; }); | 1377 rootScope.watch('::foo', (v, _) => log('::foo:$v')); |
1244 rootScope.watch('init', (_, __) { | 1378 |
1245 rootScope.runAsync(() => rootScope.eval('value = 123; log = log + "=" ')
); | 1379 rootScope.apply(); |
1246 expect(rootScope.context['value']).toEqual(0); | 1380 expect(log).toEqual(['foo:null']); |
1247 }); | 1381 log.clear(); |
1248 rootScope.digest(); | 1382 |
1249 expect(rootScope.context['log']).toEqual('.=.'); | 1383 rootScope.context['foo'] = true; |
1250 })); | 1384 rootScope.apply(); |
1251 | 1385 expect(log).toEqual(['foo:true', ':foo:true', '::foo:true']); |
1252 it(r'should run async in the same order as added', inject((RootScope rootSco
pe) { | 1386 log.clear(); |
1253 rootScope.context['log'] = ''; | 1387 |
1254 rootScope.runAsync(() => rootScope.eval("log = log + 1")); | 1388 rootScope.context['foo'] = 123; |
1255 rootScope.runAsync(() => rootScope.eval("log = log + 2")); | 1389 rootScope.apply(); |
1256 rootScope.digest(); | 1390 expect(log).toEqual(['foo:123', ':foo:123']); |
1257 expect(rootScope.context['log']).toEqual('12'); | 1391 log.clear(); |
1258 })); | 1392 |
1259 }); | 1393 rootScope.context['foo'] = null; |
1260 | 1394 rootScope.apply(); |
1261 | 1395 expect(log).toEqual(['foo:null']); |
1262 describe('domRead/domWrite', () { | 1396 log.clear(); |
1263 it(r'should run writes before reads', () { | 1397 }); |
1264 module((Module module) { | 1398 }); |
| 1399 |
| 1400 |
| 1401 describe('runAsync', () { |
| 1402 it(r'should run callback before watch', (RootScope rootScope) { |
| 1403 var log = ''; |
| 1404 rootScope.runAsync(() { log += 'parent.async;'; }); |
| 1405 rootScope.watch('value', (_, __) { log += 'parent.digest;'; }); |
| 1406 rootScope.digest(); |
| 1407 expect(log).toEqual('parent.async;parent.digest;'); |
| 1408 }); |
| 1409 |
| 1410 it(r'should cause a digest rerun', (RootScope rootScope) { |
| 1411 rootScope.context['log'] = ''; |
| 1412 rootScope.context['value'] = 0; |
| 1413 // NOTE(deboer): watch listener string functions not yet supported |
| 1414 //rootScope.watch('value', 'log = log + ".";'); |
| 1415 rootScope.watch('value', (_, __) { rootScope.context['log'] += "."; }); |
| 1416 rootScope.watch('init', (_, __) { |
| 1417 rootScope.runAsync(() => rootScope.eval('value = 123; log = log + "="
')); |
| 1418 expect(rootScope.context['value']).toEqual(0); |
| 1419 }); |
| 1420 rootScope.digest(); |
| 1421 expect(rootScope.context['log']).toEqual('.=.'); |
| 1422 }); |
| 1423 |
| 1424 it(r'should run async in the same order as added', (RootScope rootScope) { |
| 1425 rootScope.context['log'] = ''; |
| 1426 rootScope.runAsync(() => rootScope.eval("log = log + 1")); |
| 1427 rootScope.runAsync(() => rootScope.eval("log = log + 2")); |
| 1428 rootScope.digest(); |
| 1429 expect(rootScope.context['log']).toEqual('12'); |
| 1430 }); |
| 1431 }); |
| 1432 |
| 1433 |
| 1434 describe('domRead/domWrite', () { |
| 1435 beforeEachModule((Module module) { |
1265 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); | 1436 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); |
1266 }); | 1437 }); |
1267 inject((RootScope rootScope, Logger logger, ExceptionHandler e) { | 1438 |
| 1439 it(r'should run writes before reads', (RootScope rootScope, Logger logger,
ExceptionHandler e) { |
1268 LoggingExceptionHandler exceptionHandler = e as LoggingExceptionHandler; | 1440 LoggingExceptionHandler exceptionHandler = e as LoggingExceptionHandler; |
1269 rootScope.domWrite(() { | 1441 rootScope.domWrite(() { |
1270 logger('write1'); | 1442 logger('write1'); |
1271 rootScope.domWrite(() => logger('write2')); | 1443 rootScope.domWrite(() => logger('write2')); |
1272 throw 'write1'; | 1444 throw 'write1'; |
1273 }); | 1445 }); |
1274 rootScope.domRead(() { | 1446 rootScope.domRead(() { |
1275 logger('read1'); | 1447 logger('read1'); |
1276 rootScope.domRead(() => logger('read2')); | 1448 rootScope.domRead(() => logger('read2')); |
1277 rootScope.domWrite(() => logger('write3')); | 1449 rootScope.domWrite(() => logger('write3')); |
1278 throw 'read1'; | 1450 throw 'read1'; |
1279 }); | 1451 }); |
1280 rootScope.watch('value', (_, __) => logger('observe'), readOnly: true); | 1452 rootScope.watch('value', (_, __) => logger('observe'), |
| 1453 canChangeModel: false); |
1281 rootScope.flush(); | 1454 rootScope.flush(); |
1282 expect(logger).toEqual(['write1', 'write2', 'observe', 'read1', 'read2',
'write3']); | 1455 expect(logger).toEqual(['write1', 'write2', 'observe', 'read1', 'read2',
'write3']); |
1283 expect(exceptionHandler.errors.length).toEqual(2); | 1456 expect(exceptionHandler.errors.length).toEqual(2); |
1284 expect(exceptionHandler.errors[0].error).toEqual('write1'); | 1457 expect(exceptionHandler.errors[0].error).toEqual('write1'); |
1285 expect(exceptionHandler.errors[1].error).toEqual('read1'); | 1458 expect(exceptionHandler.errors[1].error).toEqual('read1'); |
1286 }); | 1459 }); |
1287 }); | 1460 }); |
1288 }); | |
1289 | 1461 |
1290 describe('exceptionHander', () { | 1462 describe('exceptionHander', () { |
1291 it('should call ExceptionHandler on zone errors', () { | 1463 beforeEachModule((Module module) { |
1292 module((Module module) { | |
1293 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); | 1464 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); |
1294 }); | 1465 }); |
1295 async((inject((RootScope rootScope, NgZone zone, ExceptionHandler e) { | 1466 |
| 1467 it('should call ExceptionHandler on zone errors', |
| 1468 async((RootScope rootScope, VmTurnZone zone, ExceptionHandler e) { |
1296 zone.run(() { | 1469 zone.run(() { |
1297 scheduleMicrotask(() => throw 'my error'); | 1470 scheduleMicrotask(() => throw 'my error'); |
1298 }); | 1471 }); |
1299 var errors = (e as LoggingExceptionHandler).errors; | 1472 var errors = (e as LoggingExceptionHandler).errors; |
1300 expect(errors.length).toEqual(1); | 1473 expect(errors.length).toEqual(1); |
1301 expect(errors.first.error).toEqual('my error'); | 1474 expect(errors.first.error).toEqual('my error'); |
1302 }))); | 1475 })); |
1303 }); | |
1304 | 1476 |
1305 it('should call ExceptionHandler on digest errors', () { | 1477 it('should call ExceptionHandler on digest errors', |
1306 module((Module module) { | 1478 async((RootScope rootScope, VmTurnZone zone, ExceptionHandler e) { |
1307 module.type(ExceptionHandler, implementedBy: LoggingExceptionHandler); | |
1308 }); | |
1309 async((inject((RootScope rootScope, NgZone zone, ExceptionHandler e) { | |
1310 rootScope.context['badOne'] = () => new Map(); | 1479 rootScope.context['badOne'] = () => new Map(); |
1311 rootScope.watch('badOne()', (_, __) => null); | 1480 rootScope.watch('badOne()', (_, __) => null); |
1312 | 1481 |
1313 try { | 1482 try { |
1314 zone.run(() => null); | 1483 zone.run(() => null); |
1315 } catch(_) {} | 1484 } catch(_) {} |
1316 | 1485 |
1317 var errors = (e as LoggingExceptionHandler).errors; | 1486 var errors = (e as LoggingExceptionHandler).errors; |
1318 expect(errors.length).toEqual(1); | 1487 expect(errors.length).toEqual(1); |
1319 expect(errors.first.error, startsWith('Model did not stabilize')); | 1488 expect(errors.first.error, startsWith('Model did not stabilize')); |
1320 }))); | 1489 })); |
| 1490 }); |
| 1491 |
| 1492 describe('logging', () { |
| 1493 it('should log a message on digest if reporting is enabled', (RootScope ro
otScope, |
| 1494 Injector injector) { |
| 1495 ScopeStatsConfig config = injector.get(ScopeStatsConfig); |
| 1496 config.emit = true; |
| 1497 rootScope.digest(); |
| 1498 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke
d) |
| 1499 .toEqual(true); |
| 1500 }); |
| 1501 |
| 1502 it('should log a message on flush if reporting is enabled', (RootScope roo
tScope, |
| 1503 Injector injector) { |
| 1504 ScopeStatsConfig config = injector.get(ScopeStatsConfig); |
| 1505 config.emit = true; |
| 1506 rootScope.flush(); |
| 1507 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke
d) |
| 1508 .toEqual(true); |
| 1509 }); |
| 1510 |
| 1511 it('should not log a message on digest if reporting is disabled', (RootSco
pe rootScope, |
| 1512 Injector injector) { |
| 1513 rootScope.digest(); |
| 1514 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke
d) |
| 1515 .toEqual(false); |
| 1516 }); |
| 1517 |
| 1518 it('should not log a message on flush if reporting is disabled', (RootScop
e rootScope, |
| 1519 Injector injector) { |
| 1520 rootScope.flush(); |
| 1521 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke
d) |
| 1522 .toEqual(false); |
| 1523 }); |
| 1524 |
| 1525 it('can be turned on at runtime', (RootScope rootScope, Injector injector)
{ |
| 1526 rootScope.digest(); |
| 1527 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke
d) |
| 1528 .toEqual(false); |
| 1529 ScopeStatsConfig config = injector.get(ScopeStatsConfig); |
| 1530 config.emit = true; |
| 1531 rootScope.digest(); |
| 1532 expect((injector.get(ScopeStatsEmitter) as MockScopeStatsEmitter).invoke
d) |
| 1533 .toEqual(true); |
| 1534 }); |
1321 }); | 1535 }); |
1322 }); | 1536 }); |
1323 }); | 1537 } |
1324 | 1538 |
1325 @NgFilter(name: 'multiply') | 1539 @Formatter(name: 'identity') |
| 1540 class _IdentityFilter { |
| 1541 Logger logger; |
| 1542 _IdentityFilter(this.logger); |
| 1543 call(v) { |
| 1544 logger('identity'); |
| 1545 return v; |
| 1546 } |
| 1547 } |
| 1548 |
| 1549 @Formatter(name: 'keys') |
| 1550 class _MapKeys { |
| 1551 Logger logger; |
| 1552 _MapKeys(this.logger); |
| 1553 call(Map m) { |
| 1554 logger('keys'); |
| 1555 return m.keys; |
| 1556 } |
| 1557 } |
| 1558 |
| 1559 @Formatter(name: 'multiply') |
1326 class _MultiplyFilter { | 1560 class _MultiplyFilter { |
1327 call(a, b) => a * b; | 1561 call(a, b) => a * b; |
1328 } | 1562 } |
1329 | 1563 |
1330 @NgFilter(name: 'listHead') | 1564 @Formatter(name: 'listHead') |
1331 class _ListHeadFilter { | 1565 class _ListHeadFilter { |
1332 Logger logger; | 1566 Logger logger; |
1333 _ListHeadFilter(Logger this.logger); | 1567 _ListHeadFilter(this.logger); |
1334 call(list, head) { | 1568 call(list, head) { |
1335 logger('listHead'); | 1569 logger('listHead'); |
1336 return [head]..addAll(list); | 1570 return [head]..addAll(list); |
1337 } | 1571 } |
1338 } | 1572 } |
1339 | 1573 |
1340 | 1574 @Formatter(name: 'listTail') |
1341 @NgFilter(name: 'listTail') | |
1342 class _ListTailFilter { | 1575 class _ListTailFilter { |
1343 Logger logger; | 1576 Logger logger; |
1344 _ListTailFilter(Logger this.logger); | 1577 _ListTailFilter(this.logger); |
1345 call(list, tail) { | 1578 call(list, tail) { |
1346 logger('listTail'); | 1579 logger('listTail'); |
1347 return new List.from(list)..add(tail); | 1580 return new List.from(list)..add(tail); |
1348 } | 1581 } |
1349 } | 1582 } |
1350 | 1583 |
1351 @NgFilter(name: 'sort') | 1584 @Formatter(name: 'sort') |
1352 class _SortFilter { | 1585 class _SortFilter { |
1353 Logger logger; | 1586 Logger logger; |
1354 _SortFilter(Logger this.logger); | 1587 _SortFilter(this.logger); |
1355 call(list) { | 1588 call(list) { |
1356 logger('sort'); | 1589 logger('sort'); |
1357 return new List.from(list)..sort(); | 1590 return new List.from(list)..sort(); |
1358 } | 1591 } |
1359 } | 1592 } |
1360 | 1593 |
1361 @NgFilter(name:'newFilter') | 1594 @Formatter(name:'newFilter') |
1362 class FilterOne { | 1595 class FilterOne { |
1363 call(String str) { | 1596 call(String str) { |
1364 return '$str 1'; | 1597 return '$str 1'; |
1365 } | 1598 } |
1366 } | 1599 } |
1367 | 1600 |
1368 @NgFilter(name:'newFilter') | 1601 @Formatter(name:'newFilter') |
1369 class FilterTwo { | 1602 class FilterTwo { |
1370 call(String str) { | 1603 call(String str) { |
1371 return '$str 2'; | 1604 return '$str 2'; |
1372 } | 1605 } |
1373 } | 1606 } |
| 1607 |
| 1608 class MockScopeStatsEmitter implements ScopeStatsEmitter { |
| 1609 bool invoked = false; |
| 1610 |
| 1611 void emitMessage(String message) {} |
| 1612 |
| 1613 void emitSummary(List<int> digestTimes, int flushPhaseDuration, |
| 1614 int assertFlushPhaseDuration) {} |
| 1615 |
| 1616 void emit(String phaseOrLoopNo, AvgStopwatch fieldStopwatch, |
| 1617 AvgStopwatch evalStopwatch, AvgStopwatch processStopwatch) { |
| 1618 invoked = true; |
| 1619 } |
| 1620 } |
| 1621 |
| 1622 class UnstableList { |
| 1623 List get list => new List.generate(3, (i) => i); |
| 1624 } |
| 1625 |
OLD | NEW |