Skip to content

Commit 4130094

Browse files
committed
feat: Improve wasmJs and iOS testing and database handling
- Refactor WebAssembly (wasmJs) database worker to support both SQLite3-WASM/OPFS and SQL.js as a fallback. - Add `deleteDatabase` functionality for iOS to properly clean up database files, including WAL and SHM. - Configure wasmJs browser tests to run with Karma and Chrome. - Introduce `WebUiTests.kt` and `IosUiTests.kt` extending a common test base to improve test isolation. - Bump app versions to `8.5.0` for Android and iOS. refactor: Use test tags and remove `runBlockingAll` in UI tests - Replace string resource lookups in UI tests with `testTag` modifiers for improved stability and performance. - Create a centralized `TestTags.kt` file to define all UI test tags. - Remove the `runBlockingAll` utility and update tests to use standard coroutine scopes. - Add a reusable `PasswordSaveButton` composable to reduce code duplication in password-related dialogs. docs: Consolidate documentation into the /docs directory - Move module structure, version management, and testing documentation into the central `/docs` directory. - Update `CONTRIBUTING.md` and other guides to link to the new centralized documentation.
1 parent 9d62a12 commit 4130094

File tree

74 files changed

+1320
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1320
-844
lines changed

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
- Desktop app: `./gradlew :app:desktop:run` (launches JVM desktop Compose app).
2222
- Desktop UI tests: `./gradlew :app:desktop:jvmTest` (uses `uiTestJUnit4`).
2323
- iOS: `cd iosApp && pod install` then open `iosApp/iosApp.xcworkspace` in Xcode and run. Regenerate podspec if needed: `./gradlew :app:ios-kit:podspec`.
24+
- iOS UI tests: `./gradlew :app:ios-kit:iosSimulatorArm64Test` (requires iOS simulator; uses multiplatform Compose UI tests).
2425
- Web app: `./gradlew :app:web:wasmJsBrowserDevelopmentRun --continuous` (launches the web app in a browser with hot reload).
26+
- Web UI tests: `./gradlew :app:web:wasmJsBrowserTest` (requires Chrome binary via `CHROME_BIN` environment variable; uses Karma/Chrome headless).
2527
- Build without iOS link tasks: `./gradle/build_quick.sh` (see [gradle/build_quick.sh](gradle/build_quick.sh))
2628

2729
## AI Agent Workflow for Verifying Changes
@@ -40,6 +42,8 @@ After making changes, AI agents must perform the following checks sequentially.
4042
- Unit tests: Kotlin Multiplatform `kotlin("test")`/JUnit. Run all with `./gradlew test` or per module (e.g., `:ui:shared:jvmTest`).
4143
- Android UI tests: Espresso/Compose in `app/android/src/androidTest`. Run with `connectedCheck`.
4244
- Desktop UI tests: in `app/desktop/src/jvmTest` using `uiTestJUnit4`.
45+
- iOS UI tests: Multiplatform Compose UI tests in `app/ios-kit/src/commonTest`. Run with `:app:ios-kit:iosSimulatorArm64Test`.
46+
- Web UI tests: Multiplatform Compose UI tests in `app/web/src/wasmJsTest`. Run with `:app:web:wasmJsBrowserTest` (requires `CHROME_BIN` environment variable).
4347
- Name tests `FooBarTest.kt`; prefer Given_When_Then method names.
4448

4549
## Commit & Pull Request Guidelines

CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [8.5.0] - 2026-01-08
9+
10+
### Features
11+
- Improve wasmJs and iOS database handling and tests
12+
- Refactor WebAssembly (wasmJs) database worker to support both SQLite3-WASM/OPFS and SQL.js as a fallback
13+
- Add `deleteDatabase` functionality for iOS to properly clean up database files (WAL and SHM)
14+
- Configure wasmJs browser tests to run with Karma and Chrome
15+
- Improve test isolation across platforms
16+
17+
### Bug Fixes
18+
- Fix build warnings related to Kotlin compiler options and wasmJs API changes
19+
- Add `-Xexpect-actual-classes` compiler option to support new class hierarchy model
20+
- Update `runBlockingAll` implementation for wasmJs target to use `startCoroutine`
21+
- Fix webpack configuration for dynamic repo root detection and SQLite WASM asset availability
22+
23+
### Refactoring
24+
- Improve database connection handling and error checking
25+
- Refactor `checkDbConnection` into `hasDbConnection` suspend function in MainViewModel
26+
- Introduce safer access patterns with proper exception handling
27+
- Use more specific exception types (e.g., `java.sql.SQLException`)
28+
- Consolidate documentation into `/docs` directory
29+
- Move module structure and version management documentation
30+
- Update contributing guides to link to centralized documentation
31+
- Use test tags instead of string resources in UI tests
32+
- Create centralized `TestTags.kt` for all test tag constants
33+
- Update UI composables to use `modifier.testTag()` attribute
34+
- Improve test stability by removing dynamic string lookups
35+
- Create reusable `PasswordSaveButton` composable to eliminate duplication
36+
- Remove `runBlockingAll` from UI tests
37+
- Simplify UI test code by using coroutine scopes directly
38+
- Update `DbTestEncryptor` to be a suspend function
39+
- Align test infrastructure with modern Kotlin coroutines patterns
40+
41+
### Chores
42+
- Format `build.gradle.kts` for Android
43+
- Update dependencies and optimization settings
44+
- Improve code organization and consistency
45+
846
## [8.4.9] - 2025-12-23
947

1048
### Features

