├── project ├── build.properties └── plugins.sbt ├── .travis.yml ├── src ├── main │ └── scala │ │ └── rxscalajs │ │ ├── subjects │ │ ├── AsyncSubject.scala │ │ ├── BehaviorSubject.scala │ │ └── ReplaySubject.scala │ │ ├── ConnectableObservable.scala │ │ ├── facade │ │ ├── BehaviorSubjectFacade.scala │ │ ├── ReplaySubjectFacade.scala │ │ ├── AsyncSubjectFacade.scala │ │ ├── ConnectableObservableFacade.scala │ │ ├── SubjectFacade.scala │ │ └── ObservableFacade.scala │ │ ├── subscription │ │ ├── InnerSubscriber.scala │ │ ├── SubjectSubscription.scala │ │ ├── OuterSubscriber.scala │ │ ├── Subscription.scala │ │ ├── Subscriber.scala │ │ └── ObserverFacade.scala │ │ ├── scheduler │ │ └── Action.scala │ │ ├── CombineObservable.scala │ │ ├── Observer.scala │ │ ├── Subject.scala │ │ ├── dom │ │ ├── Ajax.scala │ │ └── AjaxObservableFacade.scala │ │ ├── Scheduler.scala │ │ └── Notification.scala └── test │ └── scala │ └── rxscalajs │ ├── LawTests.scala │ └── ObservableTest.scala ├── .gitattributes ├── .gitignore └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | jdk: 3 | - oraclejdk8 4 | scala: 5 | - 2.12.0 6 | - 2.11.8 7 | - 2.10.6 8 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven" 2 | 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2") 4 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.20") 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.0.0") 6 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") 7 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 8 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subjects/AsyncSubject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subjects 2 | 3 | 4 | import rxscalajs.Subject 5 | import rxscalajs.facade.AsyncSubjectFacade 6 | 7 | 8 | class AsyncSubject[T] protected(inner: AsyncSubjectFacade[T]) extends Subject[T](inner) 9 | 10 | object AsyncSubject { 11 | def apply[T](): AsyncSubject[T] = new AsyncSubject(new AsyncSubjectFacade()) 12 | } -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/ConnectableObservable.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import rxscalajs.facade.ConnectableObservableFacade 4 | import rxscalajs.subscription.Subscription 5 | 6 | class ConnectableObservable[+T] protected[rxscalajs](val inner: ConnectableObservableFacade[T]) { 7 | def connect: Subscription = inner.connect 8 | def refCount: Observable[T] = new Observable[T](inner.refCount) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subjects/BehaviorSubject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subjects 2 | 3 | import rxscalajs.Subject 4 | import rxscalajs.facade.BehaviorSubjectFacade 5 | 6 | 7 | class BehaviorSubject[T] protected(inner: BehaviorSubjectFacade[T]) extends Subject[T](inner) 8 | 9 | 10 | object BehaviorSubject { 11 | def apply[T](defaultValue: T): BehaviorSubject[T] = new BehaviorSubject(new BehaviorSubjectFacade(defaultValue)) 12 | } -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/BehaviorSubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import scala.scalajs.js 3 | import scala.scalajs.js.annotation.JSImport 4 | 5 | 6 | @js.native 7 | @JSImport("rxjs/Rx", "BehaviorSubject", globalFallback = "Rx.BehaviorSubject") 8 | class BehaviorSubjectFacade[T] protected() extends SubjectFacade[T] { 9 | def this(_value: T) = this() 10 | def getValue(): T = js.native 11 | var value: T = js.native 12 | } 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/InnerSubscriber.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | 7 | @js.native 8 | @JSImport("rxjs/Rx", "InnerSubscription", globalFallback = "Rx.InnerSubscription") 9 | class InnerSubscriber[T, R] protected() extends Subscriber[R] { 10 | def this(parent: OuterSubscriber[T, R], outerValue: T, outerIndex: Double) = this() 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/ReplaySubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs.Scheduler 3 | 4 | import scala.scalajs.js.annotation.JSImport 5 | import scala.scalajs.js 6 | 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "ReplaySubject", globalFallback = "Rx.ReplaySubject") 10 | class ReplaySubjectFacade[T] protected() extends SubjectFacade[T] { 11 | def this(bufferSize: Int = ???, windowTime: Int = ???, scheduler: Scheduler = ???) = this() 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/SubjectSubscription.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import rxscalajs.facade.SubjectFacade 4 | 5 | import scala.scalajs.js.annotation.JSImport 6 | import scala.scalajs.js 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "SubjectSubscription", globalFallback = "Rx.SubjectSubscription") 10 | class SubjectSubscription protected () extends Subscription { 11 | def this(subject: SubjectFacade[js.Any], observer: ObserverFacade[js.Any]) = this() 12 | var subject: SubjectFacade[js.Any] = js.native 13 | var observer: ObserverFacade[js.Any] = js.native 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/AsyncSubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs.subscription.{AnonymousSubscription, Subscriber} 3 | 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation.JSImport 6 | 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "AsyncSubject", globalFallback = "Rx.AsyncSubject") 10 | class AsyncSubjectFacade[T] extends SubjectFacade[T] { 11 | var value: T = js.native 12 | var hasNext: Boolean = js.native 13 | def _subscribe(subscriber: Subscriber[js.Any]): AnonymousSubscription = js.native 14 | def _next(value: T): Unit = js.native 15 | def _complete(): Unit = js.native 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/ConnectableObservableFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | 3 | import rxscalajs.subscription.Subscription 4 | 5 | import scala.scalajs.js.annotation.JSImport 6 | import scala.scalajs.js 7 | 8 | 9 | 10 | 11 | 12 | @js.native 13 | @JSImport("rxjs/Rx", "ConnectableObservable", globalFallback = "Rx.ConnectableObservable") 14 | class ConnectableObservableFacade[+T] protected() extends ObservableFacade[T] { 15 | def this(source: ObservableFacade[T], subjectFactory: js.Function0[SubjectFacade[T]]) = this() 16 | def connect(): Subscription = js.native 17 | def refCount(): ObservableFacade[T] = js.native 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/OuterSubscriber.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | 7 | @js.native 8 | @JSImport("rxjs/Rx", "OuterSubscriber", globalFallback = "Rx.OuterSubscriber") 9 | class OuterSubscriber[T, R] extends Subscriber[T] { 10 | def notifyNext(outerValue: T, innerValue: R, outerIndex: Double, innerIndex: Double, innerSub: InnerSubscriber[T, R]): Unit = js.native 11 | def notifyError(error: js.Any, innerSub: InnerSubscriber[T, R]): Unit = js.native 12 | def notifyComplete(innerSub: InnerSubscriber[T, R]): Unit = js.native 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/scheduler/Action.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.scheduler 2 | 3 | import rxscalajs.subscription.Subscription 4 | 5 | import scala.scalajs.js 6 | import js.annotation._ 7 | import js.| 8 | 9 | 10 | 11 | import rxscalajs.Scheduler 12 | 13 | @js.native 14 | trait Action[T] extends Subscription { 15 | var work: js.Function1[T, Unit] | Subscription = js.native 16 | var state: T = js.native 17 | var delay: Double = js.native 18 | def schedule(state: T = ???, delay: Double = ???): Unit = js.native 19 | def execute(): Unit = js.native 20 | var scheduler: Scheduler = js.native 21 | var error: js.Any = js.native 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/CombineObservable.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import cats._ 4 | 5 | class CombineObservable[A](val value: Observable[A]) extends AnyVal 6 | object CombineObservable { 7 | implicit def combineObservableApplicative: Applicative[CombineObservable] = new Applicative[CombineObservable] { 8 | def pure[A](x: A): CombineObservable[A] = new CombineObservable(Observable.just(x)) 9 | 10 | def ap[A, B](ff: CombineObservable[(A) => B])(fa: CombineObservable[A]) = new CombineObservable( 11 | ff.value.combineLatestWith(fa.value)((f, a) => f(a)) 12 | ) 13 | 14 | override def map[A, B](fa: CombineObservable[A])(f: A => B): CombineObservable[B] = 15 | new CombineObservable(fa.value.map(f)) 16 | 17 | override def product[A, B](fa: CombineObservable[A], fb: CombineObservable[B]): CombineObservable[(A, B)] = 18 | new CombineObservable(fa.value.combineLatest(fb.value)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subjects/ReplaySubject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subjects 2 | 3 | import rxscalajs.{Scheduler, Subject} 4 | import rxscalajs.facade.ReplaySubjectFacade 5 | 6 | import scala.concurrent.duration.FiniteDuration 7 | 8 | 9 | class ReplaySubject[T] protected(inner: ReplaySubjectFacade[T]) extends Subject[T](inner) 10 | 11 | 12 | object ReplaySubject { 13 | def apply[T](): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade()) 14 | def withSize[T](bufferSize: Int): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade(bufferSize)) 15 | def withTime[T](time: FiniteDuration, scheduler: Scheduler): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade(windowTime = time.toMillis.toInt, scheduler = scheduler)) 16 | def withTimeAndSize[T](time: FiniteDuration, size: Int, scheduler: Scheduler): ReplaySubject[T] = new ReplaySubject(new ReplaySubjectFacade(size, time.toMillis.toInt,scheduler)) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/SubjectFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs.subscription.{AnonymousSubscription, ObserverFacade} 3 | 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation._ 6 | 7 | 8 | 9 | @js.native 10 | @JSImport("rxjs/Rx", "Subject", globalFallback = "Rx.Subject") 11 | class SubjectFacade[T] protected() extends ObservableFacade[T] with AnonymousSubscription with ObserverFacade[T] { 12 | def this(destination: ObserverFacade[T] = ???, source: ObservableFacade[T] = ???) = this() 13 | 14 | override def next(value: T): Unit = js.native 15 | override def error(err: js.Any = ???): Unit = js.native 16 | override def complete(): Unit = js.native 17 | def asObservable(): ObservableFacade[T] = js.native 18 | def throwIfUnsubscribed(): js.Dynamic = js.native 19 | } 20 | 21 | @js.native 22 | @JSImport("rxjs/Rx", "Subject", globalFallback = "Rx.Subject") 23 | object SubjectFacade extends js.Object { 24 | var create: js.Function = js.native 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | .idea/ 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | # Windows 50 | # ========================= 51 | 52 | # Windows image file caches 53 | Thumbs.db 54 | ehthumbs.db 55 | 56 | # Folder config file 57 | Desktop.ini 58 | 59 | # Recycle Bin used on file shares 60 | $RECYCLE.BIN/ 61 | 62 | # Windows Installer files 63 | *.cab 64 | *.msi 65 | *.msm 66 | *.msp 67 | 68 | # Windows shortcuts 69 | *.lnk 70 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/Observer.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import scala.scalajs.js 4 | 5 | /** 6 | * Provides a mechanism for receiving push-based notifications. 7 | * 8 | * After an Observer calls an [[rxscalajs.Observable]]'s `subscribe` method, the Observable 9 | * calls the Observer's `onNext` method to provide notifications. A well-behaved Observable will 10 | * call an Observer's `onCompleted` or `onError` methods exactly once. 11 | */ 12 | trait Observer[-T] { 13 | /** 14 | * Provides the Observer with new data. 15 | * 16 | * The [[rxscalajs.Observable]] calls this closure 0 or more times. 17 | * 18 | * The [[rxscalajs.Observable]] will not call this method again after it calls either `completed` or `error`. 19 | */ 20 | def next(t: T): Unit 21 | /** 22 | * Notifies the Observer that the [[rxscalajs.Observable]] has experienced an error condition. 23 | * 24 | * If the [[rxscalajs.Observable]] calls this method, it will not thereafter call `next` or `completed`. 25 | */ 26 | def error(err: js.Any): Unit 27 | /** 28 | * Notifies the Observer that the [[rxscalajs.Observable]] has finished sending push-based notifications. 29 | * 30 | * The [[rxscalajs.Observable]] will not call this method if it calls `error`. 31 | */ 32 | def complete(): Unit 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/Subscription.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.{JSImport, JSName} 5 | import scala.scalajs.js.| 6 | 7 | @js.native 8 | trait AnonymousSubscription extends js.Object { 9 | /** 10 | * Call this method to stop receiving notifications on the Observer that was registered when 11 | * this Subscription was received. 12 | */ 13 | def unsubscribe(): Unit = js.native 14 | } 15 | 16 | 17 | @js.native 18 | @JSImport("rxjs/Rx", "Subscription", globalFallback = "Rx.Subscription") 19 | /** 20 | * Subscriptions are returned from all `Observable.subscribe` methods to allow unsubscribing. 21 | * 22 | * This interface is the equivalent of `IDisposable` in the .NET Rx implementation. 23 | */ 24 | class Subscription protected () extends AnonymousSubscription { 25 | def add(teardown: Subscription | js.Function0[Unit]): Subscription = js.native 26 | def remove(sub: Subscription): Unit = js.native 27 | def this(unsubscribe: js.Function0[Unit] = js.native) = this() 28 | 29 | def closed: Boolean = js.native 30 | 31 | @JSName("closed") 32 | def isUnsubscribed: Boolean = js.native 33 | } 34 | 35 | @js.native 36 | @JSImport("rxjs/Rx", "Subscription", globalFallback = "Rx.Subscription") 37 | object Subscription extends js.Object { 38 | var EMPTY: Subscription = js.native 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/Subscriber.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.| 5 | import scala.scalajs.js.annotation.JSImport 6 | 7 | 8 | @js.native 9 | @JSImport("rxjs/Rx", "Subscriber", globalFallback = "Rx.Subscriber") 10 | /** 11 | * An extension of the [[ObserverFacade]] trait which adds subscription handling 12 | * (unsubscribe, isUnsubscribed, and `add` methods) and backpressure handling 13 | * (onStart and request methods). 14 | * 15 | * After a [[Subscriber]] calls an [[rxscalajs.Observable]]'s `subscribe` method, the 16 | * [[rxscalajs.Observable]] calls the [[Subscriber]]'s onNext method to emit items. A well-behaved 17 | * [[rxscalajs.Observable]] will call a [[Subscriber]]'s onCompleted method exactly once or the [[Subscriber]]'s 18 | * onError method exactly once. 19 | * 20 | * 21 | */ 22 | class Subscriber[T] () extends Subscription { 23 | def this(destinationOrNext: ObserverFacade[js.Any] | js.Function1[T, Unit] = ???, error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???) = this() 24 | 25 | def next(value: T = ???): Unit = js.native 26 | def error(err: js.Any = ???): Unit = js.native 27 | } 28 | 29 | @js.native 30 | @JSImport("rxjs/Rx", "Subscriber", globalFallback = "Rx.Subscriber") 31 | object Subscriber extends js.Object { 32 | def create[T](next: js.Function1[T, Unit] = ???, error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???): Subscriber[T] = js.native 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/Subject.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import rxscalajs.facade.SubjectFacade 4 | 5 | import scala.scalajs.js 6 | 7 | /** 8 | * A Subject is an Observable and an Observer at the same time. 9 | * 10 | */ 11 | class Subject[T] protected(inner: SubjectFacade[T]) extends Observable[T](inner) with Observer[T] { 12 | 13 | def next(value: T): Unit = inner.next(value) 14 | def error(err: js.Any): Unit = inner.error(err) 15 | def complete(): Unit = inner.complete() 16 | def asObservable(): Observable[T] = this 17 | def unsubscribe(): Unit = inner.unsubscribe() 18 | 19 | 20 | } 21 | /** 22 | * Subject that, once an `Observer` has subscribed, emits all subsequently observed items to the 23 | * subscriber. 24 | *

