├── .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 : 15 |
16 | Stream 2 : 17 |
18 | Stream 3 : 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 | 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 : 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 : 15 |
      16 | Stream 2 : 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 : 15 |
        16 | Stream 2 : 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 | 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 : 15 |
          16 | Stream 2 : 17 |
          18 | Stream 3 : 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 | 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 : 15 |
            16 | Stream 2 : 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 | 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 | 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 | 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 | 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 | 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 | 17 |
                      18 | 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 | 17 |
                        18 | 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 : 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 |
                            14 |
                            15 |
                            16 |
                            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 | } --------------------------------------------------------------------------------