Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ android {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
}
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
// If signing.properties file not found, gradle will build an unsigned APK.
// For release builds, provide the required "signingPropsFilePath" for a signed APK, using:
// ./gradlew assembleRelease -PsigningPropsFilePath=absolute-file-path/signing.properties
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.esri.arcgismaps.kotlin.sampleviewer

import androidx.test.core.graphics.writeToTestStorage
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.captureToBitmap
import androidx.test.espresso.matcher.ViewMatchers.isRoot
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.rules.TestName
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ScenarioRuleScreenshotTest {

@get:Rule
val scenarioRule = activityScenarioRule<SampleViewerLauncherActivity>()

@get:Rule
val testName = TestName()

@Test
fun launch_idle_and_save_screenshot() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
onView(isRoot())
.perform(captureToBitmap { bmp ->
bmp.writeToTestStorage("Launcher_${testName.methodName}")
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.esri.arcgismaps.kotlin.sampleviewer

import android.content.Context
import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File

/**
* Run with: ./gradlew connectedAndroidTest
*/
@RunWith(AndroidJUnit4::class)
class UIAutomatorScreenshotTest {

private lateinit var device: UiDevice

@Before
fun setup() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.pressHome()
device.waitForIdle()

// Launch the app
val context = ApplicationProvider.getApplicationContext<Context>()
val intent = context.packageManager.getLaunchIntentForPackage(
"com.esri.arcgismaps.kotlin.sampleviewer"
)?.apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}

context.startActivity(intent)

// Wait for app to launch
device.wait(
Until.hasObject(By.pkg("com.esri.arcgismaps.kotlin.sampleviewer")),
10000
)
device.waitForIdle()
}


@Test
fun testHomeScreenScreenshot() = runBlocking {
val screenshotFile = getScreenshotFile("HomeScreen").apply {
parentFile?.mkdirs()
}
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
assertTrue(device.takeScreenshot(screenshotFile))
}

private fun getScreenshotFile(fileName: String): File {
val picturesDir = android.os.Environment.getExternalStoragePublicDirectory(
android.os.Environment.DIRECTORY_PICTURES
)
val screenshotDir = File(picturesDir, "SampleViewerScreenshots")
screenshotDir.mkdirs()
return File(screenshotDir, "${System.currentTimeMillis()}_${fileName}.png")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.esri.arcgismaps.kotlin.sampleviewer.model

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SampleInfoRepositoryTest {

private val json = Json { ignoreUnknownKeys = true }

/**
* Given the generated assets in 'samples/samples.json',
* when the DefaultSampleInfoRepository is loaded,
* then the count of samples emitted by getAllSamples()
* should match the JSON sample count.
*/
@Test
fun verifySampleCount() = runBlocking {
val context: Context = InstrumentationRegistry.getInstrumentation().targetContext

// Read the generated asset directly and count top-level entries.
val samplesJsonString = context.assets.open("samples/samples.json").bufferedReader().use { it.readText() }
val parsed = json.decodeFromString<Map<String, Map<String, String>>>(samplesJsonString)
val jsonCount = parsed.size

// Load the repository from the generated assets.
DefaultSampleInfoRepository.load(context)

// Wait for the repository flow to be populated.
// The StateFlow emits an initial empty list, so we wait until it emits the expected count.
val sampleViewerCount = withTimeout(30_000) {
DefaultSampleInfoRepository.getAllSamples()
.first { samples -> samples.size >= jsonCount }
.size
}

assertEquals(jsonCount, sampleViewerCount)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,11 @@ object DefaultSampleInfoRepository : SampleInfoRepository {
override fun getSamplesInCategory(sampleCategory: SampleCategory): Flow<List<Sample>> {
return sampleData.map { it.filter { sample -> sample.metadata.sampleCategory == sampleCategory } }
}

/**
* Get all samples from the repository.
*/
override fun getAllSamples(): Flow<List<Sample>> {
return sampleData
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ interface SampleInfoRepository {
fun getSamplesInCategory(sampleCategory: SampleCategory): Flow<List<Sample>>

fun getSampleByName(sampleName: String): Flow<Sample>

fun getAllSamples(): Flow<List<Sample>>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import com.android.build.api.dsl.ApplicationExtension
import com.esri.arcgismaps.kotlin.build_logic.convention.configureAndroidCompose
import com.esri.arcgismaps.kotlin.build_logic.convention.configureAndroidComposeTests
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
Expand All @@ -14,6 +15,7 @@ class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
}
val extension = extensions.getByType<ApplicationExtension>()
configureAndroidCompose(extension)
configureAndroidComposeTests(extension)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import com.android.build.gradle.LibraryExtension
import com.esri.arcgismaps.kotlin.build_logic.convention.configureKotlinAndroid
import com.esri.arcgismaps.kotlin.build_logic.convention.implementation
import com.esri.arcgismaps.kotlin.build_logic.convention.libs
import com.esri.arcgismaps.kotlin.build_logic.convention.testImplementation
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.kotlin

class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
Expand All @@ -21,7 +19,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
configureKotlinAndroid(this)
compileSdk = libs.findVersion("targetSdk").get().toString().toInt()
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
Expand All @@ -47,7 +44,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
}

dependencies {
testImplementation(kotlin("test"))
// External libraries
implementation(libs.findLibrary("androidx-constraintlayout").get())
implementation(libs.findLibrary("androidx-appcompat").get())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,34 @@ internal fun Project.configureAndroidCompose(
dependencies {
val composeBom = libs.findLibrary("androidx-compose-bom").get()
implementation(platform(composeBom))
androidTestImplementation(platform(composeBom))
implementation(libs.findLibrary("androidx-activity-compose").get())
implementation(libs.findLibrary("androidx-compose-material3").get())
implementation(libs.findLibrary("androidx-lifecycle-viewmodel-compose").get())
implementation(libs.findLibrary("androidx-compose-ui-tooling-preview").get())
implementation(libs.findLibrary("androidx-concurrent-futures").get())
implementation(libs.findLibrary("androidx-concurrent-futures-ktx").get())
debugImplementation(libs.findLibrary("androidx-compose-ui-tooling").get())
debugImplementation(libs.findLibrary("androidx-compose-ui-test-manifest").get())
androidTestImplementation(libs.findLibrary("androidx-compose-ui-test").get())
androidTestImplementation(libs.findLibrary("androidx-compose-ui-test-junit4").get())
}
}
}

internal fun Project.configureAndroidComposeTests(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
commonExtension.apply {
dependencies {
val composeBom = libs.findLibrary("androidx-compose-bom").get()
androidTestImplementation(platform(composeBom))
androidTestImplementation(libs.findLibrary("androidx-compose-ui-test-junit4").get())
androidTestImplementation(libs.findLibrary("androidx-test-uiautomator").get())
androidTestImplementation(libs.findLibrary("androidx-test-runner").get())
androidTestImplementation(libs.findLibrary("androidx-test-rules").get())
androidTestImplementation(libs.findLibrary("androidx-test-ext-junit-ktx").get())
androidTestImplementation(libs.findLibrary("androidx-junit").get())
androidTestImplementation(libs.findLibrary("androidx-espresso-core").get())
androidTestImplementation(libs.findLibrary("kotlinx-coroutines-test").get())
debugImplementation(libs.findLibrary("androidx-compose-ui-test-manifest").get())
debugImplementation(libs.findLibrary("junit").get())
}
}
}
15 changes: 14 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ materialIconsExt = "1.7.8"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
uiautomator = "2.3.0"
testRunner = "1.7.0"
testRules = "1.7.0"
testExtJunit = "1.3.0"
androidxConcurrent = "1.3.0"
androidxConcurrentKtx = "1.3.0"
coroutinesTest = "1.10.2"

### Application Verions
versionCode = "3000000"
Expand Down Expand Up @@ -73,7 +80,6 @@ androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graph
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
Expand All @@ -100,6 +106,13 @@ play-services-location = { group = "com.google.android.gms", name = "play-servic
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "testRunner" }
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "testRules" }
androidx-test-ext-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "testExtJunit" }
androidx-concurrent-futures = { group = "androidx.concurrent", name = "concurrent-futures", version.ref = "androidxConcurrent" }
androidx-concurrent-futures-ktx = { group = "androidx.concurrent", name = "concurrent-futures-ktx", version.ref = "androidxConcurrentKtx" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" }

# Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
Expand Down
Loading