Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6421b01ba | ||
|
|
9bc4a8c81b | ||
|
|
8cae135784 | ||
|
|
eba5d64998 | ||
|
|
8eec09e832 | ||
|
|
69537da95b | ||
|
|
a679b88497 | ||
|
|
e570b9d88f | ||
|
|
ce37523e11 | ||
|
|
3601c190ac | ||
|
|
b0044dd0fc | ||
|
|
b2c2be45bf | ||
|
|
6f219b6da2 | ||
|
|
8d6c567c84 | ||
|
|
2b2010a22c | ||
|
|
27af06e180 | ||
|
|
499088cf43 | ||
|
|
ce579f21f9 | ||
|
|
5a4643dc57 | ||
|
|
2a65dc4a9e | ||
|
|
d08aa25f76 | ||
|
|
410c7ccd1d | ||
|
|
5443cc1486 | ||
|
|
683cb0cfbb | ||
|
|
cbf89b4158 |
@@ -1,3 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
**/dependencies/** linguist-generated
|
||||
**/clawicons/** linguist-generated
|
||||
android/src/main/baseline-prof.txt linguist-generated
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 127 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
@@ -10,6 +10,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Clean secrets
|
||||
run: scripts/signing-cleanup.sh
|
||||
|
||||
- name: Get Changelog Entry
|
||||
- name: Get changelog entry
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@b97ce03a10d9bdbb07beb491c76a5a01d78cd3ef # v2.2.2
|
||||
with:
|
||||
|
||||
+10
-1
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.10.0] - 2022-11-30
|
||||
|
||||
### Changed
|
||||
|
||||
- Configure Coil image loader with better caching settings
|
||||
- Refresh packaged baseline profile
|
||||
|
||||
## [1.9.0] - 2022-11-28
|
||||
|
||||
### Changed
|
||||
@@ -81,7 +88,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Initial Play Store release
|
||||
|
||||
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/1.9.0...HEAD
|
||||
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/1.10.0...HEAD
|
||||
|
||||
[1.10.0]: https://github.com/msfjarvis/compose-lobsters/compare/1.9.0...1.10.0
|
||||
|
||||
[1.9.0]: https://github.com/msfjarvis/compose-lobsters/compare/1.8.0...1.9.0
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
Unofficial Android app for read-only access to [lobste.rs](https://lobste.rs), built with [Jetpack Compose](https://developer.android.com/jetpack/compose).
|
||||
|
||||

|
||||
<img src="https://github.com/msfjarvis/compose-lobsters/blob/main/.github/readme_feature.webp"
|
||||
alt="Get it on Google Play"
|
||||
height="550" />
|
||||
|
||||
## Download
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-dontobfuscate
|
||||
@@ -33,9 +33,10 @@ android {
|
||||
}
|
||||
buildTypes {
|
||||
create("benchmark") {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks += listOf("release")
|
||||
signingConfig = signingConfigs["debug"]
|
||||
matchingFallbacks += "release"
|
||||
isDebuggable = false
|
||||
proguardFile("benchmark-rules.pro")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +60,7 @@ dependencies {
|
||||
implementation(libs.androidx.lifecycle.compose)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.coil)
|
||||
implementation(libs.copydown)
|
||||
|
||||
Generated
+5133
-3100
File diff suppressed because it is too large
Load Diff
@@ -20,11 +20,14 @@ import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
@@ -54,7 +57,10 @@ import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
|
||||
import dev.msfjarvis.claw.common.user.UserProfile
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(
|
||||
ExperimentalComposeUiApi::class,
|
||||
ExperimentalMaterial3Api::class,
|
||||
)
|
||||
@Composable
|
||||
fun LobstersApp(
|
||||
urlLauncher: UrlLauncher,
|
||||
@@ -156,7 +162,7 @@ fun LobstersApp(
|
||||
isVisible = navItems.any { it.route == currentDestination },
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
modifier = modifier.semantics { testTagsAsResourceId = true },
|
||||
) { paddingValues ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
@@ -174,7 +180,6 @@ fun LobstersApp(
|
||||
lazyPagingItems = hottestPosts,
|
||||
listState = hottestListState,
|
||||
isPostSaved = viewModel::isPostSaved,
|
||||
reloadPosts = viewModel::refreshHottestPosts,
|
||||
postActions = postActions,
|
||||
)
|
||||
}
|
||||
@@ -186,7 +191,6 @@ fun LobstersApp(
|
||||
lazyPagingItems = newestPosts,
|
||||
listState = newestListState,
|
||||
isPostSaved = viewModel::isPostSaved,
|
||||
reloadPosts = viewModel::refreshNewestPosts,
|
||||
postActions = postActions,
|
||||
)
|
||||
}
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.navigation.NavController
|
||||
import dev.msfjarvis.claw.android.ui.navigation.Destinations
|
||||
|
||||
@@ -73,7 +74,8 @@ fun ClawNavigationBar(
|
||||
if (navItem.route != Destinations.startDestination.route) {
|
||||
navController.navigate(navItem.route)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.testTag(navItem.label.uppercase()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,12 @@ fun NetworkPosts(
|
||||
lazyPagingItems: LazyPagingItems<LobstersPost>,
|
||||
listState: LazyListState,
|
||||
isPostSaved: suspend (SavedPost) -> Boolean,
|
||||
reloadPosts: () -> Unit,
|
||||
postActions: PostActions,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val refreshLoadState = lazyPagingItems.loadState.refresh
|
||||
val isRefreshing = refreshLoadState == LoadState.Loading
|
||||
val pullRefreshState = rememberPullRefreshState(isRefreshing, reloadPosts)
|
||||
val pullRefreshState = rememberPullRefreshState(isRefreshing, lazyPagingItems::refresh)
|
||||
Box(modifier = modifier.fillMaxSize().pullRefresh(pullRefreshState)) {
|
||||
if (lazyPagingItems.itemCount == 0 && refreshLoadState is LoadState.Error) {
|
||||
NetworkError(
|
||||
|
||||
@@ -37,16 +37,10 @@ constructor(
|
||||
private val pagingSourceFactory: LobstersPagingSource.Factory,
|
||||
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
private var hottestPostsPagingSource: LobstersPagingSource? = null
|
||||
private var newestPostsPagingSource: LobstersPagingSource? = null
|
||||
private val hottestPostsPager =
|
||||
Pager(PagingConfig(20)) {
|
||||
pagingSourceFactory.create(api::getHottestPosts).also { hottestPostsPagingSource = it }
|
||||
}
|
||||
Pager(PagingConfig(pageSize = 20)) { pagingSourceFactory.create(api::getHottestPosts) }
|
||||
private val newestPostsPager =
|
||||
Pager(PagingConfig(20)) {
|
||||
pagingSourceFactory.create(api::getHottestPosts).also { newestPostsPagingSource = it }
|
||||
}
|
||||
Pager(PagingConfig(pageSize = 20)) { pagingSourceFactory.create(api::getHottestPosts) }
|
||||
|
||||
val hottestPosts
|
||||
get() = hottestPostsPager.flow
|
||||
@@ -104,12 +98,4 @@ constructor(
|
||||
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshHottestPosts() {
|
||||
hottestPostsPagingSource?.invalidate()
|
||||
}
|
||||
|
||||
fun refreshNewestPosts() {
|
||||
newestPostsPagingSource?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
|
||||
#
|
||||
versioning-plugin.versionCode=10900
|
||||
versioning-plugin.versionName=1.9.0
|
||||
versioning-plugin.versionCode=11000
|
||||
versioning-plugin.versionName=1.10.0
|
||||
|
||||
@@ -25,8 +25,8 @@ dependencies {
|
||||
implementation(libs.dagger)
|
||||
implementation(libs.javax.inject)
|
||||
testImplementation(testFixtures(libs.eithernet))
|
||||
testImplementation(kotlin("test-junit"))
|
||||
testImplementation(libs.kotlinx.coroutines.core)
|
||||
testImplementation(libs.kotest.assertions.core)
|
||||
testImplementation(libs.kotest.runner.junit5)
|
||||
testImplementation(libs.kotlinx.serialization.json)
|
||||
testImplementation(libs.retrofit.kotlinxSerializationConverter)
|
||||
}
|
||||
|
||||
@@ -11,42 +11,40 @@ import com.slack.eithernet.test.newEitherNetController
|
||||
import dev.msfjarvis.claw.model.LobstersPost
|
||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
||||
import dev.msfjarvis.claw.model.User
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.types.shouldBeTypeOf
|
||||
|
||||
class ApiTest {
|
||||
class ApiTest : FunSpec() {
|
||||
private val wrapper = ApiWrapper(newEitherNetController())
|
||||
private val api
|
||||
get() = wrapper.api
|
||||
|
||||
@Test
|
||||
fun `api gets correct number of items`() = runBlocking {
|
||||
val posts = api.getHottestPosts(1)
|
||||
assertIs<Success<List<LobstersPost>>>(posts)
|
||||
assertEquals(25, posts.value.size)
|
||||
}
|
||||
init {
|
||||
test("api gets correct number of items") {
|
||||
val posts = api.getHottestPosts(1)
|
||||
posts.shouldBeTypeOf<Success<List<LobstersPost>>>()
|
||||
posts.value shouldHaveSize 25
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `posts with no urls`() = runBlocking {
|
||||
val posts = api.getHottestPosts(1)
|
||||
assertIs<Success<List<LobstersPost>>>(posts)
|
||||
val commentsOnlyPosts = posts.value.asSequence().filter { it.url.isEmpty() }.toSet()
|
||||
assertEquals(2, commentsOnlyPosts.size)
|
||||
}
|
||||
test("posts with no urls") {
|
||||
val posts = api.getHottestPosts(1)
|
||||
posts.shouldBeTypeOf<Success<List<LobstersPost>>>()
|
||||
val commentsOnlyPosts = posts.value.asSequence().filter { it.url.isEmpty() }.toSet()
|
||||
commentsOnlyPosts shouldHaveSize 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `post details with comments`() = runBlocking {
|
||||
val postDetails = api.getPostDetails("tdfoqh")
|
||||
assertIs<Success<LobstersPostDetails>>(postDetails)
|
||||
assertEquals(7, postDetails.value.comments.size)
|
||||
}
|
||||
test("post details with comments") {
|
||||
val postDetails = api.getPostDetails("tdfoqh")
|
||||
postDetails.shouldBeTypeOf<Success<LobstersPostDetails>>()
|
||||
postDetails.value.comments shouldHaveSize 7
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get user details`() = runBlocking {
|
||||
val user = api.getUser("msfjarvis")
|
||||
assertIs<Success<User>>(user)
|
||||
assertEquals("msfjarvis", user.value.username)
|
||||
test("get user details") {
|
||||
val user = api.getUser("msfjarvis")
|
||||
user.shouldBeTypeOf<Success<User>>()
|
||||
user.value.username shouldBe "msfjarvis"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class ApiWrapper(controller: EitherNetController<LobstersApi>) {
|
||||
|
||||
init {
|
||||
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
|
||||
controller.enqueue(LobstersApi::getNewestPosts) { success(hottest) }
|
||||
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
|
||||
controller.enqueue(LobstersApi::getPostDetails) { success(postDetails) }
|
||||
controller.enqueue(LobstersApi::getUser) { success(user) }
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ android {
|
||||
buildTypes {
|
||||
create("benchmark") {
|
||||
isDebuggable = true
|
||||
signingConfig = getByName("debug").signingConfig
|
||||
matchingFallbacks += listOf("release")
|
||||
signingConfig = signingConfigs["debug"]
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,14 @@ import androidx.test.filters.LargeTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@LargeTest
|
||||
@Ignore(
|
||||
"These tests fail saying that ProfileInstaller 1.3.0-alpha02 should be used which we already are"
|
||||
)
|
||||
class BaselineProfileBenchmark {
|
||||
@get:Rule val benchmarkRule = MacrobenchmarkRule()
|
||||
private lateinit var device: UiDevice
|
||||
|
||||
@@ -9,19 +9,51 @@ package dev.msfjarvis.claw.benchmark
|
||||
import androidx.benchmark.macro.MacrobenchmarkScope
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.Until
|
||||
|
||||
const val PACKAGE_NAME = "dev.msfjarvis.claw.android"
|
||||
private const val AWAIT_TIMEOUT = 10_000L
|
||||
private const val SAVE_BUTTON_DESC = "Add to saved posts"
|
||||
private const val NAV_ID_HOTTEST = "HOTTEST"
|
||||
private const val NAV_ID_NEWEST = "NEWEST"
|
||||
private const val NAV_ID_SAVED = "SAVED"
|
||||
private const val COMMENT_BUTTON_DESC = "Open comments"
|
||||
|
||||
fun MacrobenchmarkScope.exploreUI(device: UiDevice) {
|
||||
startActivityAndWait()
|
||||
device.run {
|
||||
listOf("HOTTEST", "NEWEST", "SAVED").forEach { desc ->
|
||||
findObject(By.desc(desc)).click()
|
||||
waitForIdle()
|
||||
}
|
||||
findObject(By.desc("HOTTEST")).click()
|
||||
waitForIdle()
|
||||
findObjects(By.desc("Open comments")).first().click()
|
||||
waitForIdle()
|
||||
savePosts()
|
||||
|
||||
exploreScreens()
|
||||
|
||||
returnToHottestScreen()
|
||||
|
||||
openCommentsScreen()
|
||||
}
|
||||
}
|
||||
|
||||
private fun UiDevice.waitForSubmitterName() {
|
||||
wait(Until.hasObject(By.textContains("Submitted by")), AWAIT_TIMEOUT)
|
||||
}
|
||||
|
||||
private fun UiDevice.savePosts() {
|
||||
waitForSubmitterName()
|
||||
findObjects(By.desc(SAVE_BUTTON_DESC)).forEach { btn -> btn.click() }
|
||||
}
|
||||
|
||||
private fun UiDevice.exploreScreens() {
|
||||
listOf(NAV_ID_HOTTEST, NAV_ID_NEWEST, NAV_ID_SAVED).forEach { tag ->
|
||||
findObject(By.res(tag)).click()
|
||||
waitForSubmitterName()
|
||||
}
|
||||
}
|
||||
|
||||
private fun UiDevice.returnToHottestScreen() {
|
||||
findObject(By.res(NAV_ID_HOTTEST)).click()
|
||||
waitForSubmitterName()
|
||||
}
|
||||
|
||||
private fun UiDevice.openCommentsScreen() {
|
||||
findObjects(By.desc(COMMENT_BUTTON_DESC)).first().click()
|
||||
waitForSubmitterName()
|
||||
}
|
||||
|
||||
+4
-27
@@ -10,10 +10,9 @@ package dev.msfjarvis.aps.gradle
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import com.android.build.api.dsl.Lint
|
||||
import com.android.build.api.dsl.TestExtension
|
||||
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
|
||||
import com.android.build.api.variant.LibraryAndroidComponentsExtension
|
||||
import com.android.build.gradle.TestedExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
|
||||
import org.gradle.android.AndroidCacheFixPlugin
|
||||
import org.gradle.api.JavaVersion
|
||||
@@ -35,21 +34,13 @@ class AndroidCommonPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
project.configureSlimTests()
|
||||
project.pluginManager.apply(AndroidCacheFixPlugin::class)
|
||||
project.extensions.findByType<BaseAppModuleExtension>()?.run { lint.configureLint(project) }
|
||||
project.extensions.findByType<LibraryExtension>()?.run { lint.configureLint(project) }
|
||||
project.extensions.findByType<TestedExtension>()?.run {
|
||||
project.extensions.findByType<BaseExtension>()?.run {
|
||||
compileSdkVersion(COMPILE_SDK)
|
||||
defaultConfig {
|
||||
minSdk = MIN_SDK
|
||||
targetSdk = TARGET_SDK
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
named("main") { java.srcDirs("src/main/kotlin") }
|
||||
named("test") { java.srcDirs("src/test/kotlin") }
|
||||
named("androidTest") { java.srcDirs("src/androidTest/kotlin") }
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources.excludes.add("**/*.version")
|
||||
resources.excludes.add("**/*.txt")
|
||||
@@ -69,22 +60,8 @@ class AndroidCommonPlugin : Plugin<Project> {
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
}
|
||||
project.extensions.findByType<TestExtension>()?.run {
|
||||
compileSdk = COMPILE_SDK
|
||||
defaultConfig {
|
||||
minSdk = MIN_SDK
|
||||
targetSdk = TARGET_SDK
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
testOptions {
|
||||
animationsDisabled = true
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
}
|
||||
project.extensions.findByType<BaseAppModuleExtension>()?.run { lint.configureLint(project) }
|
||||
project.extensions.findByType<LibraryExtension>()?.run { lint.configureLint(project) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ class KotlinCommonPlugin : Plugin<Project> {
|
||||
withType<Test>().configureEach {
|
||||
maxParallelForks = Runtime.getRuntime().availableProcessors() * 2
|
||||
testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,13 @@ import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.disk.DiskCache
|
||||
import coil.memory.MemoryCache
|
||||
import com.deliveryhero.whetstone.app.ApplicationScope
|
||||
import com.squareup.anvil.annotations.ContributesMultibinding
|
||||
import dev.msfjarvis.claw.core.injection.AppPlugin
|
||||
import javax.inject.Inject
|
||||
|
||||
class CoilAppPlugin : AppPlugin {
|
||||
@ContributesMultibinding(ApplicationScope::class)
|
||||
class CoilAppPlugin @Inject constructor() : AppPlugin {
|
||||
override fun apply(application: Application) {
|
||||
Coil.setImageLoader {
|
||||
ImageLoader.Builder(application)
|
||||
|
||||
@@ -33,7 +33,8 @@ dependencies {
|
||||
implementation(projects.core)
|
||||
implementation(libs.sqldelight.androidDriver)
|
||||
implementation(libs.sqldelight.primitiveAdapters)
|
||||
testImplementation(libs.sqldelight.jvmDriver)
|
||||
testImplementation(libs.kotest.assertions.core)
|
||||
testImplementation(libs.kotest.runner.junit5)
|
||||
testImplementation(libs.kotlinx.coroutines.core)
|
||||
testImplementation(kotlin("test-junit"))
|
||||
testImplementation(libs.sqldelight.jvmDriver)
|
||||
}
|
||||
|
||||
+91
-110
@@ -10,129 +10,110 @@ import app.cash.sqldelight.adapter.primitive.IntColumnAdapter
|
||||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
import dev.msfjarvis.claw.database.LobstersDatabase
|
||||
import dev.msfjarvis.claw.database.model.TagsAdapter
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeEmpty
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
|
||||
class SqlDelightQueriesTest {
|
||||
class SqlDelightQueriesTest : FunSpec() {
|
||||
|
||||
private lateinit var postQueries: SavedPostQueries
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
LobstersDatabase.Schema.create(driver)
|
||||
val database =
|
||||
LobstersDatabase(
|
||||
driver,
|
||||
SavedPost.Adapter(IntColumnAdapter, TagsAdapter()),
|
||||
)
|
||||
postQueries = database.savedPostQueries
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectCount() = runBlocking {
|
||||
val posts = createTestData(5)
|
||||
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
val postCount = postQueries.selectCount().executeAsOne()
|
||||
assertEquals(5, postCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertIntoDatabase() = runBlocking {
|
||||
// Get 5 posts
|
||||
val posts = createTestData(5)
|
||||
|
||||
// Insert posts into DB
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
// Check post count
|
||||
val postsCount = postQueries.selectCount().executeAsOne()
|
||||
assertEquals(5, postsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceFromDatabase() = runBlocking {
|
||||
// Get 1 post
|
||||
val post = createTestData(1)[0]
|
||||
|
||||
// Insert post into DB
|
||||
postQueries.insertOrReplacePost(post)
|
||||
|
||||
// Create a new post and try replacing it
|
||||
val newPost = post.copy(submitterName = "Fake name")
|
||||
postQueries.insertOrReplacePost(newPost)
|
||||
|
||||
// Check post count
|
||||
val postsCount = postQueries.selectCount().executeAsOne()
|
||||
assertEquals(1, postsCount)
|
||||
|
||||
// Check if post is updated
|
||||
val postFromDb = postQueries.selectPost(post.shortId).executeAsOne()
|
||||
assertEquals("Fake name", postFromDb.submitterName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectPost() = runBlocking {
|
||||
// Get 1 post
|
||||
val post = createTestData(1)[0]
|
||||
|
||||
// Insert post into DB
|
||||
postQueries.insertOrReplacePost(post)
|
||||
|
||||
val postFromDb = postQueries.selectAllPosts().executeAsOne()
|
||||
assertEquals("test_id_1", postFromDb.shortId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectAllPosts() = runBlocking {
|
||||
// Get 5 post
|
||||
val posts = createTestData(5)
|
||||
|
||||
// Insert posts into DB
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
val postsFromDb = postQueries.selectAllPosts().executeAsList()
|
||||
|
||||
// Check if all posts have correct shortId
|
||||
for (i in 1..5) {
|
||||
assertEquals("test_id_$i", postsFromDb[i - 1].shortId)
|
||||
init {
|
||||
beforeEach {
|
||||
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
LobstersDatabase.Schema.create(driver)
|
||||
val database =
|
||||
LobstersDatabase(
|
||||
driver,
|
||||
SavedPost.Adapter(IntColumnAdapter, TagsAdapter()),
|
||||
)
|
||||
postQueries = database.savedPostQueries
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePost() = runBlocking {
|
||||
// Create 3 posts and insert them to DB
|
||||
val posts = createTestData(3)
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
test("add and count posts") {
|
||||
val posts = createTestData(5)
|
||||
|
||||
// Delete 2nd post
|
||||
postQueries.deletePost("test_id_2")
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
val postsFromDB = postQueries.selectAllPosts().executeAsList()
|
||||
val postCount = postQueries.selectCount().executeAsOne()
|
||||
|
||||
// Check if size is 2, and only the correct post is deleted
|
||||
assertEquals(2, postsFromDB.size)
|
||||
assertEquals("test_id_1", postsFromDB[0].shortId)
|
||||
assertEquals("test_id_3", postsFromDB[1].shortId)
|
||||
}
|
||||
postCount shouldBe 5
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAllPost() = runBlocking {
|
||||
// Create 5 posts and insert them to DB
|
||||
val posts = createTestData(5)
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
test("update post in database") {
|
||||
// Get 1 post
|
||||
val post = createTestData(1)[0]
|
||||
|
||||
// Delete all posts
|
||||
postQueries.deleteAllPosts()
|
||||
// Insert post into DB
|
||||
postQueries.insertOrReplacePost(post)
|
||||
|
||||
val postsCount = postQueries.selectCount().executeAsOne()
|
||||
// Create a new post and try replacing it
|
||||
val newPost = post.copy(submitterName = "Fake name")
|
||||
postQueries.insertOrReplacePost(newPost)
|
||||
|
||||
// Check if db is empty
|
||||
assertEquals(0, postsCount)
|
||||
// Check post count
|
||||
val postsCount = postQueries.selectCount().executeAsOne()
|
||||
postsCount shouldBe 1
|
||||
|
||||
// Check if post is updated
|
||||
val postFromDb = postQueries.selectPost(post.shortId).executeAsOne()
|
||||
|
||||
postFromDb.submitterName shouldBe "Fake name"
|
||||
}
|
||||
|
||||
test("get post from db") {
|
||||
// Get 1 post
|
||||
val post = createTestData(1)[0]
|
||||
|
||||
// Insert post into DB
|
||||
postQueries.insertOrReplacePost(post)
|
||||
|
||||
val postFromDb = postQueries.selectAllPosts().executeAsOne()
|
||||
postFromDb.shortId shouldBe "test_id_1"
|
||||
}
|
||||
|
||||
test("get multiple posts from db") {
|
||||
// Get 5 post
|
||||
val posts = createTestData(5)
|
||||
|
||||
// Insert posts into DB
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
val postsFromDb = postQueries.selectAllPosts().executeAsList()
|
||||
|
||||
// Check if all posts have correct shortId
|
||||
postsFromDb.forEachIndexed { index, post -> post.shortId shouldBe "test_id_${index.inc()}" }
|
||||
}
|
||||
|
||||
test("delete post") {
|
||||
// Create 3 posts and insert them to DB
|
||||
val posts = createTestData(3)
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
// Delete 2nd post
|
||||
postQueries.deletePost("test_id_2")
|
||||
|
||||
val postsFromDB = postQueries.selectAllPosts().executeAsList()
|
||||
|
||||
// Check if size is 2, and only the correct post is deleted
|
||||
postsFromDB shouldHaveSize 2
|
||||
postsFromDB[0].shortId shouldBe "test_id_1"
|
||||
postsFromDB[1].shortId shouldBe "test_id_3"
|
||||
}
|
||||
|
||||
test("delete all posts") {
|
||||
// Create 5 posts and insert them to DB
|
||||
val posts = createTestData(5)
|
||||
posts.forEach { postQueries.insertOrReplacePost(it) }
|
||||
|
||||
// Delete all posts
|
||||
postQueries.deleteAllPosts()
|
||||
|
||||
val dbPosts = postQueries.selectAllPosts().executeAsList()
|
||||
|
||||
dbPosts.shouldBeEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTestData(count: Int): ArrayList<SavedPost> {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues></ManuallySuppressedIssues>
|
||||
<CurrentIssues>
|
||||
<ID>LongMethod:LobstersApp.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun LobstersApp( urlLauncher: UrlLauncher, htmlConverter: HTMLConverter, setWebUri: (String?) -> Unit, modifier: Modifier = Modifier, viewModel: ClawViewModel = injectedViewModel(), )</ID>
|
||||
<ID>LongParameterList:NetworkPosts.kt$( lazyPagingItems: LazyPagingItems<LobstersPost>, listState: LazyListState, isPostSaved: suspend (SavedPost) -> Boolean, reloadPosts: () -> Unit, postActions: PostActions, modifier: Modifier = Modifier, )</ID>
|
||||
<ID>LongMethod:LobstersApp.kt$@OptIn( ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class, ) @Composable fun LobstersApp( urlLauncher: UrlLauncher, htmlConverter: HTMLConverter, setWebUri: (String?) -> Unit, modifier: Modifier = Modifier, viewModel: ClawViewModel = injectedViewModel(), )</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
||||
+9
-4
@@ -53,9 +53,6 @@ android.nonTransitiveRClass=true
|
||||
# instead of using a parameter wildcard which keeps them all.
|
||||
android.useMinimalKeepRules=true
|
||||
|
||||
# Enable resource optimizations for release build
|
||||
android.enableResourceOptimizations=true
|
||||
|
||||
# Default Android build features
|
||||
android.defaults.buildfeatures.buildconfig=false
|
||||
android.defaults.buildfeatures.aidl=false
|
||||
@@ -63,8 +60,16 @@ android.defaults.buildfeatures.renderscript=false
|
||||
android.defaults.buildfeatures.resvalues=false
|
||||
android.defaults.buildfeatures.shaders=false
|
||||
|
||||
# Experimental features
|
||||
android.disableEarlyManifestParsing=true
|
||||
android.enablePartialRIncrementalBuilds=true
|
||||
# TODO: enable once we're back on AGP 8.x
|
||||
# android.experimental.testOptions.uninstallIncompatibleApks=true
|
||||
android.enableBuildConfigAsBytecode=true
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
|
||||
# Disable warnings about unsupported features, we know what we're doing
|
||||
android.suppressUnsupportedOptionWarnings=android.enableR8.fullMode,android.enableResourceOptimizations,android.nonTransitiveRClass,android.suppressUnsupportedOptionWarnings,android.sdk.channel
|
||||
android.suppressUnsupportedOptionWarnings=android.disableEarlyManifestParsing,android.enablePartialRIncrementalBuilds,android.enableBuildConfigAsBytecode,android.experimental.enableNewResourceShrinker.preciseShrinking,android.suppressUnsupportedOptionWarnings
|
||||
|
||||
# Maven publishing
|
||||
GROUP=com.github.android-password-store
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
accompanist = "0.27.1"
|
||||
accompanist = "0.28.0"
|
||||
# @pin https://issuetracker.google.com/issues/259556213
|
||||
agp = "7.3.1"
|
||||
androidx-test = "1.5.0"
|
||||
@@ -8,6 +8,7 @@ coil = "2.2.2"
|
||||
composeCompiler = "1.4.0-alpha02"
|
||||
coroutines = "1.6.4"
|
||||
dagger = "2.44.2"
|
||||
kotest = "5.5.4"
|
||||
kotlin = "1.7.21"
|
||||
# @pin Needs to be aligned with Retrofit
|
||||
okhttp = "3.14.9"
|
||||
@@ -15,7 +16,7 @@ retrofit = "2.9.0"
|
||||
richtext = "0.15.0"
|
||||
serialization = "1.4.1"
|
||||
sqldelight = "2.0.0-alpha04"
|
||||
whetstone = "0.3.0"
|
||||
whetstone = "0.5.0-SNAPSHOT"
|
||||
workmanager = "2.8.0-beta02"
|
||||
|
||||
[libraries]
|
||||
@@ -68,6 +69,8 @@ eithernet = "com.slack.eithernet:eithernet:1.2.1"
|
||||
javapoet = "com.squareup:javapoet:1.13.0"
|
||||
javax-inject = "javax.inject:javax.inject:1"
|
||||
jsoup = "org.jsoup:jsoup:1.15.3"
|
||||
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
|
||||
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
|
||||
@@ -87,4 +90,4 @@ whetstone = { module = "com.deliveryhero.whetstone:whetstone", version.ref = "wh
|
||||
android-test = { id = "com.android.test", version.ref = "agp" }
|
||||
anvil = "com.squareup.anvil:2.4.2"
|
||||
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||
whetstone = { id = "com.deliveryhero.whetstone", version.ref = "whetstone" }
|
||||
whetstone = { id = "dev.msfjarvis.whetstone", version.ref = "whetstone" }
|
||||
|
||||
+8
-27
@@ -6,8 +6,6 @@
|
||||
*/
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
import me.champeau.gradle.igp.gitRepositories
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
@@ -43,38 +41,17 @@ pluginManagement {
|
||||
includeModule("gradle.plugin.org.gradle.android", "android-cache-fix-gradle-plugin")
|
||||
includeModule("com.sergei-lapin.napt", "com.sergei-lapin.napt.gradle.plugin")
|
||||
includeModule("com.sergei-lapin.napt", "gradle")
|
||||
includeModule("me.champeau.includegit", "me.champeau.includegit.gradle.plugin")
|
||||
includeModule("me.champeau.gradle.includegit", "plugin")
|
||||
}
|
||||
}
|
||||
exclusiveContent {
|
||||
forRepository { maven("https://oss.sonatype.org/content/repositories/snapshots/") }
|
||||
filter { includeGroup("dev.msfjarvis.whetstone") }
|
||||
}
|
||||
includeBuild("build-logic")
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins { id("me.champeau.includegit") version "0.1.5" }
|
||||
|
||||
gitRepositories {
|
||||
checkoutsDirectory.set(rootProject.projectDir.resolve("build/checkouts"))
|
||||
include("whetstone") {
|
||||
uri.set("https://github.com/msfjarvis/whetstone")
|
||||
tag.set("msfjarvis-2022-11-27")
|
||||
includeBuild {
|
||||
dependencySubstitution {
|
||||
for (module in
|
||||
listOf(
|
||||
"whetstone",
|
||||
"whetstone-compiler",
|
||||
"whetstone-compose",
|
||||
"whetstone-worker",
|
||||
)) {
|
||||
substitute(module("com.deliveryhero.whetstone:$module")).using(project(":$module"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
@@ -155,6 +132,10 @@ dependencyResolutionManagement {
|
||||
includeModule("gradle.plugin.org.gradle.android", "android-cache-fix-gradle-plugin")
|
||||
}
|
||||
}
|
||||
exclusiveContent {
|
||||
forRepository { maven("https://oss.sonatype.org/content/repositories/snapshots/") }
|
||||
filter { includeGroup("dev.msfjarvis.whetstone") }
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user