Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2c88a4e
configure all tests to use junit4
DeleMike May 5, 2025
4f2ea7a
test(AboutUtil): add unit tests for AboutUtil composables and non-UI …
DeleMike May 5, 2025
8253514
add license header
DeleMike May 5, 2025
e2ca905
Merge remote-tracking branch 'origin/main' into feat/write-test-fns-f…
DeleMike May 17, 2025
e2a960e
Merge remote-tracking branch 'origin/main' into feat/write-test-fns-f…
DeleMike May 17, 2025
a4c4ff1
Merge remote-tracking branch 'origin/main' into feat/write-test-fns-f…
DeleMike May 26, 2025
876ab20
Merge remote-tracking branch 'origin/main' into feat/write-test-fns-f…
DeleMike May 31, 2025
57fe5d9
add AboutUtil Instrumentation tests
DeleMike Jun 9, 2025
aae6609
Merge branch 'main' into feat/write-test-fns-for-about-util
DeleMike Jun 9, 2025
a72268b
Merge branch 'main' into feat/write-test-fns-for-about-util
DeleMike Jun 10, 2025
f9b3b84
Merge branch 'main' into feat/write-test-fns-for-about-util
DeleMike Jun 11, 2025
9b8e3a8
Merge branch 'main' into feat/write-test-fns-for-about-util
DeleMike Jun 11, 2025
58a904e
Merge branch 'main' into feat/write-test-fns-for-about-util
DeleMike Jun 15, 2025
e3693d3
Merge remote-tracking branch 'origin/main' into feat/write-test-fns-f…
DeleMike Jun 22, 2025
3aa2ad2
fix gradle file
DeleMike Jun 22, 2025
4d6a720
update android CI/CD
DeleMike Jun 22, 2025
08b856a
fix sharehelper class
DeleMike Jun 22, 2025
9832e0e
fix class to follow kotlin standard formatting
DeleMike Jun 22, 2025
7ceb7cb
Fix NoActivityResumedException in onShareScribeClick test by launchin…
DeleMike Jun 22, 2025
9e1d2f7
docs: add KDoc for AboutUtilInstrumentedTest and its test methods
DeleMike Jun 22, 2025
e7dfa9f
chore:formating the build.gradle.kts
angrezichatterbox Jun 24, 2025
1416b72
chore:migrate from junit4 to junit4 for unit tests
angrezichatterbox Jun 24, 2025
1d490e9
Standardize comments in testing file
andrewtavis Jun 24, 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
51 changes: 38 additions & 13 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ plugins {
id("org.jmailen.kotlinter")
id("io.gitlab.arturbosch.detekt")
id("com.google.devtools.ksp") version "2.0.0-1.0.22" apply true
id("de.mannodermaus.android-junit5") version "1.11.2.0"
// id("de.mannodermaus.android-junit5") version "1.11.2.0"
id("org.jetbrains.kotlin.plugin.compose") version "2.0.0"
id("jacoco")
kotlin("plugin.serialization") version "1.9.0"
Expand All @@ -28,11 +28,11 @@ jacoco {
}

val kotlinVersion by extra("2.0.0")
val junit5Version by extra("5.11.2")
//val junit5Version by extra("5.11.2")
val mockkVersion by extra("1.13.13")

android {
compileSdk = 34
compileSdk = 35

defaultConfig {
applicationId = "be.scri"
Expand All @@ -45,6 +45,16 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

packaging {
resources {
pickFirsts.add("META-INF/LICENSE*")
pickFirsts.add("META-INF/ASL2.0")
pickFirsts.add("META-INF/NOTICE*")
pickFirsts.add("META-INF/LGPL2.1")

}
}


kover {
verify {
Expand All @@ -66,12 +76,12 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}

buildFeatures {
Expand Down Expand Up @@ -112,6 +122,9 @@ android {

sourceSets {
getByName("main").java.srcDirs("src/main/kotlin")
named("test") {
java.srcDirs("src/test/java", "src/test/kotlin")
}
}

lint {
Expand Down Expand Up @@ -230,12 +243,24 @@ dependencies {
implementation("androidx.navigation:navigation-compose:$2.8.4")

// Testing libraries
testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version")
// testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version")
testImplementation("io.mockk:mockk:$mockkVersion")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.13")
testImplementation("androidx.compose.ui:ui-test-junit4:1.8.0") // Compose UI Testing for unit tests
testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20")
testImplementation ("org.mockito:mockito-core:5.12.0")
testImplementation ("org.mockito:mockito-inline:5.2.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("com.google.truth:truth:1.4.4")
// testImplementation ("org.mockito:mockito-junit-jupiter:5.12.0")


// For Instrumentation Tests
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.7.5")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.8.0")
androidTestImplementation("io.mockk:mockk-android:$mockkVersion")
androidTestImplementation("com.google.truth:truth:1.4.4")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.5")

// Espresso for UI tests
Expand All @@ -247,8 +272,8 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-intents:3.6.1")

// JUnit 5 dependencies
testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version")
// testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version")
// testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version")

// AndroidJUnit4 is included
androidTestImplementation("androidx.test.ext:junit:1.2.1")
Expand Down Expand Up @@ -307,9 +332,9 @@ tasks.withType(Test::class) {
}
}

tasks.withType<Test> {
useJUnitPlatform()
}
//tasks.withType<Test> {
// useJUnitPlatform()
//}

tasks.register<JacocoReport>("jacocoTestReport") {
group = "Reporting"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package be.scri.ui.screens.about

import android.content.Intent
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtraWithKey
import androidx.test.espresso.intent.matcher.IntentMatchers.hasType
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import be.scri.R
import be.scri.activities.MainActivity
import be.scri.ui.models.ScribeItem
import com.google.common.truth.Truth.assertThat
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matchers.`is`
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AboutUtilInstrumentedTest {
@get:Rule
val composeTestRule = createComposeRule()

@Before
fun setup() {
Intents.init()
}

@After
fun tearDown() {
Intents.release()
}

@Test
fun test_onShareScribeClick_doesNotCrash() {
// Use the app context
val context = InstrumentationRegistry.getInstrumentation().targetContext

AboutUtil.onShareScribeClick(context)

Intents.intended(hasAction(Intent.ACTION_CHOOSER))
}

@Test
fun test_onRateScribeClick_doesNotCrash() {
val scenario = ActivityScenario.launch(MainActivity::class.java)
scenario.onActivity { activity ->
AboutUtil.onRateScribeClick(activity)
}
}

@Test
fun test_onMailClick_launchesEmailIntent() {
val context = InstrumentationRegistry.getInstrumentation().targetContext

AboutUtil.onMailClick(context)

Intents.intended(
allOf(
hasAction(Intent.ACTION_CHOOSER),
hasExtraWithKey(Intent.EXTRA_INTENT),
hasExtra(
`is`(Intent.EXTRA_INTENT),
allOf(
hasAction(Intent.ACTION_SEND),
hasType("message/rfc822"),
),
),
),
)
}

@Test
fun testGetCommunityList() {
println("Testing getCommunityList...")

var wikimediaClicked = false
var shareClicked = false
val context = InstrumentationRegistry.getInstrumentation().targetContext

composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
val communityList =
AboutUtil.getCommunityList(
onWikimediaAndScribeClick = { wikimediaClicked = true },
onShareScribeClick = { shareClicked = true },
context = context,
)

// Test list is not empty
assertThat(communityList.items).isNotEmpty()
assertThat(communityList.items).hasSize(5)

// Test each item has required fields
communityList.items.forEach { item ->
assertThat(item).isInstanceOf(ScribeItem.ExternalLinkItem::class.java)
val linkItem = item as ScribeItem.ExternalLinkItem

assertThat(linkItem.leadingIcon).isNotNull()
assertThat(linkItem.title).isNotNull()
assertThat(linkItem.trailingIcon).isNotNull()
assertThat(linkItem.onClick).isNotNull()
}

// Test specific items
val githubItem = communityList.items[0] as ScribeItem.ExternalLinkItem
assertThat(githubItem.leadingIcon).isEqualTo(R.drawable.github_logo)
assertThat(githubItem.title).isEqualTo(R.string.app_about_community_github)

val shareItem = communityList.items[3] as ScribeItem.ExternalLinkItem
assertThat(shareItem.leadingIcon).isEqualTo(R.drawable.share_icon)
assertThat(shareItem.title).isEqualTo(R.string.app_about_community_share_scribe)

val wikimediaItem = communityList.items[4] as ScribeItem.ExternalLinkItem
assertThat(wikimediaItem.leadingIcon).isEqualTo(R.drawable.wikimedia_logo_black)
assertThat(wikimediaItem.title).isEqualTo(R.string.app_about_community_wikimedia)

// Test onClick callbacks
shareItem.onClick()
wikimediaItem.onClick()
}
}

// Verify callbacks were triggered
assertThat(shareClicked).isTrue()
assertThat(wikimediaClicked).isTrue()

println("getCommunityList test passed!")
}

@Test
fun testGetFeedbackAndSupportList() {
println("Testing getFeedbackAndSupportList...")

var rateClicked = false
var mailClicked = false
var resetHintsClicked = false
val context = InstrumentationRegistry.getInstrumentation().targetContext

composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
val feedbackList =
AboutUtil.getFeedbackAndSupportList(
onRateScribeClick = { rateClicked = true },
onMailClick = { mailClicked = true },
onResetHintsClick = { resetHintsClicked = true },
context = context,
)

// Test list is not empty
assertThat(feedbackList.items).isNotEmpty()
assertThat(feedbackList.items).hasSize(5)

// Test each item has required fields
feedbackList.items.forEach { item ->
assertThat(item).isInstanceOf(ScribeItem.ExternalLinkItem::class.java)
val linkItem = item as ScribeItem.ExternalLinkItem

assertThat(linkItem.leadingIcon).isNotNull()
assertThat(linkItem.title).isNotNull()
assertThat(linkItem.trailingIcon).isNotNull()
assertThat(linkItem.onClick).isNotNull()
}

// Test specific items
val rateItem = feedbackList.items[0] as ScribeItem.ExternalLinkItem
assertThat(rateItem.leadingIcon).isEqualTo(R.drawable.star)
assertThat(rateItem.title).isEqualTo(R.string.app_about_feedback_rate_scribe)

val mailItem = feedbackList.items[2] as ScribeItem.ExternalLinkItem
assertThat(mailItem.leadingIcon).isEqualTo(R.drawable.mail_icon)
assertThat(mailItem.title).isEqualTo(R.string.app_about_feedback_email)

val hintsItem = feedbackList.items[4] as ScribeItem.ExternalLinkItem
assertThat(hintsItem.leadingIcon).isEqualTo(R.drawable.light_bulb_icon)
assertThat(hintsItem.title).isEqualTo(R.string.app_about_feedback_app_hints)

// Test onClick callbacks
rateItem.onClick()
mailItem.onClick()
hintsItem.onClick()
}
}

// Verify callbacks were triggered
assertThat(rateClicked).isTrue()
assertThat(mailClicked).isTrue()
assertThat(resetHintsClicked).isTrue()

println("getFeedbackAndSupportList test passed!")
}

@Test
fun testGetLegalListItems() {
println("Testing getLegalListItems...")

var privacyPolicyClicked = false
var thirdPartyLicensesClicked = false
val context = InstrumentationRegistry.getInstrumentation().targetContext

composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
val legalList =
AboutUtil.getLegalListItems(
onPrivacyPolicyClick = { privacyPolicyClicked = true },
onThirdPartyLicensesClick = { thirdPartyLicensesClicked = true },
)

// Test list is not empty
assertThat(legalList.items).isNotEmpty()
assertThat(legalList.items).hasSize(2)

// Test each item has required fields
legalList.items.forEach { item ->
assertThat(item).isInstanceOf(ScribeItem.ExternalLinkItem::class.java)
val linkItem = item as ScribeItem.ExternalLinkItem

assertThat(linkItem.leadingIcon).isNotNull()
assertThat(linkItem.title).isNotNull()
assertThat(linkItem.trailingIcon).isNotNull()
assertThat(linkItem.onClick).isNotNull()
}

// Test specific items
val privacyItem = legalList.items[0] as ScribeItem.ExternalLinkItem
assertThat(privacyItem.leadingIcon).isEqualTo(R.drawable.shield_lock)
assertThat(privacyItem.title).isEqualTo(R.string.app_about_legal_privacy_policy)
assertThat(privacyItem.trailingIcon).isEqualTo(R.drawable.right_arrow)

val licenseItem = legalList.items[1] as ScribeItem.ExternalLinkItem
assertThat(licenseItem.leadingIcon).isEqualTo(R.drawable.license_icon)
assertThat(licenseItem.title).isEqualTo(R.string.app_about_legal_third_party)
assertThat(licenseItem.trailingIcon).isEqualTo(R.drawable.right_arrow)

// Test onClick callbacks
privacyItem.onClick()
licenseItem.onClick()
}
}

// Verify callbacks were triggered
assertThat(privacyPolicyClicked).isTrue()
assertThat(thirdPartyLicensesClicked).isTrue()

println("getLegalListItems test passed!")
}

@Test
fun testExternalLinksConstants() {
println("Testing ExternalLinks constants...")

// Test that external links are properly defined
assertThat(be.scri.ui.screens.about.ExternalLinks.GITHUB_SCRIBE).isNotEmpty()
assertThat(be.scri.ui.screens.about.ExternalLinks.GITHUB_ISSUES).isNotEmpty()
assertThat(be.scri.ui.screens.about.ExternalLinks.GITHUB_RELEASES).isNotEmpty()
assertThat(be.scri.ui.screens.about.ExternalLinks.MATRIX).isNotEmpty()
assertThat(be.scri.ui.screens.about.ExternalLinks.MASTODON).isNotEmpty()

println("ExternalLinks constants test passed!")
}
}
4 changes: 2 additions & 2 deletions app/src/test/kotlin/helpers/AlphanumericComparatorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
package helpers

import be.scri.helpers.AlphanumericComparator
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.Assert.assertEquals
import org.junit.Test

class AlphanumericComparatorTest {
private val subject = AlphanumericComparator()
Expand Down
Loading