diff --git a/build.gradle.kts b/build.gradle.kts index 9b33a5f..a013d02 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,8 @@ plugins { alias(libs.plugins.kotlinx.benchmark) apply false alias(libs.plugins.allopen) apply false alias(libs.plugins.kotlin.plugin.parcelize) apply false + id("com.google.devtools.ksp") version "2.0.0-1.0.22" apply false + id("dev.mokkery") version "2.1.1" apply false } buildscript { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 818b490..668b9aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -105,9 +105,10 @@ ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", ktor-serialization-xml = { module = "io.ktor:ktor-serialization-kotlinx-xml", version.ref = "ktor" } circuit-foundation = { module = "com.slack.circuit:circuit-foundation", version.ref = "circuit" } swipe = { module = "me.saket.swipe:swipe", version = "1.3.0" } -mockk = {module = "io.mockk:mockk", version.ref = "mockk"} -jetbrains-compose-runtime = {module = "org.jetbrains.compose.runtime:runtime", version.ref="jetbrainsCompose"} - +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +jetbrains-compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrainsCompose" } +allopen = { module = "org.jetbrains.kotlin:kotlin-allopen", version.ref = "kotlin" } +mokkery = { module = "dev.mokkery:mokkery-gradle", version = "2.1.1" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } @@ -123,3 +124,5 @@ sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } kotlinx-benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "benchmark" } allopen = { id = "org.jetbrains.kotlin.plugin.allopen", version.ref = "kotlin" } +[bundles] +testing = ["kotlin-test-common", "kotlin-test-annotations-common", "turbine", "kotlinx-coroutines-test"] diff --git a/paging-runtime/build.gradle.kts b/paging-runtime/build.gradle.kts index 18ee357..a22cecb 100644 --- a/paging-runtime/build.gradle.kts +++ b/paging-runtime/build.gradle.kts @@ -1,3 +1,5 @@ +import dev.mokkery.MockMode + plugins { id("storex.android.library") id("storex.multiplatform") @@ -21,22 +23,19 @@ kotlin { } } - commonTest { + val commonTest by getting { dependencies { - implementation(libs.kotlin.test) - implementation(libs.kotlin.test.common) - implementation(libs.kotlin.test.annotations.common) - implementation(libs.turbine) - implementation(libs.kotlinx.coroutines.test) + implementation(libs.bundles.testing) + implementation(libs.kotlinx.datetime) } } } } android { - namespace = "org.mobilenativefoundation.paging.core" + namespace = "org.mobilenativefoundation.paging.runtime" sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") compileSdk = libs.versions.android.compileSdk.get().toInt() -} +} \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/logger/impl/RealPagingLogger.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/logger/impl/RealPagingLogger.kt index 1b01411..2ea4f7e 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/logger/impl/RealPagingLogger.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/logger/impl/RealPagingLogger.kt @@ -1,29 +1,43 @@ package org.mobilenativefoundation.storex.paging.runtime.internal.logger.impl import co.touchlab.kermit.Logger -import org.mobilenativefoundation.storex.paging.runtime.Identifier -import org.mobilenativefoundation.storex.paging.runtime.PagingConfig import org.mobilenativefoundation.storex.paging.runtime.Severity import org.mobilenativefoundation.storex.paging.runtime.internal.logger.api.PagingLogger -class RealPagingLogger, K : Any>( - private val pagingConfig: PagingConfig +class RealPagingLogger( + private val severity: Severity ) : PagingLogger { override fun debug(message: String) { - if (pagingConfig.logging.ordinal >= Severity.Debug.ordinal) { + if (severity.ordinal >= Severity.Debug.ordinal) { Logger.d("storex/paging") { message } } } override fun error(message: String, error: Throwable) { - if (pagingConfig.logging.ordinal >= Severity.Error.ordinal) { + if (severity.ordinal >= Severity.Error.ordinal) { Logger.e("storex/paging", error) { message } } } override fun verbose(message: String) { - if (pagingConfig.logging.ordinal >= Severity.Verbose.ordinal) { + if (severity.ordinal >= Severity.Verbose.ordinal) { Logger.v("storex/paging") { message } } } +} + +class TestPagingLogger : PagingLogger { + override fun verbose(message: String) { + println(message) + } + + override fun debug(message: String) { + println(message) + } + + override fun error(message: String, error: Throwable) { + println(message) + println(error) + } + } \ No newline at end of file diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/LoadParamsQueue.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/LoadParamsQueue.kt index 8a5e965..b39e45c 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/LoadParamsQueue.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/api/LoadParamsQueue.kt @@ -60,13 +60,13 @@ internal interface LoadParamsQueue> { * Removes the first matching element in the queue. * @return The first matching element in the queue. */ - suspend fun removeFirst(predicate: (element: Element) -> Boolean): Element + suspend fun removeFirst(predicate: (element: Element) -> Boolean) /** * Removes the last matching element in the queue. * @return The last matching element in the queue. */ - suspend fun removeLast(predicate: (element: Element) -> Boolean): Element + suspend fun removeLast(predicate: (element: Element) -> Boolean) /** * Returns the last element in the queue without removing it. diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt index 11a960a..a800ad0 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/DefaultLoadingHandler.kt @@ -284,7 +284,9 @@ internal class DefaultLoadingHandler, K : Comparable, V : } private suspend fun enqueueNext(key: K, direction: LoadDirection) { + // Skip cache because these params come from a network response val action = Action.Enqueue(key, direction, LoadStrategy.SkipCache, jump = false) + logger.debug("Enqueuing next action: $action") when (direction) { LoadDirection.Append -> queueManager.enqueueAppend(action) LoadDirection.Prepend -> queueManager.enqueuePrepend(action) diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadParamsQueue.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadParamsQueue.kt index 1a48b97..2b85b4f 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadParamsQueue.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pager/impl/RealLoadParamsQueue.kt @@ -1,6 +1,5 @@ package org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl -import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.atomic import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -46,14 +45,18 @@ internal class RealLoadParamsQueue> : LoadParamsQueue { queue.removeFirst() } - override suspend fun removeLast(predicate: (element: LoadParamsQueue.Element) -> Boolean): LoadParamsQueue.Element = mutex.withLock{ - val index = queue.indexOfLast(predicate) - return queue.removeAt(index) + override suspend fun removeLast(predicate: (element: LoadParamsQueue.Element) -> Boolean) { + mutex.withLock { + val index = queue.indexOfLast(predicate) + queue.removeAt(index) + } } - override suspend fun removeFirst(predicate: (element: LoadParamsQueue.Element) -> Boolean): LoadParamsQueue.Element { - val index = queue.indexOfFirst(predicate) - return queue.removeAt(index) + override suspend fun removeFirst(predicate: (element: LoadParamsQueue.Element) -> Boolean) { + mutex.withLock { + val index = queue.indexOfFirst(predicate) + queue.removeAt(index) + } } override suspend fun last(): LoadParamsQueue.Element = mutex.withLock { @@ -108,6 +111,7 @@ internal class RealLoadParamsQueue> : LoadParamsQueue { override suspend fun isNotEmpty(): Boolean = mutex.withLock { size.value > 0 } + override suspend fun size(): Int = mutex.withLock { size.value } diff --git a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt index b27b1b2..309cec6 100644 --- a/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt +++ b/paging-runtime/src/commonMain/kotlin/org/mobilenativefoundation/storex/paging/runtime/internal/pagingScope/impl/PagingScopeBuilder.kt @@ -56,7 +56,7 @@ class PagingScopeBuilder, K : Comparable, V : Identifiabl private val pagingConfig: PagingConfig ) : PagingScope.Builder { - private val logger = RealPagingLogger(pagingConfig) + private val logger = RealPagingLogger(pagingConfig.logging) private val actionsFlow = MutableSharedFlow>(replay = 20) private var initialState: PagingState = PagingState.initial() @@ -143,8 +143,8 @@ class PagingScopeBuilder, K : Comparable, V : Identifiabl val dispatcher = RealDispatcher(actionsFlow) val recompositionMode = RecompositionMode.ContextClock - val pagingStateManager = RealPagingStateManager(initialState) - val queueManager = RealQueueManager() + val pagingStateManager = RealPagingStateManager(initialState, logger) + val queueManager = RealQueueManager(logger) val operationApplier = ConcurrentOperationApplier(operationManager) val loadingHandler = createLoadingHandler( store = store, diff --git a/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt new file mode 100644 index 0000000..c99aec9 --- /dev/null +++ b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/runtime/DefaultLoadingHandlerTest.kt @@ -0,0 +1,230 @@ +package org.mobilenativefoundation.storex.paging.runtime + +import dev.mokkery.answering.returns +import dev.mokkery.every +import dev.mokkery.everySuspend +import dev.mokkery.matcher.any +import dev.mokkery.matcher.eq +import dev.mokkery.mock +import dev.mokkery.verify.VerifyMode.Companion.exactly +import dev.mokkery.verifySuspend +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import kotlinx.datetime.Clock +import org.mobilenativefoundation.storex.paging.custom.Middleware +import org.mobilenativefoundation.storex.paging.runtime.internal.logger.impl.TestPagingLogger +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.ExponentialBackoff +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.FetchingStateHolder +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.LoadParamsQueue +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.OperationApplier +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.PagingStateManager +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.QueueManager +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.api.RetryBookkeeper +import org.mobilenativefoundation.storex.paging.runtime.internal.pager.impl.DefaultLoadingHandler +import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.NormalizedStore +import org.mobilenativefoundation.storex.paging.runtime.internal.store.api.PageLoadState +import org.mobilenativefoundation.storex.paging.testUtils.CursorIdentifier +import org.mobilenativefoundation.storex.paging.testUtils.Post +import org.mobilenativefoundation.storex.paging.testUtils.TimelineRequest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class DefaultLoadingHandlerTest { + + private val appendQueue = mock>() + private val prependQueue = mock>() + + private val pagingState = MutableStateFlow(PagingState.initial()) + private val fetchingState = MutableStateFlow(FetchingState()) + + private val pagingStateManager = mock> { + every { pagingState } returns this@DefaultLoadingHandlerTest.pagingState + } + private val store = mock>() + private val queueManager = mock> { + every { appendQueue } returns this@DefaultLoadingHandlerTest.appendQueue + every { prependQueue } returns this@DefaultLoadingHandlerTest.prependQueue + } + private val fetchingStateHolder = mock> { + every { state } returns this@DefaultLoadingHandlerTest.fetchingState + } + private val errorHandlingStrategy = ErrorHandlingStrategy.Ignore + private val middleware = emptyList>() + private val operationApplier = mock>() + private val retryBookkeeper = mock>() + private val logger = TestPagingLogger() + private val exponentialBackoff = mock() + + private lateinit var loadingHandler: DefaultLoadingHandler + + @BeforeTest + fun setup() { + loadingHandler = DefaultLoadingHandler( + store, + pagingStateManager, + queueManager, + fetchingStateHolder, + errorHandlingStrategy, + middleware, + operationApplier, + retryBookkeeper, + logger, + exponentialBackoff + ) + } + + private fun createFakePost(id: Int): Post { + val timestamp = Clock.System.now().toEpochMilliseconds() + return Post( + id = CursorIdentifier("$timestamp-$id"), + title = "title-$id", + body = "body-$id", + authorId = "author-$id" + ) + } + + @Test + fun handleAppendLoading_whenLoadIsSuccess_thenShouldUpdateStatesAndEnqueueNext() = runTest { + // Given + val cursor = "1" + val nextCursor = "21" + val size = 20 + val key = TimelineRequest(cursor, size) + val strategy = LoadStrategy.SkipCache + val direction = LoadDirection.Append + val nextKey = TimelineRequest(nextCursor, size) + val loadParams = PagingSource.LoadParams(key, strategy, direction) + + val expectedPosts = List(size) { createFakePost(it + 1) } + + val expectedStoreFlow = flowOf( + PageLoadState.Success( + isTerminal = false, + snapshot = ItemSnapshotList(expectedPosts), + prevKey = null, + nextKey = nextKey, + source = PageLoadState.Success.Source.Network + ) + ) + + everySuspend { store.loadPage(eq(loadParams)) }.returns(expectedStoreFlow) + everySuspend { operationApplier.applyOperations(any(), any(), any(), any()) } returns ItemSnapshotList(expectedPosts) + + // When + loadingHandler.handleAppendLoading(loadParams, true) + + // Then + verifySuspend(exactly(1)) { store.loadPage(eq(loadParams)) } + verifySuspend(exactly(1)) { fetchingStateHolder.updateMaxRequestSoFar(eq(key)) } + verifySuspend(exactly(1)) { fetchingStateHolder.updateMinRequestSoFar(eq(key)) } + verifySuspend(exactly(1)) { pagingStateManager.updateWithAppendLoading() } + verifySuspend(exactly(1)) { pagingStateManager.updateWithAppendData(eq(expectedPosts.map { it.id }), eq(false)) } + verifySuspend(exactly(1)) { + queueManager.enqueueAppend( + eq( + Action.Enqueue( + key = nextKey, + direction = direction, + strategy = strategy, + jump = false + ) + ) + ) + } + } + + @Test + fun handlePrependLoading_whenLoadIsSuccess_thenShouldUpdateStatesAndEnqueuePrev() = runTest { + // Given + val cursor = "21" + val prevCursor = "1" + val size = 20 + val key = TimelineRequest(cursor, size) + val strategy = LoadStrategy.SkipCache + val direction = LoadDirection.Prepend + val prevKey = TimelineRequest(prevCursor, size) + val loadParams = PagingSource.LoadParams(key, strategy, direction) + + val expectedPosts = List(size) { createFakePost(it + 21) } + + val expectedStoreFlow = flowOf( + PageLoadState.Success( + isTerminal = false, + snapshot = ItemSnapshotList(expectedPosts), + prevKey = prevKey, + nextKey = null, + source = PageLoadState.Success.Source.Network + ) + ) + + everySuspend { store.loadPage(eq(loadParams)) }.returns(expectedStoreFlow) + everySuspend { operationApplier.applyOperations(any(), any(), any(), any()) } returns ItemSnapshotList(expectedPosts) + + // When + loadingHandler.handlePrependLoading(loadParams) + + // Then + verifySuspend(exactly(1)) { store.loadPage(eq(loadParams)) } + verifySuspend(exactly(1)) { fetchingStateHolder.updateMaxRequestSoFar(eq(key)) } + verifySuspend(exactly(1)) { fetchingStateHolder.updateMinRequestSoFar(eq(key)) } + verifySuspend(exactly(1)) { pagingStateManager.updateWithPrependLoading() } + verifySuspend(exactly(1)) { pagingStateManager.updateWithPrependData(eq(expectedPosts.map { it.id }), eq(false)) } + verifySuspend(exactly(1)) { + queueManager.enqueuePrepend( + eq( + Action.Enqueue( + key = prevKey, + direction = direction, + strategy = strategy, + jump = false + ) + ) + ) + } + } + + @Test + fun handleAppendLoading_givenPassThroughErrorStrategy_whenError_thenShouldUpdateStates() = runTest { + // Given + val cursor = "1" + val size = 20 + val key = TimelineRequest(cursor, size) + val strategy = LoadStrategy.SkipCache + val direction = LoadDirection.Append + val loadParams = PagingSource.LoadParams(key, strategy, direction) + val expectedPosts = List(size) { createFakePost(it + 21) } + val errorHandlingStrategy = ErrorHandlingStrategy.PassThrough + + loadingHandler = DefaultLoadingHandler( + store, + pagingStateManager, + queueManager, + fetchingStateHolder, + errorHandlingStrategy, + middleware, + operationApplier, + retryBookkeeper, + logger, + exponentialBackoff + ) + + val expectedStoreFlow = flowOf( + PageLoadState.Error.Message("", true) + ) + + everySuspend { store.loadPage(eq(loadParams)) }.returns(expectedStoreFlow) + everySuspend { operationApplier.applyOperations(any(), any(), any(), any()) } returns ItemSnapshotList(expectedPosts) + + // When + loadingHandler.handleAppendLoading(loadParams) + + // Then + verifySuspend(exactly(1)) { store.loadPage(eq(loadParams)) } + verifySuspend(exactly(1)) { fetchingStateHolder.updateMaxRequestSoFar(eq(key)) } + verifySuspend(exactly(1)) { fetchingStateHolder.updateMinRequestSoFar(eq(key)) } + verifySuspend(exactly(1)) { store.clearPage(eq(loadParams.key)) } + verifySuspend(exactly(1)) { pagingStateManager.updateWithAppendError(any()) } + verifySuspend(exactly(1)) { queueManager.updateExistingPendingJob(eq(loadParams.key), eq(false), eq(true)) } + } +} \ No newline at end of file diff --git a/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/CursorIdentifier.kt b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/CursorIdentifier.kt new file mode 100644 index 0000000..d379d35 --- /dev/null +++ b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/CursorIdentifier.kt @@ -0,0 +1,34 @@ +package org.mobilenativefoundation.storex.paging.testUtils + +import org.mobilenativefoundation.storex.paging.runtime.Identifier + +data class CursorIdentifier(val cursor: String) : Identifier { + // Splitting the cursor into two components for efficient comparison + private val timestamp: Long + private val uniqueId: String + + init { + // Parsing the cursor string into its components + val parts = cursor.split('-') + require(parts.size == 2) { "Invalid cursor format. Expected 'timestamp-uniqueId'" } + + // Converting the timestamp string to a Long for numerical comparison + timestamp = parts[0].toLongOrNull() ?: throw IllegalArgumentException("Invalid timestamp in cursor") + uniqueId = parts[1] + } + + override fun minus(other: CursorIdentifier): Int { + // Compare timestamps + val timeDiff = this.timestamp - other.timestamp + + return when { + // If timestamps are different, use their difference + // Coercing to Int range to avoid overflow issues + timeDiff != 0L -> timeDiff.coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() + + // If timestamps are the same, compare the unique parts lexicographically + // This ensures a consistent, deterministic ordering + else -> this.uniqueId.compareTo(other.uniqueId) + } + } +} diff --git a/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/Post.kt b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/Post.kt new file mode 100644 index 0000000..8fe146d --- /dev/null +++ b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/Post.kt @@ -0,0 +1,10 @@ +package org.mobilenativefoundation.storex.paging.testUtils + +import org.mobilenativefoundation.storex.paging.runtime.Identifiable + +data class Post( + override val id: CursorIdentifier, + val title: String, + val body: String, + val authorId: String +) : Identifiable diff --git a/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/TimelineRequest.kt b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/TimelineRequest.kt new file mode 100644 index 0000000..f6f7acb --- /dev/null +++ b/paging-runtime/src/commonTest/kotlin/org/mobilenativefoundation/storex/paging/testUtils/TimelineRequest.kt @@ -0,0 +1,19 @@ +package org.mobilenativefoundation.storex.paging.testUtils + +data class TimelineRequest( + val cursor: String?, + val size: Int = 20, + val headers: Map = emptyMap() +) : Comparable { + override fun compareTo(other: TimelineRequest): Int { + return if (cursor != null && other.cursor != null) { + cursor.compareTo(other.cursor) + } else if (cursor != null) { + 1 + } else if (other.cursor != null) { + -1 + } else { + 0 + } + } +} diff --git a/tooling/plugins/build.gradle.kts b/tooling/plugins/build.gradle.kts index 5e9def3..fc4f05b 100644 --- a/tooling/plugins/build.gradle.kts +++ b/tooling/plugins/build.gradle.kts @@ -16,6 +16,8 @@ java { dependencies { compileOnly(libs.android.gradle.plugin) compileOnly(libs.kotlin.gradle.plugin) + compileOnly(libs.allopen) + compileOnly(libs.mokkery) } gradlePlugin { diff --git a/tooling/plugins/src/main/kotlin/org/mobilenativefoundation/storex/paging/tooling/plugins/KotlinMultiplatformConventionPlugin.kt b/tooling/plugins/src/main/kotlin/org/mobilenativefoundation/storex/paging/tooling/plugins/KotlinMultiplatformConventionPlugin.kt index 8081475..ca43346 100644 --- a/tooling/plugins/src/main/kotlin/org/mobilenativefoundation/storex/paging/tooling/plugins/KotlinMultiplatformConventionPlugin.kt +++ b/tooling/plugins/src/main/kotlin/org/mobilenativefoundation/storex/paging/tooling/plugins/KotlinMultiplatformConventionPlugin.kt @@ -2,6 +2,8 @@ package org.mobilenativefoundation.storex.paging.tooling.plugins import com.android.build.api.dsl.CommonExtension import com.android.build.api.dsl.LibraryExtension +import dev.mokkery.MockMode +import dev.mokkery.gradle.MokkeryGradleExtension import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project @@ -24,9 +26,11 @@ class KotlinMultiplatformConventionPlugin : Plugin { with(pluginManager) { apply("org.jetbrains.kotlin.multiplatform") + apply("org.jetbrains.kotlin.plugin.allopen") + apply("com.google.devtools.ksp") + apply("dev.mokkery") } - extensions.configure { applyDefaultHierarchyTemplate() @@ -93,8 +97,7 @@ class KotlinMultiplatformConventionPlugin : Plugin { } configureKotlin() - - + configureMokkery() } } @@ -131,7 +134,6 @@ class KotlinAndroidLibraryConventionPlugin : Plugin { } - object Versions { const val COMPILE_SDK = 34 const val MIN_SDK = 31 @@ -183,3 +185,9 @@ fun KotlinMultiplatformExtension.kotlinOptions(block: KotlinJvmOptions.() -> Uni internal val Project.libs: VersionCatalog get() = extensions.getByType().named("libs") + +internal fun Project.configureMokkery() { + extensions.configure { + defaultMockMode.set(MockMode.autoUnit) + } +} \ No newline at end of file