From 7b7fa955da229fd6577af76dbc343ab640b5dc38 Mon Sep 17 00:00:00 2001 From: Yang Date: Sat, 11 Nov 2023 04:37:57 +1100 Subject: [PATCH] Restructure data layer. Custom models in `:data-common`, GraphQL schema and queries in `:data-runtime-cloud` with mappers. --- kmm/apollo-models/build.gradle.kts | 23 ---- kmm/data-common/build.gradle.kts | 11 -- .../kstreamlined/kmm/data/feed/FeedRepo.kt | 11 +- .../kmm/data/feed/model/FeedEntry.kt | 43 ++++++++ .../kmm/data/feed/model/FeedSource.kt | 14 +++ kmm/data-runtime-cloud/build.gradle.kts | 10 ++ .../kstreamlined/graphql}/FeedEntries.graphql | 0 .../kstreamlined/graphql}/FeedSources.graphql | 0 .../kstreamlined/graphql}/schema.graphqls | 0 .../kmm/data/feed/CloudFeedRepo.kt | 25 +++-- .../kmm/data/feed/mapper/FeedEntryMappers.kt | 57 ++++++++++ .../kmm/data/feed/mapper/FeedSourceMappers.kt | 28 +++++ .../kmm/data/feed/CloudFeedRepoTest.kt | 23 ++-- .../data/feed/mapper/FeedEntryMappersTest.kt | 103 ++++++++++++++++++ .../data/feed/mapper/FeedSourceMappersTest.kt | 53 +++++++++ .../kmm/data/feed/EdgeFeedRepo.kt | 11 +- .../kmm/data/feed/FakeFeedData.kt | 99 ++++++++--------- .../kmm/data/feed/FakeFeedRepo.kt | 17 ++- settings.gradle.kts | 1 - 19 files changed, 400 insertions(+), 129 deletions(-) delete mode 100644 kmm/apollo-models/build.gradle.kts create mode 100644 kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedEntry.kt create mode 100644 kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedSource.kt rename kmm/{apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo => data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql}/FeedEntries.graphql (100%) rename kmm/{apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo => data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql}/FeedSources.graphql (100%) rename kmm/{apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo => data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql}/schema.graphqls (100%) create mode 100644 kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappers.kt create mode 100644 kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappers.kt create mode 100644 kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappersTest.kt create mode 100644 kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappersTest.kt diff --git a/kmm/apollo-models/build.gradle.kts b/kmm/apollo-models/build.gradle.kts deleted file mode 100644 index 7d826e1b..00000000 --- a/kmm/apollo-models/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id("kstreamlined.kmm.jvm-and-ios") - id("com.apollographql.apollo3") -} - -apollo { - service("service") { - packageNamesFromFilePaths() - codegenModels.set("responseBased") - flattenModels.set(true) - generateDataBuilders.set(true) - } -} - -kotlin { - sourceSets { - commonMain { - dependencies { - api(libs.apollo.api) - } - } - } -} diff --git a/kmm/data-common/build.gradle.kts b/kmm/data-common/build.gradle.kts index 797481e1..67e328f1 100644 --- a/kmm/data-common/build.gradle.kts +++ b/kmm/data-common/build.gradle.kts @@ -1,14 +1,3 @@ plugins { id("kstreamlined.kmm.jvm-and-ios") } - -kotlin { - sourceSets { - commonMain { - dependencies { - api(project(":kmm:apollo-models")) - implementation(libs.kotlinx.coroutines.core) - } - } - } -} diff --git a/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FeedRepo.kt b/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FeedRepo.kt index 017676a6..039a1255 100644 --- a/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FeedRepo.kt +++ b/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FeedRepo.kt @@ -1,17 +1,16 @@ package io.github.reactivecircus.kstreamlined.kmm.data.feed -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedEntriesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedSourcesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.FeedSourceKey +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource interface FeedRepo { suspend fun loadFeedSources( refresh: Boolean = false - ): List + ): List suspend fun loadFeedEntries( - filters: List? = null, + filters: List? = null, refresh: Boolean = false, - ): List + ): List } diff --git a/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedEntry.kt b/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedEntry.kt new file mode 100644 index 00000000..bb900b25 --- /dev/null +++ b/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedEntry.kt @@ -0,0 +1,43 @@ +package io.github.reactivecircus.kstreamlined.kmm.data.feed.model + +sealed interface FeedEntry { + val id: String + val title: String + val publishTimestamp: String + val contentUrl: String + + data class KotlinBlog( + override val id: String, + override val title: String, + override val publishTimestamp: String, + override val contentUrl: String, + val featuredImageUrl: String, + val description: String, + ) : FeedEntry + + data class KotlinYouTube( + override val id: String, + override val title: String, + override val publishTimestamp: String, + override val contentUrl: String, + val thumbnailUrl: String, + val description: String, + ) : FeedEntry + + data class TalkingKotlin( + override val id: String, + override val title: String, + override val publishTimestamp: String, + override val contentUrl: String, + val podcastLogoUrl: String, + val tags: List, + ) : FeedEntry + + data class KotlinWeekly( + override val id: String, + override val title: String, + override val publishTimestamp: String, + override val contentUrl: String, + val newsletterLogoUrl: String, + ) : FeedEntry +} diff --git a/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedSource.kt b/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedSource.kt new file mode 100644 index 00000000..638e9647 --- /dev/null +++ b/kmm/data-common/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/model/FeedSource.kt @@ -0,0 +1,14 @@ +package io.github.reactivecircus.kstreamlined.kmm.data.feed.model + +data class FeedSource( + val key: Key, + val title: String, + val description: String, +) { + enum class Key { + KotlinBlog, + KotlinYouTubeChannel, + TalkingKotlinPodcast, + KotlinWeekly, + } +} diff --git a/kmm/data-runtime-cloud/build.gradle.kts b/kmm/data-runtime-cloud/build.gradle.kts index d52c76b1..5afa6039 100644 --- a/kmm/data-runtime-cloud/build.gradle.kts +++ b/kmm/data-runtime-cloud/build.gradle.kts @@ -1,6 +1,16 @@ plugins { id("kstreamlined.kmm.jvm-and-ios") id("kstreamlined.kmm.test") + id("com.apollographql.apollo3") +} + +apollo { + service("kstreamlined") { + packageName.set("io.github.reactivecircus.kstreamlined.graphql") + codegenModels.set("responseBased") + flattenModels.set(true) + generateDataBuilders.set(true) + } } kotlin { diff --git a/kmm/apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo/FeedEntries.graphql b/kmm/data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql/FeedEntries.graphql similarity index 100% rename from kmm/apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo/FeedEntries.graphql rename to kmm/data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql/FeedEntries.graphql diff --git a/kmm/apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo/FeedSources.graphql b/kmm/data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql/FeedSources.graphql similarity index 100% rename from kmm/apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo/FeedSources.graphql rename to kmm/data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql/FeedSources.graphql diff --git a/kmm/apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo/schema.graphqls b/kmm/data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql/schema.graphqls similarity index 100% rename from kmm/apollo-models/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/kmm/apollo/schema.graphqls rename to kmm/data-runtime-cloud/src/commonMain/graphql/io/github/reactivecircus/kstreamlined/graphql/schema.graphqls diff --git a/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepo.kt b/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepo.kt index 37460cae..e30833c9 100644 --- a/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepo.kt +++ b/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepo.kt @@ -3,14 +3,17 @@ package io.github.reactivecircus.kstreamlined.kmm.data.feed import co.touchlab.kermit.Logger import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedEntriesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedSourcesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.FeedSourceKey +import io.github.reactivecircus.kstreamlined.graphql.FeedEntriesQuery +import io.github.reactivecircus.kstreamlined.graphql.FeedSourcesQuery +import io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper.toApollo +import io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper.toModel +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource import io.github.reactivecircus.kstreamlined.kmm.data.networking.defaultFetchPolicy class CloudFeedRepo(private val apolloClient: ApolloClient) : FeedRepo { - override suspend fun loadFeedSources(refresh: Boolean): List { + override suspend fun loadFeedSources(refresh: Boolean): List { return runCatching { apolloClient.query(FeedSourcesQuery()) .defaultFetchPolicy(refresh) @@ -18,20 +21,24 @@ class CloudFeedRepo(private val apolloClient: ApolloClient) : FeedRepo { .dataAssertNoErrors.feedSources }.onFailure { Logger.w("Query failed", it) - }.getOrThrow() + }.getOrThrow().map { it.toModel() } } override suspend fun loadFeedEntries( - filters: List?, + filters: List?, refresh: Boolean, - ): List { + ): List { return runCatching { - apolloClient.query(FeedEntriesQuery(filters = Optional.presentIfNotNull(filters))) + apolloClient.query( + FeedEntriesQuery( + filters = Optional.presentIfNotNull(filters?.map { it.toApollo() }) + ) + ) .defaultFetchPolicy(refresh) .execute() .dataAssertNoErrors.feedEntries }.onFailure { Logger.w("Query failed", it) - }.getOrThrow() + }.getOrThrow().map { it.toModel() } } } diff --git a/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappers.kt b/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappers.kt new file mode 100644 index 00000000..aa270ce3 --- /dev/null +++ b/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappers.kt @@ -0,0 +1,57 @@ +package io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper + +import io.github.reactivecircus.kstreamlined.graphql.FeedEntriesQuery +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry + +internal fun FeedEntriesQuery.FeedEntry.toModel(): FeedEntry { + return when (this) { + is FeedEntriesQuery.KotlinBlogFeedEntry -> this.toModel() + is FeedEntriesQuery.KotlinYouTubeFeedEntry -> this.toModel() + is FeedEntriesQuery.TalkingKotlinFeedEntry -> this.toModel() + is FeedEntriesQuery.KotlinWeeklyFeedEntry -> this.toModel() + else -> error("Unknown FeedEntry subtype") + } +} + +internal fun FeedEntriesQuery.KotlinBlogFeedEntry.toModel(): FeedEntry.KotlinBlog { + return FeedEntry.KotlinBlog( + id = this.id, + title = this.title, + publishTimestamp = this.publishTimestamp, + contentUrl = this.contentUrl, + featuredImageUrl = this.featuredImageUrl, + description = this.description, + ) +} + +internal fun FeedEntriesQuery.KotlinYouTubeFeedEntry.toModel(): FeedEntry.KotlinYouTube { + return FeedEntry.KotlinYouTube( + id = this.id, + title = this.title, + publishTimestamp = this.publishTimestamp, + contentUrl = this.contentUrl, + thumbnailUrl = this.thumbnailUrl, + description = this.description, + ) +} + +internal fun FeedEntriesQuery.TalkingKotlinFeedEntry.toModel(): FeedEntry.TalkingKotlin { + return FeedEntry.TalkingKotlin( + id = this.id, + title = this.title, + publishTimestamp = this.publishTimestamp, + contentUrl = this.contentUrl, + podcastLogoUrl = this.podcastLogoUrl, + tags = this.tags, + ) +} + +internal fun FeedEntriesQuery.KotlinWeeklyFeedEntry.toModel(): FeedEntry.KotlinWeekly { + return FeedEntry.KotlinWeekly( + id = this.id, + title = this.title, + publishTimestamp = this.publishTimestamp, + contentUrl = this.contentUrl, + newsletterLogoUrl = this.newsletterLogoUrl, + ) +} diff --git a/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappers.kt b/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappers.kt new file mode 100644 index 00000000..91efa63e --- /dev/null +++ b/kmm/data-runtime-cloud/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappers.kt @@ -0,0 +1,28 @@ +package io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper + +import io.github.reactivecircus.kstreamlined.graphql.FeedSourcesQuery +import io.github.reactivecircus.kstreamlined.graphql.type.FeedSourceKey +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource + +internal fun FeedSource.Key.toApollo(): FeedSourceKey { + return when (this) { + FeedSource.Key.KotlinBlog -> FeedSourceKey.KOTLIN_BLOG + FeedSource.Key.KotlinYouTubeChannel -> FeedSourceKey.KOTLIN_YOUTUBE_CHANNEL + FeedSource.Key.TalkingKotlinPodcast -> FeedSourceKey.TALKING_KOTLIN_PODCAST + FeedSource.Key.KotlinWeekly -> FeedSourceKey.KOTLIN_WEEKLY + } +} + +internal fun FeedSourcesQuery.FeedSource.toModel(): FeedSource { + return FeedSource( + key = when (this.key) { + FeedSourceKey.KOTLIN_BLOG -> FeedSource.Key.KotlinBlog + FeedSourceKey.KOTLIN_YOUTUBE_CHANNEL -> FeedSource.Key.KotlinYouTubeChannel + FeedSourceKey.TALKING_KOTLIN_PODCAST -> FeedSource.Key.TalkingKotlinPodcast + FeedSourceKey.KOTLIN_WEEKLY -> FeedSource.Key.KotlinWeekly + else -> error("Unknown FeedSourceKey subtype") + }, + title = this.title, + description = this.description, + ) +} diff --git a/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepoTest.kt b/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepoTest.kt index b57b2677..fe740485 100644 --- a/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepoTest.kt +++ b/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/CloudFeedRepoTest.kt @@ -11,11 +11,12 @@ import com.apollographql.apollo3.exception.CacheMissException import com.apollographql.apollo3.mockserver.MockResponse import com.apollographql.apollo3.mockserver.MockServer import com.apollographql.apollo3.testing.enqueue -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedEntriesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedSourcesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildFeedSource -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildKotlinBlog -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildKotlinYouTube +import io.github.reactivecircus.kstreamlined.graphql.FeedEntriesQuery +import io.github.reactivecircus.kstreamlined.graphql.FeedSourcesQuery +import io.github.reactivecircus.kstreamlined.graphql.type.buildFeedSource +import io.github.reactivecircus.kstreamlined.graphql.type.buildKotlinBlog +import io.github.reactivecircus.kstreamlined.graphql.type.buildKotlinYouTube +import io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper.toModel import io.github.reactivecircus.kstreamlined.kmm.test.utils.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -73,7 +74,7 @@ class CloudFeedRepoTest { FeedSourcesQuery.Data(feedSources = dummyFeedSources) ) val actual = cloudFeedRepo.loadFeedSources(refresh = true) - assertEquals(dummyFeedSources, actual) + assertEquals(dummyFeedSources.map { it.toModel() }, actual) } @Test @@ -98,7 +99,7 @@ class CloudFeedRepoTest { FeedSourcesQuery.Data(feedSources = dummyFeedSources) ) val actual = cloudFeedRepo.loadFeedSources(refresh = false) - assertEquals(dummyFeedSources, actual) + assertEquals(dummyFeedSources.map { it.toModel() }, actual) } @Test @@ -118,7 +119,7 @@ class CloudFeedRepoTest { .build() ) val actual = cloudFeedRepo.loadFeedSources(refresh = false) - assertEquals(dummyFeedSources, actual) + assertEquals(dummyFeedSources.map { it.toModel() }, actual) } @Test @@ -147,7 +148,7 @@ class CloudFeedRepoTest { filters = null, refresh = true, ) - assertEquals(dummyFeedEntries, actual) + assertEquals(dummyFeedEntries.map { it.toModel() }, actual) } @Test @@ -178,7 +179,7 @@ class CloudFeedRepoTest { filters = null, refresh = false, ) - assertEquals(dummyFeedEntries, actual) + assertEquals(dummyFeedEntries.map { it.toModel() }, actual) } @Test @@ -204,7 +205,7 @@ class CloudFeedRepoTest { filters = null, refresh = false, ) - assertEquals(dummyFeedEntries, actual) + assertEquals(dummyFeedEntries.map { it.toModel() }, actual) } @Test diff --git a/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappersTest.kt b/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappersTest.kt new file mode 100644 index 00000000..772e7fd9 --- /dev/null +++ b/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedEntryMappersTest.kt @@ -0,0 +1,103 @@ +package io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper + +import io.github.reactivecircus.kstreamlined.graphql.FeedEntriesQuery +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry +import kotlin.test.Test +import kotlin.test.assertEquals + +class FeedEntryMappersTest { + + @Test + fun `FeedEntriesQuery_KotlinBlogFeedEntry maps to expected FeedEntry`() { + val apolloFeedEntry = FeedEntriesQuery.KotlinBlogFeedEntry( + id = "1", + title = "Kotlin Blog Post", + publishTimestamp = "2022-01-01T00:00:00Z", + contentUrl = "https://blog.kotlinlang.org/post", + featuredImageUrl = "https://blog.kotlinlang.org/image", + description = "A blog post about Kotlin", + __typename = "KotlinBlog" + ) + + val expectedFeedEntry = FeedEntry.KotlinBlog( + id = "1", + title = "Kotlin Blog Post", + publishTimestamp = "2022-01-01T00:00:00Z", + contentUrl = "https://blog.kotlinlang.org/post", + featuredImageUrl = "https://blog.kotlinlang.org/image", + description = "A blog post about Kotlin", + ) + + assertEquals(expectedFeedEntry, apolloFeedEntry.toModel()) + } + + @Test + fun `FeedEntriesQuery_KotlinYouTubeFeedEntry maps to expected FeedEntry`() { + val apolloFeedEntry = FeedEntriesQuery.KotlinYouTubeFeedEntry( + id = "2", + title = "Kotlin YouTube Video", + publishTimestamp = "2022-01-02T00:00:00Z", + contentUrl = "https://youtube.com/kotlinvideo", + thumbnailUrl = "https://youtube.com/kotlinvideo/thumbnail", + description = "A YouTube video about Kotlin", + __typename = "KotlinYouTube", + ) + + val expectedFeedEntry = FeedEntry.KotlinYouTube( + id = "2", + title = "Kotlin YouTube Video", + publishTimestamp = "2022-01-02T00:00:00Z", + contentUrl = "https://youtube.com/kotlinvideo", + thumbnailUrl = "https://youtube.com/kotlinvideo/thumbnail", + description = "A YouTube video about Kotlin", + ) + + assertEquals(expectedFeedEntry, apolloFeedEntry.toModel()) + } + + @Test + fun `FeedEntriesQuery_TalkingKotlinFeedEntry maps to expected FeedEntry`() { + val apolloFeedEntry = FeedEntriesQuery.TalkingKotlinFeedEntry( + id = "3", + title = "Talking Kotlin Podcast", + publishTimestamp = "2022-01-03T00:00:00Z", + contentUrl = "https://talkingkotlin.com/podcast", + podcastLogoUrl = "https://talkingkotlin.com/podcast/logo", + tags = listOf("Kotlin", "Podcast"), + __typename = "TalkingKotlin", + ) + + val expectedFeedEntry = FeedEntry.TalkingKotlin( + id = "3", + title = "Talking Kotlin Podcast", + publishTimestamp = "2022-01-03T00:00:00Z", + contentUrl = "https://talkingkotlin.com/podcast", + podcastLogoUrl = "https://talkingkotlin.com/podcast/logo", + tags = listOf("Kotlin", "Podcast"), + ) + + assertEquals(expectedFeedEntry, apolloFeedEntry.toModel()) + } + + @Test + fun `FeedEntriesQuery_KotlinWeeklyFeedEntry maps to expected FeedEntry`() { + val apolloFeedEntry = FeedEntriesQuery.KotlinWeeklyFeedEntry( + id = "4", + title = "Kotlin Weekly Newsletter", + publishTimestamp = "2022-01-04T00:00:00Z", + contentUrl = "https://kotlinweekly.net/newsletter", + newsletterLogoUrl = "https://kotlinweekly.net/newsletter/logo", + __typename = "KotlinWeekly", + ) + + val expectedFeedEntry = FeedEntry.KotlinWeekly( + id = "4", + title = "Kotlin Weekly Newsletter", + publishTimestamp = "2022-01-04T00:00:00Z", + contentUrl = "https://kotlinweekly.net/newsletter", + newsletterLogoUrl = "https://kotlinweekly.net/newsletter/logo", + ) + + assertEquals(expectedFeedEntry, apolloFeedEntry.toModel()) + } +} diff --git a/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappersTest.kt b/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappersTest.kt new file mode 100644 index 00000000..f0dc8703 --- /dev/null +++ b/kmm/data-runtime-cloud/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/mapper/FeedSourceMappersTest.kt @@ -0,0 +1,53 @@ +package io.github.reactivecircus.kstreamlined.kmm.data.feed.mapper + +import io.github.reactivecircus.kstreamlined.graphql.FeedSourcesQuery +import io.github.reactivecircus.kstreamlined.graphql.type.FeedSourceKey +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource.Key +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class FeedSourceMappersTest { + + @Test + fun `FeedSource_Key maps to expected FeedSourceKey`() { + assertEquals(FeedSourceKey.KOTLIN_BLOG, FeedSource.Key.KotlinBlog.toApollo()) + assertEquals(FeedSourceKey.KOTLIN_YOUTUBE_CHANNEL, FeedSource.Key.KotlinYouTubeChannel.toApollo()) + assertEquals(FeedSourceKey.TALKING_KOTLIN_PODCAST, FeedSource.Key.TalkingKotlinPodcast.toApollo()) + assertEquals(FeedSourceKey.KOTLIN_WEEKLY, FeedSource.Key.KotlinWeekly.toApollo()) + } + + @Test + fun `FeedSourcesQuery_FeedSource maps to expected FeedSource`() { + FeedSource.Key.values().forEach { key -> + val apolloKey = key.toApollo() + val apolloFeedSource = FeedSourcesQuery.FeedSource( + key = apolloKey, + title = "Title for $key", + description = "Description for $key", + ) + + val expectedFeedSource = FeedSource( + key = key, + title = "Title for $key", + description = "Description for $key", + ) + + assertEquals(expectedFeedSource, apolloFeedSource.toModel()) + } + } + + @Test + fun `throws exception when mapping unknown FeedSourcesQuery_FeedSource to FeedSource`() { + val apolloFeedSource = FeedSourcesQuery.FeedSource( + key = FeedSourceKey.UNKNOWN__, + title = "Kotlin Blog", + description = "The official Kotlin blog", + ) + + assertFailsWith { + apolloFeedSource.toModel() + } + } +} diff --git a/kmm/data-runtime-edge/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/EdgeFeedRepo.kt b/kmm/data-runtime-edge/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/EdgeFeedRepo.kt index c34c31c9..951f6941 100644 --- a/kmm/data-runtime-edge/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/EdgeFeedRepo.kt +++ b/kmm/data-runtime-edge/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/EdgeFeedRepo.kt @@ -1,19 +1,18 @@ package io.github.reactivecircus.kstreamlined.kmm.data.feed -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedEntriesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedSourcesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.FeedSourceKey +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource class EdgeFeedRepo : FeedRepo { - override suspend fun loadFeedSources(refresh: Boolean): List { + override suspend fun loadFeedSources(refresh: Boolean): List { TODO() } override suspend fun loadFeedEntries( - filters: List?, + filters: List?, refresh: Boolean, - ): List { + ): List { TODO() } } diff --git a/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedData.kt b/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedData.kt index 55cf115b..c7645c11 100644 --- a/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedData.kt +++ b/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedData.kt @@ -2,72 +2,65 @@ package io.github.reactivecircus.kstreamlined.kmm.data.feed -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedEntriesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedSourcesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.FeedSourceKey -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildKotlinBlog -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildKotlinWeekly -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildKotlinYouTube -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.buildTalkingKotlin +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource val FakeFeedSources = listOf( - FeedSourcesQuery.FeedSource( - key = FeedSourceKey.KOTLIN_BLOG, + FeedSource( + key = FeedSource.Key.KotlinBlog, title = "Kotlin Blog", description = "Latest news from the official Kotlin Blog", ), - FeedSourcesQuery.FeedSource( - key = FeedSourceKey.KOTLIN_YOUTUBE_CHANNEL, + FeedSource( + key = FeedSource.Key.KotlinYouTubeChannel, title = "Kotlin YouTube", description = "The official YouTube channel of the Kotlin programming language", ), - FeedSourcesQuery.FeedSource( - key = FeedSourceKey.TALKING_KOTLIN_PODCAST, + FeedSource( + key = FeedSource.Key.TalkingKotlinPodcast, title = "Talking Kotlin", description = "Technical show discussing everything Kotlin, hosted by Hadi and Sebastian", ), - FeedSourcesQuery.FeedSource( - key = FeedSourceKey.KOTLIN_WEEKLY, + FeedSource( + key = FeedSource.Key.KotlinWeekly, title = "Kotlin Weekly", description = "Weekly community Kotlin newsletter, hosted by Enrique", ), ) -val FakeFeedEntries = FeedEntriesQuery.Data { - feedEntries = listOf( - buildKotlinBlog { - id = "https://blog.jetbrains.com/?post_type=kotlin&p=264203" - title = "A New Approach to Incremental Compilation in Kotlin" - publishTimestamp = "1657882573" - contentUrl = "https://blog.jetbrains.com/kotlin/2022/07/a-new-approach-to-incremental-compilation-in-kotlin/" - featuredImageUrl = "https://blog.jetbrains.com/wp-content/uploads/2022/07/A-New-Approach-to-Incremental-Compilation-in-Kotlin-EN-2_Twitter-Blog.png" - description = "In Kotlin 1.7.0, we’ve reworked incremental compilation for project changes in cross-module dependencies. The new approach lifts previous limitations on incremental compilation. It’s now supported when changes are made inside dependent non-Kotlin modules, and it is compatible with the Gradle build cache. Support for compilation avoidance has also been improved. All of these advancements decrease […]" - }, - buildKotlinYouTube { - id = "yt:video:ihMhu3hvCCE" - title = "Kotlin and Java Interoperability in Spring Projects" - publishTimestamp = "1657114786" - contentUrl = "https://www.youtube.com/watch?v=ihMhu3hvCCE" - thumbnailUrl = "https://i2.ytimg.com/vi/ihMhu3hvCCE/hqdefault.jpg" - description = "We have configured the Kotlin compiler in a Java/Spring project - now what? Let's talk about important details you need to know about calling Java from Kotlin code and vice versa. Links: Adding Kotlin to Spring/Maven project: https://youtu.be/4-qOxvjjF8g Calling Java from Kotlin: https://kotlinlang.org/docs/java-interop.html Calling Kotlin from Java: https://kotlinlang.org/docs/java-to-kotlin-interop.html Kotlin Spring compiler plugin: https://kotlinlang.org/docs/all-open-plugin.html#spring-support Just starting with Kotlin? Learn Kotlin by creating real-world applications with JetBrains Academy Build simple games, a chat bot, a coffee machine simulator, and other interactive projects step by step in a hands-on learning environment. Get started: https://hyperskill.org/join/fromyoutubetoJetSalesStat?redirect=true&next=/tracks/18 #springboot #springframework #kotlin #interoperability" - }, - buildTalkingKotlin { - id = "https://talkingkotlin.com/turbocharging-kotlin-arrow-analysis-optics-meta" - title = "Turbocharging Kotlin: Arrow Analysis, Optics & Meta" - publishTimestamp = "1656374400" - contentUrl = "https://talkingkotlin.com/turbocharging-kotlin-arrow-analysis-optics-meta/" - podcastLogoUrl = "https://talkingkotlin.com/images/kotlin_talking_logo.png" - tags = listOf( - "Arrow", - "Code Quality", - ) - }, - buildKotlinWeekly { - id = "21a2c7f9e24fae1631468c5507e4ff7c" - title = "Kotlin Weekly #312 has just been published!" - publishTimestamp = "1658675020" - contentUrl = "https://t.co/7JzvarYb05" - newsletterLogoUrl = "https://pbs.twimg.com/profile_images/883969154667204608/26qTz9AE_400x400.jpg" - } - ) -}.feedEntries +val FakeFeedEntries = listOf( + FeedEntry.KotlinBlog( + id = "https://blog.jetbrains.com/?post_type=kotlin&p=264203", + title = "A New Approach to Incremental Compilation in Kotlin", + publishTimestamp = "1657882573", + contentUrl = "https://blog.jetbrains.com/kotlin/2022/07/a-new-approach-to-incremental-compilation-in-kotlin/", + featuredImageUrl = "https://blog.jetbrains.com/wp-content/uploads/2022/07/A-New-Approach-to-Incremental-Compilation-in-Kotlin-EN-2_Twitter-Blog.png", + description = "In Kotlin 1.7.0, we’ve reworked incremental compilation for project changes in cross-module dependencies. The new approach lifts previous limitations on incremental compilation. It’s now supported when changes are made inside dependent non-Kotlin modules, and it is compatible with the Gradle build cache. Support for compilation avoidance has also been improved. All of these advancements decrease […]", + ), + FeedEntry.KotlinYouTube( + id = "yt:video:ihMhu3hvCCE", + title = "Kotlin and Java Interoperability in Spring Projects", + publishTimestamp = "1657114786", + contentUrl = "https://www.youtube.com/watch?v=ihMhu3hvCCE", + thumbnailUrl = "https://i2.ytimg.com/vi/ihMhu3hvCCE/hqdefault.jpg", + description = "We have configured the Kotlin compiler in a Java/Spring project - now what? Let's talk about important details you need to know about calling Java from Kotlin code and vice versa. Links: Adding Kotlin to Spring/Maven project: https://youtu.be/4-qOxvjjF8g Calling Java from Kotlin: https://kotlinlang.org/docs/java-interop.html Calling Kotlin from Java: https://kotlinlang.org/docs/java-to-kotlin-interop.html Kotlin Spring compiler plugin: https://kotlinlang.org/docs/all-open-plugin.html#spring-support Just starting with Kotlin? Learn Kotlin by creating real-world applications with JetBrains Academy Build simple games, a chat bot, a coffee machine simulator, and other interactive projects step by step in a hands-on learning environment. Get started: https://hyperskill.org/join/fromyoutubetoJetSalesStat?redirect=true&next=/tracks/18 #springboot #springframework #kotlin #interoperability", + ), + FeedEntry.TalkingKotlin( + id = "https://talkingkotlin.com/turbocharging-kotlin-arrow-analysis-optics-meta", + title = "Turbocharging Kotlin: Arrow Analysis, Optics & Meta", + publishTimestamp = "1656374400", + contentUrl = "https://talkingkotlin.com/turbocharging-kotlin-arrow-analysis-optics-meta/", + podcastLogoUrl = "https://talkingkotlin.com/images/kotlin_talking_logo.png", + tags = listOf( + "Arrow", + "Code Quality", + ), + ), + FeedEntry.KotlinWeekly( + id = "21a2c7f9e24fae1631468c5507e4ff7c", + title = "Kotlin Weekly #312 has just been published!", + publishTimestamp = "1658675020", + contentUrl = "https://t.co/7JzvarYb05", + newsletterLogoUrl = "https://pbs.twimg.com/profile_images/883969154667204608/26qTz9AE_400x400.jpg", + ), +) diff --git a/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedRepo.kt b/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedRepo.kt index 925c3457..b1c8cc09 100644 --- a/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedRepo.kt +++ b/kmm/data-testing/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmm/data/feed/FakeFeedRepo.kt @@ -1,29 +1,28 @@ package io.github.reactivecircus.kstreamlined.kmm.data.feed -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedEntriesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.FeedSourcesQuery -import io.github.reactivecircus.kstreamlined.kmm.apollo.type.FeedSourceKey +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedEntry +import io.github.reactivecircus.kstreamlined.kmm.data.feed.model.FeedSource class FakeFeedRepo : FeedRepo { - var nextFeedSourcesResponse: suspend () -> List = { + var nextFeedSourcesResponse: suspend () -> List = { FakeFeedSources } var nextFeedEntriesResponse: suspend ( - filters: List? - ) -> List = { + filters: List? + ) -> List = { FakeFeedEntries } - override suspend fun loadFeedSources(refresh: Boolean): List { + override suspend fun loadFeedSources(refresh: Boolean): List { return nextFeedSourcesResponse() } override suspend fun loadFeedEntries( - filters: List?, + filters: List?, refresh: Boolean, - ): List { + ): List { return nextFeedEntriesResponse(filters) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index d8781af7..c1e4b0a9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -54,7 +54,6 @@ plugins { rootProject.name = "kstreamlined-mobile" // KMM -include(":kmm:apollo-models") include(":kmm:data-common") include(":kmm:data-runtime-cloud") include(":kmm:data-runtime-edge")