├── .buildscript └── deploy_snapshot.sh ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── gradle-mvn-push.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── reagent-rxjava2 ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── reagent │ │ └── rxjava2 │ │ ├── CompletableRxToReagent.kt │ │ ├── JobDisposable.kt │ │ ├── MaybeRxToReagent.kt │ │ ├── ObservableReagentToRx.kt │ │ ├── ObservableRxToReagent.kt │ │ ├── SingleRxToReagent.kt │ │ ├── TaskReagentToRx.kt │ │ └── converters.kt │ └── test │ └── kotlin │ └── reagent │ └── rxjava2 │ ├── CompletableRxToReagentTest.kt │ ├── MaybeRxToReagentTest.kt │ ├── ObservableReagentToRxTest.kt │ ├── ObservableRxToReagentTest.kt │ ├── SingleRxToReagentTest.kt │ └── TaskReagentToRxTest.kt ├── reagent-rxjs6 ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── reagent │ │ └── rxjs6 │ │ └── converters.kt │ └── test │ └── kotlin │ └── reagent │ └── rxjs6 │ └── ObservableReagentToRxTest.kt ├── reagent ├── common │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── reagent │ │ │ ├── Observable.kt │ │ │ ├── Task.kt │ │ │ ├── operator │ │ │ ├── all.kt │ │ │ ├── any.kt │ │ │ ├── concatMap.kt │ │ │ ├── concatWith.kt │ │ │ ├── count.kt │ │ │ ├── distinct.kt │ │ │ ├── distinctUntilChanged.kt │ │ │ ├── drop.kt │ │ │ ├── filter.kt │ │ │ ├── flatMap.kt │ │ │ ├── fold.kt │ │ │ ├── ignoreElements.kt │ │ │ ├── iterator.kt │ │ │ ├── map.kt │ │ │ ├── minMax.kt │ │ │ ├── none.kt │ │ │ ├── reduce.kt │ │ │ ├── take.kt │ │ │ └── toList.kt │ │ │ └── source │ │ │ ├── ObservableArray.kt │ │ │ ├── ObservableConcat.kt │ │ │ ├── ObservableDeferred.kt │ │ │ ├── ObservableEmpty.kt │ │ │ ├── ObservableFromSuspendingLambda.kt │ │ │ ├── ObservableIntervalInt.kt │ │ │ ├── ObservableIterable.kt │ │ │ ├── ObservableSequence.kt │ │ │ ├── TaskDeferred.kt │ │ │ ├── TaskError.kt │ │ │ ├── TaskFromLambda.kt │ │ │ ├── TaskFromSuspendingLambda.kt │ │ │ ├── TaskJust.kt │ │ │ ├── TaskTimerInt.kt │ │ │ └── functions.kt │ │ └── test │ │ └── kotlin │ │ └── reagent │ │ ├── PolymorphismTest.kt │ │ ├── operator │ │ ├── ObservableAllTest.kt │ │ ├── ObservableAnyTest.kt │ │ ├── ObservableConcatMapTest.kt │ │ ├── ObservableConcatWithTest.kt │ │ ├── ObservableCountTest.kt │ │ ├── ObservableDistinctTest.kt │ │ ├── ObservableDistinctUntilChangedTest.kt │ │ ├── ObservableDropTest.kt │ │ ├── ObservableDropWhileTest.kt │ │ ├── ObservableFilterTest.kt │ │ ├── ObservableFlatMapTest.kt │ │ ├── ObservableFoldTest.kt │ │ ├── ObservableIgnoreElementsTest.kt │ │ ├── ObservableIteratorTest.kt │ │ ├── ObservableMapTest.kt │ │ ├── ObservableMinMaxTest.kt │ │ ├── ObservableNoneTest.kt │ │ ├── ObservableReduceTest.kt │ │ ├── ObservableTakeWhileTest.kt │ │ ├── ObservableTaskTest.kt │ │ ├── ObservableToListTest.kt │ │ ├── TaskFilterTest.kt │ │ ├── TaskFlapMapTest.kt │ │ ├── TaskIteratorTest.kt │ │ └── TaskMapTest.kt │ │ ├── runTest.kt │ │ ├── source │ │ ├── ObservableSourceTest.kt │ │ ├── TaskSourceTest.kt │ │ └── test │ │ │ ├── failingSources.kt │ │ │ └── typed.kt │ │ └── tester │ │ ├── Events.kt │ │ ├── ObservableTester.kt │ │ └── OneTester.kt ├── jdk │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── reagent │ │ │ ├── Disposable.kt │ │ │ ├── Observable.kt │ │ │ ├── One.kt │ │ │ └── source │ │ │ ├── ObservableCreator.kt │ │ │ ├── ObservableDeferredCallable.kt │ │ │ ├── ObservableFromChannel.kt │ │ │ ├── ObservableFromCreator.kt │ │ │ ├── ObservableFromRunnable.kt │ │ │ ├── ObservableInterval.kt │ │ │ ├── TaskCreator.kt │ │ │ ├── TaskDeferredCallable.kt │ │ │ ├── TaskFromCallable.kt │ │ │ ├── TaskFromCreator.kt │ │ │ ├── TaskTimer.kt │ │ │ └── functionsJre.kt │ │ └── test │ │ ├── java │ │ └── reagent │ │ │ ├── ObservableJavaTest.java │ │ │ ├── TaskJavaTest.java │ │ │ └── tester │ │ │ └── RecordingObserver.kt │ │ └── kotlin │ │ └── reagent │ │ ├── runTest.kt │ │ └── source │ │ ├── ObservablePlatformSourceTest.kt │ │ └── TaskPlatformSourceTest.kt └── js │ ├── build.gradle │ ├── gradle.properties │ └── src │ ├── main │ └── kotlin │ │ └── reagent │ │ ├── Observable.kt │ │ ├── One.kt │ │ └── source │ │ ├── TaskFromPromise.kt │ │ └── functionsJs.kt │ └── test │ └── kotlin │ └── reagent │ ├── runTest.kt │ └── source │ └── TaskPlatformSourceTest.kt └── settings.gradle /.buildscript/deploy_snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. 4 | # 5 | # Adapted from https://coderwall.com/p/9b_lfq and 6 | # http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ 7 | 8 | SLUG="JakeWharton/Reagent" 9 | JDK="oraclejdk8" 10 | BRANCH="master" 11 | 12 | set -e 13 | 14 | if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then 15 | echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." 16 | elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then 17 | echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." 18 | elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 19 | echo "Skipping snapshot deployment: was pull request." 20 | elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then 21 | echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." 22 | else 23 | echo "Deploying snapshot..." 24 | ./gradlew uploadArchives 25 | echo "Snapshot deployed!" 26 | fi 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /.idea 3 | *.iml 4 | build 5 | node_modules 6 | reports 7 | local.properties 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | install: true 7 | 8 | script: ./gradlew clean build --stacktrace 9 | 10 | after_success: 11 | - .buildscript/deploy_snapshot.sh 12 | 13 | env: 14 | global: 15 | - secure: "kFLpY3MH7ERgT6kM3ALP9bHyp+UKok/+tqdkQDGcUNTcIsRWh/ImOgLXPkSrJRePjpL9+2e8cmvxh0WLHmmxiMcMrVc5FDOcWQKnXsLyeB2KG/qWlNE++M82hGuDgjW7dc8NLgOYUMxeuAUeaCxMVxdUHbwp2ZiTKdQLFunnoBev1G/DGiET/7UAH51jeDALdUunJmCgg9rfolo6urJp4elXg51mNci6Dm9hEsP8X601uFeIA9YDcHntgnFYJgU2FyqUCCRK3PV+h0XW+0LFV0hLltl9aFQapEw7tSxuLkHGuU7r7lCQArRpgS91O8R3mezVsdCelag4T5SnlNQ4j2f1W9IUi3FHI/OcEow5s7cBF8apHxct5LqnMOPRMsDhk2pp20s9qS+AxEQjy/GohS203JiG/hXLP1qQJmAv2eXpvKQmh0Cmrk0oZW+DhlVmIb5m5CcsOvrqrTcxFpW2AJxBkfMUT87E3taUBuy8DtKpHUYrSUGWBX2WNIPBC9AQ4hEC+ywMiArb6DW4wdyq5/CMheCIO1MQvfh/M4yTBobm0EQAa6ootY+jyV3p3X0WE5K53kefpb5xhrs25K5YA6UhVNs0MwXRykFgMnVrnE6LX0RH+XPCacLoowr1dkndi1c6Xt4NGsdtohJrUwUkkNB8CmJ1UEvtLDAaqEHz0pM=" 16 | - secure: "a9bmM23yb4kkCcqVBeVoOXhzjReqBooG2iVFVL6ut6144i3BxU2PXEgc6z1uHSwAnitR9ul/0ZEtE+viCOwPgyXdTUxi/QxgQVmta2nfPdekn95kbQh2+bDjS1lm8VEA1vUF93V7eDavetIX5LRkuBX43WDSwh39cPpFDpELGJshZpMIEDvcQYJ2LmjWLSbd5m0RK1jhfYev+HH8OwxAXe0JdoN+coxXUtsH5+aavnXyp7t5G168JvGlv2lRgTo65QWtw196R4kgXRcCwp2Eu55XuuQId3LzF6WFnAsq+YlG3JQzl8qoe3NFU+wAy3IVR4hDTF8n4iH7ypH72JcV7JEE+1a/gRboCsslvUh5qj8uTYoRmtopBwSR3huQ7Ddnyv8jQoN6GW+FaJpCvyNhiUU9nnQTArNiqfEt3reYUzW8MIPYJ+9YAyJLzgy9WX/g7JzGCFK36nZXMg9vNrllj1Hs77WbK/GLfnnUMWcyCl/V/FuN9wwaHZ8nw3IkrwjFOFAi1NzMMDAtqPQdR6c/6XxO8s9fYiznn6puV/tkGV7zzUDtGbPXCfSssVBLZVQRyMmgLXu3wTt925pXNKX0S2SaZ//pSuIP9Ca4jrUOLMIPRHRmP7eOZyKogiVARDOwh7IUl3uhCQwC8NPdKeG5azUABHKDIvCHD3E2TGD2czw=" 17 | 18 | branches: 19 | except: 20 | - gh-pages 21 | 22 | notifications: 23 | email: false 24 | 25 | sudo: false 26 | 27 | before_cache: 28 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 29 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 30 | 31 | cache: 32 | directories: 33 | - $HOME/.gradle/caches/ 34 | - $HOME/.gradle/wrapper/ 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Reagent 2 | ======= 3 | 4 | Experiments for future reactive libraries. 5 | 6 | * **Simpler asynchrony and concurrency primitives.** 7 | 8 | By leveraging the language and library coroutine support, the need for a custom lightweight job 9 | scheduler and executor as well as concurrency primitives can be largely eliminated. Maintenance 10 | costs for operators goes way down since imperative-looking code can be written. 11 | 12 | * **One API for multiple platforms.** 13 | 14 | Use the same reactive API and implementation in crossplatform code as well as natively inside 15 | platform-specific code. 16 | 17 | * **Polymorphic stream types.** 18 | 19 | Polymorphic types mean that just like how every `List` and `Set` is a `Collection`, 20 | every `Task` is just an `Observable` with only a single element, etc. 21 | 22 | ``` 23 | +---------------+ 24 | | | 25 | | Observable | 26 | | | 27 | +---------------+ 28 | ^ 29 | | 30 | +-------+-------+ 31 | | | 32 | | Task | 33 | | | 34 | +---------------+ 35 | ``` 36 | 37 | 38 | 39 | FAQ 40 | --- 41 | 42 | ### Should I use this? 43 | 44 | No. 45 | 46 | 47 | 48 | License 49 | ------- 50 | 51 | Copyright 2016 Jake Wharton 52 | 53 | Licensed under the Apache License, Version 2.0 (the "License"); 54 | you may not use this file except in compliance with the License. 55 | You may obtain a copy of the License at 56 | 57 | http://www.apache.org/licenses/LICENSE-2.0 58 | 59 | Unless required by applicable law or agreed to in writing, software 60 | distributed under the License is distributed on an "AS IS" BASIS, 61 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 62 | See the License for the specific language governing permissions and 63 | limitations under the License. 64 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.versions = [ 3 | 'kotlin': '1.2.30', 4 | 'coroutines': '0.22.5', 5 | ] 6 | 7 | ext.deps = [ 8 | 'kotlin': [ 9 | 'stdLib': [ 10 | 'common': "org.jetbrains.kotlin:kotlin-stdlib-common:${versions.kotlin}", 11 | 'jdk': "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}", 12 | 'js': "org.jetbrains.kotlin:kotlin-stdlib-js:${versions.kotlin}", 13 | ], 14 | 'test': [ 15 | 'common': "org.jetbrains.kotlin:kotlin-test-common:${versions.kotlin}", 16 | 'annotations': "org.jetbrains.kotlin:kotlin-test-annotations-common:${versions.kotlin}", 17 | 'jdk': "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}", 18 | 'js': "org.jetbrains.kotlin:kotlin-test-js:${versions.kotlin}", 19 | ], 20 | 'coroutines': [ 21 | 'common': "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${versions.coroutines}", 22 | 'jdk': "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutines}", 23 | 'js': "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${versions.coroutines}", 24 | ], 25 | ], 26 | 'rxjava2': 'io.reactivex.rxjava2:rxjava:2.1.10', 27 | 'truth': 'com.google.truth:truth:0.39', 28 | ] 29 | 30 | dependencies { 31 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" 32 | classpath 'com.moowork.gradle:gradle-node-plugin:1.2.0' 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | gradlePluginPortal() 38 | } 39 | } 40 | 41 | subprojects { 42 | group = GROUP 43 | version = VERSION_NAME 44 | 45 | repositories { 46 | mavenCentral() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.jakewharton.reagent 2 | VERSION_NAME=0.0.1-SNAPSHOT 3 | 4 | POM_DESCRIPTION=Experiments for future reactive libraries. 5 | 6 | POM_URL=https://github.com/JakeWharton/Reagent/ 7 | POM_SCM_URL=https://github.com/JakeWharton/Reagent/ 8 | POM_SCM_CONNECTION=scm:git:git://github.com/JakeWharton/Reagent.git 9 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/JakeWharton/Reagent.git 10 | 11 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 12 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 13 | POM_LICENCE_DIST=repo 14 | 15 | POM_DEVELOPER_ID=jakewharton 16 | POM_DEVELOPER_NAME=Jake Wharton 17 | -------------------------------------------------------------------------------- /gradle/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | version = VERSION_NAME 21 | group = GROUP 22 | 23 | def isReleaseBuild() { 24 | return VERSION_NAME.contains("SNAPSHOT") == false 25 | } 26 | 27 | def getReleaseRepositoryUrl() { 28 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 29 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 30 | } 31 | 32 | def getSnapshotRepositoryUrl() { 33 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 34 | : "https://oss.sonatype.org/content/repositories/snapshots/" 35 | } 36 | 37 | def getRepositoryUsername() { 38 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" 39 | } 40 | 41 | def getRepositoryPassword() { 42 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" 43 | } 44 | 45 | afterEvaluate { project -> 46 | uploadArchives { 47 | repositories { 48 | mavenDeployer { 49 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 50 | 51 | pom.groupId = GROUP 52 | pom.artifactId = POM_ARTIFACT_ID 53 | pom.version = VERSION_NAME 54 | 55 | repository(url: getReleaseRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 59 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 60 | } 61 | 62 | pom.project { 63 | name POM_NAME 64 | packaging POM_PACKAGING 65 | description POM_DESCRIPTION 66 | url POM_URL 67 | 68 | scm { 69 | url POM_SCM_URL 70 | connection POM_SCM_CONNECTION 71 | developerConnection POM_SCM_DEV_CONNECTION 72 | } 73 | 74 | licenses { 75 | license { 76 | name POM_LICENCE_NAME 77 | url POM_LICENCE_URL 78 | distribution POM_LICENCE_DIST 79 | } 80 | } 81 | 82 | developers { 83 | developer { 84 | id POM_DEVELOPER_ID 85 | name POM_DEVELOPER_NAME 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | signing { 94 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 95 | sign configurations.archives 96 | } 97 | 98 | if (project.getPlugins().hasPlugin('com.android.application') || 99 | project.getPlugins().hasPlugin('com.android.library')) { 100 | task install(type: Upload, dependsOn: assemble) { 101 | repositories.mavenInstaller { 102 | configuration = configurations.archives 103 | 104 | pom.groupId = GROUP 105 | pom.artifactId = POM_ARTIFACT_ID 106 | pom.version = VERSION_NAME 107 | 108 | pom.project { 109 | name POM_NAME 110 | packaging POM_PACKAGING 111 | description POM_DESCRIPTION 112 | url POM_URL 113 | 114 | scm { 115 | url POM_SCM_URL 116 | connection POM_SCM_CONNECTION 117 | developerConnection POM_SCM_DEV_CONNECTION 118 | } 119 | 120 | licenses { 121 | license { 122 | name POM_LICENCE_NAME 123 | url POM_LICENCE_URL 124 | distribution POM_LICENCE_DIST 125 | } 126 | } 127 | 128 | developers { 129 | developer { 130 | id POM_DEVELOPER_ID 131 | name POM_DEVELOPER_NAME 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | task androidJavadocs(type: Javadoc) { 139 | source = android.sourceSets.main.java.source 140 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 141 | } 142 | 143 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 144 | classifier = 'javadoc' 145 | from androidJavadocs.destinationDir 146 | } 147 | 148 | task androidSourcesJar(type: Jar) { 149 | classifier = 'sources' 150 | from android.sourceSets.main.java.source 151 | } 152 | } else { 153 | install { 154 | repositories.mavenInstaller { 155 | pom.groupId = GROUP 156 | pom.artifactId = POM_ARTIFACT_ID 157 | pom.version = VERSION_NAME 158 | 159 | pom.project { 160 | name POM_NAME 161 | packaging POM_PACKAGING 162 | description POM_DESCRIPTION 163 | url POM_URL 164 | 165 | scm { 166 | url POM_SCM_URL 167 | connection POM_SCM_CONNECTION 168 | developerConnection POM_SCM_DEV_CONNECTION 169 | } 170 | 171 | licenses { 172 | license { 173 | name POM_LICENCE_NAME 174 | url POM_LICENCE_URL 175 | distribution POM_LICENCE_DIST 176 | } 177 | } 178 | 179 | developers { 180 | developer { 181 | id POM_DEVELOPER_ID 182 | name POM_DEVELOPER_NAME 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | task sourcesJar(type: Jar, dependsOn:classes) { 190 | classifier = 'sources' 191 | from sourceSets.main.allSource 192 | } 193 | 194 | task javadocJar(type: Jar, dependsOn:javadoc) { 195 | classifier = 'javadoc' 196 | from javadoc.destinationDir 197 | } 198 | } 199 | 200 | if (JavaVersion.current().isJava8Compatible()) { 201 | allprojects { 202 | tasks.withType(Javadoc) { 203 | options.addStringOption('Xdoclint:none', '-quiet') 204 | } 205 | } 206 | } 207 | 208 | artifacts { 209 | if (project.getPlugins().hasPlugin('com.android.application') || 210 | project.getPlugins().hasPlugin('com.android.library')) { 211 | archives androidSourcesJar 212 | archives androidJavadocsJar 213 | } else { 214 | archives sourcesJar 215 | archives javadocJar 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/Reagent/f0f9605775b84a68a2901ab64bb1212297e17022/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /reagent-rxjava2/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'org.jetbrains.kotlin.jvm' 3 | 4 | sourceCompatibility = JavaVersion.VERSION_1_6 5 | targetCompatibility = JavaVersion.VERSION_1_6 6 | 7 | dependencies { 8 | api project(':reagent:jdk') 9 | api deps.rxjava2 10 | 11 | testImplementation deps.kotlin.test.jdk 12 | } 13 | 14 | kotlin { 15 | experimental { 16 | coroutines 'enable' 17 | } 18 | } 19 | 20 | test { 21 | testLogging { 22 | events 'passed', 'skipped', 'failed' 23 | } 24 | } 25 | 26 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 27 | -------------------------------------------------------------------------------- /reagent-rxjava2/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=reagent-rxjava2 2 | POM_NAME=Reagent RxJava2 Converters 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/CompletableRxToReagent.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.disposables.Disposable 4 | import kotlinx.coroutines.experimental.suspendCancellableCoroutine 5 | import reagent.Task 6 | import io.reactivex.Completable as RxCompletable 7 | import io.reactivex.CompletableObserver as RxCompletableObserver 8 | 9 | internal class CompletableRxToReagent(private val upstream: RxCompletable) : Task() { 10 | override suspend fun produce() = suspendCancellableCoroutine { continuation -> 11 | upstream.subscribe(object : RxCompletableObserver { 12 | override fun onSubscribe(d: Disposable) { 13 | continuation.invokeOnCompletion { 14 | if (continuation.isCancelled) { 15 | d.dispose() 16 | } 17 | } 18 | } 19 | 20 | override fun onComplete() { 21 | continuation.resume(Unit) 22 | } 23 | 24 | override fun onError(e: Throwable) { 25 | continuation.resumeWithException(e) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/JobDisposable.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.disposables.Disposable 4 | import kotlinx.coroutines.experimental.Job 5 | 6 | internal class JobDisposable(private val job: Job) : Disposable { 7 | override fun isDisposed() = job.isCancelled 8 | 9 | override fun dispose() { 10 | job.cancel() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/MaybeRxToReagent.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.disposables.Disposable 4 | import kotlinx.coroutines.experimental.suspendCancellableCoroutine 5 | import reagent.Task 6 | import io.reactivex.Maybe as RxMaybe 7 | import io.reactivex.MaybeObserver as RxMaybeObserver 8 | 9 | internal class MaybeRxToReagent(private val upstream: RxMaybe) : Task() { 10 | override suspend fun produce() = suspendCancellableCoroutine { continuation -> 11 | upstream.subscribe(object : RxMaybeObserver { 12 | override fun onSubscribe(d: Disposable) { 13 | continuation.invokeOnCompletion { 14 | if (continuation.isCancelled) { 15 | d.dispose() 16 | } 17 | } 18 | } 19 | 20 | override fun onSuccess(item: I) { 21 | continuation.resume(item) 22 | } 23 | 24 | override fun onComplete() { 25 | continuation.resume(null) 26 | } 27 | 28 | override fun onError(e: Throwable) { 29 | continuation.resumeWithException(e) 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/ObservableReagentToRx.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.Observer 4 | import io.reactivex.disposables.Disposables 5 | import io.reactivex.exceptions.CompositeException 6 | import io.reactivex.exceptions.Exceptions 7 | import io.reactivex.exceptions.UndeliverableException 8 | import io.reactivex.plugins.RxJavaPlugins 9 | import kotlinx.coroutines.experimental.CoroutineStart.LAZY 10 | import kotlinx.coroutines.experimental.JobCancellationException 11 | import kotlinx.coroutines.experimental.Unconfined 12 | import kotlinx.coroutines.experimental.launch 13 | import reagent.Observable 14 | 15 | internal class ObservableReagentToRx( 16 | private val upstream: Observable 17 | ): io.reactivex.Observable() { 18 | override fun subscribeActual(observer: Observer) { 19 | val job = launch(Unconfined, LAZY) { 20 | try { 21 | upstream.subscribe { 22 | observer.onNext(it) 23 | return@subscribe isActive 24 | } 25 | } catch (ignored: JobCancellationException) { 26 | return@launch 27 | } catch (t: Throwable) { 28 | Exceptions.throwIfFatal(t) 29 | try { 30 | observer.onError(t) 31 | } catch (inner: Throwable) { 32 | RxJavaPlugins.onError(CompositeException(t, inner)) 33 | } 34 | return@launch 35 | } 36 | try { 37 | observer.onComplete() 38 | } catch (t: Throwable) { 39 | RxJavaPlugins.onError(UndeliverableException(t)) 40 | } 41 | } 42 | observer.onSubscribe(JobDisposable(job)) 43 | job.start() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/ObservableRxToReagent.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.disposables.Disposable 4 | import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED 5 | import kotlinx.coroutines.experimental.Unconfined 6 | import kotlinx.coroutines.experimental.launch 7 | import kotlinx.coroutines.experimental.suspendCancellableCoroutine 8 | import reagent.Emitter 9 | import reagent.Observable 10 | import io.reactivex.Observable as RxObservable 11 | import io.reactivex.Observer as RxObserver 12 | 13 | internal class ObservableRxToReagent(private val upstream: RxObservable) : Observable() { 14 | override suspend fun subscribe(emit: Emitter) { 15 | suspendCancellableCoroutine { continuation -> 16 | upstream.subscribe(object : RxObserver { 17 | override fun onSubscribe(d: Disposable) { 18 | continuation.invokeOnCompletion { 19 | if (continuation.isCancelled) { 20 | d.dispose() 21 | } 22 | } 23 | } 24 | 25 | override fun onNext(item: I) { 26 | launch(Unconfined, UNDISPATCHED, parent = continuation) { 27 | emit(item) 28 | } 29 | } 30 | 31 | override fun onComplete() { 32 | continuation.resume(Unit) 33 | } 34 | 35 | override fun onError(e: Throwable) { 36 | continuation.resumeWithException(e) 37 | } 38 | }) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/SingleRxToReagent.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.disposables.Disposable 4 | import kotlinx.coroutines.experimental.suspendCancellableCoroutine 5 | import reagent.Task 6 | import io.reactivex.Single as RxSingle 7 | import io.reactivex.SingleObserver as RxSingleObserver 8 | 9 | internal class SingleRxToReagent(private val upstream: RxSingle) : Task() { 10 | override suspend fun produce() = suspendCancellableCoroutine { continuation -> 11 | upstream.subscribe(object : RxSingleObserver { 12 | override fun onSubscribe(d: Disposable) { 13 | continuation.invokeOnCompletion { 14 | if (continuation.isCancelled) { 15 | d.dispose() 16 | } 17 | } 18 | } 19 | 20 | override fun onSuccess(item: I) { 21 | continuation.resume(item) 22 | } 23 | 24 | override fun onError(e: Throwable) { 25 | continuation.resumeWithException(e) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/TaskReagentToRx.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import io.reactivex.Single 4 | import io.reactivex.SingleObserver 5 | import io.reactivex.exceptions.CompositeException 6 | import io.reactivex.exceptions.UndeliverableException 7 | import io.reactivex.plugins.RxJavaPlugins 8 | import kotlinx.coroutines.experimental.CoroutineStart.LAZY 9 | import kotlinx.coroutines.experimental.JobCancellationException 10 | import kotlinx.coroutines.experimental.Unconfined 11 | import kotlinx.coroutines.experimental.launch 12 | import reagent.Task 13 | 14 | internal class TaskReagentToRx( 15 | private val upstream: Task 16 | ) : Single() { 17 | override fun subscribeActual(observer: SingleObserver) { 18 | val job = launch(Unconfined, LAZY) { 19 | val result = try { 20 | upstream.produce() 21 | } catch (ignored: JobCancellationException) { 22 | return@launch 23 | } catch (t: Throwable) { 24 | try { 25 | observer.onError(t) 26 | } catch (inner: Throwable) { 27 | RxJavaPlugins.onError(CompositeException(t, inner)) 28 | } 29 | return@launch 30 | } 31 | try { 32 | observer.onSuccess(result) 33 | } catch (t: Throwable) { 34 | RxJavaPlugins.onError(UndeliverableException(t)) 35 | } 36 | } 37 | observer.onSubscribe(JobDisposable(job)) 38 | job.start() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/main/kotlin/reagent/rxjava2/converters.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("RxJava2Converters") 2 | 3 | package reagent.rxjava2 4 | 5 | import reagent.Observable 6 | import reagent.Task 7 | import io.reactivex.Completable as RxCompletable 8 | import io.reactivex.Maybe as RxMaybe 9 | import io.reactivex.Observable as RxObservable 10 | import io.reactivex.Single as RxSingle 11 | 12 | fun Observable.toRx(): RxObservable = ObservableReagentToRx(this) 13 | fun Task.toRx(): RxSingle = TaskReagentToRx(this) 14 | 15 | fun RxObservable.toReagent(): Observable = ObservableRxToReagent(this) 16 | fun RxMaybe.toReagent(): Task = MaybeRxToReagent(this) 17 | fun RxSingle.toReagent(): Task = SingleRxToReagent(this) 18 | fun RxCompletable.toReagent(): Task = CompletableRxToReagent(this) 19 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/test/kotlin/reagent/rxjava2/CompletableRxToReagentTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import kotlinx.coroutines.experimental.runBlocking 4 | import org.junit.Assert.fail 5 | import org.junit.Test 6 | import kotlin.test.assertSame 7 | import io.reactivex.Completable as RxCompletable 8 | 9 | class CompletableRxToReagentTest { 10 | @Test fun complete() = runBlocking { 11 | RxCompletable.complete() 12 | .toReagent() 13 | .produce() 14 | } 15 | 16 | @Test fun error() = runBlocking { 17 | val exception = RuntimeException("Oops!") 18 | try { 19 | RxCompletable.error(exception) 20 | .toReagent() 21 | .produce() 22 | fail() 23 | } catch (t: Throwable) { 24 | assertSame(exception, t) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/test/kotlin/reagent/rxjava2/MaybeRxToReagentTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import kotlinx.coroutines.experimental.runBlocking 4 | import org.junit.Assert.fail 5 | import org.junit.Test 6 | import kotlin.test.assertEquals 7 | import kotlin.test.assertNull 8 | import kotlin.test.assertSame 9 | import io.reactivex.Maybe as RxMaybe 10 | 11 | class MaybeRxToReagentTest { 12 | @Test fun complete() = runBlocking { 13 | val value = RxMaybe.empty() 14 | .toReagent() 15 | .produce() 16 | assertNull(value) 17 | } 18 | 19 | @Test fun item() = runBlocking { 20 | val value = RxMaybe.just("Hello") 21 | .toReagent() 22 | .produce() 23 | assertEquals("Hello", value) 24 | } 25 | 26 | @Test fun error() = runBlocking { 27 | val exception = RuntimeException("Oops!") 28 | try { 29 | RxMaybe.error(exception) 30 | .toReagent() 31 | .produce() 32 | fail() 33 | } catch (t: Throwable) { 34 | assertSame(exception, t) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/test/kotlin/reagent/rxjava2/ObservableReagentToRxTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import org.junit.Test 4 | import reagent.Observable 5 | import reagent.source.emptyObservable 6 | import reagent.source.observable 7 | import reagent.source.observableOf 8 | import reagent.source.toTask 9 | import kotlin.test.assertEquals 10 | 11 | class ObservableReagentToRxTest { 12 | @Test fun empty() { 13 | emptyObservable() 14 | .toRx() 15 | .test() 16 | .assertComplete() 17 | } 18 | 19 | @Test fun singleItem() { 20 | (observableOf(1) as Observable) 21 | .toRx() 22 | .test() 23 | .assertValue(1) 24 | .assertComplete() 25 | } 26 | 27 | @Test fun multipleItems() { 28 | observableOf(1, 2, 3) 29 | .toRx() 30 | .test() 31 | .assertValues(1, 2, 3) 32 | .assertComplete() 33 | } 34 | 35 | @Test fun error() { 36 | val exception = RuntimeException("Oops!") 37 | exception.toTask() 38 | .toRx() 39 | .test() 40 | .assertError(exception) 41 | } 42 | 43 | @Test fun upstreamNotified() { 44 | val results = mutableListOf() 45 | observable { 46 | for (i in 1..2) { 47 | results.add(it(i)) 48 | } 49 | }.toRx().take(2).test().assertValues(1, 2).assertComplete() 50 | assertEquals(listOf(true, false), results) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/test/kotlin/reagent/rxjava2/ObservableRxToReagentTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import kotlinx.coroutines.experimental.runBlocking 4 | import org.junit.Assert.fail 5 | import org.junit.Test 6 | import reagent.operator.toList 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertSame 9 | import io.reactivex.Observable as RxObservable 10 | 11 | class ObservableRxToReagentTest { 12 | @Test fun empty() = runBlocking { 13 | val value = RxObservable.empty() 14 | .toReagent() 15 | .toList() 16 | assertEquals(emptyList(), value) 17 | } 18 | 19 | @Test fun item() = runBlocking { 20 | val value = RxObservable.just("Hello") 21 | .toReagent() 22 | .toList() 23 | assertEquals(listOf("Hello"), value) 24 | } 25 | 26 | @Test fun items() = runBlocking { 27 | val value = RxObservable.just("Hello", "World") 28 | .toReagent() 29 | .toList() 30 | assertEquals(listOf("Hello", "World"), value) 31 | } 32 | 33 | @Test fun error() = runBlocking { 34 | val exception = RuntimeException("Oops!") 35 | try { 36 | RxObservable.error(exception) 37 | .toReagent() 38 | .toList() 39 | fail() 40 | } catch (t: Throwable) { 41 | assertSame(exception, t) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/test/kotlin/reagent/rxjava2/SingleRxToReagentTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import kotlinx.coroutines.experimental.runBlocking 4 | import org.junit.Assert.fail 5 | import org.junit.Test 6 | import kotlin.test.assertEquals 7 | import kotlin.test.assertSame 8 | import io.reactivex.Single as RxSingle 9 | 10 | class SingleRxToReagentTest { 11 | @Test fun item() = runBlocking { 12 | val value = RxSingle.just("Hello") 13 | .toReagent() 14 | .produce() 15 | assertEquals("Hello", value) 16 | } 17 | 18 | @Test fun error() = runBlocking { 19 | val exception = RuntimeException("Oops!") 20 | try { 21 | RxSingle.error(exception) 22 | .toReagent() 23 | .produce() 24 | fail() 25 | } catch (t: Throwable) { 26 | assertSame(exception, t) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /reagent-rxjava2/src/test/kotlin/reagent/rxjava2/TaskReagentToRxTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjava2 2 | 3 | import org.junit.Test 4 | import reagent.source.observableOf 5 | import reagent.source.toTask 6 | 7 | class TaskReagentToRxTest { 8 | @Test fun item() { 9 | observableOf(1) 10 | .toRx() 11 | .test() 12 | .assertValue(1) 13 | .assertComplete() 14 | } 15 | 16 | @Test fun error() { 17 | val exception = RuntimeException("Oops!") 18 | exception.toTask() 19 | .toRx() 20 | .test() 21 | .assertError(exception) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reagent-rxjs6/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin2js' 2 | apply plugin: 'com.moowork.node' 3 | 4 | archivesBaseName = 'reagent-rxjs6' 5 | 6 | dependencies { 7 | compile project(':reagent:js') 8 | 9 | testCompile deps.kotlin.test.js 10 | } 11 | 12 | kotlin { 13 | experimental { 14 | coroutines 'enable' 15 | } 16 | } 17 | 18 | task populateNodeModules(type: Copy, dependsOn: compileKotlin2Js) { 19 | from compileKotlin2Js.destinationDir 20 | configurations.testCompile.each { 21 | from zipTree(it.absolutePath).matching { include '*.js' } 22 | } 23 | 24 | into "${buildDir}/node_modules" 25 | } 26 | 27 | [compileKotlin2Js, compileTestKotlin2Js]*.configure { 28 | kotlinOptions.moduleKind = "commonjs" 29 | } 30 | 31 | node { 32 | download = true 33 | } 34 | 35 | task installTestRunner(type: NpmTask) { 36 | args = ['install', 'mocha'] 37 | } 38 | 39 | task runTests(type: NodeTask, dependsOn: [compileTestKotlin2Js, populateNodeModules, installTestRunner]) { 40 | script = file('node_modules/.bin/mocha') 41 | args = [projectDir.toPath().relativize(file(compileTestKotlin2Js.outputFile).toPath())] 42 | } 43 | 44 | test.dependsOn(runTests) 45 | 46 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 47 | -------------------------------------------------------------------------------- /reagent-rxjs6/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=reagent-rxjs6 2 | POM_NAME=Reagent RxJS 6 Converters 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /reagent-rxjs6/src/main/kotlin/reagent/rxjs6/converters.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjs6 2 | 3 | import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED 4 | import kotlinx.coroutines.experimental.JobCancellationException 5 | import kotlinx.coroutines.experimental.Unconfined 6 | import kotlinx.coroutines.experimental.launch 7 | import reagent.Observable 8 | 9 | fun Observable.toRx(): dynamic { 10 | return Rx.Observable.create { observer -> 11 | val job = launch(Unconfined, UNDISPATCHED) { 12 | try { 13 | this@toRx.subscribe { 14 | observer.onNext(it) 15 | true // TODO 16 | } 17 | } catch (ignored: JobCancellationException) { 18 | return@launch 19 | } catch (t: Throwable) { 20 | observer.onError(t) 21 | return@launch 22 | } 23 | observer.onComplete() 24 | } 25 | return@create { job.cancel() } 26 | } 27 | } 28 | 29 | @JsModule("Rx") 30 | private external object Rx { 31 | object Observable { 32 | fun create(body: (Observer) -> TeardownLogic) 33 | } 34 | 35 | interface Observer { 36 | fun onNext(value: I) 37 | fun onComplete() 38 | fun onError(t: Throwable) 39 | } 40 | } 41 | 42 | private typealias TeardownLogic = () -> Unit 43 | -------------------------------------------------------------------------------- /reagent-rxjs6/src/test/kotlin/reagent/rxjs6/ObservableReagentToRxTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.rxjs6 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertTrue 5 | 6 | class ObservableReagentToRxTest { 7 | @Test fun empty() { 8 | // TODO how do we test this? 9 | assertTrue(true) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /reagent/common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.jetbrains.kotlin.platform.common' 2 | 3 | dependencies { 4 | compile deps.kotlin.stdLib.common 5 | compile deps.kotlin.coroutines.common 6 | testCompile deps.kotlin.test.annotations 7 | testCompile deps.kotlin.test.common 8 | } 9 | 10 | kotlin { 11 | experimental { 12 | coroutines 'enable' 13 | } 14 | } 15 | 16 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 17 | -------------------------------------------------------------------------------- /reagent/common/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=reagent-common 2 | POM_NAME=Reagent 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/Observable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent 17 | 18 | /** Emits 0 to infinite items and then signals complete or error. */ 19 | expect abstract class Observable() { 20 | abstract suspend fun subscribe(emit: Emitter) 21 | } 22 | 23 | typealias Emitter = suspend (item: I) -> Boolean 24 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/Task.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent 17 | 18 | import kotlin.DeprecationLevel.HIDDEN 19 | 20 | /** Emits a single item or errors. */ 21 | expect abstract class Task() : Observable { 22 | abstract suspend fun produce(): I 23 | 24 | @Deprecated("Optimized implementation for polymorphism.", level = HIDDEN) 25 | override suspend fun subscribe(emit: Emitter) 26 | } 27 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/all.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | 6 | fun Observable.all(predicate: (I) -> Boolean): Task = ObservableAll(this, predicate) 7 | 8 | internal class ObservableAll( 9 | private val upstream: Observable, 10 | private val predicate: (I) -> Boolean 11 | ) : Task() { 12 | override suspend fun produce(): Boolean { 13 | var result = true 14 | upstream.subscribe { 15 | if (result && !predicate(it)) { 16 | result = false 17 | return@subscribe false 18 | } 19 | return@subscribe true 20 | } 21 | return result 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/any.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | 6 | fun Observable.any(predicate: (I) -> Boolean): Task = ObserableAny(this, predicate) 7 | 8 | internal class ObserableAny( 9 | private val upstream: Observable, 10 | private val predicate: (I) -> Boolean 11 | ) : Task() { 12 | override suspend fun produce(): Boolean { 13 | var result = false 14 | upstream.subscribe { 15 | if (predicate(it)) { 16 | result = true 17 | return@subscribe false 18 | } 19 | return@subscribe true 20 | } 21 | return result 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/concatMap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.Emitter 19 | import reagent.Observable 20 | import kotlin.DeprecationLevel.ERROR 21 | 22 | fun Observable.concatMap(func: (I) -> Observable): Observable = ObservableConcatMapObservable(this, func) 23 | 24 | internal class ObservableConcatMapObservable( 25 | private val upstream: Observable, 26 | private val func: (U) -> Observable 27 | ) : Observable() { 28 | override suspend fun subscribe(emit: Emitter) { 29 | upstream.subscribe upstream@ { 30 | var more = true 31 | func(it).subscribe inner@ { 32 | if (!emit(it)) { 33 | more = false 34 | return@inner false 35 | } 36 | return@inner true 37 | } 38 | return@upstream more 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/concatWith.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.source.concat 5 | 6 | fun Observable.concatWith(other: Observable) = concat(this, other) 7 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/count.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | 6 | fun Observable.count(): Task = ObservableCount(this) 7 | 8 | internal class ObservableCount( 9 | private val upstream: Observable 10 | ) : Task() { 11 | override suspend fun produce(): Int { 12 | var count = 0 13 | upstream.subscribe { 14 | count++ 15 | return@subscribe true 16 | } 17 | return count 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/distinct.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | fun Observable.distinct(): Observable = ObservableDistinct(this, { it }) 7 | fun Observable.distinctBy(selector: (I) -> Any?): Observable = ObservableDistinct(this, selector) 8 | 9 | internal class ObservableDistinct( 10 | private val upstream: Observable, 11 | private val selector: (I) -> Any? 12 | ) : Observable() { 13 | override suspend fun subscribe(emit: Emitter) { 14 | val seen = mutableSetOf() 15 | upstream.subscribe upstream@ { 16 | if (seen.add(selector(it))) { 17 | return@upstream emit(it) 18 | } 19 | return@upstream true 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/distinctUntilChanged.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | fun Observable.distinctUntilChanged(): Observable = ObservableDistinctUntilChanged(this, { it }) 7 | fun Observable.distinctUntilChangedBy(selector: (I) -> Any?): Observable = ObservableDistinctUntilChanged(this, selector) 8 | 9 | internal class ObservableDistinctUntilChanged( 10 | private val upstream: Observable, 11 | private val selector: (I) -> Any? 12 | ) : Observable() { 13 | override suspend fun subscribe(emit: Emitter) { 14 | var first = true 15 | var previous: Any? = null 16 | upstream.subscribe { 17 | val selected = selector(it) 18 | if (first) { 19 | previous = selected 20 | first = false 21 | emit(it) 22 | } else if (selected != previous) { 23 | previous = selected 24 | emit(it) 25 | } else { 26 | true 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/drop.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | fun Observable.drop(count: Long): Observable = ObservableDrop(this, count) 7 | fun Observable.dropOrError(count: Long): Observable = ObservableDrop(this, count, require = true) 8 | 9 | fun Observable.dropWhile(predicate: (I) -> Boolean): Observable = ObservableDropWhile(this, predicate) 10 | 11 | internal class ObservableDrop( 12 | private val upstream: Observable, 13 | private val count: Long, 14 | private val require: Boolean = false 15 | ) : Observable() { 16 | override suspend fun subscribe(emit: Emitter) { 17 | var seen = 0 18 | upstream.subscribe { 19 | if (++seen > count) { 20 | emit(it) 21 | } else { 22 | true 23 | } 24 | } 25 | if (require && seen < count) { 26 | val items = if (count == 1L) "item" else "items" 27 | throw NoSuchElementException("Drop wanted at least $count $items but saw $seen") 28 | } 29 | } 30 | } 31 | 32 | internal class ObservableDropWhile( 33 | private val upstream: Observable, 34 | private val predicate: (I) -> Boolean 35 | ) : Observable() { 36 | override suspend fun subscribe(emit: Emitter) { 37 | var taking = false 38 | upstream.subscribe upstream@ { 39 | if (!taking) { 40 | if (predicate(it)) { 41 | return@upstream true 42 | } 43 | taking = true 44 | } 45 | return@upstream emit(it) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/filter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.Emitter 19 | import reagent.Observable 20 | import reagent.Task 21 | 22 | fun Observable.filter(predicate: (I) -> Boolean): Observable = ObservableFilter(this, predicate) 23 | 24 | fun Task.filter(predicate: (I) -> Boolean): Task = TaskFilter(this, predicate) 25 | 26 | internal class ObservableFilter( 27 | private val upstream: Observable, 28 | private val predicate: (I) -> Boolean 29 | ) : Observable() { 30 | override suspend fun subscribe(emit: Emitter) { 31 | upstream.subscribe { 32 | if (predicate(it)) { 33 | emit(it) 34 | } else { 35 | true 36 | } 37 | } 38 | } 39 | } 40 | 41 | internal class TaskFilter( 42 | private val upstream: Task, 43 | private val predicate: (I) -> Boolean 44 | ) : Task() { 45 | override suspend fun produce(): I? { 46 | val value = upstream.produce() 47 | return if (predicate(value)) value else null 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/flatMap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.Emitter 19 | import reagent.Observable 20 | import reagent.Task 21 | 22 | fun Observable.flatMap(func: (I) -> Observable): Observable = ObservableFlatMapObservable(this, func) 23 | 24 | internal class ObservableFlatMapObservable( 25 | private val upstream: Observable, 26 | private val func: (U) -> Observable 27 | ) : Observable() { 28 | override suspend fun subscribe(emit: Emitter) = TODO() 29 | } 30 | 31 | fun Task.flatMap(func: (I) -> Task): Task = TaskFlatMapTask(this, func) 32 | 33 | internal class TaskFlatMapTask( 34 | private val upstream: Task, 35 | private val func: (U) -> Task 36 | ) : Task() { 37 | override suspend fun produce() = func(upstream.produce()).produce() 38 | } 39 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/fold.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.Observable 19 | import reagent.Task 20 | 21 | fun Observable.fold(initial: R, operation: (accumulator: R, item: I) -> R): Task = ObservableFold(this, initial, operation) 22 | 23 | internal class ObservableFold( 24 | private val upstream: Observable, 25 | private val initial: R, 26 | private val operation: (accumulator: R, item: I) -> R 27 | ) : Task() { 28 | override suspend fun produce(): R { 29 | var value = initial 30 | upstream.subscribe { 31 | value = operation(value, it) 32 | return@subscribe true 33 | } 34 | return value 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/ignoreElements.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | fun Observable<*>.ignoreElements(): Observable = ObservableIgnoreElements(this) 7 | 8 | internal class ObservableIgnoreElements( 9 | private val upstream: Observable<*> 10 | ): Observable() { 11 | override suspend fun subscribe(emit: Emitter) { 12 | upstream.subscribe { true } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/iterator.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | 6 | interface SuspendableIterator { 7 | suspend operator fun hasNext(): Boolean 8 | suspend operator fun next(): I 9 | } 10 | 11 | operator fun Observable.iterator(): SuspendableIterator = ObservableIterator(this) 12 | operator fun Task.iterator(): SuspendableIterator = TaskIterator(this) 13 | 14 | internal class ObservableIterator(private val observable: Observable) : SuspendableIterator { 15 | // TODO i_have_no_idea_what_im_doing.gif 16 | 17 | override suspend fun hasNext() = TODO() 18 | override suspend fun next() = TODO() 19 | } 20 | 21 | internal class TaskIterator(private val task: Task) : SuspendableIterator { 22 | private var done = false 23 | 24 | override suspend fun hasNext() = !done 25 | 26 | override suspend fun next(): I { 27 | if (done) throw IllegalStateException("Must call hasNext() before next()") 28 | done = true 29 | return task.produce() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/map.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.Emitter 19 | import reagent.Observable 20 | import reagent.Task 21 | 22 | fun Observable.map(mapper: (I) -> O): Observable = ObservableMap(this, mapper) 23 | 24 | fun Task.map(mapper: (I) -> O): Task = TaskMap(this, mapper) 25 | 26 | internal class ObservableMap( 27 | private val upstream: Observable, 28 | private val mapper: (U) -> D 29 | ) : Observable() { 30 | override suspend fun subscribe(emit: Emitter) { 31 | upstream.subscribe { emit(mapper(it)) } 32 | } 33 | } 34 | 35 | internal class TaskMap( 36 | private val upstream: Task, 37 | private val mapper: (U) -> D 38 | ) : Task() { 39 | override suspend fun produce() = mapper(upstream.produce()) 40 | } 41 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/minMax.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | import kotlin.math.sign 6 | 7 | fun > Observable.max(): Task = ObservableComparing(this, 1, { it }) 8 | fun > Observable.min(): Task = ObservableComparing(this, -1, { it }) 9 | fun > Observable.maxBy(selector: (I) -> S): Task = ObservableComparing(this, 1, selector) 10 | fun > Observable.minBy(selector: (I) -> S): Task = ObservableComparing(this, -1, selector) 11 | 12 | internal class ObservableComparing>( 13 | private val upstream: Observable, 14 | private val order: Int, 15 | private val selector: (I) -> S 16 | ): Task() { 17 | @Suppress("UNCHECKED_CAST") // 'max' is set to an R instance before cast occurs. 18 | override suspend fun produce(): I { 19 | var first = true 20 | var max: Any? = null 21 | upstream.subscribe { 22 | if (first) { 23 | max = it 24 | first = false 25 | } else if (selector(it).compareTo(selector(max as I)).sign == order) { 26 | max = it 27 | } 28 | return@subscribe true 29 | } 30 | if (first) { 31 | throw NoSuchElementException("No elements to compare") 32 | } 33 | return max as I 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/none.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | 6 | fun Observable.none(predicate: (I) -> Boolean): Task = ObservableNone(this, predicate) 7 | 8 | internal class ObservableNone( 9 | private val upstream: Observable, 10 | private val predicate: (I) -> Boolean 11 | ) : Task() { 12 | override suspend fun produce(): Boolean { 13 | var result = true 14 | upstream.subscribe { 15 | if (result && predicate(it)) { 16 | result = false 17 | return@subscribe false 18 | } 19 | return@subscribe true 20 | } 21 | return result 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/reduce.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.Observable 19 | import reagent.Task 20 | import kotlin.DeprecationLevel.WARNING 21 | 22 | fun Observable.reduce(operation: (accumulator: R, item: I) -> R): Task = ObservableReduce(this, operation) 23 | 24 | @Deprecated("Reduce has no effect on single-element stream types", level = WARNING) 25 | fun Task.reduce(operation: (accumulator: R, item: I) -> R): Task = this 26 | 27 | internal class ObservableReduce( 28 | private val upstream: Observable, 29 | private val operation: (accumulator: R, item: I) -> R 30 | ) : Task() { 31 | @Suppress("UNCHECKED_CAST") // 'accumulator' is set to an R instance before cast occurs. 32 | override suspend fun produce(): R { 33 | var first = true 34 | var accumulator: Any? = null 35 | upstream.subscribe { 36 | if (first) { 37 | accumulator = it 38 | first = false 39 | } else { 40 | accumulator = operation(accumulator as R, it) 41 | } 42 | return@subscribe true 43 | } 44 | if (first) { 45 | throw NoSuchElementException("Reduce requires a non-empty Observable") 46 | } 47 | return accumulator as R 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/take.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | fun Observable.take(count: Long): Observable = ObservableTake(this, count) 7 | fun Observable.takeOrError(count: Long): Observable = ObservableTake(this, count, require = true) 8 | 9 | fun Observable.takeWhile(predicate: (I) -> Boolean): Observable = ObservableTakeWhile(this, predicate) 10 | 11 | internal class ObservableTake( 12 | private val upstream: Observable, 13 | private val count: Long, 14 | private val require: Boolean = false 15 | ) : Observable() { 16 | override suspend fun subscribe(emit: Emitter) { 17 | var seen = 0 18 | upstream.subscribe { 19 | if (seen++ < count) { 20 | emit(it) 21 | } else { 22 | false 23 | } 24 | } 25 | if (require && seen < count) { 26 | val items = if (count == 1L) "item" else "items" 27 | throw NoSuchElementException("Take wanted $count $items but saw $seen") 28 | } 29 | } 30 | } 31 | 32 | internal class ObservableTakeWhile( 33 | private val upstream: Observable, 34 | private val predicate: (I) -> Boolean 35 | ) : Observable() { 36 | override suspend fun subscribe(emit: Emitter) { 37 | var taking = true 38 | upstream.subscribe upstream@ { 39 | if (taking) { 40 | if (predicate(it)) { 41 | return@upstream emit(it) 42 | } else { 43 | taking = false 44 | } 45 | } 46 | return@upstream false 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/operator/toList.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | 5 | suspend fun Observable.toList(): List { 6 | val items = mutableListOf() 7 | subscribe { items.add(it) } 8 | return items 9 | } 10 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableArray.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableArray(private val items: Array) : Observable() { 7 | override suspend fun subscribe(emit: Emitter) { 8 | for (item in items) { 9 | if (!emit(item)) { 10 | return 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableConcat.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableConcat(private val observables: Iterable>) : Observable() { 7 | override suspend fun subscribe(emit: Emitter) { 8 | observables.forEach { it.subscribe(emit) } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableDeferred.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableDeferred(private val func: () -> Observable): Observable() { 7 | override suspend fun subscribe(emit: Emitter) = func().subscribe(emit) 8 | } 9 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableEmpty.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal object ObservableEmpty : Observable() { 7 | override suspend fun subscribe(emit: Emitter) = Unit 8 | } 9 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableFromSuspendingLambda.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableFromSuspendingLambda( 7 | private val body: suspend (emit: Emitter) -> Unit 8 | ): Observable() { 9 | override suspend fun subscribe(emit: Emitter) = body(emit) 10 | } 11 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableIntervalInt.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.delay 4 | import reagent.Emitter 5 | import reagent.Observable 6 | 7 | internal class ObservableIntervalInt(private val periodMillis: Int): Observable() { 8 | override suspend fun subscribe(emit: Emitter) { 9 | var count = 0 10 | while (true) { 11 | delay(periodMillis) 12 | emit(count++) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableIterable.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableIterable(private val iterable: Iterable): Observable() { 7 | override suspend fun subscribe(emit: Emitter) { 8 | for (item in iterable) { 9 | if (!emit(item)) { 10 | return 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/ObservableSequence.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableSequence(private val sequence: Sequence): Observable() { 7 | override suspend fun subscribe(emit: Emitter) { 8 | for (item in sequence) { 9 | if (!emit(item)) { 10 | return 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/TaskDeferred.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | 5 | internal class TaskDeferred(private val func: () -> Task) : Task() { 6 | override suspend fun produce() = func().produce() 7 | } 8 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/TaskError.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | 5 | internal class TaskError(private val t: Throwable) : Task() { 6 | override suspend fun produce() = throw t 7 | } 8 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/TaskFromLambda.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | 5 | internal class TaskFromLambda(private val func: () -> I) : Task() { 6 | override suspend fun produce() = func() 7 | } 8 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/TaskFromSuspendingLambda.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | 5 | internal class TaskFromSuspendingLambda(private val body: suspend () -> I): Task() { 6 | override suspend fun produce() = body() 7 | } 8 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/TaskJust.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | 5 | internal class TaskJust(private val item: I) : Task() { 6 | override suspend fun produce() = item 7 | } 8 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/TaskTimerInt.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.delay 4 | import reagent.Task 5 | 6 | internal class TaskTimerInt(private val delayMillis: Int): Task() { 7 | override suspend fun produce() = delay(delayMillis) 8 | } 9 | -------------------------------------------------------------------------------- /reagent/common/src/main/kotlin/reagent/source/functions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NOTHING_TO_INLINE") // Public API convenience aliases. 2 | 3 | package reagent.source 4 | 5 | import reagent.Emitter 6 | import reagent.Observable 7 | import reagent.Task 8 | 9 | fun observable(body: suspend (emit: Emitter) -> Unit): Observable = ObservableFromSuspendingLambda(body) 10 | fun task(body: suspend () -> I): Task = TaskFromSuspendingLambda(body) 11 | 12 | fun emptyObservable(): Observable = ObservableEmpty 13 | fun observableOf(): Observable = ObservableEmpty 14 | fun observableOf(item: I): Task = TaskJust(item) 15 | fun observableOf(vararg items: I): Observable = when (items.size) { 16 | 0 -> ObservableEmpty 17 | 1 -> TaskJust(items[0]) 18 | else -> ObservableArray(items) 19 | } 20 | 21 | fun observableReturning(func: () -> I): Task = TaskFromLambda(func) 22 | 23 | fun deferObservable(func: () -> Observable): Observable = ObservableDeferred(func) 24 | fun deferTask(func: () -> Task): Task = TaskDeferred(func) 25 | 26 | fun Throwable.toTask(): Task = TaskError(this) 27 | 28 | fun Array.toObservable(): Observable = ObservableArray(this) 29 | fun Iterable.toObservable(): Observable = ObservableIterable(this) 30 | fun Sequence.toObservable(): Observable = ObservableSequence(this) 31 | 32 | expect fun interval(periodMillis: Int): Observable 33 | expect fun timer(delayMillis: Int): Task 34 | 35 | inline fun concat(first: Observable, second: Observable) = concat(listOf(first, second)) 36 | fun concat(vararg observables: Observable): Observable = ObservableConcat(observables.toList()) 37 | fun concat(observables: Iterable>): Observable = ObservableConcat(observables.toList()) 38 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/PolymorphismTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent 17 | 18 | import reagent.source.observableOf 19 | import reagent.source.toTask 20 | import reagent.tester.testObservable 21 | import reagent.tester.testTask 22 | import kotlin.test.Test 23 | 24 | class PolymorphismTest { 25 | @Test fun oneItem() = runTest { 26 | val one = observableOf("Hello") 27 | one.testTask { 28 | item("Hello") 29 | } 30 | one.testObservable { 31 | item("Hello") 32 | complete() 33 | } 34 | } 35 | 36 | @Test fun oneError() = runTest { 37 | val exception = RuntimeException("Oops") 38 | val one = exception.toTask() 39 | one.testTask { 40 | error(exception) 41 | } 42 | one.testObservable { 43 | error(exception) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableAllTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observable 5 | import reagent.source.observableOf 6 | import reagent.source.test.emptyActualObservable 7 | import reagent.tester.testObservable 8 | import reagent.tester.testTask 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | import kotlin.test.fail 12 | 13 | class ObservableAllTest { 14 | @Test fun empty() = runTest { 15 | emptyActualObservable() 16 | .all { fail() } 17 | .testTask { 18 | item(true) 19 | } 20 | } 21 | 22 | @Test fun success() = runTest { 23 | var called = 0 24 | observableOf(1, 2, 3) 25 | .all { called++; true } 26 | .testTask { 27 | item(true) 28 | } 29 | assertEquals(3, called) 30 | } 31 | 32 | @Test fun failure() = runTest { 33 | var called = 0 34 | observableOf(1, 2, 3) 35 | .all { called++; it % 2 == 1 } 36 | .testTask { 37 | item(false) 38 | } 39 | assertEquals(2, called) 40 | } 41 | 42 | @Test fun emitterReturnValue() = runTest { 43 | var result: Boolean? = null 44 | observable { 45 | result = it(1) 46 | }.all { 47 | false 48 | }.testObservable { 49 | item(false) 50 | complete() 51 | } 52 | assertEquals(false, result) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableAnyTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observableOf 5 | import reagent.source.observable 6 | import reagent.source.test.emptyActualObservable 7 | import reagent.tester.testObservable 8 | import reagent.tester.testTask 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | import kotlin.test.fail 12 | 13 | class ObservableAnyTest { 14 | @Test fun empty() = runTest { 15 | emptyActualObservable() 16 | .any { fail() } 17 | .testTask { 18 | item(false) 19 | } 20 | } 21 | 22 | @Test fun success() = runTest { 23 | observableOf(1, 2, 3) 24 | .any { it == 2 } 25 | .testTask { 26 | item(true) 27 | } 28 | } 29 | 30 | @Test fun failure() = runTest { 31 | observableOf(1, 2, 3) 32 | .all { false } 33 | .testTask { 34 | item(false) 35 | } 36 | } 37 | 38 | @Test fun emitterReturnValue() = runTest { 39 | var result: Boolean? = null 40 | observable { 41 | result = it(1) 42 | }.any { 43 | true 44 | }.testObservable { 45 | item(true) 46 | complete() 47 | } 48 | assertEquals(false, result) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableConcatMapTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | class ObservableConcatMapTest { 19 | // TODO overload resolution doesn't work here 20 | // @Test fun concatMapObservable() = runTest { 21 | // val concatMapItems = mutableListOf() 22 | // var observableCalled = 0 23 | // 24 | // observableOf("Task", "Two") 25 | // .concatMap { 26 | // concatMapItems.add(it) 27 | // observableReturning { ++observableCalled } 28 | // } 29 | // .testObservable { 30 | // item(1) 31 | // item(2) 32 | // complete() 33 | // } 34 | // } 35 | // 36 | // @Test fun concatMapObservableEmpty() = runTest { 37 | // emptyActualObservable() 38 | // .concatMap { failObservable() } 39 | // .testObservable { 40 | // complete() 41 | // } 42 | // } 43 | // 44 | // @Test fun concatMapObservableError() = runTest { 45 | // val exception = RuntimeException("Oops!") 46 | // exception.toActualObservable() 47 | // .concatMap { failObservable() } 48 | // .testObservable { 49 | // error(exception) 50 | // } 51 | // } 52 | // 53 | // @Test fun concatMapOne() = runTest { 54 | // val concatMapItems = mutableListOf() 55 | // var oneCalled = 0 56 | // 57 | // observableOf("Task", "Two") 58 | // .concatMap { 59 | // concatMapItems.add(it) 60 | // observableReturning { ++oneCalled } 61 | // } 62 | // .testObservable { 63 | // item(1) 64 | // item(2) 65 | // complete() 66 | // } 67 | // 68 | // assertEquals(listOf("Task", "Two"), concatMapItems) 69 | // assertEquals(2, oneCalled) 70 | // } 71 | // 72 | // @Test fun concatMapOneEmpty() = runTest { 73 | // emptyActualObservable() 74 | // .concatMap { failOne() } 75 | // .testObservable { 76 | // complete() 77 | // } 78 | // } 79 | // 80 | // @Test fun concatMapOneError() = runTest { 81 | // val exception = RuntimeException("Oops!") 82 | // exception.toActualObservable() 83 | // .concatMap { failOne() } 84 | // .testObservable { 85 | // error(exception) 86 | // } 87 | // } 88 | } 89 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableConcatWithTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observableOf 5 | import reagent.tester.testObservable 6 | import kotlin.test.Test 7 | 8 | class ObservableConcatWithTest { 9 | @Test fun concatWith() = runTest { 10 | observableOf(1, 2) 11 | .concatWith(observableOf(3, 4)) 12 | .testObservable { 13 | item(1) 14 | item(2) 15 | item(3) 16 | item(4) 17 | complete() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableCountTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.test.emptyActualObservable 5 | import reagent.source.observableOf 6 | import reagent.tester.testTask 7 | import kotlin.test.Test 8 | 9 | class ObservableCountTest { 10 | @Test fun empty() = runTest { 11 | emptyActualObservable() 12 | .count() 13 | .testTask { 14 | item(0) 15 | } 16 | } 17 | 18 | @Test fun nonEmpty() = runTest { 19 | observableOf(1, 2, 3) 20 | .count() 21 | .testTask { 22 | item(3) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableDistinctTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observableOf 6 | import reagent.tester.testObservable 7 | import kotlin.test.Test 8 | import kotlin.test.fail 9 | 10 | class ObservableDistinctTest { 11 | @Test fun empty() = runTest { 12 | emptyObservable() 13 | .distinct() 14 | .testObservable { 15 | complete() 16 | } 17 | } 18 | 19 | @Test fun emptyBy() = runTest { 20 | emptyObservable() 21 | .distinctBy { fail() } 22 | .testObservable { 23 | complete() 24 | } 25 | } 26 | 27 | @Test fun multiple() = runTest { 28 | observableOf(1, 2, 1, 3, 2, 4, 3, 5, 4, 5) 29 | .distinct() 30 | .testObservable { 31 | item(1) 32 | item(2) 33 | item(3) 34 | item(4) 35 | item(5) 36 | complete() 37 | } 38 | } 39 | 40 | @Test fun multipleBy() = runTest { 41 | observableOf("Hey", "There", "Hello", "To", "You", "There's", "Hats") 42 | .distinctBy { it[0] } 43 | .testObservable { 44 | item("Hey") 45 | item("There") 46 | item("You") 47 | complete() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableDistinctUntilChangedTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observableOf 6 | import reagent.tester.testObservable 7 | import kotlin.test.Test 8 | import kotlin.test.fail 9 | 10 | class ObservableDistinctUntilChangedTest { 11 | @Test fun empty() = runTest { 12 | emptyObservable() 13 | .distinctUntilChanged() 14 | .testObservable { 15 | complete() 16 | } 17 | } 18 | 19 | @Test fun emptyBy() = runTest { 20 | emptyObservable() 21 | .distinctUntilChangedBy { fail() } 22 | .testObservable { 23 | complete() 24 | } 25 | } 26 | 27 | @Test fun multiple() = runTest { 28 | observableOf("Hello", "World", "World", "Hello", "Hello", "Hello", "World") 29 | .distinctUntilChanged() 30 | .testObservable { 31 | item("Hello") 32 | item("World") 33 | item("Hello") 34 | item("World") 35 | complete() 36 | } 37 | } 38 | 39 | @Test fun multipleBy() = runTest { 40 | observableOf("He", "Has", "Gone", "Galloping", "To", "The", "Head") 41 | .distinctUntilChangedBy { it[0] } 42 | .testObservable { 43 | item("He") 44 | item("Gone") 45 | item("To") 46 | item("Head") 47 | complete() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableDropTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observable 6 | import reagent.source.observableOf 7 | import reagent.tester.testObservable 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertTrue 11 | 12 | class ObservableDropTest { 13 | @Test fun emptyDrop() = runTest { 14 | emptyObservable() 15 | .drop(1) 16 | .testObservable { 17 | complete() 18 | } 19 | } 20 | 21 | @Test fun emptyDropOrError() = runTest { 22 | emptyObservable() 23 | .dropOrError(1) 24 | .testObservable { 25 | error { 26 | assertTrue(it is NoSuchElementException) 27 | assertEquals("Drop wanted at least 1 item but saw 0", it.message) 28 | } 29 | } 30 | } 31 | 32 | @Test fun oneDropOne() = runTest { 33 | observableOf(1) 34 | .drop(1) 35 | .testObservable { 36 | complete() 37 | } 38 | } 39 | 40 | @Test fun oneDropOneOrError() = runTest { 41 | observableOf(1) 42 | .dropOrError(1) 43 | .testObservable { 44 | complete() 45 | } 46 | } 47 | 48 | @Test fun twoDropOne() = runTest { 49 | observableOf(1, 2) 50 | .drop(1) 51 | .testObservable { 52 | item(2) 53 | complete() 54 | } 55 | } 56 | 57 | @Test fun twoDropOneOrError() = runTest { 58 | observableOf(1, 2) 59 | .dropOrError(1) 60 | .testObservable { 61 | item(2) 62 | complete() 63 | } 64 | } 65 | 66 | @Test fun emitterReturnValue() = runTest { 67 | val emits = mutableListOf() 68 | observable { 69 | for (i in 1..4) { 70 | emits.add(it(i)) 71 | } 72 | }.drop(2).take(1).testObservable { 73 | item(3) 74 | complete() 75 | } 76 | assertEquals(listOf(true, true, true, false), emits) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableDropWhileTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observable 6 | import reagent.source.observableOf 7 | import reagent.tester.testObservable 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.fail 11 | 12 | class ObservableDropWhileTest { 13 | @Test fun empty() = runTest { 14 | emptyObservable() 15 | .dropWhile { fail() } 16 | .testObservable { 17 | complete() 18 | } 19 | } 20 | 21 | @Test fun dropAll() = runTest { 22 | var called = 0 23 | observableOf(1, 2, 3) 24 | .dropWhile { called++; true } 25 | .testObservable { 26 | complete() 27 | } 28 | assertEquals(3, called) 29 | } 30 | 31 | @Test fun dropSome() = runTest { 32 | observableOf(1, 2, 3) 33 | .dropWhile { it < 2 } 34 | .testObservable { 35 | item(2) 36 | item(3) 37 | complete() 38 | } 39 | } 40 | 41 | @Test fun dropNone() = runTest { 42 | var called = 0 43 | observableOf(1, 2, 3) 44 | .dropWhile { called++; false } 45 | .testObservable { 46 | item(1) 47 | item(2) 48 | item(3) 49 | complete() 50 | } 51 | assertEquals(1, called) 52 | } 53 | 54 | @Test fun dropThrowing() = runTest { 55 | val exception = RuntimeException("Oops!") 56 | observableOf(1, 2, 3) 57 | .dropWhile { throw exception } 58 | .testObservable { 59 | error(exception) 60 | } 61 | } 62 | 63 | @Test fun emitterReturnValue() = runTest { 64 | val emits = mutableListOf() 65 | observable { 66 | for (i in 1..4) { 67 | emits.add(it(i)) 68 | } 69 | }.dropWhile { it < 3 }.take(1).testObservable { 70 | item(3) 71 | complete() 72 | } 73 | assertEquals(listOf(true, true, true, false), emits) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableFilterTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observableOf 5 | import reagent.source.test.emptyActualObservable 6 | import reagent.source.test.toActualObservable 7 | import reagent.tester.testObservable 8 | import kotlin.test.Test 9 | import kotlin.test.fail 10 | 11 | class ObservableFilterTest { 12 | @Test fun filter() = runTest { 13 | observableOf("Hello", "World") 14 | .filter { it == "Hello" } 15 | .testObservable { 16 | item("Hello") 17 | complete() 18 | } 19 | } 20 | 21 | @Test fun filterEmpty() = runTest { 22 | emptyActualObservable() 23 | .filter { fail() } 24 | .testObservable { 25 | complete() 26 | } 27 | } 28 | 29 | @Test fun filterError() = runTest { 30 | val exception = RuntimeException("Oops!") 31 | exception.toActualObservable() 32 | .filter { fail() } 33 | .testObservable { 34 | error(exception) 35 | } 36 | } 37 | 38 | @Test fun filterThrowing() = runTest { 39 | val exception = RuntimeException("Oops!") 40 | observableOf("Hello", "World") 41 | .filter { throw exception } 42 | .testObservable { 43 | error(exception) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableFlatMapTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | class ObservableFlatMapTest { 19 | // TODO overload resolution doesn't work here 20 | // @Test fun flatMapObservable() = runTest { 21 | // val flatMapItems = mutableListOf() 22 | // var observableCalled = 0 23 | // 24 | // observableOf("Task", "Two") 25 | // .flatMap { 26 | // flatMapItems.add(it) 27 | // observableReturning { ++observableCalled } 28 | // } 29 | // .testObservable { 30 | // item(1) 31 | // item(2) 32 | // complete() 33 | // } 34 | // } 35 | // 36 | // @Test fun flatMapObservableEmpty() = runTest { 37 | // emptyActualObservable() 38 | // .flatMap { failObservable() } 39 | // .testObservable { 40 | // complete() 41 | // } 42 | // } 43 | // 44 | // @Test fun flatMapObservableError() = runTest { 45 | // val exception = RuntimeException("Oops!") 46 | // exception.toActualObservable() 47 | // .flatMap { failObservable() } 48 | // .testObservable { 49 | // error(exception) 50 | // } 51 | // } 52 | // 53 | // @Test fun flatMapOne() = runTest { 54 | // val flatMapItems = mutableListOf() 55 | // var oneCalled = 0 56 | // 57 | // observableOf("Task", "Two") 58 | // .flatMap { 59 | // flatMapItems.add(it) 60 | // observableReturning { ++oneCalled } 61 | // } 62 | // .testObservable { 63 | // item(1) 64 | // item(2) 65 | // complete() 66 | // } 67 | // 68 | // assertEquals(listOf("Task", "Two"), flatMapItems) 69 | // assertEquals(2, oneCalled) 70 | // } 71 | // 72 | // @Test fun flatMapOneEmpty() = runTest { 73 | // emptyActualObservable() 74 | // .flatMap { failOne() } 75 | // .testObservable { 76 | // complete() 77 | // } 78 | // } 79 | // 80 | // @Test fun flatMapOneError() = runTest { 81 | // val exception = RuntimeException("Oops!") 82 | // exception.toActualObservable() 83 | // .flatMap { failOne() } 84 | // .testObservable { 85 | // error(exception) 86 | // } 87 | // } 88 | } 89 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableFoldTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observableOf 6 | import reagent.tester.testTask 7 | import kotlin.test.Test 8 | import kotlin.test.assertEquals 9 | import kotlin.test.fail 10 | 11 | class ObservableFoldTest { 12 | @Test fun empty() = runTest { 13 | emptyObservable() 14 | .fold("Hello") { _, _ -> fail() } 15 | .testTask { 16 | item("Hello") 17 | } 18 | } 19 | 20 | @Test fun one() = runTest { 21 | observableOf("Item") 22 | .fold("Seed") { accumulator, item -> 23 | assertEquals("Seed", accumulator) 24 | assertEquals("Item", item) 25 | "Return" 26 | }.testTask { 27 | item("Return") 28 | } 29 | } 30 | 31 | @Test fun multiple() = runTest { 32 | observableOf(1, 2, 3) 33 | .fold(0) { accumulator, item -> accumulator + item } 34 | .testTask { 35 | item(6) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableIgnoreElementsTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observableOf 6 | import reagent.tester.testObservable 7 | import kotlin.test.Test 8 | 9 | class ObservableIgnoreElementsTest { 10 | @Test fun empty() = runTest { 11 | emptyObservable() 12 | .ignoreElements() 13 | .testObservable { 14 | complete() 15 | } 16 | } 17 | 18 | @Test fun items() = runTest { 19 | observableOf(1, 2, 3) 20 | .ignoreElements() 21 | .testObservable { 22 | complete() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableIteratorTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | import reagent.runTest 6 | import reagent.source.observableOf 7 | import reagent.source.test.emptyActualObservable 8 | import reagent.source.test.toActualObservable 9 | import kotlin.test.Ignore 10 | import kotlin.test.Test 11 | import kotlin.test.assertEquals 12 | import kotlin.test.assertFalse 13 | import kotlin.test.assertSame 14 | import kotlin.test.assertTrue 15 | import kotlin.test.fail 16 | 17 | @Ignore 18 | class ObservableIteratorTest { 19 | @Test fun single() = runTest { 20 | val items = mutableListOf() 21 | for (item in observableOf("Hello")) { 22 | items.add(item) 23 | } 24 | assertEquals(listOf("Hello"), items) 25 | } 26 | 27 | @Test fun multiple() = runTest { 28 | val items = mutableListOf() 29 | for (item in observableOf("Hello", "World")) { 30 | items.add(item) 31 | } 32 | assertEquals(listOf("Hello", "World"), items) 33 | } 34 | 35 | @Test fun empty() = runTest { 36 | for (item in emptyActualObservable()) { 37 | fail() 38 | } 39 | } 40 | 41 | @Test fun error() = runTest { 42 | val exception = RuntimeException() 43 | try { 44 | for (item in exception.toActualObservable()) { 45 | fail() 46 | } 47 | fail() 48 | } catch (actual: Throwable) { 49 | assertSame(exception, actual) 50 | } 51 | } 52 | 53 | @Test fun iteratorContract() = runTest { 54 | var called = 0 55 | val task = object : Observable() { 56 | override suspend fun subscribe(emit: Emitter) { 57 | called++ 58 | emit("Hello") 59 | emit("World") 60 | } 61 | } 62 | 63 | val iterator = task.iterator() 64 | assertEquals(0, called) 65 | 66 | assertTrue(iterator.hasNext()) 67 | assertEquals("Hello", iterator.next()) 68 | assertEquals(1, called) 69 | 70 | assertTrue(iterator.hasNext()) 71 | assertEquals("World", iterator.next()) 72 | 73 | assertFalse(iterator.hasNext()) 74 | try { 75 | iterator.next() 76 | } catch (e: IllegalStateException) { 77 | assertEquals("Must call hasNext() before next()", e.message) 78 | } 79 | assertEquals(1, called) 80 | } 81 | 82 | @Test fun iteratorContractNextOnly() = runTest { 83 | var called = 0 84 | val task = object : Observable() { 85 | override suspend fun subscribe(emit: Emitter) { 86 | called++ 87 | emit("Hello") 88 | emit("World") 89 | } 90 | } 91 | 92 | val iterator = task.iterator() 93 | assertEquals(0, called) 94 | 95 | assertEquals("Hello", iterator.next()) 96 | assertEquals(1, called) 97 | 98 | assertEquals("World", iterator.next()) 99 | 100 | try { 101 | iterator.next() 102 | } catch (e: IllegalStateException) { 103 | assertEquals("Must call hasNext() before next()", e.message) 104 | } 105 | assertEquals(1, called) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableMapTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.runTest 19 | import reagent.source.observableOf 20 | import reagent.source.test.emptyActualObservable 21 | import reagent.source.test.toActualObservable 22 | import reagent.tester.testObservable 23 | import kotlin.test.Test 24 | import kotlin.test.fail 25 | 26 | class ObservableMapTest { 27 | @Test fun map() = runTest { 28 | observableOf("Hello", "World") 29 | .map(String::toUpperCase) 30 | .testObservable { 31 | item("HELLO") 32 | item("WORLD") 33 | complete() 34 | } 35 | } 36 | 37 | @Test fun mapEmpty() = runTest { 38 | emptyActualObservable() 39 | .map { fail() } 40 | .testObservable { 41 | complete() 42 | } 43 | } 44 | 45 | @Test fun mapError() = runTest { 46 | val exception = RuntimeException("Oops!") 47 | exception.toActualObservable() 48 | .map { fail() } 49 | .testObservable { 50 | error(exception) 51 | } 52 | } 53 | 54 | @Test fun mapThrowing() = runTest { 55 | val exception = RuntimeException("Oops!") 56 | observableOf("Hello", "World") 57 | .map { throw exception } 58 | .testObservable { 59 | error(exception) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableMinMaxTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observableOf 6 | import reagent.tester.testTask 7 | import kotlin.test.Test 8 | import kotlin.test.assertEquals 9 | import kotlin.test.assertTrue 10 | import kotlin.test.fail 11 | 12 | class ObservableMinMaxTest { 13 | @Test fun emptyMax() = runTest { 14 | emptyObservable() 15 | .max() 16 | .testTask { 17 | error { 18 | assertTrue(it is NoSuchElementException) 19 | assertEquals("No elements to compare", it.message) 20 | } 21 | } 22 | } 23 | 24 | @Test fun emptyMaxBy() = runTest { 25 | emptyObservable() 26 | .maxBy { fail() } 27 | .testTask { 28 | error { 29 | assertTrue(it is NoSuchElementException) 30 | assertEquals("No elements to compare", it.message) 31 | } 32 | } 33 | } 34 | 35 | @Test fun emptyMin() = runTest { 36 | emptyObservable() 37 | .min() 38 | .testTask { 39 | error { 40 | assertTrue(it is NoSuchElementException) 41 | assertEquals("No elements to compare", it.message) 42 | } 43 | } 44 | } 45 | 46 | @Test fun emptyMinBy() = runTest { 47 | emptyObservable() 48 | .minBy { fail() } 49 | .testTask { 50 | error { 51 | assertTrue(it is NoSuchElementException) 52 | assertEquals("No elements to compare", it.message) 53 | } 54 | } 55 | } 56 | 57 | @Test fun maxOne() = runTest { 58 | observableOf(1) 59 | .max() 60 | .testTask { 61 | item(1) 62 | } 63 | } 64 | 65 | @Test fun maxMany() = runTest { 66 | observableOf(1, 5, 2, 3, 4) 67 | .max() 68 | .testTask { 69 | item(5) 70 | } 71 | } 72 | 73 | @Test fun maxByOne() = runTest { 74 | observableOf(1) 75 | .maxBy { fail() } 76 | .testTask { 77 | item(1) 78 | } 79 | } 80 | 81 | @Test fun maxByMany() = runTest { 82 | observableOf("Hello", "World", "Test") 83 | .maxBy { it[0] } 84 | .testTask { 85 | item("World") 86 | } 87 | } 88 | 89 | @Test fun minOne() = runTest { 90 | observableOf(1) 91 | .min() 92 | .testTask { 93 | item(1) 94 | } 95 | } 96 | 97 | @Test fun minMany() = runTest { 98 | observableOf(5, 1, 4, 2, 3) 99 | .min() 100 | .testTask { 101 | item(1) 102 | } 103 | } 104 | 105 | @Test fun minByOne() = runTest { 106 | observableOf(1) 107 | .minBy { fail() } 108 | .testTask { 109 | item(1) 110 | } 111 | } 112 | 113 | @Test fun minByMany() = runTest { 114 | observableOf("Test", "Hello", "World") 115 | .minBy { it[0] } 116 | .testTask { 117 | item("Hello") 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableNoneTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observable 5 | import reagent.source.observableOf 6 | import reagent.source.test.emptyActualObservable 7 | import reagent.tester.testObservable 8 | import reagent.tester.testTask 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | import kotlin.test.fail 12 | 13 | class ObservableNoneTest { 14 | @Test fun empty() = runTest { 15 | emptyActualObservable() 16 | .none { fail() } 17 | .testTask { 18 | item(true) 19 | } 20 | } 21 | 22 | @Test fun success() = runTest { 23 | var called = 0 24 | observableOf(1, 2, 3) 25 | .none { called++; false } 26 | .testTask { 27 | item(true) 28 | } 29 | assertEquals(3, called) 30 | } 31 | 32 | @Test fun failure() = runTest { 33 | var called = 0 34 | observableOf(1, 2, 3) 35 | .none { called++; it % 2 == 0 } 36 | .testTask { 37 | item(false) 38 | } 39 | assertEquals(2, called) 40 | } 41 | 42 | @Test fun emitterReturnValue() = runTest { 43 | var result: Boolean? = null 44 | observable { 45 | result = it(1) 46 | }.none { 47 | true 48 | }.testObservable { 49 | item(false) 50 | complete() 51 | } 52 | assertEquals(false, result) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableReduceTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Observable 4 | import reagent.runTest 5 | import reagent.source.emptyObservable 6 | import reagent.source.observableOf 7 | import reagent.tester.testTask 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertTrue 11 | import kotlin.test.fail 12 | 13 | class ObservableReduceTest { 14 | @Test fun empty() = runTest { 15 | emptyObservable() 16 | .reduce { _, _ -> fail() } 17 | .testTask { 18 | error { 19 | assertTrue(it is NoSuchElementException) 20 | assertEquals("Reduce requires a non-empty Observable", it.message) 21 | } 22 | } 23 | } 24 | 25 | @Test fun one() = runTest { 26 | (observableOf(1) as Observable) 27 | .reduce { _, _ -> fail() } 28 | .testTask { 29 | item(1) 30 | } 31 | } 32 | 33 | @Test fun multiple() = runTest { 34 | observableOf(1, 2, 3) 35 | .reduce { accumulator, item -> accumulator + item } 36 | .testTask { 37 | item(6) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableTakeWhileTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observable 6 | import reagent.source.observableOf 7 | import reagent.tester.testObservable 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.fail 11 | 12 | class ObservableTakeWhileTest { 13 | @Test fun empty() = runTest { 14 | emptyObservable() 15 | .takeWhile { fail() } 16 | .testObservable { 17 | complete() 18 | } 19 | } 20 | 21 | @Test fun takeNone() = runTest { 22 | var called = 0 23 | observableOf(1, 2, 3) 24 | .takeWhile { called++; false } 25 | .testObservable { 26 | complete() 27 | } 28 | assertEquals(1, called) 29 | } 30 | 31 | @Test fun takeSome() = runTest { 32 | observableOf(1, 2, 3) 33 | .takeWhile { it < 3 } 34 | .testObservable { 35 | item(1) 36 | item(2) 37 | complete() 38 | } 39 | } 40 | 41 | @Test fun takeAll() = runTest { 42 | var called = 0 43 | observableOf(1, 2, 3) 44 | .takeWhile { called++; true } 45 | .testObservable { 46 | item(1) 47 | item(2) 48 | item(3) 49 | complete() 50 | } 51 | assertEquals(3, called) 52 | } 53 | 54 | @Test fun takeThrowing() = runTest { 55 | val exception = RuntimeException("Oops!") 56 | observableOf(1, 2, 3) 57 | .takeWhile { throw exception } 58 | .testObservable { 59 | error(exception) 60 | } 61 | } 62 | 63 | @Test fun emitterReturnValue() = runTest { 64 | val emits = mutableListOf() 65 | observable { 66 | for (i in 1..2) { 67 | emits.add(it(i)) 68 | } 69 | }.takeWhile { it < 2 }.testObservable { 70 | item(1) 71 | complete() 72 | } 73 | assertEquals(listOf(true, false), emits) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableTaskTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.emptyObservable 5 | import reagent.source.observable 6 | import reagent.source.observableOf 7 | import reagent.tester.testObservable 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertTrue 11 | 12 | class ObservableTaskTest { 13 | @Test fun emptyTake() = runTest { 14 | emptyObservable() 15 | .take(1) 16 | .testObservable { 17 | complete() 18 | } 19 | } 20 | 21 | @Test fun emptyTakeOrError() = runTest { 22 | emptyObservable() 23 | .takeOrError(1) 24 | .testObservable { 25 | error { 26 | assertTrue(it is NoSuchElementException) 27 | assertEquals("Take wanted 1 item but saw 0", it.message) 28 | } 29 | } 30 | } 31 | 32 | @Test fun oneTakeOne() = runTest { 33 | observableOf(1) 34 | .take(1) 35 | .testObservable { 36 | item(1) 37 | complete() 38 | } 39 | } 40 | 41 | @Test fun oneTakeOneOrError() = runTest { 42 | observableOf(1) 43 | .takeOrError(1) 44 | .testObservable { 45 | item(1) 46 | complete() 47 | } 48 | } 49 | 50 | @Test fun twoTakeOne() = runTest { 51 | observableOf(1, 2) 52 | .take(1) 53 | .testObservable { 54 | item(1) 55 | complete() 56 | } 57 | } 58 | 59 | @Test fun twoTakeOneOrError() = runTest { 60 | observableOf(1, 2) 61 | .takeOrError(1) 62 | .testObservable { 63 | item(1) 64 | complete() 65 | } 66 | } 67 | 68 | @Test fun emitterReturnValue() = runTest { 69 | val emits = mutableListOf() 70 | observable { 71 | for (i in 1..2) { 72 | emits.add(it(i)) 73 | } 74 | }.take(1).testObservable { 75 | item(1) 76 | complete() 77 | } 78 | assertEquals(listOf(true, false), emits) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/ObservableToListTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observableOf 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | 8 | class ObservableToListTest { 9 | @Test fun toList() = runTest { 10 | val list = observableOf(1, 2, 3).toList() 11 | assertEquals(listOf(1, 2, 3), list) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/TaskFilterTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.runTest 4 | import reagent.source.observableOf 5 | import reagent.source.toTask 6 | import reagent.tester.testTask 7 | import kotlin.test.Test 8 | import kotlin.test.fail 9 | 10 | class TaskFilterTest { 11 | @Test fun filter() = runTest { 12 | observableOf("Hello") 13 | .filter { it == "Hello" } 14 | .testTask { 15 | item("Hello") 16 | } 17 | } 18 | @Test fun filterOut() = runTest { 19 | observableOf("Hello") 20 | .filter { it != "Hello" } 21 | .testTask { 22 | item(null) 23 | } 24 | } 25 | 26 | @Test fun filterError() = runTest { 27 | val exception = RuntimeException("Oops!") 28 | exception.toTask() 29 | .filter { fail() } 30 | .testTask { 31 | error(exception) 32 | } 33 | } 34 | 35 | @Test fun mapThrowing() = runTest { 36 | val exception = RuntimeException("Oops!") 37 | observableOf("Hello") 38 | .filter { throw exception } 39 | .testTask { 40 | error(exception) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/TaskFlapMapTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import kotlin.test.Ignore 19 | 20 | // TODO overload resolution doesn't work here 21 | @Ignore 22 | class TaskFlapMapTest { 23 | // @Test fun flatMapObservable() = runTest { 24 | // oneOf("Item") 25 | // .flatMap { observableOf("Hello", "World") } 26 | // .testObservable { 27 | // item("Hello") 28 | // item("World") 29 | // complete() 30 | // } 31 | // } 32 | // 33 | // @Test fun flatMapObservableError() = runTest { 34 | // val exception = RuntimeException("Oops!") 35 | // exception.toTask() 36 | // .flatMap { failObservable() } 37 | // .testObservable { 38 | // error(exception) 39 | // } 40 | // } 41 | // 42 | // @Test fun flatMapOne() = runTest { 43 | // oneOf("Item") 44 | // .flatMap { oneOf("Hello") } 45 | // .testTask { 46 | // item("Hello") 47 | // } 48 | // } 49 | // 50 | // @Test fun flatMapOneError() = runTest { 51 | // val exception = RuntimeException("Oops!") 52 | // exception.toTask() 53 | // .flatMap { failOne() } 54 | // .testTask { 55 | // error(exception) 56 | // } 57 | // } 58 | // 59 | // @Test fun flatMapTask() = runTest { 60 | // oneOf("Item") 61 | // .flatMap { emptyObservable() } 62 | // .testTask { 63 | // complete() 64 | // } 65 | // } 66 | // 67 | // @Test fun flatMapTaskError() = runTest { 68 | // val exception = RuntimeException("Oops!") 69 | // exception.toTask() 70 | // .flatMap { failTask() } 71 | // .testTask { 72 | // error(exception) 73 | // } 74 | // } 75 | } 76 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/TaskIteratorTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.operator 2 | 3 | import reagent.Task 4 | import reagent.runTest 5 | import reagent.source.observableOf 6 | import reagent.source.toTask 7 | import kotlin.test.Test 8 | import kotlin.test.assertEquals 9 | import kotlin.test.assertFalse 10 | import kotlin.test.assertSame 11 | import kotlin.test.assertTrue 12 | import kotlin.test.fail 13 | 14 | class TaskIteratorTest { 15 | @Test fun item() = runTest { 16 | val items = mutableListOf() 17 | for (item in observableOf("Hello")) { 18 | items.add(item) 19 | } 20 | assertEquals(listOf("Hello"), items) 21 | } 22 | 23 | @Test fun error() = runTest { 24 | val exception = RuntimeException() 25 | try { 26 | for (item in exception.toTask()) { 27 | fail() 28 | } 29 | fail() 30 | } catch (actual: Throwable) { 31 | assertSame(exception, actual) 32 | } 33 | } 34 | 35 | @Test fun iteratorContract() = runTest { 36 | var called = 0 37 | val task = object : Task() { 38 | override suspend fun produce(): String { 39 | called++ 40 | return "Hello" 41 | } 42 | } 43 | 44 | val iterator = task.iterator() 45 | assertEquals(0, called) 46 | 47 | assertTrue(iterator.hasNext()) 48 | assertEquals("Hello", iterator.next()) 49 | assertEquals(1, called) 50 | 51 | assertFalse(iterator.hasNext()) 52 | try { 53 | iterator.next() 54 | } catch (e: IllegalStateException) { 55 | assertEquals("Must call hasNext() before next()", e.message) 56 | } 57 | assertEquals(1, called) 58 | } 59 | 60 | @Test fun iteratorContractNextOnly() = runTest { 61 | var called = 0 62 | val task = object : Task() { 63 | override suspend fun produce(): String { 64 | called++ 65 | return "Hello" 66 | } 67 | } 68 | 69 | val iterator = task.iterator() 70 | assertEquals(0, called) 71 | 72 | assertEquals("Hello", iterator.next()) 73 | assertEquals(1, called) 74 | 75 | try { 76 | iterator.next() 77 | } catch (e: IllegalStateException) { 78 | assertEquals("Must call hasNext() before next()", e.message) 79 | } 80 | assertEquals(1, called) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/operator/TaskMapTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.operator 17 | 18 | import reagent.runTest 19 | import reagent.source.observableOf 20 | import reagent.source.toTask 21 | import reagent.tester.testTask 22 | import kotlin.test.Test 23 | import kotlin.test.fail 24 | 25 | class TaskMapTest { 26 | @Test fun map() = runTest { 27 | observableOf("Hello") 28 | .map(String::toUpperCase) 29 | .testTask { 30 | item("HELLO") 31 | } 32 | } 33 | 34 | @Test fun mapError() = runTest { 35 | val exception = RuntimeException("Oops!") 36 | exception.toTask() 37 | .map { fail() } 38 | .testTask { 39 | error(exception) 40 | } 41 | } 42 | 43 | @Test fun mapThrowing() = runTest { 44 | val exception = RuntimeException("Oops!") 45 | observableOf("Hello") 46 | .map { throw exception } 47 | .testTask { 48 | error(exception) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/runTest.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | import kotlinx.coroutines.experimental.CoroutineScope 4 | 5 | expect fun runTest(block: suspend CoroutineScope.() -> Unit) 6 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/source/ObservableSourceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.source 17 | 18 | import kotlinx.coroutines.experimental.delay 19 | import reagent.Observable 20 | import reagent.runTest 21 | import reagent.tester.testObservable 22 | import kotlin.test.Test 23 | import kotlin.test.assertEquals 24 | 25 | class ObservableSourceTest { 26 | @Test fun suspendingLambda() = runTest { 27 | observable { emit -> 28 | delay(10) 29 | emit("Hello") 30 | }.testObservable { 31 | item("Hello") 32 | complete() 33 | } 34 | } 35 | 36 | @Test fun ofSingle() = runTest { 37 | observableOf("Hello") 38 | .testObservable { 39 | item("Hello") 40 | complete() 41 | } 42 | } 43 | 44 | @Test fun ofMultiple() = runTest { 45 | observableOf("Hello", "World") 46 | .testObservable { 47 | item("Hello") 48 | item("World") 49 | complete() 50 | } 51 | } 52 | 53 | @Test fun ofEmpty() = runTest { 54 | observableOf() 55 | .testObservable { 56 | complete() 57 | } 58 | } 59 | 60 | @Test fun array() = runTest { 61 | arrayOf("Hello", "World") 62 | .toObservable() 63 | .testObservable { 64 | item("Hello") 65 | item("World") 66 | complete() 67 | } 68 | } 69 | 70 | @Test fun iterable() = runTest { 71 | listOf("Hello", "World") 72 | .toObservable() 73 | .testObservable { 74 | item("Hello") 75 | item("World") 76 | complete() 77 | } 78 | } 79 | 80 | @Test fun sequence() = runTest { 81 | sequenceOf(1, 2, 4, 8) 82 | .toObservable() 83 | .testObservable { 84 | item(1) 85 | item(2) 86 | item(4) 87 | item(8) 88 | complete() 89 | } 90 | } 91 | 92 | @Test fun defer() = runTest { 93 | var called = 0 94 | val deferred = deferObservable { called++; observableOf("Hello") } 95 | deferred.testObservable { 96 | item("Hello") 97 | complete() 98 | } 99 | assertEquals(1, called) 100 | deferred.testObservable { 101 | item("Hello") 102 | complete() 103 | } 104 | assertEquals(2, called) 105 | } 106 | 107 | @Test fun concatEmpty() = runTest { 108 | concat(emptyList>()) 109 | .testObservable { 110 | complete() 111 | } 112 | } 113 | 114 | @Test fun concat() = runTest { 115 | concat( 116 | observableOf(1), 117 | observableOf(2), 118 | observableOf(3), 119 | observableOf(4) 120 | ).testObservable { 121 | item(1) 122 | item(2) 123 | item(3) 124 | item(4) 125 | complete() 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/source/TaskSourceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.source 17 | 18 | import kotlinx.coroutines.experimental.delay 19 | import reagent.runTest 20 | import reagent.tester.testTask 21 | import kotlin.test.Test 22 | import kotlin.test.assertEquals 23 | import kotlin.test.assertTrue 24 | 25 | class TaskSourceTest { 26 | @Test fun suspendingLambda() = runTest { 27 | task { 28 | delay(10) 29 | "Hello" 30 | }.testTask { 31 | item("Hello") 32 | } 33 | } 34 | 35 | @Test fun of() = runTest { 36 | observableOf("Hello") 37 | .testTask { 38 | item("Hello") 39 | } 40 | } 41 | 42 | @Test fun throwable() = runTest { 43 | val exception = RuntimeException("Oops!") 44 | exception.toTask() 45 | .testTask { 46 | error(exception) 47 | } 48 | } 49 | 50 | @Test fun returning() = runTest { 51 | var called = false 52 | observableReturning { called = true; 0 } 53 | .testTask { 54 | item(0) 55 | } 56 | assertTrue(called) 57 | } 58 | 59 | @Test fun returningThrowing() = runTest { 60 | val exception = RuntimeException("Oops!") 61 | observableReturning { throw exception } 62 | .testTask { 63 | error(exception) 64 | } 65 | } 66 | 67 | @Test fun defer() = runTest { 68 | var called = 0 69 | val deferred = deferTask { called++; observableOf("Hello") } 70 | deferred.testTask { 71 | item("Hello") 72 | } 73 | assertEquals(1, called) 74 | deferred.testTask { 75 | item("Hello") 76 | } 77 | assertEquals(2, called) 78 | } 79 | 80 | @Test fun timer() = runTest { 81 | // TODO need virtual time context to validate this works! 82 | val timer = timer(100) 83 | timer.testTask { 84 | item(Unit) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/source/test/failingSources.kt: -------------------------------------------------------------------------------- 1 | package reagent.source.test 2 | 3 | import reagent.Observable 4 | import reagent.Task 5 | import kotlin.test.fail 6 | 7 | fun failObservable(): Observable = fail() 8 | fun failOne(): Task = fail() 9 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/source/test/typed.kt: -------------------------------------------------------------------------------- 1 | package reagent.source.test 2 | 3 | import reagent.Observable 4 | import reagent.source.emptyObservable 5 | import reagent.source.toTask 6 | 7 | fun emptyActualObservable(): Observable = emptyObservable() 8 | 9 | fun Throwable.toActualObservable(): Observable = toTask() 10 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/tester/Events.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.tester 17 | 18 | data class Item(val item: T) 19 | object Complete 20 | data class Error(val t: Throwable) 21 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/tester/ObservableTester.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.tester 17 | 18 | import reagent.Observable 19 | import kotlin.test.assertEquals 20 | import kotlin.test.assertSame 21 | import kotlin.test.assertTrue 22 | 23 | class ObservableAsserter(private val events: MutableList) { 24 | fun item(item: T) { 25 | assertEquals(Item(item), events.removeAt(0)) 26 | } 27 | 28 | fun complete() { 29 | assertSame(Complete, events.removeAt(0)) 30 | } 31 | 32 | fun error(t: Throwable) { 33 | assertEquals(Error(t), events.removeAt(0)) 34 | } 35 | 36 | fun error(asserter: (Throwable) -> Unit) { 37 | asserter((events.removeAt(0) as Error).t) 38 | } 39 | } 40 | 41 | suspend fun Observable.testObservable(assertions: ObservableAsserter.() -> Unit) { 42 | val events = mutableListOf() 43 | 44 | try { 45 | subscribe { 46 | events.add(Item(it)) 47 | } 48 | events.add(Complete) 49 | } catch (t: Throwable) { 50 | events.add(Error(t)) 51 | } 52 | 53 | ObservableAsserter(events).assertions() 54 | 55 | assertTrue(events.isEmpty(), "Unconsumed events: $events") 56 | } 57 | -------------------------------------------------------------------------------- /reagent/common/src/test/kotlin/reagent/tester/OneTester.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.tester 17 | 18 | import reagent.Task 19 | import kotlin.test.assertEquals 20 | import kotlin.test.assertTrue 21 | 22 | class TaskAsserter(private val events: MutableList) { 23 | fun item(item: T) { 24 | assertEquals(Item(item), events.removeAt(0)) 25 | } 26 | 27 | fun error(t: Throwable) { 28 | assertEquals(Error(t), events.removeAt(0)) 29 | } 30 | 31 | fun error(asserter: (Throwable) -> Unit) { 32 | asserter((events.removeAt(0) as Error).t) 33 | } 34 | } 35 | 36 | suspend fun Task.testTask(assertions: TaskAsserter.() -> Unit) { 37 | val events = mutableListOf() 38 | 39 | try { 40 | events.add(Item(produce())) 41 | } catch (t: Throwable) { 42 | events.add(Error(t)) 43 | } 44 | 45 | TaskAsserter(events).assertions() 46 | 47 | assertTrue(events.isEmpty(), "Unconsumed events: $events") 48 | } 49 | -------------------------------------------------------------------------------- /reagent/jdk/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'org.jetbrains.kotlin.platform.jvm' 3 | 4 | sourceCompatibility = JavaVersion.VERSION_1_6 5 | targetCompatibility = JavaVersion.VERSION_1_6 6 | 7 | archivesBaseName = 'reagent' 8 | 9 | dependencies { 10 | expectedBy project(':reagent:common') 11 | api deps.kotlin.stdLib.jdk 12 | api deps.kotlin.coroutines.jdk 13 | testImplementation deps.kotlin.test.jdk 14 | testImplementation deps.truth 15 | } 16 | 17 | kotlin { 18 | experimental { 19 | coroutines 'enable' 20 | } 21 | } 22 | 23 | test { 24 | testLogging { 25 | events 'passed', 'skipped', 'failed' 26 | } 27 | } 28 | 29 | compileTestJava { 30 | sourceCompatibility = JavaVersion.VERSION_1_8 31 | targetCompatibility = JavaVersion.VERSION_1_8 32 | } 33 | 34 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 35 | -------------------------------------------------------------------------------- /reagent/jdk/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=reagent-jdk 2 | POM_NAME=Reagent 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/Disposable.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | import kotlinx.coroutines.experimental.Job 4 | 5 | interface Disposable { 6 | val isDisposed: Boolean 7 | fun dispose() 8 | } 9 | 10 | internal class JobDisposable(private val job: Job): Disposable { 11 | override val isDisposed get() = job.isCancelled 12 | override fun dispose() { 13 | job.cancel() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/Observable.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED 4 | import kotlinx.coroutines.experimental.Unconfined 5 | import kotlinx.coroutines.experimental.launch 6 | import reagent.source.ObservableCreator 7 | import reagent.source.ObservableArray 8 | import reagent.source.ObservableFromCreator 9 | import reagent.source.ObservableIterable 10 | import reagent.source.ObservableDeferredCallable 11 | import reagent.source.TaskError 12 | import reagent.source.TaskFromCallable 13 | import reagent.source.TaskJust 14 | import reagent.source.ObservableEmpty 15 | import reagent.source.ObservableFromRunnable 16 | import java.util.concurrent.Callable 17 | 18 | /** Emits 0 to infinite items and then signals complete or error. */ 19 | actual abstract class Observable { 20 | actual abstract suspend fun subscribe(emit: Emitter) 21 | 22 | interface Observer { 23 | fun onNext(item: I) 24 | fun onComplete() 25 | fun onError(t: Throwable) 26 | } 27 | 28 | fun subscribe(observer: Observer): Disposable { 29 | val job = launch(Unconfined, UNDISPATCHED) { 30 | try { 31 | subscribe { 32 | observer.onNext(it) 33 | true // TODO hook up Disposable-like thing 34 | } 35 | } catch (t: Throwable) { 36 | observer.onError(t) 37 | return@launch 38 | } 39 | observer.onComplete() 40 | } 41 | return JobDisposable(job) 42 | } 43 | 44 | companion object { 45 | @JvmStatic fun createObservable(body: ObservableCreator): Observable = ObservableFromCreator(body) 46 | @JvmStatic fun empty(): Observable = ObservableEmpty 47 | @JvmStatic fun just(item: I): Observable = TaskJust(item) 48 | @JvmStatic fun error(t: Throwable): Observable = TaskError(t) 49 | @JvmStatic fun fromArray(vararg items: I): Observable = ObservableArray(items) 50 | @JvmStatic fun fromIterable(items: Iterable): Observable = ObservableIterable(items) 51 | @JvmStatic fun fromRunnable(runnable: Runnable): Observable = ObservableFromRunnable(runnable) 52 | @JvmStatic fun fromCallable(callable: Callable): Observable = TaskFromCallable(callable) 53 | @JvmStatic fun deferObservable(callable: Callable>): Observable = ObservableDeferredCallable(callable) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/One.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED 4 | import kotlinx.coroutines.experimental.Unconfined 5 | import kotlinx.coroutines.experimental.launch 6 | import reagent.source.TaskCreator 7 | import reagent.source.TaskDeferredCallable 8 | import reagent.source.TaskError 9 | import reagent.source.TaskFromCallable 10 | import reagent.source.TaskFromCreator 11 | import reagent.source.TaskJust 12 | import java.util.concurrent.Callable 13 | 14 | /** Emits a single item or errors. */ 15 | actual abstract class Task : Observable() { 16 | actual abstract suspend fun produce(): I 17 | 18 | actual override suspend fun subscribe(emit: Emitter) { 19 | emit(produce()) 20 | } 21 | 22 | interface Observer { 23 | fun onItem(item: I) 24 | fun onError(t: Throwable) 25 | } 26 | 27 | fun subscribe(observer: Observer): Disposable { 28 | val job = launch(Unconfined, UNDISPATCHED) { 29 | val value = try { 30 | produce() 31 | } catch (t: Throwable) { 32 | observer.onError(t) 33 | return@launch 34 | } 35 | observer.onItem(value) 36 | } 37 | return JobDisposable(job) 38 | } 39 | 40 | companion object { 41 | @JvmStatic fun createOne(body: TaskCreator): Task = TaskFromCreator(body) 42 | @JvmStatic fun just(item: I): Task = TaskJust(item) 43 | @JvmStatic fun error(t: Throwable): Task = TaskError(t) 44 | @JvmStatic fun fromCallable(callable: Callable): Task = TaskFromCallable(callable) 45 | @JvmStatic fun deferOne(callable: Callable>): Task = TaskDeferredCallable(callable) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/ObservableCreator.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Observable 4 | 5 | interface ObservableCreator { 6 | fun subscribe(downstream: Downstream) 7 | 8 | interface Downstream : Observable.Observer { 9 | val isDisposed: Boolean 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/ObservableDeferredCallable.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | import java.util.concurrent.Callable 6 | 7 | internal class ObservableDeferredCallable(private val func: Callable>): Observable() { 8 | override suspend fun subscribe(emit: Emitter) = func.call().subscribe(emit) 9 | } 10 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/ObservableFromChannel.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.channels.ReceiveChannel 4 | import kotlinx.coroutines.experimental.channels.consumeEach 5 | import reagent.Emitter 6 | import reagent.Observable 7 | 8 | internal class ObservableFromChannel(private val channel: ReceiveChannel) : Observable() { 9 | override suspend fun subscribe(emit: Emitter) { 10 | for (item in channel) { 11 | if (!emit(item)) { 12 | return 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/ObservableFromCreator.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.CancellableContinuation 4 | import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED 5 | import kotlinx.coroutines.experimental.Unconfined 6 | import kotlinx.coroutines.experimental.launch 7 | import kotlinx.coroutines.experimental.suspendCancellableCoroutine 8 | import reagent.Emitter 9 | import reagent.Observable 10 | import reagent.source.ObservableCreator.Downstream 11 | 12 | internal class ObservableFromCreator(private val creator: ObservableCreator): Observable() { 13 | override suspend fun subscribe(emit: Emitter) { 14 | suspendCancellableCoroutine { 15 | creator.subscribe(DownstreamEmitter(it, emit)) 16 | } 17 | } 18 | 19 | class DownstreamEmitter( 20 | private val continuation: CancellableContinuation, 21 | private val emit: Emitter 22 | ) : Downstream { 23 | override val isDisposed get() = continuation.isCancelled 24 | 25 | override fun onNext(item: I) { 26 | launch(Unconfined, 27 | UNDISPATCHED) { 28 | emit(item) 29 | } 30 | } 31 | 32 | override fun onComplete() { 33 | continuation.resume(Unit) 34 | } 35 | 36 | override fun onError(t: Throwable) { 37 | continuation.resumeWithException(t) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/ObservableFromRunnable.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Emitter 4 | import reagent.Observable 5 | 6 | internal class ObservableFromRunnable(private val func: Runnable) : Observable() { 7 | override suspend fun subscribe(emit: Emitter) = func.run() 8 | } 9 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/ObservableInterval.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.delay 4 | import reagent.Emitter 5 | import reagent.Observable 6 | import java.util.concurrent.TimeUnit 7 | 8 | internal class ObservableInterval(private val period: Long, private val unit: TimeUnit): Observable() { 9 | override suspend fun subscribe(emit: Emitter) { 10 | var count = 0 11 | while (true) { 12 | delay(period, unit) 13 | if (!emit(count++)) { 14 | return 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/TaskCreator.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | 5 | interface TaskCreator { 6 | fun subscribe(downstream: Downstream) 7 | 8 | interface Downstream : Task.Observer { 9 | val isDisposed: Boolean 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/TaskDeferredCallable.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | import java.util.concurrent.Callable 5 | 6 | internal class TaskDeferredCallable(private val func: Callable>): Task() { 7 | override suspend fun produce() = func.call().produce() 8 | } 9 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/TaskFromCallable.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | import java.util.concurrent.Callable 5 | 6 | internal class TaskFromCallable(private val func: Callable) : Task() { 7 | override suspend fun produce() = func.call() 8 | } 9 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/TaskFromCreator.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.CancellableContinuation 4 | import kotlinx.coroutines.experimental.suspendCancellableCoroutine 5 | import reagent.Task 6 | import reagent.source.TaskCreator.Downstream 7 | 8 | internal class TaskFromCreator(private val creator: TaskCreator): Task() { 9 | override suspend fun produce() = suspendCancellableCoroutine { 10 | creator.subscribe(DownstreamProducer(it)) 11 | } 12 | 13 | class DownstreamProducer( 14 | private val continuation: CancellableContinuation 15 | ) : Downstream { 16 | override val isDisposed get() = continuation.isCancelled 17 | 18 | override fun onItem(item: I) { 19 | continuation.resume(item) 20 | } 21 | 22 | override fun onError(t: Throwable) { 23 | continuation.resumeWithException(t) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/TaskTimer.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | import java.util.concurrent.TimeUnit 5 | 6 | internal class TaskTimer(private val delay: Long, private val unit: TimeUnit): Task() { 7 | override suspend fun produce() = kotlinx.coroutines.experimental.delay(delay, unit) 8 | } 9 | -------------------------------------------------------------------------------- /reagent/jdk/src/main/kotlin/reagent/source/functionsJre.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.source 17 | 18 | import kotlinx.coroutines.experimental.channels.ReceiveChannel 19 | import reagent.Observable 20 | import reagent.Task 21 | import java.time.Duration 22 | import java.util.concurrent.Callable 23 | import java.util.concurrent.TimeUnit 24 | import java.util.concurrent.TimeUnit.MILLISECONDS 25 | 26 | fun Callable.asObservable(): Task = TaskFromCallable(this) 27 | fun Runnable.asObservable(): Observable = ObservableFromRunnable(this) 28 | 29 | fun ReceiveChannel.toObservable(): Observable = ObservableFromChannel(this) 30 | 31 | @Deprecated( 32 | "Use overload that accepts a TimeUnit.", 33 | ReplaceWith("interval(periodMillis, MILLISECONDS)", "java.util.concurrent.TimeUnit.MILLISECONDS") 34 | ) 35 | actual fun interval(periodMillis: Int): Observable = ObservableIntervalInt(periodMillis) 36 | fun interval(period: Long, unit: TimeUnit): Observable = ObservableInterval(period, unit) 37 | fun Duration.asInterval(): Observable = ObservableInterval(toMillis(), MILLISECONDS) 38 | 39 | @Deprecated( 40 | "Use overload that accepts a TimeUnit.", 41 | ReplaceWith("timer(delayMillis, MILLISECONDS)", "java.util.concurrent.TimeUnit.MILLISECONDS") 42 | ) 43 | actual fun timer(delayMillis: Int): Task = TaskTimerInt(delayMillis) 44 | fun timer(delay: Long, unit: TimeUnit): Task = TaskTimer(delay, unit) 45 | fun Duration.asTimer(): Task = TaskTimer(toMillis(), MILLISECONDS) 46 | -------------------------------------------------------------------------------- /reagent/jdk/src/test/java/reagent/ObservableJavaTest.java: -------------------------------------------------------------------------------- 1 | package reagent; 2 | 3 | import com.google.common.truth.BooleanSubject; 4 | import com.google.common.truth.DefaultSubject; 5 | import com.google.common.truth.IntegerSubject; 6 | import com.google.common.truth.StringSubject; 7 | import com.google.common.truth.Truth; 8 | import java.util.Arrays; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import reagent.tester.ObservableRecorder; 14 | import reagent.tester.RecordingRule; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | import static org.junit.Assert.assertTrue; 18 | 19 | public final class ObservableJavaTest { 20 | @Rule public final RecordingRule rule = new RecordingRule(); 21 | 22 | @Test public void create() { 23 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 24 | Observable.createObservable(downstream -> { 25 | downstream.onNext("Hello"); 26 | downstream.onNext("World"); 27 | downstream.onComplete(); 28 | }).subscribe(recorder); 29 | 30 | recorder.assertItem().isEqualTo("Hello"); 31 | recorder.assertItem().isEqualTo("World"); 32 | recorder.assertComplete(); 33 | } 34 | 35 | @Test public void createError() { 36 | RuntimeException exception = new RuntimeException("Oops!"); 37 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 38 | Observable.createObservable(downstream -> downstream.onError(exception)).subscribe(recorder); 39 | 40 | recorder.assertError().isSameAs(exception); 41 | } 42 | 43 | @Test public void empty() { 44 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 45 | Observable.empty().subscribe(recorder); 46 | 47 | recorder.assertComplete(); 48 | } 49 | 50 | @Test public void just() { 51 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 52 | Observable.just("Hello").subscribe(recorder); 53 | 54 | recorder.assertItem().isEqualTo("Hello"); 55 | recorder.assertComplete(); 56 | } 57 | 58 | @Test public void error() { 59 | RuntimeException exception = new RuntimeException("Oops!"); 60 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 61 | Observable.error(exception).subscribe(recorder); 62 | 63 | recorder.assertError().isSameAs(exception); 64 | } 65 | 66 | @Test public void fromArray() { 67 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 68 | Observable.fromArray("Hello", "World").subscribe(recorder); 69 | 70 | recorder.assertItem().isEqualTo("Hello"); 71 | recorder.assertItem().isEqualTo("World"); 72 | recorder.assertComplete(); 73 | } 74 | 75 | @Test public void fromIterable() { 76 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 77 | Observable.fromIterable(Arrays.asList("Hello", "World")).subscribe(recorder); 78 | 79 | recorder.assertItem().isEqualTo("Hello"); 80 | recorder.assertItem().isEqualTo("World"); 81 | recorder.assertComplete(); 82 | } 83 | 84 | @Test public void defer() { 85 | AtomicInteger count = new AtomicInteger(); 86 | Observable deferred = Observable.deferObservable(() -> Observable.just(count.incrementAndGet())); 87 | 88 | ObservableRecorder recorder1 = rule.observable(Truth::assertThat); 89 | deferred.subscribe(recorder1); 90 | recorder1.assertItem().isEqualTo(1); 91 | recorder1.assertComplete(); 92 | 93 | ObservableRecorder recorder2 = rule.observable(Truth::assertThat); 94 | deferred.subscribe(recorder2); 95 | recorder2.assertItem().isEqualTo(2); 96 | recorder2.assertComplete(); 97 | } 98 | 99 | @Test public void fromCallable() { 100 | AtomicBoolean called = new AtomicBoolean(); 101 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 102 | Observable.fromCallable(() -> called.compareAndSet(false, true)).subscribe(recorder); 103 | assertTrue(called.get()); 104 | 105 | recorder.assertItem().isTrue(); 106 | recorder.assertComplete(); 107 | } 108 | 109 | @Test public void fromCallableThrowing() { 110 | RuntimeException exception = new RuntimeException("Oops!"); 111 | ObservableRecorder recorder = rule.observable(Truth::assertThat); 112 | Observable.fromCallable(() -> { throw exception; }).subscribe(recorder); 113 | 114 | recorder.assertError().isSameAs(exception); 115 | } 116 | 117 | @Test public void fromRunnable() { 118 | AtomicBoolean called = new AtomicBoolean(); 119 | ObservableRecorder recorder = rule.observable(ObservableJavaTest::assertThat); 120 | Observable.fromRunnable(() -> called.compareAndSet(false, true)).subscribe(recorder); 121 | assertTrue(called.get()); 122 | 123 | recorder.assertComplete(); 124 | } 125 | 126 | @Test public void fromRunnableThrowing() { 127 | RuntimeException exception = new RuntimeException("Oops!"); 128 | ObservableRecorder recorder = rule.observable(ObservableJavaTest::assertThat); 129 | Observable.fromRunnable(() -> { throw exception; }).subscribe(recorder); 130 | 131 | recorder.assertError().isSameAs(exception); 132 | } 133 | 134 | private static DefaultSubject assertThat(Object value) { 135 | return (DefaultSubject) Truth.assertThat(value); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /reagent/jdk/src/test/java/reagent/TaskJavaTest.java: -------------------------------------------------------------------------------- 1 | package reagent; 2 | 3 | import com.google.common.truth.BooleanSubject; 4 | import com.google.common.truth.IntegerSubject; 5 | import com.google.common.truth.StringSubject; 6 | import com.google.common.truth.Truth; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import reagent.tester.TaskRecorder; 12 | import reagent.tester.RecordingRule; 13 | 14 | import static org.junit.Assert.assertTrue; 15 | 16 | public final class TaskJavaTest { 17 | @Rule public final RecordingRule rule = new RecordingRule(); 18 | 19 | @Test public void create() { 20 | TaskRecorder recorder = rule.one(Truth::assertThat); 21 | Task.createOne(downstream -> downstream.onItem("Hello")).subscribe(recorder); 22 | 23 | recorder.assertItem().isEqualTo("Hello"); 24 | } 25 | 26 | @Test public void createError() { 27 | RuntimeException exception = new RuntimeException("Oops!"); 28 | TaskRecorder recorder = rule.one(Truth::assertThat); 29 | Task.createOne(downstream -> downstream.onError(exception)).subscribe(recorder); 30 | 31 | recorder.assertError().isSameAs(exception); 32 | } 33 | 34 | @Test public void just() { 35 | TaskRecorder recorder = rule.one(Truth::assertThat); 36 | Task.just("Hello").subscribe(recorder); 37 | 38 | recorder.assertItem().isEqualTo("Hello"); 39 | } 40 | 41 | @Test public void error() { 42 | RuntimeException exception = new RuntimeException("Oops!"); 43 | TaskRecorder recorder = rule.one(Truth::assertThat); 44 | Task.error(exception).subscribe(recorder); 45 | 46 | recorder.assertError().isSameAs(exception); 47 | } 48 | 49 | @Test public void defer() { 50 | AtomicInteger count = new AtomicInteger(); 51 | Task deferred = Task.deferOne(() -> Task.just(count.incrementAndGet())); 52 | 53 | TaskRecorder recorder1 = rule.one(Truth::assertThat); 54 | deferred.subscribe(recorder1); 55 | recorder1.assertItem().isEqualTo(1); 56 | 57 | TaskRecorder recorder2 = rule.one(Truth::assertThat); 58 | deferred.subscribe(recorder2); 59 | recorder2.assertItem().isEqualTo(2); 60 | } 61 | 62 | @Test public void fromCallable() { 63 | AtomicBoolean called = new AtomicBoolean(); 64 | TaskRecorder recorder = rule.one(Truth::assertThat); 65 | Task.fromCallable(() -> called.compareAndSet(false, true)).subscribe(recorder); 66 | assertTrue(called.get()); 67 | 68 | recorder.assertItem().isTrue(); 69 | } 70 | 71 | @Test public void fromCallableThrowing() { 72 | RuntimeException exception = new RuntimeException("Oops!"); 73 | TaskRecorder recorder = rule.one(Truth::assertThat); 74 | Task.fromCallable(() -> { throw exception; }).subscribe(recorder); 75 | 76 | recorder.assertError().isSameAs(exception); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /reagent/jdk/src/test/java/reagent/tester/RecordingObserver.kt: -------------------------------------------------------------------------------- 1 | package reagent.tester 2 | 3 | import com.google.common.truth.Subject 4 | import com.google.common.truth.ThrowableSubject 5 | import com.google.common.truth.Truth.assertThat 6 | import org.junit.rules.TestRule 7 | import org.junit.runner.Description 8 | import org.junit.runners.model.Statement 9 | import reagent.Observable 10 | import reagent.Task 11 | import java.util.concurrent.LinkedBlockingDeque 12 | import java.util.concurrent.TimeUnit.SECONDS 13 | import kotlin.DeprecationLevel.ERROR 14 | import kotlin.test.assertEquals 15 | import kotlin.test.fail 16 | 17 | class RecordingRule : TestRule { 18 | private val recorders = mutableListOf>() 19 | 20 | override fun apply(base: Statement, description: Description): Statement { 21 | return object : Statement() { 22 | override fun evaluate() { 23 | try { 24 | base.evaluate() 25 | recorders.forEach { it.assertExhausted() } 26 | } finally { 27 | recorders.clear() 28 | } 29 | } 30 | } 31 | } 32 | 33 | fun > observable(asserter: (I) -> S) = ObservableRecorder(asserter).also { recorders.add(it) } 34 | fun > one(asserter: (I) -> S) = TaskRecorder(asserter).also { recorders.add(it) } 35 | } 36 | 37 | abstract class Recorder>( 38 | private val asserter: (I) -> S 39 | ) { 40 | private val events = LinkedBlockingDeque() 41 | 42 | protected fun event(event: Any) { 43 | events.addLast(event) 44 | } 45 | 46 | open fun assertItem(): S { 47 | val item = events.pollFirst(1, SECONDS) ?: fail("Timed out waiting for complete.") 48 | if (item is Item<*>) { 49 | @Suppress("UNCHECKED_CAST") 50 | return asserter(item.item as I) 51 | } 52 | fail("Expected item but was $item") 53 | } 54 | 55 | open fun assertComplete() { 56 | val item = events.pollFirst(1, SECONDS) ?: fail("Timed out waiting for complete.") 57 | assertEquals(Complete, item) 58 | } 59 | 60 | fun assertError(): ThrowableSubject { 61 | val item = events.pollFirst(1, SECONDS) ?: fail("Timed out waiting for complete.") 62 | if (item is Error) { 63 | return assertThat(item.t) 64 | } 65 | fail("Expected error but was $item") 66 | } 67 | 68 | fun assertExhausted() { 69 | assertThat(events).isEmpty() 70 | } 71 | } 72 | 73 | class ObservableRecorder>( 74 | asserter: (I) -> S 75 | ) : Recorder(asserter), Observable.Observer { 76 | override fun onNext(item: I) = event(Item(item)) 77 | override fun onComplete() = event(Complete) 78 | override fun onError(t: Throwable) = event(Error(t)) 79 | } 80 | 81 | class TaskRecorder>( 82 | asserter: (I) -> S 83 | ) : Recorder(asserter), Task.Observer { 84 | override fun onItem(item: I) = event(Item(item)) 85 | override fun onError(t: Throwable) = event(Error(t)) 86 | 87 | @Deprecated("Task does not have complete events.", level = ERROR) 88 | override fun assertComplete() = fail() 89 | } 90 | -------------------------------------------------------------------------------- /reagent/jdk/src/test/kotlin/reagent/runTest.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | import kotlinx.coroutines.experimental.CoroutineScope 4 | import kotlinx.coroutines.experimental.runBlocking 5 | 6 | actual fun runTest(block: suspend CoroutineScope.() -> Unit) = runBlocking { 7 | block() 8 | } 9 | -------------------------------------------------------------------------------- /reagent/jdk/src/test/kotlin/reagent/source/ObservablePlatformSourceTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import kotlinx.coroutines.experimental.channels.Channel 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Test 6 | import reagent.runTest 7 | import reagent.tester.testObservable 8 | import java.util.concurrent.Callable 9 | import java.util.concurrent.atomic.AtomicBoolean 10 | 11 | class ObservablePlatformSourceTest { 12 | @Test fun channel() = runTest { 13 | val channel = Channel(2) 14 | channel.send("Hello") 15 | channel.send("World") 16 | channel.close() 17 | 18 | channel.toObservable() 19 | .testObservable { 20 | item("Hello") 21 | item("World") 22 | complete() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /reagent/jdk/src/test/kotlin/reagent/source/TaskPlatformSourceTest.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import org.junit.Assert.assertTrue 4 | import org.junit.Test 5 | import reagent.runTest 6 | import reagent.tester.testTask 7 | import java.time.Duration 8 | import java.util.concurrent.Callable 9 | import java.util.concurrent.TimeUnit.MILLISECONDS 10 | import java.util.concurrent.atomic.AtomicBoolean 11 | import kotlin.system.measureTimeMillis 12 | 13 | class TaskPlatformSourceTest { 14 | @Test fun fromCallable() = runTest { 15 | val called = AtomicBoolean() 16 | Callable { called.getAndSet(true) } 17 | .asObservable() 18 | .testTask { 19 | item(false) 20 | } 21 | assertTrue(called.get()) 22 | } 23 | 24 | @Test fun fromCallableThrowing() = runTest { 25 | val exception = RuntimeException("Oops!") 26 | Callable { throw exception } 27 | .asObservable() 28 | .testTask { 29 | error(exception) 30 | } 31 | } 32 | 33 | @Test fun timerWithUnit() = runTest { 34 | // TODO need virtual time context to validate this works! 35 | val timer = timer(100, MILLISECONDS) 36 | val took = measureTimeMillis { 37 | timer.testTask { item(Unit) } 38 | } 39 | assertTrue(took >= 100) 40 | } 41 | 42 | @Test fun durationTimer() = runTest { 43 | // TODO need virtual time context to validate this works! 44 | val timer = Duration.ofMillis(100).asTimer() 45 | val took = measureTimeMillis { 46 | timer.testTask { item(Unit) } 47 | } 48 | assertTrue(took >= 100) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reagent/js/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.jetbrains.kotlin.platform.js' 2 | apply plugin: 'com.moowork.node' 3 | 4 | archivesBaseName = 'reagent' 5 | 6 | dependencies { 7 | expectedBy project(':reagent:common') 8 | compile deps.kotlin.stdLib.js 9 | compile deps.kotlin.coroutines.js 10 | testCompile deps.kotlin.test.js 11 | } 12 | 13 | kotlin { 14 | experimental { 15 | coroutines 'enable' 16 | } 17 | } 18 | 19 | task populateNodeModules(type: Copy, dependsOn: compileKotlin2Js) { 20 | from compileKotlin2Js.destinationDir 21 | configurations.testCompile.each { 22 | from zipTree(it.absolutePath).matching { include '*.js' } 23 | } 24 | 25 | into "${buildDir}/node_modules" 26 | } 27 | 28 | [compileKotlin2Js, compileTestKotlin2Js]*.configure { 29 | kotlinOptions.moduleKind = "commonjs" 30 | } 31 | 32 | node { 33 | download = true 34 | } 35 | 36 | task installTestRunner(type: NpmTask) { 37 | args = ['install', 'mocha'] 38 | } 39 | 40 | task runTests(type: NodeTask, dependsOn: [compileTestKotlin2Js, populateNodeModules, installTestRunner]) { 41 | script = file('node_modules/.bin/mocha') 42 | args = [projectDir.toPath().relativize(file(compileTestKotlin2Js.outputFile).toPath())] 43 | } 44 | 45 | test.dependsOn(runTests) 46 | 47 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 48 | -------------------------------------------------------------------------------- /reagent/js/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=reagent-js 2 | POM_NAME=Reagent 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /reagent/js/src/main/kotlin/reagent/Observable.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | /** Emits 0 to infinite items and then signals complete or error. */ 4 | actual abstract class Observable { 5 | actual abstract suspend fun subscribe(emit: Emitter) 6 | } 7 | -------------------------------------------------------------------------------- /reagent/js/src/main/kotlin/reagent/One.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | /** Emits a single item or errors. */ 4 | actual abstract class Task : Observable() { 5 | actual abstract suspend fun produce(): I 6 | 7 | actual override suspend fun subscribe(emit: Emitter) { 8 | emit(produce()) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /reagent/js/src/main/kotlin/reagent/source/TaskFromPromise.kt: -------------------------------------------------------------------------------- 1 | package reagent.source 2 | 3 | import reagent.Task 4 | import kotlin.coroutines.experimental.suspendCoroutine 5 | import kotlin.js.Promise 6 | 7 | internal class TaskFromPromise(private val promise: Promise): Task() { 8 | override suspend fun produce() = suspendCoroutine { continuation -> 9 | promise.then(continuation::resume, continuation::resumeWithException) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /reagent/js/src/main/kotlin/reagent/source/functionsJs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.source 17 | 18 | import kotlin.js.Promise 19 | import kotlinx.coroutines.experimental.launch 20 | import reagent.Observable 21 | import reagent.Task 22 | 23 | actual fun interval(periodMillis: Int): Observable = ObservableIntervalInt(periodMillis) 24 | actual fun timer(delayMillis: Int): Task = TaskTimerInt(delayMillis) 25 | 26 | fun Promise.toTask(): Task = TaskFromPromise(this) 27 | 28 | fun Task.toPromise(): Promise = Promise { resolve, reject -> 29 | launch { 30 | val result = try { 31 | produce() 32 | } catch (t: Throwable) { 33 | reject(t) 34 | return@launch 35 | } 36 | resolve(result) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /reagent/js/src/test/kotlin/reagent/runTest.kt: -------------------------------------------------------------------------------- 1 | package reagent 2 | 3 | import kotlinx.coroutines.experimental.CoroutineScope 4 | import kotlinx.coroutines.experimental.promise 5 | 6 | actual fun runTest(block: suspend CoroutineScope.() -> Unit) = promise { block() }.asDynamic() 7 | -------------------------------------------------------------------------------- /reagent/js/src/test/kotlin/reagent/source/TaskPlatformSourceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reagent.source 17 | 18 | import kotlinx.coroutines.experimental.await 19 | import reagent.runTest 20 | import reagent.tester.testTask 21 | import kotlin.js.Promise 22 | import kotlin.test.Test 23 | import kotlin.test.assertEquals 24 | import kotlin.test.assertSame 25 | import kotlin.test.fail 26 | 27 | class TaskPlatformSourceTest { 28 | @Test fun toTaskResolve() = runTest { 29 | Promise.resolve("Hello") 30 | .toTask() 31 | .testTask { 32 | item("Hello") 33 | } 34 | } 35 | 36 | @Test fun toTaskReject() = runTest { 37 | val exception = RuntimeException("Hello") 38 | Promise.reject(exception) 39 | .toTask() 40 | .testTask { 41 | error(exception) 42 | } 43 | } 44 | 45 | @Test fun itemToPromise() = runTest { 46 | val value = observableOf("Hello") 47 | .toPromise() 48 | .await() 49 | assertEquals("Hello", value) 50 | } 51 | 52 | @Test fun errorToPromise() = runTest { 53 | val exception = RuntimeException("Hello") 54 | val promise = exception.toTask() 55 | .toPromise() 56 | try { 57 | promise.await() 58 | fail() 59 | } catch (t: Throwable) { 60 | assertSame(exception, t) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reagent-root' 2 | 3 | include ':reagent:common' 4 | include ':reagent:jdk' 5 | include ':reagent:js' 6 | 7 | include ':reagent-rxjava2' 8 | include ':reagent-rxjs6' 9 | --------------------------------------------------------------------------------