diff --git a/build.gradle.kts b/build.gradle.kts index e80a02d8..9c175ec9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,11 +8,13 @@ plugins { alias(libs.plugins.kmmbridge) apply false alias(libs.plugins.skie) apply false alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.sqldelight) apply false alias(libs.plugins.grammarKitComposer) apply false alias(libs.plugins.mavenPublishPlugin) apply false alias(libs.plugins.downloadPlugin) apply false alias(libs.plugins.kotlinter) apply false + alias(libs.plugins.keeper) apply false } // Having different versions of this lead to the issue mentioned here diff --git a/core-tests-android/.gitignore b/core-tests-android/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core-tests-android/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core-tests-android/build.gradle.kts b/core-tests-android/build.gradle.kts new file mode 100644 index 00000000..46c4fad2 --- /dev/null +++ b/core-tests-android/build.gradle.kts @@ -0,0 +1,54 @@ +import com.slack.keeper.optInToKeeper + +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.keeper) +} + +dependencies { + implementation(projects.core) + implementation(libs.androidx.core) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.material) + + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.test.coroutines) + androidTestImplementation(libs.test.turbine) +} + +android { + namespace = "com.powersync.testing" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + applicationId = "com.powersync.testing" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + signingConfig = signingConfigs.getByName("debug") + } + } + testBuildType = "release" + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } +} + +androidComponents { + beforeVariants { it.optInToKeeper() } +} diff --git a/core-tests-android/proguard-rules.pro b/core-tests-android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core-tests-android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core-tests-android/src/androidTest/java/com/powersync/AndroidDatabaseTest.kt b/core-tests-android/src/androidTest/java/com/powersync/AndroidDatabaseTest.kt new file mode 100644 index 00000000..0ca5d9ea --- /dev/null +++ b/core-tests-android/src/androidTest/java/com/powersync/AndroidDatabaseTest.kt @@ -0,0 +1,94 @@ +package com.powersync + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import app.cash.turbine.turbineScope +import com.powersync.db.schema.Schema +import com.powersync.testutils.UserRow +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.After + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* +import org.junit.Before + +@RunWith(AndroidJUnit4::class) +class AndroidDatabaseTest { + private lateinit var database: PowerSyncDatabase + + @Before + fun setupDatabase() { + database = + PowerSyncDatabase( + factory = DatabaseDriverFactory(InstrumentationRegistry.getInstrumentation().targetContext), + schema = Schema(UserRow.table), + dbFilename = "testdb", + ) + + runBlocking { + database.disconnectAndClear(true) + } + } + + @After + fun tearDown() { + runBlocking { database.disconnectAndClear(true) } + } + + @Test + fun testLinksPowerSync() = + runTest { + database.get("SELECT powersync_rs_version() AS r;") { it.getString(0)!! } + } + + @Test + fun testTableUpdates() = + runTest { + turbineScope { + val query = database.watch("SELECT * FROM users") { UserRow.from(it) }.testIn(this) + + // Wait for initial query + assertEquals(0, query.awaitItem().size) + + database.execute( + "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)", + listOf("Test", "test@example.org"), + ) + assertEquals(1, query.awaitItem().size) + + database.writeTransaction { + it.execute( + "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)", + listOf("Test2", "test2@example.org"), + ) + it.execute( + "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)", + listOf("Test3", "test3@example.org"), + ) + } + + assertEquals(3, query.awaitItem().size) + + try { + database.writeTransaction { + it.execute("DELETE FROM users;") + it.execute("syntax error, revert please") + } + } catch (e: Exception) { + // Ignore + } + + database.execute( + "INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)", + listOf("Test4", "test4@example.org"), + ) + assertEquals(4, query.awaitItem().size) + + query.expectNoEvents() + query.cancel() + } + } +} \ No newline at end of file diff --git a/core-tests-android/src/androidTest/java/com/powersync/testutils/UserRow.kt b/core-tests-android/src/androidTest/java/com/powersync/testutils/UserRow.kt new file mode 100644 index 00000000..caf65765 --- /dev/null +++ b/core-tests-android/src/androidTest/java/com/powersync/testutils/UserRow.kt @@ -0,0 +1,23 @@ +package com.powersync.testutils + +import com.powersync.db.SqlCursor +import com.powersync.db.getString +import com.powersync.db.schema.Column +import com.powersync.db.schema.Table + +data class UserRow( + val id: String, + val name: String, + val email: String, +) { + companion object { + fun from(cursor: SqlCursor): UserRow = + UserRow( + id = cursor.getString("id"), + name = cursor.getString("name"), + email = cursor.getString("email"), + ) + + val table = Table(name = "users", columns = listOf(Column.text("name"), Column.text("email"))) + } +} diff --git a/core-tests-android/src/main/AndroidManifest.xml b/core-tests-android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..eaf882a7 --- /dev/null +++ b/core-tests-android/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/core-tests-android/src/main/res/drawable/ic_launcher_background.xml b/core-tests-android/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..956b344d --- /dev/null +++ b/core-tests-android/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-tests-android/src/main/res/drawable/ic_launcher_foreground.xml b/core-tests-android/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..1ee14938 --- /dev/null +++ b/core-tests-android/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..50ec8862 --- /dev/null +++ b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..50ec8862 --- /dev/null +++ b/core-tests-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..b2dfe3d1 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..62b611da Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..1b9a6956 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9287f508 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9126ae37 Binary files /dev/null and b/core-tests-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/core-tests-android/src/main/res/values-night/themes.xml b/core-tests-android/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..62fbedd2 --- /dev/null +++ b/core-tests-android/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/core-tests-android/src/main/res/values/colors.xml b/core-tests-android/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/core-tests-android/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/core-tests-android/src/main/res/values/strings.xml b/core-tests-android/src/main/res/values/strings.xml new file mode 100644 index 00000000..859dfd76 --- /dev/null +++ b/core-tests-android/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Core Android tests + \ No newline at end of file diff --git a/core-tests-android/src/main/res/values/themes.xml b/core-tests-android/src/main/res/values/themes.xml new file mode 100644 index 00000000..25692b5b --- /dev/null +++ b/core-tests-android/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 413e3749..4f4ce091 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -276,6 +276,7 @@ android { libs.versions.android.minSdk .get() .toInt() + consumerProguardFiles("proguard-rules.pro") @Suppress("UnstableApiUsage") externalNativeBuild { diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro new file mode 100644 index 00000000..aeed3990 --- /dev/null +++ b/core/proguard-rules.pro @@ -0,0 +1,11 @@ +# If the app calls the JNI method to initialize driver bindings, keep that method +# (so that it can be linked through JNI) and the other methods called from native +# code. +-if class com.powersync.DatabaseDriverFactory { + private void setupSqliteBinding(); +} +-keep class com.powersync.DatabaseDriverFactory { + private void setupSqliteBinding(); + private void onTableUpdate(java.lang.String); + private void onTransactionCommit(boolean); +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73d20659..a4db612f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,7 @@ download-plugin = "5.5.0" grammerKit = "0.1.12" mokkery = "2.4.0" kotlinter = "4.4.1" +keeper = "0.16.1" # Sample - Android androidx-core = "1.13.1" @@ -46,7 +47,9 @@ androidx-activity-compose = "1.9.2" androidx-appcompat = "1.7.0" androidx-espresso-core = "3.6.1" androidx-material = "1.12.0" -androidx-test-junit = "1.2.1" +androidx-test-runner = "1.6.2" +androidx-test-rules = "1.6.1" +junitVersion = "1.2.1" [libraries] configuration-annotations = { module = "co.touchlab.skie:configuration-annotations", version.ref = "configurationAnnotations" } @@ -56,7 +59,8 @@ powersync-sqlite-core-android = { module = "co.powersync:powersync-sqlite-core", mavenPublishPlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" } test-junit = { group = "junit", name = "junit", version.ref = "junit" } -test-junitKtx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-junit" } +test-android-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } +test-android-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" } test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -101,6 +105,7 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose-preview" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } [plugins] androidApplication = { id = "com.android.application", version.ref = "android-gradle-plugin" } @@ -119,6 +124,8 @@ mavenPublishPlugin = { id = "com.vanniktech.maven.publish", version.ref = "maven downloadPlugin = { id = "de.undercouch.download", version.ref = "download-plugin" } mokkery = { id = "dev.mokkery", version.ref = "mokkery" } kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter" } +keeper = { id = "com.slack.keeper", version.ref = "keeper" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } [bundles] sqldelight = [ diff --git a/settings.gradle.kts b/settings.gradle.kts index 06426d2a..23fcbe5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,6 +21,7 @@ dependencyResolutionManagement { rootProject.name = "powersync-root" include(":core") +include(":core-tests-android") include(":connectors:supabase") include(":dialect")