Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e847168
tests: add unit tests for MainActivityViewModel
superus8r Jul 20, 2025
73d0925
docs: document a TODO for fixing the unused value of hasPermission.
superus8r Jul 20, 2025
52a9918
tests: improve MainActivityViewModelTest with additional connection s…
superus8r Jul 20, 2025
3fbca75
nitpick: change openDeviceAndPort to private in MainActivityViewModel
superus8r Jul 20, 2025
17e4532
docs: add TODO comments for DROID-17 in MainActivityViewModel
superus8r Jul 20, 2025
745467c
tests: refactor MainActivityViewModelTest setup and cleanup methods
superus8r Jul 20, 2025
8e985a7
refactor: move USB device observation out of the init method
superus8r Jul 20, 2025
9416aa1
refactor(tests): use runTest block
superus8r Jul 20, 2025
0ed880c
tests: make sure connectIfAlreadyHasPermission works as expected
superus8r Jul 20, 2025
7538082
tests: make sure startObservingUsbDevice works as expected
superus8r Jul 20, 2025
1416d96
tests: make sure startObservingUsbDevice works as expected when the d…
superus8r Jul 20, 2025
2e7737a
tests(nitpick): standardize comment casing to match the rest of the t…
superus8r Jul 20, 2025
2263ad6
tests: make sure disconnect and serialWrite methods work as expected
superus8r Jul 20, 2025
8cf9bfc
nitpick: reformat the file
superus8r Jul 20, 2025
24dab40
nitpick: reformat MainActivity; no changes
superus8r Jul 20, 2025
d0c7087
nitpick(refactor): avoid wildcard imports
superus8r Aug 7, 2025
06b4eff
chore: hopefully fix jacoco
superus8r Aug 7, 2025
ce5e28d
chore: exclude MainActivity file from jacoco because it was being sho…
superus8r Aug 7, 2025
cc2197c
tests: make sure all branches are covered for getArduinoType
superus8r Aug 7, 2025
6bab0ea
tests: avoid using mockkstatic and return vendorId and productId
superus8r Aug 7, 2025
3aa15b9
tests: avoid using mockkstatic and return vendorId and productId
superus8r Aug 7, 2025
4dce806
nitpick: fix formatting to match ktlint rules
superus8r Aug 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import java.io.FileInputStream
import java.util.*
import java.util.Base64
import java.util.Properties

plugins {
id("com.android.application")
Expand Down Expand Up @@ -110,6 +111,35 @@ tasks.register<JacocoReport>("jacocoTestReport") {
csv.required.set(false)
}

val fileFilter = listOf(
"**/R.class",
"**/MainActivity.*",
"**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*",
"**/*Test*.*", "android/**/*.*",
"**/Dagger*.*", "**/*_Hilt*.*", "**/*Hilt*.*",
)
val kotlinDebugTree = fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")) { exclude(fileFilter) }
val mainKotlinSrc = layout.projectDirectory.dir("src/main/kotlin")
sourceDirectories.from(files(mainKotlinSrc))
classDirectories.from(files(kotlinDebugTree))
executionData.from(fileTree(layout.buildDirectory) {
include(
"outputs/managed_device_code_coverage/**/*.ec",
"outputs/unit_test_code_coverage/**/*.exec",
)
})
}

