@@ -28,6 +28,7 @@ import kotlinx.coroutines.test.TestScope
28
28
import kotlinx.coroutines.test.runTest
29
29
import kotlin.test.*
30
30
31
+ @Suppress(" BooleanLiteralArgument" )
31
32
@OptIn(ExperimentalKmpTorApi ::class )
32
33
class AbstractTorServiceUIUnitTest {
33
34
@@ -49,12 +50,15 @@ class AbstractTorServiceUIUnitTest {
49
50
50
51
val scope = serviceChildScope
51
52
52
- val updates = mutableListOf<Triple < State , Boolean , Boolean > >()
53
+ val updates = mutableListOf<Update >()
53
54
54
55
val instanceStatesTest: Collection <State > get() = instanceStates
55
56
57
+ fun previousTest () { previous() }
58
+ fun nextTest () { next() }
59
+
56
60
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) ))
58
62
}
59
63
60
64
protected override fun onDestroy () {
@@ -121,6 +125,11 @@ class AbstractTorServiceUIUnitTest {
121
125
122
126
fun postStateChangeTest () { postStateChange() }
123
127
}
128
+
129
+ data class Update (val state : State , val indicators : Indicators ) {
130
+
131
+ data class Indicators (val hasPrevious : Boolean , val hasNext : Boolean )
132
+ }
124
133
}
125
134
126
135
val config = TestUI .Config (mapOf (" " to " " ))
@@ -151,7 +160,7 @@ class AbstractTorServiceUIUnitTest {
151
160
}
152
161
153
162
@Test
154
- fun givenFactory_whenNewUIInstance_thenArgsAreVerified () = runTest {
163
+ fun givenFactory_whenNewUI_thenArgsAreVerified () = runTest {
155
164
run {
156
165
val args = TestUI .Args (config, this )
157
166
val factory = TestUI .Factory (config)
@@ -176,7 +185,7 @@ class AbstractTorServiceUIUnitTest {
176
185
}
177
186
178
187
@Test
179
- fun givenFactory_whenNewUIInstance_thenOnDestroyCallbackSet () = runTest {
188
+ fun givenFactory_whenNewUI_thenOnDestroyCallbackSet () = runTest {
180
189
val factory = TestUI .Factory (config)
181
190
val instance = factory.newInstanceUI(TestUI .Args (config, this ))
182
191
@@ -191,7 +200,7 @@ class AbstractTorServiceUIUnitTest {
191
200
}
192
201
193
202
@Test
194
- fun givenUIInstance_whenNewInstanceState_thenArgsAreVerified () = runTest {
203
+ fun givenUI_whenNewInstanceState_thenArgsAreVerified () = runTest {
195
204
var state: TestUI .State ? = null
196
205
var iArgs: AbstractTorServiceUI .Args .Instance ? = null
197
206
@@ -221,7 +230,7 @@ class AbstractTorServiceUIUnitTest {
221
230
}
222
231
223
232
@Test
224
- fun givenUIInstance_whenInstanceState_thenDispatchesUpdatesToUI () = runTest {
233
+ fun givenUI_whenInstanceState_thenDispatchesUpdatesToUI () = runTest {
225
234
val factory = TestUI .Factory (config)
226
235
val ui = factory.newInstanceUI(TestUI .Args (config, this ))
227
236
val (instanceJob, instance) = ui.newTestInstanceState(fid = " abcde12345" )
@@ -233,12 +242,12 @@ class AbstractTorServiceUIUnitTest {
233
242
234
243
delayTest()
235
244
assertEquals(1 , ui.updates.size)
236
- assertEquals(" abcde12345" , ui.updates[0 ].first .fid)
245
+ assertEquals(" abcde12345" , ui.updates[0 ].state .fid)
237
246
238
247
instance.postStateChangeTest()
239
248
delayTest()
240
249
assertEquals(2 , ui.updates.size)
241
- assertEquals(" abcde12345" , ui.updates[1 ].first .fid)
250
+ assertEquals(" abcde12345" , ui.updates[1 ].state .fid)
242
251
243
252
instanceJob.cancel()
244
253
delayTest()
@@ -251,6 +260,117 @@ class AbstractTorServiceUIUnitTest {
251
260
assertTrue(ui.instanceStatesTest.isEmpty())
252
261
}
253
262
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
+
254
374
private suspend fun delayTest () = delay(500 )
255
375
256
376
// overloaded with defaults for making tests simpler
0 commit comments