Skip to content

Commit

Permalink
New: Var.distinct et al. Fixes #131
Browse files Browse the repository at this point in the history
  • Loading branch information
raquo committed Nov 16, 2024
1 parent a996fec commit ec56260
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
41 changes: 41 additions & 0 deletions src/main/scala/com/raquo/airstream/state/Var.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,47 @@ trait Var[A] extends SignalSource[A] with Sink[A] with Named {
)
}

/** Distinct events (but keep all errors) by == (equals) comparison */
def distinct: Var[A] = distinctByFn(_ == _)

/** Distinct events (but keep all errors) by matching key
* Note: `key(event)` might be evaluated more than once for each event
*/
def distinctBy(key: A => Any): Var[A] = distinctByFn(key(_) == key(_))

/** Distinct events (but keep all errors) by reference equality (eq) */
def distinctByRef(implicit ev: A <:< AnyRef): Var[A] = distinctByFn(ev(_) eq ev(_))

/** Distinct events (but keep all errors) using a comparison function */
def distinctByFn(isSame: (A, A) => Boolean): Var[A] = distinctTry {
case (Success(prev), Success(next)) => isSame(prev, next)
case _ => false
}

/** Distinct errors only (but keep all events) using a comparison function */
def distinctErrors(isSame: (Throwable, Throwable) => Boolean): Var[A] = distinctTry {
case (Failure(prevErr), Failure(nextErr)) => isSame(prevErr, nextErr)
case _ => false
}

/** Distinct all values (both events and errors) using a comparison function */
def distinctTry(isSame: (Try[A], Try[A]) => Boolean): Var[A] = {
val distinctSignal = new LazyStrictSignal[A, A](
signal.distinctTry(isSame), identity, displayName, displayNameSuffix = ".distinct*.signal"
)
new LazyDerivedVar2[A, A](
parent = this,
signal = distinctSignal,
updateParent = (currValue, nextValue) =>
if (isSame(currValue, nextValue)) {
None
} else {
Some(nextValue)
},
displayNameSuffix = ".distinct*"
)
}

def setTry(tryValue: Try[A]): Unit = writer.onTry(tryValue)

final def set(value: A): Unit = setTry(Success(value))
Expand Down
136 changes: 136 additions & 0 deletions src/test/scala/com/raquo/airstream/state/DistinctVarSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.raquo.airstream.state

import com.raquo.airstream.UnitSpec
import com.raquo.airstream.core.AirstreamError
import com.raquo.airstream.fixtures.{Effect, TestableOwner}
import org.scalatest.BeforeAndAfter

import scala.collection.mutable

class DistinctVarSpec extends UnitSpec with BeforeAndAfter {

private val effects = mutable.Buffer[Effect[Foo]]()

private val errorEffects = mutable.Buffer[Effect[Throwable]]()

private val errorCallback = (err: Throwable) => {
errorEffects += Effect("unhandled", err)
()
}

before {
errorEffects.clear()
AirstreamError.registerUnhandledErrorCallback(errorCallback)
AirstreamError.unregisterUnhandledErrorCallback(AirstreamError.consoleErrorCallback)
}

after {
AirstreamError.registerUnhandledErrorCallback(AirstreamError.consoleErrorCallback)
AirstreamError.unregisterUnhandledErrorCallback(errorCallback)
assert(errorEffects.isEmpty) // #Note this fails the test rather inelegantly
}

case class Foo(id: Int)

it("distinct") {
val owner = new TestableOwner
val sourceVar = Var(Foo(1))

val distinctVar = sourceVar.distinct

// --

sourceVar.signal.foreach { v =>
effects += Effect("source", v)
}(owner)

distinctVar.signal.foreach { v =>
effects += Effect("distinct", v)
}(owner)

assertEquals(effects.toList, List(
Effect("source", Foo(1)),
Effect("distinct", Foo(1))
))
effects.clear()

// --

assertEquals(sourceVar.now(), Foo(1))
assertEquals(distinctVar.now(), Foo(1))

assertEquals(effects.toList, Nil)

// --

sourceVar.set(Foo(2))

assertEquals(sourceVar.now(), Foo(2))
assertEquals(distinctVar.now(), Foo(2))

assertEquals(effects.toList, List(
Effect("source", Foo(2)),
Effect("distinct", Foo(2))
))
effects.clear()

// --

sourceVar.set(Foo(2))

assertEquals(sourceVar.now(), Foo(2))
assertEquals(distinctVar.now(), Foo(2))

assertEquals(effects.toList, List(
Effect("source", Foo(2))
))
effects.clear()

// --

sourceVar.set(Foo(2))

assertEquals(sourceVar.now(), Foo(2))
assertEquals(distinctVar.now(), Foo(2))

assertEquals(effects.toList, List(
Effect("source", Foo(2)),
))
effects.clear()

// --

sourceVar.set(Foo(3))

assertEquals(sourceVar.now(), Foo(3))
assertEquals(distinctVar.now(), Foo(3))

assertEquals(effects.toList, List(
Effect("source", Foo(3)),
Effect("distinct", Foo(3))
))
effects.clear()

// --

distinctVar.set(Foo(3))

assertEquals(sourceVar.now(), Foo(3))
assertEquals(distinctVar.now(), Foo(3))

assertEquals(effects.toList, Nil)

// --

sourceVar.set(Foo(3))

assertEquals(sourceVar.now(), Foo(3))
assertEquals(distinctVar.now(), Foo(3))

assertEquals(effects.toList, List(
Effect("source", Foo(3)),
))
effects.clear()
}
}

0 comments on commit ec56260

Please sign in to comment.