From 6491f0a808213886ffa5d8e4c5e43beedf67ae87 Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Thu, 12 Sep 2024 16:46:32 -0400 Subject: [PATCH] Increase code coverage (pt3). --- .../formula/internal/ListenerImpl.kt | 7 +- .../instacart/formula/internal/Listeners.kt | 19 +++-- .../formula/internal/SnapshotImpl.kt | 2 +- .../instacart/formula/FormulaRuntimeTest.kt | 73 +++++++++++++++++++ 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt b/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt index 8bd545d4..b6509c6b 100644 --- a/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt @@ -8,14 +8,15 @@ import com.instacart.formula.plugin.Dispatcher * Note: this class is not a data class because equality is based on instance and not [key]. */ @PublishedApi -internal class ListenerImpl(private val key: Any) : Listener { +internal class ListenerImpl( + private val key: Any, + private var transition: Transition +) : Listener { @Volatile private var manager: FormulaManagerImpl? = null @Volatile private var snapshotImpl: SnapshotImpl? = null @Volatile private var executionType: Transition.ExecutionType? = null - private lateinit var transition: Transition - override fun invoke(event: EventT) { // TODO: log if null listener (it might be due to formula removal or due to callback removal) val manager = manager ?: return diff --git a/formula/src/main/java/com/instacart/formula/internal/Listeners.kt b/formula/src/main/java/com/instacart/formula/internal/Listeners.kt index 679bbf9b..518ffb55 100644 --- a/formula/src/main/java/com/instacart/formula/internal/Listeners.kt +++ b/formula/src/main/java/com/instacart/formula/internal/Listeners.kt @@ -1,18 +1,24 @@ package com.instacart.formula.internal +import com.instacart.formula.Transition + internal class Listeners { private var listeners: SingleRequestMap>? = null private var indexes: MutableMap? = null - fun initOrFindListener(key: Any, useIndex: Boolean): ListenerImpl { - val currentHolder = listenerHolder(key) + fun initOrFindListener( + key: Any, + useIndex: Boolean, + transition: Transition + ): ListenerImpl { + val currentHolder = listenerHolder(key, transition) return if (!currentHolder.requested) { currentHolder.requested = true currentHolder.value as ListenerImpl } else if (useIndex) { val index = nextIndex(key) val indexedKey = IndexedKey(key, index) - initOrFindListener(indexedKey, useIndex) + initOrFindListener(indexedKey, useIndex, transition) } else { throw IllegalStateException("Listener $key is already defined. Unexpected issue.") } @@ -60,13 +66,16 @@ internal class Listeners { return index } - private fun listenerHolder(key: Any): SingleRequestHolder> { + private fun listenerHolder( + key: Any, + transition: Transition + ): SingleRequestHolder> { val listeners = listeners ?: run { val initialized: SingleRequestMap> = mutableMapOf() this.listeners = initialized initialized } - return listeners.findOrInit(key) { ListenerImpl(key) } + return listeners.findOrInit(key) { ListenerImpl(key, transition) } } } diff --git a/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt b/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt index 4db49662..794e149b 100644 --- a/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt @@ -64,7 +64,7 @@ internal class SnapshotImpl( transition: Transition ): Listener { ensureNotRunning() - val listener = listeners.initOrFindListener(key, useIndex) + val listener = listeners.initOrFindListener(key, useIndex, transition) listener.setDependencies(delegate, this, executionType, transition) return listener } diff --git a/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt b/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt index f8a8d07c..b5f1ebc8 100644 --- a/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt +++ b/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt @@ -663,6 +663,79 @@ class FormulaRuntimeTest(val runtime: TestableRuntime, val name: String) { } } + @Test + fun `dispatched event is ignored if listener was disabled before event is processed`() { + data class State( + val listenerEnabled: Boolean = true, + val value: Int = 0, + ) + + data class Output( + val value: Int, + val increment: () -> Unit, + ) + + val disableListenerRelay = runtime.newRelay() + val formula = object : Formula() { + override fun initialState(input: Unit): State = State() + + override fun Snapshot.evaluate(): Evaluation { + val increment = if (state.listenerEnabled) { + context.callback { + transition(state.copy(value = state.value.inc())) + } + } else { + context.callback { none() } + } + return Evaluation( + output = Output( + value = state.value, + increment = increment + ), + actions = context.actions { + disableListenerRelay.action().onEvent { + val listener = state.copy(listenerEnabled = false) + transition(listener) + } + } + ) + } + } + + val dispatcher = object : Dispatcher { + var dispatches = mutableListOf<() -> Unit>() + + override fun isDispatchNeeded(): Boolean { + return true + } + + override fun dispatch(executable: () -> Unit) { + dispatches.add(executable) + } + + fun executeAndClear() { + val local = dispatches + dispatches = mutableListOf() + local.forEach { it.invoke() } + } + } + val observer = runtime.test(formula, defaultDispatcher = dispatcher) + + // Initialize formula + observer.input(Unit) + dispatcher.executeAndClear() + + // First + val increment = observer.values().last().increment + disableListenerRelay.triggerEvent() + increment() + increment() + increment() + + dispatcher.executeAndClear() + observer.output { assertThat(value).isEqualTo(0) } + } + @Test fun `dispatching does not affect event order`() { var observer: TestFormulaObserver? = null FormulaPlugins.setPlugin(object : Plugin {