1 package expo.modules.av.progress
2 
3 import io.mockk.Runs
4 import io.mockk.every
5 import io.mockk.just
6 import io.mockk.mockk
7 import io.mockk.spyk
8 import io.mockk.verify
9 import org.junit.jupiter.api.BeforeEach
10 import org.junit.jupiter.api.Test
11 
12 internal class ProgressLooperTest {
13 
14   interface TestTimeMachine : TimeMachine {
advanceBynull15     fun advanceBy(interval: Long)
16     fun triggerListeners(by: Long = Long.MAX_VALUE)
17   }
18 
19   object TimeMachineInstance : TestTimeMachine {
20 
21     override var time = 0L
22 
23     var callbacks: Map<TimeMachineTick, Long> = HashMap()
24 
25     override fun advanceBy(interval: Long) {
26       time += interval
27     }
28 
29     override fun triggerListeners(by: Long) {
30       val toInvoke = callbacks.filter { it.value < by }
31       callbacks = callbacks.filter { it.value >= by }
32       toInvoke.forEach {
33         it.key()
34       }
35     }
36 
37     override fun scheduleAt(intervalMillis: Long, callback: TimeMachineTick) {
38       if (intervalMillis > 0) {
39         callbacks = callbacks.plus(callback to time + intervalMillis)
40       }
41     }
42 
43     fun reset() {
44       callbacks = HashMap()
45       time = 0
46     }
47   }
48 
49   lateinit var looper: ProgressLooper
50   lateinit var callback: TimeMachineTick
51   lateinit var timeMachine: TestTimeMachine
52 
53   @BeforeEach
setUpnull54   fun setUp() {
55     TimeMachineInstance.reset()
56     timeMachine = spyk(TimeMachineInstance)
57     looper = ProgressLooper(timeMachine)
58     callback = mockk()
59     every { callback() } just Runs
60   }
61 
62   @Test
callback not invoked prematurelynull63   fun `callback not invoked prematurely`() {
64     looper.loop(1000, callback)
65     verify(exactly = 0) { callback() }
66   }
67 
68   @Test
callback invoked once after time passednull69   fun `callback invoked once after time passed`() {
70     looper.loop(1000L, callback)
71     timeMachine.advanceBy(1000L)
72     timeMachine.triggerListeners()
73     verify(exactly = 1) { callback() }
74   }
75 
76   @Test
callback invoked twice after two timeoutsnull77   fun `callback invoked twice after two timeouts`() {
78     looper.loop(1000, callback)
79     timeMachine.advanceBy(1100)
80     timeMachine.triggerListeners()
81     timeMachine.advanceBy(1100)
82     timeMachine.triggerListeners()
83     verify(exactly = 2) { callback() }
84   }
85 
86   @Test
callback invoked once after twice too big timeoutnull87   fun `callback invoked once after twice too big timeout`() {
88     looper.loop(1000, callback)
89     timeMachine.advanceBy(2200)
90     timeMachine.triggerListeners(2200)
91     verify(exactly = 1) { callback() }
92   }
93 
94   @Test
callback not invoked after looping stoppednull95   fun `callback not invoked after looping stopped`() {
96     looper.loop(1000L, callback)
97     timeMachine.advanceBy(1001)
98     timeMachine.triggerListeners()
99     verify(exactly = 1) { callback() }
100     looper.stopLooping()
101     timeMachine.advanceBy(1001)
102     timeMachine.triggerListeners()
103     verify(exactly = 1) { callback() }
104   }
105 
106   @Test
callback not invoked earlier if interval shortenednull107   fun `callback not invoked earlier if interval shortened`() {
108     looper.loop(1000L, callback)
109 
110     looper.loop(100, callback)
111     timeMachine.advanceBy(110)
112     timeMachine.triggerListeners(110)
113     verify(exactly = 0) { callback() }
114 
115     timeMachine.advanceBy(900)
116     timeMachine.triggerListeners(1010)
117     verify(exactly = 1) { callback() }
118 
119     timeMachine.advanceBy(100)
120     timeMachine.triggerListeners(1110)
121     verify(exactly = 2) { callback() }
122   }
123 
124   @Test
callback invoked earlier even if interval lengthenednull125   fun `callback invoked earlier even if interval lengthened`() {
126     looper.loop(1000, callback)
127 
128     looper.loop(2000, callback)
129     timeMachine.advanceBy(1100)
130     timeMachine.triggerListeners(1100)
131 
132     verify(exactly = 1) { callback() }
133   }
134 
135   @Test
callback not invoked later even if interval lengthenednull136   fun `callback not invoked later even if interval lengthened`() {
137     looper.loop(1000, callback)
138 
139     looper.loop(2000, callback)
140     timeMachine.advanceBy(1110)
141     timeMachine.triggerListeners(1100)
142 
143     verify(exactly = 1) { callback() }
144 
145     timeMachine.advanceBy(1110)
146     timeMachine.triggerListeners(2200)
147 
148     verify(exactly = 1) { callback() }
149   }
150 
151   @Test
next tick scheduled with adjustment to passed time when invoked too latenull152   fun `next tick scheduled with adjustment to passed time when invoked too late`() {
153     looper.loop(1000L, callback)
154 
155     timeMachine.advanceBy(1100)
156     timeMachine.triggerListeners(1100)
157 
158     verify(exactly = 1) { timeMachine.scheduleAt(900, any()) }
159   }
160 
161   @Test
next tick scheduled with adjustment to passed time when invoked too earlynull162   fun `next tick scheduled with adjustment to passed time when invoked too early`() {
163     looper.loop(1000L, callback)
164 
165     timeMachine.advanceBy(900)
166     timeMachine.triggerListeners()
167 
168     verify(exactly = 1) { timeMachine.scheduleAt(1100, any()) }
169   }
170 
171   @Test
old listener not notified after new is registerednull172   fun `old listener not notified after new is registered`() {
173     looper.loop(1000, callback)
174 
175     timeMachine.advanceBy(1100)
176     timeMachine.triggerListeners()
177     verify(exactly = 1) { callback() }
178 
179     looper.setListener { }
180     timeMachine.advanceBy(1100)
181     timeMachine.triggerListeners()
182 
183     verify(exactly = 1) { callback() }
184   }
185 
186   @Test
new listener is notified after registrationnull187   fun `new listener is notified after registration`() {
188     looper.loop(1000) { }
189 
190     timeMachine.advanceBy(1100)
191     timeMachine.triggerListeners()
192     looper.setListener(callback)
193     timeMachine.advanceBy(1100)
194     timeMachine.triggerListeners()
195 
196     verify(exactly = 1) { callback() }
197   }
198 
199   @Test
time machine not called if no looping startednull200   fun `time machine not called if no looping started`() {
201     timeMachine.advanceBy(1100)
202     timeMachine.triggerListeners()
203 
204     verify(exactly = 0) { timeMachine.scheduleAt(any(), any()) }
205   }
206 
207   @Test
time machine not called after looping stoppednull208   fun `time machine not called after looping stopped`() {
209     looper.loop(1000) {}
210     verify(exactly = 1) { timeMachine.scheduleAt(any(), any()) }
211 
212     timeMachine.advanceBy(1100)
213     timeMachine.triggerListeners()
214     verify(exactly = 2) { timeMachine.scheduleAt(any(), any()) }
215 
216     looper.stopLooping()
217     timeMachine.advanceBy(100000)
218     timeMachine.triggerListeners()
219     verify(exactly = 2) { timeMachine.scheduleAt(any(), any()) }
220   }
221 }
222