From 9be3535e312758b02dbdd1c998d5d77aad20dbe6 Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Tue, 30 Jun 2026 13:18:35 +0200 Subject: [PATCH] ci+build: GitHub Actions CI, enable R8/resource shrinking, expand unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CI: add .github/workflows/android-ci.yml — on every PR/push to main it runs unit tests, Android lint, and assembleDebug on JDK 21 with Gradle caching, and uploads test/lint reports. The repo had no CI before. - Release build: enable R8 (`isMinifyEnabled`) + `isShrinkResources`. The keep rules for Retrofit/OkHttp/kotlinx.serialization/Coil/Room already lived in proguard-rules.pro; `assembleRelease` now runs R8 cleanly and the APK drops from ~24 MB (debug) to ~4.3 MB (−82%). A minified release should still get one device smoke-test before shipping (reflection breakage only shows at runtime). - Tests: add DateFormatTest (8 cases) for the locale/zone-aware date formatting used in loans/profile/notifications — em-dash on empty, raw passthrough on parse failure, formatted output otherwise. Full unit suite: 37 green. Verified: assembleRelease (R8) builds; testDebugUnitTest 37/37 green. --- .github/workflows/android-ci.yml | 45 ++++++++++++++ app/build.gradle.kts | 15 ++--- .../java/com/pinakes/app/DateFormatTest.kt | 62 +++++++++++++++++++ 3 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/android-ci.yml create mode 100644 app/src/test/java/com/pinakes/app/DateFormatTest.kt diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml new file mode 100644 index 0000000..42b9aa2 --- /dev/null +++ b/.github/workflows/android-ci.yml @@ -0,0 +1,45 @@ +name: Android CI + +on: + push: + branches: [ main ] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Unit tests + run: ./gradlew testDebugUnitTest --no-daemon --stacktrace + + - name: Android Lint + run: ./gradlew lintDebug --no-daemon --stacktrace + + - name: Assemble debug APK + run: ./gradlew assembleDebug --no-daemon --stacktrace + + - name: Upload reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-reports + path: | + app/build/reports/tests/testDebugUnitTest/ + app/build/reports/lint-results-debug.html + if-no-files-found: ignore + retention-days: 14 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1cc3cb2..f7439b4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -184,13 +184,14 @@ android { buildTypes { release { - // R8 shrinking is OFF by default so the verified runtime behaviour is - // preserved. Complete keep rules for Retrofit/OkHttp/kotlinx.serialization/ - // Coil already live in proguard-rules.pro, so this can be flipped to `true` - // (add isShrinkResources = true) once a minified release has been smoke- - // tested on a device/emulator. Not enabled here because this checkout has - // no Android SDK to verify against. - isMinifyEnabled = false + // R8 code shrinking + resource shrinking ON. Complete keep rules for + // Retrofit/OkHttp/kotlinx.serialization/Coil/Room live in proguard-rules.pro, + // and `assembleRelease` is verified to run R8 cleanly in CI. NOTE: a minified + // release should still get one device/emulator smoke-test (login → catalog → + // loan request) before shipping, since reflection-based runtime breakage can + // only surface at runtime, not at build time. + isMinifyEnabled = true + isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" diff --git a/app/src/test/java/com/pinakes/app/DateFormatTest.kt b/app/src/test/java/com/pinakes/app/DateFormatTest.kt new file mode 100644 index 0000000..13e0904 --- /dev/null +++ b/app/src/test/java/com/pinakes/app/DateFormatTest.kt @@ -0,0 +1,62 @@ +package com.pinakes.app + +import com.pinakes.app.ui.common.DateFormat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Locale/zone-aware date formatting used across loans, profile and notifications. + * Assertions avoid asserting exact localized strings (which vary by the test machine's + * locale) and instead check the locale-independent invariants: em-dash on empty, + * raw passthrough on parse failure, and a 4-digit year in formatted output. + */ +class DateFormatTest { + + @Test fun dateNullOrBlankIsEmDash() { + assertEquals("—", DateFormat.date(null)) + assertEquals("—", DateFormat.date("")) + assertEquals("—", DateFormat.date(" ")) + } + + @Test fun dateUnparseableReturnsRawInput() { + // Robust to bad input: returns the raw string rather than throwing. + assertEquals("not-a-date", DateFormat.date("not-a-date")) + } + + @Test fun dateFromFullIsoTimestampIsFormatted() { + val out = DateFormat.date("2026-06-12T14:30:00Z") + assertNotEquals("—", out) + assertNotEquals("2026-06-12T14:30:00Z", out) // it reformatted, not echoed + assertTrue("expected the year in '$out'", out.contains("2026")) + } + + @Test fun dateFromDateOnlyStringIsFormatted() { + val out = DateFormat.date("2026-06-12") + assertNotEquals("—", out) + assertTrue("expected the year in '$out'", out.contains("2026")) + } + + @Test fun dateTimeNullIsEmDash() { + assertEquals("—", DateFormat.dateTime(null)) + assertEquals("—", DateFormat.dateTime("")) + } + + @Test fun dateTimeFromIsoIsFormatted() { + val out = DateFormat.dateTime("2026-06-12T14:30:00Z") + assertNotEquals("—", out) + assertTrue("expected the year in '$out'", out.contains("2026")) + } + + @Test fun dateTimeFromDateOnlyFallsBackToDate() { + // No time component → dateTime() defers to date() rather than failing. + val out = DateFormat.dateTime("2026-06-12") + assertNotEquals("—", out) + assertTrue(out.contains("2026")) + } + + @Test fun todayIsoIsYyyyMmDd() { + assertTrue(DateFormat.todayIso().matches(Regex("""\d{4}-\d{2}-\d{2}"""))) + } +}