• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 The Android Open Source Project
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 package com.android.systemui.kairos
18 
19 import com.android.systemui.kairos.util.MapPatch
20 import com.android.systemui.kairos.util.These
21 import com.android.systemui.kairos.util.maybeOf
22 import com.android.systemui.kairos.util.toMaybe
23 import kotlin.time.Duration.Companion.seconds
24 import kotlinx.coroutines.ExperimentalCoroutinesApi
25 import kotlinx.coroutines.launch
26 import kotlinx.coroutines.test.TestDispatcher
27 import kotlinx.coroutines.test.UnconfinedTestDispatcher
28 import kotlinx.coroutines.test.runTest
29 import org.junit.Assert
30 import org.junit.Assert.assertTrue
31 import org.junit.Test
32 
33 @OptIn(ExperimentalCoroutinesApi::class)
34 class KairosSamples {
35 
36     @Test fun test_mapMaybe() = runSample { mapMaybe() }
37 
38     fun BuildScope.mapMaybe() {
39         val emitter = MutableEvents<String>()
40         val ints = emitter.mapMaybe { it.toIntOrNull().toMaybe() }
41 
42         var observedInput: String? = null
43         emitter.observe { observedInput = it }
44 
45         var observedInt: Int? = null
46         ints.observe { observedInt = it }
47 
48         launchEffect {
49             // parse succeeds
50             emitter.emit("6")
51             assertEquals(observedInput, "6")
52             assertEquals(observedInt, 6)
53 
54             // parse fails
55             emitter.emit("foo")
56             assertEquals(observedInput, "foo")
57             assertEquals(observedInt, 6)
58 
59             // parse succeeds
60             emitter.emit("500")
61             assertEquals(observedInput, "500")
62             assertEquals(observedInt, 500)
63         }
64     }
65 
66     @Test fun test_mapCheap() = runSample { mapCheap() }
67 
68     fun BuildScope.mapCheap() {
69         val emitter = MutableEvents<Int>()
70 
71         var invocationCount = 0
72         val squared =
73             emitter.mapCheap {
74                 invocationCount++
75                 it * it
76             }
77 
78         var observedSquare: Int? = null
79         squared.observe { observedSquare = it }
80 
81         launchEffect {
82             emitter.emit(10)
83             assertTrue(invocationCount >= 1)
84             assertEquals(observedSquare, 100)
85 
86             emitter.emit(2)
87             assertTrue(invocationCount >= 2)
88             assertEquals(observedSquare, 4)
89         }
90     }
91 
92     @Test fun test_mapEvents() = runSample { mapEvents() }
93 
94     fun BuildScope.mapEvents() {
95         val emitter = MutableEvents<Int>()
96 
97         val squared = emitter.map { it * it }
98 
99         var observedSquare: Int? = null
100         squared.observe { observedSquare = it }
101 
102         launchEffect {
103             emitter.emit(10)
104             assertEquals(observedSquare, 100)
105 
106             emitter.emit(2)
107             assertEquals(observedSquare, 4)
108         }
109     }
110 
111     @Test fun test_eventsLoop() = runSample { eventsLoop() }
112 
113     fun BuildScope.eventsLoop() {
114         val emitter = MutableEvents<Unit>()
115         var newCount: Events<Int> by EventsLoop()
116         val count = newCount.holdState(0)
117         newCount = emitter.map { count.sample() + 1 }
118 
119         var observedCount = 0
120         count.observe { observedCount = it }
121 
122         launchEffect {
123             emitter.emit(Unit)
124             assertEquals(observedCount, expected = 1)
125 
126             emitter.emit(Unit)
127             assertEquals(observedCount, expected = 2)
128         }
129     }
130 
131     @Test fun test_stateLoop() = runSample { stateLoop() }
132 
133     fun BuildScope.stateLoop() {
134         val emitter = MutableEvents<Unit>()
135         var count: State<Int> by StateLoop()
136         count = emitter.map { count.sample() + 1 }.holdState(0)
137 
138         var observedCount = 0
139         count.observe { observedCount = it }
140 
141         launchEffect {
142             emitter.emit(Unit)
143             assertEquals(observedCount, expected = 1)
144 
145             emitter.emit(Unit)
146             assertEquals(observedCount, expected = 2)
147         }
148     }
149 
150     @Test fun test_changes() = runSample { changes() }
151 
152     fun BuildScope.changes() {
153         val emitter = MutableEvents<Int>()
154         val state = emitter.holdState(0)
155 
156         var numEmissions = 0
157         emitter.observe { numEmissions++ }
158 
159         var observedState = 0
160         var numChangeEmissions = 0
161         state.changes.observe {
162             observedState = it
163             numChangeEmissions++
164         }
165 
166         launchEffect {
167             emitter.emit(0)
168             assertEquals(numEmissions, expected = 1)
169             assertEquals(numChangeEmissions, expected = 0)
170             assertEquals(observedState, expected = 0)
171 
172             emitter.emit(5)
173             assertEquals(numEmissions, expected = 2)
174             assertEquals(numChangeEmissions, expected = 1)
175             assertEquals(observedState, expected = 5)
176 
177             emitter.emit(3)
178             assertEquals(numEmissions, expected = 3)
179             assertEquals(numChangeEmissions, expected = 2)
180             assertEquals(observedState, expected = 3)
181 
182             emitter.emit(3)
183             assertEquals(numEmissions, expected = 4)
184             assertEquals(numChangeEmissions, expected = 2)
185             assertEquals(observedState, expected = 3)
186 
187             emitter.emit(5)
188             assertEquals(numEmissions, expected = 5)
189             assertEquals(numChangeEmissions, expected = 3)
190             assertEquals(observedState, expected = 5)
191         }
192     }
193 
194     @Test fun test_partitionThese() = runSample { partitionThese() }
195 
196     fun BuildScope.partitionThese() {
197         val emitter = MutableEvents<These<Int, String>>()
198         val (lefts, rights) = emitter.partitionThese()
199 
200         var observedLeft: Int? = null
201         lefts.observe { observedLeft = it }
202 
203         var observedRight: String? = null
204         rights.observe { observedRight = it }
205 
206         launchEffect {
207             emitter.emit(These.first(10))
208             assertEquals(observedLeft, 10)
209             assertEquals(observedRight, null)
210 
211             emitter.emit(These.both(2, "foo"))
212             assertEquals(observedLeft, 2)
213             assertEquals(observedRight, "foo")
214 
215             emitter.emit(These.second("bar"))
216             assertEquals(observedLeft, 2)
217             assertEquals(observedRight, "bar")
218         }
219     }
220 
221     @Test fun test_merge() = runSample { merge() }
222 
223     fun BuildScope.merge() {
224         val emitter = MutableEvents<Int>()
225         val fizz = emitter.mapNotNull { if (it % 3 == 0) "Fizz" else null }
226         val buzz = emitter.mapNotNull { if (it % 5 == 0) "Buzz" else null }
227         val fizzbuzz = fizz.mergeWith(buzz) { _, _ -> "Fizz Buzz" }
228         val output = mergeLeft(fizzbuzz, emitter.mapCheap { it.toString() })
229 
230         var observedOutput: String? = null
231         output.observe { observedOutput = it }
232 
233         launchEffect {
234             emitter.emit(1)
235             assertEquals(observedOutput, "1")
236             emitter.emit(2)
237             assertEquals(observedOutput, "2")
238             emitter.emit(3)
239             assertEquals(observedOutput, "Fizz")
240             emitter.emit(4)
241             assertEquals(observedOutput, "4")
242             emitter.emit(5)
243             assertEquals(observedOutput, "Buzz")
244             emitter.emit(6)
245             assertEquals(observedOutput, "Fizz")
246             emitter.emit(15)
247             assertEquals(observedOutput, "Fizz Buzz")
248         }
249     }
250 
251     @Test fun test_groupByKey() = runSample { groupByKey() }
252 
253     fun BuildScope.groupByKey() {
254         val emitter = MutableEvents<Map<String, Int>>()
255         val grouped = emitter.groupByKey()
256         val groupA = grouped["A"]
257         val groupB = grouped["B"]
258 
259         var numEmissions = 0
260         emitter.observe { numEmissions++ }
261 
262         var observedA: Int? = null
263         groupA.observe { observedA = it }
264 
265         var observedB: Int? = null
266         groupB.observe { observedB = it }
267 
268         launchEffect {
269             // emit to group A
270             emitter.emit(mapOf("A" to 3))
271             assertEquals(numEmissions, 1)
272             assertEquals(observedA, 3)
273             assertEquals(observedB, null)
274 
275             // emit to groups B and C, even though there are no observers of C
276             emitter.emit(mapOf("B" to 9, "C" to 100))
277             assertEquals(numEmissions, 2)
278             assertEquals(observedA, 3)
279             assertEquals(observedB, 9)
280 
281             // emit to groups A and B
282             emitter.emit(mapOf("B" to 6, "A" to 14))
283             assertEquals(numEmissions, 3)
284             assertEquals(observedA, 14)
285             assertEquals(observedB, 6)
286 
287             // emit to group with no listeners
288             emitter.emit(mapOf("Q" to -66))
289             assertEquals(numEmissions, 4)
290             assertEquals(observedA, 14)
291             assertEquals(observedB, 6)
292 
293             // no-op emission
294             emitter.emit(emptyMap())
295             assertEquals(numEmissions, 5)
296             assertEquals(observedA, 14)
297             assertEquals(observedB, 6)
298         }
299     }
300 
301     @Test fun test_switchEvents() = runSample { switchEvents() }
302 
303     fun BuildScope.switchEvents() {
304         val negator = MutableEvents<Unit>()
305         val emitter = MutableEvents<Int>()
306         val negate = negator.foldState(false) { _, negate -> !negate }
307         val output =
308             negate.map { negate -> if (negate) emitter.map { it * -1 } else emitter }.switchEvents()
309 
310         var observed: Int? = null
311         output.observe { observed = it }
312 
313         launchEffect {
314             // emit like normal
315             emitter.emit(10)
316             assertEquals(observed, 10)
317 
318             // enable negation
319             observed = null
320             negator.emit(Unit)
321             assertEquals(observed, null)
322 
323             emitter.emit(99)
324             assertEquals(observed, -99)
325 
326             // disable negation
327             observed = null
328             negator.emit(Unit)
329             emitter.emit(7)
330             assertEquals(observed, 7)
331         }
332     }
333 
334     @Test fun test_switchEventsPromptly() = runSample { switchEventsPromptly() }
335 
336     fun BuildScope.switchEventsPromptly() {
337         val emitter = MutableEvents<Int>()
338         val enabled = emitter.map { it > 10 }.holdState(false)
339         val switchedIn = enabled.map { enabled -> if (enabled) emitter else emptyEvents }
340         val deferredSwitch = switchedIn.switchEvents()
341         val promptSwitch = switchedIn.switchEventsPromptly()
342 
343         var observedDeferred: Int? = null
344         deferredSwitch.observe { observedDeferred = it }
345 
346         var observedPrompt: Int? = null
347         promptSwitch.observe { observedPrompt = it }
348 
349         launchEffect {
350             emitter.emit(3)
351             assertEquals(observedDeferred, null)
352             assertEquals(observedPrompt, null)
353 
354             emitter.emit(20)
355             assertEquals(observedDeferred, null)
356             assertEquals(observedPrompt, 20)
357 
358             emitter.emit(30)
359             assertEquals(observedDeferred, 30)
360             assertEquals(observedPrompt, 30)
361 
362             emitter.emit(8)
363             assertEquals(observedDeferred, 8)
364             assertEquals(observedPrompt, 8)
365 
366             emitter.emit(1)
367             assertEquals(observedDeferred, 8)
368             assertEquals(observedPrompt, 8)
369         }
370     }
371 
372     @Test fun test_sampleTransactional() = runSample { sampleTransactional() }
373 
374     fun BuildScope.sampleTransactional() {
375         var store = 0
376         val transactional = transactionally { store++ }
377 
378         effect {
379             assertEquals(store, 0)
380             assertEquals(transactional.sample(), 0)
381             assertEquals(store, 1)
382             assertEquals(transactional.sample(), 0)
383             assertEquals(store, 1)
384         }
385     }
386 
387     @Test fun test_states() = runSample { states() }
388 
389     fun BuildScope.states() {
390         val constantState = stateOf(10)
391         effect { assertEquals(constantState.sample(), 10) }
392 
393         val mappedConstantState: State<Int> = constantState.map { it * 2 }
394         effect { assertEquals(mappedConstantState.sample(), 20) }
395 
396         val emitter = MutableEvents<Int>()
397         val heldState: State<Int?> = emitter.holdState(null)
398         effect { assertEquals(heldState.sample(), null) }
399 
400         var observed: Int? = null
401         var wasObserved = false
402         heldState.observe {
403             observed = it
404             wasObserved = true
405         }
406         launchEffect {
407             assertTrue(wasObserved)
408             emitter.emit(4)
409             assertEquals(observed, 4)
410         }
411 
412         val combinedStates: State<Pair<Int, Int?>> =
413             combine(mappedConstantState, heldState) { a, b -> Pair(a, b) }
414 
415         effect { assertEquals(combinedStates.sample(), 20 to null) }
416 
417         var observedPair: Pair<Int, Int?>? = null
418         combinedStates.observe { observedPair = it }
419         launchEffect {
420             emitter.emit(12)
421             assertEquals(observedPair, 20 to 12)
422         }
423     }
424 
425     @Test fun test_holdState() = runSample { holdState() }
426 
427     fun BuildScope.holdState() {
428         val emitter = MutableEvents<Int>()
429         val heldState: State<Int?> = emitter.holdState(null)
430         effect { assertEquals(heldState.sample(), null) }
431 
432         var observed: Int? = null
433         var wasObserved = false
434         heldState.observe {
435             observed = it
436             wasObserved = true
437         }
438         launchEffect {
439             // observation of the initial state took place immediately
440             assertTrue(wasObserved)
441 
442             // state changes are also observed
443             emitter.emit(4)
444             assertEquals(observed, 4)
445 
446             emitter.emit(20)
447             assertEquals(observed, 20)
448         }
449     }
450 
451     @Test fun test_mapState() = runSample { mapState() }
452 
453     fun BuildScope.mapState() {
454         val emitter = MutableEvents<Int>()
455         val held: State<Int> = emitter.holdState(0)
456         val squared: State<Int> = held.map { it * it }
457 
458         var observed: Int? = null
459         squared.observe { observed = it }
460 
461         launchEffect {
462             assertEquals(observed, 0)
463 
464             emitter.emit(10)
465             assertEquals(observed, 100)
466         }
467     }
468 
469     @Test fun test_combineState() = runSample { combineState() }
470 
471     fun BuildScope.combineState() {
472         val emitter = MutableEvents<Int>()
473         val state = emitter.holdState(0)
474         val squared = state.map { it * it }
475         val negated = state.map { -it }
476         val combined = squared.combine(negated) { a, b -> Pair(a, b) }
477 
478         val observed = mutableListOf<Pair<Int, Int>>()
479         combined.observe { observed.add(it) }
480 
481         launchEffect {
482             emitter.emit(10)
483             emitter.emit(20)
484             emitter.emit(3)
485 
486             assertEquals(observed, listOf(0 to 0, 100 to -10, 400 to -20, 9 to -3))
487         }
488     }
489 
490     @Test fun test_flatMap() = runSample { flatMap() }
491 
492     fun BuildScope.flatMap() {
493         val toggler = MutableEvents<Unit>()
494         val firstEmitter = MutableEvents<Unit>()
495         val secondEmitter = MutableEvents<Unit>()
496 
497         val firstCount: State<Int> = firstEmitter.foldState(0) { _, count -> count + 1 }
498         val secondCount: State<Int> = secondEmitter.foldState(0) { _, count -> count + 1 }
499         val toggleState: State<Boolean> = toggler.foldState(true) { _, state -> !state }
500 
501         val activeCount: State<Int> =
502             toggleState.flatMap { b -> if (b) firstCount else secondCount }
503 
504         var observed: Int? = null
505         activeCount.observe { observed = it }
506 
507         launchEffect {
508             assertEquals(observed, 0)
509 
510             firstEmitter.emit(Unit)
511             assertEquals(observed, 1)
512 
513             secondEmitter.emit(Unit)
514             assertEquals(observed, 1)
515 
516             secondEmitter.emit(Unit)
517             assertEquals(observed, 1)
518 
519             toggler.emit(Unit)
520             assertEquals(observed, 2)
521 
522             toggler.emit(Unit)
523             assertEquals(observed, 1)
524         }
525     }
526 
527     @Test fun test_incrementals() = runSample { incrementals() }
528 
529     fun BuildScope.incrementals() {
530         val patchEmitter = MutableEvents<MapPatch<String, Int>>()
531         val incremental: Incremental<String, Int> = patchEmitter.foldStateMapIncrementally()
532         val squared = incremental.mapValues { (key, value) -> value * value }
533 
534         var observedUpdate: MapPatch<String, Int>? = null
535         squared.updates.observe { observedUpdate = it }
536 
537         var observedState: Map<String, Int>? = null
538         squared.observe { observedState = it }
539 
540         launchEffect {
541             assertEquals(observedState, emptyMap())
542             assertEquals(observedUpdate, null)
543 
544             // add entry: A => 10
545             patchEmitter.emit(mapOf("A" to maybeOf(10)))
546             assertEquals(observedState, mapOf("A" to 100))
547             assertEquals(observedUpdate, mapOf("A" to maybeOf(100)))
548 
549             // update entry: A => 5
550             // add entry: B => 6
551             patchEmitter.emit(mapOf("A" to maybeOf(5), "B" to maybeOf(6)))
552             assertEquals(observedState, mapOf("A" to 25, "B" to 36))
553             assertEquals(observedUpdate, mapOf("A" to maybeOf(25), "B" to maybeOf(36)))
554 
555             // remove entry: A
556             // add entry: C => 9
557             // remove non-existent entry: F
558             patchEmitter.emit(mapOf("A" to maybeOf(), "C" to maybeOf(9), "F" to maybeOf()))
559             assertEquals(observedState, mapOf("B" to 36, "C" to 81))
560             // non-existent entry is filtered from the update
561             assertEquals(observedUpdate, mapOf("A" to maybeOf(), "C" to maybeOf(81)))
562         }
563     }
564 
565     @Test fun test_mergeEventsIncrementally() = runSample(block = mergeEventsIncrementally())
566 
567     fun mergeEventsIncrementally(): BuildSpec<Unit> = buildSpec {
568         val patchEmitter = MutableEvents<MapPatch<String, Events<Int>>>()
569         val incremental: Incremental<String, Events<Int>> = patchEmitter.foldStateMapIncrementally()
570         val merged: Events<Map<String, Int>> = incremental.mergeEventsIncrementally()
571 
572         var observed: Map<String, Int>? = null
573         merged.observe { observed = it }
574 
575         launchEffect {
576             // add events entry: A
577             val emitterA = MutableEvents<Int>()
578             patchEmitter.emit(mapOf("A" to maybeOf(emitterA)))
579 
580             emitterA.emit(100)
581             assertEquals(observed, mapOf("A" to 100))
582 
583             // add events entry: B
584             val emitterB = MutableEvents<Int>()
585             patchEmitter.emit(mapOf("B" to maybeOf(emitterB)))
586 
587             // merged emits from both A and B
588             emitterB.emit(5)
589             assertEquals(observed, mapOf("B" to 5))
590 
591             emitterA.emit(20)
592             assertEquals(observed, mapOf("A" to 20))
593 
594             // remove entry: A
595             patchEmitter.emit(mapOf("A" to maybeOf()))
596             emitterA.emit(0)
597             // event is not emitted now that A has been removed
598             assertEquals(observed, mapOf("A" to 20))
599 
600             // but B still works
601             emitterB.emit(3)
602             assertEquals(observed, mapOf("B" to 3))
603         }
604     }
605 
606     @Test
607     fun test_mergeEventsIncrementallyPromptly() =
608         runSample(block = mergeEventsIncrementallyPromptly())
609 
610     fun mergeEventsIncrementallyPromptly(): BuildSpec<Unit> = buildSpec {
611         val patchEmitter = MutableEvents<MapPatch<String, Events<Int>>>()
612         val incremental: Incremental<String, Events<Int>> = patchEmitter.foldStateMapIncrementally()
613         val deferredMerge: Events<Map<String, Int>> = incremental.mergeEventsIncrementally()
614         val promptMerge: Events<Map<String, Int>> = incremental.mergeEventsIncrementallyPromptly()
615 
616         var observedDeferred: Map<String, Int>? = null
617         deferredMerge.observe { observedDeferred = it }
618 
619         var observedPrompt: Map<String, Int>? = null
620         promptMerge.observe { observedPrompt = it }
621 
622         launchEffect {
623             val emitterA = MutableEvents<Int>()
624             patchEmitter.emit(mapOf("A" to maybeOf(emitterA)))
625 
626             emitterA.emit(100)
627             assertEquals(observedDeferred, mapOf("A" to 100))
628             assertEquals(observedPrompt, mapOf("A" to 100))
629 
630             val emitterB = patchEmitter.map { 5 }
631             patchEmitter.emit(mapOf("B" to maybeOf(emitterB)))
632 
633             assertEquals(observedDeferred, mapOf("A" to 100))
634             assertEquals(observedPrompt, mapOf("B" to 5))
635         }
636     }
637 
638     @Test fun test_applyLatestStateful() = runSample(block = applyLatestStateful())
639 
640     fun applyLatestStateful(): BuildSpec<Unit> = buildSpec {
641         val reset = MutableEvents<Unit>()
642         val emitter = MutableEvents<Unit>()
643         val stateEvents: Events<State<Int>> =
644             reset
645                 .map { statefully { emitter.foldState(0) { _, count -> count + 1 } } }
646                 .applyLatestStateful()
647         val activeState: State<State<Int>?> = stateEvents.holdState(null)
648 
649         launchEffect {
650             // nothing is active yet
651             kairosNetwork.transact { assertEquals(activeState.sample(), null) }
652 
653             // activate the counter
654             reset.emit(Unit)
655             val firstState =
656                 kairosNetwork.transact {
657                     assertEquals(activeState.sample()?.sample(), 0)
658                     activeState.sample()!!
659                 }
660 
661             // emit twice
662             emitter.emit(Unit)
663             emitter.emit(Unit)
664             kairosNetwork.transact { assertEquals(firstState.sample(), 2) }
665 
666             // start a new counter, disabling the old one
667             reset.emit(Unit)
668             val secondState =
669                 kairosNetwork.transact {
670                     assertEquals(activeState.sample()?.sample(), 0)
671                     activeState.sample()!!
672                 }
673             kairosNetwork.transact { assertEquals(firstState.sample(), 2) }
674 
675             // emit: the new counter updates, but the old one does not
676             emitter.emit(Unit)
677             kairosNetwork.transact { assertEquals(secondState.sample(), 1) }
678             kairosNetwork.transact { assertEquals(firstState.sample(), 2) }
679         }
680     }
681 
682     @Test fun test_applyLatestStatefulForKey() = runSample(block = applyLatestStatefulForKey())
683 
684     fun applyLatestStatefulForKey(): BuildSpec<Unit> = buildSpec {
685         val reset = MutableEvents<String>()
686         val emitter = MutableEvents<String>()
687         val stateEvents: Events<MapPatch<String, State<Int>>> =
688             reset
689                 .map { key ->
690                     mapOf(
691                         key to
692                             maybeOf(
693                                 statefully {
694                                     emitter
695                                         .filter { it == key }
696                                         .foldState(0) { _, count -> count + 1 }
697                                 }
698                             )
699                     )
700                 }
701                 .applyLatestStatefulForKey()
702         val activeStatesByKey: Incremental<String, State<Int>> =
703             stateEvents.foldStateMapIncrementally(emptyMap())
704 
705         launchEffect {
706             // nothing is active yet
707             kairosNetwork.transact { assertEquals(activeStatesByKey.sample(), emptyMap()) }
708 
709             // activate a new entry A
710             reset.emit("A")
711             val firstStateA =
712                 kairosNetwork.transact {
713                     val stateMap: Map<String, State<Int>> = activeStatesByKey.sample()
714                     assertEquals(stateMap.keys, setOf("A"))
715                     stateMap.getValue("A").also { assertEquals(it.sample(), 0) }
716                 }
717 
718             // emit twice to A
719             emitter.emit("A")
720             emitter.emit("A")
721             kairosNetwork.transact { assertEquals(firstStateA.sample(), 2) }
722 
723             // active a new entry B
724             reset.emit("B")
725             val firstStateB =
726                 kairosNetwork.transact {
727                     val stateMap: Map<String, State<Int>> = activeStatesByKey.sample()
728                     assertEquals(stateMap.keys, setOf("A", "B"))
729                     stateMap.getValue("B").also {
730                         assertEquals(it.sample(), 0)
731                         assertEquals(firstStateA.sample(), 2)
732                     }
733                 }
734 
735             // emit once to B
736             emitter.emit("B")
737             kairosNetwork.transact {
738                 assertEquals(firstStateA.sample(), 2)
739                 assertEquals(firstStateB.sample(), 1)
740             }
741 
742             // activate a new entry for A, disabling the old entry
743             reset.emit("A")
744             val secondStateA =
745                 kairosNetwork.transact {
746                     val stateMap: Map<String, State<Int>> = activeStatesByKey.sample()
747                     assertEquals(stateMap.keys, setOf("A", "B"))
748                     stateMap.getValue("A").also {
749                         assertEquals(it.sample(), 0)
750                         assertEquals(firstStateB.sample(), 1)
751                     }
752                 }
753 
754             // emit to A: the new A state updates, but the old one does not
755             emitter.emit("A")
756             kairosNetwork.transact {
757                 assertEquals(firstStateA.sample(), 2)
758                 assertEquals(secondStateA.sample(), 1)
759             }
760         }
761     }
762 
763     private fun runSample(
764         dispatcher: TestDispatcher = UnconfinedTestDispatcher(),
765         block: BuildScope.() -> Unit,
766     ) {
767         runTest(dispatcher, timeout = 1.seconds) {
768             val kairosNetwork = backgroundScope.launchKairosNetwork()
769             backgroundScope.launch { kairosNetwork.activateSpec { block() } }
770         }
771     }
772 }
773 
assertEqualsnull774 private fun <T> assertEquals(actual: T, expected: T) = Assert.assertEquals(expected, actual)
775