CONTRIBUTING.md

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -341,31 +341,7 @@ class FeatureViewModelTest {
341341

342342
## Module Structure
343343

344-
### Core Modules
345-
346-
- **core:domain** - Pure business logic, no dependencies
347-
- **core:presentation** - ViewModels, state management
348-
- **core:data:db-sqldelight** - SQLDelight data implementation (default)
349-
- **core:data:db-room** - Room data implementation (alternative)
350-
- **core:test** - Shared test utilities
351-
352-
### UI Modules
353-
354-
- **ui:shared** - Shared Compose UI (100% shared)
355-
- **ui:test-jvm** - UI testing framework (Kaspresso-inspired)
356-
357-
### App Modules
358-
359-
- **app:android** - Android application
360-
- **app:desktop** - Desktop JVM application
361-
- **app:web** - Web/Wasm application
362-
- **app:ios-kit** - iOS framework (CocoaPods)
363-
- **app:iosApp** - iOS application (Swift/SwiftUI)
364-
365-
### Build Modules
366-
367-
- **build-logic** - Gradle convention plugins
368-
- **thirdparty** - Vendored dependencies
344+
**See [docs/README.md](docs/README.md#module-documentation) for the list of modules and their documentation.**
369345

370346
### Module Guidelines
371347

app/android/build.gradle.kts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ android {
2424
applicationId = "com.softartdev.noteroom"
2525
minSdk = libs.versions.minSdk.get().toInt()
2626
targetSdk = libs.versions.targetSdk.get().toInt()
27-
versionCode = 849
28-
versionName = "8.4.9"
27+
versionCode = 850
28+
versionName = "8.5.0"
2929
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
3030
testInstrumentationRunnerArguments["clearPackageData"] = "true"
3131
vectorDrawables.useSupportLibrary = true
@@ -40,7 +40,10 @@ android {
4040
getByName("release") {
4141
isMinifyEnabled = true
4242
isShrinkResources = true
43-
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
43+
proguardFiles(
44+
getDefaultProguardFile("proguard-android-optimize.txt"),
45+
"proguard-rules.pro"
46+
)
4447
configure<CrashlyticsExtension> { mappingFileUploadEnabled = true }
4548
signingConfig = signingConfigs.getByName("config")
4649
}
@@ -58,7 +61,7 @@ android {
5861
compose = true
5962
}
6063
packagingOptions.resources.excludes.add("/META-INF/{AL2.0,LGPL2.1}")
61-
testOptions{
64+
testOptions {
6265
execution = "ANDROIDX_TEST_ORCHESTRATOR"
6366
emulatorControl.enable = true
6467
}
@@ -112,6 +115,6 @@ dependencies {
112115
androidTestImplementation(libs.leakCanary.android.instrumentation)
113116
lintChecks(libs.android.security.lint)
114117
}
115-
tasks.withType<UploadMappingFileTask>{
118+
tasks.withType<UploadMappingFileTask> {
116119
dependsOn("processDebugGoogleServices")
117120
}

app/android/src/androidTest/java/com/softartdev/notedelight/ui/CustomActivityScenarioRule.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ import android.app.Activity
44
import androidx.activity.ComponentActivity
55
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
66
import androidx.test.core.app.ActivityScenario
7+
import kotlinx.coroutines.runBlocking
78
import org.junit.rules.ExternalResource
89

910
inline fun <reified A : ComponentActivity> customAndroidComposeRule(
10-
noinline beforeActivityLaunched: () -> Unit
11+
noinline beforeActivityLaunched: suspend () -> Unit
1112
): AndroidComposeTestRule<CustomActivityScenarioRule<A>, A> {
1213
return customAndroidComposeRule(A::class.java, beforeActivityLaunched)
1314
}
1415

1516
fun <A : ComponentActivity> customAndroidComposeRule(
1617
activityClass: Class<A>,
17-
beforeActivityLaunched: () -> Unit
18+
beforeActivityLaunched: suspend () -> Unit
1819
): AndroidComposeTestRule<CustomActivityScenarioRule<A>, A> = AndroidComposeTestRule(
1920
activityRule = CustomActivityScenarioRule(activityClass, beforeActivityLaunched),
2021
activityProvider = ::provideActivity
@@ -30,13 +31,13 @@ fun <A : Activity> provideActivity(customActivityScenarioRule: CustomActivitySce
3031

3132
class CustomActivityScenarioRule<A : Activity>(
3233
private val activityClass: Class<A>,
33-
private val beforeActivityLaunched: () -> Unit
34+
private val beforeActivityLaunched: suspend () -> Unit
3435
) : ExternalResource() {
3536

3637
internal lateinit var scenario: ActivityScenario<A>
3738

3839
override fun before() {
39-
beforeActivityLaunched()
40+
runBlocking { beforeActivityLaunched() }
4041
scenario = ActivityScenario.launch(activityClass)
4142
}
4243

app/android/src/androidTest/java/com/softartdev/notedelight/ui/SignOutTest.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ package com.softartdev.notedelight.ui
22

33
import androidx.compose.ui.test.assertIsDisplayed
44
import androidx.compose.ui.test.junit4.createAndroidComposeRule
5-
import androidx.compose.ui.test.onNodeWithContentDescription
5+
import androidx.compose.ui.test.onNodeWithTag
66
import androidx.lifecycle.Lifecycle.State.DESTROYED
77
import androidx.test.espresso.Espresso
88
import androidx.test.ext.junit.runners.AndroidJUnit4
99
import androidx.test.filters.LargeTest
1010
import com.softartdev.notedelight.MainActivity
11+
import com.softartdev.notedelight.util.CREATE_NOTE_FAB_TAG
1112
import kotlinx.coroutines.test.runTest
1213
import leakcanary.DetectLeaksAfterTestSuccess
1314
import leakcanary.TestDescriptionHolder
14-
import notedelight.ui.shared.generated.resources.Res
15-
import notedelight.ui.shared.generated.resources.create_note
16-
import org.jetbrains.compose.resources.getString
1715
import org.junit.Assert.assertTrue
1816
import org.junit.Rule
1917
import org.junit.Test
@@ -34,7 +32,7 @@ class SignOutTest {
3432
@Test
3533
fun signOutTest() = runTest {
3634
composeTestRule
37-
.onNodeWithContentDescription(label = getString(Res.string.create_note))
35+
.onNodeWithTag(CREATE_NOTE_FAB_TAG)
3836
.assertIsDisplayed()
3937

4038
Espresso.pressBackUnconditionally()

app/desktop/build.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ kotlin {
1616
applyDefaultHierarchyTemplate()
1717

1818
sourceSets {
19-
all {
20-
languageSettings.optIn("kotlin.RequiresOptIn")
21-
}
2219
jvmMain.dependencies {
2320
implementation(projects.core.domain)
2421
implementation(projects.core.presentation)
@@ -44,6 +41,9 @@ kotlin {
4441
implementation(libs.kotlinx.datetime)
4542
implementation(libs.androidx.lifecycle.runtime.testing)
4643
}
44+
all {
45+
languageSettings.optIn("kotlin.RequiresOptIn")
46+
}
4747
}
4848
}
4949

@@ -53,7 +53,7 @@ compose.desktop {
5353
nativeDistributions {
5454
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
5555
packageName = "Note Delight"
56-
packageVersion = "8.4.9"
56+
packageVersion = "8.5.0"
5757
description = "Note app with encryption"
5858
copyright = "© 2023 SoftArtDev"
5959
macOS.iconFile.set(project.file("src/jvmMain/resources/app_icon.icns"))

app/ios-kit/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,44 @@ dependencies {
358358

359359
Kotlin code can be debugged from Xcode with proper symbol mapping.
360360

361+
## Testing
362+
363+
### UI Tests
364+
365+
The iOS kit includes multiplatform Compose UI tests that extend `CommonUiTests` from the `ui/test` module:
366+
367+
**Location**: `app/ios-kit/src/commonTest/kotlin/IosUiTests.kt`
368+
369+
**Test Coverage**:
370+
- CRUD operations
371+
- Title editing after create/save
372+
- Database prepopulation
373+
- Encryption flow
374+
- Password settings
375+
- Locale switching
376+
377+
**Running Tests**:
378+
379+
```bash
380+
# Requires iOS Simulator to be running
381+
./gradlew :app:ios-kit:iosSimulatorArm64Test
382+
```
383+
384+
**Test Configuration**:
385+
- Tests run on iOS Simulator (arm64)
386+
- Uses Compose Multiplatform testing API
387+
- Database is automatically cleaned up before each test
388+
- Handles iOS-specific database file cleanup (WAL, journal files)
389+
390+
**Database Management**:
391+
Tests use improved database deletion that properly handles:
392+
- Main database file (`notes.db`)
393+
- Write-Ahead Logging files (`notes.db-wal`)
394+
- Shared memory files (`notes.db-shm`)
395+
- Journal files (`notes.db-journal`)
396+
397+
This ensures clean test state and prevents test interference.
398+
361399
## Updating Framework
362400

363401
After changing Kotlin code:

app/ios-kit/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
@file:OptIn(ExperimentalComposeLibrary::class)
2+
13
import org.gradle.internal.os.OperatingSystem
4+
import org.jetbrains.compose.ExperimentalComposeLibrary
25

36
plugins {
47
alias(libs.plugins.kotlin.multiplatform)
@@ -39,5 +42,13 @@ kotlin {
3942
implementation(project.dependencies.platform(libs.koin.bom))
4043
api(libs.koin.core)
4144
}
45+
commonTest.dependencies {
46+
implementation(kotlin("test"))
47+
implementation(projects.ui.test)
48+
implementation(compose.uiTest)
49+
implementation(compose.materialIconsExtended)
50+
implementation(libs.androidx.lifecycle.runtime.compose)
51+
implementation(libs.androidx.lifecycle.runtime.testing)
52+
}
4253
}
4354
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.softartdev.notedelight
2+
3+
import kotlinx.coroutines.test.TestResult
4+
import kotlin.test.AfterTest
5+
import kotlin.test.BeforeTest
6+
import kotlin.test.Test
7+
8+
class IosUiTests : CommonUiTests() {
9+
10+
@BeforeTest
11+
override fun setUp() = super.setUp()
12+
13+
@AfterTest
14+
override fun tearDown() = super.tearDown()
15+
16+
@Test
17+
override fun crudNoteTest(): TestResult = super.crudNoteTest()
18+
19+
@Test
20+
override fun editTitleAfterCreateTest(): TestResult = super.editTitleAfterCreateTest()
21+
22+
@Test
23+
override fun editTitleAfterSaveTest(): TestResult = super.editTitleAfterSaveTest()
24+
25+
@Test
26+
override fun prepopulateDatabase(): TestResult = super.prepopulateDatabase()
27+
28+
@Test
29+
override fun flowAfterCryptTest(): TestResult = super.flowAfterCryptTest()
30+
31+
@Test
32+
override fun settingPasswordTest(): TestResult = super.settingPasswordTest()
33+
34+
@Test
35+
override fun localeTest(): TestResult = super.localeTest()
36+
}

0 commit comments

Comments
 (0)