Skip to content
Closed
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
45 changes: 45 additions & 0 deletions .github/workflows/android-ci.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 8 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
62 changes: 62 additions & 0 deletions app/src/test/java/com/pinakes/app/DateFormatTest.kt
Original file line number Diff line number Diff line change
@@ -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}""")))
}
}
Loading