├── .gitignore
├── LICENSE
├── README.md
├── doc
└── getting_started.md
├── example
├── amb
│ ├── amb_example.dart
│ └── amb_example.html
├── average
│ ├── average_example.dart
│ └── average_example.html
├── buffer
│ ├── buffer_example.dart
│ └── buffer_example.html
├── combineLatest
│ ├── combineLatest_example.dart
│ └── combineLatest_example.html
├── concat
│ ├── concat_example.dart
│ └── concat_example.html
├── delay
│ ├── delay_example.dart
│ └── delay_example.html
├── max
│ ├── max_example.dart
│ └── max_example.html
├── merge
│ ├── merge_example.dart
│ └── merge_example.html
├── min
│ ├── min_example.dart
│ └── min_example.html
├── onErrorResumeNext
│ ├── onErrorResumeNext_example.dart
│ └── onErrorResumeNext_example.html
├── repeat
│ ├── repeat_example.dart
│ └── repeat_example.html
├── sample
│ ├── sample_example.dart
│ └── sample_example.html
├── scan
│ ├── scan_example.dart
│ └── scan_example.html
├── startWith
│ ├── startWith_example.dart
│ └── startWith_example.html
├── sum
│ ├── sum_example.dart
│ └── sum_example.html
├── switchFrom
│ ├── switchFrom_example.dart
│ └── switchFrom_example.html
├── throttle
│ ├── throttle_example.dart
│ └── throttle_example.html
├── timeOut
│ ├── timeOut_example.dart
│ └── timeOut_example.html
├── timeOutAt
│ ├── timeOutAt_example.dart
│ └── timeOutAt_example.html
├── window
│ ├── window_example.dart
│ └── window_example.html
└── zip
│ ├── zip_example.dart
│ └── zip_example.html
├── lib
├── stream_ext.dart
├── timeout_error.dart
└── tuple.dart
├── pubspec.yaml
└── test
├── extensions
├── amb_test.dart
├── average_test.dart
├── buffer_test.dart
├── combineLatest_test.dart
├── concat_test.dart
├── delay_test.dart
├── max_test.dart
├── merge_test.dart
├── min_test.dart
├── onErrorResumeNext_test.dart
├── repeat_test.dart
├── sample_test.dart
├── scan_test.dart
├── startWith_test.dart
├── sum_test.dart
├── switchFrom_test.dart
├── throttle_test.dart
├── timeOutAt_test.dart
├── timeOut_test.dart
├── window_test.dart
└── zip_test.dart
└── stream_ext_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # files and directories created by pub, Dart Editor, and dart2js
2 | packages/
3 | *.project
4 | *.buildlog
5 | *.js_
6 | *.js.deps
7 | *.js.map
8 | *.lock
9 |
10 | # files and directories dropped by other development environments
11 | .project // Eclipse
12 | *.iml // IntelliJ
13 | *.ipr // IntelliJ
14 | *.iws // IntelliJ
15 | .idea/ // IntelliJ
16 | .DS_Store // Mac
17 |
18 | # generated JavaScript files
19 | *.dart.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Yan Cui
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | stream_ext
2 | ========
3 |
4 | This is a port of the Rx functions for working with Dart's `Stream` type, which already has the basic set of Rx APIs such as `select` and `where`.
5 |
6 | This library adds the more 'exotic' Rx functions such as `delay`, `buffer`, `scan`, `throttle`, `zip`, `merge`, etc. to help make working with Dart's `Stream` type even easier!
7 |
8 | ### Getting Started
9 |
10 | To get up to speed with **stream_ext**:
11 |
12 | * have a look at the [Getting Started](https://github.com/theburningmonk/stream_ext/blob/master/doc/getting_started.md) document
13 | * the [wiki](https://github.com/theburningmonk/stream_ext/wiki) also contains details (including marble diagrams) for each of the functions
14 | * the [example](https://github.com/theburningmonk/stream_ext/tree/master/example) directory also contains more useful/meaningful usages for each of the functions
15 |
16 | ### Resources
17 |
18 | - [Download](https://pub.dartlang.org/packages/stream_ext)
19 | - [API doc](http://www.dartdocs.org/documentation/stream_ext/0.4.0/index.html)
20 | - [Bug Tracker](https://github.com/theburningmonk/stream_ext/issues)
21 | - Follow [@theburningmonk](https://twitter.com/theburningmonk) on Twitter for updates
22 |
--------------------------------------------------------------------------------
/doc/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting started with stream_ext
2 |
3 | Learn about the extension functions for working with `Stream` type with `stream_ext`.
4 |
5 | ### amb
6 |
7 | The `StreamExt.amb` method propagates values from the stream that reacts first with a value.
8 |
9 | This method will ignore any errors received from either stream until the first value is received.
10 | The stream which reacts first with a value will have its values and errors propagated through the output stream.
11 |
12 | The output stream will complete if:
13 | * neither stream produced a value before completing
14 | * the propagated stream has completed
15 | * **closeOnError** flag is set to true and an error is received in the propagated stream
16 |
17 | Example:
18 |
19 | var stream1 = new Stream.periodic(new Duration(seconds : 1), (n) => n);
20 | var stream2 = new Stream.periodic(new Duration(seconds : 2), (n) => n);
21 | var amb = StreamExt.amb(stream1, stream2);
22 |
23 |
24 | ### average
25 |
26 | The `StreamExt.average` method returns the average of the values as a `Future` which completes when the input stream is done.
27 |
28 | This method uses the supplied **map** function to convert each input value into a `num`. If a **map** function is not specified then the identity function is used instead.
29 |
30 | If **closeOnError** flag is set to true, then any error in the **map** function or from the input stream will complete the `Future` with the error.
31 | Otherwise, any errors will be swallowed and excluded from the final average.
32 |
33 | Example:
34 |
35 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n).take(10);
36 | StreamExt.average(input).then(print);
37 |
38 |
39 | ### buffer
40 |
41 | The `StreamExt.buffer` method creates a new stream which buffers values from the input stream produced within the sepcified **duration** and return the buffered values as a list.
42 |
43 | The buffered stream will complete if:
44 | * the input stream has completed and any buffered values have been pushed
45 | * **closeOnError** flag is set to true and an error is received
46 |
47 | Example
48 |
49 | var input = new Stream.periodic(new Duration(milliseconds : 10), (n) => n);
50 | var buffered = StreamExt.buffer(input, new Duration(seconds : 1));
51 |
52 |
53 | ### combineLatest
54 |
55 | The `StreamExt.combineLastest` method merges two streams into one by using the **selector** function to generate a new value whenever one of the streams produces a value.
56 |
57 | The merged stream will complete if:
58 | * both input streams have completed
59 | * **closeOnError** flag is set to true and an error is received
60 |
61 | Example
62 |
63 | var stream1 = new Stream.periodic(new Duration(milliseconds : 10), (n) => n);
64 | var stream2 = new Stream.periodic(new Duration(milliseconds : 100), (n) => n);
65 |
66 | var merged = StreamExt.combineLatest(stream1, stream2, (a, b) => a + b);
67 |
68 |
69 | ### concat
70 |
71 | The `StreamExt.concat` method concatenates two streams together, when the first stream completes the second stream is subscribed to.
72 | Until the first stream is done any values and errors from the second stream is ignored.
73 |
74 | The concatenated stream will complete if:
75 | * both input streams have completed (if stream 2 completes before stream 1 then the concatenated stream is completed when stream 1 completes)
76 | * **closeOnError** flag is set to true and an error is received in the active input stream (stream 1 until it completes, then stream 2)
77 |
78 | Example
79 |
80 | var stream1 = new Stream.periodic(new Duration(milliseconds : 10), (n) => n).take(10);
81 | var stream2 = new Stream.periodic(new Duration(milliseconds : 100), (n) => n).take(10);
82 |
83 | var concat = StreamExt.concat(stream1, stream2);
84 |
85 |
86 | ### delay
87 |
88 | The `StreamExt.delay` method creates a new stream whose values are sourced from the input stream but each delivered after the specified **duration**.
89 |
90 | The delayed stream will complete if:
91 | * the input has completed and the delayed complete message has been pushed
92 | * the **closeOnError** flag is set to true and an error is received from the input stream
93 |
94 | Example
95 |
96 | var input = new StreamController.broadcast().stream;
97 |
98 | // each event from the input stream is delivered 1 second after it was originally received
99 | var delayed = StreamExt.delay(input, new Duration(seconds : 1));
100 |
101 |
102 | ### max
103 |
104 | The `StreamExt.max` method returns the maximum value as a `Future` when the input stream is done, as determined by the supplied **compare** function which compares the current maximum value against any new value produced by the input stream.
105 |
106 | The **compare** function must act as a `Comparator`.
107 |
108 | If **closeOnError** flag is set to true, then any error in the **compare** function will complete the `Future` with the error.
109 | Otherwise, any errors will be swallowed and excluded from the final maximum.
110 |
111 | Example
112 |
113 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n).take(10);
114 | StreamExt.max(input, (a, b) => a.compareTo(b)).then(print);
115 |
116 |
117 | ### merge
118 |
119 | The `StreamExt.merge` method merges two streams into a single unitifed output stream.
120 |
121 | The merged stream will forward any values and errors received from the input streams and will complete if:
122 | * both input streams have completed
123 | * the **closeOnError** flag is set to true and an error is received from either input stream
124 |
125 | Example:
126 |
127 | var stream1 = new StreamController.broadcast().stream;
128 | var stream2 = new StreamController.broadcast().stream;
129 |
130 | var merged = StreamExt.merge(stream1, stream2);
131 |
132 |
133 | ### min
134 |
135 | The `StreamExt.min` method returns the minimum value as a `Future` when the input stream is done, as determined by the supplied **compare** function which compares the current minimum value against any new value produced by the input stream.
136 |
137 | The **compare** function must act as a `Comparator`.
138 |
139 | If **closeOnError** flag is set to true, then any error in the **compare** function will complete the `Future` with the error.
140 | Otherwise, any errors will be swallowed and excluded from the final minimum.
141 |
142 | Example
143 |
144 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n).take(10);
145 | StreamExt.min(input, (a, b) => a.compareTo(b)).then(print);
146 |
147 |
148 | ### onErrorResumeNext
149 |
150 | The `StreamExt.onErrorResumeNext` method allows the continuation of a stream with another regardless of whether the first stream completes gracefully or due to an error.
151 |
152 | The output stream will complete if:
153 | * both input streams have completed (if stream 2 completes before stream 1 then the output stream is completed when stream 1 completes)
154 | * **closeOnError** flag is set to true and an error is received in the continuation stream
155 |
156 | Example:
157 |
158 | var stream1 = new StreamController.broadcast().stream;
159 | var stream2 = new StreamController.broadcast().stream;
160 |
161 | var resumed = StreamExt.onErrorResumeNext(stream1, stream2);
162 |
163 |
164 | ### repeat
165 |
166 | The `StreamExt.repeat` method allows you to repeat the input stream for the specified number of times.
167 | If **repeatCount** is not set, then the input stream will be repeated **indefinitely**.
168 |
169 | The `done` value is not delivered when the input stream completes, but only after the input stream has been repeated the required number of times.
170 |
171 | The output stream will complete if:
172 | * the input stream has been repeated the required number of times
173 | * the **closeOnError** flag is set to true and an error has been received
174 |
175 | Example
176 |
177 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n).take(10);
178 | var repeated = StreamExt.repeat(input, repeatCount : 3);
179 |
180 |
181 | ### sample
182 |
183 | The `StreamExt.sample` method creates a new stream by taking the last value from the input stream for every specified **duration**.
184 |
185 | The sampled stream will complete if:
186 | * the input stream has completed and any sampled message has been delivered
187 | * the **closeOnError** flag is set to true and an error has been received
188 |
189 | Example
190 |
191 | var input = new Stream.periodic(new Duration(milliseconds : 150), (n) => n).take(100);
192 | var sampled = StreamExt.sample(input, new Duration(seconds : 1));
193 |
194 |
195 | ### scan
196 |
197 | The `StreamExt.scan` method creates a new stream by applying an **accumulator** function over the values produced by the input stream and returns each intermediate result with the specified seed and accumulator.
198 |
199 | The output stream will complete if:
200 | * the input stream has completed
201 | * **closeOnError** flag is set to true and an error is received
202 |
203 | Example
204 |
205 | var input = new Stream.periodic(new Duration(milliseconds : 150), (n) => n);
206 | var runningTotal = StreamExt.scan(input, 0, (acc, elem) => acc + elem);
207 |
208 |
209 | ### startWith
210 |
211 | The `StreamExt.startWith` method allows you to prefix values to a stream.
212 | The supplied values are delivered as soon as the listener is subscribed before the listener receives values from the input stream.
213 |
214 | The output stream will complete if:
215 | * the input stream has completed
216 | * **closeOnError** flag is set to true and an error is received
217 |
218 | Example
219 |
220 | var input = new Stream.periodic(new Duration(milliseconds : 150), (n) => n);
221 | var output = StreamExt.startWith(input, [ -3, -2, -1 ]);
222 |
223 |
224 | ### sum
225 |
226 | The `StreamExt.sum` method returns the sum of all the input values as a `Future` when the input stream is done, using the supplied **map** function to convert each input value into a `num`.
227 |
228 | If a **map** function is not specified then the identity function is used.
229 |
230 | If **closeOnError** flag is set to true, then any error in the **map** function will complete the `Future` with the error.
231 | Otherwise, any errors will be swallowed and excluded from the final sum.
232 |
233 | Example
234 |
235 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n).take(10);
236 | StreamExt.sum(input).then(print);
237 |
238 |
239 | ### switchFrom
240 |
241 | The `StreamExt.switchFrom` method transforms a stream of streams into a stream producing values only from the most recent stream.
242 |
243 | The output stream will complete if:
244 | * the input stream has completed and the last stream has completed
245 | * **closeOnError** flag is set to true and an error is received in the active stream
246 |
247 | Example
248 |
249 | var input = new StreamController.broadcast().stream;
250 | var switched = StreamExt.switchFrom(input);
251 | input.add(new Stream.periodic(new Duration(seconds : 1)));
252 |
253 |
254 | ### throttle
255 |
256 | The `StreamExt.throttle` method creates a new stream based on values produced by the specified input, upon forwarding a value from the input stream it'll ignore any subsequent values produced by the input stream until the the flow of new values has paused for the specified duration, after which the last value produced by the input stream is then delivered.
257 |
258 | The throttled stream will complete if:
259 | * the input stream has completed and the any throttled message has been delivered
260 | * the **closeOnError** flag is set to true and an error is received from the input stream
261 |
262 | Example
263 |
264 | var input = new StreamController.broadcast().stream;
265 | var throttled = StreamExt.throttle(input, new Duration(seconds : 1));
266 |
267 |
268 | ### timeOut
269 |
270 | The `StreamExt.timeOut` method allows you to terminate a stream with a **TimeoutError** if the specified **duration** between values elapsed.
271 |
272 | The output stream will complete if:
273 | * the input stream has completed
274 | * the specified **duration** between input values has elpased
275 | * **closeOnError** flag is set to true and an error is received
276 |
277 | Example
278 |
279 | var input = new StreamController.broadcast().stream;
280 | var timedOut = StreamExt.timeOut(input, new Duration(seconds : 3));
281 |
282 |
283 | ### timeOutAt
284 |
285 | The `StreamExt.timeOutAt` method allows you to terminate a stream with a **TimeoutError** at the specified **dueTime**.
286 |
287 | The output stream will complete if:
288 | * the input stream has completed
289 | * the specified **dueTime** has elapsed
290 | * **closeOnError** flag is set to true and an error is received
291 |
292 | Example
293 |
294 | var input = new StreamController.broadcast().stream;
295 | var timedOut = StreamExt.timeOutAt(input, new DateTime.now().add(new Duration(seconds : 10));
296 |
297 |
298 | ### window
299 |
300 | The `StreamExt.window` method projects each value from the input stream into consecutive non-overlapping windows.
301 | Each value proudced by the output stream contains a list of values up to the specified count.
302 |
303 | The output stream will complete if:
304 | * the input stream has completed and any buffered elements have been upshed
305 | * **closeOnError** flag is set to true and an error is received
306 |
307 | Example
308 |
309 | var input = new StreamController.broadcast().stream;
310 | var windowed = StreamExt.window(input, 3);
311 |
312 |
313 | ### zip
314 |
315 | The `StreamExt.zip` method zips two streams into one by combining their values in a pairwise fashion.
316 |
317 | The zipped stream will complete if:
318 | * either input stream has completed
319 | * **closeOnError** flag is set to true and an error is received
320 |
321 | Example
322 |
323 | var mouseMove = document.onMouseMove;
324 | var mouseDrags =
325 | StreamExt
326 | .zip(mouseMove,
327 | mouseMove.skip(1),
328 | (MouseEvent left, MouseEvent right) => new MouseMove(right.screen.x - left.screen.x, right.screen.y - left.screen.y))
329 | .where((_) => isDragging);
330 |
331 |
332 | ## Examples
333 |
334 | Please take a look at the **example** directory for more complete and meaningful usages of each of the extension functions.
335 |
336 | ## Package Import
337 |
338 | Add the `stream_ext` depedency to your pubspec.yaml ...
339 |
340 | name: hello_world
341 | description: hello world
342 | dependencies:
343 | stream_ext: any
344 |
345 | ... then import the library in your Dart code.
346 |
347 | import 'package:stream_ext/stream_ext.dart';
348 |
--------------------------------------------------------------------------------
/example/amb/amb_example.dart:
--------------------------------------------------------------------------------
1 | library amb_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btn2 = query("#btn_2");
10 | var btn3 = query("#btn_3");
11 |
12 | var btnErr1 = query("#btn_err_1");
13 | var btnErr2 = query("#btn_err_2");
14 | var btnErr3 = query("#btn_err_3");
15 |
16 | var btnDone1 = query("#btn_done_1");
17 | var btnDone2 = query("#btn_done_2");
18 | var btnDone3 = query("#btn_done_3");
19 |
20 | var output = query("#output");
21 |
22 | var contr1 = new StreamController.broadcast();
23 | var contr2 = new StreamController.broadcast();
24 | var contr3 = new StreamController.broadcast();
25 |
26 | log(msg) => output.children.add(new DivElement()..text = msg);
27 |
28 | var stream1 = contr1.stream;
29 | var stream2 = contr2.stream;
30 | var stream3 = contr3.stream;
31 | var ambigious = StreamExt.amb(StreamExt.amb(stream1, stream2), stream3);
32 |
33 | StreamExt.log(stream1, "stream1", log);
34 | StreamExt.log(stream2, "stream2", log);
35 | StreamExt.log(stream3, "stream3", log);
36 | StreamExt.log(ambigious, "amb", log);
37 |
38 | btn1.onClick.listen((_) => contr1.add("new event"));
39 | btn2.onClick.listen((_) => contr2.add("new event"));
40 | btn3.onClick.listen((_) => contr3.add("new event"));
41 |
42 | btnErr1.onClick.listen((_) => contr1.addError("new error"));
43 | btnErr2.onClick.listen((_) => contr2.addError("new error"));
44 | btnErr3.onClick.listen((_) => contr3.addError("new error"));
45 |
46 | btnDone1.onClick.listen((_) => contr1.close());
47 | btnDone2.onClick.listen((_) => contr2.close());
48 | btnDone3.onClick.listen((_) => contr3.close());
49 | }
--------------------------------------------------------------------------------
/example/amb/amb_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Amb
6 |
7 |
8 |
9 |
10 | Amb Demo
11 |
12 | Use the StreamExt.amb function to propagate events and errors from the first stream that reacts with a value.
13 |
14 | Stream 1 : Event Error Done
15 |
16 | Stream 2 : Event Error Done
17 |
18 | Stream 3 : Event Error Done
19 |
20 |
21 | Output :
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/average/average_example.dart:
--------------------------------------------------------------------------------
1 | library avg_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | InputElement input;
8 | ButtonElement done;
9 | SpanElement avglLetters;
10 | SpanElement avgWords;
11 |
12 | main() {
13 | input = query('#input_text');
14 | done = query('#done');
15 | avglLetters = query('#letter_count');
16 | avgWords = query('#word_count');
17 |
18 | _setup();
19 | }
20 |
21 | _setup() {
22 | var controller = new StreamController.broadcast();
23 | var inputStream = controller.stream;
24 |
25 | var inputSub = input.onKeyDown.listen((KeyboardEvent evt) {
26 | if (evt.keyCode == KeyCode.ENTER) {
27 | controller.add(input.value);
28 | input.value = null;
29 | }
30 | });
31 |
32 | var letters = StreamExt.average(inputStream, map : (String str) => str.length);
33 | var words = StreamExt.average(inputStream, map : (String str) => str.split(" ").where((str) => str.length > 0).length);
34 |
35 | var doneSub = done.onClick.listen((_) {
36 | if (!controller.isClosed) controller.close();
37 | });
38 |
39 | Future
40 | .wait([ letters.then((avg) => avglLetters.text = "$avg"),
41 | words.then((avg) => avgWords.text = "$avg") ])
42 | .then((_) {
43 | inputSub.cancel();
44 | doneSub.cancel();
45 | _setup();
46 | });
47 | }
--------------------------------------------------------------------------------
/example/average/average_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Average
6 |
7 |
8 |
9 |
10 | Average Demo
11 |
12 | Use the StreamExt.average function to yield the total word and letter count.
13 |
14 | I'm done
15 |
16 | Average Words :
17 |
18 | Average letters :
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/buffer/buffer_example.dart:
--------------------------------------------------------------------------------
1 | library buffer_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btnErr1 = query("#btn_err_1");
10 | var btnDone1 = query("#btn_done_1");
11 |
12 | var output = query("#output");
13 |
14 | var contrl = new StreamController.broadcast();
15 | var input = contrl.stream;
16 |
17 | var buffers = StreamExt.buffer(input, new Duration(seconds : 3));
18 |
19 | log(msg) => output.children.add(new DivElement()..text = msg);
20 |
21 | StreamExt.log(input, "input", log);
22 | StreamExt.log(buffers, "window", log);
23 |
24 | var idx = 0;
25 | btn1.onClick.listen((_) => contrl.add(idx++));
26 | btnErr1.onClick.listen((_) => contrl.addError("new error"));
27 | btnDone1.onClick.listen((_) => contrl.close());
28 | }
--------------------------------------------------------------------------------
/example/buffer/buffer_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Buffer
6 |
7 |
8 |
9 |
10 | Buffer Demo
11 |
12 | Use the StreamExt.buffer function to group input elements produced in 3 second windows.
13 |
14 | Input : Event Error Done
15 |
16 |
17 | Output :
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/combineLatest/combineLatest_example.dart:
--------------------------------------------------------------------------------
1 | library combineLatest_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btn2 = query("#btn_2");
10 |
11 | var btnErr1 = query("#btn_err_1");
12 | var btnErr2 = query("#btn_err_2");
13 |
14 | var btnDone1 = query("#btn_done_1");
15 | var btnDone2 = query("#btn_done_2");
16 |
17 | var output = query("#output");
18 |
19 | var contr1 = new StreamController.broadcast();
20 | var contr2 = new StreamController.broadcast();
21 |
22 | var stream1 = contr1.stream;
23 | var stream2 = contr2.stream;
24 | var combined = StreamExt.combineLatest(stream1, stream2, (a, b) => "($a, $b)");
25 |
26 | log(msg) => output.children.add(new DivElement()..text = msg);
27 |
28 | StreamExt.log(stream1, "stream1", log);
29 | StreamExt.log(stream2, "stream2", log);
30 | StreamExt.log(combined, "combined", log);
31 |
32 | var idx1 = 0;
33 | btn1.onClick.listen((_) => contr1.add(idx1++));
34 |
35 | var idx2 = 0;
36 | btn2.onClick.listen((_) => contr2.add(idx2++));
37 |
38 | btnErr1.onClick.listen((_) => contr1.addError("new error"));
39 | btnErr2.onClick.listen((_) => contr2.addError("new error"));
40 |
41 | btnDone1.onClick.listen((_) => contr1.close());
42 | btnDone2.onClick.listen((_) => contr2.close());
43 | }
--------------------------------------------------------------------------------
/example/combineLatest/combineLatest_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - CombineLatest
6 |
7 |
8 |
9 |
10 | CombineLatest Demo
11 |
12 | Use the StreamExt.combineLatest function to merge events from the two source treams.
13 |
14 | Stream 1 : Event Error Done
15 |
16 | Stream 2 : Event Error Done
17 |
18 |
19 | Output :
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/concat/concat_example.dart:
--------------------------------------------------------------------------------
1 | library concat_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btn2 = query("#btn_2");
10 |
11 | var btnErr1 = query("#btn_err_1");
12 | var btnErr2 = query("#btn_err_2");
13 |
14 | var btnDone1 = query("#btn_done_1");
15 | var btnDone2 = query("#btn_done_2");
16 |
17 | var output = query("#output");
18 |
19 | var contr1 = new StreamController.broadcast();
20 | var contr2 = new StreamController.broadcast();
21 |
22 | var stream1 = contr1.stream;
23 | var stream2 = contr2.stream;
24 | var concat = StreamExt.concat(stream1, stream2);
25 |
26 | log(msg) => output.children.add(new DivElement()..text = msg);
27 |
28 | StreamExt.log(stream1, "stream1", log);
29 | StreamExt.log(stream2, "stream2", log);
30 | StreamExt.log(concat, "concatenated", log);
31 |
32 | btn1.onClick.listen((_) => contr1.add("new event"));
33 | btn2.onClick.listen((_) => contr2.add("new event"));
34 |
35 | btnErr1.onClick.listen((_) => contr1.addError("new error"));
36 | btnErr2.onClick.listen((_) => contr2.addError("new error"));
37 |
38 | btnDone1.onClick.listen((_) => contr1.close());
39 | btnDone2.onClick.listen((_) => contr2.close());
40 | }
--------------------------------------------------------------------------------
/example/concat/concat_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Concat
6 |
7 |
8 |
9 |
10 | Concat Demo
11 |
12 | Use the StreamExt.concat function to concatenate events from two source treams.
13 |
14 | Stream 1 : Event Error Done
15 |
16 | Stream 2 : Event Error Done
17 |
18 |
19 | Output :
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/delay/delay_example.dart:
--------------------------------------------------------------------------------
1 | library delay_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | _trackMouse("Time flies like an arrow");
9 | }
10 |
11 | _trackMouse(String message) {
12 | Element container = query('#container');
13 |
14 | Stream mouseMove = container.onMouseMove;
15 | var chars = new List.generate(message.length, (i) => message[i]);
16 |
17 | for (var i = 0; i < chars.length; i++) {
18 | Element label = new SpanElement()
19 | ..text = chars[i];
20 | container.children.add(label);
21 | label.style.left = "${i * 10}px";
22 | label.style.position = "relative";
23 |
24 | StreamExt.delay(mouseMove, new Duration(milliseconds : i * 100))
25 | ..listen((MouseEvent evt) {
26 | label
27 | ..style.left = "${evt.offset.x + i * 10}px"
28 | ..style.top = "${evt.offset.y}px";
29 | });
30 | }
31 | }
--------------------------------------------------------------------------------
/example/delay/delay_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Delay
6 |
7 |
8 |
9 |
10 | Delay Dmoe
11 |
12 | Use the StreamExt.delay function to delay delivering events from the source stream to the output stream
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/max/max_example.dart:
--------------------------------------------------------------------------------
1 | library max_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | InputElement input;
8 | ButtonElement done;
9 | SpanElement longestWord;
10 | SpanElement longestSentence;
11 |
12 | main() {
13 | input = query('#input_text');
14 | done = query('#done');
15 | longestWord = query('#longest_word');
16 | longestSentence = query('#longest_sentence');
17 |
18 | _setup();
19 | }
20 |
21 | _setup() {
22 | var controller = new StreamController.broadcast();
23 | Stream inputStream = controller.stream;
24 |
25 | var inputSub = input.onKeyDown.listen((KeyboardEvent evt) {
26 | if (evt.keyCode == KeyCode.ENTER) {
27 | controller.add(input.value);
28 | input.value = null;
29 | }
30 | });
31 |
32 | compare(String l, String r) => l.length.compareTo(r.length);
33 | var sentence = StreamExt.max(inputStream, compare);
34 | var wordStream = inputStream.expand((x) => x.split(" ").where((str) => str.length > 0));
35 | var word = StreamExt.max(wordStream, compare);
36 |
37 | var doneSub = done.onClick.listen((_) {
38 | if (!controller.isClosed) controller.close();
39 | });
40 |
41 | Future
42 | .wait([ sentence.then((x) => longestSentence.text = "$x"),
43 | word.then((x) => longestWord.text = "$x") ])
44 | .then((_) {
45 | inputSub.cancel();
46 | doneSub.cancel();
47 | _setup();
48 | });
49 | }
--------------------------------------------------------------------------------
/example/max/max_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Max
6 |
7 |
8 |
9 |
10 | Max Demo
11 |
12 | Use the StreamExt.max function to yield the longest word and sentence.
13 |
14 | I'm done
15 |
16 | Longest word :
17 |
18 | Longest sentence :
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/merge/merge_example.dart:
--------------------------------------------------------------------------------
1 | library merge_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btn2 = query("#btn_2");
10 | var btn3 = query("#btn_3");
11 |
12 | var btnErr1 = query("#btn_err_1");
13 | var btnErr2 = query("#btn_err_2");
14 | var btnErr3 = query("#btn_err_3");
15 |
16 | var btnDone1 = query("#btn_done_1");
17 | var btnDone2 = query("#btn_done_2");
18 | var btnDone3 = query("#btn_done_3");
19 |
20 | var output = query("#output");
21 |
22 | var contr1 = new StreamController.broadcast();
23 | var contr2 = new StreamController.broadcast();
24 | var contr3 = new StreamController.broadcast();
25 |
26 | var stream1 = contr1.stream;
27 | var stream2 = contr2.stream;
28 | var stream3 = contr3.stream;
29 | var merged = StreamExt.merge(StreamExt.merge(stream1, stream2), stream3);
30 |
31 | log(msg) => output.children.add(new DivElement()..text = msg);
32 |
33 | StreamExt.log(stream1, "stream1", log);
34 | StreamExt.log(stream2, "stream2", log);
35 | StreamExt.log(stream3, "stream3", log);
36 | StreamExt.log(merged, "merged", log);
37 |
38 | btn1.onClick.listen((_) => contr1.add("new event"));
39 | btn2.onClick.listen((_) => contr2.add("new event"));
40 | btn3.onClick.listen((_) => contr3.add("new event"));
41 |
42 | btnErr1.onClick.listen((_) => contr1.addError("new error"));
43 | btnErr2.onClick.listen((_) => contr2.addError("new error"));
44 | btnErr3.onClick.listen((_) => contr3.addError("new error"));
45 |
46 | btnDone1.onClick.listen((_) => contr1.close());
47 | btnDone2.onClick.listen((_) => contr2.close());
48 | btnDone3.onClick.listen((_) => contr3.close());
49 | }
--------------------------------------------------------------------------------
/example/merge/merge_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Merge
6 |
7 |
8 |
9 |
10 | Merge Demo
11 |
12 | Use the StreamExt.merge function to merge events from the three source treams.
13 |
14 | Stream 1 : Event Error Done
15 |
16 | Stream 2 : Event Error Done
17 |
18 | Stream 3 : Event Error Done
19 |
20 |
21 | Output :
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/min/min_example.dart:
--------------------------------------------------------------------------------
1 | library min_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | InputElement input;
8 | ButtonElement done;
9 | SpanElement shortestWord;
10 | SpanElement shortestSentence;
11 |
12 | main() {
13 | input = query('#input_text');
14 | done = query('#done');
15 | shortestWord = query('#shortest_word');
16 | shortestSentence = query('#shortest_sentence');
17 |
18 | _setup();
19 | }
20 |
21 | _setup() {
22 | var controller = new StreamController.broadcast();
23 | Stream inputStream = controller.stream;
24 |
25 | var inputSub = input.onKeyDown.listen((KeyboardEvent evt) {
26 | if (evt.keyCode == KeyCode.ENTER) {
27 | controller.add(input.value);
28 | input.value = null;
29 | }
30 | });
31 |
32 | compare(String l, String r) => l.length.compareTo(r.length);
33 | var sentence = StreamExt.min(inputStream, compare);
34 | var wordStream = inputStream.expand((x) => x.split(" ").where((str) => str.length > 0));
35 | var word = StreamExt.min(wordStream, compare);
36 |
37 | var doneSub = done.onClick.listen((_) {
38 | if (!controller.isClosed) controller.close();
39 | });
40 |
41 | Future
42 | .wait([ sentence.then((x) => shortestSentence.text = "$x"),
43 | word.then((x) => shortestWord.text = "$x") ])
44 | .then((_) {
45 | inputSub.cancel();
46 | doneSub.cancel();
47 | _setup();
48 | });
49 | }
--------------------------------------------------------------------------------
/example/min/min_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Min
6 |
7 |
8 |
9 |
10 | Min Demo
11 |
12 | Use the StreamExt.min function to yield the shortest word and sentence.
13 |
14 | I'm done
15 |
16 | Shortest word :
17 |
18 | Shortest sentence :
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/onErrorResumeNext/onErrorResumeNext_example.dart:
--------------------------------------------------------------------------------
1 | library onErrorResumeNext_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btn2 = query("#btn_2");
10 |
11 | var btnErr1 = query("#btn_err_1");
12 | var btnErr2 = query("#btn_err_2");
13 |
14 | var btnDone1 = query("#btn_done_1");
15 | var btnDone2 = query("#btn_done_2");
16 |
17 | var output = query("#output");
18 |
19 | var contr1 = new StreamController.broadcast();
20 | var contr2 = new StreamController.broadcast();
21 |
22 | var stream1 = contr1.stream;
23 | var stream2 = contr2.stream;
24 | var resumed = StreamExt.onErrorResumeNext(stream1, stream2);
25 |
26 | log(msg) => output.children.add(new DivElement()..text = msg);
27 |
28 | StreamExt.log(stream1, "stream1", log);
29 | StreamExt.log(stream2, "stream2", log);
30 | StreamExt.log(resumed, "resumed", log);
31 |
32 | btn1.onClick.listen((_) => contr1.add("new event"));
33 | btn2.onClick.listen((_) => contr2.add("new event"));
34 |
35 | btnErr1.onClick.listen((_) => contr1.addError("new error"));
36 | btnErr2.onClick.listen((_) => contr2.addError("new error"));
37 |
38 | btnDone1.onClick.listen((_) => contr1.close());
39 | btnDone2.onClick.listen((_) => contr2.close());
40 | }
--------------------------------------------------------------------------------
/example/onErrorResumeNext/onErrorResumeNext_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - OnErrorResumeNext
6 |
7 |
8 |
9 |
10 | OnErrorResumeNext Demo
11 |
12 | Use the StreamExt.onErrorResumeNext function to concatenate events from two source treams after the first stream errors or finishes.
13 |
14 | Stream 1 : Event Error Done
15 |
16 | Stream 2 : Event Error Done
17 |
18 |
19 | Output :
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/repeat/repeat_example.dart:
--------------------------------------------------------------------------------
1 | library repeat_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | ButtonElement start = query('#btn_start');
9 | InputElement input = query('#input');
10 | var output = query('#output');
11 |
12 | start.onClick.listen((_) {
13 | start.disabled = true;
14 |
15 | var inputStream = new Stream.periodic(new Duration(seconds : 1), (n) => n)
16 | .take(5);
17 | var repeatCount;
18 | try {
19 | repeatCount = int.parse(input.value);
20 | } catch (_) {
21 | }
22 |
23 | StreamExt.repeat(inputStream, repeatCount : repeatCount)
24 | ..listen((n) => output.children.add(new DivElement()..text = "$n (${new DateTime.now()})"),
25 | onError : (err) => output.children.add(new DivElement()..text = "$err"),
26 | onDone : () => output.children.add(new DivElement()..text = "done (${new DateTime.now()})"));
27 | });
28 | }
--------------------------------------------------------------------------------
/example/repeat/repeat_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Repeat
6 |
7 |
8 |
9 |
10 | Repeat Demo
11 |
12 | Use the StreamExt.repeat function to repeat the stream a number of times whilst preserving the timing of the evnets.
13 |
14 | Repeat times,
15 | Start publish events once per second
16 |
17 |
18 | Output :
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/sample/sample_example.dart:
--------------------------------------------------------------------------------
1 | library sample_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | ButtonElement start = query('#btn_start');
9 | var samples = query('#samples');
10 |
11 | start.onClick.listen((_) {
12 | start.disabled = true;
13 |
14 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n);
15 | StreamExt.sample(input, new Duration(seconds : 5))
16 | ..listen((n) => samples.children.add(new DivElement()..text = "$n"));
17 | });
18 | }
--------------------------------------------------------------------------------
/example/sample/sample_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Sample
6 |
7 |
8 |
9 | Sample Demo
10 | Use the StreamExt.sample function to sample values from the input source.
11 |
12 | Start publish events once per second
13 |
14 |
15 | Samples :
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/scan/scan_example.dart:
--------------------------------------------------------------------------------
1 | library scan_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | main() {
8 | InputElement input = query('#input_text');
9 | var output = query('#output');
10 |
11 | var controller = new StreamController.broadcast();
12 | var inputStream = controller.stream;
13 |
14 | input.onKeyDown.listen((KeyboardEvent evt) {
15 | if (evt.keyCode == KeyCode.ENTER) {
16 | controller.add(input.value);
17 | input.value = null;
18 | }
19 | });
20 |
21 | var outputStream = StreamExt.scan(inputStream, null, (acc, elem) {
22 | if (acc == null) {
23 | return elem;
24 | } else {
25 | return "$acc, $elem";
26 | }
27 | });
28 |
29 | outputStream.listen((data) => output.text = data);
30 | }
--------------------------------------------------------------------------------
/example/scan/scan_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Scan
6 |
7 |
8 |
9 |
10 | Scan Demo
11 |
12 | Use the StreamExt.scan function to continuously append to the CSV.
13 |
14 |
15 |
16 | Output :
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/startWith/startWith_example.dart:
--------------------------------------------------------------------------------
1 | library startWith_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | ButtonElement start = query('#btn_start');
9 | var samples = query('#output');
10 |
11 | start.onClick.listen((_) {
12 | start.disabled = true;
13 |
14 | var input = new Stream.periodic(new Duration(seconds : 1), (n) => n);
15 | StreamExt.startWith(input, [ -3, -2, -1 ])
16 | ..listen((n) => samples.children.add(new DivElement()..text = "$n"));
17 | });
18 | }
--------------------------------------------------------------------------------
/example/startWith/startWith_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - StartWith
6 |
7 |
8 |
9 |
10 | StartWith Demo
11 |
12 | Use the StreamExt.startWith function to prepand values to the start of a stream.
13 |
14 | Start publish events once per second
15 |
16 |
17 | Output :
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/sum/sum_example.dart:
--------------------------------------------------------------------------------
1 | library sum_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | InputElement input;
8 | ButtonElement done;
9 | SpanElement totalLetters;
10 | SpanElement totalWords;
11 |
12 | main() {
13 | input = query('#input_text');
14 | done = query('#done');
15 | totalLetters = query('#letter_count');
16 | totalWords = query('#word_count');
17 |
18 | _setup();
19 | }
20 |
21 | _setup() {
22 | var controller = new StreamController.broadcast();
23 | var inputStream = controller.stream;
24 |
25 | var inputSub = input.onKeyDown.listen((KeyboardEvent evt) {
26 | if (evt.keyCode == KeyCode.ENTER) {
27 | controller.add(input.value);
28 | input.value = null;
29 | }
30 | });
31 |
32 | var letters = StreamExt.sum(inputStream, map : (String str) => str.length);
33 | var words = StreamExt.sum(inputStream, map : (String str) => str.split(" ").where((str) => str.length > 0).length);
34 |
35 | var doneSub = done.onClick.listen((_) {
36 | if (!controller.isClosed) controller.close();
37 | });
38 |
39 | Future
40 | .wait([ letters.then((sum) => totalLetters.text = "$sum"),
41 | words.then((sum) => totalWords.text = "$sum") ])
42 | .then((_) {
43 | inputSub.cancel();
44 | doneSub.cancel();
45 | _setup();
46 | });
47 | }
--------------------------------------------------------------------------------
/example/sum/sum_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Sum
6 |
7 |
8 |
9 |
10 | Sum Demo
11 |
12 | Use the StreamExt.sum function to yield the total word and letter count.
13 |
14 | I'm done
15 |
16 | Total Words :
17 |
18 | Total letters :
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/switchFrom/switchFrom_example.dart:
--------------------------------------------------------------------------------
1 | library switchFrom_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var start = query("#btn_newStream");
9 | var output = query("#output");
10 |
11 | var contrl = new StreamController.broadcast();
12 |
13 | log(msg) => output.children.add(new DivElement()..text = msg);
14 |
15 | var stream = contrl.stream;
16 | var switched = StreamExt.switchFrom(stream);
17 |
18 | StreamExt.log(stream, "stream", log);
19 | StreamExt.log(switched, "switchFrom", log);
20 |
21 | var index = 0;
22 | start.onClick.listen((_) {
23 | index++;
24 | contrl.add(new Stream.periodic(new Duration(seconds : 1), (n) => n)
25 | .map((n) => "stream ${index} - $n"));
26 | });
27 | }
--------------------------------------------------------------------------------
/example/switchFrom/switchFrom_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - SwitchFrom
6 |
7 |
8 |
9 |
10 | SwitchFrom Demo
11 | Use the StreamExt.switchFrom function to throttle the input requests.
12 |
13 | Start New Stream
14 |
15 |
16 | Output :
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/throttle/throttle_example.dart:
--------------------------------------------------------------------------------
1 | library throttle_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:js/js.dart' as js;
6 | import 'package:stream_ext/stream_ext.dart';
7 |
8 | UListElement results;
9 | ParagraphElement error;
10 |
11 | void main() {
12 | results = query('#results');
13 | error = query('#error');
14 |
15 | var searcher = query('#searcher');
16 | Stream keyUp = searcher.onKeyUp;
17 |
18 | StreamExt.throttle(keyUp, new Duration(milliseconds : 250))
19 | .listen((KeyboardEvent evt) {
20 | queryWikipedia(searcher.value);
21 | });
22 | }
23 |
24 | void queryWikipedia(String term) {
25 | js.scoped(() {
26 | // create a top-level JavaScript function called myJsonpCallback
27 | js.context.jsonpCallback = new js.Callback.once((jsonData) {
28 | results.children.clear();
29 |
30 | // the response from Wikipedia should be of an array of two elements - the search term and the array of results
31 | for (var i = 0; i < jsonData[1].length; i++) {
32 | results.children.add(new DivElement()..text = jsonData[1][i]);
33 | }
34 | });
35 |
36 | // add a script tag for the api required
37 | ScriptElement script = new Element.tag("script");
38 |
39 | // add the callback function name to the URL
40 | script.src = "http://en.wikipedia.org/w/api.php?action=opensearch&search=$term&format=json&callback=jsonpCallback";
41 | document.body.children.add(script); // add the script to the DOM
42 | });
43 | }
--------------------------------------------------------------------------------
/example/throttle/throttle_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Throttle
6 |
7 |
8 |
9 |
10 | Throttle Demo
11 | Use the StreamExt.throttle function to throttle the input requests.
12 |
13 |
14 | Search Wikipedia:
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/timeOut/timeOut_example.dart:
--------------------------------------------------------------------------------
1 | library timeOut_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | main() {
8 | var newValue = query("#btn_event");
9 | var start = query("#btn_start");
10 | var input = query("#input");
11 | var output = query("#output");
12 |
13 | log(prefix, value) => output.children.add(new DivElement()..text = "${new DateTime.now()} : $prefix - $value");
14 |
15 | var controller = new StreamController.broadcast();
16 | var stream = controller.stream;
17 |
18 | var i = 0;
19 | newValue.onClick.listen((_) => controller.add(i++));
20 | stream.listen((x) => log("input stream", x));
21 |
22 | start.onClick.listen((_) {
23 | var seconds = int.parse(input.value);
24 | StreamExt.timeOut(stream, new Duration(seconds : seconds))
25 | .listen((x) => log("output stream", x),
26 | onError : (err) => log("output stream", err),
27 | onDone : () => log("output stream", "done"));
28 |
29 | log("output stream", "subscribed");
30 | start.disabled = true;
31 | });
32 | }
--------------------------------------------------------------------------------
/example/timeOut/timeOut_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - TimeOut
6 |
7 |
8 |
9 |
10 | TimeOut Demo
11 | Use the StreamExt.timeOut function to terminate the stream after the specified number of seconds between values elapsed.
12 |
13 |
14 | Time out stream after
seconds gap between values.
15 |
16 |
New Value
17 |
18 |
Start
19 |
20 | Output :
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/timeOutAt/timeOutAt_example.dart:
--------------------------------------------------------------------------------
1 | library timeOutAt_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | main() {
8 | var newValue = query("#btn_event");
9 | var start = query("#btn_start");
10 | var input = query("#input");
11 | var output = query("#output");
12 |
13 | log(prefix, value) => output.children.add(new DivElement()..text = "${new DateTime.now()} : $prefix - $value");
14 |
15 | var controller = new StreamController.broadcast();
16 | var stream = controller.stream;
17 |
18 | var i = 0;
19 | newValue.onClick.listen((_) => controller.add(i++));
20 | stream.listen((x) => log("input stream", x));
21 |
22 | start.onClick.listen((_) {
23 | try {
24 | var dueTime = DateTime.parse(input.value);
25 | StreamExt.timeOutAt(stream, dueTime)
26 | .listen((x) => log("output stream", x),
27 | onError : (err) => log("output stream", err),
28 | onDone : () => log("output stream", "done"));
29 |
30 | log("output stream", "subscribed");
31 | start.disabled = true;
32 | } catch (ex) {
33 | log("incorrect date format", "expect date in yyyy-MM-dd HH:mm:ss");
34 | }
35 | });
36 | }
--------------------------------------------------------------------------------
/example/timeOutAt/timeOutAt_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - TimeOutAt
6 |
7 |
8 |
9 |
10 | TimeOutAt Demo
11 | Use the StreamExt.timeOutAt function to terminate the stream at the specified due time.
12 |
13 |
14 | Time out stream at
.
15 |
16 |
New Value
17 |
18 |
Start
19 |
20 | Output :
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/window/window_example.dart:
--------------------------------------------------------------------------------
1 | library window_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | void main() {
8 | var btn1 = query("#btn_1");
9 | var btnErr1 = query("#btn_err_1");
10 | var btnDone1 = query("#btn_done_1");
11 |
12 | var output = query("#output");
13 |
14 | var contrl = new StreamController.broadcast();
15 | var input = contrl.stream;
16 |
17 | var windows = StreamExt.window(input, 3);
18 |
19 | log(msg) => output.children.add(new DivElement()..text = msg);
20 |
21 | StreamExt.log(input, "input", log);
22 | StreamExt.log(windows, "window", log);
23 |
24 | var idx = 0;
25 | btn1.onClick.listen((_) => contrl.add(idx++));
26 | btnErr1.onClick.listen((_) => contrl.addError("new error"));
27 | btnDone1.onClick.listen((_) => contrl.close());
28 | }
--------------------------------------------------------------------------------
/example/window/window_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Window
6 |
7 |
8 |
9 |
10 | Window Demo
11 |
12 | Use the StreamExt.window function to group input elements into lists of 3.
13 |
14 | Input : Event Error Done
15 |
16 |
17 | Output :
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/zip/zip_example.dart:
--------------------------------------------------------------------------------
1 | library zip_example;
2 |
3 | import 'dart:html';
4 | import 'dart:async';
5 | import 'dart:math';
6 | import 'package:stream_ext/stream_ext.dart';
7 |
8 | void main() {
9 | var container = query('#container');
10 | var box = query('#box');
11 |
12 | Stream mouseDown = box.onMouseDown;
13 | Stream mouseUp = document.onMouseUp;
14 | Stream mouseMove = document.onMouseMove;
15 |
16 | bool isDragging = false;
17 | mouseDown.listen((_) => isDragging = true);
18 | mouseUp.listen((_) => isDragging = false);
19 |
20 | var mouseDrags =
21 | StreamExt
22 | .zip(mouseMove,
23 | mouseMove.skip(1),
24 | (MouseEvent left, MouseEvent right) => new MouseMove(right.screen.x - left.screen.x, right.screen.y - left.screen.y))
25 | .where((_) => isDragging);
26 |
27 | var minOffsetLeft = box.offsetLeft;
28 | var maxOffsetLeft = box.offsetLeft + container.clientWidth - box.clientWidth;
29 | var minOffsetTop = box.offsetTop;
30 | var maxOffsetTop = box.offsetTop + container.clientHeight - box.clientHeight;
31 |
32 | mouseDrags.listen((MouseMove move) {
33 | var offsetLeft = min(max(minOffsetLeft, box.offsetLeft + move.xChange), maxOffsetLeft);
34 | var offsetTop = min(max(minOffsetTop, box.offsetTop + move.yChange), maxOffsetTop);
35 |
36 | box.style.left = "${offsetLeft}px";
37 | box.style.top = "${offsetTop}px";
38 | });
39 | }
40 |
41 | class MouseMove {
42 | int xChange;
43 | int yChange;
44 |
45 | MouseMove(this.xChange, this.yChange);
46 | }
--------------------------------------------------------------------------------
/example/zip/zip_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Demo - Zip
6 |
7 |
8 |
9 |
10 | Zip demo
11 | Using the StreamExt.zip function to create drag-and-drop effect.
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lib/stream_ext.dart:
--------------------------------------------------------------------------------
1 | library stream_ext;
2 |
3 | import 'dart:async';
4 |
5 | part "timeout_error.dart";
6 | part "tuple.dart";
7 |
8 | class StreamExt {
9 | static _defaultArg (x, defaultVal) => x == null ? defaultVal : x;
10 |
11 | static _identity(x) => x; // the identity function
12 |
13 | static _getOnErrorHandler(StreamController controller, closeOnError) {
14 | return closeOnError
15 | ? (err) {
16 | if (!controller.isClosed) {
17 | controller.addError(err);
18 | controller.close();
19 | }
20 | }
21 | : (err) {
22 | if (!controller.isClosed) {
23 | controller.addError(err);
24 | }
25 | };
26 | }
27 |
28 | static _tryAddError(StreamController controller, error) {
29 | if (!controller.isClosed) controller.addError(error);
30 | }
31 |
32 | static _tryClose(StreamController controller) {
33 | if (!controller.isClosed) controller.close();
34 | }
35 |
36 | static _tryAdd(StreamController controller, event) {
37 | if (!controller.isClosed) controller.add(event);
38 | }
39 |
40 | static _tryRun(void delegate(), void onError(err)) {
41 | try {
42 | delegate();
43 | }
44 | catch (ex) {
45 | onError(ex);
46 | }
47 | }
48 |
49 | /**
50 | * Propagates values from the stream that reacts first with a value.
51 | *
52 | * This method will ignore any errors received from either stream until the first value is received. The stream which reacts first with
53 | * a value will have its values and errors propagated through the output stream.
54 | *
55 | * The output stream will complete if:
56 | *
57 | * * neither stream produced a value before completing
58 | * * the propagated stream has completed
59 | * * [closeOnError] flag is set to true and an error is received in the propagated stream
60 | */
61 | static Stream amb(Stream stream1, Stream stream2, { bool closeOnError : false, bool sync : false }) {
62 | var controller = new StreamController.broadcast(sync : sync);
63 | var onError = _getOnErrorHandler(controller, closeOnError);
64 |
65 | StreamSubscription subscription1, subscription2;
66 | Completer completer1 = new Completer(), completer2 = new Completer();
67 | var started = false;
68 |
69 | void tryStart (StreamSubscription subscription, Completer completer,
70 | StreamSubscription otherSubscription, Completer otherCompleter,
71 | value) {
72 | if (!started) {
73 | started = true;
74 | controller.add(value);
75 |
76 | // update the handlers to propagate values and errors on the stream
77 | subscription.onData((x) => _tryAdd(controller, x));
78 | subscription.onError(onError);
79 | subscription.onDone(() {
80 | if (!completer.isCompleted) completer.complete();
81 | _tryClose(controller);
82 | });
83 |
84 | // cancel the subscription to the other unused stream and complete its completer
85 | otherSubscription.cancel();
86 | if (!otherCompleter.isCompleted) otherCompleter.complete();
87 | }
88 | }
89 |
90 | subscription1 = stream1.listen((x) => tryStart(subscription1, completer1, subscription2, completer2, x),
91 | onError : (_) { }, // surpress errors before value
92 | onDone : () => completer1.complete());
93 | subscription2 = stream2.listen((x) => tryStart(subscription2, completer2, subscription1, completer1, x),
94 | onError : (_) { }, // surpress errors before value
95 | onDone : () => completer2.complete());
96 |
97 | // catch-all in case neither stream produced a value before completing
98 | Future.wait([ completer1.future, completer2.future ])
99 | .then((_) => _tryClose(controller));
100 |
101 | return controller.stream;
102 | }
103 |
104 | /**
105 | * Returns the average of the values as a [Future] which completes when the input stream is done.
106 | *
107 | * This method uses the supplied [map] function to convert each input value into a [num].
108 | * If a [map] function is not specified then the identity function is used.
109 | *
110 | * If [closeOnError] flag is set to true, then any error in the [map] function or from the input stream will complete the [Future] with the error.
111 | * Otherwise, any errors will be swallowed and excluded from the final average.
112 | */
113 | static Future average(Stream input, { num map (dynamic elem), bool closeOnError : false, bool sync : false }) {
114 | if (map == null) {
115 | map = _identity;
116 | }
117 |
118 | var sum = 0;
119 | var count = 0;
120 | var completer = new Completer();
121 | var onError = closeOnError ? (err) => completer.completeError(err) : (_) {};
122 |
123 | void handleNewValue(x) => _tryRun(() {
124 | var newVal = map(x);
125 | sum += newVal;
126 | count++;
127 | }, onError);
128 |
129 | input.listen(handleNewValue,
130 | onError : onError,
131 | onDone : () {
132 | if (!completer.isCompleted) completer.complete(sum / count);
133 | });
134 |
135 | return completer.future;
136 | }
137 |
138 | /**
139 | * Creates a new stream which buffers values from the input stream produced within the specified [duration] and
140 | * return the buffered values as a list.
141 | *
142 | * The buffered stream will complete if:
143 | *
144 | * * the input stream has completed and any buffered values have been pushed
145 | * * [closeOnError] flag is set to true and an error is received
146 | */
147 | static Stream buffer(Stream input, Duration duration, { bool closeOnError : false, bool sync : false }) {
148 | var controller = new StreamController.broadcast(sync : sync);
149 | var onError = _getOnErrorHandler(controller, closeOnError);
150 |
151 | var buffer = new List();
152 | void pushBuffer() {
153 | if (buffer.length > 0) {
154 | _tryAdd(controller, buffer.toList()); // add a clone instead of the buffer list
155 | buffer.clear();
156 | }
157 | }
158 |
159 | var timer = new Timer.periodic(duration, (_) => pushBuffer());
160 |
161 | input.listen(buffer.add,
162 | onError : onError,
163 | onDone : () {
164 | pushBuffer();
165 | _tryClose(controller);
166 | if (timer.isActive) {
167 | timer.cancel();
168 | }
169 | });
170 |
171 | return controller.stream;
172 | }
173 |
174 | /**
175 | * Merges two streams into one by using the [selector] function to generate new a new value whenever one of the input streams produces a new value.
176 | *
177 | * The merged stream will complete if:
178 | *
179 | * * both input streams have completed
180 | * * [closeOnError] flag is set to true and an error is received
181 | */
182 | static Stream combineLatest(Stream stream1, Stream stream2, dynamic selector(dynamic item1, dynamic item2), { bool closeOnError : false, bool sync : false }) {
183 | var controller = new StreamController.broadcast(sync : sync);
184 | var completer1 = new Completer();
185 | var completer2 = new Completer();
186 | var onError = _getOnErrorHandler(controller, closeOnError);
187 |
188 | // current latest items on each stream
189 | var item1;
190 | var item2;
191 |
192 | void handleNewValue() {
193 | if (item1 != null && item2 != null) {
194 | _tryRun(() => _tryAdd(controller, selector(item1, item2)), onError);
195 | }
196 | }
197 |
198 | stream1.listen((x) {
199 | item1 = x;
200 | handleNewValue();
201 | },
202 | onError : onError,
203 | onDone : completer1.complete);
204 | stream2.listen((x) {
205 | item2 = x;
206 | handleNewValue();
207 | },
208 | onError : onError,
209 | onDone : completer2.complete);
210 |
211 | Future
212 | .wait([ completer1.future, completer2.future ])
213 | .then((_) => _tryClose(controller));
214 |
215 | return controller.stream;
216 | }
217 |
218 | /**
219 | * Concatenates the two input streams together, when the first stream completes the second stream is subscribed to. Until the first stream is done any
220 | * values and errors from the second stream is ignored.
221 | *
222 | * The concatenated stream will complete if:
223 | *
224 | * * both input streams have completed (if stream 2 completes before stream 1 then the concatenated stream is completed when stream 1 completes)
225 | * * [closeOnError] flag is set to true and an error is received in the active input stream (stream 1 until it completes, then stream 2)
226 | */
227 | static Stream concat(Stream stream1, Stream stream2, { bool closeOnError : false, bool sync : false }) {
228 | var controller = new StreamController.broadcast(sync : sync);
229 | var onError = _getOnErrorHandler(controller, closeOnError);
230 | var completer1 = new Completer();
231 | var completer2 = new Completer();
232 |
233 | // note : this looks somewhat convoluted and unnecessary, but the reason to subscribe to both input streams and use
234 | // another bool flag to indicate if we're handling value from stream 1 is to help us more gracefully handle the case
235 | // when the second stream completes before the first so that when the first stream completes it should actually
236 | // complete theoutput stream rather than attempt to subscribed to the second stream at that point
237 | void handleNewValue (x, isStream1) {
238 | if (isStream1 == !completer1.isCompleted) {
239 | _tryAdd(controller, x);
240 | }
241 | }
242 |
243 | stream1.listen((x) => handleNewValue(x, true),
244 | onError : onError,
245 | onDone : () {
246 | completer1.complete();
247 |
248 | // close the output stream eagerly if stream 2 had already completed by now
249 | if (completer2.isCompleted) _tryClose(controller);
250 | });
251 | stream2.listen((x) => handleNewValue(x, false),
252 | onError : (err) {
253 | if (completer1.isCompleted) onError(err);
254 | },
255 | onDone : () {
256 | completer2.complete();
257 |
258 | // close the output stream eagerly if stream 1 had already completed by now
259 | if (completer1.isCompleted) _tryClose(controller);
260 | });
261 |
262 | Future
263 | .wait([ completer1.future, completer2.future ])
264 | .then((_) => _tryClose(controller));
265 |
266 | return controller.stream;
267 | }
268 |
269 | /**
270 | * Creates a new stream whose values are sourced from the input stream but each delivered after the specified duration.
271 | *
272 | * The delayed stream will complete if:
273 | *
274 | * * the input stream has completed and the delayed complete message has been pushed
275 | * * [closeOnError] flag is set to true and an error is received
276 | */
277 | static Stream delay(Stream input, Duration duration, { bool closeOnError : false, bool sync : false }) {
278 | var controller = new StreamController.broadcast(sync : sync);
279 | var onError = _getOnErrorHandler(controller, closeOnError);
280 |
281 | delayCall(f, [ x ]) => x == null ? new Timer(duration, f) : new Timer(duration, () => f(x));
282 |
283 | input.listen((x) => delayCall(() => _tryAdd(controller, x)),
284 | onError : (ex) => delayCall(onError, ex),
285 | onDone : () => delayCall(_tryClose, controller));
286 |
287 | return controller.stream;
288 | }
289 |
290 | /**
291 | * Helper method to provide an easy way to log when new values and errors are received and when the stream is done.
292 | */
293 | static void log(Stream input, [ String prefix, void log(Object msg) ]) {
294 | prefix = _defaultArg(prefix, "");
295 | log = _defaultArg(log, print);
296 |
297 | input.listen((x) => log("($prefix) Value at ${new DateTime.now()} - $x"),
298 | onError : (err) => log("($prefix) Error at ${new DateTime.now()} - $err"),
299 | onDone : () => log("($prefix) Done at ${new DateTime.now()}"));
300 | }
301 |
302 | /**
303 | * Returns the maximum value as a [Future] when the input stream is done, as determined by the supplied [compare] function which compares the
304 | * current maximum value against any new value produced by the input stream.
305 | *
306 | * The [compare] function must act as a [Comparator].
307 | *
308 | * If [closeOnError] flag is set to true, then any error in the [compare] function will complete the [Future] with the error. Otherwise, any errors
309 | * will be swallowed and excluded from the final maximum.
310 | */
311 | static Future max(Stream input, int compare(dynamic a, dynamic b), { bool closeOnError : false, bool sync : false }) {
312 | var completer = new Completer();
313 | var onError = closeOnError ? (err) => completer.completeError(err) : (_) {};
314 |
315 | var maximum;
316 |
317 | void handleNewValue(x) => _tryRun(() {
318 | if (maximum == null || compare(maximum, x) < 0) {
319 | maximum = x;
320 | }
321 | }, onError);
322 |
323 | input.listen(handleNewValue,
324 | onError : onError,
325 | onDone : () {
326 | if (!completer.isCompleted) completer.complete(maximum);
327 | });
328 |
329 | return completer.future;
330 | }
331 |
332 | /**
333 | * Merges two stream into one, the merged stream will forward any values and errors received from the input streams.
334 | *
335 | * The merged stream will complete if:
336 | *
337 | * * both input streams have completed
338 | * * [closeOnError] flag is set to true and an error is received
339 | */
340 | static Stream merge(Stream stream1, Stream stream2, { bool closeOnError : false, bool sync : false }) {
341 | var controller = new StreamController.broadcast(sync : sync);
342 | var completer1 = new Completer();
343 | var completer2 = new Completer();
344 | var onError = _getOnErrorHandler(controller, closeOnError);
345 |
346 | stream1.listen((x) => _tryAdd(controller, x),
347 | onError : onError,
348 | onDone : completer1.complete);
349 | stream2.listen((x) => _tryAdd(controller, x),
350 | onError : onError,
351 | onDone : completer2.complete);
352 |
353 | Future
354 | .wait([ completer1.future, completer2.future ])
355 | .then((_) => _tryClose(controller));
356 |
357 | return controller.stream;
358 | }
359 |
360 | /**
361 | * Returns the minimum value as a [Future], as determined by the supplied [compare] function which compares the current minimum value against
362 | * any new value produced by the input [Stream].
363 | *
364 | * The [compare] function must act as a [Comparator].
365 | *
366 | * If [closeOnError] flag is set to true, then any error in the [compare] function will complete the [Future] with the error. Otherwise, any errors
367 | * will be swallowed and excluded from the final minimum.
368 | */
369 | static Future min(Stream input, int compare(dynamic a, dynamic b), { bool closeOnError : false, bool sync : false }) {
370 | var completer = new Completer();
371 | var onError = closeOnError ? (err) => completer.completeError(err) : (_) {};
372 |
373 | var minimum;
374 |
375 | void handleNewValue(x) => _tryRun(() {
376 | if (minimum == null || compare(minimum, x) > 0) {
377 | minimum = x;
378 | }
379 | }, onError);
380 |
381 | input.listen(handleNewValue,
382 | onError : onError,
383 | onDone : () {
384 | if (!completer.isCompleted) completer.complete(minimum);
385 | });
386 |
387 | return completer.future;
388 | }
389 |
390 | /**
391 | * Allows the continuation of a stream with another regardless of whether the first stream completes gracefully or due to an error.
392 | *
393 | * The output stream will complete if:
394 | *
395 | * * both input streams have completed (if stream 2 completes before stream 1 then the output stream is completed when stream 1 completes)
396 | * * [closeOnError] flag is set to true and an error is received in the continuation stream
397 | */
398 | static Stream onErrorResumeNext(Stream stream1, Stream stream2, { bool closeOnError : false, bool sync : false }) {
399 | var controller = new StreamController.broadcast(sync : sync);
400 | var onError = _getOnErrorHandler(controller, closeOnError);
401 | var completer1 = new Completer();
402 | var completer2 = new Completer();
403 |
404 | // note : this looks somewhat convoluted and unnecessary, but the reason to subscribe to both input streams and use
405 | // another bool flag to indicate if we're handling value from stream 1 is to help us more gracefully handle the case
406 | // when the second stream completes before the first so that when the first stream completes it should actually
407 | // complete theoutput stream rather than attempt to subscribed to the second stream at that point
408 | void handleNewValue (x, isStream1) {
409 | if (isStream1 == !completer1.isCompleted) {
410 | _tryAdd(controller, x);
411 | }
412 | }
413 |
414 | void resume () {
415 | if (!completer1.isCompleted) completer1.complete();
416 |
417 | // close the output stream eagerly if stream 2 had already completed by now
418 | if (completer2.isCompleted) _tryClose(controller);
419 | }
420 |
421 | stream1.listen((x) => handleNewValue(x, true),
422 | onError : (_) => resume(),
423 | onDone : resume);
424 | stream2.listen((x) => handleNewValue(x, false),
425 | onError : (err) {
426 | if (completer1.isCompleted) onError(err);
427 | },
428 | onDone : () {
429 | completer2.complete();
430 |
431 | // close the output stream eagerly if stream 1 had already completed by now
432 | if (completer1.isCompleted) _tryClose(controller);
433 | });
434 |
435 | Future
436 | .wait([ completer1.future, completer2.future ])
437 | .then((_) => _tryClose(controller));
438 |
439 | return controller.stream;
440 | }
441 |
442 | /**
443 | * Allows you to repeat the input stream for the specified number of times. If [repeatCount] is not set, then the input
444 | * stream will be repeated **indefinitely**.
445 | *
446 | * The `done` value is not delivered when the input stream completes, but only after the input stream has been repeated
447 | * the required number of times.
448 | *
449 | * The output stream will complete if:
450 | *
451 | * * the input stream has been repeated the required number of times
452 | * * the [closeOnError] flag is set to true and an error has been received
453 | */
454 | static Stream repeat(Stream input, { int repeatCount, bool closeOnError : false, bool sync : false }) {
455 | var controller = new StreamController.broadcast(sync : sync);
456 | var onError = _getOnErrorHandler(controller, closeOnError);
457 |
458 | var events = new List();
459 | var lastValue = new DateTime.now();
460 | var end;
461 |
462 | // record a received value for later use
463 | void record(x) {
464 | // record the time stamp that the value is received at before pushing the value to the output stream
465 | var now = new DateTime.now();
466 | var timestamp = now.difference(lastValue);
467 |
468 | _tryAdd(controller, x);
469 | events.add(new _Tuple(x, timestamp));
470 | lastValue = now;
471 | }
472 |
473 | // replys the stream inputs once
474 | Future replayOnce() {
475 | // no event was received, so create a future that completes after the duration of the original stream
476 | if (events.length == 0 && end != null) {
477 | return new Future.delayed(end.difference(lastValue));
478 | }
479 |
480 | return events.fold(
481 | new Future.sync((){}),
482 | (Future prev, next) =>
483 | prev.then((_) =>
484 | new Future.delayed(next.item2, () => _tryAdd(controller, next.item1))));
485 | }
486 |
487 | // recursively replay the stream until we've reached the required count
488 | void replayRec([ int count = 0 ]) {
489 | if (repeatCount != null && count >= repeatCount) {
490 | _tryClose(controller);
491 | } else {
492 | replayOnce()
493 | ..then((_) => replayRec(count + 1));
494 | }
495 | }
496 |
497 | input.listen(record,
498 | onError : onError,
499 | onDone : () {
500 | end = new DateTime.now();
501 | replayRec();
502 | });
503 |
504 | return controller.stream;
505 | }
506 |
507 | /**
508 | * Creates a new stream by taking the last value from the input stream for every specified [duration].
509 | *
510 | * The sampled stream will complete if:
511 | *
512 | * * the input stream has completed and any sampled message has been delivered
513 | * * [closeOnError] flag is set to true and an error is received
514 | */
515 | static Stream sample(Stream input, Duration duration, { bool closeOnError : false, bool sync : false }) {
516 | var controller = new StreamController.broadcast(sync : sync);
517 | var onError = _getOnErrorHandler(controller, closeOnError);
518 |
519 | var buffer;
520 | var timer = new Timer.periodic(duration, (_) {
521 | if (buffer != null) {
522 | _tryAdd(controller, buffer);
523 | buffer = null;
524 | }
525 | });
526 |
527 | input.listen((x) => buffer = x,
528 | onError : onError,
529 | onDone : () {
530 | timer.cancel();
531 | if (buffer != null) {
532 | _tryAdd(controller, buffer);
533 | }
534 | _tryClose(controller);
535 | });
536 |
537 | return controller.stream;
538 | }
539 |
540 | /**
541 | * Creates a new stream by applying an [accumulator] function over the values produced by the input stream and
542 | * returns each intermediate result with the specified seed and accumulator.
543 | *
544 | * The output stream will complete if:
545 | *
546 | * * the input stream has completed
547 | * * [closeOnError] flag is set to true and an error is received
548 | */
549 | static Stream scan(Stream input, dynamic seed, dynamic accumulator(dynamic acc, dynamic element), { bool closeOnError : false, bool sync : false }) {
550 | var controller = new StreamController.broadcast(sync : sync);
551 | var onError = _getOnErrorHandler(controller, closeOnError);
552 |
553 | var acc = seed;
554 |
555 | void handleNewValue(x) {
556 | _tryRun(() {
557 | acc = accumulator(acc, x);
558 | _tryAdd(controller, acc);
559 | }, onError);
560 | }
561 |
562 | input.listen(handleNewValue,
563 | onError : onError,
564 | onDone : () => _tryClose(controller));
565 |
566 | return controller.stream;
567 | }
568 |
569 | /**
570 | * Allows you to prefix values to a stream. The supplied values are delivered as soon as the listener is subscribed before
571 | * the listener receives values from the input stream.
572 | *
573 | * The output stream will complete if:
574 | *
575 | * * the input stream has completed
576 | * * [closeOnError] flag is set to true and an error is received
577 | */
578 | static Stream startWith(Stream input, Iterable values, { bool closeOnError : false, bool sync : false }) {
579 | var controller = new StreamController.broadcast(sync : sync);
580 |
581 | // until the initial values are sent, add the values received from the input stream into a buffer
582 | var buffer = new List<_Tuple>();
583 | var addValue = (x) => buffer.add(new _Tuple(x, false));
584 | var onError = (x) => buffer.add(new _Tuple(x, true));
585 |
586 | controller
587 | .addStream(new Stream.fromIterable(values))
588 | .then((_) {
589 | // now that initial values are sent, send the values we have in the buffer and any future values
590 | // from the input stream will be send directly through the controller
591 | addValue = (x) => _tryAdd(controller, x);
592 | onError = _getOnErrorHandler(controller, closeOnError);
593 |
594 | buffer.forEach((tuple) {
595 | var push = tuple.item2 ? onError : addValue;
596 | push(tuple.item1);
597 | });
598 | buffer.clear();
599 | });
600 |
601 | input.listen(addValue,
602 | onError : onError,
603 | onDone : () => _tryClose(controller));
604 |
605 | return controller.stream;
606 | }
607 |
608 | /**
609 | * Returns the sum of the values as a [Future], using the supplied [map] function to convert each input value into a [num].
610 | *
611 | * If a [map] function is not specified then the identity function is used.
612 | *
613 | * If [closeOnError] flag is set to true, then any error in the [map] function will complete the [Future] with the error. Otherwise, any errors
614 | * will be swallowed and excluded from the final sum.
615 | */
616 | static Future sum(Stream input, { num map (dynamic elem), bool closeOnError : false, bool sync : false }) {
617 | if (map == null) {
618 | map = _identity;
619 | }
620 |
621 | var sum = 0;
622 | var completer = new Completer();
623 | var onError = closeOnError ? (err) => completer.completeError(err) : (_) {};
624 |
625 | void handleNewValue(x) => _tryRun(() {
626 | var newVal = map(x);
627 | sum += newVal;
628 | }, onError);
629 |
630 | input.listen(handleNewValue,
631 | onError : onError,
632 | onDone : () {
633 | if (!completer.isCompleted) completer.complete(sum);
634 | });
635 |
636 | return completer.future;
637 | }
638 |
639 | /**
640 | * Transforms a stream of streams into a stream producing values only from the most recent stream.
641 | *
642 | * The output stream will complete if:
643 | *
644 | * * the input stream has completed and the last stream has completed
645 | * * [closeOnError] flag is set to true and an error is received in the active stream
646 | */
647 | static Stream switchFrom(Stream inputs, { bool closeOnError : false, bool sync : false }) {
648 | var controller = new StreamController.broadcast(sync : sync);
649 | var onError = _getOnErrorHandler(controller, closeOnError);
650 |
651 | StreamSubscription current;
652 | var inputFinished = false;
653 |
654 | void handleNewInput(Stream stream) {
655 | if (current != null) current.cancel();
656 |
657 | current = stream.listen((x) => _tryAdd(controller, x),
658 | onError : onError,
659 | onDone : () {
660 | current.cancel();
661 | current = null;
662 |
663 | if (inputFinished) _tryClose(controller);
664 | });
665 | }
666 |
667 | inputs.listen(handleNewInput,
668 | onDone : () {
669 | inputFinished = true;
670 | if (current == null) _tryClose(controller);
671 | });
672 |
673 | return controller.stream;
674 | }
675 |
676 | /**
677 | * Creates a new stream who stops the flow of values produced by the input stream until no new value has been produced by the input stream after the specified duration.
678 | *
679 | * The throttled stream will complete if:
680 | *
681 | * * the input stream has completed and any throttled message has been delivered
682 | * * [closeOnError] flag is set to true and an error is received
683 | */
684 | static Stream throttle(Stream input, Duration duration, { bool closeOnError : false, bool sync : false }) {
685 | var controller = new StreamController.broadcast(sync : sync);
686 | var onError = _getOnErrorHandler(controller, closeOnError);
687 |
688 | var isThrottling = false;
689 | var buffer;
690 | void handleNewValue(x) {
691 | // if this is the first item then push it
692 | if (!isThrottling) {
693 | _tryAdd(controller, x);
694 | isThrottling = true;
695 |
696 | new Timer(duration, () => isThrottling = false);
697 | } else {
698 | buffer = x;
699 | isThrottling = true;
700 |
701 | new Timer(duration, () {
702 | // when the timer callback is invoked after the timeout, check if there has been any
703 | // new items by comparing the last item against our captured closure 'x'
704 | // only push the event to the output stream if the captured event has not been
705 | // superceded by a subsequent event
706 | if (buffer == x) {
707 | _tryAdd(controller, x);
708 |
709 | // reset
710 | isThrottling = false;
711 | buffer = null;
712 | }
713 | });
714 | }
715 | }
716 |
717 | input.listen(handleNewValue,
718 | onError : onError,
719 | onDone : () {
720 | if (isThrottling && buffer != null) {
721 | _tryAdd(controller, buffer);
722 | }
723 | _tryClose(controller);
724 | });
725 |
726 | return controller.stream;
727 | }
728 |
729 | /**
730 | * Allows you to terminate a stream with a [TimeoutError] if the specified [duration] between values elapsed.
731 | *
732 | * The output stream will complete if:
733 | *
734 | * * the input stream has completed
735 | * * the specified [duration] between input values has elpased
736 | * * [closeOnError] flag is set to true and an error is received
737 | */
738 | static Stream timeOut(Stream input, Duration duration, { bool closeOnError : false, bool sync : false }) {
739 | var controller = new StreamController.broadcast(sync : sync);
740 | var onError = _getOnErrorHandler(controller, closeOnError);
741 |
742 | DateTime lastValueTimestamp;
743 | void startTimer() {
744 | new Timer(duration, () {
745 | if (lastValueTimestamp == null ||
746 | new DateTime.now().difference(lastValueTimestamp) >= duration) {
747 | _tryAddError(controller, new TimeoutError(duration));
748 | _tryClose(controller);
749 | }
750 | });
751 | }
752 |
753 | void handleNewValue(x) {
754 | _tryAdd(controller, x);
755 | lastValueTimestamp = new DateTime.now();
756 |
757 | startTimer();
758 | }
759 |
760 | startTimer();
761 |
762 | input.listen(handleNewValue,
763 | onError : onError,
764 | onDone : () => _tryClose(controller));
765 |
766 | return controller.stream;
767 | }
768 |
769 | /**
770 | * Allows you to terminate a stream with a [TimeoutError] at the specified [dueTime].
771 | *
772 | * The output stream will complete if:
773 | *
774 | * * the input stream has completed
775 | * * the specified [dueTime] has elapsed
776 | * * [closeOnError] flag is set to true and an error is received
777 | */
778 | static Stream timeOutAt(Stream input, DateTime dueTime, { bool closeOnError : false, bool sync : false }) {
779 | var controller = new StreamController.broadcast(sync : sync);
780 | var onError = _getOnErrorHandler(controller, closeOnError);
781 | var duration = dueTime.difference(new DateTime.now());
782 |
783 | new Timer(duration, () {
784 | _tryAddError(controller, new TimeoutError(duration));
785 | _tryClose(controller);
786 | });
787 |
788 | input.listen((x) => _tryAdd(controller, x),
789 | onError : onError,
790 | onDone : () => _tryClose(controller));
791 |
792 | return controller.stream;
793 | }
794 |
795 | /**
796 | * Projects each value from the input stream into consecutive non-overlapping windows.
797 | *
798 | * Each value produced by the output stream will contains a list of value up to the specified count.
799 | *
800 | * The output stream will complete if:
801 | *
802 | * * the input stream has completed and any buffered values have been pushed
803 | * * [closeOnError] flag is set to true and an error is received
804 | */
805 | static Stream window(Stream input, int count, { bool closeOnError : false, bool sync : false }) {
806 | var controller = new StreamController.broadcast(sync : sync);
807 | var onError = _getOnErrorHandler(controller, closeOnError);
808 |
809 | var buffer = new List();
810 | void pushBuffer() {
811 | if (buffer.length == count) {
812 | _tryAdd(controller, buffer.toList()); // add a clone instead of the buffer list
813 | buffer.clear();
814 | }
815 | }
816 |
817 | void handleNewValue(x) {
818 | buffer.add(x);
819 | pushBuffer();
820 | }
821 |
822 | input.listen(handleNewValue,
823 | onError : onError,
824 | onDone : () {
825 | if (buffer.length > 0) {
826 | _tryAdd(controller, buffer.toList()); // add a clone instead of the buffer list
827 | }
828 | _tryClose(controller);
829 | });
830 |
831 | return controller.stream;
832 | }
833 |
834 | /**
835 | * Zips two streams into one by combining their values in a pairwise fashion.
836 | *
837 | * The zipped stream will complete if:
838 | *
839 | * * either input stream has completed
840 | * * [closeOnError] flag is set to true and an error is received
841 | */
842 | static Stream zip(Stream stream1, Stream stream2, dynamic zipper(dynamic item1, dynamic item2), { bool closeOnError : false, bool sync : false }) {
843 | var controller = new StreamController.broadcast(sync : sync);
844 | var onError = _getOnErrorHandler(controller, closeOnError);
845 |
846 | // lists to track the data that had been buffered for the two streams
847 | var buffer1 = new List();
848 | var buffer2 = new List();
849 |
850 | // handler for new event being added to the list on the left
851 | void handleNewValue(List left, List right, dynamic newValue) {
852 | left.add(newValue);
853 |
854 | if (right.isEmpty) {
855 | return;
856 | }
857 |
858 | var item1 = buffer1[0];
859 | var item2 = buffer2[0];
860 |
861 | _tryRun(() {
862 | _tryAdd(controller, zipper(item1, item2));
863 |
864 | // only remove the items from the buffer after the zipper function succeeds
865 | buffer1.removeAt(0);
866 | buffer2.removeAt(0);
867 | }, onError);
868 | }
869 |
870 | stream1.listen((x) => handleNewValue(buffer1, buffer2, x),
871 | onError : onError,
872 | onDone : () => _tryClose(controller));
873 | stream2.listen((x) => handleNewValue(buffer2, buffer1, x),
874 | onError : onError,
875 | onDone : () => _tryClose(controller));
876 |
877 | return controller.stream;
878 | }
879 | }
--------------------------------------------------------------------------------
/lib/timeout_error.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext;
2 |
3 | class TimeoutError {
4 | Duration duration;
5 |
6 | TimeoutError(this.duration);
7 | }
--------------------------------------------------------------------------------
/lib/tuple.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext;
2 |
3 | // a private tuple helper class
4 | class _Tuple {
5 | T1 item1;
6 | T2 item2;
7 |
8 | _Tuple(this.item1, this.item2);
9 | }
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: stream_ext
2 | version: 0.4.0
3 | author: Yan Cui
4 | description: Extension functions for Stream class, adding functions such as merge, zip, delay, combineLatest to make working with streams even easier!
5 | homepage: http://theburningmonk.github.io/stream_ext/
6 | documentation: http://stream-ext.s3.amazonaws.com/doc/v0.3.0/index.html
7 | dev_dependencies:
8 | browser: any
9 | js: any
10 | unittest: any
11 |
--------------------------------------------------------------------------------
/test/extensions/amb_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class AmbTests {
4 | void start() {
5 | group('amb', () {
6 | _ambFirstStreamWins();
7 | _ambSecondStreamWins();
8 | _ambNoValues();
9 | _errorsBeforeValueAreIgnored();
10 | _ambNotCloseOnError();
11 | _ambCloseOnError();
12 | });
13 | }
14 |
15 | void _ambFirstStreamWins() {
16 | test('fst stream wins', () {
17 | var controller1 = new StreamController.broadcast(sync : true);
18 | var controller2 = new StreamController.broadcast(sync : true);
19 |
20 | var stream1 = controller1.stream;
21 | var stream2 = controller2.stream;
22 |
23 | var list = new List();
24 | var hasErr = false;
25 | var isDone = false;
26 | StreamExt.amb(stream1, stream2, sync : true)
27 | ..listen(list.add,
28 | onError : (_) => hasErr = true,
29 | onDone : () => isDone = true);
30 |
31 | controller1.add(0); // win!
32 | controller2.add(1); // ignored
33 | controller2.add(2); // ignored
34 | controller1.add(3);
35 |
36 | Future
37 | .wait([ controller1.close(), controller2.close() ])
38 | .then((_) {
39 | expect(list.length, equals(2), reason : "output stream should contain 2 values");
40 | expect(list, equals([ 0, 3 ]), reason : "output stream should contain values 0 and 3");
41 |
42 | expect(hasErr, equals(false), reason : "output stream should not have received error");
43 | expect(isDone, equals(true), reason : "output stream should be completed");
44 | });
45 | });
46 | }
47 |
48 | void _ambSecondStreamWins() {
49 | test('snd stream wins', () {
50 | var controller1 = new StreamController.broadcast(sync : true);
51 | var controller2 = new StreamController.broadcast(sync : true);
52 |
53 | var stream1 = controller1.stream;
54 | var stream2 = controller2.stream;
55 |
56 | var list = new List();
57 | var hasErr = false;
58 | var isDone = false;
59 | StreamExt.amb(stream1, stream2, sync : true)
60 | ..listen(list.add,
61 | onError : (_) => hasErr = true,
62 | onDone : () => isDone = true);
63 |
64 | controller2.add(0); // win!
65 | controller1.add(1); // ignored
66 | controller2.add(2);
67 | controller1.add(3); // ignored
68 |
69 | Future
70 | .wait([ controller1.close(), controller2.close() ])
71 | .then((_) {
72 | expect(list.length, equals(2), reason : "output stream should contain 2 values");
73 | expect(list, equals([ 0, 2 ]), reason : "output stream should contain values 0 and 2");
74 |
75 | expect(hasErr, equals(false), reason : "output stream should not have received error");
76 | expect(isDone, equals(true), reason : "output stream should be completed");
77 | });
78 | });
79 | }
80 |
81 | void _ambNoValues() {
82 | test('no values', () {
83 | var controller1 = new StreamController.broadcast(sync : true);
84 | var controller2 = new StreamController.broadcast(sync : true);
85 |
86 | var stream1 = controller1.stream;
87 | var stream2 = controller2.stream;
88 |
89 | var list = new List();
90 | var hasErr = false;
91 | var isDone = false;
92 | StreamExt.amb(stream1, stream2, sync : true)
93 | ..listen(list.add,
94 | onError : (_) => hasErr = true,
95 | onDone : () => isDone = true);
96 |
97 | Future
98 | .wait([ controller1.close(), controller2.close() ])
99 | .then((_) {
100 | expect(list.length, equals(0), reason : "output stream should contain no value");
101 |
102 | expect(hasErr, equals(false), reason : "output stream should not have received error");
103 | expect(isDone, equals(true), reason : "output stream should be completed");
104 | });
105 | });
106 | }
107 |
108 | void _errorsBeforeValueAreIgnored() {
109 | test('errors before value ignored', () {
110 | var controller1 = new StreamController.broadcast(sync : true);
111 | var controller2 = new StreamController.broadcast(sync : true);
112 |
113 | var stream1 = controller1.stream;
114 | var stream2 = controller2.stream;
115 |
116 | var list = new List();
117 | var hasErr = false;
118 | var isDone = false;
119 | StreamExt.amb(stream1, stream2, sync : true)
120 | ..listen(list.add,
121 | onError : (_) => hasErr = true,
122 | onDone : () => isDone = true);
123 |
124 | controller1.addError("failed"); // ignored
125 | controller2.addError("failed"); // ignored
126 | controller1.add(0); // win!
127 | controller2.add(1); // ignored
128 | controller2.add(2); // ignored
129 | controller1.add(3);
130 |
131 | Future
132 | .wait([ controller1.close(), controller2.close() ])
133 | .then((_) {
134 | expect(list.length, equals(2), reason : "output stream should contain 2 values");
135 | expect(list, equals([ 0, 3 ]), reason : "output stream should contain values 0 and 3");
136 |
137 | expect(hasErr, equals(false), reason : "output stream should not have received error");
138 | expect(isDone, equals(true), reason : "output stream should be completed");
139 | });
140 | });
141 | }
142 |
143 | void _ambNotCloseOnError() {
144 | test('not close on error', () {
145 | var controller1 = new StreamController.broadcast(sync : true);
146 | var controller2 = new StreamController.broadcast(sync : true);
147 |
148 | var stream1 = controller1.stream;
149 | var stream2 = controller2.stream;
150 |
151 | var list = new List();
152 | var errors = new List();
153 | var isDone = false;
154 | StreamExt.amb(stream1, stream2, sync : true)
155 | ..listen(list.add,
156 | onError : errors.add,
157 | onDone : () => isDone = true);
158 |
159 | controller1.add(0); // wins!
160 | controller2.addError("failed"); // ignored
161 | controller1.add(1);
162 | controller1.addError("failed2");
163 | controller1.add(2);
164 | controller2.add(3); // ignored
165 |
166 | Future
167 | .wait([ controller1.close(), controller2.close() ])
168 | .then((_) {
169 | expect(list.length, equals(3), reason : "output stream should have 3 values");
170 | expect(list, equals([ 0, 1, 2 ]), reason : "output stream should contain values 0, 1 and 2");
171 |
172 | expect(errors, equals([ "failed2" ]), reason : "output stream should have received error");
173 | expect(isDone, equals(true), reason : "output stream should be completed");
174 | });
175 | });
176 | }
177 |
178 | void _ambCloseOnError() {
179 | test('close on error', () {
180 | var controller1 = new StreamController.broadcast(sync : true);
181 | var controller2 = new StreamController.broadcast(sync : true);
182 |
183 | var stream1 = controller1.stream;
184 | var stream2 = controller2.stream;
185 |
186 | var list = new List();
187 | var errors = new List();
188 | var isDone = false;
189 | StreamExt.amb(stream1, stream2, closeOnError : true, sync : true)
190 | ..listen(list.add,
191 | onError : errors.add,
192 | onDone : () => isDone = true);
193 |
194 | controller1.addError("failed1"); // ignored
195 | controller2.addError("failed2"); // ignored
196 | controller1.add(0); // wins!
197 | controller2.addError("failed3"); // ignored
198 | controller1.add(1);
199 | controller1.addError("failed4");
200 | controller2.add(2);
201 |
202 | expect(list.length, equals(2), reason : "output stream should have 2 values before the error");
203 | expect(list, equals([ 0, 1 ]), reason : "output stream should contain the event values 0 and 1");
204 |
205 | expect(errors, equals([ "failed4" ]), reason : "output stream should have received error");
206 | expect(isDone, equals(true), reason : "output stream should be completed");
207 |
208 | controller1.close();
209 | controller2.close();
210 | });
211 | }
212 | }
--------------------------------------------------------------------------------
/test/extensions/average_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class AverageTests {
4 | void start() {
5 | group('average', () {
6 | _avgWithInts();
7 | _avgWithDoubles();
8 | _avgWithMixOfIntsAndDoubles();
9 | _avgNotCloseOnError();
10 | _avgCloseOnError();
11 | _avgWithUserErrorNotCloseOnError();
12 | _avgWithUserErrorCloseOnError();
13 | _avgWithMapper();
14 | });
15 | }
16 |
17 | void _avgWithInts() {
18 | test("with ints", () {
19 | var controller = new StreamController.broadcast(sync : true);
20 | var input = controller.stream;
21 |
22 | Future result = StreamExt.average(input, sync : true);
23 |
24 | controller.add(1);
25 | controller.add(2);
26 | controller.add(3);
27 | controller.add(4);
28 | controller.close()
29 | .then((_) => result)
30 | .then((avg) => expect(avg, equals(2.5), reason : "avg should be 2.5"));
31 | });
32 | }
33 |
34 | void _avgWithDoubles() {
35 | test("with doubles", () {
36 | var controller = new StreamController.broadcast(sync : true);
37 | var input = controller.stream;
38 |
39 | Future result = StreamExt.average(input, sync : true);
40 |
41 | controller.add(1.5);
42 | controller.add(2.5);
43 | controller.add(3.5);
44 | controller.add(4.9);
45 | controller.close()
46 | .then((_) => result)
47 | .then((avg) => expect(avg, equals(3.1), reason : "avg should be 3.1"));
48 | });
49 | }
50 |
51 | void _avgWithMixOfIntsAndDoubles() {
52 | test("with mix of ints and doubles", () {
53 | var controller = new StreamController.broadcast(sync : true);
54 | var input = controller.stream;
55 |
56 | Future result = StreamExt.average(input, sync : true);
57 |
58 | controller.add(1);
59 | controller.add(2.5);
60 | controller.add(3);
61 | controller.add(5.9);
62 | controller.close()
63 | .then((_) => result)
64 | .then((avg) => expect(avg, equals(3.1), reason : "avg should be 3.1"));
65 | });
66 | }
67 |
68 | void _avgNotCloseOnError() {
69 | test("not close on error", () {
70 | var controller = new StreamController.broadcast(sync : true);
71 | var input = controller.stream;
72 |
73 | Future result = StreamExt.average(input, sync : true);
74 |
75 | controller.add(1);
76 | controller.add(2);
77 | controller.addError("failed");
78 | controller.add(3);
79 | controller.add(4);
80 | controller.close()
81 | .then((_) => result)
82 | .then((avg) => expect(avg, equals(2.5), reason : "avg should be 2.5"));
83 | });
84 | }
85 |
86 | void _avgCloseOnError() {
87 | test("close on error", () {
88 | var controller = new StreamController.broadcast(sync : true);
89 | var input = controller.stream;
90 |
91 | var error;
92 | Future result = StreamExt.average(input, closeOnError : true, sync : true)
93 | .catchError((err) => error = err);
94 |
95 | controller.add(1);
96 | controller.add(2);
97 | controller.addError("failed");
98 | controller.add(3);
99 | controller.add(4);
100 | controller.close()
101 | .then((_) => result)
102 | .then((_) => expect(error, equals("failed"), reason : "avg should have failed"));
103 | });
104 | }
105 |
106 | void _avgWithUserErrorNotCloseOnError() {
107 | test("with user error not close on error", () {
108 | var controller = new StreamController.broadcast(sync : true);
109 | var input = controller.stream;
110 |
111 | Future result = StreamExt.average(input, sync : true);
112 |
113 | controller.add(2);
114 | controller.add(2.5);
115 | controller.add("3"); // this should cause error but ignored
116 | controller.add(4.5);
117 | controller.close()
118 | .then((_) => result)
119 | .then((avg) => expect(avg, equals(3), reason : "avg should be 3"));
120 | });
121 | }
122 |
123 | void _avgWithUserErrorCloseOnError() {
124 | test("with user error close on error", () {
125 | var controller = new StreamController.broadcast(sync : true);
126 | var input = controller.stream;
127 |
128 | var error;
129 | Future result = StreamExt.average(input, closeOnError : true, sync : true)
130 | .catchError((err) => error = err);
131 |
132 | controller.add(1);
133 | controller.add(2.5);
134 | controller.add("failed"); // this should cause error and terminate the avg
135 | controller.add(4.5);
136 | controller.close()
137 | .then((_) => result)
138 | .then((_) => expect(error is TypeError, equals(true), reason : "avg should have failed"));
139 | });
140 | }
141 |
142 | void _avgWithMapper() {
143 | test("with mapper", () {
144 | var controller = new StreamController.broadcast(sync : true);
145 | var input = controller.stream;
146 |
147 | Future result = StreamExt.average(input, map : (String str) => str.length, sync : true);
148 |
149 | controller.add("hello");
150 | controller.add(" ");
151 | controller.add("world");
152 | controller.add("!");
153 | controller.close()
154 | .then((_) => result)
155 | .then((avg) => expect(avg, equals(3), reason : "avg should be 3"));
156 | });
157 | }
158 | }
--------------------------------------------------------------------------------
/test/extensions/buffer_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class BufferTests {
4 | void start() {
5 | group('buffer', () {
6 | _bufferWithNoErrors();
7 | _bufferNotCloseOnError();
8 | _bufferCloseOnError();
9 | });
10 | }
11 |
12 | void _bufferWithNoErrors() {
13 | test("no errors", () {
14 | var controller = new StreamController.broadcast(sync : true);
15 | var input = controller.stream;
16 |
17 | var list = new List();
18 | var hasErr = false;
19 | var isDone = false;
20 | StreamExt.buffer(input, new Duration(milliseconds : 1), sync : true)
21 | ..listen(list.add,
22 | onError : (_) => hasErr = true,
23 | onDone : () => isDone = true);
24 |
25 | controller.add(0);
26 | controller.add(1);
27 | controller.add(2);
28 |
29 | new Timer(new Duration(milliseconds : 2), () {
30 | controller.add(3);
31 | controller.add(4);
32 | controller.close().then((_) {
33 | expect(list.length, equals(2), reason : "buffered stream should have two events");
34 | expect(list, equals([ [ 0, 1, 2 ], [ 3, 4 ] ]), reason : "buffered stream should contain lists [ 0, 1, 2 ] and [ 3, 4 ]");
35 |
36 | expect(hasErr, equals(false), reason : "buffered stream should not have received error");
37 | expect(isDone, equals(true), reason : "buffered stream should be completed");
38 | });
39 | });
40 | });
41 | }
42 |
43 | void _bufferNotCloseOnError() {
44 | test("not close on error", () {
45 | var controller = new StreamController.broadcast(sync : true);
46 | var input = controller.stream;
47 |
48 | var list = new List();
49 | var hasErr = false;
50 | var isDone = false;
51 | StreamExt.buffer(input, new Duration(milliseconds : 1), sync : true)
52 | ..listen(list.add,
53 | onError : (_) => hasErr = true,
54 | onDone : () => isDone = true);
55 |
56 | controller.add(0);
57 | controller.add(1);
58 | controller.addError("failed");
59 | controller.add(2);
60 |
61 | new Timer(new Duration(milliseconds : 2), () {
62 | controller.add(3);
63 | controller.add(4);
64 | controller.close().then((_) {
65 | expect(list.length, equals(2), reason : "buffered stream should have two events");
66 | expect(list, equals([ [ 0, 1, 2 ], [ 3, 4 ] ]), reason : "buffered stream should contain lists [ 0, 1, 2 ], [ 3, 4 ]");
67 |
68 | expect(hasErr, equals(true), reason : "buffered stream should have received error");
69 | expect(isDone, equals(true), reason : "buffered stream should be completed");
70 | });
71 | });
72 | });
73 | }
74 |
75 | void _bufferCloseOnError() {
76 | test("close on error", () {
77 | var controller = new StreamController.broadcast(sync : true);
78 | var input = controller.stream;
79 |
80 | var list = new List();
81 | var hasErr = false;
82 | var isDone = false;
83 | StreamExt.buffer(input, new Duration(milliseconds : 1), closeOnError : true, sync : true)
84 | ..listen(list.add,
85 | onError : (_) => hasErr = true,
86 | onDone : () => isDone = true);
87 |
88 | controller.add(0);
89 | controller.add(1);
90 | controller.add(2);
91 |
92 | new Timer(new Duration(milliseconds : 2), () {
93 | controller.add(3);
94 | controller.add(4);
95 | controller.addError("failed");
96 | controller.close().then((_) {
97 | expect(list.length, equals(1), reason : "buffered stream should have only one event before the error");
98 | expect(list, equals([ [ 0, 1, 2 ] ]), reason : "buffered stream should contain list [ 0, 1, 2 ]");
99 |
100 | expect(hasErr, equals(true), reason : "buffered stream should have received error");
101 | expect(isDone, equals(true), reason : "buffered stream should be completed");
102 | });
103 | });
104 | });
105 | }
106 | }
--------------------------------------------------------------------------------
/test/extensions/combineLatest_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class CombineLatestTests {
4 | void start() {
5 | group('combineLatest', () {
6 | _combineLatestWithNoErrors();
7 | _combineLatestNotCloseOnError();
8 | _combineLatestCloseOnError();
9 | _combineLatestWithUserNotCloseOnError();
10 | _combineLatestWithUserErrorCloseOnError();
11 | });
12 | }
13 |
14 | void _combineLatestWithNoErrors() {
15 | test('no errors', () {
16 | var controller1 = new StreamController.broadcast(sync : true);
17 | var controller2 = new StreamController.broadcast(sync : true);
18 |
19 | var stream1 = controller1.stream;
20 | var stream2 = controller2.stream;
21 |
22 | var list = new List();
23 | var hasErr = false;
24 | var isDone = false;
25 | StreamExt.combineLatest(stream1, stream2, (a, b) => "($a, $b)", sync : true)
26 | ..listen(list.add,
27 | onError : (_) => hasErr = true,
28 | onDone : () => isDone = true);
29 |
30 | controller1.add(0);
31 | controller1.add(1);
32 | controller2.add(2); // paired with 1
33 | controller2.add(3); // paired with 1
34 | controller1.add(4); // paired with 3
35 |
36 | var future1 = controller1.close();
37 | controller2.add(5); // paired with 4
38 | var future2 = controller2.close();
39 |
40 | Future
41 | .wait([ future1, future2 ])
42 | .then((_) {
43 | expect(list.length, equals(4), reason : "combined stream should have four combined events");
44 | expect(list, equals([ "(1, 2)", "(1, 3)", "(4, 3)", "(4, 5)" ]),
45 | reason : "combined stream should contain the event value (1, 2), (1, 3), (4, 3) and (4, 5)");
46 |
47 | expect(hasErr, equals(false), reason : "combined stream should not have received error");
48 | expect(isDone, equals(true), reason : "combined stream should be completed");
49 | });
50 | });
51 | }
52 |
53 | void _combineLatestNotCloseOnError() {
54 | test('not close on error', () {
55 | var controller1 = new StreamController.broadcast(sync : true);
56 | var controller2 = new StreamController.broadcast(sync : true);
57 |
58 | var stream1 = controller1.stream;
59 | var stream2 = controller2.stream;
60 |
61 | var list = new List();
62 | var hasErr = false;
63 | var isDone = false;
64 | StreamExt.combineLatest(stream1, stream2, (a, b) => "($a, $b)", sync : true)
65 | ..listen(list.add,
66 | onError : (_) => hasErr = true,
67 | onDone : () => isDone = true);
68 |
69 | controller1.add(0);
70 | controller2.addError("failed");
71 | controller1.add(1);
72 | controller2.add(2); // paired with 1
73 |
74 | controller1.addError("failed");
75 | var future1 = controller1.close();
76 | controller2.add(3); // paired with 1
77 | var future2 = controller2.close();
78 |
79 | Future
80 | .wait([ future1, future2 ])
81 | .then((_) {
82 | expect(list.length, equals(2), reason : "combined stream should have two combind events");
83 | expect(list, equals([ "(1, 2)", "(1, 3)" ]), reason : "combined stream should contain the event value (1, 2) and (1, 3)");
84 |
85 | expect(hasErr, equals(true), reason : "combined stream should have received error");
86 | expect(isDone, equals(true), reason : "combined stream should be completed");
87 | });
88 | });
89 | }
90 |
91 | void _combineLatestCloseOnError() {
92 | test('close on error', () {
93 | var controller1 = new StreamController.broadcast(sync : true);
94 | var controller2 = new StreamController.broadcast(sync : true);
95 |
96 | var stream1 = controller1.stream;
97 | var stream2 = controller2.stream;
98 |
99 | var list = new List();
100 | var hasErr = false;
101 | var isDone = false;
102 | StreamExt.combineLatest(stream1, stream2, (a, b) => "($a, $b)", closeOnError : true, sync : true)
103 | ..listen(list.add,
104 | onError : (_) => hasErr = true,
105 | onDone : () => isDone = true);
106 |
107 | controller1.add(0);
108 | controller2.add(1); // paired with 0
109 | controller2.add(2); // paired with 0
110 | controller2.addError("failed");
111 | controller1.add(3);
112 | controller2.add(4);
113 |
114 | new Timer(new Duration(milliseconds : 5), () {
115 | expect(list.length, equals(2), reason : "combined stream should have two events before the error");
116 | expect(list, equals([ "(0, 1)", "(0, 2)" ]), reason : "combined stream should contain the event value (0, 1) and (0, 2)");
117 |
118 | expect(hasErr, equals(true), reason : "combined stream should have received error");
119 | expect(isDone, equals(true), reason : "combined stream should be completed");
120 | });
121 | });
122 | }
123 |
124 | void _combineLatestWithUserNotCloseOnError() {
125 | test('with user error not close on error', () {
126 | var controller1 = new StreamController.broadcast(sync : true);
127 | var controller2 = new StreamController.broadcast(sync : true);
128 |
129 | var stream1 = controller1.stream;
130 | var stream2 = controller2.stream;
131 |
132 | var list = new List();
133 | var hasErr = false;
134 | var error;
135 | var isDone = false;
136 |
137 | // passes on a function that will except if given the wrong type of value
138 | StreamExt.combineLatest(stream1, stream2, (a, b) => a + b, sync : true)
139 | ..listen(list.add,
140 | onError : (err) {
141 | hasErr = true;
142 | error = err;
143 | },
144 | onDone : () => isDone = true);
145 |
146 | controller1.add(0);
147 | controller2.add(1); // paired with 0
148 | controller2.add(2); // paired with 0
149 | controller1.add("3"); // this should cause error
150 | controller1.add(3); // paired with 2
151 |
152 | Future
153 | .wait([ controller1.close(), controller2.close() ])
154 | .then((_) {
155 | expect(list.length, equals(3), reason : "combined stream should have three combind events");
156 | expect(list, equals([ 1, 2, 5 ]), reason : "combined stream should contain the event value 1, 2 and 5");
157 |
158 | expect(hasErr, equals(true), reason : "combined stream should have received error");
159 | expect(error is TypeError, equals(true), reason : "combined stream should have received a TypeError");
160 | expect(isDone, equals(true), reason : "combined stream should be completed");
161 | });
162 | });
163 | }
164 |
165 | void _combineLatestWithUserErrorCloseOnError() {
166 | test('with user error close on rrror', () {
167 | var controller1 = new StreamController.broadcast(sync : true);
168 | var controller2 = new StreamController.broadcast(sync : true);
169 |
170 | var stream1 = controller1.stream;
171 | var stream2 = controller2.stream;
172 |
173 | var list = new List();
174 | var hasErr = false;
175 | var error;
176 | var isDone = false;
177 |
178 | // passes on a function that will except if given the wrong type of value
179 | StreamExt.combineLatest(stream1, stream2, (a, b) => a + b, closeOnError : true, sync : true)
180 | ..listen(list.add,
181 | onError : (err) {
182 | hasErr = true;
183 | error = err;
184 | },
185 | onDone : () => isDone = true);
186 |
187 | controller1.add(0);
188 | controller2.add(1); // paired with 0
189 | controller2.add(2); // paired with 0
190 | controller1.add("3"); // this should cause error
191 | controller1.add(3);
192 |
193 | new Timer(new Duration(milliseconds : 5), () {
194 | expect(list.length, equals(2), reason : "combined stream should have two events before the error");
195 | expect(list, equals([ 1, 2 ]), reason : "combined stream should contain the event value 1 and 2");
196 |
197 | expect(hasErr, equals(true), reason : "combined stream should have received error");
198 | expect(error is TypeError, equals(true), reason : "combined stream should have received a TypeError");
199 | expect(isDone, equals(true), reason : "combined stream should be completed");
200 | });
201 | });
202 | }
203 | }
--------------------------------------------------------------------------------
/test/extensions/concat_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class ConcatTests {
4 | void start() {
5 | group('concat', () {
6 | _concatWithNoErrors();
7 | _concatStream2CompletesBeforeStream1();
8 | _concatNotCloseOnError();
9 | _concatCloseOnError();
10 | });
11 | }
12 |
13 | void _concatWithNoErrors() {
14 | test('no errors', () {
15 | var controller1 = new StreamController.broadcast(sync : true);
16 | var controller2 = new StreamController.broadcast(sync : true);
17 |
18 | var stream1 = controller1.stream;
19 | var stream2 = controller2.stream;
20 |
21 | var list = new List();
22 | var hasErr = false;
23 | var isDone = false;
24 | StreamExt.concat(stream1, stream2, sync : true)
25 | ..listen(list.add,
26 | onError : (_) => hasErr = true,
27 | onDone : () => isDone = true);
28 |
29 | controller1.add(0);
30 | controller2.add(1); // ignored
31 | controller2.add(2); // ignored
32 | controller1.add(3);
33 | controller1.close() // now should be yielding from stream2
34 | .then((_) {
35 | controller2.add(4);
36 | controller2.add(5);
37 | controller2.close()
38 | .then((_) {
39 | expect(list.length, equals(4), reason : "concatenated stream should contain 4 values");
40 | expect(list, equals([ 0, 3, 4, 5 ]), reason : "concatenated stream should contain values 0, 3, 4 and 5");
41 |
42 | expect(hasErr, equals(false), reason : "concatenated stream should not have received error");
43 | expect(isDone, equals(true), reason : "concatenated stream should be completed");
44 | });
45 | });
46 | });
47 | }
48 |
49 | void _concatStream2CompletesBeforeStream1() {
50 | test('stream 2 completes before stream 1', () {
51 | var controller1 = new StreamController.broadcast(sync : true);
52 | var controller2 = new StreamController.broadcast(sync : true);
53 |
54 | var stream1 = controller1.stream;
55 | var stream2 = controller2.stream;
56 |
57 | var list = new List();
58 | var hasErr = false;
59 | var isDone = false;
60 | StreamExt.concat(stream1, stream2, sync : true)
61 | ..listen(list.add,
62 | onError : (_) => hasErr = true,
63 | onDone : () => isDone = true);
64 |
65 | controller1.add(0);
66 | controller2.add(1); // ignored
67 | controller2.add(2); // ignored
68 | controller1.add(3);
69 | controller2.close()
70 | .then((_) {
71 | controller1.add(4);
72 | controller1.close() // since stream 2 is already done this should close the stream straight away
73 | .then((_) {
74 | expect(list.length, equals(3), reason : "concatenated stream should contain 3 values");
75 | expect(list, equals([ 0, 3, 4 ]), reason : "concatenated stream should contain values 0, 3 and 4");
76 |
77 | expect(hasErr, equals(false), reason : "concatenated stream should not have received error");
78 | expect(isDone, equals(true), reason : "concatenated stream should be completed");
79 | });
80 | });
81 | });
82 | }
83 |
84 | void _concatNotCloseOnError() {
85 | test('not close on error', () {
86 | var controller1 = new StreamController.broadcast(sync : true);
87 | var controller2 = new StreamController.broadcast(sync : true);
88 |
89 | var stream1 = controller1.stream;
90 | var stream2 = controller2.stream;
91 |
92 | var list = new List();
93 | var errors = new List();
94 | var isDone = false;
95 | StreamExt.concat(stream1, stream2, sync : true)
96 | ..listen(list.add,
97 | onError : errors.add,
98 | onDone : () => isDone = true);
99 |
100 | controller1.add(0);
101 | controller2.add(1); // ignored
102 | controller2.addError("failed1"); // ignored since we're still yield stream 1
103 | controller2.add(2); // ignored
104 | controller1.addError("failed2");
105 | controller1.add(3);
106 | controller1.close();
107 | controller2.add(4);
108 | controller2.addError("failed3");
109 | controller2.add(5);
110 | controller2.close()
111 | .then((_) {
112 | expect(list.length, equals(4), reason : "concatenated stream should have only 4 events");
113 | expect(list, equals([ 0, 3, 4, 5 ]), reason : "concatenated stream should contain values 0, 3, 4 and 5");
114 |
115 | expect(errors, equals([ "failed2", "failed3" ]), reason : "concatenated stream should have received error");
116 | expect(isDone, equals(true), reason : "concatenated stream should be completed");
117 | });
118 | });
119 | }
120 |
121 | void _concatCloseOnError() {
122 | test('close on error', () {
123 | var controller1 = new StreamController.broadcast(sync : true);
124 | var controller2 = new StreamController.broadcast(sync : true);
125 |
126 | var stream1 = controller1.stream;
127 | var stream2 = controller2.stream;
128 |
129 | var list = new List();
130 | var error;
131 | var isDone = false;
132 | StreamExt.concat(stream1, stream2, closeOnError : true, sync : true)
133 | ..listen(list.add,
134 | onError : (err) => error = err,
135 | onDone : () => isDone = true);
136 |
137 | controller1.add(0);
138 | controller2.addError("failed1"); // ignored
139 | controller1.add(1);
140 | controller1.close();
141 | controller2.addError("failed2");
142 | controller2.add(2);
143 |
144 | Future
145 | .wait([ controller1.close(), controller2.close() ])
146 | .then((_) {
147 | expect(list.length, equals(2), reason : "concatenated stream should have only two events before the error");
148 | expect(list[0], equals(0), reason : "concatenated stream should contain the event value 0");
149 |
150 | expect(error, equals("failed2"), reason : "concatenated stream should have received error");
151 | expect(isDone, equals(true), reason : "concatenated stream should be completed");
152 | });
153 | });
154 | }
155 | }
--------------------------------------------------------------------------------
/test/extensions/delay_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class DelayTests {
4 | void start() {
5 | group('delay', () {
6 | _delayWithNoErrors();
7 | _delayNotCloseOnError();
8 | _delayCloseOnError();
9 | });
10 | }
11 |
12 | void _delayWithNoErrors() {
13 | test("no errors", () {
14 | var data = new List.generate(3, (n) => n);
15 | var input = new Stream.fromIterable(data);
16 |
17 | var list = new List();
18 | var hasErr = false;
19 | var isDone = false;
20 | StreamExt.delay(input, new Duration(milliseconds : 1), sync : true)
21 | ..listen(list.add,
22 | onError : (_) => hasErr = true,
23 | onDone : () => isDone = true);
24 |
25 | // since events are delayed by 1 milliseconds, give it some buffer space and check after 5 ms if
26 | // all the delayed events have been processed
27 | Future future = new Future.delayed(new Duration(milliseconds : 10), () {
28 | expect(list.length, equals(3), reason : "delayed stream should have all three events");
29 |
30 | for (var i = 0; i <= 2; i++) {
31 | expect(list.where((n) => n == i).length, equals(1), reason : "delayed stream should contain $i");
32 | }
33 |
34 | expect(hasErr, equals(false), reason : "delayed stream should not have received error");
35 | expect(isDone, equals(true), reason : "delayed stream should be completed");
36 | });
37 |
38 | expect(future, completes);
39 | });
40 | }
41 |
42 | void _delayNotCloseOnError() {
43 | test('not close on error', () {
44 | var controller = new StreamController.broadcast(sync : true);
45 | var origin = controller.stream;
46 |
47 | var list = new List();
48 | var hasErr = false;
49 | var isDone = false;
50 | StreamExt.delay(origin, new Duration(milliseconds : 1), sync : true)
51 | ..listen(list.add,
52 | onError : (_) => hasErr = true,
53 | onDone : () => isDone = true);
54 |
55 | controller.add(0);
56 | controller.addError("failed");
57 | controller.add(1);
58 | controller.add(2);
59 | controller.close();
60 |
61 | Future future = new Future.delayed(new Duration(milliseconds : 2), () {
62 | expect(list.length, equals(3), reason : "delayed stream should have all three events");
63 |
64 | for (var i = 0; i <= 2; i++) {
65 | expect(list.where((n) => n == i).length, equals(1), reason : "delayed stream should contain $i");
66 | }
67 |
68 | expect(hasErr, equals(true), reason : "delayed stream should have received error");
69 | expect(isDone, equals(true), reason : "delayed stream should be completed");
70 | });
71 |
72 | expect(future, completes);
73 | });
74 | }
75 |
76 | void _delayCloseOnError() {
77 | test('close on error', () {
78 | var controller = new StreamController.broadcast(sync : true);
79 | var origin = controller.stream;
80 |
81 | var list = new List();
82 | var hasErr = false;
83 | var isDone = false;
84 | StreamExt.delay(origin, new Duration(milliseconds : 1), closeOnError : true, sync : true)
85 | ..listen(list.add,
86 | onError : (_) => hasErr = true,
87 | onDone : () => isDone = true);
88 |
89 | controller.add(0);
90 |
91 | // give sufficient time for the first message to be delivered before sending error
92 | new Timer(new Duration(milliseconds : 2), () {
93 | controller.addError("failed");
94 | controller.add(1);
95 | controller.add(2);
96 | });
97 |
98 | // closing the controllers happen asynchronously, so give it a few milliseconds for both to complete and trigger
99 | // the merged stream to also complete
100 | Future future = new Future.delayed(new Duration(milliseconds : 5), () {
101 | expect(list.length, equals(1), reason : "delayed stream should have only event before error");
102 | expect(list[0], equals(0), reason : "delayed stream should contain the event value 0");
103 |
104 | expect(hasErr, equals(true), reason : "delayed stream should have received error");
105 | expect(isDone, equals(true), reason : "delayed stream should be completed");
106 | });
107 |
108 | expect(future, completes);
109 | });
110 | }
111 | }
--------------------------------------------------------------------------------
/test/extensions/max_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class MaxTests {
4 | int compare(a, b) => a.compareTo(b);
5 |
6 | void start() {
7 | group('max', () {
8 | _maxWithInts();
9 | _maxWithDoubles();
10 | _maxWithMixOfIntsAndDoubles();
11 | _maxNotCloseOnError();
12 | _maxCloseOnError();
13 | _maxWithUserErrorNotCloseOnError();
14 | _maxWithUserErrorCloseOnError();
15 | _maxWithMapper();
16 | });
17 | }
18 |
19 | void _maxWithInts() {
20 | test("with ints", () {
21 | var controller = new StreamController.broadcast(sync : true);
22 | var input = controller.stream;
23 |
24 | Future result = StreamExt.max(input, compare, sync : true);
25 |
26 | controller.add(3);
27 | controller.add(2);
28 | controller.add(1);
29 | controller.add(4);
30 | controller.close()
31 | .then((_) => result)
32 | .then((max) => expect(max, equals(4), reason : "max should be 4"));
33 | });
34 | }
35 |
36 | void _maxWithDoubles() {
37 | test("with doubles", () {
38 | var controller = new StreamController.broadcast(sync : true);
39 | var input = controller.stream;
40 |
41 | Future result = StreamExt.max(input, compare, sync : true);
42 |
43 | controller.add(3.5);
44 | controller.add(2.5);
45 | controller.add(1.5);
46 | controller.add(4.5);
47 | controller.close()
48 | .then((_) => result)
49 | .then((max) => expect(max, equals(4.5), reason : "max should be 4.5"));
50 | });
51 | }
52 |
53 | void _maxWithMixOfIntsAndDoubles() {
54 | test("with mix of ints and doubles", () {
55 | var controller = new StreamController.broadcast(sync : true);
56 | var input = controller.stream;
57 |
58 | Future result = StreamExt.max(input, compare, sync : true);
59 |
60 | controller.add(3);
61 | controller.add(2.5);
62 | controller.add(1);
63 | controller.add(4.5);
64 | controller.close()
65 | .then((_) => result)
66 | .then((max) => expect(max, equals(4.5), reason : "max should be 4.5"));
67 | });
68 | }
69 |
70 | void _maxNotCloseOnError() {
71 | test("not close on error", () {
72 | var controller = new StreamController.broadcast(sync : true);
73 | var input = controller.stream;
74 |
75 | Future result = StreamExt.max(input, compare, sync : true);
76 |
77 | controller.add(3);
78 | controller.add(2);
79 | controller.addError("failed");
80 | controller.add(1);
81 | controller.add(4);
82 | controller.close()
83 | .then((_) => result)
84 | .then((max) => expect(max, equals(4), reason : "max should be 4"));
85 | });
86 | }
87 |
88 | void _maxCloseOnError() {
89 | test("close on error", () {
90 | var controller = new StreamController.broadcast(sync : true);
91 | var input = controller.stream;
92 |
93 | var error;
94 | Future result = StreamExt.max(input, compare, closeOnError : true, sync : true)
95 | .catchError((err) => error = err);
96 |
97 | controller.add(1);
98 | controller.add(2);
99 | controller.addError("failed");
100 | controller.add(3);
101 | controller.add(4);
102 | controller.close()
103 | .then((_) => result)
104 | .then((_) => expect(error, equals("failed"), reason : "max should have failed"));
105 | });
106 | }
107 |
108 | void _maxWithUserErrorNotCloseOnError() {
109 | test("with user error not close on error", () {
110 | var controller = new StreamController.broadcast(sync : true);
111 | var input = controller.stream;
112 |
113 | Future result = StreamExt.max(input, compare, sync : true);
114 |
115 | controller.add(3);
116 | controller.add(2.5);
117 | controller.add("3"); // this should cause error but ignored
118 | controller.add(4.5);
119 | controller.close()
120 | .then((_) => result)
121 | .then((max) => expect(max, equals(4.5), reason : "max should be 4.5"));
122 | });
123 | }
124 |
125 | void _maxWithUserErrorCloseOnError() {
126 | test("with user error close on error", () {
127 | var controller = new StreamController.broadcast(sync : true);
128 | var input = controller.stream;
129 |
130 | var error;
131 | Future result = StreamExt.max(input, compare, closeOnError : true, sync : true)
132 | .catchError((err) => error = err);
133 |
134 | controller.add(2.5);
135 | controller.add(1);
136 | controller.add("failed"); // this should cause error and terminate the sum
137 | controller.add(4.5);
138 | controller.close()
139 | .then((_) => result)
140 | .then((_) => expect(error is TypeError, equals(true), reason : "max should have failed"));
141 | });
142 | }
143 |
144 | void _maxWithMapper() {
145 | test("with mapper", () {
146 | var controller = new StreamController.broadcast(sync : true);
147 | var input = controller.stream;
148 |
149 | Future result = StreamExt.max(input, (String l, String r) => l.length.compareTo(r.length), sync : true);
150 |
151 | controller.add("hello");
152 | controller.add(" ");
153 | controller.add("world");
154 | controller.add("!");
155 | controller.close()
156 | .then((_) => result)
157 | .then((max) => expect(max, equals("hello"), reason : "max should be 'hello'"));
158 | });
159 | }
160 | }
--------------------------------------------------------------------------------
/test/extensions/merge_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class MergeTests {
4 | void start() {
5 | group('merge', () {
6 | _mergeWithNoErrors();
7 | _mergeNotCloseOnError();
8 | _mergeCloseOnError();
9 | });
10 | }
11 |
12 | void _mergeWithNoErrors() {
13 | test('no errors', () {
14 | var controller1 = new StreamController.broadcast(sync : true);
15 | var controller2 = new StreamController.broadcast(sync : true);
16 |
17 | var stream1 = controller1.stream;
18 | var stream2 = controller2.stream;
19 |
20 | var list = new List();
21 | var hasErr = false;
22 | var isDone = false;
23 | StreamExt.merge(stream1, stream2, sync : true)
24 | ..listen(list.add,
25 | onError : (_) => hasErr = true,
26 | onDone : () => isDone = true);
27 |
28 | controller1.add(0);
29 | controller2.add(1);
30 | controller2.add(2);
31 | controller1.add(3);
32 |
33 | Future
34 | .wait([ controller1.close(), controller2.close() ])
35 | .then((_) {
36 | expect(list.length, equals(4), reason : "merged stream should contain 4 values");
37 | expect(list, equals([ 0, 1, 2, 3 ]), reason : "merged stream should contain values 0, 1, 2 and 3");
38 |
39 | expect(hasErr, equals(false), reason : "merged stream should not have received error");
40 | expect(isDone, equals(true), reason : "merged stream should be completed");
41 | });
42 | });
43 | }
44 |
45 | void _mergeNotCloseOnError() {
46 | test('not close on error', () {
47 | var controller1 = new StreamController.broadcast(sync : true);
48 | var controller2 = new StreamController.broadcast(sync : true);
49 |
50 | var stream1 = controller1.stream;
51 | var stream2 = controller2.stream;
52 |
53 | var list = new List();
54 | var hasErr = false;
55 | var isDone = false;
56 | StreamExt.merge(stream1, stream2, sync : true)
57 | ..listen(list.add,
58 | onError : (_) => hasErr = true,
59 | onDone : () => isDone = true);
60 |
61 | controller1.add(0);
62 | controller2.addError("failed");
63 | controller1.add(1);
64 | controller2.add(2);
65 |
66 | Future
67 | .wait([ controller1.close(), controller2.close() ])
68 | .then((_) {
69 | expect(list.length, equals(3), reason : "merged stream should have all three events");
70 | expect(list, equals([ 0, 1, 2 ]), reason : "merged stream should contain values 0, 1 and 2");
71 |
72 | expect(hasErr, equals(true), reason : "merged stream should have received error");
73 | expect(isDone, equals(true), reason : "merged stream should be completed");
74 | });
75 | });
76 | }
77 |
78 | void _mergeCloseOnError() {
79 | test('close on error', () {
80 | var controller1 = new StreamController.broadcast(sync : true);
81 | var controller2 = new StreamController.broadcast(sync : true);
82 |
83 | var stream1 = controller1.stream;
84 | var stream2 = controller2.stream;
85 |
86 | var list = new List();
87 | var hasErr = false;
88 | var isDone = false;
89 | StreamExt.merge(stream1, stream2, closeOnError : true, sync : true)
90 | ..listen(list.add,
91 | onError : (_) => hasErr = true,
92 | onDone : () => isDone = true);
93 |
94 | controller1.add(0);
95 | controller2.addError("failed");
96 | controller1.add(1);
97 | controller2.add(2);
98 |
99 | Future
100 | .wait([ controller1.close(), controller2.close() ])
101 | .then((_) {
102 | expect(list.length, equals(1), reason : "merged stream should have only one event before the error");
103 | expect(list[0], equals(0), reason : "merged stream should contain the event value 0");
104 |
105 | expect(hasErr, equals(true), reason : "merged stream should have received error");
106 | expect(isDone, equals(true), reason : "merged stream should be completed");
107 | });
108 | });
109 | }
110 | }
--------------------------------------------------------------------------------
/test/extensions/min_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class MinTests {
4 | int compare(a, b) => a.compareTo(b);
5 |
6 | void start() {
7 | group('min', () {
8 | _minWithInts();
9 | _minWithDoubles();
10 | _minWithMixOfIntsAndDoubles();
11 | _minNotCloseOnError();
12 | _minCloseOnError();
13 | _minWithUserErrorNotCloseOnError();
14 | _minWithUserErrorCloseOnError();
15 | _minWithMapper();
16 | });
17 | }
18 |
19 | void _minWithInts() {
20 | test("with ints", () {
21 | var controller = new StreamController.broadcast(sync : true);
22 | var input = controller.stream;
23 |
24 | Future result = StreamExt.min(input, compare, sync : true);
25 |
26 | controller.add(3);
27 | controller.add(2);
28 | controller.add(1);
29 | controller.add(4);
30 | controller.close()
31 | .then((_) => result)
32 | .then((min) => expect(min, equals(1), reason : "min should be 1"));
33 | });
34 | }
35 |
36 | void _minWithDoubles() {
37 | test("with doubles", () {
38 | var controller = new StreamController.broadcast(sync : true);
39 | var input = controller.stream;
40 |
41 | Future result = StreamExt.min(input, compare, sync : true);
42 |
43 | controller.add(3.5);
44 | controller.add(2.5);
45 | controller.add(1.5);
46 | controller.add(4.5);
47 | controller.close()
48 | .then((_) => result)
49 | .then((min) => expect(min, equals(1.5), reason : "min should be 1.5"));
50 | });
51 | }
52 |
53 | void _minWithMixOfIntsAndDoubles() {
54 | test("with mix of ints and doubles", () {
55 | var controller = new StreamController.broadcast(sync : true);
56 | var input = controller.stream;
57 |
58 | Future result = StreamExt.min(input, compare, sync : true);
59 |
60 | controller.add(3);
61 | controller.add(2.5);
62 | controller.add(1);
63 | controller.add(4.5);
64 | controller.close()
65 | .then((_) => result)
66 | .then((min) => expect(min, equals(1), reason : "min should be 1"));
67 | });
68 | }
69 |
70 | void _minNotCloseOnError() {
71 | test("not close on error", () {
72 | var controller = new StreamController.broadcast(sync : true);
73 | var input = controller.stream;
74 |
75 | Future result = StreamExt.min(input, compare, sync : true);
76 |
77 | controller.add(3);
78 | controller.add(2);
79 | controller.addError("failed");
80 | controller.add(1);
81 | controller.add(4);
82 | controller.close()
83 | .then((_) => result)
84 | .then((min) => expect(min, equals(1), reason : "min should be 1"));
85 | });
86 | }
87 |
88 | void _minCloseOnError() {
89 | test("close on error", () {
90 | var controller = new StreamController.broadcast(sync : true);
91 | var input = controller.stream;
92 |
93 | var error;
94 | Future result = StreamExt.min(input, compare, closeOnError : true, sync : true)
95 | .catchError((err) => error = err);
96 |
97 | controller.add(1);
98 | controller.add(2);
99 | controller.addError("failed");
100 | controller.add(3);
101 | controller.add(4);
102 | controller.close()
103 | .then((_) => result)
104 | .then((_) => expect(error, equals("failed"), reason : "min should have failed"));
105 | });
106 | }
107 |
108 | void _minWithUserErrorNotCloseOnError() {
109 | test("with user error not close on error", () {
110 | var controller = new StreamController.broadcast(sync : true);
111 | var input = controller.stream;
112 |
113 | Future result = StreamExt.min(input, compare, sync : true);
114 |
115 | controller.add(3);
116 | controller.add(2.5);
117 | controller.add("3"); // this should cause error but ignored
118 | controller.add(4.5);
119 | controller.close()
120 | .then((_) => result)
121 | .then((min) => expect(min, equals(2.5), reason : "min should be 2.5"));
122 | });
123 | }
124 |
125 | void _minWithUserErrorCloseOnError() {
126 | test("with user error close on error", () {
127 | var controller = new StreamController.broadcast(sync : true);
128 | var input = controller.stream;
129 |
130 | var error;
131 | Future result = StreamExt.min(input, compare, closeOnError : true, sync : true)
132 | .catchError((err) => error = err);
133 |
134 | controller.add(2.5);
135 | controller.add(1);
136 | controller.add("failed"); // this should cause error and terminate the sum
137 | controller.add(4.5);
138 | controller.close()
139 | .then((_) => result)
140 | .then((_) => expect(error is TypeError, equals(true), reason : "min should have failed"));
141 | });
142 | }
143 |
144 | void _minWithMapper() {
145 | test("with mapper", () {
146 | var controller = new StreamController.broadcast(sync : true);
147 | var input = controller.stream;
148 |
149 | Future result = StreamExt.min(input, (String l, String r) => l.length.compareTo(r.length), sync : true);
150 |
151 | controller.add("hello");
152 | controller.add(" ");
153 | controller.add("world");
154 | controller.add("!");
155 | controller.close()
156 | .then((_) => result)
157 | .then((min) => expect(min, equals(" "), reason : "min should be ' '"));
158 | });
159 | }
160 | }
--------------------------------------------------------------------------------
/test/extensions/onErrorResumeNext_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class OnErrorResumeNextTests {
4 | void start() {
5 | group('onErrorResumeNext', () {
6 | _resumeWithNoErrors();
7 | _resumeStream2CompletesBeforeStream1();
8 | _resumeWithError();
9 | _resumeNotCloseOnError();
10 | _resumeCloseOnError();
11 | });
12 | }
13 |
14 | void _resumeWithNoErrors() {
15 | test('no errors', () {
16 | var controller1 = new StreamController.broadcast(sync : true);
17 | var controller2 = new StreamController.broadcast(sync : true);
18 |
19 | var stream1 = controller1.stream;
20 | var stream2 = controller2.stream;
21 |
22 | var list = new List();
23 | var hasErr = false;
24 | var isDone = false;
25 | StreamExt.onErrorResumeNext(stream1, stream2, sync : true)
26 | ..listen(list.add,
27 | onError : (_) => hasErr = true,
28 | onDone : () => isDone = true);
29 |
30 | controller1.add(0);
31 | controller2.add(1); // ignored
32 | controller2.add(2); // ignored
33 | controller1.add(3);
34 | controller1.close() // now should be yielding from stream2
35 | .then((_) {
36 | controller2.add(4);
37 | controller2.add(5);
38 | controller2.close()
39 | .then((_) {
40 | expect(list.length, equals(4), reason : "resumed stream should contain 4 values");
41 | expect(list, equals([ 0, 3, 4, 5 ]), reason : "resumed stream should contain values 0, 3, 4 and 5");
42 |
43 | expect(hasErr, equals(false), reason : "resumed stream should not have received error");
44 | expect(isDone, equals(true), reason : "resumed stream should be completed");
45 | });
46 | });
47 | });
48 | }
49 |
50 | void _resumeStream2CompletesBeforeStream1() {
51 | test('stream 2 completes before stream 1', () {
52 | var controller1 = new StreamController.broadcast(sync : true);
53 | var controller2 = new StreamController.broadcast(sync : true);
54 |
55 | var stream1 = controller1.stream;
56 | var stream2 = controller2.stream;
57 |
58 | var list = new List();
59 | var hasErr = false;
60 | var isDone = false;
61 | StreamExt.onErrorResumeNext(stream1, stream2, sync : true)
62 | ..listen(list.add,
63 | onError : (_) => hasErr = true,
64 | onDone : () => isDone = true);
65 |
66 | controller1.add(0);
67 | controller2.add(1); // ignored
68 | controller2.add(2); // ignored
69 | controller1.add(3);
70 | controller2.close()
71 | .then((_) {
72 | controller1.add(4);
73 | controller1.close() // since stream 2 is already done this should close the stream straight away
74 | .then((_) {
75 | expect(list.length, equals(3), reason : "resumed stream should contain 3 values");
76 | expect(list, equals([ 0, 3, 4 ]), reason : "resumed stream should contain values 0, 3 and 4");
77 |
78 | expect(hasErr, equals(false), reason : "resumed stream should not have received error");
79 | expect(isDone, equals(true), reason : "resumed stream should be completed");
80 | });
81 | });
82 | });
83 | }
84 |
85 | void _resumeWithError() {
86 | test('resume on error', () {
87 | var controller1 = new StreamController.broadcast(sync : true);
88 | var controller2 = new StreamController.broadcast(sync : true);
89 |
90 | var stream1 = controller1.stream;
91 | var stream2 = controller2.stream;
92 |
93 | var list = new List();
94 | var hasErr = false;
95 | var isDone = false;
96 | StreamExt.onErrorResumeNext(stream1, stream2, sync : true)
97 | ..listen(list.add,
98 | onError : (_) => hasErr = true,
99 | onDone : () => isDone = true);
100 |
101 | controller1.add(0);
102 | controller2.add(1); // ignored
103 | controller2.add(2); // ignored
104 | controller1.add(3);
105 | controller1.addError("failed"); // now should be yielding from stream2
106 | controller1.add(4); // ignored
107 | controller2.add(5);
108 |
109 | controller2.close()
110 | .then((_) {
111 | expect(list.length, equals(3), reason : "resumed stream should contain 3 values");
112 | expect(list, equals([ 0, 3, 5 ]), reason : "resumed stream should contain values 0, 3 and 5");
113 |
114 | expect(hasErr, equals(false), reason : "resumed stream should not have received error");
115 | expect(isDone, equals(true), reason : "resumed stream should be completed");
116 | })
117 | .then((_) => controller1.close());
118 | });
119 | }
120 |
121 | void _resumeNotCloseOnError() {
122 | test('not close on error', () {
123 | var controller1 = new StreamController.broadcast(sync : true);
124 | var controller2 = new StreamController.broadcast(sync : true);
125 |
126 | var stream1 = controller1.stream;
127 | var stream2 = controller2.stream;
128 |
129 | var list = new List();
130 | var errors = new List();
131 | var isDone = false;
132 | StreamExt.onErrorResumeNext(stream1, stream2, sync : true)
133 | ..listen(list.add,
134 | onError : errors.add,
135 | onDone : () => isDone = true);
136 |
137 | controller1.add(0);
138 | controller2.add(1); // ignored
139 | controller2.addError("failed1"); // ignored since we're still yield stream 1
140 | controller2.add(2); // ignored
141 | controller1.addError("failed2"); // now start yielding stream 2
142 | controller1.add(3); // ignored
143 | controller1.close();
144 | controller2.add(4);
145 | controller2.addError("failed3");
146 | controller2.add(5);
147 | controller2.close()
148 | .then((_) {
149 | expect(list.length, equals(3), reason : "resumed stream should have only 3 events");
150 | expect(list, equals([ 0, 4, 5 ]), reason : "resumed stream should contain values 0, 4 and 5");
151 |
152 | expect(errors, equals([ "failed3" ]), reason : "resumed stream should have received error");
153 | expect(isDone, equals(true), reason : "resumed stream should be completed");
154 | });
155 | });
156 | }
157 |
158 | void _resumeCloseOnError() {
159 | test('close on error', () {
160 | var controller1 = new StreamController.broadcast(sync : true);
161 | var controller2 = new StreamController.broadcast(sync : true);
162 |
163 | var stream1 = controller1.stream;
164 | var stream2 = controller2.stream;
165 |
166 | var list = new List();
167 | var error;
168 | var isDone = false;
169 | StreamExt.onErrorResumeNext(stream1, stream2, closeOnError : true, sync : true)
170 | ..listen(list.add,
171 | onError : (err) => error = err,
172 | onDone : () => isDone = true);
173 |
174 | controller1.add(0);
175 | controller2.addError("failed1"); // ignored
176 | controller1.add(1);
177 | controller1.close();
178 | controller2.addError("failed2"); // should close the output stream
179 | controller2.add(2);
180 |
181 | expect(list.length, equals(2), reason : "resumed stream should have only two events before the error");
182 | expect(list, equals([ 0, 1 ]), reason : "resumed stream should contain the values 0 and 1");
183 |
184 | expect(error, equals("failed2"), reason : "resumed stream should have received error");
185 | expect(isDone, equals(true), reason : "resumed stream should be completed");
186 |
187 | controller2.close();
188 | });
189 | }
190 | }
--------------------------------------------------------------------------------
/test/extensions/repeat_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class RepeatTests {
4 | void start() {
5 | group('repeat', () {
6 | _repeatWithNoErrors(0);
7 | _repeatWithNoErrors(1);
8 | _repeatWithNoErrors(2);
9 | _repeatNotCloseOnError(0);
10 | _repeatNotCloseOnError(1);
11 | _repeatNotCloseOnError(2);
12 | _repeatCloseOnError();
13 | });
14 | }
15 |
16 | void _repeatWithNoErrors(int repeatCount) {
17 | test('$repeatCount times no errors', () {
18 | var controller = new StreamController.broadcast(sync : true);
19 | var input = controller.stream;
20 |
21 | var list = new List();
22 | var hasErr = false;
23 | var isDone = false;
24 | StreamExt.repeat(input, repeatCount : repeatCount, sync : true)
25 | ..listen(list.add,
26 | onError : (_) => hasErr = true,
27 | onDone : () => isDone = true);
28 |
29 | controller.add(0);
30 | controller.add(1);
31 | controller.add(2);
32 |
33 | Future future =
34 | controller
35 | .close()
36 | .then((_) => new Future.delayed(new Duration(milliseconds : 2), () {
37 | expect(list.length, equals(3 * (1 + repeatCount)), reason : "repeated [$repeatCount] stream should contain ${3 * (1 + repeatCount)} values");
38 | expect(list,
39 | equals(new List.generate(1 + repeatCount, (i) => i).expand((_) => [ 0, 1, 2 ])),
40 | reason : "repeated stream should contain ${1 + repeatCount} sets of [ 0, 1, 2 ]");
41 |
42 | expect(hasErr, equals(false), reason : "repeated stream should not have received error");
43 | expect(isDone, equals(true), reason : "repeated stream should be completed");
44 | }));
45 | future.then((_) {
46 |
47 | });
48 |
49 | expect(future, completes);
50 | });
51 | }
52 |
53 | void _repeatNotCloseOnError(int repeatCount) {
54 | test('$repeatCount times not close on error', () {
55 | var controller = new StreamController.broadcast(sync : true);
56 | var input = controller.stream;
57 |
58 | var list = new List();
59 | var errors = new List();
60 | var isDone = false;
61 | StreamExt.repeat(input, repeatCount : repeatCount, sync : true)
62 | ..listen(list.add,
63 | onError : errors.add,
64 | onDone : () => isDone = true);
65 |
66 | controller.add(0);
67 | controller.add(1);
68 | controller.addError("failed");
69 | controller.add(2);
70 |
71 | Future future =
72 | controller
73 | .close()
74 | .then((_) => new Future.delayed(new Duration(milliseconds : 2), () {
75 | expect(list.length, equals(3 * (1 + repeatCount)), reason : "repeated [$repeatCount] stream should contain ${3 * (1 + repeatCount)} values");
76 | expect(list,
77 | equals(new List.generate(1 + repeatCount, (i) => i).expand((_) => [ 0, 1, 2 ])),
78 | reason : "repeated stream should contain ${1 + repeatCount} sets of [ 0, 1, 2 ]");
79 |
80 | expect(errors, equals([ "failed" ]), reason : "repeated stream should have received error only once (not repeated)");
81 | expect(isDone, equals(true), reason : "repeated stream should be completed");
82 | }));
83 |
84 | expect(future, completes);
85 | });
86 | }
87 |
88 | void _repeatCloseOnError() {
89 | test('close on error', () {
90 | var controller = new StreamController.broadcast(sync : true);
91 | var input = controller.stream;
92 |
93 | var list = new List();
94 | var error;
95 | var isDone = false;
96 | StreamExt.repeat(input, repeatCount : 1, closeOnError : true, sync : true)
97 | ..listen(list.add,
98 | onError : (err) => error = err,
99 | onDone : () => isDone = true);
100 |
101 | controller.add(0);
102 | controller.addError("failed");
103 | controller.add(1);
104 | controller.add(2);
105 |
106 | expect(list.length, equals(1), reason : "repeated stream should have only one event before the error");
107 | expect(list, equals([ 0 ] ), reason : "repeated stream should contain the value 0");
108 |
109 | expect(error, equals("failed"), reason : "repeated stream should have received error");
110 | expect(isDone, equals(true), reason : "repeated stream should be completed");
111 | });
112 | }
113 | }
--------------------------------------------------------------------------------
/test/extensions/sample_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class SampleTests {
4 | void start() {
5 | group('sample', () {
6 | _sampleWithNoErrors();
7 | _sampleIgnoresEmptyTimeWindows();
8 | _sampleNotCloseOnError();
9 | _sampleCloseOnError();
10 | });
11 | }
12 |
13 | void _sampleWithNoErrors() {
14 | test("no errors", () {
15 | var controller = new StreamController.broadcast(sync : true);
16 | var input = controller.stream;
17 |
18 | var list = new List();
19 | var hasErr = false;
20 | var isDone = false;
21 | StreamExt.sample(input, new Duration(milliseconds : 1), sync : true)
22 | ..listen(list.add,
23 | onError : (_) => hasErr = true,
24 | onDone : () => isDone = true);
25 |
26 | controller.add(0);
27 | controller.add(1);
28 | controller.add(2);
29 | controller.add(3);
30 |
31 | new Timer(new Duration(milliseconds : 1), () {
32 | controller.add(4);
33 | controller.close()
34 | .then((_) {
35 | expect(list.length, equals(2), reason : "sampled stream should have only two events");
36 | expect(list, equals([ 3, 4 ]), reason : "sampled stream should contain values 3 and 4");
37 |
38 | expect(hasErr, equals(false), reason : "sampled stream should not have received error");
39 | expect(isDone, equals(true), reason : "sampled stream should be completed");
40 | });
41 | });
42 | });
43 | }
44 |
45 | void _sampleIgnoresEmptyTimeWindows() {
46 | test("no errors", () {
47 | var controller = new StreamController.broadcast(sync : true);
48 | var input = controller.stream;
49 |
50 | var list = new List();
51 | var hasErr = false;
52 | var isDone = false;
53 | StreamExt.sample(input, new Duration(milliseconds : 1), sync : true)
54 | ..listen(list.add,
55 | onError : (_) => hasErr = true,
56 | onDone : () => isDone = true);
57 |
58 | controller.add(0);
59 | controller.add(1);
60 | controller.add(2);
61 | controller.add(3);
62 |
63 | new Timer(new Duration(milliseconds : 2), () {
64 | controller.close()
65 | .then((_) {
66 | expect(list.length, equals(1), reason : "sampled stream should have only one event");
67 | expect(list, equals([ 3 ]), reason : "sampled stream should contain value 3");
68 |
69 | expect(hasErr, equals(false), reason : "sampled stream should not have received error");
70 | expect(isDone, equals(true), reason : "sampled stream should be completed");
71 | });
72 | });
73 | });
74 | }
75 |
76 | void _sampleNotCloseOnError() {
77 | test("not close on error", () {
78 | var controller = new StreamController.broadcast(sync : true);
79 | var input = controller.stream;
80 |
81 | var list = new List();
82 | var error;
83 | var isDone = false;
84 | StreamExt.sample(input, new Duration(milliseconds : 1), sync : true)
85 | ..listen(list.add,
86 | onError : (err) => error = err,
87 | onDone : () => isDone = true);
88 |
89 | controller.add(0);
90 | controller.addError("failed");
91 | controller.add(1);
92 | controller.add(2);
93 | controller.add(3);
94 |
95 | new Timer(new Duration(milliseconds : 1), () {
96 | controller.add(4);
97 | controller.close()
98 | .then((_) {
99 | expect(list.length, equals(2), reason : "sampled stream should have only two events");
100 | expect(list, equals([ 3, 4 ]), reason : "sampled stream should contain values 3 and 4");
101 |
102 | expect(error, equals("failed"), reason : "sampled stream should have received error");
103 | expect(isDone, equals(true), reason : "sampled stream should be completed");
104 | });
105 | });
106 | });
107 | }
108 |
109 | void _sampleCloseOnError() {
110 | test("close on error", () {
111 | var controller = new StreamController.broadcast(sync : true);
112 | var input = controller.stream;
113 |
114 | var list = new List();
115 | var error;
116 | var isDone = false;
117 | StreamExt.sample(input, new Duration(milliseconds : 1), closeOnError : true, sync : true)
118 | ..listen(list.add,
119 | onError : (err) => error = err,
120 | onDone : () => isDone = true);
121 |
122 | controller.add(0);
123 | controller.add(1);
124 | controller.add(2);
125 | controller.add(3);
126 |
127 | new Timer(new Duration(milliseconds : 1), () {
128 | controller.add(4); // this should not be received since the error would interrupt the sample window
129 | controller.addError("failed");
130 | controller.add(5);
131 | controller.close();
132 | });
133 |
134 | new Timer(new Duration(milliseconds : 10), () {
135 | expect(list.length, equals(1), reason : "sampled stream should have only one event");
136 | expect(list, equals([ 3 ]), reason : "sampled stream should contain value 3");
137 |
138 | expect(error, equals("failed"), reason : "sampled stream should have received error");
139 | expect(isDone, equals(true), reason : "sampled stream should be completed");
140 | });
141 | });
142 | }
143 | }
--------------------------------------------------------------------------------
/test/extensions/scan_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class ScanTests {
4 | void start() {
5 | group('scan', () {
6 | _scanWithNoErrors();
7 | _scanNotCloseOnError();
8 | _scanCloseOnError();
9 | _scanWithUserErrorNotCloseOnError();
10 | _scanWithUserErrorCloseOnError();
11 | });
12 | }
13 |
14 | void _scanWithNoErrors() {
15 | test("no errors", () {
16 | var controller = new StreamController.broadcast(sync : true);
17 | var input = controller.stream;
18 |
19 | var list = new List();
20 | var hasErr = false;
21 | var isDone = false;
22 | StreamExt.scan(input, 0, (a, b) => a + b, sync : true)
23 | ..listen(list.add,
24 | onError : (_) => hasErr = true,
25 | onDone : () => isDone = true);
26 |
27 | controller.add(1);
28 | controller.add(2);
29 | controller.add(3);
30 | controller.add(4);
31 | controller.close().then((_) {
32 | expect(list.length, equals(4), reason : "output stream should have 4 events");
33 | expect(list, equals([ 1, 3, 6, 10 ]),
34 | reason : "output stream should contain values 1, 3, 6 and 10");
35 |
36 | expect(hasErr, equals(false), reason : "output stream should not have received error");
37 | expect(isDone, equals(true), reason : "output stream should be completed");
38 | });
39 | });
40 | }
41 |
42 | void _scanNotCloseOnError() {
43 | test("not close on error", () {
44 | var controller = new StreamController.broadcast(sync : true);
45 | var input = controller.stream;
46 |
47 | var list = new List();
48 | var hasErr = false;
49 | var isDone = false;
50 | StreamExt.scan(input, 0, (a, b) => a + b, sync : true)
51 | ..listen(list.add,
52 | onError : (_) => hasErr = true,
53 | onDone : () => isDone = true);
54 |
55 | controller.add(1);
56 | controller.add(2);
57 | controller.addError("failed");
58 | controller.add(3);
59 | controller.add(4);
60 | controller.close().then((_) {
61 | expect(list.length, equals(4), reason : "output stream should have 4 events");
62 | expect(list, equals([ 1, 3, 6, 10 ]),
63 | reason : "output stream should contain values 1, 3, 6 and 10");
64 |
65 | expect(hasErr, equals(true), reason : "output stream should have received error");
66 | expect(isDone, equals(true), reason : "output stream should be completed");
67 | });
68 | });
69 | }
70 |
71 | void _scanCloseOnError() {
72 | test("close on error", () {
73 | var controller = new StreamController.broadcast(sync : true);
74 | var input = controller.stream;
75 |
76 | var list = new List();
77 | var hasErr = false;
78 | var isDone = false;
79 | StreamExt.scan(input, 0, (a, b) => a + b, closeOnError : true, sync : true)
80 | ..listen(list.add,
81 | onError : (_) => hasErr = true,
82 | onDone : () => isDone = true);
83 |
84 | controller.add(1);
85 | controller.add(2);
86 | controller.addError("failed");
87 | controller.add(3);
88 | controller.add(4);
89 |
90 | new Timer(new Duration(milliseconds : 5), () {
91 | expect(list.length, equals(2), reason : "output stream should have only two events before the error");
92 | expect(list, equals([ 1, 3 ]),
93 | reason : "output stream should contain values 1 and 3");
94 |
95 | expect(hasErr, equals(true), reason : "output stream should have received error");
96 | expect(isDone, equals(true), reason : "output stream should be completed");
97 | });
98 | });
99 | }
100 |
101 | void _scanWithUserErrorNotCloseOnError() {
102 | test("with user error not close on error", () {
103 | var controller = new StreamController.broadcast(sync : true);
104 | var input = controller.stream;
105 |
106 | var list = new List();
107 | var hasErr = false;
108 | var error;
109 | var isDone = false;
110 | StreamExt.scan(input, 0, (a, b) => a + b, sync : true)
111 | ..listen(list.add,
112 | onError : (err) {
113 | hasErr = true;
114 | error = err;
115 | },
116 | onDone : () => isDone = true);
117 |
118 | controller.add(1);
119 | controller.add(2);
120 | controller.add("3"); // this should cause error
121 | controller.add(4);
122 | controller.close().then((_) {
123 | expect(list.length, equals(3), reason : "output stream should have three events");
124 | expect(list, equals([ 1, 3, 7 ]), reason : "output stream should contain values 1, 3 and 7");
125 |
126 | expect(hasErr, equals(true), reason : "output stream should have received error");
127 | expect(error is TypeError, equals(true), reason : "output stream should have received a TypeError");
128 | expect(isDone, equals(true), reason : "output stream should be completed");
129 | });
130 | });
131 | }
132 |
133 | void _scanWithUserErrorCloseOnError() {
134 | test("with user error close on error", () {
135 | var controller = new StreamController.broadcast(sync : true);
136 | var input = controller.stream;
137 |
138 | var list = new List();
139 | var hasErr = false;
140 | var error;
141 | var isDone = false;
142 | StreamExt.scan(input, 0, (a, b) => a + b, closeOnError : true, sync : true)
143 | ..listen(list.add,
144 | onError : (err) {
145 | hasErr = true;
146 | error = err;
147 | },
148 | onDone : () => isDone = true);
149 |
150 | controller.add(1);
151 | controller.add(2);
152 | controller.add("3"); // this should cause error
153 | controller.add(4);
154 |
155 | new Timer(new Duration(milliseconds : 5), () {
156 | expect(list.length, equals(2), reason : "output stream should have two events before the error");
157 | expect(list, equals([ 1, 3 ]), reason : "output stream should contain values 1 and 3");
158 |
159 | expect(hasErr, equals(true), reason : "output stream should have received error");
160 | expect(error is TypeError, equals(true), reason : "output stream should have received a TypeError");
161 | expect(isDone, equals(true), reason : "output stream should be completed");
162 | });
163 | });
164 | }
165 | }
--------------------------------------------------------------------------------
/test/extensions/startWith_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class StartWithTests {
4 | void start() {
5 | group('startWith', () {
6 | _startWithWithNoErrors();
7 | _startWithNotCloseOnError();
8 | _startWithCloseOnError();
9 | });
10 | }
11 |
12 | void _startWithWithNoErrors() {
13 | test('no errors', () {
14 | var controller = new StreamController.broadcast(sync : true);
15 | var input = controller.stream;
16 |
17 | var list = new List();
18 | var hasErr = false;
19 | var isDone = false;
20 | StreamExt.startWith(input, [ -3, -2, -1 ], sync : true)
21 | ..listen(list.add,
22 | onError : (_) => hasErr = true,
23 | onDone : () => isDone = true);
24 |
25 | controller.add(0);
26 | controller.add(1);
27 | controller.add(2);
28 |
29 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
30 | future.then((_) {
31 | expect(list.length, equals(6), reason : "output stream should contain 6 values");
32 | expect(list, equals([ -3, -2, -1, 0, 1, 2 ]),
33 | reason : "output stream should contain values from -3 to 2");
34 |
35 | expect(hasErr, equals(false), reason : "output stream should not have received error");
36 | expect(isDone, equals(true), reason : "output stream should be completed");
37 | });
38 |
39 | expect(future, completes);
40 | });
41 | }
42 |
43 | void _startWithNotCloseOnError() {
44 | test('not close on error', () {
45 | var controller = new StreamController.broadcast(sync : true);
46 | var input = controller.stream;
47 |
48 | var list = new List();
49 | var hasErr = false;
50 | var isDone = false;
51 | StreamExt.startWith(input, [ -3, -2, -1 ], sync : true)
52 | ..listen(list.add,
53 | onError : (_) => hasErr = true,
54 | onDone : () => isDone = true);
55 |
56 | controller.add(0);
57 | controller.add(1);
58 | controller.addError("failed");
59 | controller.add(2);
60 |
61 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
62 | future.then((_) {
63 | expect(list.length, equals(6), reason : "output stream should contain 6 values");
64 | expect(list, equals([ -3, -2, -1, 0, 1, 2 ]), reason : "output stream should contain values from -3 to 2");
65 |
66 | expect(hasErr, equals(true), reason : "output stream should have received error");
67 | expect(isDone, equals(true), reason : "output stream should be completed");
68 | });
69 |
70 | expect(future, completes);
71 | });
72 | }
73 |
74 | void _startWithCloseOnError() {
75 | test('close on error', () {
76 | var controller = new StreamController.broadcast(sync : true);
77 | var input = controller.stream;
78 |
79 | var list = new List();
80 | var hasErr = false;
81 | var isDone = false;
82 | StreamExt.startWith(input, [ -3, -2, -1 ], closeOnError : true, sync : true)
83 | ..listen(list.add,
84 | onError : (_) => hasErr = true,
85 | onDone : () => isDone = true);
86 |
87 | controller.add(0);
88 | controller.addError("failed");
89 | controller.add(1);
90 | controller.add(2);
91 |
92 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
93 | future.then((_) {
94 | expect(list.length, equals(4), reason : "output stream should have only four events before the error");
95 | expect(list, equals([ -3, -2, -1, 0 ]), reason : "output stream should contain the event value 0");
96 |
97 | expect(hasErr, equals(true), reason : "output stream should have received error");
98 | expect(isDone, equals(true), reason : "output stream should be completed");
99 | });
100 |
101 | expect(future, completes);
102 | });
103 | }
104 | }
--------------------------------------------------------------------------------
/test/extensions/sum_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class SumTests {
4 | void start() {
5 | group('sum', () {
6 | _sumWithInts();
7 | _sumWithDoubles();
8 | _sumWithMixOfIntsAndDoubles();
9 | _sumNotCloseOnError();
10 | _sumCloseOnError();
11 | _sumWithUserErrorNotCloseOnError();
12 | _sumWithUserErrorCloseOnError();
13 | _sumWithMapper();
14 | });
15 | }
16 |
17 | void _sumWithInts() {
18 | test("with ints", () {
19 | var controller = new StreamController.broadcast(sync : true);
20 | var input = controller.stream;
21 |
22 | Future result = StreamExt.sum(input, sync : true);
23 |
24 | controller.add(1);
25 | controller.add(2);
26 | controller.add(3);
27 | controller.add(4);
28 | controller.close()
29 | .then((_) => result)
30 | .then((sum) => expect(sum, equals(10), reason : "sum should be 10"));
31 | });
32 | }
33 |
34 | void _sumWithDoubles() {
35 | test("with doubles", () {
36 | var controller = new StreamController.broadcast(sync : true);
37 | var input = controller.stream;
38 |
39 | Future result = StreamExt.sum(input, sync : true);
40 |
41 | controller.add(1.5);
42 | controller.add(2.5);
43 | controller.add(3.5);
44 | controller.add(4.5);
45 | controller.close()
46 | .then((_) => result)
47 | .then((sum) => expect(sum, equals(12.0), reason : "sum should be 12.0"));
48 | });
49 | }
50 |
51 | void _sumWithMixOfIntsAndDoubles() {
52 | test("with mix of ints and doubles", () {
53 | var controller = new StreamController.broadcast(sync : true);
54 | var input = controller.stream;
55 |
56 | Future result = StreamExt.sum(input, sync : true);
57 |
58 | controller.add(1);
59 | controller.add(2.5);
60 | controller.add(3);
61 | controller.add(4.5);
62 | controller.close()
63 | .then((_) => result)
64 | .then((sum) => expect(sum, equals(11), reason : "sum should be 11"));
65 | });
66 | }
67 |
68 | void _sumNotCloseOnError() {
69 | test("not close on error", () {
70 | var controller = new StreamController.broadcast(sync : true);
71 | var input = controller.stream;
72 |
73 | Future result = StreamExt.sum(input, sync : true);
74 |
75 | controller.add(1);
76 | controller.add(2);
77 | controller.addError("failed");
78 | controller.add(3);
79 | controller.add(4);
80 | controller.close()
81 | .then((_) => result)
82 | .then((sum) => expect(sum, equals(10), reason : "sum should be 10"));
83 | });
84 | }
85 |
86 | void _sumCloseOnError() {
87 | test("close on error", () {
88 | var controller = new StreamController.broadcast(sync : true);
89 | var input = controller.stream;
90 |
91 | var error;
92 | Future result = StreamExt.sum(input, closeOnError : true, sync : true)
93 | .catchError((err) => error = err);
94 |
95 | controller.add(1);
96 | controller.add(2);
97 | controller.addError("failed");
98 | controller.add(3);
99 | controller.add(4);
100 | controller.close()
101 | .then((_) => result)
102 | .then((_) => expect(error, equals("failed"), reason : "sum should have failed"));
103 | });
104 | }
105 |
106 | void _sumWithUserErrorNotCloseOnError() {
107 | test("with user error not close on error", () {
108 | var controller = new StreamController.broadcast(sync : true);
109 | var input = controller.stream;
110 |
111 | Future result = StreamExt.sum(input, sync : true);
112 |
113 | controller.add(1);
114 | controller.add(2.5);
115 | controller.add("3"); // this should cause error but ignored
116 | controller.add(4.5);
117 | controller.close()
118 | .then((_) => result)
119 | .then((sum) => expect(sum, equals(8), reason : "sum should be 8"));
120 | });
121 | }
122 |
123 | void _sumWithUserErrorCloseOnError() {
124 | test("with user error close on error", () {
125 | var controller = new StreamController.broadcast(sync : true);
126 | var input = controller.stream;
127 |
128 | var error;
129 | Future result = StreamExt.sum(input, closeOnError : true, sync : true)
130 | .catchError((err) => error = err);
131 |
132 | controller.add(1);
133 | controller.add(2.5);
134 | controller.add("failed"); // this should cause error and terminate the sum
135 | controller.add(4.5);
136 | controller.close()
137 | .then((_) => result)
138 | .then((_) => expect(error is TypeError, equals(true), reason : "sum should have failed"));
139 | });
140 | }
141 |
142 | void _sumWithMapper() {
143 | test("with mapper", () {
144 | var controller = new StreamController.broadcast(sync : true);
145 | var input = controller.stream;
146 |
147 | Future result = StreamExt.sum(input, map : (String str) => str.length, sync : true);
148 |
149 | controller.add("hello");
150 | controller.add(" ");
151 | controller.add("world");
152 | controller.add("!");
153 | controller.close()
154 | .then((_) => result)
155 | .then((sum) => expect(sum, equals(12), reason : "sum should be 12"));
156 | });
157 | }
158 | }
--------------------------------------------------------------------------------
/test/extensions/switchFrom_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class SwitchFromTests {
4 | void start() {
5 | group('switchFrom', () {
6 | _switchStreamsWithValues();
7 | _swtichActiveStreamClosesFirst();
8 | _swtichNotCloseOnError();
9 | _swtichCloseOnError();
10 | });
11 | }
12 |
13 | void _switchStreamsWithValues() {
14 | test('all streams produced value', () {
15 | var controller1 = new StreamController.broadcast(sync : true);
16 | var controller2 = new StreamController.broadcast(sync : true);
17 | var controller = new StreamController.broadcast(sync : true);
18 |
19 | var stream1 = controller1.stream;
20 | var stream2 = controller2.stream;
21 | var input = controller.stream;
22 |
23 | var list = new List();
24 | var hasErr = false;
25 | var isDone = false;
26 | StreamExt.switchFrom(input, sync : true)
27 | ..listen(list.add,
28 | onError : (_) => hasErr = true,
29 | onDone : () => isDone = true);
30 |
31 | controller.add(stream1);
32 | controller1.add(0);
33 | controller1.add(1);
34 | controller.add(stream2);
35 | controller1.add(2); // ignore
36 | controller2.add(3);
37 | controller2.add(4);
38 |
39 | controller.close()
40 | .then((_) {
41 | controller2.add(5);
42 | return controller2.close();
43 | })
44 | .then((_) {
45 | expect(list.length, equals(5), reason : "output stream should contain 5 values");
46 | expect(list, equals([ 0, 1, 3, 4, 5 ]), reason : "output stream should contain values 0, 1, 3, 4 and 5");
47 |
48 | expect(hasErr, equals(false), reason : "output stream should not have received error");
49 | expect(isDone, equals(true), reason : "output stream should be completed");
50 | })
51 | .then((_) => controller1.close());
52 | });
53 | }
54 |
55 | void _swtichActiveStreamClosesFirst() {
56 | test('active stream closes first', () {
57 | var controller1 = new StreamController.broadcast(sync : true);
58 | var controller2 = new StreamController.broadcast(sync : true);
59 | var controller = new StreamController.broadcast(sync : true);
60 |
61 | var stream1 = controller1.stream;
62 | var stream2 = controller2.stream;
63 | var input = controller.stream;
64 |
65 | var list = new List();
66 | var hasErr = false;
67 | var isDone = false;
68 | StreamExt.switchFrom(input, sync : true)
69 | ..listen(list.add,
70 | onError : (_) => hasErr = true,
71 | onDone : () => isDone = true);
72 |
73 | controller.add(stream1);
74 | controller.add(stream2);
75 |
76 | controller2.close() // the active stream closes first but the input stream is still going
77 | .then((_) => expect(isDone, equals(false), reason : "output stream should be closed only after the input stream closes"))
78 | .then((_) => controller.close())
79 | .then((_) {
80 | expect(list.length, equals(0), reason : "output stream should contain no values");
81 |
82 | expect(hasErr, equals(false), reason : "output stream should not have received error");
83 | expect(isDone, equals(true), reason : "output stream should be completed");
84 | })
85 | .then((_) => controller1.close());
86 | });
87 | }
88 |
89 | void _swtichNotCloseOnError() {
90 | test('not close on error', () {
91 | var controller1 = new StreamController.broadcast(sync : true);
92 | var controller2 = new StreamController.broadcast(sync : true);
93 | var controller = new StreamController.broadcast(sync : true);
94 |
95 | var stream1 = controller1.stream;
96 | var stream2 = controller2.stream;
97 | var input = controller.stream;
98 |
99 | var list = new List();
100 | var errors = new List();
101 | var isDone = false;
102 | StreamExt.switchFrom(input, sync : true)
103 | ..listen(list.add,
104 | onError : errors.add,
105 | onDone : () => isDone = true);
106 |
107 | controller.add(stream1);
108 | controller1.add(0);
109 | controller1.addError("failed1");
110 | controller1.add(1);
111 | controller.add(stream2);
112 | controller2.add(2);
113 | controller1.addError("failed2"); // ignored
114 | controller2.addError("failed3");
115 | controller2.add(3);
116 |
117 | controller.close() // the input stream closes first but the active stream is still going
118 | .then((_) => expect(isDone, equals(false), reason : "output stream should be closed only after the active stream closes"))
119 | .then((_) => controller2.close())
120 | .then((_) {
121 | expect(list.length, equals(4), reason : "output stream should contain 4 values");
122 | expect(list, equals([ 0, 1, 2, 3 ]), reason : "output stream should contain values 0, 1, 2 and 3");
123 |
124 | expect(errors, equals([ "failed1", "failed3" ]), reason : "output stream should have received error");
125 | expect(isDone, equals(true), reason : "output stream should be completed");
126 | })
127 | .then((_) => controller1.close());
128 | });
129 | }
130 |
131 | void _swtichCloseOnError() {
132 | test('close on error', () {
133 | var controller1 = new StreamController.broadcast(sync : true);
134 | var controller2 = new StreamController.broadcast(sync : true);
135 | var controller = new StreamController.broadcast(sync : true);
136 |
137 | var stream1 = controller1.stream;
138 | var stream2 = controller2.stream;
139 | var input = controller.stream;
140 |
141 | var list = new List();
142 | var errors = new List();
143 | var isDone = false;
144 | StreamExt.switchFrom(input, closeOnError : true, sync : true)
145 | ..listen(list.add,
146 | onError : errors.add,
147 | onDone : () => isDone = true);
148 |
149 | controller.add(stream1);
150 | controller1.add(0);
151 | controller1.addError("failed1");
152 | controller1.add(1);
153 | controller.add(stream2);
154 | controller2.add(2);
155 | controller1.addError("failed2"); // ignored
156 | controller2.addError("failed3");
157 | controller2.add(3);
158 |
159 | expect(list.length, equals(1), reason : "output stream should contain 1 value");
160 | expect(list, equals([ 0 ]), reason : "output stream should contain values 0");
161 |
162 | expect(errors, equals([ "failed1" ]), reason : "output stream should have received error");
163 | expect(isDone, equals(true), reason : "output stream should be completed");
164 |
165 | controller1.close();
166 | controller2.close();
167 | controller1.close();
168 | });
169 | }
170 | }
--------------------------------------------------------------------------------
/test/extensions/throttle_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class ThrottleTests {
4 | void start() {
5 | group('throttle', () {
6 | _throttleWithNoErrors();
7 | _throttleNotCloseOnError();
8 | _throttleCloseOnError();
9 | });
10 | }
11 |
12 | void _throttleWithNoErrors() {
13 | test("no errors", () {
14 | var controller = new StreamController.broadcast(sync : true);
15 | var input = controller.stream;
16 |
17 | var list = new List();
18 | var hasErr = false;
19 | var isDone = false;
20 | StreamExt.throttle(input, new Duration(milliseconds : 1), sync : true)
21 | ..listen(list.add,
22 | onError : (_) => hasErr = true,
23 | onDone : () => isDone = true);
24 |
25 | controller.add(0);
26 | controller.add(1);
27 | controller.add(2);
28 | controller.add(3);
29 |
30 | new Timer(new Duration(milliseconds : 2), () {
31 | controller.add(4);
32 | controller.close();
33 | });
34 |
35 | new Timer(new Duration(milliseconds : 10), () {
36 | expect(list.length, equals(3), reason : "throttled stream should have only three events");
37 | expect(list, equals([ 0, 3, 4]), reason : "throttled stream should contain values 0, 3 and 4");
38 |
39 | expect(hasErr, equals(false), reason : "throttled stream should not have received error");
40 | expect(isDone, equals(true), reason : "throttled stream should be completed");
41 | });
42 | });
43 | }
44 |
45 | void _throttleNotCloseOnError() {
46 | test("not close on error", () {
47 | var controller = new StreamController.broadcast(sync : true);
48 | var input = controller.stream;
49 |
50 | var list = new List();
51 | var hasErr = false;
52 | var isDone = false;
53 | StreamExt.throttle(input, new Duration(milliseconds : 1), sync : true)
54 | ..listen(list.add,
55 | onError : (_) => hasErr = true,
56 | onDone : () => isDone = true);
57 |
58 | controller.add(0);
59 | controller.addError("failed");
60 | controller.add(1);
61 | controller.add(2);
62 | controller.add(3);
63 |
64 | new Timer(new Duration(milliseconds : 2), () {
65 | controller.add(4);
66 | controller.close();
67 | });
68 |
69 | new Timer(new Duration(milliseconds : 10), () {
70 | expect(list.length, equals(3), reason : "throttled stream should have only three events");
71 | expect(list, equals([ 0, 3, 4]), reason : "throttled stream should contain values 0, 3 and 4");
72 |
73 | expect(hasErr, equals(true), reason : "throttled stream should have received error");
74 | expect(isDone, equals(true), reason : "throttled stream should be completed");
75 | });
76 | });
77 | }
78 |
79 | void _throttleCloseOnError() {
80 | test("close on error", () {
81 | var controller = new StreamController.broadcast(sync : true);
82 | var input = controller.stream;
83 |
84 | var list = new List();
85 | var hasErr = false;
86 | var isDone = false;
87 | StreamExt.throttle(input, new Duration(milliseconds : 1), closeOnError : true, sync : true)
88 | ..listen(list.add,
89 | onError : (_) => hasErr = true,
90 | onDone : () => isDone = true);
91 |
92 | controller.add(0);
93 | controller.add(1);
94 | controller.add(2);
95 | controller.add(3);
96 |
97 | new Timer(new Duration(milliseconds : 2), () {
98 | controller.add(4);
99 | controller.addError("failed");
100 | controller.add(5); // this should not be received since the error would have closed the output stream
101 |
102 | controller.close();
103 | });
104 |
105 | new Timer(new Duration(milliseconds : 10), () {
106 | expect(list.length, equals(3), reason : "throttled stream should have only three events");
107 | expect(list, equals([ 0, 3, 4]), reason : "throttled stream should contain values 0, 3 and 4");
108 |
109 | expect(hasErr, equals(true), reason : "throttled stream should have received error");
110 | expect(isDone, equals(true), reason : "throttled stream should be completed");
111 | });
112 | });
113 | }
114 | }
--------------------------------------------------------------------------------
/test/extensions/timeOutAt_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class TimeOutAtTests {
4 | void start() {
5 | group('timeOutAt', () {
6 | _timeOutAtWithNoValues();
7 | _timeOutAtWithValues();
8 | _timeOutAtInThePast();
9 | _timeOutAtNotCloseOnError();
10 | _timeOutAtCloseOnError();
11 | });
12 | }
13 |
14 | void _timeOutAtWithNoValues() {
15 | test("no values", () {
16 | var controller = new StreamController.broadcast(sync : true);
17 | var input = controller.stream;
18 |
19 | var dueTime = new DateTime.now().add(new Duration(milliseconds : 1));
20 | var list = new List();
21 | var error;
22 | var isDone = false;
23 | StreamExt.timeOutAt(input, dueTime, sync : true)
24 | ..listen(list.add,
25 | onError : (err) => error = err,
26 | onDone : () => isDone = true);
27 |
28 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
29 | future.then((_) {
30 | expect(list.length, equals(0), reason : "output stream should have no value");
31 |
32 | expect(error is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
33 | expect(isDone, equals(true), reason : "output stream should be completed");
34 | });
35 |
36 | expect(future, completes);
37 | });
38 | }
39 |
40 | void _timeOutAtWithValues() {
41 | test("with values", () {
42 | var controller = new StreamController.broadcast(sync : true);
43 | var input = controller.stream;
44 |
45 | var dueTime = new DateTime.now().add(new Duration(milliseconds : 1));
46 | var list = new List();
47 | var error;
48 | var isDone = false;
49 | StreamExt.timeOutAt(input, dueTime, sync : true)
50 | ..listen(list.add,
51 | onError : (err) => error = err,
52 | onDone : () => isDone = true);
53 |
54 | controller.add(0);
55 |
56 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
57 | future.then((_) {
58 | expect(list.length, equals(1), reason : "output stream should have 1 value");
59 | expect(list, equals([ 0 ]), reason : "output stream should contain the value 1");
60 |
61 | expect(error is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
62 | expect(isDone, equals(true), reason : "output stream should be completed");
63 | });
64 |
65 | expect(future, completes);
66 | });
67 | }
68 |
69 | void _timeOutAtInThePast() {
70 | test("in the past", () {
71 | var controller = new StreamController.broadcast(sync : true);
72 | var input = controller.stream;
73 |
74 | var dueTime = new DateTime.now().subtract(new Duration(seconds : 1));
75 | var list = new List();
76 | var error;
77 | var isDone = false;
78 | StreamExt.timeOutAt(input, dueTime, sync : true)
79 | ..listen(list.add,
80 | onError : (err) => error = err,
81 | onDone : () => isDone = true);
82 |
83 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
84 | future.then((_) {
85 | expect(list.length, equals(0), reason : "output stream should have no value");
86 |
87 | expect(error is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
88 | expect(isDone, equals(true), reason : "output stream should be completed");
89 | });
90 |
91 | expect(future, completes);
92 | });
93 | }
94 |
95 | void _timeOutAtNotCloseOnError() {
96 | test("not close on error", () {
97 | var controller = new StreamController.broadcast(sync : true);
98 | var input = controller.stream;
99 |
100 | var dueTime = new DateTime.now().add(new Duration(milliseconds : 1));
101 | var list = new List();
102 | var errors = new List();
103 | var isDone = false;
104 | StreamExt.timeOutAt(input, dueTime, sync : true)
105 | ..listen(list.add,
106 | onError : errors.add,
107 | onDone : () => isDone = true);
108 |
109 | controller.add(0);
110 | controller.addError("failed");
111 | controller.add(1);
112 |
113 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
114 | future.then((_) {
115 | expect(list.length, equals(2), reason : "output stream should have 2 value");
116 | expect(list, equals([ 0, 1 ]), reason : "output stream should contain the values 0 and 1");
117 |
118 | expect(errors.length, equals(2), reason : "output stream should have received 2 errors");
119 | expect(errors[0], equals("failed"), reason : "output stream should have an error value 'failed'");
120 | expect(errors[1] is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
121 | expect(isDone, equals(true), reason : "output stream should be completed");
122 | });
123 |
124 | expect(future, completes);
125 | });
126 | }
127 |
128 | void _timeOutAtCloseOnError() {
129 | test("close on error", () {
130 | var controller = new StreamController.broadcast(sync : true);
131 | var input = controller.stream;
132 |
133 | var dueTime = new DateTime.now().add(new Duration(milliseconds : 1));
134 | var list = new List();
135 | var errors = new List();
136 | var isDone = false;
137 | StreamExt.timeOutAt(input, dueTime, closeOnError : true, sync : true)
138 | ..listen(list.add,
139 | onError : errors.add,
140 | onDone : () => isDone = true);
141 |
142 | controller.add(0);
143 | controller.addError("failed");
144 | controller.add(1);
145 |
146 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
147 | future.then((_) {
148 | expect(list.length, equals(1), reason : "output stream should have only 1 value before the error");
149 | expect(list, equals([ 0 ]), reason : "output stream should contain the value 0");
150 |
151 | expect(errors.length, equals(1), reason : "output stream should have received 1 error");
152 | expect(errors, equals([ "failed" ]), reason : "output stream should not have received a timeout error");
153 | expect(isDone, equals(true), reason : "output stream should be completed");
154 | });
155 |
156 | expect(future, completes);
157 | });
158 | }
159 | }
--------------------------------------------------------------------------------
/test/extensions/timeOut_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class TimeOutTests {
4 | void start() {
5 | group('timeOut', () {
6 | _timeOutWithNoValues();
7 | _timeOutWithGapInValues();
8 | _timeOutNotCloseOnError();
9 | _timeOutCloseOnError();
10 | });
11 | }
12 |
13 | void _timeOutWithNoValues() {
14 | test("no values", () {
15 | var controller = new StreamController.broadcast(sync : true);
16 | var input = controller.stream;
17 |
18 | var list = new List();
19 | var error;
20 | var isDone = false;
21 | StreamExt.timeOut(input, new Duration(milliseconds : 1), sync : true)
22 | ..listen(list.add,
23 | onError : (err) => error = err,
24 | onDone : () => isDone = true);
25 |
26 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
27 | future.then((_) {
28 | expect(list.length, equals(0), reason : "output stream should have no value");
29 |
30 | expect(error is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
31 | expect(isDone, equals(true), reason : "output stream should be completed");
32 | });
33 |
34 | expect(future, completes);
35 | });
36 | }
37 |
38 | void _timeOutWithGapInValues() {
39 | test("gap in values", () {
40 | var controller = new StreamController.broadcast(sync : true);
41 | var input = controller.stream;
42 |
43 | var list = new List();
44 | var error;
45 | var isDone = false;
46 | StreamExt.timeOut(input, new Duration(milliseconds : 1), sync : true)
47 | ..listen(list.add,
48 | onError : (err) => error = err,
49 | onDone : () => isDone = true);
50 |
51 | controller.add(0);
52 |
53 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
54 | future.then((_) {
55 | expect(list.length, equals(1), reason : "output stream should have 1 value");
56 | expect(list, equals([ 0 ]), reason : "output stream should contain the value 1");
57 |
58 | expect(error is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
59 | expect(isDone, equals(true), reason : "output stream should be completed");
60 | });
61 |
62 | expect(future, completes);
63 | });
64 | }
65 |
66 | void _timeOutNotCloseOnError() {
67 | test("not close on error", () {
68 | var controller = new StreamController.broadcast(sync : true);
69 | var input = controller.stream;
70 |
71 | var list = new List();
72 | var errors = new List();
73 | var isDone = false;
74 | StreamExt.timeOut(input, new Duration(milliseconds : 1), sync : true)
75 | ..listen(list.add,
76 | onError : errors.add,
77 | onDone : () => isDone = true);
78 |
79 | controller.add(0);
80 | controller.addError("failed");
81 | controller.add(1);
82 |
83 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
84 | future.then((_) {
85 | expect(list.length, equals(2), reason : "output stream should have 2 value");
86 | expect(list, equals([ 0, 1 ]), reason : "output stream should contain the values 0 and 1");
87 |
88 | expect(errors.length, equals(2), reason : "output stream should have received 2 errors");
89 | expect(errors[0], equals("failed"), reason : "output stream should have an error value 'failed'");
90 | expect(errors[1] is TimeoutError, equals(true), reason : "output stream should have received a timeout error");
91 | expect(isDone, equals(true), reason : "output stream should be completed");
92 | });
93 |
94 | expect(future, completes);
95 | });
96 | }
97 |
98 | void _timeOutCloseOnError() {
99 | test("close on error", () {
100 | var controller = new StreamController.broadcast(sync : true);
101 | var input = controller.stream;
102 |
103 | var list = new List();
104 | var errors = new List();
105 | var isDone = false;
106 | StreamExt.timeOut(input, new Duration(milliseconds : 1), closeOnError : true, sync : true)
107 | ..listen(list.add,
108 | onError : errors.add,
109 | onDone : () => isDone = true);
110 |
111 | controller.add(0);
112 | controller.addError("failed");
113 | controller.add(1);
114 |
115 | Future future = new Future.delayed(new Duration(milliseconds : 2)).then((_) => controller.close());
116 | future.then((_) {
117 | expect(list.length, equals(1), reason : "output stream should have only 1 value before the error");
118 | expect(list, equals([ 0 ]), reason : "output stream should contain the value 0");
119 |
120 | expect(errors.length, equals(1), reason : "output stream should have received 1 error");
121 | expect(errors, equals([ "failed" ]), reason : "output stream should not have received a timeout error");
122 | expect(isDone, equals(true), reason : "output stream should be completed");
123 | });
124 |
125 | expect(future, completes);
126 | });
127 | }
128 | }
--------------------------------------------------------------------------------
/test/extensions/window_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class WindowTests {
4 | void start() {
5 | group('window', () {
6 | _windowWithNoErrors();
7 | _windowNotCloseOnError();
8 | _windowCloseOnError();
9 | });
10 | }
11 |
12 | void _windowWithNoErrors() {
13 | test("no errors", () {
14 | var controller = new StreamController.broadcast(sync : true);
15 | var input = controller.stream;
16 |
17 | var list = new List();
18 | var hasErr = false;
19 | var isDone = false;
20 | StreamExt.window(input, 3, sync : true)
21 | ..listen(list.add,
22 | onError : (_) => hasErr = true,
23 | onDone : () => isDone = true);
24 |
25 | controller.add(0);
26 | controller.add(1);
27 | controller.add(2);
28 | controller.add(3);
29 | controller.add(4);
30 | controller.add(5);
31 | controller.add(6);
32 |
33 | controller.close().then((_) {
34 | expect(list.length, equals(3), reason : "windowed stream should have three events");
35 | expect(list, equals([ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6 ] ]),
36 | reason : "windowed stream should contain lists [ 0, 1, 2 ], [ 3, 4, 5 ] and [ 6 ]");
37 |
38 | expect(hasErr, equals(false), reason : "windowed stream should not have received error");
39 | expect(isDone, equals(true), reason : "windowed stream should be completed");
40 | });
41 | });
42 | }
43 |
44 | void _windowNotCloseOnError() {
45 | test("not close on error", () {
46 | var controller = new StreamController.broadcast(sync : true);
47 | var input = controller.stream;
48 |
49 | var list = new List();
50 | var hasErr = false;
51 | var isDone = false;
52 | StreamExt.window(input, 3, sync : true)
53 | ..listen(list.add,
54 | onError : (_) => hasErr = true,
55 | onDone : () => isDone = true);
56 |
57 | controller.add(0);
58 | controller.add(1);
59 | controller.addError("failed");
60 | controller.add(2);
61 | controller.add(3);
62 | controller.add(4);
63 | controller.add(5);
64 | controller.add(6);
65 |
66 | controller.close().then((_) {
67 | expect(list.length, equals(3), reason : "windowed stream should have three events");
68 | expect(list, equals([ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6 ] ]),
69 | reason : "windowed stream should contain lists [ 0, 1, 2 ], [ 3, 4, 5 ] and [ 6 ]");
70 |
71 | expect(hasErr, equals(true), reason : "windowed stream should have received error");
72 | expect(isDone, equals(true), reason : "windowed stream should be completed");
73 | });
74 | });
75 | }
76 |
77 | void _windowCloseOnError() {
78 | test("close on error", () {
79 | var controller = new StreamController.broadcast(sync : true);
80 | var input = controller.stream;
81 |
82 | var list = new List();
83 | var hasErr = false;
84 | var isDone = false;
85 | StreamExt.window(input, 3, closeOnError : true, sync : true)
86 | ..listen(list.add,
87 | onError : (_) => hasErr = true,
88 | onDone : () => isDone = true);
89 |
90 | controller.add(0);
91 | controller.add(1);
92 | controller.add(2);
93 | controller.add(3);
94 | controller.addError("failed");
95 | controller.add(4);
96 |
97 | controller.close().then((_) {
98 | expect(list.length, equals(1), reason : "windowed stream should have only one event before the error");
99 | expect(list, equals([ [ 0, 1, 2 ] ]),
100 | reason : "windowed stream should contain list [ 0, 1, 2 ]");
101 |
102 | expect(hasErr, equals(true), reason : "windowed stream should have received error");
103 | expect(isDone, equals(true), reason : "windowed stream should be completed");
104 | });
105 | });
106 | }
107 | }
--------------------------------------------------------------------------------
/test/extensions/zip_test.dart:
--------------------------------------------------------------------------------
1 | part of stream_ext_test;
2 |
3 | class ZipTests {
4 | void start() {
5 | group('zip', () {
6 | _zipWithNoErrors();
7 | _zipNotCloseOnError();
8 | _zipCloseOnError();
9 | _zipWithUserErrorNotCloseOnError();
10 | _zipWithUserErrorCloseOnError();
11 | });
12 | }
13 |
14 | void _zipWithNoErrors() {
15 | test("no errors", () {
16 | var controller1 = new StreamController.broadcast(sync : true);
17 | var controller2 = new StreamController.broadcast(sync : true);
18 | var stream1 = controller1.stream;
19 | var stream2 = controller2.stream;
20 |
21 | var list = new List();
22 | var hasErr = false;
23 | var isDone = false;
24 | StreamExt.zip(stream1, stream2, (a, b) => "$a, $b", sync : true)
25 | ..listen(list.add,
26 | onError : (_) => hasErr = true,
27 | onDone : () => isDone = true);
28 |
29 | controller1.add(0);
30 | controller1.add(1);
31 | controller2.add(3); // paired with 0
32 | controller2.add(4); // paired with 1
33 | controller2.add(5);
34 | controller1.add(2); // paired with 2
35 |
36 | var future2 = controller2.close();
37 | controller1.add(6); // not received since other stream is complete
38 | var future1 = controller1.close();
39 |
40 | Future
41 | .wait([ future1, future2 ])
42 | .then((_) {
43 | expect(list.length, equals(3), reason : "zipped stream should have three events");
44 | expect(list, equals([ "0, 3", "1, 4", "2, 5" ]),
45 | reason : "zipped stream should contain values (0, 3), (1, 4) and (2, 5)");
46 |
47 | expect(hasErr, equals(false), reason : "zipped stream should not have received error");
48 | expect(isDone, equals(true), reason : "zipped stream should be completed");
49 | });
50 | });
51 | }
52 |
53 | void _zipNotCloseOnError() {
54 | test("not close on error", () {
55 | var controller1 = new StreamController.broadcast(sync : true);
56 | var controller2 = new StreamController.broadcast(sync : true);
57 | var stream1 = controller1.stream;
58 | var stream2 = controller2.stream;
59 |
60 | var list = new List();
61 | var hasErr = false;
62 | var isDone = false;
63 | StreamExt.zip(stream1, stream2, (a, b) => "$a, $b", sync : true)
64 | ..listen(list.add,
65 | onError : (_) => hasErr = true,
66 | onDone : () => isDone = true);
67 |
68 | controller1.add(0);
69 | controller1.add(1);
70 | controller2.addError("failed");
71 | controller2.add(3); // paired with 0
72 | controller2.add(4); // paired with 1
73 | controller1.addError("failed");
74 | controller2.add(5);
75 | controller1.add(2); // paired with 2
76 |
77 | var future2 = controller2.close();
78 | controller1.add(6); // not received since other stream is complete
79 | var future1 = controller1.close();
80 |
81 | Future
82 | .wait([ future1, future2 ])
83 | .then((_) {
84 | expect(list.length, equals(3), reason : "zipped stream should have three events");
85 | expect(list, equals([ "0, 3", "1, 4", "2, 5" ]),
86 | reason : "zipped stream should contain values (0, 3), (1, 4) and (2, 5)");
87 |
88 | expect(hasErr, equals(true), reason : "zipped stream should have received error");
89 | expect(isDone, equals(true), reason : "zipped stream should be completed");
90 | });
91 | });
92 | }
93 |
94 | void _zipCloseOnError() {
95 | test("close on error", () {
96 | var controller1 = new StreamController.broadcast(sync : true);
97 | var controller2 = new StreamController.broadcast(sync : true);
98 | var stream1 = controller1.stream;
99 | var stream2 = controller2.stream;
100 |
101 | var list = new List();
102 | var hasErr = false;
103 | var isDone = false;
104 | StreamExt.zip(stream1, stream2, (a, b) => "$a, $b", closeOnError : true, sync : true)
105 | ..listen(list.add,
106 | onError : (_) => hasErr = true,
107 | onDone : () => isDone = true);
108 |
109 | controller1.add(0);
110 | controller1.add(1);
111 | controller2.add(3); // paired with 0
112 | controller1.addError("failed");
113 | controller2.add(4); // not paired
114 |
115 | new Timer(new Duration(milliseconds : 5), () {
116 | expect(list.length, equals(1), reason : "zipped stream should have only one event before the error");
117 | expect(list, equals([ "0, 3" ]), reason : "zipped stream should contain values (0, 3)");
118 |
119 | expect(hasErr, equals(true), reason : "zipped stream should have received error");
120 | expect(isDone, equals(true), reason : "zipped stream should be completed");
121 | });
122 | });
123 | }
124 |
125 | void _zipWithUserErrorNotCloseOnError() {
126 | test("with user error not close on error", () {
127 | var controller1 = new StreamController.broadcast(sync : true);
128 | var controller2 = new StreamController.broadcast(sync : true);
129 | var stream1 = controller1.stream;
130 | var stream2 = controller2.stream;
131 |
132 | var list = new List();
133 | var numErrors = 0;
134 | var errors = new List();
135 | var isDone = false;
136 | StreamExt.zip(stream1, stream2, (a, b) => a + b, sync : true)
137 | ..listen(list.add,
138 | onError : (err) {
139 | numErrors++;
140 | errors.add(err);
141 | },
142 | onDone : () => isDone = true);
143 |
144 | controller1.add(0);
145 | controller1.add(1);
146 | controller2.add(3); // paired with 0
147 | controller2.add("4"); // should cause error
148 | controller2.add(4); // will still error since "4" is the next value to be processed
149 |
150 | Future
151 | .wait([ controller1.close(), controller2.close() ])
152 | .then((_) {
153 | expect(list.length, equals(1), reason : "zipped stream should have only one event before bad value");
154 | expect(list, equals([ 3 ]), reason : "zipped stream should contain values 3");
155 |
156 | expect(numErrors, equals(2), reason : "zipped stream should have received 2 errors");
157 | expect(errors.every((x) => x is TypeError), equals(true), reason : "zipped stream should have received TypeErrors");
158 | expect(isDone, equals(true), reason : "zipped stream should be completed");
159 | });
160 | });
161 | }
162 |
163 | void _zipWithUserErrorCloseOnError() {
164 | test("with user error close on error", () {
165 | var controller1 = new StreamController.broadcast(sync : true);
166 | var controller2 = new StreamController.broadcast(sync : true);
167 | var stream1 = controller1.stream;
168 | var stream2 = controller2.stream;
169 |
170 | var list = new List();
171 | var numErrors = 0;
172 | var errors = new List();
173 | var isDone = false;
174 | StreamExt.zip(stream1, stream2, (a, b) => a + b, closeOnError : true, sync : true)
175 | ..listen(list.add,
176 | onError : (err) {
177 | numErrors++;
178 | errors.add(err);
179 | },
180 | onDone : () => isDone = true);
181 |
182 | controller1.add(0);
183 | controller1.add(1);
184 | controller2.add(3); // paired with 0
185 | controller2.add("4"); // should cause error
186 | controller2.add(4); // stream should be closed by now
187 |
188 | new Timer(new Duration(milliseconds : 5), () {
189 | expect(list.length, equals(1), reason : "zipped stream should have only one event before bad value");
190 | expect(list, equals([ 3 ]), reason : "zipped stream should contain values 3");
191 |
192 | expect(numErrors, equals(1), reason : "zipped stream should have received 1 error");
193 | expect(errors.every((x) => x is TypeError), equals(true), reason : "zipped stream should have received TypeError");
194 | expect(isDone, equals(true), reason : "zipped stream should be completed");
195 | });
196 | });
197 | }
198 | }
--------------------------------------------------------------------------------
/test/stream_ext_test.dart:
--------------------------------------------------------------------------------
1 | library stream_ext_test;
2 |
3 | import 'dart:async';
4 | import 'package:unittest/unittest.dart';
5 | import 'package:stream_ext/stream_ext.dart';
6 |
7 | part "extensions/amb_test.dart";
8 | part "extensions/average_test.dart";
9 | part "extensions/buffer_test.dart";
10 | part "extensions/combineLatest_test.dart";
11 | part "extensions/concat_test.dart";
12 | part "extensions/delay_test.dart";
13 | part "extensions/max_test.dart";
14 | part "extensions/merge_test.dart";
15 | part "extensions/min_test.dart";
16 | part "extensions/onErrorResumeNext_test.dart";
17 | part "extensions/repeat_test.dart";
18 | part "extensions/sample_test.dart";
19 | part "extensions/scan_test.dart";
20 | part "extensions/startWith_test.dart";
21 | part "extensions/sum_test.dart";
22 | part "extensions/switchFrom_test.dart";
23 | part "extensions/throttle_test.dart";
24 | part "extensions/timeOut_test.dart";
25 | part "extensions/timeOutAt_test.dart";
26 | part "extensions/window_test.dart";
27 | part "extensions/zip_test.dart";
28 |
29 | main() {
30 | new AmbTests().start();
31 | new AverageTests().start();
32 | new BufferTests().start();
33 | new CombineLatestTests().start();
34 | new ConcatTests().start();
35 | new DelayTests().start();
36 | new MaxTests().start();
37 | new MergeTests().start();
38 | new MinTests().start();
39 | new OnErrorResumeNextTests().start();
40 | new RepeatTests().start();
41 | new SampleTests().start();
42 | new ScanTests().start();
43 | new StartWithTests().start();
44 | new SumTests().start();
45 | new SwitchFromTests().start();
46 | new ThrottleTests().start();
47 | new TimeOutTests().start();
48 | new TimeOutAtTests().start();
49 | new WindowTests().start();
50 | new ZipTests().start();
51 | }
--------------------------------------------------------------------------------