tasks.register<JacocoReport>("jacocoUiOnly") {

dependsOn("pixel2api30DebugAndroidTest")

reports {
xml.required.set(true)
html.required.set(true)
csv.required.set(false)
}

val fileFilter = listOf(
"**/R.class", "**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*",
"**/*Test*.*", "android/**/*.*",
Expand All @@ -123,7 +153,6 @@ tasks.register<JacocoReport>("jacocoTestReport") {
classDirectories.from(files(javaDebugTree, kotlinDebugTree))
executionData.from(fileTree(layout.buildDirectory) {
include(
"outputs/unit_test_code_coverage/**/*.exec",
"outputs/managed_device_code_coverage/**/*.ec",
"outputs/managed_device_code_coverage/**/*.exec"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith


@RunWith(AndroidJUnit4::class)
internal class MainActivityAndroidTest {

@get:Rule
var rule = activityScenarioRule<MainActivity>()

private fun ensureMenuIsAccessible(
menuItemId: Int,
onVisible: () -> Unit,
onOverflow: () -> Unit
onOverflow: () -> Unit,
) {
try {
// Try to find the menu item first
Expand All @@ -49,57 +47,53 @@ internal class MainActivityAndroidTest {
}

@Test
fun checkActionMenuItemSettingsIsDisplayed() = ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {

// assert
onView(withId(R.id.actionSettings)).check(matches(isDisplayed()))
},
onOverflow = {

// assert
onView(withText(R.string.title_settings)).check(matches(isDisplayed()))
}
)
fun checkActionMenuItemSettingsIsDisplayed() =
ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {
// assert
onView(withId(R.id.actionSettings)).check(matches(isDisplayed()))
},
onOverflow = {
// assert
onView(withText(R.string.title_settings)).check(matches(isDisplayed()))
},
)

@Test
fun checkActionMenuItemConnectIsDisplayed() = ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {

// assert
onView(withId(R.id.actionConnect)).check(matches(isDisplayed()))
},
onOverflow = {

// assert
onView(withText(R.string.title_connect)).check(matches(isDisplayed()))
}
)
fun checkActionMenuItemConnectIsDisplayed() =
ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {
// assert
onView(withId(R.id.actionConnect)).check(matches(isDisplayed()))
},
onOverflow = {
// assert
onView(withText(R.string.title_connect)).check(matches(isDisplayed()))
},
)

@Test
fun checkActionMenuItemDisconnectIsDisplayed() = ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {

// assert
onView(withId(R.id.actionDisconnect)).check(matches(isDisplayed()))
},
onOverflow = {

// assert
onView(withText(R.string.title_disconnect)).check(matches(isDisplayed()))
}
)
fun checkActionMenuItemDisconnectIsDisplayed() =
ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {
// assert
onView(withId(R.id.actionDisconnect)).check(matches(isDisplayed()))
},
onOverflow = {
// assert
onView(withText(R.string.title_disconnect)).check(matches(isDisplayed()))
},
)

@Test
fun clickingSettingsOpensSettingsBottomSheet() {
// arrange
ensureMenuIsAccessible(
menuItemId = R.id.actionSettings,
onVisible = {

// act
onView(withId(R.id.actionSettings)).perform(click())

Expand All @@ -112,7 +106,7 @@ internal class MainActivityAndroidTest {

// assert
onView(withId(R.id.composeViewSettingContent)).check(matches(isDisplayed()))
}
},
)
}
}
22 changes: 12 additions & 10 deletions app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,21 @@ import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.kabiri.android.usbterminal.util.scrollToLastLine
import org.kabiri.android.usbterminal.ui.setting.SettingModalBottomSheet
import org.kabiri.android.usbterminal.ui.setting.SettingViewModel
import org.kabiri.android.usbterminal.util.scrollToLastLine
import org.kabiri.android.usbterminal.viewmodel.MainActivityViewModel

private const val TAG = "MainActivity"

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

companion object {
private const val TAG = "MainActivity"
}

private val viewModel by viewModels<MainActivityViewModel>()
private val settingViewModel by viewModels<SettingViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.startObservingUsbDevice()
setContentView(R.layout.activity_main)

// avoid system navbar or soft keyboard overlapping the content.
Expand Down Expand Up @@ -67,9 +65,11 @@ class MainActivity : AppCompatActivity() {
fun sendCommand() {
val input = etInput.text.toString()
// append the input to console
if (viewModel.serialWrite(input))
etInput.setText("") // clear the terminal input.
else Log.e(TAG, "The message was not sent to Arduino")
if (viewModel.serialWrite(input)) {
etInput.setText("")
} else {
Log.e(TAG, "The message was not sent to Arduino")
}
}

// send the command to device when the button is clicked.
Expand All @@ -83,7 +83,9 @@ class MainActivity : AppCompatActivity() {
event.action == KeyEvent.ACTION_DOWN)) {
sendCommand()
true
} else false
} else {
false
}
}
}

Expand Down
Loading