Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ commands:
- restore_cache:
key: v1-gradle-wrapper-{{ arch }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}
- restore_cache:
key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle" }}-{{ checksum "settings.gradle" }}-{{ checksum "gradle.properties" }}-{{ checksum "app/build.gradle.kts" }}
key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle.kts" }}-{{ checksum "settings.gradle.kts" }}-{{ checksum "gradle.properties" }}-{{ checksum "app/build.gradle.kts" }}
restore_bundler_cache:
steps:
- restore_cache:
Expand All @@ -24,7 +24,7 @@ commands:
- save_cache:
paths:
- ~/.gradle/caches
key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle" }}-{{ checksum "settings.gradle" }}-{{ checksum "gradle.properties" }}-{{ checksum "app/build.gradle.kts" }}
key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle.kts" }}-{{ checksum "settings.gradle.kts" }}-{{ checksum "gradle.properties" }}-{{ checksum "app/build.gradle.kts" }}
save_bundler_cache:
steps:
- save_cache:
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.OS }}-gradle-caches-cache-${{ hashFiles('build.gradle') }}
key: ${{ runner.OS }}-gradle-caches-cache-${{ hashFiles('build.gradle.kts') }}
restore-keys: |
${{ runner.OS }}-gradle-caches-cache-
- name: generate ksProp file
Expand Down Expand Up @@ -91,11 +91,21 @@ jobs:
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests
- name: run tests with screen record
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedCheck
script: |
adb shell screenrecord /sdcard/ui-test.mp4 &
SCREENRECORD_PID=$!
./gradlew connectedCheck || true
kill $SCREENRECORD_PID || true
adb pull /sdcard/ui-test.mp4 ./ui-test.mp4 || true
- name: Upload UI test video
uses: actions/upload-artifact@v4
with:
name: ui-test-video
path: ./ui-test.mp4

notify-slack:
needs: unit-test
Expand Down
112 changes: 50 additions & 62 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
named("debug") {
isTestCoverageEnabled = true
enableUnitTestCoverage = true
enableAndroidTestCoverage = true
}
}

Expand Down Expand Up @@ -94,8 +95,7 @@ android {
}

jacoco {
val jacoco_version: String by project
toolVersion = jacoco_version
toolVersion = libs.versions.jacoco.get()
reportsDirectory.set(layout.buildDirectory.dir("mergedReportDir"))
}

Expand Down Expand Up @@ -216,82 +216,70 @@ fun loadKeyStore(name: String): Properties? {
}
}

val firebase_bom_version: String by project
val hilt_version: String by project
val coroutines_version: String by project
val material_version: String by project
val mockk_version: String by project
dependencies {

implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.core:core-ktx:1.16.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
// AndroidX
implementation(libs.appcompat)
implementation(libs.core.ktx)
implementation(libs.constraintlayout)

// Firebase
implementation(platform("com.google.firebase:firebase-bom:$firebase_bom_version"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics.ktx)
implementation(libs.firebase.crashlytics.ktx)

// Dependency Injection
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-compiler:$hilt_version")
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)

// Coroutines
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.extensions)
implementation(libs.coroutines.core)
implementation(libs.coroutines.android)

// Compose Bom
val composeBom = platform("androidx.compose:compose-bom:2023.06.01")
val composeBom = platform(libs.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material3:material3")
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
// Compose - Android Studio Preview support
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.activity:activity-compose:1.10.1")
implementation(libs.compose.ui.tooling.preview)
debugImplementation(libs.compose.ui.tooling)
implementation(libs.activity.compose)

// Other UI Libraries
implementation("com.google.android.material:material:$material_version")

// data
implementation("androidx.datastore:datastore-preferences:1.1.4")

// unit test libs
testImplementation("junit:junit:4.13.2")

// instrumented test libs
androidTestImplementation("androidx.test:core:1.6.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
implementation(libs.material)

// Hamcrest for view matching
androidTestImplementation("org.hamcrest:hamcrest-library:2.2")
androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test:rules:1.6.1")
// Data
implementation(libs.datastore.preferences)

// coroutine testing
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
// Unit Test Libraries
testImplementation(libs.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.truth)
testImplementation(libs.mockk.android)
testImplementation(libs.mockk.agent)

// google truth for assertions
testImplementation("com.google.truth:truth:1.1.3")
androidTestImplementation("androidx.test.ext:truth:1.6.0")

// mockk
testImplementation("io.mockk:mockk-android:$mockk_version")
testImplementation("io.mockk:mockk-agent:$mockk_version")
androidTestImplementation("io.mockk:mockk-android:$mockk_version")
androidTestImplementation("io.mockk:mockk-agent:$mockk_version")

// hilt testing - https://developer.android.com/training/dependency-injection/hilt-testing
androidTestImplementation("com.google.dagger:hilt-android-testing:$hilt_version")
kaptAndroidTest("com.google.dagger:hilt-android-compiler:$hilt_version")
// Instrumented Test Libraries
androidTestImplementation(composeBom)
androidTestImplementation(libs.coroutines.test)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.ext.junit.ktx)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(libs.hamcrest)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.truth)
androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.mockk.agent)

