Skip to content

Commit d5bafd0

Browse files
Merge pull request #288 from square/zachklipp/test-on-api-21
Start running UI tests on API 21.
2 parents f8d0105 + 882dfcd commit d5bafd0

File tree

9 files changed

+66
-32
lines changed

9 files changed

+66
-32
lines changed

.github/workflows/kotlin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ jobs:
6363
fail-fast: false
6464
matrix:
6565
api-level:
66-
# Tests are failing on APIs <24.
66+
- 21
6767
- 24
6868
- 29
6969
steps:

samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/Component.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ class Component(context: AppCompatActivity) {
5858
val timeMachineWorkflow = TimeMachineAppWorkflow(appWorkflow, clock, context)
5959

6060
val timeMachineModelFactory = TimeMachineModel.Factory(
61-
context, timeMachineWorkflow, context.getExternalFilesDir(null)!!
61+
context, timeMachineWorkflow, traceFilesDir = context.filesDir
6262
)
6363
}

samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/TimeMachineModel.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import java.io.File
1414
class TimeMachineModel(
1515
private val savedState: SavedStateHandle,
1616
private val workflow: TimeMachineAppWorkflow,
17-
private val externalFilesDir: File
17+
private val traceFilesDir: File
1818
) : ViewModel() {
1919
val renderings: StateFlow<Any> by lazy {
20-
val traceFile = externalFilesDir.resolve("workflow-trace-dungeon.json")
20+
val traceFile = traceFilesDir.resolve("workflow-trace-dungeon.json")
2121

2222
@OptIn(WorkflowUiExperimentalApi::class)
2323
renderWorkflowIn(
@@ -32,7 +32,7 @@ class TimeMachineModel(
3232
class Factory(
3333
owner: SavedStateRegistryOwner,
3434
private val workflow: TimeMachineAppWorkflow,
35-
private val externalFilesDir: File
35+
private val traceFilesDir: File
3636
) : AbstractSavedStateViewModelFactory(owner, null) {
3737
override fun <T : ViewModel> create(
3838
key: String,
@@ -41,7 +41,7 @@ class TimeMachineModel(
4141
): T {
4242
if (modelClass == TimeMachineModel::class.java) {
4343
@Suppress("UNCHECKED_CAST")
44-
return TimeMachineModel(handle, workflow, externalFilesDir) as T
44+
return TimeMachineModel(handle, workflow, traceFilesDir) as T
4545
}
4646

4747
throw IllegalArgumentException("Unknown ViewModel type $modelClass")

samples/tictactoe/app/src/main/java/com/squareup/sample/mainactivity/TicTacToeComponent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class TicTacToeComponent : ViewModel() {
5555
private val ticTacToeWorkflow = TicTacToeWorkflow(authWorkflow(), gameWorkflow())
5656

5757
fun ticTacToeModelFactory(owner: AppCompatActivity): TicTacToeModel.Factory =
58-
TicTacToeModel.Factory(owner, ticTacToeWorkflow, owner.getExternalFilesDir(null)!!)
58+
TicTacToeModel.Factory(owner, ticTacToeWorkflow, traceFilesDir = owner.filesDir)
5959

6060
companion object {
6161
init {

samples/tictactoe/app/src/main/java/com/squareup/sample/mainactivity/TicTacToeModel.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import java.io.File
1818
class TicTacToeModel(
1919
private val savedState: SavedStateHandle,
2020
private val workflow: TicTacToeWorkflow,
21-
private val externalFilesDir: File
21+
private val traceFilesDir: File
2222
) : ViewModel() {
2323
private val running = Job()
2424

2525
@OptIn(WorkflowUiExperimentalApi::class)
2626
val renderings: StateFlow<Any> by lazy {
27-
val traceFile = externalFilesDir.resolve("workflow-trace-tictactoe.json")
27+
val traceFile = traceFilesDir.resolve("workflow-trace-tictactoe.json")
2828

2929
renderWorkflowIn(
3030
workflow = workflow,
@@ -46,7 +46,7 @@ class TicTacToeModel(
4646
class Factory(
4747
owner: SavedStateRegistryOwner,
4848
private val workflow: TicTacToeWorkflow,
49-
private val externalFilesDir: File
49+
private val traceFilesDir: File
5050
) : AbstractSavedStateViewModelFactory(owner, null) {
5151
override fun <T : ViewModel> create(
5252
key: String,
@@ -55,7 +55,7 @@ class TicTacToeModel(
5555
): T {
5656
if (modelClass == TicTacToeModel::class.java) {
5757
@Suppress("UNCHECKED_CAST")
58-
return TicTacToeModel(handle, workflow, externalFilesDir) as T
58+
return TicTacToeModel(handle, workflow, traceFilesDir) as T
5959
}
6060

6161
throw IllegalArgumentException("Unknown ViewModel type $modelClass")

samples/todo-android/app/src/main/java/com/squareup/sample/mainactivity/ToDoActivity.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ToDoActivity : AppCompatActivity() {
2828

2929
setContentView(
3030
WorkflowLayout(this).apply {
31-
start(model.ensureWorkflow(getExternalFilesDir(null)!!), viewRegistry)
31+
start(model.ensureWorkflow(traceFilesDir = filesDir), viewRegistry)
3232
}
3333
)
3434
}
@@ -48,9 +48,9 @@ class ToDoModel(private val savedState: SavedStateHandle) : ViewModel() {
4848
private var renderings: StateFlow<Any>? = null
4949

5050
@OptIn(WorkflowUiExperimentalApi::class)
51-
fun ensureWorkflow(externalFilesDir: File): StateFlow<Any> {
51+
fun ensureWorkflow(traceFilesDir: File): StateFlow<Any> {
5252
if (renderings == null) {
53-
val traceFile = externalFilesDir.resolve("workflow-trace-todo.json")
53+
val traceFile = traceFilesDir.resolve("workflow-trace-todo.json")
5454

5555
renderings = renderWorkflowIn(
5656
workflow = TodoListsAppWorkflow,

workflow-ui/backstack-android/src/androidTest/java/com/squareup/workflow1/ui/backstack/test/BackstackContainerTest.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.squareup.workflow1.ui.backstack.test
22

33
import android.view.View
4+
import androidx.lifecycle.Lifecycle.State.CREATED
45
import androidx.lifecycle.Lifecycle.State.RESUMED
56
import androidx.lifecycle.Lifecycle.State.STARTED
67
import androidx.test.ext.junit.rules.ActivityScenarioRule
@@ -166,7 +167,14 @@ internal class BackstackContainerTest {
166167
// endregion
167168
// region Lifecycle tests
168169

169-
@Test fun lifecycle_pause_then_resume() {
170+
/**
171+
* We test stop instead of pause because on older Android versions (e.g. level 21),
172+
* `moveToState(STARTED)` will also stop the lifecycle, not just pause it. By just using stopped,
173+
* which is consistent across all the versions we care about, we don't need to special-case our
174+
* assertions, but we're still testing fundamentally the same thing (moving between non-terminal
175+
* lifecycle states).
176+
*/
177+
@Test fun lifecycle_stop_then_resume() {
170178
assertThat(scenario.state).isEqualTo(RESUMED)
171179
scenario.onActivity {
172180
it.update(LeafRendering("initial"))
@@ -184,19 +192,23 @@ internal class BackstackContainerTest {
184192
).inOrder()
185193
}
186194

187-
scenario.moveToState(STARTED)
195+
scenario.moveToState(CREATED)
188196

189197
scenario.onActivity {
190-
assertThat(it.consumeLifecycleEvents()).containsExactly(
198+
assertThat(it.consumeLifecycleEvents()).containsAtLeast(
191199
"LeafView initial ON_PAUSE",
192200
"activity onPause",
193-
)
201+
"LeafView initial ON_STOP",
202+
"activity onStop",
203+
).inOrder()
194204
}
195205

196206
scenario.moveToState(RESUMED)
197207

198208
scenario.onActivity {
199209
assertThat(it.consumeLifecycleEvents()).containsExactly(
210+
"activity onStart",
211+
"LeafView initial ON_START",
200212
"activity onResume",
201213
"LeafView initial ON_RESUME",
202214
)

workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleTest.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.squareup.workflow1.ui
22

3+
import androidx.lifecycle.Lifecycle.State.CREATED
34
import androidx.lifecycle.Lifecycle.State.RESUMED
4-
import androidx.lifecycle.Lifecycle.State.STARTED
55
import androidx.lifecycle.LifecycleOwner
66
import androidx.test.ext.junit.rules.ActivityScenarioRule
77
import com.google.common.truth.Truth.assertThat
@@ -19,7 +19,14 @@ internal class WorkflowViewStubLifecycleTest {
1919
ActivityScenarioRule(WorkflowViewStubLifecycleActivity::class.java)
2020
private val scenario get() = scenarioRule.scenario
2121

22-
@Test fun pause_then_resume() {
22+
/**
23+
* We test stop instead of pause because on older Android versions (e.g. level 21),
24+
* `moveToState(STARTED)` will also stop the lifecycle, not just pause it. By just using stopped,
25+
* which is consistent across all the versions we care about, we don't need to special-case our
26+
* assertions, but we're still testing fundamentally the same thing (moving between non-terminal
27+
* lifecycle states).
28+
*/
29+
@Test fun stop_then_resume() {
2330
assertThat(scenario.state).isEqualTo(RESUMED)
2431
scenario.onActivity {
2532
assertThat(it.consumeLifecycleEvents()).containsExactly(
@@ -33,20 +40,24 @@ internal class WorkflowViewStubLifecycleTest {
3340
)
3441
}
3542

36-
scenario.moveToState(STARTED)
43+
scenario.moveToState(CREATED)
3744

3845
scenario.onActivity {
3946
assertThat(it.consumeLifecycleEvents()).containsExactly(
4047
"LeafView initial ON_PAUSE",
4148
"activity onPause",
49+
"LeafView initial ON_STOP",
50+
"activity onStop",
4251
)
4352
}
4453

4554
scenario.moveToState(RESUMED)
4655

4756
scenario.onActivity {
4857
assertThat(it.consumeLifecycleEvents()).containsExactly(
58+
"activity onStart",
4959
"activity onResume",
60+
"LeafView initial ON_START",
5061
"LeafView initial ON_RESUME",
5162
)
5263
}
@@ -124,7 +135,7 @@ internal class WorkflowViewStubLifecycleTest {
124135
}
125136
}
126137

127-
@Test fun replace_after_pause() {
138+
@Test fun replace_after_stop() {
128139
assertThat(scenario.state).isEqualTo(RESUMED)
129140
scenario.onActivity {
130141
assertThat(it.consumeLifecycleEvents()).containsExactly(
@@ -138,20 +149,20 @@ internal class WorkflowViewStubLifecycleTest {
138149
)
139150
}
140151

141-
scenario.moveToState(STARTED)
152+
scenario.moveToState(CREATED)
142153

143154
scenario.onActivity {
144155
it.update(LeafRendering("next"))
145156

146157
assertThat(it.consumeLifecycleEvents()).containsExactly(
147158
"LeafView initial ON_PAUSE",
148159
"activity onPause",
149-
"LeafView initial onDetached",
150160
"LeafView initial ON_STOP",
161+
"activity onStop",
162+
"LeafView initial onDetached",
151163
"LeafView initial ON_DESTROY",
152164
"LeafView next onAttached",
153165
"LeafView next ON_CREATE",
154-
"LeafView next ON_START",
155166
)
156167
}
157168
}

workflow-ui/modal-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleTest.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.squareup.workflow1.ui.modal.test
22

3+
import androidx.lifecycle.Lifecycle.State.CREATED
34
import androidx.lifecycle.Lifecycle.State.RESUMED
4-
import androidx.lifecycle.Lifecycle.State.STARTED
55
import androidx.lifecycle.LifecycleOwner
66
import androidx.test.ext.junit.rules.ActivityScenarioRule
77
import com.google.common.truth.Truth.assertThat
@@ -20,7 +20,14 @@ internal class ModalViewContainerLifecycleTest {
2020
ActivityScenarioRule(ModalViewContainerLifecycleActivity::class.java)
2121
private val scenario get() = scenarioRule.scenario
2222

23-
@Test fun pause_then_resume() {
23+
/**
24+
* We test stop instead of pause because on older Android versions (e.g. level 21),
25+
* `moveToState(STARTED)` will also stop the lifecycle, not just pause it. By just using stopped,
26+
* which is consistent across all the versions we care about, we don't need to special-case our
27+
* assertions, but we're still testing fundamentally the same thing (moving between non-terminal
28+
* lifecycle states).
29+
*/
30+
@Test fun stop_then_resume() {
2431
assertThat(scenario.state).isEqualTo(RESUMED)
2532
scenario.onActivity {
2633
it.update(LeafRendering("initial"))
@@ -38,19 +45,23 @@ internal class ModalViewContainerLifecycleTest {
3845
)
3946
}
4047

41-
scenario.moveToState(STARTED)
48+
scenario.moveToState(CREATED)
4249

4350
scenario.onActivity {
4451
assertThat(it.consumeLifecycleEvents()).containsExactly(
4552
"LeafView initial ON_PAUSE",
4653
"activity onPause",
54+
"LeafView initial ON_STOP",
55+
"activity onStop",
4756
)
4857
}
4958

5059
scenario.moveToState(RESUMED)
5160

5261
scenario.onActivity {
5362
assertThat(it.consumeLifecycleEvents()).containsExactly(
63+
"activity onStart",
64+
"LeafView initial ON_START",
5465
"activity onResume",
5566
"LeafView initial ON_RESUME",
5667
)
@@ -180,7 +191,7 @@ internal class ModalViewContainerLifecycleTest {
180191
}
181192
}
182193

183-
@Test fun replace_after_pause() {
194+
@Test fun replace_after_stop() {
184195
assertThat(scenario.state).isEqualTo(RESUMED)
185196
scenario.onActivity {
186197
it.update(LeafRendering("initial"))
@@ -198,7 +209,7 @@ internal class ModalViewContainerLifecycleTest {
198209
)
199210
}
200211

201-
scenario.moveToState(STARTED)
212+
scenario.moveToState(CREATED)
202213

203214
scenario.onActivity {
204215
it.update(LeafRendering("next"))
@@ -208,12 +219,12 @@ internal class ModalViewContainerLifecycleTest {
208219
assertThat(it.consumeLifecycleEvents()).containsExactly(
209220
"LeafView initial ON_PAUSE",
210221
"activity onPause",
211-
"LeafView initial onDetached",
212222
"LeafView initial ON_STOP",
223+
"activity onStop",
224+
"LeafView initial onDetached",
213225
"LeafView initial ON_DESTROY",
214226
"LeafView next onAttached",
215227
"LeafView next ON_CREATE",
216-
"LeafView next ON_START",
217228
)
218229
}
219230
}

0 commit comments

Comments
 (0)