Skip to content

Commit 3c75363

Browse files
committed
Add next/previous cycle functionality tests
1 parent 0d49a88 commit 3c75363

File tree

2 files changed

+130
-8
lines changed

2 files changed

+130
-8
lines changed

library/runtime-service/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/service/AbstractTorServiceUI.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,8 @@ internal constructor(
812812
private fun IS.postUpdate() {
813813
if (_displayed != fileIDKey()) return
814814
val instance = this
815+
val hasPrevious = hasPrevious
816+
val hasNext = hasNext
815817

816818
serviceChildScope.launch {
817819
if (_displayed != fileIDKey()) return@launch

library/runtime-service/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/service/AbstractTorServiceUIUnitTest.kt

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import kotlinx.coroutines.test.TestScope
2828
import kotlinx.coroutines.test.runTest
2929
import kotlin.test.*
3030

31+
@Suppress("BooleanLiteralArgument")
3132
@OptIn(ExperimentalKmpTorApi::class)
3233
class AbstractTorServiceUIUnitTest {
3334

@@ -49,12 +50,15 @@ class AbstractTorServiceUIUnitTest {
4950

5051
val scope = serviceChildScope
5152

52-
val updates = mutableListOf<Triple<State, Boolean, Boolean>>()
53+
val updates = mutableListOf<Update>()
5354

5455
val instanceStatesTest: Collection<State> get() = instanceStates
5556

57+
fun previousTest() { previous() }
58+
fun nextTest() { next() }
59+
5660
protected override fun onUpdate(displayed: State, hasPrevious: Boolean, hasNext: Boolean) {
57-
updates.add(Triple(displayed, hasPrevious, hasNext))
61+
updates.add(Update(displayed, Update.Indicators(hasPrevious, hasNext)))
5862
}
5963

6064
protected override fun onDestroy() {
@@ -121,6 +125,11 @@ class AbstractTorServiceUIUnitTest {
121125

122126
fun postStateChangeTest() { postStateChange() }
123127
}
128+
129+
data class Update(val state: State, val indicators: Indicators) {
130+
131+
data class Indicators(val hasPrevious: Boolean, val hasNext: Boolean)
132+
}
124133
}
125134

126135
val config = TestUI.Config(mapOf("" to ""))
@@ -151,7 +160,7 @@ class AbstractTorServiceUIUnitTest {
151160
}
152161

153162
@Test
154-
fun givenFactory_whenNewUIInstance_thenArgsAreVerified() = runTest {
163+
fun givenFactory_whenNewUI_thenArgsAreVerified() = runTest {
155164
run {
156165
val args = TestUI.Args(config, this)
157166
val factory = TestUI.Factory(config)
@@ -176,7 +185,7 @@ class AbstractTorServiceUIUnitTest {
176185
}
177186

178187
@Test
179-
fun givenFactory_whenNewUIInstance_thenOnDestroyCallbackSet() = runTest {
188+
fun givenFactory_whenNewUI_thenOnDestroyCallbackSet() = runTest {
180189
val factory = TestUI.Factory(config)
181190
val instance = factory.newInstanceUI(TestUI.Args(config, this))
182191

@@ -191,7 +200,7 @@ class AbstractTorServiceUIUnitTest {
191200
}
192201

193202
@Test
194-
fun givenUIInstance_whenNewInstanceState_thenArgsAreVerified() = runTest {
203+
fun givenUI_whenNewInstanceState_thenArgsAreVerified() = runTest {
195204
var state: TestUI.State? = null
196205
var iArgs: AbstractTorServiceUI.Args.Instance? = null
197206

@@ -221,7 +230,7 @@ class AbstractTorServiceUIUnitTest {
221230
}
222231

223232
@Test
224-
fun givenUIInstance_whenInstanceState_thenDispatchesUpdatesToUI() = runTest {
233+
fun givenUI_whenInstanceState_thenDispatchesUpdatesToUI() = runTest {
225234
val factory = TestUI.Factory(config)
226235
val ui = factory.newInstanceUI(TestUI.Args(config, this))
227236
val (instanceJob, instance) = ui.newTestInstanceState(fid = "abcde12345")
@@ -233,12 +242,12 @@ class AbstractTorServiceUIUnitTest {
233242

234243
delayTest()
235244
assertEquals(1, ui.updates.size)
236-
assertEquals("abcde12345", ui.updates[0].first.fid)
245+
assertEquals("abcde12345", ui.updates[0].state.fid)
237246

238247
instance.postStateChangeTest()
239248
delayTest()
240249
assertEquals(2, ui.updates.size)
241-
assertEquals("abcde12345", ui.updates[1].first.fid)
250+
assertEquals("abcde12345", ui.updates[1].state.fid)
242251

243252
instanceJob.cancel()
244253
delayTest()
@@ -251,6 +260,117 @@ class AbstractTorServiceUIUnitTest {
251260
assertTrue(ui.instanceStatesTest.isEmpty())
252261
}
253262

263+
@Test
264+
fun givenUI_whenMultipleInstanceStates_thenUpdatesHasPreviousOrNextAsExpected() = runTest {
265+
val factory = TestUI.Factory(config)
266+
val ui = factory.newInstanceUI(TestUI.Args(config, this))
267+
268+
val numInstances = 5
269+
val instances = mutableListOf<Pair<Job, TestUI.State>>()
270+
repeat(numInstances) { i ->
271+
val pair = ui.newTestInstanceState(fid = "abcde12345$i")
272+
instances.add(pair)
273+
delayTest()
274+
}
275+
276+
assertEquals(numInstances, instances.size)
277+
assertEquals(instances.size, ui.instanceStatesTest.size)
278+
279+
// Even with 5 added, the only update posted should be when
280+
// the 2nd instance was created for hasNext
281+
assertEquals(2, ui.updates.size)
282+
assertEquals(TestUI.Update.Indicators(false, false), ui.updates[0].indicators)
283+
assertEquals(TestUI.Update.Indicators(false, true), ui.updates[1].indicators)
284+
285+
// instanceNumber 0 -> 1
286+
ui.nextTest()
287+
delayTest()
288+
assertEquals(3, ui.updates.size)
289+
assertEquals(TestUI.Update.Indicators(true, true), ui.updates[2].indicators)
290+
291+
// instanceNumber 1 -> 2
292+
ui.nextTest()
293+
// instanceNumber 2 -> 3
294+
ui.nextTest()
295+
296+
delayTest()
297+
// 1st update should not post an update b/c the _displayed
298+
// variable that was set was changed in 2nd call. This indicates
299+
// that the launch lambda performs a check before calling onUpdate.
300+
assertEquals(4, ui.updates.size)
301+
assertEquals(ui.instanceStatesTest.elementAt(3), ui.updates[3].state)
302+
assertEquals(TestUI.Update.Indicators(true, true), ui.updates[3].indicators)
303+
304+
// instanceNumber 3 -> 2
305+
ui.previousTest()
306+
delayTest()
307+
assertEquals(5, ui.updates.size)
308+
assertEquals(ui.instanceStatesTest.elementAt(2), ui.updates[4].state)
309+
assertEquals(TestUI.Update.Indicators(true, true), ui.updates[4].indicators)
310+
311+
// instanceNumber 2 -> 3
312+
ui.nextTest()
313+
// instanceNumber 3 -> 4
314+
ui.nextTest()
315+
delayTest()
316+
assertEquals(6, ui.updates.size)
317+
assertEquals(ui.instanceStatesTest.last(), ui.updates[5].state)
318+
assertEquals(TestUI.Update.Indicators(true, false), ui.updates[5].indicators)
319+
320+
// Should not do anything because on last instance
321+
ui.nextTest()
322+
delayTest()
323+
assertEquals(6, ui.updates.size)
324+
325+
// instanceNumber 4 -> 3
326+
// Instance removed, update with new displayed Instance
327+
instances.last().first.cancel()
328+
// Should immediately remove instance, but not dispatch an update
329+
// b/c is not next to currently displayed.
330+
assertEquals(numInstances - 1, ui.instanceStatesTest.size)
331+
delayTest()
332+
assertEquals(7, ui.updates.size)
333+
assertEquals(ui.instanceStatesTest.last(), ui.updates[6].state)
334+
assertEquals(TestUI.Update.Indicators(true, false), ui.updates[6].indicators)
335+
336+
// instanceNumber 3 -> 2
337+
instances.first().first.cancel()
338+
// Should immediately remove instance, but not dispatch an update
339+
// b/c is not next to currently displayed.
340+
assertEquals(numInstances - 2, ui.instanceStatesTest.size)
341+
delayTest()
342+
assertEquals(7, ui.updates.size)
343+
344+
// Should not post b/c it is not the currently displayed instance
345+
ui.instanceStatesTest.first().postStateChangeTest()
346+
delayTest()
347+
assertEquals(7, ui.updates.size)
348+
349+
// instanceNumber 2 -> 1
350+
ui.previousTest()
351+
delayTest()
352+
assertEquals(8, ui.updates.size)
353+
assertEquals(TestUI.Update.Indicators(true, true), ui.updates[7].indicators)
354+
355+
assertEquals(3, ui.instanceStatesTest.size)
356+
assertEquals(instances[2].first, ui.instanceStatesTest.elementAt(1).scope.coroutineContext.job)
357+
358+
// instanceNumber 1 -> 0
359+
instances[2].first.cancel()
360+
delayTest()
361+
assertEquals(9, ui.updates.size)
362+
assertEquals(TestUI.Update.Indicators(false, true), ui.updates[8].indicators)
363+
364+
// Destroy remaining
365+
instances.forEach { it.first.cancel() }
366+
delayTest()
367+
// No updates should post b/c all were removed and the
368+
// currently set _displayed variable is null and does not
369+
// match the instance in the coroutine launch lambda, so
370+
// stops.
371+
assertEquals(9, ui.updates.size)
372+
}
373+
254374
private suspend fun delayTest() = delay(500)
255375

256376
// overloaded with defaults for making tests simpler

0 commit comments

Comments
 (0)