Skip to content
Merged
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
121 changes: 86 additions & 35 deletions .github/workflows/android_ci.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,97 @@
name: VentNote Build and Test CI
name: VentNote CI

on:
pull_request:
push:
branches: [ master ]
pull_request:
branches: [ master, staging ]

jobs:
test:
name: Run Unit Tests
lint-and-unit-test:
name: Lint and Unit Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
cache: gradle

- name: Unit tests
run: ./gradlew test --stacktrace
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Run Lint
run: ./gradlew lintDebug --stacktrace

- name: Run Unit Tests
run: ./gradlew testDebugUnitTest --stacktrace

build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
cache: gradle
- name: Upload Test Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: lint-and-unit-test-reports
path: |
**/build/reports/lint-results-*.html
**/build/reports/tests/testDebugUnitTest/

- name: Clean project
run: ./gradlew clean --stacktrace
# instrumentation-test:
# name: Instrumentation Tests
# runs-on: macos-latest
# timeout-minutes: 60
# strategy:
# matrix:
# api-level: [34]
# steps:
# - uses: actions/checkout@v4
#
# - name: Set up JDK 17
# uses: actions/setup-java@v4
# with:
# java-version: '17'
# distribution: 'temurin'
# cache: gradle
#
# - name: Run Instrumentation Tests
# uses: reactivecircus/android-emulator-runner@v2
# with:
# api-level: ${{ matrix.api-level }}
# arch: arm64-v8a
# target: google_apis
# force-avd-creation: true
# emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
# disable-animations: true
# # Increased RAM for better stability on ARM runners
# disk-size: 4096M
# heap-size: 512M
# script: ./gradlew connectedDebugAndroidTest --stacktrace
#
# - name: Upload Instrumentation Reports
# if: always()
# uses: actions/upload-artifact@v4
# with:
# name: instrumentation-test-reports
# path: app/build/reports/androidTests/connected/

- name: Lint Debug
run: ./gradlew lintDebug --stacktrace
build-check:
name: Build APK Check
runs-on: ubuntu-latest
needs: [lint-and-unit-test]
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Build debug APK
run: ./gradlew build --stacktrace
- name: Build Debug APK
run: ./gradlew assembleDebug --stacktrace

- name: Upload Debug APK
uses: actions/upload-artifact@v4
with:
name: debug-apk
path: app/build/outputs/apk/debug/*.apk
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
//package com.digiventure.utils
//
//import androidx.test.ext.junit.runners.AndroidJUnit4
//import org.junit.runner.RunWith
//
//@RunWith(AndroidJUnit4::class)
//abstract class BaseAcceptanceTest {
//// @get:Rule(order = 0)
//// val composeTestRule = createComposeRule()
//}
package com.digiventure.utils

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
abstract class BaseAcceptanceTest
138 changes: 138 additions & 0 deletions app/src/androidTest/java/com/digiventure/ventnote/NavDrawerFeature.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.digiventure.ventnote

import android.content.Intent
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
import com.digiventure.utils.BaseAcceptanceTest
import com.digiventure.ventnote.commons.TestTags
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.hamcrest.Matchers.allOf
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@HiltAndroidTest
class NavDrawerFeature : BaseAcceptanceTest() {

@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<MainActivity>()

@Before
fun setUp() {
hiltRule.inject()
Intents.init()

// Open the drawer
composeTestRule.onNodeWithTag(TestTags.NOTES_PAGE).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.MENU_ICON_BUTTON).performClick()
composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(TestTags.NAV_DRAWER, useUnmergedTree = true).assertIsDisplayed()
}

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

@Test
fun initialState_showsMenuItems() {
composeTestRule.onNodeWithTag(TestTags.RATE_APP_TILE).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.MORE_APPS_TILE).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.APP_VERSION_TILE).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.THEME_TILE).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.COLOR_MODE_TILE).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.BACKUP_TILE).assertIsDisplayed()
}

@Test
fun rateApp_launchesPlayStore() {
composeTestRule.onNodeWithTag(TestTags.RATE_APP_TILE).performClick()

intended(allOf(
hasAction(Intent.ACTION_VIEW),
hasData("https://play.google.com/store/apps/details?id=com.digiventure.ventnote")
))
}

@Test
fun moreApps_launchesDeveloperPage() {
composeTestRule.onNodeWithTag(TestTags.MORE_APPS_TILE).performClick()

intended(allOf(
hasAction(Intent.ACTION_VIEW),
hasData("https://play.google.com/store/apps/developer?id=Mattrmost")
))
}

@Test
fun backupNavigation_navigatesToBackupPage() {
composeTestRule.onNodeWithTag(TestTags.BACKUP_TILE).performClick()
composeTestRule.waitForIdle()

// Use text or tag to verify backup page (usually has "Backup" title in header)
composeTestRule.onNodeWithText("Backup Notes").assertIsDisplayed()
}

@Test
fun themeColorChange_updatesTheme() {
// Verify we can click all theme colors
composeTestRule.onNodeWithTag(TestTags.THEME_COLOR_PURPLE).performClick()
composeTestRule.onNodeWithTag(TestTags.THEME_COLOR_CRIMSON).performClick()
composeTestRule.onNodeWithTag(TestTags.THEME_COLOR_CADMIUM_GREEN).performClick()
composeTestRule.onNodeWithTag(TestTags.THEME_COLOR_COBALT_BLUE).performClick()

// Clicks should not crash and should update internal state (hard to verify without custom matchers)
composeTestRule.onNodeWithTag(TestTags.THEME_TILE).assertIsDisplayed()
}

@Test
fun colorModeToggle_updatesMode() {
// Find the current mode by checking the subtitle text
val lightModeText = "switch to light mode"
val darkModeText = "switch to dark mode"

// Initial click to toggle (assuming default is light mode or whatever)
// We look for either text to be sure
val initialNode = composeTestRule.onNode(
hasText(lightModeText, ignoreCase = true) or hasText(darkModeText, ignoreCase = true)
)

initialNode.assertIsDisplayed()
val isInitiallyLight = try {
composeTestRule.onNodeWithText(darkModeText, ignoreCase = true).assertIsDisplayed()
true // It says "switch to dark", so it is currently light
} catch (_: Throwable) {
false
}

// Toggle
composeTestRule.onNodeWithTag(TestTags.COLOR_MODE_TILE).performClick()
composeTestRule.waitForIdle()

// Verify text swapped
if (isInitiallyLight) {
composeTestRule.onNodeWithText(lightModeText, ignoreCase = true).assertIsDisplayed()
} else {
composeTestRule.onNodeWithText(darkModeText, ignoreCase = true).assertIsDisplayed()
}
}

@Test
fun drawer_canBeClosed() {
// Click outside or use a specific close mechanism if available,
// but here we can just swipe or click the content area if accessible.
// Easiest is to just verify it closes on back press
androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(android.view.KeyEvent.KEYCODE_BACK)
composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(TestTags.NAV_DRAWER).assertDoesNotExist()
}
}
Loading