// Hilt Testing
androidTestImplementation(libs.hilt.android.testing)
kaptAndroidTest(libs.hilt.android.compiler)

// Android Serial Controller
implementation("com.github.superus8r:UsbSerial:6.1.1")
implementation(libs.usb.serial)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.kabiri.android.usbterminal

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
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) {
try {
// Try to find the menu item first
onView(withId(menuItemId)).check(matches(isDisplayed()))
} catch (_: NoMatchingViewException) {
// If not found then open the overflow menu
openActionBarOverflowOrOptionsMenu(
InstrumentationRegistry.getInstrumentation().targetContext
)
}
}

@Test
fun checkUiViewsAreDisplayed() {
// arrange
// act
// assert
onView(withId(R.id.tvOutput)).check(matches(isDisplayed()))
onView(withId(R.id.btEnter)).check(matches(isDisplayed()))
onView(withId(R.id.etInput)).check(matches(isDisplayed()))
}

@Test
fun checkActionMenuItemsAreDisplayed() {
// arrange
// act
// Ensure action items are accessible, either in the toolbar or via overflow
ensureMenuIsAccessible(R.id.actionSettings)
ensureMenuIsAccessible(R.id.actionConnect)
ensureMenuIsAccessible(R.id.actionDisconnect)

// assert
// Check menu items are displayed
onView(withId(R.id.actionSettings)).check(matches(isDisplayed()))
onView(withId(R.id.actionConnect)).check(matches(isDisplayed()))
onView(withId(R.id.actionDisconnect)).check(matches(isDisplayed()))
}

@Test
fun clickingSettingsOpensSettingsBottomSheet() {
// arrange
// Ensure the action item is accisble, either in the toolbar or via overflow
ensureMenuIsAccessible(R.id.actionSettings)

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

// assert
onView(withId(R.id.composeViewSettingContent)).check(matches(isDisplayed()))
}
}

This file was deleted.

28 changes: 9 additions & 19 deletions build.gradle → build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
buildscript {
ext {
coroutines_version = "1.6.4"
firebase_bom_version = "32.8.0"
hilt_version = "2.56.2"
jacoco_version = "0.8.8"
kotlin_version = "2.1.20"
material_version = "1.12.0"
mockk_version = "1.14.2"
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "com.google.gms:google-services:4.4.1"
classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.9"
classpath(libs.hilt.android.gradle.plugin)
classpath(libs.google.services)
classpath(libs.firebase.crashlytics.gradle)
}
}

plugins {
id("com.android.application") version '8.10.0' apply false
id("org.jetbrains.kotlin.android") version "2.1.20" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.1.20"
id("org.sonarqube") version "3.5.0.2730"
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.sonarqube)
}

task clean(type: Delete) {
delete layout.buildDirectory
tasks.register<Delete>("clean") {
delete(layout.buildDirectory)
}

sonarqube {
Expand All @@ -35,7 +26,6 @@ sonarqube {
property("sonar.host.url", "https://sonarcloud.io")

property("sonar.binaries", project(":app").layout.buildDirectory.dir("tmp/kotlin-classes/debug").get().asFile.absolutePath)
// sonar requires absolute path for lint and jacoco reports!
property("sonar.androidLint.reportPaths", project(":app").layout.buildDirectory.dir("reports/lint-results-debug.xml").get().asFile.absolutePath)
property("sonar.coverage.jacoco.xmlReportPaths", project(":app").layout.buildDirectory.dir("mergedReportDir/jacocoTestReport/jacocoTestReport.xml").get().asFile.absolutePath)
}
Expand Down
Loading
Loading