├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── src │ ├── buffer_when.dart │ ├── buffer_with_count.dart │ ├── combine.dart │ ├── concat.dart │ ├── concat_all.dart │ ├── debounce.dart │ ├── delay.dart │ ├── do_action.dart │ ├── event_stream.dart │ ├── flat_map.dart │ ├── flat_map_latest.dart │ ├── merge.dart │ ├── merge_all.dart │ ├── sample_on.dart │ ├── sample_periodically.dart │ ├── scan.dart │ ├── select_first.dart │ ├── skip_until.dart │ ├── start_with.dart │ ├── take_until.dart │ ├── typedefs.dart │ ├── util.dart │ ├── when.dart │ └── zip.dart └── stream_transformers.dart ├── pubspec.yaml ├── test ├── all_tests.dart ├── buffer_when_test.dart ├── buffer_with_count_test.dart ├── combine_test.dart ├── concat_all_test.dart ├── concat_test.dart ├── debounce_test.dart ├── do_action_test.dart ├── flat_map_latest_test.dart ├── flat_map_test.dart ├── merge_all_test.dart ├── merge_test.dart ├── sample_on_test.dart ├── sample_periodically_test.dart ├── scan_test.dart ├── select_first_test.dart ├── skip_until_test.dart ├── start_with_test.dart ├── take_until_test.dart ├── util.dart ├── when_test.dart └── zip_test.dart └── tool └── travis.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | .packages 5 | .pub/ 6 | build/ 7 | packages 8 | pubspec.lock 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | # Speed up builds by using containerization. 3 | sudo: false 4 | env: 5 | global: 6 | # COVERALLS_TOKEN 7 | secure: aTnwsQmp/ngr1eXNVULJekdHhgvQq3PomunMOqeqA+5OY1MpSrdQgMigjT/VdjaMFe6mackHCR7sIhWcsiT2weZItVkeOYckT41ICrogP7bau7GgpcCjntHZ0byYN8MImbo/zJPZG3sh2iZwL2YFx+TjjHHkAbn7RovKeAmhMIM= 8 | dart: 9 | - stable 10 | - dev 11 | script: ./tool/travis.sh 12 | after_success: 13 | - pub global activate dart_coveralls 14 | - pub global run dart_coveralls:dart_coveralls report --exclude-test-files --token $COVERALLS_TOKEN ./test/all_tests.dart 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | *Note:* Patch versions that only include documentation changes are omitted. 4 | 5 | ## 0.3.0+3 (09/16/2015) 6 | - Fix type issues found using DDC [[#23](https://github.com/danschultz/stream_transformers/pull/23)] 7 | - Thanks [Arron Washington], [Devon Carew]! 8 | 9 | 10 | ## 0.3.0 (02/30/2015) 11 | - Add `Concat` [[#15](https://github.com/danschultz/stream_transformers/issues/15)] 12 | - Add `ConcatAll` [[#16](https://github.com/danschultz/stream_transformers/issues/16)] 13 | - Add `DoAction` [[#4](https://github.com/danschultz/stream_transformers/issues/4)] 14 | - Add `MergeAll` [[#2](https://github.com/danschultz/stream_transformers/issues/2)] 15 | - Add `SampleOn` [[#1](https://github.com/danschultz/stream_transformers/issues/1)] 16 | - Add `SamplePeriodically` [[#3](https://github.com/danschultz/stream_transformers/issues/3)] 17 | - Add `SelectFirst` [[#13](https://github.com/danschultz/stream_transformers/issues/13)] 18 | - Add `StartWith` [[#14](https://github.com/danschultz/stream_transformers/issues/14)] 19 | - `FlatMap` now closes only when its source is closed. 20 | 21 | ## 0.2.0 (02/01/2015) 22 | - Changed the behavior of debounce to debounce the first value [[#5](https://github.com/danschultz/stream_transformers/issues/5)] 23 | - Rename `StreamConverter` to `Mapper`, and generalized type signature to `R Mapper(A value)` [[#10](https://github.com/danschultz/stream_transformers/issues/10)] 24 | - Make sure errors for transformers are forwarded as documented 25 | - Make sure internal subscriptions are cancelled when the transformed stream subscriptions are cancelled 26 | 27 | ## 0.1.0+3 (01/25/2015) 28 | - Fixed [issue #6](https://github.com/danschultz/stream_transformers/issues/6) where `Scan` doesn't include the initial value in the transformed stream. 29 | 30 | ## 0.1.0+1 (01/14/2015) 31 | - Fixed an issue where `When` and `Zip` transformers would not return the same stream type. 32 | 33 | ## 0.1.0 (01/10/2015) 34 | Initial version 35 | 36 | - Add `BufferWhen` transformer 37 | - Add `Combine` transformer 38 | - Add `Debounce` transformer 39 | - Add `Delay` transformer 40 | - Add `FlatMap` transformer 41 | - Add `FlatMapLatest` transformer 42 | - Add `Merge` transformer 43 | - Add `Scan` transformer 44 | - Add `SkipUntil` transformer 45 | - Add `TakeUntil` transformer 46 | - Add `When` transformer 47 | - Add `Zip` transformer 48 | 49 | [Devon Carew]: https://github.com/devoncarew 50 | [Arron Washington]: https://github.com/radicaled 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 frappe-dart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stream_transformers 2 | 3 | [![Build Status](https://travis-ci.org/danschultz/stream_transformers.svg)](https://travis-ci.org/danschultz/stream_transformers) 4 | [![Coverage Status](https://coveralls.io/repos/danschultz/stream_transformers/badge.svg?branch=master)](https://coveralls.io/r/danschultz/stream_transformers?branch=master) 5 | 6 | This library provides a set of stream transformers for Dart's `Stream` class. 7 | 8 | These transformers are used internally by [Frappe]. If you're looking for a more featured functional reactive programming (FRP) library for Dart, you should look there. 9 | 10 | ## Transformers 11 | 12 | * [BufferWhen](#bufferwhen) 13 | * [Combine](#combine) 14 | * [Concat](#concat) 15 | * [ConcatAll](#concatall) 16 | * [Debounce](#debounce) 17 | * [Delay](#delay) 18 | * [DoAction](#doaction) 19 | * [FlatMap](#flatmap) 20 | * [FlatMapLatest](#flatmaplatest) 21 | * [Merge](#merge) 22 | * [MergeAll](#mergeall) 23 | * [SampleOn](#sampleon) 24 | * [SamplePeriodically](#sampleperiodically) 25 | * [Scan](#scan) 26 | * [SelectFirst](#selectfirst) 27 | * [SkipUntil](#skipuntil) 28 | * [StartWith](#startwith) 29 | * [TakeUntil](#takeuntil) 30 | * [When](#when) 31 | * [Zip](#zip) 32 | 33 | ### `BufferWhen` 34 | Pauses the delivery of events from the source stream when the signal stream delivers a value of `true`. The buffered events are delivered when the signal delivers a value of `false`. Errors originating from the source and signal streams will be forwarded to the transformed stream and will not be buffered. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 35 | 36 | **Example:** 37 | 38 | ```dart 39 | var controller = new StreamController(); 40 | var signal = new StreamController(); 41 | 42 | var stream = controller.stream; 43 | 44 | var buffered = stream.transform(new BufferWhen(signal.stream)); 45 | 46 | controller.add(1); 47 | signal.add(true); 48 | controller.add(2); 49 | 50 | buffered.listen(print); // Prints: 1 51 | ``` 52 | 53 | ### `Combine` 54 | Combines the latest values of two streams using a two argument function. The combining function will not be called until each stream delivers its first value. After the first value of each stream is delivered, the combining function will be invoked for each event from the source streams. Errors occurring on the streams will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 55 | 56 | **Example:** 57 | 58 | ```dart 59 | var controller1 = new StreamController(); 60 | var controller2 = new StreamController(); 61 | 62 | var combined = controller1.stream.transform(new Combine(controller2.stream, (a, b) => a + b)); 63 | 64 | combined.listen(print); 65 | 66 | controller1.add(1); 67 | controller2.add(1); // Prints: 2 68 | controller1.add(2); // Prints: 3 69 | controller2.add(2); // Prints: 4 70 | ``` 71 | 72 | Use the static function `Combine.all(List)` to combine a list of streams together. The returned stream will contain a `List` that contains the current values of each of the streams. 73 | 74 | **Example:** 75 | 76 | ```dart 77 | var controller1 = new StreamController(); 78 | var controller2 = new StreamController(); 79 | 80 | var combined = Combine.all([controller1.stream, controller2.stream]); 81 | 82 | combined.listen(print); 83 | 84 | controller1.add(1); 85 | controller2.add(2); // Prints: [1, 2] 86 | controller1.add(3); // Prints: [3, 2] 87 | controller2.add(4); // Prints: [3, 4] 88 | ``` 89 | 90 | ### `Concat` 91 | Concatenates two streams into one stream by delivering the values of the source stream, and then delivering the values of the other stream once the source stream completes. This means that it's possible that events from the second stream might not be included if the source stream hasn't completed. Use `Concat.all()` to concatenate many streams. 92 | 93 | Errors will be forwarded from either stream, whether or not the source stream has completed. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 94 | 95 | **Example:** 96 | 97 | ```dart 98 | var source = new StreamController(); 99 | var other = new StreamController(); 100 | 101 | var stream = source.stream.transform(new Concat(other.stream)); 102 | stream.listen(print); 103 | 104 | other..add(1)..add(2); 105 | source..add(3)..add(4)..close(); 106 | 107 | // 3 108 | // 4 109 | // 1 110 | // 2 111 | ``` 112 | 113 | ### `ConcatAll` 114 | Concatenates a stream of streams into a single stream, by delivering the first stream's values, and then delivering the next stream's values after the previous stream has completed. 115 | 116 | This means that it's possible that events from the second stream might not be included if the source stream hasn't completed. Use `Concat.all()` to concatenate many streams. 117 | 118 | Errors will be forwarded from either stream, whether or not the source stream has completed. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 119 | 120 | **Example:** 121 | 122 | ```dart 123 | var source = new StreamController(); 124 | var other1 = new StreamController(); 125 | var other2 = new StreamController(); 126 | 127 | source..add(other1.stream)..add(other2.stream); 128 | 129 | other2..add(1)..add(2); 130 | other1..add(3)..add(4)..close(); 131 | 132 | var stream = source.stream.transform(new ConcatAll()); 133 | stream.listen(print); 134 | 135 | // 3 136 | // 4 137 | // 1 138 | // 2 139 | ``` 140 | 141 | ### `Debounce` 142 | Delivers the last event in the stream after the duration has passed without receiving an event. 143 | 144 | Errors occurring on the source stream will not be ignored. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 145 | 146 | ``` 147 | source: asdf----asdf---- 148 | source.debounce(2): -----f-------f-- 149 | ``` 150 | 151 | **Example:** 152 | 153 | ```dart 154 | var controller = new StreamController(); 155 | 156 | var debounced = controller.stream.transform(new Debounce(new Duration(seconds:1))); 157 | debounced.listen(print); 158 | 159 | controller.add(1); 160 | controller.add(2); 161 | controller.add(3); 162 | 163 | // Prints: 3 164 | ``` 165 | 166 | ### `Delay` 167 | Throttles the delivery of each event by a given duration. Errors occurring on the source stream will not be delayed. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 168 | 169 | **Example:** 170 | 171 | ```dart 172 | var controller = new StreamController(); 173 | var delayed = controller.stream.transform(new Delay(new Duration(seconds: 2))); 174 | 175 | // source: asdf---- 176 | // source.delayed(2): --a--s--d--f--- 177 | ``` 178 | 179 | ### `DoAction` 180 | Invokes a side-effect function for each value, error and done event in the stream. 181 | 182 | This is useful for debugging, but also invoking `preventDefault` for browser events. Side effects will only be invoked once if the transformed stream has multiple subscribers. 183 | 184 | Errors occurring on the source stream will be forwarded to the returned stream, even when passing an error handler to `DoAction`. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 185 | 186 | **Example:** 187 | 188 | ```dart 189 | var controller = new StreamController(); 190 | var sideEffect = new DoAction((value) => print("Do Next: $value"), 191 | onError: (error) => print("Do Error: $error"), 192 | onDone: () => print("Do Done")); 193 | var stream = controller.stream.transform(sideEffect); 194 | 195 | stream.listen((value) => print("Next: $value"), 196 | onError: (e) => print("Error: $e"), 197 | onDone: () => print("Done")); 198 | 199 | controller..add(1)..add(2)..close(); 200 | 201 | // Do Next: 1 202 | // Next: 1 203 | // Do Next: 2 204 | // Next: 2 205 | // Do Done 206 | // Done 207 | ``` 208 | 209 | ### `FlatMap` 210 | Spawns a new stream from a function for each event in the source stream. The returned stream will contain the events and errors from each of the spawned streams until they're closed. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 211 | 212 | **Example:** 213 | 214 | ```dart 215 | var controller = new StreamController(); 216 | var flapMapped = controller.stream.transform(new FlatMap((value) => new Stream.fromIterable([value + 1])); 217 | 218 | flatMapped.listen(print); 219 | 220 | controller.add(1); // Prints: 2 221 | controller.add(2); // Prints: 3 222 | ``` 223 | 224 | ### `FlatMapLatest` 225 | Similar to `FlatMap`, but instead of including events from all spawned streams, only includes the ones from the latest stream. Think of this as stream switching. 226 | 227 | **Example:** 228 | 229 | ```dart 230 | var controller = new StreamController(); 231 | var latest = controller.stream.transform(new FlatMapLatest((value) => new Stream.fromIterable([value + 1])); 232 | 233 | latest.listen(print); 234 | 235 | controller.add(1); 236 | controller.add(2); // Prints: 3 237 | ``` 238 | 239 | ### `Merge` 240 | Combines the events from two streams into a single stream. Errors occurring on a source stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 241 | 242 | **Example:** 243 | 244 | ```dart 245 | var controller1 = new StreamController(); 246 | var controller2 = new StreamController(); 247 | 248 | var merged = controller1.stream.transform(new Merge(controller2.stream)); 249 | 250 | merged.listen(print); 251 | 252 | controller1.add(1); // Prints: 1 253 | controller2.add(2); // Prints: 2 254 | controller1.add(3); // Prints: 3 255 | controller2.add(4); // Prints: 4 256 | ``` 257 | 258 | Use the static function `Merge.all(List)` to merge all streams of a list into a single stream. 259 | 260 | ### `MergeAll` 261 | Combines the events from a stream of streams into a single stream. 262 | 263 | The returned stream will contain the errors occurring on any stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 264 | 265 | **Example:** 266 | 267 | ```dart 268 | var source = new StreamController(); 269 | var stream1 = new Stream.fromIterable([1, 2]); 270 | var stream2 = new Stream.fromIterable([3, 4]); 271 | 272 | var merged = source.stream.transform(new MergeAll()); 273 | source..add(stream1)..add(stream2); 274 | 275 | merged.listen(print); 276 | 277 | // 1 278 | // 2 279 | // 3 280 | // 4 281 | ``` 282 | 283 | ### `SampleOn` 284 | Takes the latest value of the source stream whenever the trigger stream produces an event. 285 | 286 | Errors that happen on the source stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 287 | 288 | **Example:** 289 | 290 | ```dart 291 | // values start at 0 292 | var source = new Stream.periodic(new Duration(seconds: 1), (i) => i); 293 | var trigger = new Stream.periodic(new Duration(seconds: 2), (i) => i); 294 | 295 | var stream = source.stream.transform(new SampleOn(trigger.stream)).take(3); 296 | 297 | stream.listen(print); 298 | 299 | // 0 300 | // 2 301 | // 4 302 | ``` 303 | 304 | ### `SamplePeriodically` 305 | Takes the latest value of the source stream at a specified interval. 306 | 307 | Errors that happen on the source stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 308 | 309 | **Example:** 310 | 311 | ```dart 312 | // values start at 0 313 | var source = new Stream.periodic(new Duration(seconds: 1), (i) => i); 314 | var stream = source.stream.transform(new SamplePeriodically(new Duration(seconds: 2)).take(3); 315 | 316 | stream.listen(print); 317 | 318 | // 0 319 | // 2 320 | // 4 321 | ``` 322 | 323 | ### `Scan` 324 | Reduces the values of a stream into a single value by using an initial value and an accumulator function. The function is passed the previous accumulated value and the current value of the stream. This is useful for maintaining state using a stream. Errors occurring on the source stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 325 | 326 | **Example:** 327 | 328 | ```dart 329 | var button = new ButtonElement(); 330 | 331 | var clickCount = button.onClick.transform(new Scan(0, (previous, current) => previous + 1)); 332 | 333 | clickCount.listen(print); 334 | 335 | // [button click] .. prints: 1 336 | // [button click] .. prints: 2 337 | ``` 338 | 339 | ### `SelectFirst` 340 | Forwards events from the first stream to deliver an event. 341 | 342 | Errors are forwarded from both streams until a stream is selected. Once a stream is selected, only errors from the selected stream are forwarded. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 343 | 344 | **Example:** 345 | 346 | ```dart 347 | var stream1 = new Stream.periodic(new Duration(seconds: 1)).map((_) => "Stream 1"); 348 | var stream2 = new Stream.periodic(new Duration(seconds: 2)).map((_) => "Stream 2"); 349 | 350 | var selected = stream1.transform(new SelectFirst(stream2)).take(1); 351 | selected.listen(print); 352 | 353 | // Stream 1 354 | ``` 355 | 356 | ### `SkipUntil` 357 | Waits to deliver events from a stream until the signal `Stream` delivers a value. Errors that happen on the source stream will be forwarded once the `Stream` delivers its value. Errors happening on the signal stream will be forwarded immediately. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 358 | 359 | **Example:** 360 | 361 | ```dart 362 | var signal = new StreamController(); 363 | var controller = new StreamController(); 364 | 365 | var skipStream = controller.stream.transform(new SkipUntil(signal.stream)); 366 | 367 | skipStream.listen(print); 368 | 369 | controller.add(1); 370 | controller.add(2); 371 | signal.add(true); 372 | controller.add(3); // Prints: 3 373 | controller.add(4); // Prints: 4 374 | ``` 375 | 376 | ### `StartWith` 377 | Prepends values to the beginning of a stream. Use `StartWith.many` to prepend multiple values. 378 | 379 | Errors on the source stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 380 | 381 | **Example:** 382 | 383 | ```dart 384 | var source = new Stream.fromIterable([2, 3]); 385 | var stream = source.transform(new StartWith(1)); 386 | stream.listen(print); 387 | 388 | // 1 389 | // 2 390 | // 3 391 | ``` 392 | 393 | ### `TakeUntil` 394 | Delivers events from the source stream until the signal `Stream` produces a value. At which point, the transformed stream closes. The returned stream will continue to deliver values if the signal stream closes without a value. 395 | 396 | This is useful for automatically cancelling a stream subscription to prevent memory leaks. Errors that happen on the source and signal stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 397 | 398 | **Example:** 399 | 400 | ```dart 401 | var signal = new StreamController(); 402 | var controller = new StreamController(); 403 | 404 | var takeUntil = controller.stream.transform(new TakeUntil(signal.stream)); 405 | 406 | takeUntil.listen(print); 407 | 408 | controller.add(1); // Prints: 1 409 | controller.add(2); // Prints: 2 410 | signal.add(true); 411 | controller.add(3); 412 | controller.add(4); 413 | ``` 414 | 415 | ### `When` 416 | Starts delivering events from the source stream when the signal stream delivers a value of `true`. Events are skipped when the signal stream delivers a value of `false`. Errors from the source or toggle stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 417 | 418 | **Example:** 419 | 420 | ```dart 421 | var controller = new StreamController(); 422 | var signal = new StreamController(); 423 | 424 | var whenStream = controller.stream.transform(new When(signal.stream)); 425 | 426 | whenStream.listen(print); 427 | 428 | controller.add(1); 429 | signal.add(true); 430 | controller.add(2); // Prints: 2 431 | signal.add(false); 432 | controller.add(3); 433 | ``` 434 | 435 | ### `Zip` 436 | Combines the events of two streams into one by invoking a combiner function that is invoked when each stream delivers an event at each index. The transformed stream finishes when either source stream finishes. Errors from either stream will be forwarded to the transformed stream. If the source stream is a broadcast stream, then the transformed stream will also be a broadcast stream. 437 | 438 | **Example:** 439 | 440 | ```dart 441 | var controller1 = new StreamController(); 442 | var controller2 = new StreamController(); 443 | 444 | var zipped = controller1.stream.transform(new Zip(controller2.stream, (a, b) => a + b)); 445 | 446 | zipped.listen(print); 447 | 448 | controller1.add(1); 449 | controller1.add(2); 450 | controller2.add(1); // Prints 2 451 | controller1.add(3); 452 | controller2.add(2); // Prints 4 453 | controller2.add(3); // Prints 6 454 | ``` 455 | 456 | ## Running Tests 457 | Tests are run using [test_runner]. 458 | 459 | * Install *test_runner*: `pub global activate test_runner` 460 | * Run *test_runner* inside *stream_transformers*: `pub global run run_tests` 461 | 462 | ## Features and bugs 463 | 464 | Please file feature requests and bugs at the [issue tracker][tracker]. 465 | 466 | [tracker]: https://github.com/frappe-dart/stream_transformers/issues 467 | [Frappe]: https://github.com/danschultz/frappe 468 | [test_runner]: https://pub.dartlang.org/packages/test_runner 469 | -------------------------------------------------------------------------------- /lib/src/buffer_when.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Pauses the delivery of events from the source stream when the signal stream 4 | /// delivers a value of `true`. The buffered events are delivered when the signal 5 | /// delivers a value of `false`. Errors originating from the source and signal 6 | /// streams will be forwarded to the transformed stream and will not be buffered. 7 | /// If the source stream is a broadcast stream, then the transformed stream will 8 | /// also be a broadcast stream. 9 | /// 10 | /// **Example:** 11 | /// 12 | /// var controller = new StreamController(); 13 | /// var signal = new StreamController(); 14 | /// 15 | /// var stream = controller.stream; 16 | /// 17 | /// var buffered = stream.transform(new BufferWhen(signal.stream)); 18 | /// 19 | /// controller.add(1); 20 | /// signal.add(true); 21 | /// controller.add(2); 22 | /// 23 | /// buffered.listen(print); // Prints: 1 24 | class BufferWhen implements StreamTransformer { 25 | final Stream _signal; 26 | 27 | BufferWhen(Stream signal) : _signal = signal; 28 | 29 | Stream bind(Stream stream) { 30 | StreamSubscription signalSubscription; 31 | StreamSubscription streamSubscription; 32 | StreamController controller; 33 | 34 | void done() { 35 | signalSubscription.cancel(); 36 | streamSubscription.cancel(); 37 | controller.close(); 38 | } 39 | 40 | void onListen() { 41 | streamSubscription = stream.listen(controller.add, onError: controller.addError, onDone: done); 42 | signalSubscription = _signal.listen((isBuffering) { 43 | if (isBuffering) { 44 | streamSubscription.pause(); 45 | } else { 46 | streamSubscription.resume(); 47 | } 48 | }); 49 | } 50 | 51 | void onPause() { 52 | signalSubscription.pause(); 53 | streamSubscription.pause(); 54 | } 55 | 56 | void onResume() { 57 | signalSubscription.resume(); 58 | streamSubscription.resume(); 59 | } 60 | 61 | controller = _createControllerLikeStream( 62 | stream: stream, 63 | onListen: onListen, 64 | onResume: onResume, 65 | onPause: onPause, 66 | onCancel: done); 67 | 68 | return controller.stream; 69 | } 70 | } -------------------------------------------------------------------------------- /lib/src/buffer_with_count.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Projects each element of an observable sequence into zero or more buffers which are produced based on element count information. 4 | /// 5 | /// **Example:** 6 | /// 7 | /// var controller = new StreamController(); 8 | /// 9 | /// var stream = controller.stream; 10 | /// 11 | /// var buffered = stream.transform(new BufferWithCount(2, 1)); 12 | /// 13 | /// controller.add(1); 14 | /// controller.add(2); 15 | /// controller.add(3); 16 | /// controller.add(4); 17 | /// controller.close(); 18 | /// 19 | /// buffered.listen(print); // Prints: [1, 2], [2, 3], [3, 4], [4] 20 | class BufferWithCount implements StreamTransformer { 21 | 22 | final int _count; 23 | final int _skip; 24 | final int _bufferKeep; 25 | 26 | BufferWithCount(int count, [int skip]) : 27 | _count = count, 28 | _skip = (skip == null) ? count : skip, 29 | _bufferKeep = count - ((skip == null) ? count : skip) { 30 | if (_skip <= 0 || _skip > _count) throw new ArgumentError('skip has to be greater than zero and smaller than count'); 31 | } 32 | 33 | Stream bind(Stream stream) { 34 | List buffer = []; 35 | 36 | return _bindStream(like: stream, onListen: (EventSink> sink) { 37 | 38 | void done() { 39 | if (buffer.isNotEmpty) sink.add(buffer); 40 | sink.close(); 41 | } 42 | 43 | void onData(T data) { 44 | buffer.add(data); 45 | 46 | if (buffer.length == _count) { 47 | sink.add(buffer); 48 | buffer = buffer.sublist(_count - _bufferKeep); 49 | } 50 | } 51 | 52 | return stream.listen(onData, onError: sink.addError, onDone: done); 53 | }); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /lib/src/combine.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Combines the latest values of two streams using a two argument function. 4 | /// The combining function will not be called until each stream delivers its 5 | /// first value. After the first value of each stream is delivered, the 6 | /// combining function will be invoked for each event from the source streams. 7 | /// Errors occurring on the streams will be forwarded to the transformed 8 | /// stream. If the source stream is a broadcast stream, then the transformed 9 | /// stream will also be a broadcast stream. 10 | /// 11 | /// **Example:** 12 | /// 13 | /// var controller1 = new StreamController(); 14 | /// var controller2 = new StreamController(); 15 | /// 16 | /// var combined = controller1.stream.transform(new Combine(controller2.stream, (a, b) => a + b)); 17 | /// 18 | /// combined.listen(print); 19 | /// 20 | /// controller1.add(1); 21 | /// controller2.add(1); // Prints: 2 22 | /// controller1.add(2); // Prints: 3 23 | /// controller2.add(2); // Prints: 4 24 | class Combine implements StreamTransformer { 25 | /// Combines a list of stream together, where the returned stream will contain 26 | /// `List`s that contain the current values of each of the streams. 27 | static Stream all(List streams) { 28 | return _bindStream(onListen: (EventSink sink) { 29 | Stream merged = Merge.all(streams.map((stream) => stream.map((event) => [stream, event]))); 30 | Stream> values = merged.transform(new Scan({}, (previous, current) { 31 | var values = new Map.from(previous); 32 | values[current.first] = current.last; 33 | return values; 34 | })); 35 | 36 | return values 37 | .where((values) => values.length == streams.length) 38 | .map((values) => streams.map((stream) => values[stream]).toList(growable: false)) 39 | .listen((combined) => sink.add(combined), onError: sink.addError, onDone: sink.close); 40 | }); 41 | } 42 | 43 | final Stream _other; 44 | final Combiner _combiner; 45 | 46 | Combine(Stream other, Combiner combiner) : 47 | _other = other, 48 | _combiner = combiner; 49 | 50 | Stream bind(Stream stream) { 51 | return _bindStream(like: stream, onListen: (EventSink sink) { 52 | return Combine.all([stream, _other]).listen( 53 | (values) => sink.add(_combiner(values.first, values.last)), 54 | onError: sink.addError, 55 | onDone: sink.close); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/concat.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Concatenates two streams into one stream by delivering the values of the source stream, 4 | /// and then delivering the values of the other stream once the source stream completes. 5 | /// This means that it's possible that events from the second stream might not be included 6 | /// if the source stream hasn't completed. Use `Concat.all()` to concatenate many streams. 7 | /// 8 | /// Errors will be forwarded from either stream, whether or not the source stream has 9 | /// completed. If the source stream is a broadcast stream, then the transformed stream will 10 | /// also be a broadcast stream. 11 | /// 12 | /// **Example:** 13 | /// 14 | /// var source = new StreamController(); 15 | /// var other = new StreamController(); 16 | /// 17 | /// var stream = source.stream.transform(new Concat(other.stream)); 18 | /// stream.listen(print); 19 | /// 20 | /// other..add(1)..add(2); 21 | /// source..add(3)..add(4)..close(); 22 | /// 23 | /// // 3 24 | /// // 4 25 | /// // 1 26 | /// // 2 27 | class Concat implements StreamTransformer { 28 | static Stream all(Iterable streams) { 29 | return _bindStream(like: streams.first, onListen: (EventSink sink) { 30 | return new Stream.fromIterable(streams).transform(new ConcatAll()) 31 | .listen(sink.add, onError: sink.addError, onDone: sink.close); 32 | }); 33 | } 34 | 35 | final Stream _other; 36 | 37 | Concat(Stream other) : _other = other; 38 | 39 | Stream bind(Stream stream) { 40 | return all([stream, _other]); 41 | } 42 | } -------------------------------------------------------------------------------- /lib/src/concat_all.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Concatenates a stream of streams into a single stream, by delivering the first stream's 4 | /// values, and then delivering the next stream's values after the previous stream has 5 | /// completed. 6 | /// 7 | /// This means that it's possible that events from the second stream might not be included 8 | /// if the source stream hasn't completed. Use `Concat.all()` to concatenate many streams. 9 | /// 10 | /// Errors will be forwarded from either stream, whether or not the source stream has 11 | /// completed. If the source stream is a broadcast stream, then the transformed stream will 12 | /// also be a broadcast stream. 13 | /// 14 | /// **Example:** 15 | /// 16 | /// var source = new StreamController(); 17 | /// var other1 = new StreamController(); 18 | /// var other2 = new StreamController(); 19 | /// 20 | /// source..add(other1.stream)..add(other2.stream); 21 | /// 22 | /// other2..add(1)..add(2); 23 | /// other1..add(3)..add(4)..close(); 24 | /// 25 | /// var stream = source.stream.transform(new ConcatAll()); 26 | /// stream.listen(print); 27 | /// 28 | /// // 3 29 | /// // 4 30 | /// // 1 31 | /// // 2 32 | class ConcatAll, R> implements StreamTransformer { 33 | ConcatAll(); 34 | 35 | Stream bind(Stream stream) { 36 | return stream.asyncExpand((stream) => stream); 37 | } 38 | } -------------------------------------------------------------------------------- /lib/src/debounce.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Delivers the last event in the stream after the duration has passed 4 | /// without receiving an event. 5 | /// 6 | /// Errors occurring on the source stream will not be ignored. If the 7 | /// source stream is a broadcast stream, then the transformed stream will 8 | /// also be a broadcast stream. 9 | /// 10 | /// source: asdf----asdf---- 11 | /// source.debounce(2): -----f-------f-- 12 | /// 13 | /// **Example:** 14 | /// 15 | /// var controller = new StreamController(); 16 | /// 17 | /// var debounced = controller.stream.transform(new Debounce(new Duration(seconds:1))); 18 | /// debounced.listen(print); 19 | /// 20 | /// controller.add(1); 21 | /// controller.add(2); 22 | /// controller.add(3); 23 | /// 24 | /// // Prints: 3 25 | class Debounce implements StreamTransformer { 26 | final Duration _duration; 27 | 28 | Debounce(Duration duration) : _duration = duration; 29 | 30 | Stream bind(Stream stream) { 31 | return stream.transform(new FlatMapLatest((value) => new Stream.periodic(_duration, (_) => value).take(1))); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/src/delay.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Throttles the delivery of each event by a given duration. Errors occurring 4 | /// on the source stream will not be delayed. If the source stream is a broadcast 5 | /// stream, then the transformed stream will also be a broadcast stream. 6 | /// 7 | /// **Example:** 8 | /// 9 | /// var controller = new StreamController(); 10 | /// var delayed = controller.stream.transform(new Delay(new Duration(seconds: 2))); 11 | /// 12 | /// // source: asdf---- 13 | /// // source.delayed(2): --a--s--d--f--- 14 | class Delay implements StreamTransformer { 15 | final Duration _duration; 16 | 17 | Delay(Duration duration) : _duration = duration; 18 | 19 | Stream bind(Stream stream) { 20 | return stream.asyncMap((event) => new Future.delayed(_duration, () => event)); 21 | } 22 | } -------------------------------------------------------------------------------- /lib/src/do_action.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Invokes a side-effect function for each value, error and done event in the stream. 4 | /// 5 | /// This is useful for debugging, but also invoking `preventDefault` for browser events. 6 | /// Side effects will only be invoked once if the transformed stream has multiple 7 | /// subscribers. 8 | /// 9 | /// Errors occurring on the source stream will be forwarded to the returned stream, even 10 | /// when passing an error handler to `DoAction`. If the source stream is a broadcast 11 | /// stream, then the transformed stream will also be a broadcast stream. 12 | /// 13 | /// **Example:** 14 | /// 15 | /// var controller = new StreamController(); 16 | /// var sideEffect = new DoAction((value) => print("Do Next: $value"), 17 | /// onError: (error) => print("Do Error: $error"), 18 | /// onDone: () => print("Do Done")); 19 | /// var stream = controller.stream.transform(sideEffect); 20 | /// 21 | /// stream.listen((value) => print("Next: $value"), 22 | /// onError: (e) => print("Error: $e"), 23 | /// onDone: () => print("Done")); 24 | /// 25 | /// controller..add(1)..add(2)..close(); 26 | /// 27 | /// // Do Next: 1 28 | /// // Next: 1 29 | /// // Do Next: 2 30 | /// // Next: 2 31 | /// // Do Done 32 | /// // Done 33 | class DoAction implements StreamTransformer { 34 | final Function _onData; 35 | final Function _onError; 36 | final Function _onDone; 37 | 38 | DoAction(void onData(T value), {Function onError, void onDone()}) : 39 | _onData = onData, 40 | _onError = onError, 41 | _onDone = onDone; 42 | 43 | Stream bind(Stream stream) { 44 | var input = stream.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 45 | StreamSubscription subscription; 46 | return _bindStream(like: stream, onListen: (EventSink sink) { 47 | subscription = input.listen(_onData, onError: _onError, onDone: _onDone); 48 | return input.listen(sink.add, onError: sink.addError, onDone: sink.close); 49 | }, onCancel: () => subscription.cancel()); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/src/event_stream.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// A stream that normalizes *next* and *end* events into a single event stream, 4 | /// making it easier for instance to know when a stream has finished. 5 | /// 6 | /// Keeping this private for now, but might make it public in the future. 7 | class _EventStream extends StreamView<_Event> { 8 | _EventStream(Stream stream) : super(_bindStream(onListen: (EventSink sink) { 9 | return stream.listen( 10 | (data) => sink.add(new _Event.next(data)), 11 | onError: sink.addError, 12 | onDone: () => sink..add(new _Event.end())..close()); 13 | })); 14 | } 15 | 16 | class _Event { 17 | final _EventType type; 18 | final T value; 19 | 20 | bool get hasValue => type == isNext; 21 | 22 | bool get isNext => type == _EventType.NEXT; 23 | bool get isEnd => type == _EventType.END; 24 | 25 | _Event(this.type, this.value); 26 | 27 | factory _Event.next(T value) => new _Event(_EventType.NEXT, value); 28 | 29 | factory _Event.end() => new _Event(_EventType.END, null); 30 | } 31 | 32 | class _EventType { 33 | static const NEXT = const _EventType("next"); 34 | static const END = const _EventType("end"); 35 | 36 | final String value; 37 | 38 | const _EventType(this.value); 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/flat_map.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Spawns a new stream from a function for each event in the source stream. 4 | /// The returned stream will contain the events and errors from each of the 5 | /// spawned streams until they're closed. If the source stream is a broadcast 6 | /// stream, then the transformed stream will also be a broadcast stream. 7 | /// 8 | /// **Example:** 9 | /// 10 | /// var controller = new StreamController(); 11 | /// var flapMapped = controller.stream.transform(new FlatMap((value) => new Stream.fromIterable([value + 1])); 12 | /// 13 | /// flatMapped.listen(print); 14 | /// 15 | /// controller.add(1); // Prints: 2 16 | /// controller.add(2); // Prints: 3 17 | class FlatMap implements StreamTransformer { 18 | final Mapper> _convert; 19 | 20 | FlatMap(Mapper> convert) : _convert = convert; 21 | 22 | Stream bind(Stream stream) { 23 | var subscriptions = new Queue(); 24 | 25 | StreamSubscription onListen(EventSink sink) { 26 | var openStreams = []; 27 | var isDone = false; 28 | 29 | void closeSinkIfDone() { 30 | if (isDone && openStreams.isEmpty) { 31 | sink.close(); 32 | } 33 | } 34 | 35 | void onData(data) { 36 | Stream mappedStream = _convert(data); 37 | openStreams.add(mappedStream); 38 | subscriptions.add(mappedStream.listen( 39 | (event) => sink.add(event), 40 | onError: sink.addError, 41 | onDone: () { 42 | openStreams.remove(mappedStream); 43 | closeSinkIfDone(); 44 | })); 45 | } 46 | 47 | return stream.listen( 48 | onData, 49 | onDone: () { 50 | isDone = true; 51 | closeSinkIfDone(); 52 | }, 53 | onError: (error, stackTrace) => sink.addError(error, stackTrace)); 54 | }; 55 | 56 | void onCancel() { 57 | while (subscriptions.isNotEmpty) { 58 | subscriptions.removeFirst().cancel(); 59 | } 60 | } 61 | 62 | return _bindStream(like: stream, sync: true, onListen: onListen, onCancel: onCancel); 63 | } 64 | } -------------------------------------------------------------------------------- /lib/src/flat_map_latest.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Similar to `FlatMap`, but instead of including events from all spawned 4 | /// streams, only includes the ones from the latest stream. Think of this 5 | /// as stream switching. 6 | /// 7 | /// **Example:** 8 | /// 9 | /// var controller = new StreamController(); 10 | /// var latest = controller.stream.transform(new FlatMapLatest((value) => new Stream.fromIterable([value + 1])); 11 | /// 12 | /// latest.listen(print); 13 | /// 14 | /// controller.add(1); 15 | /// controller.add(2); // Prints: 3 16 | class FlatMapLatest implements StreamTransformer { 17 | final Mapper> _convert; 18 | 19 | FlatMapLatest(Mapper> convert) : _convert = convert; 20 | 21 | Stream bind(Stream stream) { 22 | var input = stream.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 23 | StreamSubscription doneSubscription; 24 | 25 | StreamSubscription onListen(EventSink sink) { 26 | var done = new StreamController.broadcast(); 27 | doneSubscription = input.listen((value) => done.add(true), onError: (_) {}, onDone: () => done.close()); 28 | 29 | return input 30 | .transform(new FlatMap((value) => _convert(value).transform(new TakeUntil(done.stream)))) 31 | .listen((value) => sink.add(value), onError: sink.addError, onDone: () => sink.close()); 32 | } 33 | 34 | return _bindStream(like: stream, onListen: onListen, onCancel: () => doneSubscription.cancel()); 35 | } 36 | } -------------------------------------------------------------------------------- /lib/src/merge.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Combines the events from two streams into a single stream. Errors occurring 4 | /// on any merged stream will be forwarded to the transformed stream. If the 5 | /// source stream is a broadcast stream, then the transformed stream will also 6 | /// be a broadcast stream. 7 | /// 8 | /// **Example:** 9 | /// 10 | /// var controller1 = new StreamController(); 11 | /// var controller2 = new StreamController(); 12 | /// 13 | /// var merged = controller1.stream.transform(new Merge(controller2.stream)); 14 | /// 15 | /// merged.listen(print); 16 | /// 17 | /// controller1.add(1); // Prints: 1 18 | /// controller2.add(2); // Prints: 2 19 | /// controller1.add(3); // Prints: 3 20 | /// controller2.add(4); // Prints: 4 21 | class Merge implements StreamTransformer { 22 | /// Returns a stream that contains the events from a list of streams. 23 | static Stream all(Iterable streams) { 24 | return new Stream.fromIterable(streams).transform(new MergeAll()); 25 | } 26 | 27 | final Stream _other; 28 | 29 | Merge(Stream other) : _other = other; 30 | 31 | Stream bind(Stream stream) { 32 | StreamSubscription subscriptionA; 33 | StreamSubscription subscriptionB; 34 | var completerA = new Completer(); 35 | var completerB = new Completer(); 36 | StreamController controller; 37 | 38 | void onListen() { 39 | subscriptionA = stream.listen(controller.add, onError: controller.addError, onDone: completerA.complete); 40 | subscriptionB = _other.listen(controller.add, onError: controller.addError, onDone: completerB.complete); 41 | } 42 | 43 | void onPause() { 44 | subscriptionA.pause(); 45 | subscriptionB.pause(); 46 | } 47 | 48 | void onResume() { 49 | subscriptionA.resume(); 50 | subscriptionB.resume(); 51 | } 52 | 53 | void onCancel() { 54 | subscriptionA.cancel(); 55 | subscriptionB.cancel(); 56 | } 57 | 58 | controller = _createControllerLikeStream( 59 | stream: stream, 60 | onListen: onListen, 61 | onPause: onPause, 62 | onResume: onResume, 63 | onCancel: onCancel); 64 | 65 | Future.wait([completerA.future, completerB.future]).then((_) => controller.close()); 66 | 67 | return controller.stream; 68 | 69 | // TODO(Dan): This would be the ideal implementation, but is causing some tests to fail. 70 | // return _bindStream(like: stream, onListen: (EventSink sink) { 71 | // return all([stream, _other]).listen(sink.add, onError: sink.addError, onDone: sink.close); 72 | // }); 73 | } 74 | } -------------------------------------------------------------------------------- /lib/src/merge_all.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Combines the events from a stream of streams into a single stream. 4 | /// 5 | /// The returned stream will contain the errors occurring on any stream. If the source 6 | /// stream is a broadcast stream, then the transformed stream will also be a broadcast 7 | /// stream. 8 | /// 9 | /// **Example:** 10 | /// 11 | /// var source = new StreamController(); 12 | /// var stream1 = new Stream.fromIterable([1, 2]); 13 | /// var stream2 = new Stream.fromIterable([3, 4]); 14 | /// 15 | /// var merged = source.stream.transform(new MergeAll()); 16 | /// source..add(stream1)..add(stream2); 17 | /// 18 | /// merged.listen(print); 19 | /// 20 | /// // 1 21 | /// // 2 22 | /// // 3 23 | /// // 4 24 | class MergeAll implements StreamTransformer { 25 | MergeAll(); 26 | 27 | Stream bind(Stream stream) { 28 | return stream.transform(new FlatMap((stream) => stream)); 29 | } 30 | } -------------------------------------------------------------------------------- /lib/src/sample_on.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Takes the latest value of the source stream whenever the trigger stream 4 | /// produces an event. 5 | /// 6 | /// Errors that happen on the source stream will be forwarded to the transformed 7 | /// stream. If the source stream is a broadcast stream, then the transformed 8 | /// stream will also be a broadcast stream. 9 | /// 10 | /// **Example:** 11 | /// 12 | /// // values start at 0 13 | /// var source = new Stream.periodic(new Duration(seconds: 1), (i) => i); 14 | /// var trigger = new Stream.periodic(new Duration(seconds: 2), (i) => i); 15 | /// 16 | /// var stream = source.stream.transform(new SampleOn(trigger.stream)).take(3); 17 | /// 18 | /// stream.listen(print); 19 | /// 20 | /// // 0 21 | /// // 2 22 | /// // 4 23 | class SampleOn implements StreamTransformer { 24 | final Stream _trigger; 25 | 26 | SampleOn(Stream trigger) : _trigger = trigger; 27 | 28 | Stream bind(Stream stream) { 29 | var trigger = _trigger.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 30 | StreamSubscription triggerSubscription; 31 | 32 | StreamSubscription onListen(EventSink sink) { 33 | var triggerDone = new StreamController(); 34 | 35 | // The trigger always needs to have a subscription when the returned stream is being listened to. 36 | // This prevents cases where the returned stream will contain data events after the trigger 37 | // delivers an event. 38 | triggerSubscription = trigger.listen(null, onDone: () => triggerDone.add(true)); 39 | 40 | return stream 41 | .transform(new FlatMapLatest((value) { 42 | return _bindStream(onListen: (EventSink sink) { 43 | return trigger.listen((_) => sink.add(value), onError: sink.addError, onDone: sink.close); 44 | }); 45 | })) 46 | .transform(new TakeUntil(triggerDone.stream)) 47 | .listen(sink.add, onError: sink.addError, onDone: sink.close); 48 | } 49 | 50 | return _bindStream(like: stream, onListen: onListen, onCancel: () => triggerSubscription.cancel()); 51 | } 52 | } -------------------------------------------------------------------------------- /lib/src/sample_periodically.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Takes the latest value of the source stream at a specified interval. 4 | /// 5 | /// Errors that happen on the source stream will be forwarded to the transformed 6 | /// stream. If the source stream is a broadcast stream, then the transformed 7 | /// stream will also be a broadcast stream. 8 | /// 9 | /// **Example:** 10 | /// 11 | /// // values start at 0 12 | /// var source = new Stream.periodic(new Duration(seconds: 1), (i) => i); 13 | /// var stream = source.stream.transform(new SamplePeriodically(new Duration(seconds: 2))).take(3); 14 | /// 15 | /// stream.listen(print); 16 | /// 17 | /// // 0 18 | /// // 2 19 | /// // 4 20 | class SamplePeriodically implements StreamTransformer { 21 | final Duration _duration; 22 | 23 | SamplePeriodically(Duration duration) : _duration = duration; 24 | 25 | Stream bind(Stream stream) { 26 | return stream.transform(new SampleOn(new Stream.periodic(_duration))); 27 | } 28 | } -------------------------------------------------------------------------------- /lib/src/scan.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Reduces the values of a stream into a single value by using an initial 4 | /// value and an accumulator function. The function is passed the previous 5 | /// accumulated value and the current value of the stream. This is useful 6 | /// for maintaining state using a stream. Errors occurring on the source 7 | /// stream will be forwarded to the transformed stream. If the source stream 8 | /// is a broadcast stream, then the transformed stream will also be a 9 | /// broadcast stream. 10 | /// 11 | /// **Example:** 12 | /// 13 | /// var button = new ButtonElement(); 14 | /// 15 | /// var clickCount = button.onClick.transform(new Scan(0, (previous, current) => previous + 1)); 16 | /// 17 | /// clickCount.listen(print); 18 | /// 19 | /// // [button click] .. prints: 1 20 | /// // [button click] .. prints: 2 21 | class Scan implements StreamTransformer { 22 | final R _initialValue; 23 | final Function _combine; 24 | 25 | Scan(R initialValue, R combine(R previous, A current)) : 26 | _initialValue = initialValue, 27 | _combine = combine; 28 | 29 | Stream bind(Stream stream) { 30 | return _bindStream(like: stream, onListen: (EventSink sink) { 31 | var value = _initialValue; 32 | sink.add(value); 33 | 34 | void onData(A data) { 35 | value = _combine(value, data); 36 | sink.add(value); 37 | } 38 | 39 | return stream.listen(onData, onError: sink.addError, onDone: sink.close); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/select_first.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Forwards events from the first stream to deliver an event. 4 | /// 5 | /// Errors are forwarded from both streams until a stream is selected. Once a stream is selected, 6 | /// only errors from the selected stream are forwarded. If the source stream is a broadcast stream, 7 | /// then the transformed stream will also be a broadcast stream. 8 | /// 9 | /// **Example:** 10 | /// 11 | /// var stream1 = new Stream.periodic(new Duration(seconds: 1)).map((_) => "Stream 1"); 12 | /// var stream2 = new Stream.periodic(new Duration(seconds: 2)).map((_) => "Stream 2"); 13 | /// 14 | /// var selected = stream1.transform(new SelectFirst(stream2)).take(1); 15 | /// selected.listen(print); 16 | /// 17 | /// // Stream 1 18 | class SelectFirst implements StreamTransformer { 19 | final Stream _other; 20 | 21 | SelectFirst(Stream other) : _other = other; 22 | 23 | Stream bind(Stream stream) { 24 | return _bindStream(like: stream, onListen: (EventSink sink) { 25 | var input = stream.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 26 | var other = _other.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 27 | 28 | var a = input.map((value) => {"value": value, "stream": input}); 29 | var b = other.map((value) => {"value": value, "stream": other}); 30 | 31 | var selected = a.transform(new Merge(b)).take(1); 32 | 33 | return selected 34 | .asyncExpand((selected) { 35 | var firstValue = selected["value"]; 36 | var stream = selected["stream"] as Stream; 37 | return stream.transform(new StartWith(firstValue)); 38 | }) 39 | .listen(sink.add, onError: sink.addError, onDone: sink.close); 40 | }); 41 | } 42 | } -------------------------------------------------------------------------------- /lib/src/skip_until.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Waits to deliver events from a stream until the signal `Stream` delivers a 4 | /// value. Errors that happen on the source stream will be forwarded once the 5 | /// `Stream` delivers its value. Errors happening on the signal stream will be 6 | /// forwarded immediately. If the source stream is a broadcast stream, then the 7 | /// transformed stream will also be a broadcast stream. 8 | /// 9 | /// **Example:** 10 | /// 11 | /// var signal = new StreamController(); 12 | /// var controller = new StreamController(); 13 | /// 14 | /// var skipStream = controller.stream.transform(new SkipUntil(signal.stream)); 15 | /// 16 | /// skipStream.listen(print); 17 | /// 18 | /// controller.add(1); 19 | /// controller.add(2); 20 | /// signal.add(true); 21 | /// controller.add(3); // Prints: 3 22 | /// controller.add(4); // Prints: 4 23 | class SkipUntil implements StreamTransformer { 24 | final Stream _signal; 25 | 26 | SkipUntil(Stream signal) : _signal = signal; 27 | 28 | Stream bind(Stream stream) { 29 | return stream.transform(new When(_signal.map((_) => true))); 30 | } 31 | } -------------------------------------------------------------------------------- /lib/src/start_with.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Prepends values to the beginning of a stream. Use [StartWith.many()] to prepend 4 | /// multiple values. 5 | /// 6 | /// Errors on the source stream will be forwarded to the transformed stream. If the 7 | /// source stream is a broadcast stream, then the transformed stream will also be a 8 | /// broadcast stream. 9 | /// 10 | /// **Example:** 11 | /// 12 | /// var source = new Stream.fromIterable([2, 3]); 13 | /// var stream = source.transform(new StartWith(1)); 14 | /// stream.listen(print); 15 | /// 16 | /// // 1 17 | /// // 2 18 | /// // 3 19 | class StartWith implements StreamTransformer { 20 | final Stream _beginning; 21 | 22 | StartWith._(Stream beginning) : _beginning = beginning; 23 | 24 | factory StartWith(T value) => new StartWith._(new Stream.fromIterable([value])); 25 | 26 | factory StartWith.many(Iterable values) => new StartWith._(new Stream.fromIterable(values)); 27 | 28 | Stream bind(Stream stream) { 29 | var controller = _createControllerLikeStream(stream: stream); 30 | controller.addStream(_beginning.transform(new Merge(stream))).then((_) => controller.close()); 31 | return controller.stream; 32 | } 33 | } -------------------------------------------------------------------------------- /lib/src/take_until.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Delivers events from the source stream until the signal `Stream` produces a value. 4 | /// At which point, the transformed stream closes. The returned stream will continue 5 | /// to deliver values if the signal stream closes without a value. 6 | /// 7 | /// This is useful for automatically cancelling a stream subscription to prevent memory 8 | /// leaks. Errors that happen on the source and signal stream will be forwarded to the 9 | /// transformed stream. If the source stream is a broadcast stream, then the transformed 10 | /// stream will also be a broadcast stream. 11 | /// 12 | /// **Example:** 13 | /// 14 | /// var signal = new StreamController(); 15 | /// var controller = new StreamController(); 16 | /// 17 | /// var takeUntil = controller.stream.transform(new TakeUntil(signal.stream)); 18 | /// 19 | /// takeUntil.listen(print, onDone: () => print("done")); 20 | /// 21 | /// controller.add(1); // Prints: 1 22 | /// controller.add(2); // Prints: 2 23 | /// signal.add(true); // Prints: done 24 | /// controller.add(3); 25 | /// controller.add(4); 26 | class TakeUntil implements StreamTransformer { 27 | final Stream _signal; 28 | 29 | TakeUntil(Stream signal) : _signal = signal; 30 | 31 | factory TakeUntil.fromFuture(Future future) => new TakeUntil(new Stream.fromFuture(future)); 32 | 33 | Stream bind(Stream stream) { 34 | StreamSubscription signalSubscription; 35 | 36 | StreamSubscription onListen(EventSink sink) { 37 | StreamSubscription inputSubscription; 38 | 39 | void done() { 40 | inputSubscription.cancel(); 41 | sink.close(); 42 | } 43 | 44 | signalSubscription = _signal.take(1).listen((_) => done(), onError: sink.addError); 45 | 46 | inputSubscription = stream.listen( 47 | (value) => sink.add(value), 48 | onError: sink.addError, 49 | onDone: () => done()); 50 | 51 | return inputSubscription; 52 | } 53 | 54 | return _bindStream(like: stream, onListen: onListen, onCancel: () => signalSubscription.cancel()); 55 | } 56 | } -------------------------------------------------------------------------------- /lib/src/typedefs.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | typedef R Combiner(A a, B b); 4 | typedef T Mapper(S value); -------------------------------------------------------------------------------- /lib/src/util.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | Stream _bindStream({Stream like, StreamSubscription onListen(EventSink sink), onCancel(), bool sync: false}) { 4 | StreamSubscription subscription; 5 | StreamController controller; 6 | 7 | controller = _createControllerLikeStream( 8 | stream: like, 9 | sync: sync, 10 | onListen: () => subscription = onListen(controller), 11 | onPause: () => subscription.pause(), 12 | onResume: () => subscription.resume(), 13 | onCancel: () { 14 | var futures = [onCancel, subscription.cancel] 15 | .where((function) => function != null) 16 | .map((function) => function()) 17 | .where((future) => future != null); 18 | return Future.wait(futures); 19 | }); 20 | 21 | return controller.stream; 22 | } 23 | 24 | StreamController _createControllerLikeStream({Stream stream, void onListen(), void onCancel(), void onPause(), void onResume(), bool sync: false}) { 25 | if (stream == null || !stream.isBroadcast) { 26 | return new StreamController(onListen: onListen, onCancel: onCancel, onPause: onPause, onResume: onResume, sync: sync); 27 | } else { 28 | return new StreamController.broadcast(onListen: onListen, onCancel: onCancel, sync: sync); 29 | } 30 | } 31 | 32 | // TODO: This is not used. 33 | Future _cancelSubscriptions(Iterable subscriptions) { 34 | var futures = subscriptions 35 | .map((subscription) => subscription.cancel()) 36 | .where((future) => future != null); 37 | return Future.wait(futures); 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/when.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Starts delivering events from the source stream when the signal stream 4 | /// delivers a value of `true`. Events are skipped when the signal stream 5 | /// delivers a value of `false`. Errors from the source or toggle stream will be 6 | /// forwarded to the transformed stream. If the source stream is a broadcast 7 | /// stream, then the transformed stream will also be a broadcast stream. 8 | /// 9 | /// **Example:** 10 | /// 11 | /// var controller = new StreamController(); 12 | /// var signal = new StreamController(); 13 | /// 14 | /// var whenStream = controller.stream.transform(new When(signal.stream)); 15 | /// 16 | /// whenStream.listen(print); 17 | /// 18 | /// controller.add(1); 19 | /// signal.add(true); 20 | /// controller.add(2); // Prints: 2 21 | /// signal.add(false); 22 | /// controller.add(3); 23 | class When implements StreamTransformer { 24 | final Stream _toggle; 25 | 26 | When(Stream toggle) : _toggle = toggle; 27 | 28 | Stream bind(Stream stream) { 29 | var input = stream.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 30 | 31 | return _bindStream(like: stream, onListen: (EventSink sink) { 32 | return _toggle 33 | .transform(new FlatMapLatest((isToggled) { 34 | if (isToggled) { 35 | return _bindStream(onListen: (EventSink sink) { 36 | return input.listen(sink.add, onError: sink.addError, onDone: sink.close); 37 | }); 38 | } else { 39 | return new Stream.fromIterable([]); 40 | } 41 | })) 42 | .transform(new TakeUntil(new _EventStream(input).where((event) => event.isEnd))) 43 | .listen((value) => sink.add(value), onError: sink.addError); 44 | }); 45 | } 46 | } -------------------------------------------------------------------------------- /lib/src/zip.dart: -------------------------------------------------------------------------------- 1 | part of stream_transformers; 2 | 3 | /// Combines the events of two streams into one by invoking a combiner function 4 | /// that is invoked when each stream delivers an event at each index. The 5 | /// transformed stream finishes when either source stream finishes. Errors from 6 | /// either stream will be forwarded to the transformed stream. If the source 7 | /// stream is a broadcast stream, then the transformed stream will also be a 8 | /// broadcast stream. 9 | /// 10 | /// **Example:** 11 | /// 12 | /// var controller1 = new StreamController(); 13 | /// var controller2 = new StreamController(); 14 | /// 15 | /// var zipped = controller1.stream.transform(new Zip(controller2.stream, (a, b) => a + b)); 16 | /// 17 | /// zipped.listen(print); 18 | /// 19 | /// controller1.add(1); 20 | /// controller1.add(2); 21 | /// controller2.add(1); // Prints 2 22 | /// controller1.add(3); 23 | /// controller2.add(2); // Prints 4 24 | /// controller2.add(3); // Prints 6 25 | class Zip implements StreamTransformer { 26 | final Stream _other; 27 | final Combiner _combiner; 28 | 29 | Zip(Stream other, Combiner combiner) : 30 | _other = other, 31 | _combiner = combiner; 32 | 33 | Stream bind(Stream stream) { 34 | Queue appendToQueue(Queue queue, element) => queue..add(element); 35 | 36 | var input = stream.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 37 | var other = _other.asBroadcastStream(onCancel: (subscription) => subscription.cancel()); 38 | 39 | var bufferA = input.transform(new Scan(new Queue(), appendToQueue)); 40 | var bufferB = other.transform(new Scan(new Queue(), appendToQueue)); 41 | 42 | var combined = Combine.all([bufferA, bufferB]) as Stream>; 43 | 44 | var done = Merge.all([ 45 | new _EventStream(input).where((event) => event.isEnd), 46 | new _EventStream(other).where((event) => event.isEnd)]) 47 | .handleError((e) => e) 48 | .take(1); 49 | 50 | return _bindStream(like: stream, onListen: (EventSink sink) { 51 | return combined 52 | .where((queues) => queues.first.isNotEmpty && queues.last.isNotEmpty) 53 | .map((queues) => _combiner(queues.first.removeFirst(), queues.last.removeFirst())) 54 | .transform(new TakeUntil(done)) 55 | .listen((value) => sink.add(value), onError: sink.addError, onDone: () => sink.close()); 56 | }); 57 | } 58 | } -------------------------------------------------------------------------------- /lib/stream_transformers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, . All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | /// The stream_transformers library. 5 | /// 6 | /// This is an awesome library. More dartdocs go here. 7 | library stream_transformers; 8 | 9 | import 'dart:async'; 10 | import 'dart:collection'; 11 | 12 | part 'src/buffer_when.dart'; 13 | part 'src/buffer_with_count.dart'; 14 | part 'src/combine.dart'; 15 | part 'src/concat.dart'; 16 | part 'src/concat_all.dart'; 17 | part 'src/debounce.dart'; 18 | part 'src/delay.dart'; 19 | part 'src/do_action.dart'; 20 | part 'src/event_stream.dart'; 21 | part 'src/flat_map.dart'; 22 | part 'src/flat_map_latest.dart'; 23 | part 'src/merge.dart'; 24 | part 'src/merge_all.dart'; 25 | part 'src/sample_on.dart'; 26 | part 'src/sample_periodically.dart'; 27 | part 'src/select_first.dart'; 28 | part 'src/scan.dart'; 29 | part 'src/skip_until.dart'; 30 | part 'src/start_with.dart'; 31 | part 'src/take_until.dart'; 32 | part 'src/typedefs.dart'; 33 | part 'src/when.dart'; 34 | part 'src/util.dart'; 35 | part 'src/zip.dart'; 36 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: stream_transformers 2 | description: > 3 | A Dart package for transforming streams. 4 | version: 0.3.0+3 5 | author: Dan Schultz 6 | homepage: https://www.github.com/danschultz/stream_transformers 7 | dependencies: 8 | dev_dependencies: 9 | guinness: '>=0.1.16 <0.2.0' 10 | unittest: '>=0.11.4 <0.12.0' 11 | -------------------------------------------------------------------------------- /test/all_tests.dart: -------------------------------------------------------------------------------- 1 | library all_tests; 2 | 3 | import 'buffer_when_test.dart' as buffer_when; 4 | import 'buffer_with_count_test.dart' as buffer_with_count; 5 | import 'combine_test.dart' as combine; 6 | import 'concat_test.dart' as concat; 7 | import 'concat_all_test.dart' as concat_all; 8 | import 'debounce_test.dart' as debounce_all; 9 | import 'do_action_test.dart' as do_action; 10 | import 'flat_map_test.dart' as flat_map; 11 | import 'flat_map_latest_test.dart' as flat_map_latest; 12 | import 'merge_all_test.dart' as merge_all; 13 | import 'merge_test.dart' as merge; 14 | import 'sample_on_test.dart' as sample_on; 15 | import 'sample_periodically_test.dart' as sample_periodically; 16 | import 'scan_test.dart' as scan; 17 | import 'select_first_test.dart' as select_first; 18 | import 'skip_until_test.dart' as skip_until; 19 | import 'start_with_test.dart' as start_with; 20 | import 'take_until_test.dart' as take_until; 21 | import 'when_test.dart' as when; 22 | import 'zip_test.dart' as zip; 23 | 24 | void main() { 25 | buffer_when.main(); 26 | buffer_with_count.main(); 27 | combine.main(); 28 | concat.main(); 29 | concat_all.main(); 30 | debounce_all.main(); 31 | do_action.main(); 32 | flat_map.main(); 33 | flat_map_latest.main(); 34 | merge.main(); 35 | merge_all.main(); 36 | sample_on.main(); 37 | sample_periodically.main(); 38 | scan.main(); 39 | select_first.main(); 40 | skip_until.main(); 41 | start_with.main(); 42 | take_until.main(); 43 | when.main(); 44 | zip.main(); 45 | } -------------------------------------------------------------------------------- /test/buffer_when_test.dart: -------------------------------------------------------------------------------- 1 | library buffer_when_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("BufferWhen", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | StreamController signal; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | signal = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controller.close(); 29 | signal.close(); 30 | }); 31 | 32 | it("buffers events when signal is true", () { 33 | return testStream(controller.stream.transform(new BufferWhen(signal.stream)), 34 | behavior: () { 35 | controller.add(1); 36 | signal.add(true); 37 | controller.add(2); 38 | }, 39 | expectation: (values) => expect(values).toEqual([1])); 40 | }); 41 | 42 | it("flushes buffered events when signal is false", () { 43 | return testStream(controller.stream.transform(new BufferWhen(signal.stream)), 44 | behavior: () { 45 | controller.add(1); 46 | signal.add(true); 47 | controller.add(2); 48 | signal.add(false); 49 | controller.add(3); 50 | }, 51 | expectation: (values) => expect(values).toEqual([1, 2, 3])); 52 | }); 53 | 54 | it("cancels source and signal stream when source stream is closed", () { 55 | var completerA = new Completer(); 56 | var completerB = new Completer(); 57 | var controller = new StreamController(onCancel: completerA.complete); 58 | var signal = new StreamController(onCancel: completerB.complete); 59 | 60 | return testStream( 61 | controller.stream.transform(new BufferWhen(signal.stream)), 62 | behavior: () => controller.close(), 63 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 64 | }); 65 | 66 | it("cancels source and signal stream when source stream is cancelled", () { 67 | var completerA = new Completer(); 68 | var completerB = new Completer(); 69 | var controller = new StreamController(onCancel: completerA.complete); 70 | var signal = new StreamController(onCancel: completerB.complete); 71 | 72 | return testStream( 73 | controller.stream.transform(new BufferWhen(signal.stream)), 74 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 75 | }); 76 | 77 | it("forwards errors from either source stream", () { 78 | return testErrorsAreForwarded( 79 | controller.stream.transform(new BufferWhen(signal.stream)), 80 | behavior: () { 81 | controller..addError(1)..close(); 82 | }, 83 | expectation: (errors) => expect(errors).toEqual([1])); 84 | }); 85 | 86 | it("returns a stream of the same type", () { 87 | var stream = controller.stream.transform(new BufferWhen(signal.stream)); 88 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 89 | }); 90 | } -------------------------------------------------------------------------------- /test/buffer_with_count_test.dart: -------------------------------------------------------------------------------- 1 | library buffer_with_count_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("BufferWithCount", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | 21 | beforeEach(() { 22 | controller = provider(); 23 | }); 24 | 25 | afterEach(() { 26 | controller.close(); 27 | }); 28 | 29 | it("buffers 2", () { 30 | return testStream(controller.stream.transform(new BufferWithCount(2)), 31 | behavior: () { 32 | controller.add(1); 33 | controller.add(2); 34 | controller.add(3); 35 | controller.add(4); 36 | controller.close(); 37 | }, 38 | expectation: (values) => expect(values).toEqual([[1, 2], [3, 4]])); 39 | }); 40 | 41 | it("buffers 2, 1", () { 42 | return testStream(controller.stream.transform(new BufferWithCount(2, 1)), 43 | behavior: () { 44 | controller.add(1); 45 | controller.add(2); 46 | controller.add(3); 47 | controller.add(4); 48 | controller.close(); 49 | }, 50 | expectation: (values) => expect(values).toEqual([[1, 2], [2, 3], [3, 4], [4]])); 51 | }); 52 | 53 | it("buffers 3, 2", () { 54 | return testStream(controller.stream.transform(new BufferWithCount(3, 2)), 55 | behavior: () { 56 | controller.add(1); 57 | controller.add(2); 58 | controller.add(3); 59 | controller.add(4); 60 | controller.add(5); 61 | controller.add(6); 62 | controller.close(); 63 | }, 64 | expectation: (values) => expect(values).toEqual([[1, 2, 3], [3, 4, 5], [5, 6]])); 65 | }); 66 | 67 | it("closes transformed stream when source stream is done", () { 68 | var stream = controller.stream.transform(new BufferWithCount(2)); 69 | controller..close(); 70 | return stream.toList().then((values) => expect(values).toEqual([])); 71 | }); 72 | 73 | it("cancels source listener when transformed stream is cancelled", () { 74 | var complete = new Completer(); 75 | var controller = new StreamController(onCancel: complete.complete); 76 | 77 | return testStream( 78 | controller.stream.transform(new BufferWithCount(2)), 79 | expectation: (_) => complete.future); 80 | }); 81 | 82 | it("forwards errors from either source stream", () { 83 | return testErrorsAreForwarded( 84 | controller.stream.transform(new BufferWithCount(2)), 85 | behavior: () { 86 | controller..addError(1)..close(); 87 | }, 88 | expectation: (errors) => expect(errors).toEqual([1])); 89 | }); 90 | 91 | it("returns a stream of the same type", () { 92 | var stream = controller.stream.transform(new BufferWithCount(2)); 93 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 94 | }); 95 | } -------------------------------------------------------------------------------- /test/combine_test.dart: -------------------------------------------------------------------------------- 1 | library combine_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("Combine", () { 9 | describe("with a single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with a broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController providerA()) { 19 | StreamController controllerA; 20 | StreamController controllerB; 21 | 22 | beforeEach(() { 23 | controllerA = providerA(); 24 | controllerB = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controllerA.close(); 29 | controllerB.close(); 30 | }); 31 | 32 | it("combine when both streams have an event", () { 33 | return testStream(controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)), 34 | behavior: () { 35 | controllerA.add(1); 36 | controllerB.add(1); 37 | }, 38 | expectation: (values) => expect(values).toEqual([2])); 39 | }); 40 | 41 | it("combines always after both streams have an event", () { 42 | return testStream(controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)), 43 | behavior: () { 44 | controllerA.add(1); 45 | controllerB.add(1); 46 | 47 | controllerB.add(2); 48 | }, 49 | expectation: (values) => expect(values).toEqual([2, 3])); 50 | }); 51 | 52 | it("returned stream closes when both streams are done", () { 53 | return testStream(controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)), 54 | behavior: () { 55 | controllerA.close(); 56 | controllerB.close(); 57 | }, 58 | expectation: (values) => expect(values).toEqual([])); 59 | }); 60 | 61 | it("cancels input streams when source streams are closed", () { 62 | var completerA = new Completer(); 63 | var completerB = new Completer(); 64 | var controllerA = new StreamController(onCancel: completerA.complete); 65 | var controllerB = new StreamController(onCancel: completerB.complete); 66 | 67 | return testStream( 68 | controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)), 69 | behavior: () { 70 | controllerA.close(); 71 | controllerB.close(); 72 | }, 73 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 74 | }); 75 | 76 | it("cancels input streams when source stream is cancelled", () { 77 | var completerA = new Completer(); 78 | var completerB = new Completer(); 79 | var controllerA = new StreamController(onCancel: completerA.complete); 80 | var controllerB = new StreamController(onCancel: completerB.complete); 81 | 82 | return testStream( 83 | controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)), 84 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 85 | }); 86 | 87 | it("forwards errors from either source stream", () { 88 | return testErrorsAreForwarded( 89 | controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)), 90 | behavior: () { 91 | controllerA.addError(1); 92 | controllerB.addError(2); 93 | }, 94 | expectation: (errors) => expect(errors).toEqual([1, 2])); 95 | }); 96 | 97 | it("returns a stream of the same type", () { 98 | var stream = controllerA.stream.transform(new Combine(controllerB.stream, (a, b) => a + b)); 99 | expect(stream.isBroadcast).toBe(controllerA.stream.isBroadcast); 100 | }); 101 | } -------------------------------------------------------------------------------- /test/concat_all_test.dart: -------------------------------------------------------------------------------- 1 | library concat_all_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("ConcatAll", () { 9 | describe("with a single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with a broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController providerA()) { 19 | StreamController controller; 20 | 21 | beforeEach(() { 22 | controller = providerA(); 23 | }); 24 | 25 | afterEach(() { 26 | controller.close(); 27 | }); 28 | 29 | it("doesn't forward events until previous events are done", () { 30 | return testStream(controller.stream.transform(new ConcatAll()), 31 | behavior: () { 32 | var controller1 = new StreamController(); 33 | var controller2 = new StreamController(); 34 | 35 | controller2..add(3)..add(4); 36 | controller1..add(1)..add(2)..close(); 37 | 38 | controller..add(controller1.stream)..add(controller2.stream); 39 | }, 40 | expectation: (values) => expect(values).toEqual([1, 2, 3, 4])); 41 | }); 42 | 43 | it("returns a stream of the same type", () { 44 | var stream = controller.stream.transform(new ConcatAll()); 45 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 46 | }); 47 | } -------------------------------------------------------------------------------- /test/concat_test.dart: -------------------------------------------------------------------------------- 1 | library concat_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("Concat", () { 9 | describe("with a single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with a broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController providerA()) { 19 | StreamController controllerA; 20 | StreamController controllerB; 21 | 22 | beforeEach(() { 23 | controllerA = providerA(); 24 | controllerB = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controllerA.close(); 29 | controllerB.close(); 30 | }); 31 | 32 | it("doesn't forward events until previous events are done", () { 33 | return testStream(controllerA.stream.transform(new Concat(controllerB.stream)), 34 | behavior: () { 35 | controllerB..add(2)..add(3); 36 | controllerA..add(1)..close(); 37 | }, 38 | expectation: (values) => expect(values).toEqual([1, 2, 3])); 39 | }); 40 | 41 | it("returned stream closes when both streams are done", () { 42 | var completer = new Completer(); 43 | var stream = controllerA.stream.transform(new Concat(controllerB.stream)); 44 | stream.listen(null, onDone: completer.complete); 45 | 46 | controllerA.close(); 47 | controllerB.close(); 48 | 49 | return completer.future; 50 | }); 51 | 52 | it("cancels input streams when source streams are closed", () { 53 | var completerA = new Completer(); 54 | var completerB = new Completer(); 55 | var controllerA = new StreamController(onCancel: completerA.complete); 56 | var controllerB = new StreamController(onCancel: completerB.complete); 57 | 58 | return testStream( 59 | controllerA.stream.transform(new Concat(controllerB.stream)), 60 | behavior: () { 61 | controllerA.close(); 62 | controllerB.close(); 63 | }, 64 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 65 | }); 66 | 67 | it("cancels active streams when source stream is cancelled", () { 68 | var completer = new Completer(); 69 | var controller = new StreamController(onCancel: completer.complete); 70 | 71 | return testStream( 72 | controller.stream.transform(new Concat(controllerB.stream)), 73 | expectation: (_) => completer.future); 74 | }); 75 | 76 | it("forwards errors from active stream", () { 77 | return testErrorsAreForwarded( 78 | controllerA.stream.transform(new Concat(controllerB.stream)), 79 | behavior: () { 80 | controllerA.addError(1); 81 | controllerB.addError(2); 82 | }, 83 | expectation: (errors) => expect(errors).toEqual([1, 2])); 84 | }); 85 | 86 | it("returns a stream of the same type", () { 87 | var stream = controllerA.stream.transform(new Concat(controllerB.stream)); 88 | expect(stream.isBroadcast).toBe(controllerA.stream.isBroadcast); 89 | }); 90 | } -------------------------------------------------------------------------------- /test/debounce_test.dart: -------------------------------------------------------------------------------- 1 | library debounce_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("Debounce", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | Duration duration; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | duration = new Duration(milliseconds: 50); 25 | }); 26 | 27 | afterEach(() { 28 | controller.close(); 29 | }); 30 | 31 | it("provides the last event after duration passes", () { 32 | return testStream(controller.stream.transform(new Debounce(duration)), 33 | behavior: () { 34 | controller..add(1)..add(2)..add(3); 35 | return new Future.delayed(duration * 2, () => true); 36 | }, 37 | expectation: (values) => expect(values).toEqual([3])); 38 | }); 39 | 40 | it("closes transformed stream when source stream is done", () { 41 | var stream = controller.stream.transform(new Debounce(duration)); 42 | var result = stream.toList(); 43 | controller..add(1)..close(); 44 | return result.then((values) { 45 | expect(values).toEqual([1]); 46 | }); 47 | }); 48 | 49 | it("cancels input stream when transformed stream is cancelled", () { 50 | var completerA = new Completer(); 51 | var controller = new StreamController(onCancel: completerA.complete); 52 | 53 | return testStream( 54 | controller.stream.transform(new Debounce(duration)), 55 | expectation: (_) => completerA.future); 56 | }); 57 | 58 | it("doesn't debounce errors", () { 59 | return testErrorsAreForwarded( 60 | controller.stream.transform(new Debounce(duration)), 61 | behavior: () { 62 | controller..addError(1)..addError(2)..addError(3)..close(); 63 | }, 64 | expectation: (errors) => expect(errors).toEqual([1, 2, 3])); 65 | }); 66 | 67 | it("returns a stream of the same type", () { 68 | var stream = controller.stream.transform(new Debounce(duration)); 69 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /test/do_action_test.dart: -------------------------------------------------------------------------------- 1 | library do_action_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("DoAction", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | 21 | beforeEach(() { 22 | controller = provider(); 23 | }); 24 | 25 | afterEach(() { 26 | controller.close(); 27 | }); 28 | 29 | it("invokes onData with values from the source stream", () { 30 | var actionValues = []; 31 | return testStream(controller.stream.transform(new DoAction(actionValues.add)), 32 | behavior: () => controller.add(1), 33 | expectation: (values) { 34 | expect(actionValues).toEqual([1]); 35 | expect(values).toEqual([1]); 36 | }); 37 | }); 38 | 39 | it("invokes handlers once with multiple subscribers", () { 40 | var actionValues = []; 41 | var stream = controller.stream.transform(new DoAction(actionValues.add)).asBroadcastStream(); 42 | var values1 = stream.toList(); 43 | var values2 = stream.toList(); 44 | controller..add(1)..close(); 45 | return Future.wait([values1, values2]).then((values) { 46 | expect(values).toEqual([[1], [1]]); 47 | expect(actionValues).toEqual([1]); 48 | }); 49 | }); 50 | 51 | it("invokes onError with errors from the source stream", () { 52 | var errors = []; 53 | return testStream(controller.stream.transform(new DoAction(null, onError: errors.add)).handleError((e) => e), 54 | behavior: () => controller..addError("a")..add(1), 55 | expectation: (values) { 56 | expect(errors).toEqual(["a"]); 57 | expect(values).toEqual([1]); 58 | }); 59 | }); 60 | 61 | it("invokes onDone when source stream is done", () { 62 | var completer = new Completer(); 63 | return testStream(controller.stream.transform(new DoAction(null, onDone: completer.complete)), 64 | behavior: () => controller..close(), 65 | expectation: (values) => completer.future); 66 | }); 67 | 68 | it("closes transformed stream when source stream is done", () { 69 | var stream = controller.stream.transform(new DoAction(null)); 70 | var result = stream.toList(); 71 | controller..add(1)..close(); 72 | return result.then((values) { 73 | expect(values).toEqual([1]); 74 | }); 75 | }); 76 | 77 | it("cancels input stream when transformed stream is cancelled", () { 78 | var completerA = new Completer(); 79 | var controller = new StreamController(onCancel: completerA.complete); 80 | 81 | return testStream( 82 | controller.stream.transform(new DoAction(null)), 83 | expectation: (_) => completerA.future); 84 | }); 85 | 86 | it("returns a stream of the same type", () { 87 | var stream = controller.stream.transform(new DoAction(null)); 88 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /test/flat_map_latest_test.dart: -------------------------------------------------------------------------------- 1 | library flat_map_latest_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("FlatMapLatest", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | Map spawnedControllers; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | spawnedControllers = { 25 | 1: new StreamController(), 26 | 2: new StreamController() 27 | }; 28 | }); 29 | 30 | afterEach(() { 31 | controller.close(); 32 | spawnedControllers.values.forEach((controller) => controller.close()); 33 | }); 34 | 35 | it("includes events from latest spawned stream", () { 36 | return testStream(controller.stream.transform(new FlatMapLatest((value) => spawnedControllers[value].stream)), 37 | behavior: () { 38 | controller.add(1); 39 | controller.add(2); 40 | spawnedControllers[2].add(4); 41 | return new Future(() => spawnedControllers[1].add(3)); 42 | }, 43 | expectation: (values) => expect(values).toEqual([4])); 44 | }); 45 | 46 | it("doesn't close transformed stream when source stream is done and spawned streams are not done", () { 47 | return testStream(controller.stream.transform(new FlatMapLatest((value) => spawnedControllers[value].stream)), 48 | behavior: () { 49 | controller..add(1)..close(); 50 | return new Future(() => spawnedControllers[1].add(1)); 51 | }, 52 | expectation: (values) => expect(values).toEqual([1])); 53 | }); 54 | 55 | it("closes transformed stream when source stream is done and spawned streams are done", () { 56 | var spawnedStream = new Stream.periodic(new Duration(milliseconds: 50), (i) => i).take(2); 57 | var stream = controller.stream.transform(new FlatMapLatest((value) => spawnedStream)); 58 | var result = stream.toList(); 59 | 60 | controller..add(1)..close(); 61 | 62 | return result.then((values) => expect(values).toEqual([0, 1])); 63 | }); 64 | 65 | it("cancels transformed and spawned streams when input stream is closed", () { 66 | var completers = [new Completer(), new Completer(), new Completer()]; 67 | var controllers = [ 68 | new StreamController(onCancel: () => completers[0].complete()), 69 | new StreamController(onCancel: () => completers[1].complete()), 70 | new StreamController(onCancel: () => completers[2].complete())]; 71 | 72 | return testStream( 73 | controllers[0].stream.transform(new FlatMapLatest((value) => controllers[value].stream)), 74 | behavior: () => controllers[0]..add(1)..add(2)..close(), 75 | expectation: (values) => Future.wait(completers.map((completer) => completer.future))); 76 | }); 77 | 78 | it("cancels transformed and spawned streams when transformed stream is cancelled", () { 79 | var completers = [new Completer(), new Completer(), new Completer()]; 80 | var controllers = [ 81 | new StreamController(onCancel: () => completers[0].complete()), 82 | new StreamController(onCancel: () => completers[1].complete()), 83 | new StreamController(onCancel: () => completers[2].complete())]; 84 | 85 | return testStream( 86 | controllers[0].stream.transform(new FlatMapLatest((value) => controllers[value].stream)), 87 | behavior: () => controllers[0]..add(1)..add(2), 88 | expectation: (values) => Future.wait(completers.map((completer) => completer.future))); 89 | }); 90 | 91 | it("forwards errors from source and spawned stream", () { 92 | return testErrorsAreForwarded( 93 | controller.stream.transform(new FlatMapLatest((value) => spawnedControllers[value].stream)), 94 | behavior: () { 95 | controller..add(1)..addError(1); 96 | spawnedControllers[1].addError(2); 97 | }, 98 | expectation: (errors) => expect(errors).toEqual([1, 2])); 99 | }); 100 | 101 | it("returns a stream of the same type", () { 102 | var stream = controller.stream.transform(new FlatMapLatest((value) => new Stream.fromIterable([]))); 103 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 104 | }); 105 | } -------------------------------------------------------------------------------- /test/flat_map_test.dart: -------------------------------------------------------------------------------- 1 | library flat_map_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("FlatMap", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | Map spawnedControllers; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | spawnedControllers = { 25 | 1: new StreamController(), 26 | 2: new StreamController() 27 | }; 28 | }); 29 | 30 | afterEach(() { 31 | controller.close(); 32 | spawnedControllers.values.forEach((controller) => controller.close()); 33 | }); 34 | 35 | it("includes events from spawned streams", () { 36 | return testStream(controller.stream.transform(new FlatMap((value) => spawnedControllers[value].stream)), 37 | behavior: () { 38 | controller.add(1); 39 | controller.add(2); 40 | spawnedControllers[1].add(3); 41 | spawnedControllers[2].add(4); 42 | }, 43 | expectation: (values) => expect(values).toEqual([3, 4])); 44 | }); 45 | 46 | it("doesn't close transformed stream when source stream is done and spawned streams are not done", () { 47 | return testStream(controller.stream.transform(new FlatMap((value) => spawnedControllers[value].stream)), 48 | behavior: () { 49 | controller..add(1)..close(); 50 | return new Future(() => spawnedControllers[1].add(1)); 51 | }, 52 | expectation: (values) => expect(values).toEqual([1])); 53 | }); 54 | 55 | it("closes transformed stream if source stream is closed and has no events", () { 56 | var stream = controller.stream.transform(new FlatMap((value) => new Stream.fromIterable([]))); 57 | var result = stream.toList(); 58 | 59 | controller.close(); 60 | 61 | return result.then((values) => expect(values).toEqual([])); 62 | }); 63 | 64 | it("closes transformed stream when source stream is done and spawned streams are done", () { 65 | var spawnedStream = new Stream.periodic(new Duration(milliseconds: 50), (i) => i).take(2); 66 | var stream = controller.stream.transform(new FlatMap((value) => spawnedStream)); 67 | var result = stream.toList(); 68 | 69 | controller..add(1)..close(); 70 | 71 | return result.then((values) => expect(values).toEqual([0, 1])); 72 | }); 73 | 74 | it("cancels transformed and spawned streams when input stream is cancelled", () { 75 | var completers = [new Completer(), new Completer(), new Completer()]; 76 | var controllers = [ 77 | new StreamController(onCancel: () => completers[0].complete()), 78 | new StreamController(onCancel: () => completers[1].complete()), 79 | new StreamController(onCancel: () => completers[2].complete())]; 80 | 81 | return testStream( 82 | controllers[0].stream.transform(new FlatMap((value) => controllers[value].stream)), 83 | behavior: () => controllers[0]..add(1)..add(2), 84 | expectation: (values) => Future.wait(completers.map((completer) => completer.future))); 85 | }); 86 | 87 | it("cancels subscriptions from spawned streams when transformed stream's listener is closed", () { 88 | var completers = [new Completer(), new Completer(), new Completer()]; 89 | var controllers = [ 90 | new StreamController(onCancel: () => completers[0].complete()), 91 | new StreamController(onCancel: () => completers[1].complete()), 92 | new StreamController(onCancel: () => completers[2].complete())]; 93 | 94 | return testStream( 95 | controllers[0].stream.transform(new FlatMap((value) => controllers[value].stream)), 96 | behavior: () => controllers[0]..add(1)..add(2)..close(), 97 | expectation: (values) => Future.wait(completers.map((completer) => completer.future))); 98 | }); 99 | 100 | it("forwards errors from source and spawned stream", () { 101 | return testErrorsAreForwarded( 102 | controller.stream.transform(new FlatMap((value) => spawnedControllers[value].stream)), 103 | behavior: () { 104 | controller..add(1)..addError(1); 105 | spawnedControllers[1].addError(2); 106 | }, 107 | expectation: (errors) => expect(errors).toEqual([2, 1])); 108 | }); 109 | 110 | it("returns a stream of the same type", () { 111 | var stream = controller.stream.transform(new FlatMap((value) => new Stream.fromIterable([]))); 112 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /test/merge_all_test.dart: -------------------------------------------------------------------------------- 1 | library merge_all_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("MergeAll", () { 9 | describe("with a single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with a broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | 21 | beforeEach(() { 22 | controller = provider(); 23 | }); 24 | 25 | afterEach(() { 26 | controller.close(); 27 | }); 28 | 29 | it("forwards events from a stream of a single stream", () { 30 | return testStream(controller.stream.transform(new MergeAll()), 31 | behavior: () { 32 | var controller1 = new StreamController(); 33 | controller.add(controller1.stream); 34 | controller1..add(1)..add(2); 35 | }, 36 | expectation: (values) => expect(values).toEqual([1, 2])); 37 | }); 38 | 39 | it("forwards events from a stream of a multiple streams", () { 40 | return testStream(controller.stream.transform(new MergeAll()), 41 | behavior: () { 42 | var controller1 = new StreamController(); 43 | var controller2 = new StreamController(); 44 | controller..add(controller1.stream)..add(controller2.stream); 45 | controller1..add(1)..add(2); 46 | controller2..add(3)..add(4); 47 | }, 48 | expectation: (values) => expect(values).toEqual([1, 2, 3, 4])); 49 | }); 50 | 51 | it("returned stream closes when source stream is done and contained streams are done", () { 52 | var completer = new Completer(); 53 | var stream = controller.stream.transform(new MergeAll()); 54 | stream.listen(null, onDone: completer.complete); 55 | 56 | var controller1 = new StreamController(); 57 | var controller2 = new StreamController(); 58 | 59 | controller..add(controller1.stream)..add(controller2.stream)..close(); 60 | controller1.close(); 61 | controller2.close(); 62 | 63 | return completer.future; 64 | }); 65 | 66 | it("cancels contained streams when those streams are closed", () { 67 | var completerA = new Completer(); 68 | var completerB = new Completer(); 69 | var controllerA = new StreamController(onCancel: completerA.complete); 70 | var controllerB = new StreamController(onCancel: completerB.complete); 71 | 72 | return testStream( 73 | controller.stream.transform(new MergeAll()), 74 | behavior: () { 75 | controller..add(controllerA.stream)..add(controllerB.stream); 76 | controllerA.close(); 77 | controllerB.close(); 78 | }, 79 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 80 | }); 81 | 82 | it("cancels contained streams when source stream closes", () { 83 | var completerA = new Completer(); 84 | var completerB = new Completer(); 85 | var controllerA = new StreamController(onCancel: completerA.complete); 86 | var controllerB = new StreamController(onCancel: completerB.complete); 87 | 88 | return testStream( 89 | controller.stream.transform(new MergeAll()), 90 | behavior: () { 91 | controller..add(controllerA.stream)..add(controllerB.stream)..close(); 92 | }, 93 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 94 | }); 95 | 96 | it("cancels input streams when source stream is cancelled", () { 97 | var completerA = new Completer(); 98 | var completerB = new Completer(); 99 | var controllerA = new StreamController(onCancel: completerA.complete); 100 | var controllerB = new StreamController(onCancel: completerB.complete); 101 | 102 | return testStream( 103 | controller.stream.transform(new MergeAll()), 104 | behavior: () { 105 | controller..add(controllerA.stream)..add(controllerB.stream); 106 | }, 107 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 108 | }); 109 | 110 | it("forwards errors from source stream", () { 111 | return testErrorsAreForwarded( 112 | controller.stream.transform(new MergeAll()), 113 | behavior: () { 114 | controller.addError(1); 115 | }, 116 | expectation: (errors) => expect(errors).toEqual([1])); 117 | }); 118 | 119 | it("forwards errors from contained streams", () { 120 | var controller1 = new StreamController(); 121 | var controller2 = new StreamController(); 122 | 123 | return testErrorsAreForwarded( 124 | controller.stream.transform(new MergeAll()), 125 | behavior: () { 126 | controller..add(controller1.stream)..add(controller2.stream); 127 | controller1.addError(1); 128 | controller2.addError(2); 129 | }, 130 | expectation: (errors) => expect(errors).toEqual([1, 2])); 131 | }); 132 | 133 | it("returns a stream of the same type", () { 134 | var stream = controller.stream.transform(new MergeAll()); 135 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 136 | }); 137 | } -------------------------------------------------------------------------------- /test/merge_test.dart: -------------------------------------------------------------------------------- 1 | library merge_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("Merge", () { 9 | describe("with a single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with a broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController providerA()) { 19 | StreamController controllerA; 20 | StreamController controllerB; 21 | 22 | beforeEach(() { 23 | controllerA = providerA(); 24 | controllerB = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controllerA.close(); 29 | controllerB.close(); 30 | }); 31 | 32 | it("merges events from both streams", () { 33 | return testStream(controllerA.stream.transform(new Merge(controllerB.stream)), 34 | behavior: () { 35 | controllerA.add(1); 36 | controllerB.add(2); 37 | }, 38 | expectation: (values) => expect(values).toEqual([1, 2])); 39 | }); 40 | 41 | it("returned stream closes when both streams are done", () { 42 | var completer = new Completer(); 43 | var stream = controllerA.stream.transform(new Merge(controllerB.stream)); 44 | stream.listen(null, onDone: completer.complete); 45 | 46 | controllerA.close(); 47 | controllerB.close(); 48 | 49 | return completer.future; 50 | }); 51 | 52 | it("cancels input streams when source streams are closed", () { 53 | var completerA = new Completer(); 54 | var completerB = new Completer(); 55 | var controllerA = new StreamController(onCancel: completerA.complete); 56 | var controllerB = new StreamController(onCancel: completerB.complete); 57 | 58 | return testStream( 59 | controllerA.stream.transform(new Merge(controllerB.stream)), 60 | behavior: () { 61 | controllerA.close(); 62 | controllerB.close(); 63 | }, 64 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 65 | }); 66 | 67 | it("cancels input streams when source stream is cancelled", () { 68 | var completerA = new Completer(); 69 | var completerB = new Completer(); 70 | var controllerA = new StreamController(onCancel: completerA.complete); 71 | var controllerB = new StreamController(onCancel: completerB.complete); 72 | 73 | return testStream( 74 | controllerA.stream.transform(new Merge(controllerB.stream)), 75 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 76 | }); 77 | 78 | it("forwards errors from either source stream", () { 79 | return testErrorsAreForwarded( 80 | controllerA.stream.transform(new Merge(controllerB.stream)), 81 | behavior: () { 82 | controllerA.addError(1); 83 | controllerB.addError(2); 84 | }, 85 | expectation: (errors) => expect(errors).toEqual([1, 2])); 86 | }); 87 | 88 | it("returns a stream of the same type", () { 89 | var stream = controllerA.stream.transform(new Merge(controllerB.stream)); 90 | expect(stream.isBroadcast).toBe(controllerA.stream.isBroadcast); 91 | }); 92 | } -------------------------------------------------------------------------------- /test/sample_on_test.dart: -------------------------------------------------------------------------------- 1 | library sample_on_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("SampleOn", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | 17 | it("works with periodic streams", () { 18 | var source = new Stream.periodic(new Duration(milliseconds: 50), (i) => i); 19 | var sampler = new Stream.periodic(new Duration(milliseconds: 100), (i) => i); 20 | 21 | var stream = source.transform(new SampleOn(sampler)).take(3); 22 | return stream.toList().then((values) => expect(values).toEqual([0, 2, 4])); 23 | }); 24 | 25 | it("cancels input streams when transformed stream is cancelled", () { 26 | var completerA = new Completer(); 27 | var completerB = new Completer(); 28 | 29 | var controller = new StreamController(onCancel: completerA.complete); 30 | var trigger = new StreamController(onCancel: completerB.complete); 31 | 32 | return testStream( 33 | controller.stream.transform(new SampleOn(trigger.stream)), 34 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 35 | }); 36 | }); 37 | 38 | void testWithStreamController(StreamController provider()) { 39 | StreamController controller; 40 | StreamController trigger; 41 | 42 | beforeEach(() { 43 | trigger = new StreamController(); 44 | controller = provider(); 45 | }); 46 | 47 | afterEach(() { 48 | controller.close(); 49 | trigger.close(); 50 | }); 51 | 52 | it("delivers the latest value when sample stream delivers a value", () { 53 | return testStream(controller.stream.transform(new SampleOn(trigger.stream)), 54 | behavior: () { 55 | controller.add(1); 56 | trigger.add(true); 57 | }, 58 | expectation: (values) => expect(values).toEqual([1])); 59 | }); 60 | 61 | it("redelivers the latest value when sample stream has a value", () { 62 | return testStream(controller.stream.transform(new SampleOn(trigger.stream)), 63 | behavior: () { 64 | controller.add(1); 65 | trigger.add(true); 66 | trigger.add(true); 67 | }, 68 | expectation: (values) => expect(values).toEqual([1, 1])); 69 | }); 70 | 71 | it("doesn't deliver a value if source stream is empty", () { 72 | return testStream(controller.stream.transform(new SampleOn(trigger.stream)), 73 | behavior: () { 74 | trigger.add(true); 75 | controller.add(1); 76 | }, 77 | expectation: (values) => expect(values).toEqual([])); 78 | }); 79 | 80 | it("closes transformed stream when source stream is done", () { 81 | var stream = controller.stream.transform(new SampleOn(trigger.stream)); 82 | var result = stream.toList(); 83 | controller.close(); 84 | return result.then((values) => expect(values).toEqual([])); 85 | }); 86 | 87 | it("closes transformed stream when sample stream is done", () { 88 | var stream = controller.stream.transform(new SampleOn(trigger.stream)); 89 | var result = stream.toList(); 90 | trigger.close(); 91 | return result.then((values) => expect(values).toEqual([])); 92 | }); 93 | 94 | it("returns a stream of the same type", () { 95 | var stream = controller.stream.transform(new SampleOn(trigger.stream)); 96 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /test/sample_periodically_test.dart: -------------------------------------------------------------------------------- 1 | library sample_periodically_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("SamplePeriodically", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | 17 | it("samples the source at a specified interval", () { 18 | var source = new Stream.periodic(new Duration(milliseconds: 50), (i) => i); 19 | var stream = source.transform(new SamplePeriodically(new Duration(milliseconds: 100))).take(3); 20 | return stream.toList().then((values) => expect(values).toEqual([0, 2, 4])); 21 | }); 22 | 23 | it("cancels input streams when transformed stream is cancelled", () { 24 | var completerA = new Completer(); 25 | var completerB = new Completer(); 26 | 27 | var controller = new StreamController(onCancel: completerA.complete); 28 | var trigger = new StreamController(onCancel: completerB.complete); 29 | 30 | return testStream( 31 | controller.stream.transform(new SampleOn(trigger.stream)), 32 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 33 | }); 34 | }); 35 | 36 | void testWithStreamController(StreamController provider()) { 37 | StreamController controller; 38 | StreamController trigger; 39 | 40 | beforeEach(() { 41 | trigger = new StreamController(); 42 | controller = provider(); 43 | }); 44 | 45 | afterEach(() { 46 | controller.close(); 47 | trigger.close(); 48 | }); 49 | 50 | it("returns a stream of the same type", () { 51 | var stream = controller.stream.transform(new SampleOn(trigger.stream)); 52 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /test/scan_test.dart: -------------------------------------------------------------------------------- 1 | library scan_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("Scan", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | 21 | beforeEach(() { 22 | controller = provider(); 23 | }); 24 | 25 | afterEach(() { 26 | controller.close(); 27 | }); 28 | 29 | it("calls combine for each event", () { 30 | return testStream(controller.stream.transform(new Scan(0, (a, b) => a + b)), 31 | behavior: () { 32 | controller.add(1); 33 | controller.add(2); 34 | }, 35 | expectation: (values) => expect(values).toEqual([0, 1, 3])); 36 | }); 37 | 38 | it("closes transformed stream when source stream is done", () { 39 | var stream = controller.stream.transform(new Scan(0, (a, b) => a + b)); 40 | controller..close(); 41 | return stream.toList().then((values) => expect(values).toEqual([0])); 42 | }); 43 | 44 | it("cancels source listener when transformed stream is cancelled", () { 45 | var complete = new Completer(); 46 | var controller = new StreamController(onCancel: complete.complete); 47 | 48 | return testStream( 49 | controller.stream.transform(new Scan(0, (a, b) => a + b)), 50 | expectation: (_) => complete.future); 51 | }); 52 | 53 | it("forwards errors from source stream", () { 54 | return testErrorsAreForwarded( 55 | controller.stream.transform(new Scan(0, (a, b) => a + b)), 56 | behavior: () { 57 | controller..addError(1)..close(); 58 | }, 59 | expectation: (errors) => expect(errors).toEqual([1])); 60 | }); 61 | 62 | it("returns a stream of the same type", () { 63 | var stream = controller.stream.transform(new Scan(0, (a, b) => a + b)); 64 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /test/select_first_test.dart: -------------------------------------------------------------------------------- 1 | library select_first_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("SelectFirst", () { 9 | describe("with a single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with a broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController providerA()) { 19 | StreamController controllerA; 20 | StreamController controllerB; 21 | 22 | beforeEach(() { 23 | controllerA = providerA(); 24 | controllerB = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controllerA.close(); 29 | controllerB.close(); 30 | }); 31 | 32 | describe("when selected", () { 33 | it("delivers events from the first stream to deliver an event", () { 34 | return testStream(controllerA.stream.transform(new SelectFirst(controllerB.stream)), 35 | behavior: () { 36 | controllerB.add(2); 37 | controllerA.add(1); 38 | }, 39 | expectation: (values) => expect(values).toEqual([2])); 40 | }); 41 | 42 | it("returned stream closes when selected stream is done", () { 43 | var completer = new Completer(); 44 | var stream = controllerA.stream.transform(new SelectFirst(controllerB.stream)); 45 | stream.listen(null, onDone: completer.complete); 46 | 47 | controllerA..add(1)..close(); 48 | 49 | return completer.future; 50 | }); 51 | 52 | it("cancels input streams when selected streams is closed", () { 53 | var completer = new Completer(); 54 | var other = new StreamController(onCancel: completer.complete); 55 | 56 | return testStream( 57 | controllerA.stream.transform(new SelectFirst(other.stream)), 58 | behavior: () { 59 | other..add(1)..close(); 60 | }, 61 | expectation: (_) => completer.future); 62 | }); 63 | 64 | it("forwards errors from selected stream", () { 65 | return testErrorsAreForwarded( 66 | controllerA.stream.transform(new SelectFirst(controllerB.stream)), 67 | behavior: () { 68 | controllerA..add(1)..addError("a"); 69 | }, 70 | expectation: (errors) => expect(errors).toEqual(["a"])); 71 | }); 72 | }); 73 | 74 | describe("when no events", () { 75 | it("returned stream closes when both streams are done", () { 76 | var completer = new Completer(); 77 | var stream = controllerA.stream.transform(new SelectFirst(controllerB.stream)); 78 | stream.listen(null, onDone: completer.complete); 79 | 80 | controllerA.close(); 81 | controllerB.close(); 82 | 83 | return completer.future; 84 | }); 85 | 86 | it("cancels input streams when source streams are closed", () { 87 | var completerA = new Completer(); 88 | var completerB = new Completer(); 89 | var controllerA = new StreamController(onCancel: completerA.complete); 90 | var controllerB = new StreamController(onCancel: completerB.complete); 91 | 92 | return testStream( 93 | controllerA.stream.transform(new SelectFirst(controllerB.stream)), 94 | behavior: () { 95 | controllerA.close(); 96 | controllerB.close(); 97 | }, 98 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 99 | }); 100 | 101 | it("cancels input streams when source stream is cancelled", () { 102 | var completerA = new Completer(); 103 | var completerB = new Completer(); 104 | var controllerA = new StreamController(onCancel: completerA.complete); 105 | var controllerB = new StreamController(onCancel: completerB.complete); 106 | 107 | return testStream( 108 | controllerA.stream.transform(new SelectFirst(controllerB.stream)), 109 | expectation: (_) => Future.wait([completerA.future, completerB.future])); 110 | }); 111 | 112 | it("forwards errors from either source stream", () { 113 | return testErrorsAreForwarded( 114 | controllerA.stream.transform(new SelectFirst(controllerB.stream)), 115 | behavior: () { 116 | controllerA.addError(1); 117 | controllerB.addError(2); 118 | }, 119 | expectation: (errors) => expect(errors).toEqual([1, 2])); 120 | }); 121 | }); 122 | 123 | it("returns a stream of the same type", () { 124 | var stream = controllerA.stream.transform(new SelectFirst(controllerB.stream)); 125 | expect(stream.isBroadcast).toBe(controllerA.stream.isBroadcast); 126 | }); 127 | } -------------------------------------------------------------------------------- /test/skip_until_test.dart: -------------------------------------------------------------------------------- 1 | library skip_until_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("SkipUntil", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | StreamController signal; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | signal = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controller.close(); 29 | }); 30 | 31 | it("doesn't include events until signal", () { 32 | return testStream(controller.stream.transform(new SkipUntil(signal.stream)), 33 | behavior: () => new Future(() { 34 | controller.add(1); 35 | controller.add(2); 36 | 37 | return new Future(() { 38 | signal.add(true); 39 | controller.add(3); 40 | }); 41 | }), 42 | expectation: (values) => expect(values).toEqual([3])); 43 | }); 44 | 45 | it("closes transformed stream when source stream is done", () { 46 | return testStream(controller.stream.transform(new SkipUntil(signal.stream)), 47 | behavior: () => controller.close(), 48 | expectation: (values) => expect(values).toEqual([])); 49 | }); 50 | 51 | it("cancels source and signal subscription when transformed stream listener is cancelled", () { 52 | var completers = [new Completer(), new Completer()]; 53 | var controller = new StreamController(onCancel: () => completers[0].complete()); 54 | var toggle = new StreamController(onCancel: () => completers[1].complete()); 55 | 56 | return testStream( 57 | controller.stream.transform(new SkipUntil(toggle.stream)), 58 | expectation: (values) => completers.map((completer) => completer.future)); 59 | }); 60 | 61 | it("forwards errors from source and signal stream", () { 62 | return testErrorsAreForwarded( 63 | controller.stream.transform(new SkipUntil(signal.stream)), 64 | behavior: () { 65 | controller.addError(1); 66 | signal.addError(2); 67 | }, 68 | expectation: (errors) => expect(errors).toEqual([1, 2])); 69 | }); 70 | 71 | it("returns a stream of the same type", () { 72 | var stream = controller.stream.transform(new SkipUntil(signal.stream)); 73 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 74 | }); 75 | } -------------------------------------------------------------------------------- /test/start_with_test.dart: -------------------------------------------------------------------------------- 1 | library start_with_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("StartWith", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | 17 | it("prepends when source is a Stream.fromIterable()", () { 18 | var source = new Stream.fromIterable([2, 3]); 19 | return source.transform(new StartWith(1)).toList().then((values) => expect(values).toEqual([1, 2, 3])); 20 | }); 21 | }); 22 | 23 | void testWithStreamController(StreamController provider()) { 24 | StreamController controller; 25 | 26 | beforeEach(() { 27 | controller = provider(); 28 | }); 29 | 30 | afterEach(() { 31 | controller.close(); 32 | }); 33 | 34 | it("prepends values to the stream", () { 35 | return testStream(controller.stream.transform(new StartWith(1)), 36 | behavior: () { 37 | controller..add(2)..add(3); 38 | }, 39 | expectation: (values) => expect(values).toEqual([1, 2, 3])); 40 | }); 41 | 42 | it("prepends many values to the stream", () { 43 | return testStream(controller.stream.transform(new StartWith.many([1, 2])), 44 | behavior: () { 45 | controller..add(3)..add(4); 46 | }, 47 | expectation: (values) => expect(values).toEqual([1, 2, 3, 4])); 48 | }); 49 | 50 | it("closes transformed stream when source stream is done", () { 51 | var stream = controller.stream.transform(new StartWith(1)); 52 | var result = stream.toList(); 53 | controller..add(2)..close(); 54 | return result.then((values) { 55 | expect(values).toEqual([1, 2]); 56 | }); 57 | }); 58 | 59 | it("cancels input stream when transformed stream is cancelled", () { 60 | var completerA = new Completer(); 61 | var controller = new StreamController(onCancel: completerA.complete); 62 | 63 | return testStream( 64 | controller.stream.transform(new StartWith(1)), 65 | expectation: (_) => completerA.future); 66 | }); 67 | 68 | it("forwards errors from the source stream", () { 69 | return testErrorsAreForwarded( 70 | controller.stream.transform(new StartWith(1)), 71 | behavior: () { 72 | controller.addError(1); 73 | }, 74 | expectation: (errors) => expect(errors).toEqual([1])); 75 | }); 76 | 77 | it("returns a stream of the same type", () { 78 | var stream = controller.stream.transform(new StartWith(1)); 79 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /test/take_until_test.dart: -------------------------------------------------------------------------------- 1 | library take_until_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("TakeUntil", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | StreamController signal; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | signal = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controller.close(); 29 | }); 30 | 31 | it("includes events until signal", () { 32 | return testStream(controller.stream.transform(new TakeUntil(signal.stream)), 33 | behavior: () { 34 | return new Future(() => controller..add(1)..add(2)) 35 | .then((_) => new Future(() => signal.add(true))) 36 | .then((_) => controller.add(3)); 37 | }, 38 | expectation: (values) => expect(values).toEqual([1, 2])); 39 | }); 40 | 41 | it("closes transformed stream when source stream is done", () { 42 | var stream = controller.stream.transform(new TakeUntil(signal.stream)); 43 | var result = stream.toList(); 44 | controller.close(); 45 | return result.then((values) => expect(values).toEqual([])); 46 | }); 47 | 48 | it("cancels source and signal subscriptions when transformed stream listener is cancelled", () { 49 | var completers = [new Completer(), new Completer()]; 50 | var controller = new StreamController(onCancel: () => completers[0].complete()); 51 | var signal = new StreamController(onCancel: () => completers[1].complete()); 52 | 53 | return testStream(controller.stream.transform(new TakeUntil(signal.stream)), 54 | expectation: (values) => Future.wait(completers.map((completer) => completer.future))); 55 | }); 56 | 57 | it("forwards errors from source and signal stream", () { 58 | return testErrorsAreForwarded( 59 | controller.stream.transform(new TakeUntil(signal.stream)), 60 | behavior: () { 61 | controller.addError(1); 62 | signal.addError(2); 63 | }, 64 | expectation: (errors) => expect(errors).toEqual([1, 2])); 65 | }); 66 | 67 | it("returns a stream of the same type", () { 68 | var stream = controller.stream.transform(new TakeUntil(signal.stream)); 69 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 70 | }); 71 | } -------------------------------------------------------------------------------- /test/util.dart: -------------------------------------------------------------------------------- 1 | library test_util; 2 | 3 | import 'dart:async'; 4 | 5 | Function doNothing = (_) {}; 6 | 7 | Future testStream(Stream stream, {behavior(), expectation(List values)}) { 8 | var results = []; 9 | 10 | var subscription = stream.listen((value) { 11 | results.add(value); 12 | }); 13 | 14 | return new Future(() { 15 | if (behavior != null) { 16 | return behavior(); 17 | } 18 | }) 19 | .then((_) => new Future(() { 20 | subscription.cancel(); 21 | })) 22 | .then((_) => expectation(results)); 23 | } 24 | 25 | Future testErrorsAreForwarded(Stream stream, {behavior(), expectation(List errors)}) { 26 | var errors = []; 27 | return testStream(stream.handleError((e) => errors.add(e)), 28 | behavior: behavior, 29 | expectation: (_) => expectation(errors)); 30 | } 31 | -------------------------------------------------------------------------------- /test/when_test.dart: -------------------------------------------------------------------------------- 1 | library when_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("When", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controller; 20 | StreamController toggle; 21 | 22 | beforeEach(() { 23 | controller = provider(); 24 | toggle = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controller.close(); 29 | }); 30 | 31 | it("includes events when signal is true", () { 32 | return testStream(controller.stream.transform(new When(toggle.stream)), 33 | behavior: () => new Future(() { 34 | controller.add(1); 35 | toggle.add(true); 36 | controller.add(2); 37 | }), 38 | expectation: (values) => expect(values).toEqual([2])); 39 | }); 40 | 41 | it("excludes events when signal is false", () { 42 | return testStream(controller.stream.transform(new When(toggle.stream)), 43 | behavior: () => new Future(() { 44 | controller.add(1); 45 | toggle.add(true); 46 | controller.add(2); 47 | toggle.add(false); 48 | controller.add(3); 49 | }), 50 | expectation: (values) => expect(values).toEqual([2])); 51 | }); 52 | 53 | it("doesn't close when signal stream closes", () { 54 | return testStream(controller.stream.transform(new When(toggle.stream)), 55 | behavior: () => new Future(() { 56 | controller.add(1); 57 | toggle..add(true)..close(); 58 | return new Future(() => controller.add(2)); 59 | }), 60 | expectation: (values) => expect(values).toEqual([2])); 61 | }); 62 | 63 | it("cancels source and signal subscription when transformed stream listener is cancelled", () { 64 | var completers = [new Completer(), new Completer()]; 65 | var controller = new StreamController(onCancel: () => completers[0].complete()); 66 | var toggle = new StreamController(onCancel: () => completers[1].complete()); 67 | 68 | return testStream( 69 | controller.stream.transform(new When(toggle.stream)), 70 | expectation: (values) => Future.wait(completers.map((completer) => completer.future))); 71 | }); 72 | 73 | it("forwards errors from source and toggle stream", () { 74 | return testErrorsAreForwarded( 75 | controller.stream.transform(new When(toggle.stream)), 76 | behavior: () { 77 | controller.addError(1); 78 | toggle.addError(2); 79 | }, 80 | expectation: (errors) => expect(errors).toEqual([1, 2])); 81 | }); 82 | 83 | it("returns a stream of the same type", () { 84 | var stream = controller.stream.transform(new When(toggle.stream)); 85 | expect(stream.isBroadcast).toBe(controller.stream.isBroadcast); 86 | }); 87 | } -------------------------------------------------------------------------------- /test/zip_test.dart: -------------------------------------------------------------------------------- 1 | library zip_test; 2 | 3 | import 'dart:async'; 4 | import 'package:guinness/guinness.dart'; 5 | import 'package:stream_transformers/stream_transformers.dart'; 6 | import 'util.dart'; 7 | 8 | void main() => describe("Zip", () { 9 | describe("with single subscription stream", () { 10 | testWithStreamController(() => new StreamController()); 11 | }); 12 | 13 | describe("with broadcast stream", () { 14 | testWithStreamController(() => new StreamController.broadcast()); 15 | }); 16 | }); 17 | 18 | void testWithStreamController(StreamController provider()) { 19 | StreamController controllerA; 20 | StreamController controllerB; 21 | 22 | beforeEach(() { 23 | controllerA = provider(); 24 | controllerB = new StreamController(); 25 | }); 26 | 27 | afterEach(() { 28 | controllerA.close(); 29 | controllerB.close(); 30 | }); 31 | 32 | it("combine each pair of events", () { 33 | return testStream(controllerA.stream.transform(new Zip(controllerB.stream, (a, b) => a + b)), 34 | behavior: () { 35 | controllerA.add(1); 36 | controllerB.add(1); 37 | 38 | controllerA.add(2); 39 | controllerB.add(2); 40 | 41 | controllerA.add(3); 42 | controllerA.add(4); 43 | controllerA.add(5); 44 | 45 | controllerB.add(3); 46 | }, 47 | expectation: (values) => expect(values).toEqual([2, 4, 6])); 48 | }); 49 | 50 | it("returned stream closes when source stream is done", () { 51 | var stream = controllerA.stream.transform(new Zip(controllerB.stream, (a, b) => a + b)); 52 | controllerA.close(); 53 | return stream.isEmpty; 54 | }); 55 | 56 | it("returned stream closes when other stream is done", () { 57 | var stream = controllerA.stream.transform(new Zip(controllerB.stream, (a, b) => a + b)); 58 | controllerB.close(); 59 | return stream.isEmpty; 60 | }); 61 | 62 | it("forwards errors from source and toggle stream", () { 63 | return testErrorsAreForwarded( 64 | controllerA.stream.transform(new Zip(controllerB.stream, (a, b) => a + b)), 65 | behavior: () { 66 | controllerA.addError(1); 67 | controllerB.addError(2); 68 | }, 69 | expectation: (errors) => expect(errors).toEqual([1, 2])); 70 | }); 71 | 72 | it("cancels source streams when transformed stream is cancelled", () { 73 | var completers = [new Completer(), new Completer()]; 74 | var controllerA = new StreamController(onCancel: () => completers[0].complete()); 75 | var controllerB = new StreamController(onCancel: () => completers[1].complete()); 76 | 77 | return testStream( 78 | controllerA.stream.transform(new Zip(controllerB.stream, (a, b) => a + b)), 79 | expectation: (_) => Future.wait(completers.map((completer) => completer.future))); 80 | }); 81 | 82 | it("returns a stream of the same type", () { 83 | var stream = controllerA.stream.transform(new Zip(controllerB.stream, (a, b) => a + b)); 84 | expect(stream.isBroadcast).toBe(controllerA.stream.isBroadcast); 85 | }); 86 | } -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fast fail the script on failures. 4 | set -e 5 | 6 | # Verify that the libraries are error free. 7 | dartanalyzer --fatal-warnings \ 8 | lib/stream_transformers.dart \ 9 | test/all_tests.dart 10 | 11 | # Run the tests. 12 | dart -c test/all_tests.dart 13 | --------------------------------------------------------------------------------