25 | * 26 | *

27 | * 28 | * @example 29 | {{{ 30 | val subject = Subject[String]() 31 | // observer1 will receive all onNext and onCompleted events 32 | subject.subscribe(observer1) 33 | subject.onNext("one") 34 | subject.onNext("two") 35 | // observer2 will only receive "three" and onCompleted 36 | subject.subscribe(observer2) 37 | subject.onNext("three") 38 | subject.onCompleted() 39 | }}} 40 | */ 41 | object Subject { 42 | /** 43 | * Creates and returns a new `Subject`. 44 | * 45 | * @return the new `Subject` 46 | */ 47 | def apply[T](): Subject[T] = new Subject(new SubjectFacade()) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/dom/Ajax.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.dom 2 | 3 | import org.scalajs.dom.XMLHttpRequest 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.JSON 7 | 8 | 9 | final case class Request(url: String, 10 | data: String = "", 11 | timeout: Int = 0, 12 | headers: Map[String, String] = Map.empty, 13 | crossDomain: Boolean = false, 14 | responseType: String = "", 15 | method: String = "GET") 16 | 17 | final case class Response(body: String, status: Int, responseType: String, xhr: XMLHttpRequest, response: js.Dynamic) 18 | 19 | object Ajax { 20 | import scala.scalajs.js.JSConverters._ 21 | def toJsRequest(request: Request): AjaxRequest = { 22 | js.Dynamic.literal( 23 | url = request.url, 24 | body = request.data, 25 | timeout = request.timeout, 26 | headers = request.headers.toJSDictionary, 27 | crossDomain = request.crossDomain, 28 | responseType = request.responseType, 29 | method = request.method 30 | ).asInstanceOf[AjaxRequest] 31 | } 32 | 33 | def fromJsResponse(response: AjaxResponse): Response = { 34 | val body = response.responseText.getOrElse{ 35 | if (response.responseType == "json") JSON.stringify(response.response) 36 | else response.response.toString 37 | } 38 | Response( 39 | body, 40 | response.status.toInt, 41 | response.responseType, 42 | response.xhr, 43 | response.response.asInstanceOf[js.Dynamic] 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/scala/rxscalajs/LawTests.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import cats.Eq 4 | import cats.tests.CatsSuite 5 | import org.scalacheck.{Arbitrary, Gen} 6 | import cats.laws.discipline._ 7 | import cats.kernel.laws.discipline._ 8 | 9 | class LawTests extends CatsSuite { 10 | 11 | implicit def arbObservable[A: Arbitrary]: Arbitrary[Observable[A]] = 12 | Arbitrary(Gen.oneOf((for { 13 | e <- Arbitrary.arbitrary[A] 14 | e2 <- Arbitrary.arbitrary[A] 15 | } yield Observable.of(e, e2) 16 | ), (for { 17 | e <- Arbitrary.arbitrary[A] 18 | } yield Observable.of(e)))) 19 | 20 | implicit def eqObservable[A: Eq]: Eq[Observable[A]] = new Eq[Observable[A]] { 21 | def eqv(x: Observable[A], y: Observable[A]) = { 22 | var a: Seq[A] = Seq() 23 | var b: Seq[A] = Seq() 24 | x.toSeq.subscribe(xs => a = xs) 25 | y.toSeq.subscribe(ys => b = ys) 26 | 27 | a.toList === b.toList 28 | } 29 | } 30 | 31 | implicit def eqCombineObservable[A: Eq]: Eq[CombineObservable[A]] = Eq.by(_.value) 32 | implicit def arbCombineObservable[A: Arbitrary]: Arbitrary[CombineObservable[A]] = 33 | Arbitrary(arbObservable[A].arbitrary.map(a => new CombineObservable(a))) 34 | 35 | checkAll("Observable.MonadLaws", MonadTests[Observable].monad[Int, Int, String]) 36 | checkAll("Observable.MonoidKLaws", MonoidKTests[Observable].monoidK[Int]) 37 | checkAll("CombineObservable.ApplicativeLaws", ApplicativeTests[CombineObservable].apply[Int, Int, String]) 38 | checkAll("Observable.ParallelLaws", ParallelTests[Observable, CombineObservable].parallel[Int, String]) 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/Scheduler.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import rxscalajs.subscription.Subscription 4 | 5 | import scala.scalajs.js 6 | import js.annotation._ 7 | import js.| 8 | 9 | 10 | 11 | @js.native 12 | /** 13 | * Represents an object that schedules units of work. 14 | */ 15 | trait Scheduler extends js.Object { 16 | /** 17 | * @return the scheduler's notion of current absolute time in milliseconds. 18 | */ 19 | def now(): Double = js.native 20 | /** 21 | * Schedules an Action for execution. 22 | * 23 | * @param work the Action to schedule 24 | * @return a subscription to be able to unsubscribe the action (unschedule it if not executed) 25 | */ 26 | def schedule[T](work: js.Function1[T, Subscription | Unit], delay: Double = ???, state: T = ???): Subscription = js.native 27 | def flush(): Unit = js.native 28 | } 29 | 30 | @js.native 31 | @JSImport("rxjs/Rx", "Scheduler", globalFallback = "Rx.Scheduler") 32 | /** 33 | * Represents an object that schedules units of work. 34 | */ 35 | object Scheduler extends js.Object{ 36 | /** 37 | * Schedules on a queue in the current event frame (trampoline scheduler). 38 | * Use this for iteration operations. 39 | */ 40 | val queue: Scheduler = js.native 41 | /** 42 | * Schedules on the micro task queue, which uses the fastest transport mechanism available, 43 | * either Node.js'`process.nextTick()` or Web Worker MessageChannel or setTimeout or others. 44 | * Use this for asynchronous conversions. 45 | */ 46 | val asap: Scheduler = js.native 47 | /** 48 | * Schedules work with `setInterval`. Use this for time-based operations. 49 | */ 50 | val async: Scheduler = js.native 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/subscription/ObserverFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.subscription 2 | 3 | import scala.scalajs.js 4 | 5 | 6 | 7 | @js.native 8 | trait NextObserver[T] extends js.Object { 9 | /** 10 | * Provides the Observer with new data. 11 | * 12 | * The [[rxscalajs.Observable]] calls this closure 0 or more times. 13 | * 14 | * The [[rxscalajs.Observable]] will not call this method again after it calls either `onCompleted` or `onError`. 15 | */ 16 | def next(t: T): Unit = js.native 17 | } 18 | 19 | @js.native 20 | trait ErrorObserver[T] extends js.Object { 21 | /** 22 | * Notifies the Observer that the [[rxscalajs.Observable]] has experienced an error condition. 23 | * 24 | * If the [[rxscalajs.Observable]] calls this method, it will not thereafter call `onNext` or `onCompleted`. 25 | */ 26 | def error(err :js.Any): Unit = js.native 27 | } 28 | 29 | @js.native 30 | trait CompletionObserver[T] extends js.Object { 31 | /** 32 | * Notifies the Observer that the [[rxscalajs.Observable]] has finished sending push-based notifications. 33 | * 34 | * The [[rxscalajs.Observable]] will not call this method if it calls `onError`. 35 | */ 36 | def complete(): Unit = js.native 37 | } 38 | /** 39 | Provides a mechanism for receiving push-based notifications. 40 | * 41 | * After an Observer calls an [[rxscalajs.Observable]]'s `subscribe` method, the Observable 42 | * calls the Observer's `onNext` method to provide notifications. A well-behaved Observable will 43 | * call an Observer's `onCompleted` or `onError` methods exactly once. 44 | */ 45 | @js.native 46 | trait ObserverFacade[T] extends NextObserver[T] with ErrorObserver[T] with CompletionObserver[T] 47 | 48 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/Notification.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import rxscalajs.subscription.ObserverFacade 4 | 5 | import scala.scalajs.js 6 | import js.annotation._ 7 | import js.| 8 | 9 | 10 | 11 | import rxscalajs.facade.ObservableFacade 12 | 13 | @js.native 14 | @JSImport("rxjs/Rx", "Notification", globalFallback = "Rx.Notification") 15 | /** 16 | * Emitted by Observables returned by [[rxscalajs.Observable.materialize]]. 17 | */ 18 | class Notification[T] protected () extends js.Object { 19 | def this(kind: String, value: T = ???, exception: js.Any = ???) = this() 20 | var kind: String = js.native 21 | var value: T = js.native 22 | var exception: js.Any = js.native 23 | var hasValue: Boolean = js.native 24 | def observe(observer: ObserverFacade[T]): js.Dynamic = js.native 25 | def `do`(next: js.Function1[T, Unit], error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???): js.Dynamic = js.native 26 | /** 27 | * Invokes the function corresponding to the notification. 28 | * 29 | * @param onNext 30 | * The function to invoke for an [[rxscalajs.Notification]] notification. 31 | * @param onError 32 | * The function to invoke for an [[rxscalajs.Notification]] notification. 33 | * @param onCompleted 34 | * The function to invoke for an [[rxscalajs.Notification]] notification. 35 | */ 36 | def accept(onNext: ObserverFacade[T] | js.Function1[T, Unit], onError: js.Function1[js.Any, Unit] = ???, onCompleted: js.Function0[Unit] = ???): js.Dynamic = js.native 37 | def toObservable(): ObservableFacade[T] = js.native 38 | } 39 | 40 | @js.native 41 | @JSImport("rxjs/Rx", "Notification", globalFallback = "Rx.Notification") 42 | object Notification extends js.Object { 43 | def createNext[T](value: T): Notification[T] = js.native 44 | def createError[T](err: js.Any = js.native): Notification[T] = js.native 45 | def createComplete(): Notification[js.Any] = js.native 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/dom/AjaxObservableFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.dom 2 | 3 | import rxscalajs.subscription.Subscriber 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation.JSImport 7 | import js.| 8 | import org.scalajs.dom._ 9 | import rxscalajs.facade.ObservableFacade 10 | 11 | @js.native 12 | trait AjaxRequest extends js.Object { 13 | var url: String = js.native 14 | var body: js.Any = js.native 15 | var user: String = js.native 16 | var async: Boolean = js.native 17 | var method: String = js.native 18 | var headers: Object = js.native 19 | var timeout: Double = js.native 20 | var password: String = js.native 21 | var hasContent: Boolean = js.native 22 | var crossDomain: Boolean = js.native 23 | var createXHR: js.Function0[XMLHttpRequest] = js.native 24 | var progressSubscriber: Subscriber[js.Any] = js.native 25 | var resultSelector: js.Function = js.native 26 | var responseType: String = js.native 27 | } 28 | 29 | @js.native 30 | trait AjaxCreationMethod extends js.Object { 31 | def apply[T](urlOrRequest: String | AjaxRequest): ObservableFacade[T] = js.native 32 | def get[T](url: String, resultSelector: js.Function1[AjaxResponse, T] = ???, headers: Object = ???): ObservableFacade[T] = js.native 33 | def post[T](url: String, body: js.Any = ???, headers: Object = ???): ObservableFacade[T] = js.native 34 | def put[T](url: String, body: js.Any = ???, headers: Object = ???): ObservableFacade[T] = js.native 35 | def delete[T](url: String, headers: Object = ???): ObservableFacade[T] = js.native 36 | def getJSON[T, R](url: String, resultSelector: js.Function1[T, R] = ???, headers: Object = ???): ObservableFacade[R] = js.native 37 | } 38 | 39 | @js.native 40 | @JSImport("rxjs/Rx", "AjaxObservable", globalFallback = "Rx.AjaxObservable") 41 | class AjaxObservableFacade[T] protected() extends ObservableFacade[T] { 42 | def this(urlOrRequest: String | AjaxRequest) = this() 43 | } 44 | 45 | @js.native 46 | @JSImport("rxjs/Rx", "AjaxObservable", globalFallback = "Rx.AjaxObservable") 47 | object AjaxObservableFacade extends js.Object { 48 | var create: AjaxCreationMethod = js.native 49 | } 50 | 51 | @js.native 52 | @JSImport("rxjs/Rx", "AjaxSubscriber", globalFallback = "Rx.AjaxSubscriber") 53 | class AjaxSubscriber[T] protected () extends Subscriber[Event] { 54 | def this(destination: Subscriber[T], request: AjaxRequest) = this() 55 | var request: AjaxRequest = js.native 56 | } 57 | 58 | @js.native 59 | @JSImport("rxjs/Rx", "AjaxResponse", globalFallback = "Rx.AjaxResponse") 60 | class AjaxResponse protected () extends js.Object { 61 | def this(originalEvent: Event, xhr: XMLHttpRequest, request: AjaxRequest) = this() 62 | var originalEvent: Event = js.native 63 | var xhr: XMLHttpRequest = js.native 64 | var request: AjaxRequest = js.native 65 | var status: Double = js.native 66 | var response: js.Any = js.native 67 | var responseText: js.UndefOr[String] = js.native 68 | var responseType: String = js.native 69 | } 70 | 71 | @js.native 72 | @JSImport("rxjs/Rx", "Error", globalFallback = "Rx.Error") 73 | class Error protected() extends js.Object 74 | 75 | 76 | @js.native 77 | @JSImport("rxjs/Rx", "AjaxError", globalFallback = "Rx.AjaxError") 78 | class AjaxError protected () extends Error { 79 | def this(message: String, xhr: XMLHttpRequest, request: AjaxRequest) = this() 80 | var xhr: XMLHttpRequest = js.native 81 | var request: AjaxRequest = js.native 82 | var status: Double = js.native 83 | } 84 | 85 | @js.native 86 | @JSImport("rxjs/Rx", "AjaxTimeoutError", globalFallback = "Rx.AjaxTimeoutError") 87 | class AjaxTimeoutError protected () extends AjaxError { 88 | def this(xhr: XMLHttpRequest, request: AjaxRequest) = this() 89 | } 90 | 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxScala.js [![Build Status](https://travis-ci.org/LukaJCB/rxscala-js.svg?branch=master)](https://travis-ci.org/LukaJCB/rxscala-js) [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-0.6.6.svg)](https://www.scala-js.org) [![Gitter](https://badges.gitter.im/rxscala-js/Lobby.svg)](https://gitter.im/rxscala-js/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Maven Central](https://img.shields.io/maven-central/v/com.github.lukajcb/rxscala-js_sjs0.6_2.12.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.lukajcb/rxscala-js_sjs0.6_2.12) 2 | This is a Scala adapter to [RxJs](http://github.com/ReactiveX/RxJs). 3 | 4 | Example usage: 5 | 6 | ```scala 7 | val o = Observable.interval(200).take(5) 8 | o.subscribe(n => println("n = " + n)) 9 | Observable.just(1, 2, 3, 4).reduce(_ + _) 10 | ``` 11 | 12 | Example usage in Browser: 13 | 14 | ```scala 15 | Observable.fromEvent(document.getElementById("btn"),"click") 16 | .mapTo(1) 17 | .scan(0)(_ + _) 18 | .subscribe(n => println(s"Clicked $n times")) 19 | ``` 20 | 21 | Getting Started 22 | ----- 23 | 24 | Add the following to your sbt build definition: 25 | 26 | 27 | libraryDependencies += "com.github.lukajcb" %%% "rxscala-js" % "0.15.0" 28 | 29 | 30 | then import the types from the package `rxscalajs`. 31 | 32 | ### Javascript Dependencies 33 | 34 | RxScala.js doesn't actually come bundled with the underlying `rx.js` file, so you'll need to either add them manually or specify them as `jsDependencies`: 35 | 36 | jsDependencies += "org.webjars.npm" % "rxjs" % "5.4.0" / "bundles/Rx.min.js" commonJSName "Rx" 37 | 38 | ## Differences from RxJS 39 | 40 | Similary to RxScala, this wrapper attempts to expose an API which is as Scala-idiomatic as possible. Some examples: 41 | 42 | ```scala 43 | // instead of concat: 44 | def ++[U >: T](that: Observable[U]): Observable[U] 45 | 46 | // curried in Scala collections, so curry fold also here: 47 | def foldLeft[R](seed: R)(accumulator: (R, T) => R): Observable[R] 48 | 49 | // called skip in RxJS, but drop in Scala 50 | def drop(n: Int): Observable[T] 51 | 52 | // like in the collection API 53 | def zipWithIndex: Observable[(T, Int)] 54 | 55 | // the implicit evidence argument ensures that switch can only be called on Observables of Observables: 56 | def switch[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U] 57 | 58 | ``` 59 | 60 | 61 | ## Documentation 62 | 63 | RxScala.js: 64 | 65 | - The API documentation can be found [here](http://lukajcb.github.io/rxscala-js/latest/api/rxscalajs/Observable.html). 66 | 67 | 68 | RxJs: 69 | 70 | - [API Documentation](http://reactivex.io/rxjs) 71 | 72 | 73 | If you're new to Rx, I suggest starting with [this interactive tutorial.](http://reactivex.io/learnrx/) 74 | 75 | ## Samples 76 | 77 | - [The basics](https://github.com/LukaJCB/RxScalaJsSamples/blob/master/src/main/scala/samples/main/Samples.scala) - How to use some of the most important operations in RxScala.js 78 | - [Spaceship Reactive](https://lukajcb.github.io/RxScalaJsSamples/) - A port of Spaceship Reactive found in Sergi Mansillas awesome book [Reactive Programming with RxJS](https://pragprog.com/book/smreactjs/reactive-programming-with-rxjs). Code can be found [here](https://github.com/LukaJCB/RxScalaJsSamples). 79 | - [RxScala.js as a state store](https://github.com/LukaJCB/RxScalaJsSamples/blob/master/src/main/scala/samples/main/StateStore.scala) - A basic example on how to write a simple state store with RxScala.js. (Find the working example [here](https://lukajcb.github.io/RxScalaJsSamples/assets/state-store.html)) 80 | 81 | 82 | 83 | 84 | ## Bugs and Feedback 85 | 86 | For bugs, questions and discussions please use the [Github Issues](https://github.com/LukaJCB/rxscala-js/issues). 87 | 88 | ## LICENSE 89 | 90 | Licensed under the Apache License, Version 2.0 (the "License"); 91 | you may not use this file except in compliance with the License. 92 | You may obtain a copy of the License at 93 | 94 | 95 | 96 | Unless required by applicable law or agreed to in writing, software 97 | distributed under the License is distributed on an "AS IS" BASIS, 98 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 99 | See the License for the specific language governing permissions and 100 | limitations under the License. 101 | 102 | -------------------------------------------------------------------------------- /src/main/scala/rxscalajs/facade/ObservableFacade.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs.facade 2 | import rxscalajs._ 3 | import rxscalajs.subscription.{ObserverFacade, Subscription} 4 | 5 | import scala.scalajs.js.annotation.{JSImport, JSName} 6 | import scala.scalajs.js 7 | import scala.scalajs.js._ 8 | import org.scalajs.dom._ 9 | import rxscalajs.dom.{AjaxRequest, AjaxResponse} 10 | 11 | @js.native 12 | trait Subscribable[+T] extends js.Object { 13 | def subscribe(onNext: js.Function1[T, Unit], error: js.Function1[js.Any, Unit] = ???, complete: js.Function0[Unit] = ???): Subscription = js.native 14 | def subscribe(observer: ObserverFacade[_ >: T]): Subscription = js.native 15 | } 16 | 17 | @js.native 18 | @JSImport("rxjs/Rx", "GroupedObservable", globalFallback = "Rx.GroupedObservable") 19 | class GroupedObservableFacade[K,T] protected() extends ObservableFacade[T] { 20 | def this(key: K, groupSubject: SubjectFacade[T], refCountSubscription: Subscription) = this() 21 | val key: K = js.native 22 | } 23 | @js.native 24 | trait TimeInterval[+T] extends js.Object { def value: T; def interval: Int } 25 | 26 | @js.native 27 | trait Timestamp[+T] extends js.Object { def value: T; def timestamp: Double } 28 | 29 | @js.native 30 | @JSImport("rxjs/Rx", "ErrorObservable", globalFallback = "Rx.ErrorObservable") 31 | class ErrorObservableFacade protected() extends ObservableFacade[js.Any] { 32 | def this(error: js.Any,scheduler: Scheduler = ???) = this() 33 | } 34 | 35 | 36 | @js.native 37 | @JSImport("rxjs/Rx", "Observable", globalFallback = "Rx.Observable") 38 | class ObservableFacade[+T] protected() extends Subscribable[T] { 39 | def this(subscribe: js.Function = js.native) = this() 40 | 41 | var source: ObservableFacade[js.Any] = js.native 42 | 43 | 44 | 45 | def audit[T2](durationSelector: js.Function1[T,Subscribable[T2]]): ObservableFacade[T] = js.native 46 | def auditTime(delay: Int, scheduler: Scheduler = ???): ObservableFacade[T] = js.native 47 | def buffer[T2](closingNotifier: ObservableFacade[T2]): ObservableFacade[js.Array[_ <: T]] = js.native 48 | def bufferCount(bufferSize: Int, startBufferEvery: Int = ???): ObservableFacade[js.Array[_ <: T]] = js.native 49 | def bufferTime(bufferTimeSpan: Int, bufferCreationInterval: Int = ???, scheduler: Scheduler = ???): ObservableFacade[js.Array[_ <: T]] = js.native 50 | def bufferToggle[T2,O](openings: Subscribable[O], closingSelector: js.Function1[O, Subscribable[T2]]): ObservableFacade[js.Array[_ <: T]] = js.native 51 | def bufferWhen[T2](closingSelector: js.Function0[ObservableFacade[T2]]): ObservableFacade[js.Array[_ <: T]] = js.native 52 | 53 | def `catch`[U](selector:js.Function1[js.Any, ObservableFacade[U]]): ObservableFacade[U] = js.native 54 | def onErrorResumeNext[U >: T](resumeFunction: js.Function1[js.Any, ObservableFacade[U]]): ObservableFacade[U] = js.native 55 | def combineAll[T2,R](project: js.Function1[js.Array[T2],R] = ???): ObservableFacade[R] = js.native 56 | 57 | 58 | def combineLatest[T2, R](v2: ObservableFacade[T2], project: js.Function2[T,T2,R] = ???): ObservableFacade[R] = js.native 59 | 60 | def concat[U >: T](that: ObservableFacade[U], scheduler: Scheduler = ???): ObservableFacade[U] = js.native 61 | 62 | def concatAll[U](): ObservableFacade[U] = js.native 63 | 64 | def concatMap[I, R](project: js.Function2[T,Int,ObservableFacade[I]], resultSelector: js.Function4[T, I, Int, Int, R]): ObservableFacade[R] = js.native 65 | def concatMap[R](project: js.Function2[T,Int,ObservableFacade[R]]): ObservableFacade[R] = js.native 66 | def concatMap[R](project: js.Function1[T,ObservableFacade[R]]): ObservableFacade[R] = js.native 67 | 68 | 69 | def concatMapTo[I, R](innerObservable: ObservableFacade[I], resultSelector: js.Function4[T, I, Int, Int, R] = ???): ObservableFacade[R] = js.native 70 | 71 | 72 | def count(predicate: js.Function3[T, Int, ObservableFacade[T],Boolean] = ???): ObservableFacade[Int] = js.native 73 | 74 | def debounce(durationSelector: js.Function1[T, Subscribable[Int]]): ObservableFacade[T] = js.native 75 | def debounceTime(dueTime: Int, scheduler: Scheduler = ???): ObservableFacade[T] = js.native 76 | 77 | 78 | def defaultIfEmpty[R](defaultValue: R): ObservableFacade[R] = js.native 79 | def delay(delay: Int | Date, scheduler: Scheduler = ???): ObservableFacade[T] = js.native 80 | def delayWhen[U,I](delayDurationSelector: js.Function1[T, ObservableFacade[U]], subscriptionDelay: ObservableFacade[I] = ???): ObservableFacade[T] = js.native 81 | def dematerialize[T2](): ObservableFacade[T2] = js.native 82 | def distinct[K,T2](keySelector: js.Function1[T, K] = ???, flushes: ObservableFacade[T2] = ???): ObservableFacade[T] = js.native 83 | def distinctUntilChanged[K](compare: js.Function2[K, K,Boolean], keySelector: js.Function1[T,K]): ObservableFacade[T] = js.native 84 | def distinctUntilChanged(compare: js.Function2[T, T,Boolean] = ???): ObservableFacade[T] = js.native 85 | def distinctUntilKeyChanged(key: String, compare: js.Function2[ T, T, Boolean] = ???): ObservableFacade[T] = js.native 86 | 87 | def every[T2](predicate: js.Function2[T, Int,Boolean], thisArg: T2 = ???): ObservableFacade[Boolean] = js.native 88 | def exhaust[U](): ObservableFacade[U] = js.native 89 | def exhaustMap[I, R](project: js.Function2[T, Int, ObservableFacade[R]], resultSelector: js.Function4[T, I, Int, Int, R] = ???): ObservableFacade[R] = js.native 90 | def expand[R](project: js.Function2[ T, Int, ObservableFacade[R]], concurrent: Double = ???, scheduler: Scheduler = ???): ObservableFacade[R] = js.native 91 | def filter[T2](predicate: js.Function2[ T, Int, Boolean], thisArg: T2 = ???): ObservableFacade[T] = js.native 92 | def filter[T2](predicate: js.Function1[ T, Boolean]): ObservableFacade[T] = js.native 93 | def _finally(finallySelector: js.Function0[Unit]): ObservableFacade[T] = js.native 94 | def find[T2](predicate: js.Function2[T, Int,Boolean], thisArg: T2 = ???): ObservableFacade[T] = js.native 95 | def findIndex[T2](predicate: js.Function2[T, Int, Boolean], thisArg: T2 = ???): ObservableFacade[Int] = js.native 96 | def first[ R](predicate: js.Function2[T, Int, Boolean] = ???, resultSelector: js.Function2[T,Int,R] = ???, defaultValue: R = ???): ObservableFacade[R] = js.native 97 | def groupBy[K, R,T2](keySelector: js.Function1[T,K], elementSelector: js.Function1[T,R]= ???, durationSelector: js.Function1[GroupedObservableFacade[K, R],ObservableFacade[T2]] = ???): ObservableFacade[GroupedObservableFacade[K, R]] = js.native 98 | def ignoreElements(): ObservableFacade[T] = js.native 99 | def isEmpty(): ObservableFacade[Boolean] = js.native 100 | def last[R](predicate: js.Function3[T, Int, ObservableFacade[T],Boolean] = ???, resultSelector: js.Function2[T,Int,R] = ???, defaultValue: R = ???): ObservableFacade[R] = js.native 101 | def let[ R](func: js.Function1[ObservableFacade[T],ObservableFacade[R]]): ObservableFacade[R] = js.native 102 | 103 | @JSName("map") 104 | def mapWithIndex[R](project: js.Function2[T,Int,R]): ObservableFacade[R] = js.native 105 | def map[R](project: js.Function1[T,R]): ObservableFacade[R] = js.native 106 | def mapTo[ R](value: R): ObservableFacade[R] = js.native 107 | def materialize(): ObservableFacade[Notification[_ <: T]] = js.native 108 | def merge[R >: T](that: ObservableFacade[R], concurrent: Double = ???, scheduler: Scheduler = ???): ObservableFacade[R] = js.native 109 | 110 | def mergeAll[U](concurrent: Double = ???): ObservableFacade[U] = js.native 111 | def mergeMap[R](project: js.Function2[T, Int,ObservableFacade[R]], resultSelector: js.Function4[T, R, Int, Int, R] = ???, concurrent: Double = ???): ObservableFacade[R] = js.native 112 | 113 | def mergeMap[ R](project: js.Function1[T,ObservableFacade[R]]): ObservableFacade[R] = js.native 114 | def mergeMapTo[I, R](innerObservable: ObservableFacade[I], resultSelector: js.Function4[T, I, Int, Int, R] = ???, concurrent: Double = ???): ObservableFacade[R] = js.native 115 | def mergeScan[ R](project: js.Function2[R,T,ObservableFacade[R]], seed: R, concurrent: Int = ???): ObservableFacade[R] = js.native 116 | 117 | def multicast(subject: SubjectFacade[_ >: T]): ConnectableObservableFacade[T] = js.native 118 | 119 | def observeOn(scheduler: Scheduler, delay: Int = ???): ObservableFacade[T] = js.native 120 | 121 | def pairwise(): ObservableFacade[js.Array[_ <: T]] = js.native 122 | def partition[T2](predicate: js.Function1[T,Boolean], thisArg: T2 = ???): js.Array[_ <: ObservableFacade[T]] = js.native 123 | def pluck[R](properties: String*): ObservableFacade[R] = js.native 124 | def publish(): ConnectableObservableFacade[T] = js.native 125 | 126 | def publishBehavior(value: Any): ConnectableObservableFacade[T] = js.native 127 | 128 | def publishLast(): ConnectableObservableFacade[T] = js.native 129 | def publishReplay(bufferSize: Int = ???, windowTime: Double = ???, scheduler: Scheduler = ???): ConnectableObservableFacade[T] = js.native 130 | 131 | def race(observables: js.Array[_ >: ObservableFacade[T]]): ObservableFacade[T] = js.native 132 | def reduce[R](project: js.Function2[R,T,R],seed: R = ???): ObservableFacade[R] = js.native 133 | 134 | def repeat(scheduler: Scheduler = ???, count: Int = ???): ObservableFacade[T] = js.native 135 | 136 | def retry(count: Int = ???): ObservableFacade[T] = js.native 137 | def retryWhen[T2,T3](notifier: js.Function1[ObservableFacade[T2], ObservableFacade[T3]]): ObservableFacade[T] = js.native 138 | def sample[I](notifier: ObservableFacade[I]): ObservableFacade[T] = js.native 139 | 140 | def sampleTime(delay: Int, scheduler: Scheduler = ???): ObservableFacade[T] = js.native 141 | def scan[R](accumulator: js.Function2[R, T, R],seed: R = ???): ObservableFacade[R] = js.native 142 | def share(): ObservableFacade[T] = js.native 143 | def single(predicate: js.Function3[T, Int, ObservableFacade[T],Boolean] = ???): ObservableFacade[T] = js.native 144 | 145 | 146 | def skip(total: Int): ObservableFacade[T] = js.native 147 | def skipUntil[T2](notifier: ObservableFacade[T2]): ObservableFacade[T] = js.native 148 | def skipWhile(predicate: js.Function2[T,Int,Boolean]): ObservableFacade[T] = js.native 149 | 150 | 151 | def startWith[U >: T](v1: U, scheduler: Scheduler = ???): ObservableFacade[U] = js.native 152 | def subscribeOn(scheduler: Scheduler, delay: Int = ???): ObservableFacade[T] = js.native 153 | def switch(): T = js.native 154 | def switchMap[I, R](project: js.Function2[T, Int,ObservableFacade[I]], resultSelector: js.Function4[T, I, Int, Int, R] = ???): ObservableFacade[R] = js.native 155 | def switchMap[R](project: js.Function1[T,ObservableFacade[R]]): ObservableFacade[R] = js.native 156 | def switchMapTo[ I, R](innerObservable: ObservableFacade[I], resultSelector: js.Function4[T, I, Int, Int, R] = ???): ObservableFacade[R] = js.native 157 | def take(total: Int): ObservableFacade[T] = js.native 158 | def takeLast(total: Int): ObservableFacade[T] = js.native 159 | def takeUntil[T2](notifier: ObservableFacade[T2]): ObservableFacade[T] = js.native 160 | def takeWhile(predicate: js.Function2[T,Int,Boolean]): ObservableFacade[T] = js.native 161 | def throttle(durationSelector: js.Function1[T, Subscribable[Int]]): ObservableFacade[T] = js.native 162 | def throttleTime(delay: Int, scheduler: Scheduler = ???): ObservableFacade[T] = js.native 163 | 164 | 165 | def timeInterval(scheduler: Scheduler = ???): ObservableFacade[TimeInterval[T]] = js.native 166 | def timeout(due: Int | Date, scheduler: Scheduler = ???): ObservableFacade[T] = js.native 167 | def timeoutWith[ R](due: Int | Date, withObservable: ObservableFacade[R], scheduler: Scheduler = ???): ObservableFacade[R] = js.native 168 | def timestamp(scheduler: Scheduler = ???): ObservableFacade[Timestamp[T]] = js.native 169 | def toArray(): ObservableFacade[js.Array[_ <: T]] = js.native 170 | def window[I](windowBoundaries: ObservableFacade[I]): ObservableFacade[ObservableFacade[T]] = js.native 171 | def windowCount(windowSize: Int, startWindowEvery: Int = ???): ObservableFacade[ObservableFacade[T]] = js.native 172 | def windowTime(windowTimeSpan: Int, windowCreationInterval: Int = ???, scheduler: Scheduler = ???): ObservableFacade[ObservableFacade[T]] = js.native 173 | def windowToggle[T2,O](openings: ObservableFacade[O], closingSelector: js.Function1[O, ObservableFacade[T2]]): ObservableFacade[ObservableFacade[T]] = js.native 174 | def windowWhen[T2](closingSelector: js.Function0[ObservableFacade[T2]]): ObservableFacade[ObservableFacade[T]] = js.native 175 | 176 | def withLatestFrom[T2, R](v2: ObservableFacade[T2], project: js.Function2[T, T2, R] = ???): ObservableFacade[R] = js.native 177 | 178 | def zip[T2, R](v2: ObservableFacade[T2], project: js.Function2[T,T2,R] = ???): ObservableFacade[R] = js.native 179 | 180 | def zipAll[T2,R](project: (js.Function1[js.Array[T2],R]) = ???): ObservableFacade[R] = js.native 181 | 182 | 183 | 184 | 185 | def forEach(next: js.Function1[T, Unit], PromiseCtor: Promise.type = js.native): Promise[Unit] = js.native 186 | 187 | } 188 | 189 | @js.native 190 | @JSImport("rxjs/Rx", "Observable", globalFallback = "Rx.Observable") 191 | object ObservableFacade extends js.Object { 192 | type CreatorFacade = Unit | js.Function0[Unit] 193 | 194 | def ajax(request: String | AjaxRequest): ObservableFacade[AjaxResponse] = js.native 195 | 196 | def bindCallback[T,T2](callbackFunc: js.Function, selector: js.Function, scheduler: Scheduler): js.Function1[T2, ObservableFacade[T]] = js.native 197 | 198 | def bindNodeCallback[T,T2](callbackFunc: js.Function, selector: js.Function, scheduler: Scheduler): js.Function1[T2, ObservableFacade[T]] = js.native 199 | 200 | def empty(scheduler: Scheduler = null): ObservableFacade[Nothing] = js.native 201 | 202 | def fromEvent(element: Element, eventName: String): ObservableFacade[Event] = js.native 203 | 204 | def forkJoin[T](sources: ObservableFacade[T]*): ObservableFacade[js.Array[_ <: T]] = js.native 205 | 206 | def combineLatest[T, R](sources: js.Array[ObservableFacade[T]],combineFunction: js.Function1[js.Array[_ <: T], R] = ???): ObservableFacade[R] = js.native 207 | 208 | def create[T](subscribe: js.Function1[ObserverFacade[T],CreatorFacade]): ObservableFacade[T] = js.native 209 | 210 | def concat[T, R](observables: js.Array[ObservableFacade[T]], scheduler: Scheduler = ???): ObservableFacade[R] = js.native 211 | 212 | def concatMap[T,T2, I, R](project: js.Function2[T,Int, ObservableFacade[I]], resultSelector: js.Function4[T, I, Int, Int, R] = ???): T2 = js.native 213 | 214 | def interval(period: Int = 0, scheduler: Scheduler = ???): ObservableFacade[Int] = js.native 215 | 216 | 217 | def merge[T, R](observables: js.Array[ObservableFacade[T]], scheduler: Scheduler = ???): ObservableFacade[R] = js.native 218 | 219 | def never(): ObservableFacade[Nothing] = js.native 220 | 221 | def of[T](elements: T*): ObservableFacade[T] = js.native 222 | def race[T](observables: ObservableFacade[T]*): ObservableFacade[T] = js.native 223 | 224 | def range(start: Int = 0, count: Int = 0, scheduler: Scheduler = ???): ObservableFacade[Int] = js.native 225 | def timer(initialDelay: Int = 0, period: Int = 1000, scheduler: Scheduler = ???): ObservableFacade[Int] = js.native 226 | 227 | 228 | 229 | def zip[T,R](observables: js.Array[ObservableFacade[T]], project: js.Function1[js.Array[_ <: T], R] = ??? ): ObservableFacade[R] = js.native 230 | 231 | 232 | var create: js.Function = js.native 233 | } 234 | -------------------------------------------------------------------------------- /src/test/scala/rxscalajs/ObservableTest.scala: -------------------------------------------------------------------------------- 1 | package rxscalajs 2 | 3 | import cats.{Applicative, Functor, Monad} 4 | import rxscalajs.facade.{GroupedObservableFacade, ObservableFacade} 5 | import rxscalajs.subjects.{AsyncSubject, BehaviorSubject, ReplaySubject} 6 | import rxscalajs.subscription._ 7 | import utest._ 8 | 9 | import scala.collection.mutable 10 | import scala.concurrent.{ExecutionContext, Future} 11 | import scala.concurrent.duration._ 12 | import scala.scalajs.js 13 | import scala.scalajs.js.| 14 | 15 | 16 | object ObservableTest extends TestSuite { 17 | 18 | type Creator = Unit | js.Function0[Unit] 19 | 20 | def tests = TestSuite { 21 | val unit = (n: Any) => () 22 | 23 | def toList[A](o: Observable[A]): List[A] = { 24 | val buffer = mutable.ArrayBuffer[A]() 25 | 26 | o(x => buffer.append(x)) 27 | 28 | buffer.toList 29 | } 30 | 31 | 'FacadeTests { 32 | val obs = ObservableFacade.of(1,11,21,1211,111221) 33 | val intervalObs = ObservableFacade.interval(100).take(5) 34 | val hoObs = ObservableFacade.of(obs).take(2) 35 | val notiObs = ObservableFacade.of(Notification.createNext(3),Notification.createComplete()) 36 | 'BufferCount { 37 | obs.bufferCount(2).subscribe(unit) 38 | obs.bufferCount(2, 1).subscribe(unit) 39 | } 40 | 'BufferTime { 41 | intervalObs.bufferTime(1000).subscribe(unit) 42 | intervalObs.bufferTime(1000, 1200).subscribe(unit) 43 | } 44 | 'CombineAll { 45 | val func = (n: js.Array[js.Any]) => "Hello" 46 | hoObs.combineAll(func).subscribe(unit) 47 | } 48 | 'CombineLatest { 49 | obs.combineLatest(intervalObs).subscribe(unit) 50 | obs.combineLatest[Int, Int](intervalObs, (n: Int, n2: Int) => n + n2).subscribe(unit) 51 | } 52 | 'Concat { 53 | obs.concat(intervalObs).subscribe(unit) 54 | } 55 | 'ConcatAll { 56 | hoObs.concatAll().subscribe(unit) 57 | } 58 | 'ConcatMap { 59 | obs.concatMap((n: Int, index: Int) => ObservableFacade.range(0, n)).subscribe(unit) 60 | obs.concatMap[String, Double]((n: Int, index: Int) => ObservableFacade.of("Hello", "world"), (n: Int, n2: String, index1: Int, index2: Int) => 0.4).subscribe(unit) 61 | } 62 | 'ConcatMapTo { 63 | obs.concatMapTo(ObservableFacade.of('H')).subscribe(unit) 64 | obs.concatMapTo[String, Double](ObservableFacade.of("Hello"), (n: Int, n2: String, index1: Int, index2: Int) => 0.4).subscribe(unit) 65 | } 66 | 'Count { 67 | obs.count().subscribe(unit) 68 | obs.count((i: Int, n: Int, ob: ObservableFacade[Int]) => i % 2 == 1).subscribe(unit) 69 | } 70 | 'Debounce { 71 | obs.debounce((n: Int) => ObservableFacade.interval(100).take(6)).subscribe(unit) 72 | } 73 | 'DebounceTime { 74 | obs.debounceTime(500).subscribe(unit) 75 | } 76 | 'DefaultIfEmpty { 77 | ObservableFacade.of().defaultIfEmpty(5).subscribe(unit) 78 | } 79 | 'Delay { 80 | obs.delay(50).subscribe(unit) 81 | } 82 | 'DelayWhen { 83 | obs.delayWhen((n: Int) => ObservableFacade.of(34)).subscribe(unit) 84 | obs.delayWhen((n: Int) => ObservableFacade.of("asd"), ObservableFacade.of("as")).subscribe(unit) 85 | } 86 | 'Dematerialize { 87 | notiObs.dematerialize().subscribe(unit) 88 | } 89 | 90 | 'Distinct{ 91 | obs.distinct().subscribe(unit) 92 | } 93 | 'DistinctUntilChanged { 94 | obs.distinctUntilChanged().subscribe(unit) 95 | obs.distinctUntilChanged((n: Int, n2: Int) => n > n2).subscribe(unit) 96 | obs.distinctUntilChanged((n: Int, n2: Int) => n > n2, (n: Int) => n).subscribe(unit) 97 | } 98 | 'Empty { 99 | ObservableFacade.empty().subscribe(unit) 100 | ObservableFacade.empty(Scheduler.async).subscribe(unit) 101 | } 102 | 'Never { 103 | ObservableFacade.never().subscribe(unit) 104 | } 105 | 'Every { 106 | obs.every((n: Int, n2: Int) => n > n2).subscribe(unit) 107 | } 108 | 'ExhaustMap{ 109 | hoObs.exhaustMap((n: ObservableFacade[Int], index: Int) => ObservableFacade.range(0,index)).subscribe(unit) 110 | } 111 | 'Expand { 112 | intervalObs.expand((n: Int, n2: Int) => ObservableFacade.of(n)).take(1).subscribe(unit) 113 | } 114 | 'Filter { 115 | obs.filter((n: Int, n2: Int) => n % 2 == 0).subscribe(unit) 116 | } 117 | 'First { 118 | obs.first().subscribe(unit) 119 | obs.first(defaultValue = 4).subscribe(unit) 120 | obs.first(defaultValue = 4, resultSelector = (n: Int, n2: Int) => n).subscribe(unit) 121 | obs.first((n: Int, n2: Int) => true).subscribe(unit) 122 | obs.first((n: Int, n2: Int) => true, (n: Int, n2: Int) => n).subscribe(unit) 123 | obs.first((n: Int, n2: Int) => true, (n: Int, n2: Int) => n, 4).subscribe(unit) 124 | obs.first(resultSelector = (n: Int, n2: Int) => n).subscribe(unit) 125 | } 126 | 127 | 'GroupBy { 128 | obs.groupBy((n: Int) => n % 2 == 0).subscribe(unit) 129 | val func: js.Function1[GroupedObservableFacade[Int, Int], ObservableFacade[Int]] = (grouped: GroupedObservableFacade[Int, Int]) => ObservableFacade.of(-1) 130 | obs.groupBy((n: Int) => n, (n: Int) => n, func).subscribe(unit) 131 | obs.groupBy((n: Int) => n, durationSelector = func).subscribe(unit) 132 | obs.groupBy((n: Int) => n % 2 == 0, (n: Int) => n).subscribe(unit) 133 | } 134 | 'IgnoreElements { 135 | obs.ignoreElements().subscribe(unit) 136 | } 137 | 'IsEmpty{ 138 | obs.isEmpty().subscribe(unit) 139 | } 140 | 'Last { 141 | obs.last().subscribe(unit) 142 | } 143 | 'Map { 144 | obs.map((n: Int) => "n: " + n).subscribe(unit) 145 | } 146 | 'MapWithIndex { 147 | obs.mapWithIndex((n: Int, index: Int) => "n: " + n).subscribe(unit) 148 | } 149 | 'MapTo { 150 | obs.mapTo("A").subscribe(unit) 151 | } 152 | 'Materialize { 153 | obs.materialize().subscribe(unit) 154 | } 155 | 'Merge { 156 | obs.merge(intervalObs).subscribe(unit) 157 | obs.merge(intervalObs, 3).subscribe(unit) 158 | } 159 | 'MergeAll { 160 | hoObs.mergeAll(3).subscribe(unit) 161 | } 162 | 'MergeMap { 163 | obs.mergeMap((n: Int, index: Int) => ObservableFacade.of(n)).subscribe(unit) 164 | obs.mergeMap((n: Int, index: Int) => ObservableFacade.of(n), (out: Int, in: Int, index1: Int, index2: Int) => -1).subscribe(unit) 165 | } 166 | 'MergeMapTo { 167 | obs.mergeMapTo(ObservableFacade.of("34")).subscribe(unit) 168 | obs.mergeMapTo(ObservableFacade.of(34), (out: Int, in: Int, index1: Int, index2: Int) => -1).subscribe(unit) 169 | } 170 | 'Partition { 171 | obs.partition((n: Int) => n > 4)(0).subscribe(unit) 172 | } 173 | 'Publish { 174 | obs.publish().subscribe(unit) 175 | } 176 | 'PublishBehaviour { 177 | obs.publishBehavior(3).subscribe(unit) 178 | } 179 | 'PublishLast { 180 | obs.publishLast().subscribe(unit) 181 | } 182 | 'PublishReplay { 183 | obs.publishReplay(5).subscribe(unit) 184 | } 185 | 'Race { 186 | intervalObs.race(js.Array(intervalObs)).subscribe(unit) 187 | } 188 | 'Reduce { 189 | obs.reduce((n: Int, n2: Int) => n).subscribe(unit) 190 | obs.reduce((n: Int, n2: Int) => n, -20).subscribe(unit) 191 | } 192 | 'Repeat { 193 | obs.repeat().take(3).subscribe(unit) 194 | obs.repeat(count = 2).take(7).subscribe(unit) 195 | } 196 | 'Retry { 197 | obs.retry().subscribe(unit) 198 | obs.retry(4).subscribe(unit) 199 | } 200 | 'RetryWhen { 201 | val func = (o: ObservableFacade[Any]) => o 202 | obs.retryWhen(func).subscribe(unit) 203 | } 204 | 'Sample { 205 | obs.sample(intervalObs).subscribe(unit) 206 | } 207 | 'SampleTime { 208 | intervalObs.sampleTime(500).subscribe(unit) 209 | } 210 | 'Scan { 211 | obs.scan((n: Int, n2: Int) => n + n2).subscribe(unit) 212 | obs.scan((n: Int, n2: Int) => n + n2, -20).subscribe(unit) 213 | } 214 | 'Share { 215 | obs.share().subscribe(unit) 216 | } 217 | 'Single { 218 | obs.single((n: Int, n2: Int, o: ObservableFacade[Int]) => n == 1).subscribe(unit) 219 | } 220 | 'Skip { 221 | obs.skip(2).subscribe(unit) 222 | } 223 | 'SkipUntil { 224 | obs.skipUntil(intervalObs).subscribe(unit) 225 | } 226 | 'SkipWhile { 227 | obs.skipWhile((n: Int, n2: Int) => n < 5).subscribe(unit) 228 | } 229 | 'StartWith { 230 | obs.startWith(0).subscribe(unit) 231 | } 232 | 'Switch { 233 | hoObs.switch().subscribe(unit) 234 | } 235 | 'SwitchMap { 236 | val func: js.Function2[ObservableFacade[Int], Int, ObservableFacade[Int]] = (n: ObservableFacade[Int], n2: Int) => ObservableFacade.of(n2) 237 | hoObs.switchMap(func).subscribe(unit) 238 | } 239 | 'SwitchMapTo { 240 | obs.switchMapTo(intervalObs).subscribe(unit) 241 | } 242 | 'TakeLast { 243 | obs.takeLast(2).subscribe(unit) 244 | } 245 | 'TakeUntil { 246 | obs.takeUntil(intervalObs).subscribe(unit) 247 | } 248 | 'TakeWhile { 249 | obs.takeWhile((n: Int, n2: Int) => n > 1).subscribe(unit) 250 | } 251 | 'Throttle { 252 | intervalObs.throttle((ev: Int) => ObservableFacade.interval(1000).take(2)).subscribe(unit) 253 | } 254 | 'ThrottleTime { 255 | intervalObs.throttleTime(200).subscribe(unit) 256 | } 257 | 'Window { 258 | obs.window(intervalObs).subscribe(unit) 259 | } 260 | 'WindowCount { 261 | obs.windowCount(3).subscribe(unit) 262 | } 263 | 'WindowTime { 264 | obs.windowTime(1200).subscribe(unit) 265 | } 266 | 'WindowToggle { 267 | obs.window(intervalObs).subscribe(unit) 268 | } 269 | 'WithLatestFrom { 270 | obs.withLatestFrom(intervalObs).subscribe(unit) 271 | } 272 | 'Zip { 273 | obs.zip(intervalObs).subscribe(unit) 274 | } 275 | 'Create { 276 | val func: js.Function1[ObserverFacade[Double],ObservableFacade.CreatorFacade] = (subscriber: ObserverFacade[Double]) => { 277 | subscriber.next(Math.random()) 278 | subscriber.next(Math.random()) 279 | subscriber.next(Math.random()) 280 | subscriber.complete(): ObservableFacade.CreatorFacade 281 | } 282 | val result = ObservableFacade.create(func) 283 | result.subscribe(unit) 284 | } 285 | 'CreateDisposeFunction { 286 | var x = false 287 | val func: js.Function1[ObserverFacade[Double],ObservableFacade.CreatorFacade] = (subscriber: ObserverFacade[Double]) => { 288 | subscriber.next(Math.random()) 289 | subscriber.next(Math.random()) 290 | subscriber.next(Math.random()) 291 | subscriber.complete() 292 | val disposer = () => { 293 | x = true 294 | } 295 | (disposer: js.Function0[Unit]): ObservableFacade.CreatorFacade 296 | } 297 | val result = ObservableFacade.create(func) 298 | result.subscribe(unit) 299 | assert(x) 300 | } 301 | } 302 | 'FactoryTests { 303 | 'CombineLatest { 304 | val xs = Vector(1,2,3,4).map(x => Observable.just(x)) 305 | val obs = Observable.combineLatest(xs) 306 | obs(unit) 307 | } 308 | 'CombineLatestWith { 309 | val xs = List(1,2,3,4).map(x => Observable.just(x)) 310 | val obs = Observable.combineLatestWith(xs)(_.sum) 311 | obs(unit) 312 | } 313 | } 314 | 'WrapperTests{ 315 | 316 | val obs = Observable.just(1,11,21,1211,111221) 317 | val intervalObs = Observable.interval(100.millis).take(5) 318 | val hoObs = Observable.just(obs).take(2) 319 | val notiObs = Observable.just(Notification.createNext(3),Notification.createComplete()) 320 | 'BufferCount { 321 | obs.bufferCount(2).subscribe(unit) 322 | obs.bufferCount(2, 1).subscribe(unit) 323 | } 324 | 'BufferTime { 325 | intervalObs.bufferTime(1000.millis).subscribe(unit) 326 | intervalObs.bufferTime(1000.millis, 1200.millis).subscribe(unit) 327 | intervalObs.bufferTime(1000.millis, 1200.millis,Scheduler.queue).subscribe(unit) 328 | intervalObs.bufferTime(1000.millis, 1200.millis,Scheduler.async).subscribe(unit) 329 | intervalObs.bufferTime(1000.millis, 1200.millis,Scheduler.asap).subscribe(unit) 330 | } 331 | 'CombineAll { 332 | val combined = hoObs.combineAll.take(3) 333 | combined.subscribe(unit) 334 | } 335 | 'CombineLatest { 336 | obs.combineLatest(intervalObs).subscribe(unit) 337 | obs.combineLatestWith(intervalObs)((n, n2) => n + n2).subscribe(unit) 338 | } 339 | 'CombineLatestMultiple { 340 | obs.combineLatest(intervalObs, hoObs).subscribe(unit) 341 | obs.combineLatestWith(intervalObs, hoObs)((n, n2, n3) => n + n2).subscribe(unit) 342 | obs.combineLatest(intervalObs, hoObs, notiObs).subscribe(unit) 343 | obs.combineLatestWith(intervalObs, hoObs, notiObs)((n, n2, n3, n4) => n + n2).subscribe(unit) 344 | } 345 | 'Concat { 346 | obs.concat(intervalObs).subscribe(unit) 347 | } 348 | 'ConcatAll { 349 | hoObs.concatAll.subscribe(unit) 350 | } 351 | 'ConcatMap { 352 | obs.concatMap(n=> Observable.range(0, n)).subscribe(unit) 353 | obs.concatMap[String](n=> Observable.of("Hello", "world")).subscribe(unit) 354 | } 355 | 'ConcatMapTo { 356 | obs.concatMapTo(Observable.just('H')).subscribe(unit) 357 | obs.concatMapTo(Observable.of("Hello")).subscribe(unit) 358 | } 359 | 'Count { 360 | obs.count.subscribe(unit) 361 | obs.count((i: Int, n: Int, ob: Observable[Int]) => i % 2 == 1).subscribe(unit) 362 | } 363 | 'Collect { 364 | obs.collect { case i if i > 50 => i * i }.subscribe(unit) 365 | } 366 | 'Debounce { 367 | obs.debounce((n: Int) => Observable.interval(100.millis).take(6)).subscribe(unit) 368 | } 369 | 'DebounceTime { 370 | obs.debounceTime(500.millis).subscribe(unit) 371 | } 372 | 'DefaultIfEmpty { 373 | ObservableFacade.of().defaultIfEmpty(5).subscribe(unit) 374 | } 375 | 'Delay { 376 | obs.delay(50.millis).subscribe(unit) 377 | } 378 | 'DelayWhen { 379 | obs.delayWhen((n: Int) => Observable.of(34)).subscribe(unit) 380 | obs.delayWhen((n: Int) => Observable.of("asd"), Observable.of("as")).subscribe(unit) 381 | } 382 | 'Dematerialize { 383 | notiObs.dematerialize.subscribe(unit) 384 | } 385 | 386 | 'Distinct{ 387 | obs.distinct.subscribe(unit) 388 | } 389 | 'DistinctUntilChanged { 390 | obs.distinctUntilChanged.subscribe(unit) 391 | obs.distinctUntilChanged((n: Int, n2: Int) => n > n2).subscribe(unit) 392 | obs.distinctUntilChanged((n: Int, n2: Int) => n > n2, (n: Int) => n).subscribe(unit) 393 | } 394 | 395 | 'Every { 396 | obs.every((n, n2) => n > n2).subscribe(unit) 397 | } 398 | 'Exhaust{ 399 | hoObs.exhaust.subscribe(unit) 400 | } 401 | 'ExhaustMap{ 402 | hoObs.exhaustMap((n: Observable[Int], index: Int) => Observable.range(0,index)).subscribe(unit) 403 | } 404 | 'Expand { 405 | intervalObs.expand((n: Int, n2: Int) => Observable.just(n)).take(1).subscribe(unit) 406 | } 407 | 'Filter { 408 | obs.filter((n: Int, n2: Int) => n % 2 == 0).subscribe(unit) 409 | } 410 | 'First { 411 | obs.first.subscribe(unit) 412 | } 413 | 'FirstOrElse { 414 | obs.firstOrElse(2342).subscribe(unit) 415 | } 416 | 417 | 'GroupBy { 418 | obs.groupBy(n => n % 2 == 0).subscribe(unit) 419 | obs.groupBy(n => n % 2 == 0, n => n+1).subscribe(unit) 420 | } 421 | 'IgnoreElements { 422 | obs.ignoreElements.subscribe(unit) 423 | } 424 | 'IsEmpty{ 425 | obs.isEmpty.subscribe(unit) 426 | } 427 | 'Last { 428 | obs.last.subscribe(unit) 429 | } 430 | 'Map { 431 | obs.map(_.toString).subscribe(unit) 432 | } 433 | 'MapWithIndex { 434 | obs.mapWithIndex((n: Int, index: Int) => "n: " + n).subscribe(unit) 435 | } 436 | 'MapTo { 437 | obs.mapTo("A").subscribe(unit) 438 | } 439 | 'Materialize { 440 | obs.materialize.subscribe(unit) 441 | } 442 | 'Merge { 443 | obs.merge(intervalObs).subscribe(unit) 444 | } 445 | 'MergeAll { 446 | hoObs.mergeAll(3).subscribe(unit) 447 | } 448 | 'MergeMap { 449 | obs.mergeMap((n: Int) => Observable.of(n)).subscribe(unit) 450 | obs.mergeMap(n => Observable.just(n)).subscribe(unit) 451 | } 452 | 'MergeMapTo { 453 | obs.mergeMapTo(Observable.of("34")).subscribe(unit) 454 | obs.mergeMapTo(Observable.of(34), (out: Int, in: Int, index1: Int, index2: Int) => -1).subscribe(unit) 455 | } 456 | 'Partition { 457 | obs.partition((n: Int) => n > 4)._1.subscribe(unit) 458 | } 459 | 'Publish { 460 | obs.publish.refCount.subscribe(unit) 461 | } 462 | 'PublishLast { 463 | obs.publishLast.refCount.subscribe(unit) 464 | } 465 | 'PublishReplay { 466 | obs.publishReplay(5).refCount.subscribe(unit) 467 | } 468 | 'Race { 469 | intervalObs.race(intervalObs).subscribe(unit) 470 | } 471 | 'Reduce { 472 | obs.reduce(_ + _).subscribe(unit) 473 | } 474 | 'Fold { 475 | obs.foldLeft( -20)((n: Int, n2: Int) => n).subscribe(unit) 476 | } 477 | 'Repeat { 478 | intervalObs.repeat().take(5).subscribe(unit) 479 | intervalObs.repeat(2).take(5).subscribe(unit) 480 | } 481 | 'Retry { 482 | obs.retry().subscribe(unit) 483 | obs.retry(4).subscribe(unit) 484 | } 485 | 'RetryWhen { 486 | val func = (o: Observable[Any]) => o 487 | obs.retryWhen(func).subscribe(unit) 488 | } 489 | 'Sample { 490 | obs.sample(intervalObs).subscribe(unit) 491 | } 492 | 'SampleTime { 493 | intervalObs.sampleTime(500.millis).subscribe(unit) 494 | } 495 | 'Scan { 496 | obs.scan((n: Int, n2: Int) => n + n2).subscribe(unit) 497 | obs.scan(-20)((n: Int, n2: Int) => n + n2).subscribe(unit) 498 | } 499 | 'ScanMap { 500 | import cats.implicits._ 501 | 502 | val o = Observable.just(1, 2, 3, 4) 503 | val scanned = o.scanMap(identity) 504 | 505 | val list = toList(o.scan(0)(_ + _)) 506 | 507 | assert(toList(scanned) == list) 508 | } 509 | 'ScanM { 510 | import cats.implicits._ 511 | 512 | val o = Observable.just(1, 2, 3, 4) 513 | val scanned = o.scanM(0)((acc, cur) => Option(acc + cur)) 514 | 515 | val list = toList(o.scan(0)(_ + _).map(Option.apply)) 516 | 517 | assert(toList(scanned) == list) 518 | } 519 | 'Share { 520 | obs.share.subscribe(unit) 521 | } 522 | 'Single { 523 | obs.single((n: Int, n2: Int, o: Observable[Int]) => n == 1).subscribe(unit) 524 | } 525 | 'Skip { 526 | obs.skip(2).subscribe(unit) 527 | } 528 | 'SkipUntil { 529 | obs.skipUntil(intervalObs).subscribe(unit) 530 | } 531 | 'SkipWhile { 532 | obs.skipWhile((n: Int, n2: Int) => n < 5).subscribe(unit) 533 | } 534 | 'StartWith { 535 | obs.startWith(0).subscribe(unit) 536 | } 537 | 'Switch { 538 | hoObs.switch.subscribe(unit) 539 | } 540 | 'SwitchMap { 541 | val func = (n: Observable[Int]) => Observable.of(n) 542 | hoObs.switchMap(func).subscribe(unit) 543 | } 544 | 'SwitchMapTo { 545 | obs.switchMapTo(intervalObs).subscribe(unit) 546 | } 547 | 'TakeLast { 548 | obs.takeLast(2).subscribe(unit) 549 | } 550 | 'TakeUntil { 551 | obs.takeUntil(intervalObs).subscribe(unit) 552 | } 553 | 'TakeWhile { 554 | obs.takeWhile((n: Int, n2: Int) => n > 1).subscribe(unit) 555 | } 556 | 'Throttle { 557 | intervalObs.throttle((ev: Int) => Observable.interval(1000.millis)).subscribe(unit) 558 | } 559 | 'ThrottleTime { 560 | intervalObs.throttleTime(200.millis).subscribe(unit) 561 | } 562 | 'Timestamp { 563 | intervalObs.timestamp.map(tmp => (tmp.value, tmp.timestamp)).subscribe(unit) 564 | } 565 | 'Window { 566 | obs.window(intervalObs).subscribe(unit) 567 | } 568 | 'WindowCount { 569 | obs.windowCount(3).subscribe(unit) 570 | } 571 | 'WindowTime { 572 | obs.windowTime(1200.millis).subscribe(unit) 573 | } 574 | 'WindowToggle { 575 | obs.window(intervalObs).subscribe(unit) 576 | } 577 | 'WithLatestFrom { 578 | obs.withLatestFrom(intervalObs).subscribe(unit) 579 | obs.withLatestFromWith(intervalObs)(_ + _).subscribe(unit) 580 | } 581 | 'Zip { 582 | val first = Observable.of(10, 11, 12) 583 | val second = Observable.of(10, 11, 12) 584 | first zip second subscribe(unit) 585 | obs zip intervalObs subscribe(unit) 586 | 587 | first.zipWith(second)(_ + _).subscribe(unit) 588 | } 589 | 'ZipWithIndex { 590 | obs.zipWithIndex.subscribe(unit) 591 | } 592 | 'CatchError { 593 | val errorObs = Observable.create[Int](observer => { 594 | observer.next(0) 595 | observer.error("Error!") 596 | }) 597 | errorObs.catchError(s => Observable.of(s.toString)).subscribe(unit) 598 | } 599 | 'OnErrorResumeNext { 600 | val errorObs = Observable.create[Int](observer => { 601 | observer.next(0) 602 | observer.error("Error!") 603 | }) 604 | errorObs.onErrorResumeNext(s => Observable.of(s.toString)).subscribe(unit) 605 | } 606 | 'OnErrorReturn { 607 | val errorObs = Observable.create[Int](observer => { 608 | observer.next(0) 609 | observer.error("Error!") 610 | }) 611 | errorObs.onErrorReturn(_.toString).subscribe(unit) 612 | } 613 | 614 | 'Ajax { 615 | Observable.ajax("https://api.github.com/orgs/reactivex") 616 | .map(_.response.public_repos) 617 | .subscribe(unit) 618 | } 619 | 'Create { 620 | val o = Observable.create[String](observer => { 621 | observer.next("Str") 622 | observer.next("Hello") 623 | observer.complete() 624 | }) 625 | o.subscribe(unit) 626 | } 627 | 'CreateDisposeFunction { 628 | var x = false 629 | val o = Observable.create[String](observer => { 630 | observer.next("Str") 631 | observer.next("Hello") 632 | observer.complete() 633 | val disposer = () => { 634 | x = true 635 | } 636 | disposer 637 | }) 638 | o.subscribe(unit) 639 | assert(x) 640 | } 641 | 'Empty { 642 | Observable.empty.subscribe(unit) 643 | } 644 | 'Never { 645 | Observable.never.subscribe(unit) 646 | } 647 | 'FromIterable { 648 | val iterable = List(1,24,3,35,5,34) 649 | val o = Observable.just(iterable: _*) 650 | o.subscribe(unit) 651 | } 652 | 'FromFuture { 653 | import ExecutionContext.Implicits.global 654 | val future = Future { 655 | "Hello" * 5 * 5 + 123 * 23 656 | } 657 | val o = Observable.from(future) 658 | o.subscribe(unit) 659 | } 660 | 'ForkJoin { 661 | val o = Observable.interval(300.millis).take(2) 662 | val o2 = Observable.interval(400.millis).take(1) 663 | 664 | Observable.forkJoin(o,o2).subscribe(unit) 665 | } 666 | 'StartWithMany { 667 | val o = Observable.never.startWithMany(-3,-2,-1) 668 | o.bufferCount(3).subscribe(list => { 669 | assert(list == List(-3,-2,-1)) 670 | assert(list != List(-3,-1,-2)) 671 | }) 672 | } 673 | 674 | 'ForComprehensions { 675 | val forObs = 676 | for { 677 | o <- hoObs 678 | (i1,i2) <- o zip obs 679 | } yield i1 > i2 680 | forObs.subscribe(unit) 681 | } 682 | 'SubscriptionTests { 683 | val sub = intervalObs.subscribe(unit) 684 | assert(!sub.isUnsubscribed) 685 | sub.unsubscribe() 686 | assert(sub.isUnsubscribed) 687 | } 688 | 'SubscriptionObjectTests { 689 | val empty = Subscription.EMPTY 690 | assert(empty.isUnsubscribed) 691 | } 692 | 'ObserverTests { 693 | val o = new Observer[Int] { 694 | override def next(n: Int) = unit(n) 695 | override def error(a: js.Any) = unit(a) 696 | override def complete() = unit() 697 | } 698 | 699 | intervalObs.subscribe(o) 700 | } 701 | 'SubjectTest { 702 | val s = Subject[Int]() 703 | s.scan(0)(_ + _).startWith(0) 704 | s.next(10) 705 | s.subscribe(unit) 706 | s.next(10) 707 | 708 | intervalObs.subscribe(s) 709 | 710 | } 711 | 'BehaviorSubjectTest { 712 | val s = BehaviorSubject[Int](12) 713 | s.scan(0)(_ + _).startWith(0) 714 | s.next(10) 715 | s.subscribe(unit) 716 | s.next(10) 717 | 718 | intervalObs.subscribe(s) 719 | } 720 | 'AsyncSubjectTest { 721 | val s = AsyncSubject[Int]() 722 | s.scan(0)(_ + _).startWith(0) 723 | s.next(10) 724 | s.subscribe(unit) 725 | s.next(10) 726 | 727 | intervalObs.subscribe(s) 728 | } 729 | 'ReplaySubjectTest { 730 | val s = ReplaySubject.withSize[Int](5) 731 | s.scan(0)(_ + _).startWith(0) 732 | s.next(10) 733 | s.subscribe(unit) 734 | s.next(10) 735 | 736 | intervalObs.subscribe(s) 737 | } 738 | } 739 | 740 | 'InstanceTests { 741 | import cats.implicits._ 742 | 743 | 744 | 745 | 'Functor { 746 | val o = Observable.of(1,2,3) 747 | val mapped = Functor[Observable].map(o)(_ + 1) 748 | 749 | assert(toList(mapped) == List(2,3,4)) 750 | 751 | } 752 | 753 | 'Applicative { 754 | val o = Observable.of(1,2) 755 | val t = Applicative[Observable].replicateA(2, o) 756 | 757 | assert(toList(t) == List(List(1,1), List(1,2), List(2,1), List(2,2))) 758 | 759 | } 760 | 761 | 'Monad { 762 | val o = Observable.just(1, 2) 763 | 764 | val t = Monad[Observable].flatMap(o)(n => Observable.of(0 to n: _*)) 765 | 766 | assert(toList(t) == List(0, 1, 0, 1, 2)) 767 | 768 | val o2 = Observable.of(Observable.of(1,2), Observable.of(3)) 769 | val flat = Monad[Observable].flatten(o2) 770 | 771 | assert(toList(flat) == List(1,2,3)) 772 | } 773 | } 774 | 775 | 776 | } 777 | 778 | } 779 | --------------------------------------------------------------------------------