Skip to content

Commit 9dcda8b

Browse files
committed
test: expand instrumentation flow coverage
1 parent 658b1ba commit 9dcda8b

7 files changed

Lines changed: 204 additions & 1 deletion

File tree

app/src/androidTest/java/com/kyhsgeekcode/disassembler/MainActivityActionViewProjectArchiveIntentFlowTest.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,18 @@ class MainActivityActionViewProjectArchiveIntentFlowTest {
4848
assertEquals("ArchiveFixture", ProjectManager.currentProject?.name)
4949
assertTrue(ProjectManager.currentProject?.sourceFilePath?.endsWith("sourceFilePath") == true)
5050
}
51+
52+
@Test
53+
fun actionViewProjectArchive_survivesRecreate() {
54+
composeRule.waitUntil(timeoutMillis = PROJECT_OPEN_TIMEOUT_MS) {
55+
ProjectManager.currentProject?.name == "ArchiveFixture"
56+
}
57+
58+
composeRule.activityRule.scenario.recreate()
59+
60+
composeRule.waitUntil(timeoutMillis = PROJECT_OPEN_TIMEOUT_MS) {
61+
ProjectManager.currentProject?.name == "ArchiveFixture"
62+
}
63+
composeRule.onNodeWithTag(MainTestTags.EXPORT_PROJECT_BUTTON).assertExists()
64+
}
5165
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.kyhsgeekcode.disassembler
2+
3+
import android.content.ComponentName
4+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
5+
import androidx.compose.ui.test.onNodeWithTag
6+
import androidx.compose.ui.test.performClick
7+
import androidx.test.espresso.intent.Intents.intending
8+
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
9+
import androidx.test.ext.junit.runners.AndroidJUnit4
10+
import com.kyhsgeekcode.disassembler.ui.MainTestTags
11+
import com.kyhsgeekcode.filechooser.NewFileChooserActivity
12+
import org.junit.Rule
13+
import org.junit.Test
14+
import org.junit.rules.RuleChain
15+
import org.junit.runner.RunWith
16+
17+
@RunWith(AndroidJUnit4::class)
18+
class MainActivityAdvancedImportCancelFlowTest {
19+
private val projectCleanupRule = ProjectStateCleanupRule()
20+
private val preferenceRule = PowerUserModePreferenceRule(powerUserModeEnabled = true)
21+
private val intentsRule = InstrumentationIntentsRule()
22+
private val composeRule = createAndroidComposeRule<MainActivity>()
23+
24+
@get:Rule
25+
val rules: RuleChain = RuleChain.outerRule(projectCleanupRule)
26+
.around(preferenceRule)
27+
.around(intentsRule)
28+
.around(composeRule)
29+
30+
@Test
31+
fun advancedImportCancel_keepsPowerUserEntryPointsVisible() {
32+
intending(
33+
hasComponent(
34+
ComponentName(
35+
composeRule.activity,
36+
NewFileChooserActivity::class.java
37+
)
38+
)
39+
).respondWith(createCanceledActivityResult())
40+
41+
composeRule.onNodeWithTag(MainTestTags.IMPORT_ADVANCED_BUTTON).performClick()
42+
composeRule.waitForIdle()
43+
44+
composeRule.onNodeWithTag(MainTestTags.IMPORT_SAF_BUTTON).assertExists()
45+
composeRule.onNodeWithTag(MainTestTags.IMPORT_ADVANCED_BUTTON).assertExists()
46+
}
47+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.kyhsgeekcode.disassembler
2+
3+
import android.content.Intent
4+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
5+
import androidx.compose.ui.test.onAllNodesWithTag
6+
import androidx.compose.ui.test.onNodeWithTag
7+
import androidx.compose.ui.test.performClick
8+
import androidx.test.espresso.intent.Intents.intending
9+
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
10+
import androidx.test.ext.junit.runners.AndroidJUnit4
11+
import com.kyhsgeekcode.disassembler.ui.MainTestTags
12+
import org.hamcrest.CoreMatchers.allOf
13+
import org.junit.Assert.assertTrue
14+
import org.junit.Rule
15+
import org.junit.Test
16+
import org.junit.rules.RuleChain
17+
import org.junit.runner.RunWith
18+
19+
@RunWith(AndroidJUnit4::class)
20+
class MainActivityBinaryDetailExportFlowTest {
21+
private val projectCleanupRule = ProjectStateCleanupRule()
22+
private val preferenceRule = PowerUserModePreferenceRule(powerUserModeEnabled = false)
23+
private val intentsRule = InstrumentationIntentsRule()
24+
private val composeRule = createAndroidComposeRule<MainActivity>()
25+
26+
@get:Rule
27+
val rules: RuleChain = RuleChain.outerRule(projectCleanupRule)
28+
.around(preferenceRule)
29+
.around(intentsRule)
30+
.around(composeRule)
31+
32+
@Test
33+
fun saveDetailsResult_writesTextDocument() {
34+
stubSafImport("detail-export-source.apk")
35+
val (outputFile, createDocumentResult) = createCreateDocumentResult("binary-details.txt")
36+
intending(allOf(hasAction(Intent.ACTION_CREATE_DOCUMENT))).respondWith(createDocumentResult)
37+
38+
openProjectAndDetailTab()
39+
composeRule.onNodeWithTag(MainTestTags.SAVE_DETAILS_BUTTON).performClick()
40+
41+
composeRule.waitUntil(timeoutMillis = 5_000) {
42+
outputFile.length() > 0L
43+
}
44+
45+
assertTrue(outputFile.readText().contains("File Size:"))
46+
}
47+
48+
@Test
49+
fun saveDetailsCancel_keepsProjectOpen() {
50+
stubSafImport("detail-export-cancel-source.apk")
51+
intending(allOf(hasAction(Intent.ACTION_CREATE_DOCUMENT))).respondWith(createCanceledActivityResult())
52+
53+
openProjectAndDetailTab()
54+
composeRule.onNodeWithTag(MainTestTags.SAVE_DETAILS_BUTTON).performClick()
55+
composeRule.waitForIdle()
56+
57+
composeRule.onNodeWithTag(MainTestTags.SAVE_DETAILS_BUTTON).assertExists()
58+
composeRule.onNodeWithTag(MainTestTags.EXPORT_PROJECT_BUTTON).assertExists()
59+
}
60+
61+
private fun stubSafImport(displayName: String) {
62+
intending(
63+
allOf(
64+
hasAction(Intent.ACTION_OPEN_DOCUMENT)
65+
)
66+
).respondWith(
67+
createOpenDocumentResult(
68+
displayName = displayName,
69+
content = "apk-content".encodeToByteArray()
70+
)
71+
)
72+
}
73+
74+
private fun openProjectAndDetailTab() {
75+
composeRule.onNodeWithTag(MainTestTags.IMPORT_SAF_BUTTON).performClick()
76+
composeRule.waitUntil(timeoutMillis = 5_000) {
77+
composeRule.onAllNodesWithTag(MainTestTags.EXPORT_PROJECT_BUTTON)
78+
.fetchSemanticsNodes().isNotEmpty()
79+
}
80+
composeRule.onNodeWithTag(MainTestTags.BINARY_TAB_DETAIL).performClick()
81+
composeRule.onNodeWithTag(MainTestTags.SAVE_DETAILS_BUTTON).assertExists()
82+
}
83+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.kyhsgeekcode.disassembler
2+
3+
import android.content.Intent
4+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
5+
import androidx.compose.ui.test.onNodeWithTag
6+
import androidx.compose.ui.test.performClick
7+
import androidx.test.espresso.intent.Intents.intending
8+
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
9+
import androidx.test.ext.junit.runners.AndroidJUnit4
10+
import com.kyhsgeekcode.disassembler.ui.MainTestTags
11+
import org.hamcrest.CoreMatchers.allOf
12+
import org.junit.Rule
13+
import org.junit.Test
14+
import org.junit.rules.RuleChain
15+
import org.junit.runner.RunWith
16+
17+
@RunWith(AndroidJUnit4::class)
18+
class MainActivitySafImportCancelFlowTest {
19+
private val projectCleanupRule = ProjectStateCleanupRule()
20+
private val preferenceRule = PowerUserModePreferenceRule(powerUserModeEnabled = false)
21+
private val intentsRule = InstrumentationIntentsRule()
22+
private val composeRule = createAndroidComposeRule<MainActivity>()
23+
24+
@get:Rule
25+
val rules: RuleChain = RuleChain.outerRule(projectCleanupRule)
26+
.around(preferenceRule)
27+
.around(intentsRule)
28+
.around(composeRule)
29+
30+
@Test
31+
fun safImportCancel_keepsStandardEntryPointVisible() {
32+
intending(
33+
allOf(
34+
hasAction(Intent.ACTION_OPEN_DOCUMENT)
35+
)
36+
).respondWith(createCanceledActivityResult())
37+
38+
composeRule.onNodeWithTag(MainTestTags.IMPORT_SAF_BUTTON).performClick()
39+
composeRule.waitForIdle()
40+
41+
composeRule.onNodeWithTag(MainTestTags.IMPORT_SAF_BUTTON).assertExists()
42+
composeRule.onNodeWithTag(MainTestTags.IMPORT_ADVANCED_BUTTON).assertDoesNotExist()
43+
}
44+
}

app/src/main/java/com/kyhsgeekcode/disassembler/ui/MainTestTags.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ object MainTestTags {
66
const val EXPORT_PROJECT_BUTTON = "export_project_button"
77
const val COPY_DIALOG_YES_BUTTON = "copy_dialog_yes_button"
88
const val COPY_DIALOG_NO_BUTTON = "copy_dialog_no_button"
9+
const val BINARY_TAB_DETAIL = "binary_tab_detail"
10+
const val SAVE_DETAILS_BUTTON = "save_details_button"
911
}

app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailTab.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import androidx.compose.runtime.Composable
1414
import androidx.compose.runtime.remember
1515
import androidx.compose.ui.Modifier
1616
import androidx.compose.ui.platform.LocalContext
17+
import androidx.compose.ui.platform.testTag
1718
import androidx.compose.ui.res.stringResource
1819
import androidx.compose.ui.unit.dp
1920
import com.kyhsgeekcode.disassembler.R
2021
import com.kyhsgeekcode.disassembler.exporting.buildBinaryDetailsExportFileName
2122
import com.kyhsgeekcode.disassembler.exporting.writeTextDocument
2223
import com.kyhsgeekcode.disassembler.files.AbstractFile
24+
import com.kyhsgeekcode.disassembler.ui.MainTestTags
2325
import kotlinx.coroutines.Dispatchers
2426
import kotlinx.coroutines.launch
2527
import kotlinx.coroutines.withContext
@@ -82,7 +84,9 @@ fun BinaryDetailTabContent(data: AbstractFile) {
8284
.padding(10.dp)
8385
) {
8486
Button(
85-
modifier = Modifier.padding(bottom = 12.dp),
87+
modifier = Modifier
88+
.padding(bottom = 12.dp)
89+
.testTag(MainTestTags.SAVE_DETAILS_BUTTON),
8690
onClick = {
8791
exportDetailsLauncher.launch(buildBinaryDetailsExportFileName(data.path))
8892
}

app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryTab.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.runtime.mutableStateOf
1313
import androidx.compose.runtime.remember
1414
import androidx.compose.runtime.setValue
1515
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.platform.testTag
1617
import androidx.compose.ui.res.stringResource
1718
import com.kyhsgeekcode.disassembler.MainActivity
1819
import com.kyhsgeekcode.disassembler.R
@@ -225,6 +226,7 @@ fun OpenedBinaryTabs(data: BinaryTabData, viewModel: MainViewModel) {
225226
) {
226227
titles.forEachIndexed { index, title ->
227228
Tab(
229+
modifier = Modifier.testTag(binaryTabTestTag(tabs.value[index].tabKind)),
228230
text = { Text(title) },
229231
selected = currentTabIndex.value == index,
230232
onClick = { data.setCurrentTabByIndex(index) }
@@ -235,6 +237,13 @@ fun OpenedBinaryTabs(data: BinaryTabData, viewModel: MainViewModel) {
235237
}
236238
}
237239

240+
private fun binaryTabTestTag(tabKind: BinaryTabKind): String {
241+
return when (tabKind) {
242+
is BinaryTabKind.BinaryDetail -> com.kyhsgeekcode.disassembler.ui.MainTestTags.BINARY_TAB_DETAIL
243+
else -> "binary_tab_${tabKind::class.simpleName}"
244+
}
245+
}
246+
238247
@ExperimentalFoundationApi
239248
@Composable
240249
fun BinaryTabContent(state: Int, data: BinaryTabData, viewModel: MainViewModel) {

0 commit comments

Comments
 (0)