Skip to content

Commit dd451be

Browse files
committed
Android: introduce unit tests and Kotlin
1 parent edd2d36 commit dd451be

4 files changed

Lines changed: 119 additions & 5 deletions

File tree

.github/workflows/ci-test.yml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,34 @@ jobs:
2121
- name: Check public API compatibility
2222
run: npm run check:api-compat
2323

24-
android-test:
24+
android-unit-test:
2525
needs: lint
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v7
29+
30+
- uses: jdx/mise-action@v4
31+
32+
- name: Cache npm packages
33+
uses: actions/cache@v6
34+
with:
35+
path: ~/.npm
36+
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json', 'test/test.ts') }}
37+
restore-keys: |
38+
${{ runner.os }}-npm-
39+
40+
- name: Install dependencies
41+
run: npm install
42+
43+
- name: Setup Gradle
44+
uses: gradle/actions/setup-gradle@v6
45+
46+
- name: Run Android unit tests
47+
working-directory: android
48+
run: ./gradlew :app:test
49+
50+
android-test:
51+
needs: [lint, android-unit-test]
2652
runs-on: bitrise-react-native-code-push-linux-runner
2753
strategy:
2854
matrix:
@@ -49,6 +75,16 @@ jobs:
4975
- name: Install dependencies
5076
run: npm install
5177

78+
- name: Setup Gradle
79+
uses: gradle/actions/setup-gradle@v6
80+
with:
81+
# This job's Gradle build lives in a test app generated at runtime by the test harness,
82+
# not a project checked into this repo, so there's nothing meaningful for it to
83+
# contribute back to the cache. Read-only avoids each matrix arm (bare/expo) writing
84+
# its own redundant entry; it still reuses whatever android-unit-test wrote, since
85+
# setup-gradle shares its cache across jobs in the same workflow run.
86+
cache-read-only: true
87+
5288
- name: Enable KVM
5389
run: |
5490
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules

android/app/build.gradle

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
1+
buildscript {
2+
// Read the Kotlin version from the consuming app's rootProject.ext first (most RN apps
3+
// already set this for reanimated/gesture-handler/screens/etc.), falling back to a bundled
4+
// default only when nothing else in the build graph has declared one. We deliberately use
5+
// the classic `buildscript { classpath ... } + apply plugin` form here instead of the
6+
// `plugins { id(...) version(...) }` DSL: the latter hard-fails the whole consumer build with
7+
// "plugin already on the classpath with a different version" if any other subproject (e.g.
8+
// reanimated) applies a different Kotlin version, since it enforces exact version matches per
9+
// plugin id across the whole multi-project build. The classpath form just resolves to a
10+
// single version (highest wins) with no such conflict.
11+
//
12+
// Fallback version has no ecosystem anchor to track — it only applies when nothing else in
13+
// the host build declared a Kotlin version at all, which is already rare in the RN ecosystem.
14+
// Bump it opportunistically; nothing depends on it matching anything specific.
15+
def kotlinVersion = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : "1.9.24"
16+
17+
repositories {
18+
google()
19+
mavenCentral()
20+
}
21+
dependencies {
22+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
23+
}
24+
}
25+
126
apply plugin: "com.android.library"
27+
apply plugin: "kotlin-android"
228

329
def isNewArchitectureEnabled() {
430
// To opt-in for the New Architecture, you can either:
@@ -10,10 +36,16 @@ def isNewArchitectureEnabled() {
1036

1137
def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()
1238

13-
def DEFAULT_COMPILE_SDK_VERSION = 26
14-
def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3"
15-
def DEFAULT_TARGET_SDK_VERSION = 26
16-
def DEFAULT_MIN_SDK_VERSION = 16
39+
// These are fallbacks only — real consumer apps set compileSdkVersion/buildToolsVersion/
40+
// targetSdkVersion/minSdkVersion on rootProject themselves (RN's own template always has),
41+
// so bumping these defaults doesn't change behavior for any app that already does. Keep them
42+
// aligned with RN's current app template (verified against Examples/CodePushDemo's root
43+
// build.gradle, generated from RN 0.87's template) so a consumer relying on the fallback still
44+
// gets a build that actually works with a current RN version.
45+
def DEFAULT_COMPILE_SDK_VERSION = 35 // RN template: compileSdkVersion
46+
def DEFAULT_BUILD_TOOLS_VERSION = "35.0.0" // RN template: buildToolsVersion
47+
def DEFAULT_TARGET_SDK_VERSION = 35 // RN template: targetSdkVersion
48+
def DEFAULT_MIN_SDK_VERSION = 24 // RN template: minSdkVersion (RN 0.74+'s own minimum)
1749

1850
android {
1951
namespace "com.microsoft.codepush.react"
@@ -40,9 +72,25 @@ android {
4072
buildFeatures {
4173
buildConfig true
4274
}
75+
76+
// JVM 17 matches mise.toml's `java = "17"` pin (this repo's own toolchain) and RN 0.76+'s own
77+
// minimum required JDK — not an arbitrary choice. Confirmed against Examples/CodePushDemo:
78+
// its build resolves unit-test javac to JVM 17 regardless of what's set here, so this module
79+
// must match it or Kotlin's build fails with "Inconsistent JVM Target Compatibility". Bump
80+
// this in lockstep with mise.toml's java version, never independently.
81+
compileOptions {
82+
sourceCompatibility JavaVersion.VERSION_17
83+
targetCompatibility JavaVersion.VERSION_17
84+
}
85+
86+
kotlinOptions {
87+
jvmTarget = JavaVersion.VERSION_17.toString()
88+
}
4389
}
4490

4591
dependencies {
4692
implementation 'com.facebook.react:react-android:0.82.1'
4793
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
94+
95+
testImplementation 'junit:junit:4.13.2'
4896
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.microsoft.codepush.react
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
import java.io.File
6+
7+
class CodePushUtilsTest {
8+
9+
@Test
10+
fun appendPathComponent_joinsBaseAndChildIntoAbsolutePath() {
11+
val basePath = "/data/data/com.example.app/files"
12+
val result = CodePushUtils.appendPathComponent(basePath, "CodePush")
13+
14+
assertEquals(File(basePath, "CodePush").absolutePath, result)
15+
}
16+
17+
@Test
18+
fun appendPathComponent_handlesNestedRelativeComponent() {
19+
val basePath = "/data/data/com.example.app/files/CodePush"
20+
val result = CodePushUtils.appendPathComponent(basePath, "20/CodePushLoader.android.bundle")
21+
22+
assertEquals(File(basePath, "20/CodePushLoader.android.bundle").absolutePath, result)
23+
}
24+
}

android/gradle.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@
1616
# This option should only be used with decoupled projects. More details, visit
1717
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
1818
# org.gradle.parallel=true
19+
20+
# This repo's own standalone build/test harness only — real consumer apps already set this
21+
# (RN's own template has for years), so it's never actually read by a downstream build. Needed
22+
# here because react-android pulls in AndroidX transitively, and Gradle's unit test task
23+
# resolves that classpath strictly.
24+
android.useAndroidX=true

0 commit comments

Comments
 (0)