pull/1515/merge
Simon Marquis 1 year ago committed by GitHub
commit 5411796383
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -85,7 +85,7 @@ dependencies {
implementation(projects.core.data) implementation(projects.core.data)
implementation(projects.core.model) implementation(projects.core.model)
implementation(projects.core.analytics) implementation(projects.core.analytics)
implementation(projects.sync.work) implementation(projects.sync)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.material3.adaptive)
@ -105,30 +105,32 @@ dependencies {
implementation(libs.coil.kt) implementation(libs.coil.kt)
ksp(libs.hilt.compiler) ksp(libs.hilt.compiler)
kspTest(libs.hilt.compiler)
kspAndroidTest(libs.hilt.compiler)
debugImplementation(libs.androidx.compose.ui.testManifest) debugImplementation(libs.androidx.compose.ui.testManifest)
debugImplementation(projects.uiTestHiltManifest) debugImplementation(projects.uiTestHiltManifest)
kspTest(libs.hilt.compiler)
testImplementation(projects.core.dataTest)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(projects.sync.syncTest)
testImplementation(libs.androidx.compose.ui.test) testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.androidx.work.testing) testImplementation(libs.androidx.work.testing)
testImplementation(libs.hilt.android.testing) testImplementation(libs.hilt.android.testing)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.sync))
testDemoImplementation(libs.robolectric) testDemoImplementation(libs.robolectric)
testDemoImplementation(libs.roborazzi) testDemoImplementation(libs.roborazzi)
testDemoImplementation(projects.core.screenshotTesting) testDemoImplementation(projects.core.screenshotTesting)
testDemoImplementation(testFixtures(projects.core.data))
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(projects.core.dataTest)
androidTestImplementation(projects.core.datastoreTest)
androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.navigation.testing) androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(libs.androidx.compose.ui.test)
androidTestImplementation(libs.hilt.android.testing) androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(testFixtures(projects.core.data))
androidTestImplementation(testFixtures(projects.core.datastore))
androidTestImplementation(testFixtures(projects.sync))
baselineProfile(projects.benchmarks) baselineProfile(projects.benchmarks)
} }

@ -27,10 +27,10 @@ import androidx.navigation.compose.composable
import androidx.navigation.createGraph import androidx.navigation.createGraph
import androidx.navigation.testing.TestNavHostController import androidx.navigation.testing.TestNavHostController
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.data.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.TestNetworkMonitor
import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TestTimeZoneMonitor
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher

@ -34,9 +34,9 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowSizeClass import androidx.window.core.layout.WindowSizeClass
import com.github.takahirom.roborazzi.captureRoboImage import com.github.takahirom.roborazzi.captureRoboImage
import com.google.samples.apps.nowinandroid.core.data.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme

@ -21,6 +21,7 @@ plugins {
android { android {
namespace = "com.google.samples.apps.nowinandroid.core.analytics" namespace = "com.google.samples.apps.nowinandroid.core.analytics"
testFixtures.enable = true
} }
dependencies { dependencies {
@ -28,4 +29,8 @@ dependencies {
prodImplementation(platform(libs.firebase.bom)) prodImplementation(platform(libs.firebase.bom))
prodImplementation(libs.firebase.analytics) prodImplementation(libs.firebase.analytics)
testFixturesImplementation(libs.androidx.compose.runtime) {
because("https://issuetracker.google.com/issues/259523353#comment32")
}
} }

@ -14,10 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.util package com.google.samples.apps.nowinandroid.core.analytics
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
class TestAnalyticsHelper : AnalyticsHelper { class TestAnalyticsHelper : AnalyticsHelper {

@ -1,3 +0,0 @@
# :core:data-test module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_data_test.svg)

@ -1,29 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.hilt)
}
android {
namespace = "com.google.samples.apps.nowinandroid.core.data.test"
}
dependencies {
api(projects.core.data)
implementation(libs.hilt.android.testing)
}

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest />

@ -28,6 +28,7 @@ android {
isReturnDefaultValues = true isReturnDefaultValues = true
} }
} }
testFixtures.enable = true
} }
dependencies { dependencies {
@ -41,6 +42,10 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.serialization.json) testImplementation(libs.kotlinx.serialization.json)
testImplementation(projects.core.datastoreTest)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.datastore))
testImplementation(testFixtures(projects.core.notifications))
kspTestFixtures(libs.hilt.compiler)
testFixturesImplementation(libs.hilt.android.testing)
} }

@ -40,35 +40,23 @@ import dagger.hilt.components.SingletonComponent
abstract class DataModule { abstract class DataModule {
@Binds @Binds
internal abstract fun bindsTopicRepository( internal abstract fun bindsTopicRepository(it: OfflineFirstTopicsRepository): TopicsRepository
topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository
@Binds @Binds
internal abstract fun bindsNewsResourceRepository( internal abstract fun bindsNewsResourceRepository(it: OfflineFirstNewsRepository): NewsRepository
newsRepository: OfflineFirstNewsRepository,
): NewsRepository
@Binds @Binds
internal abstract fun bindsUserDataRepository( internal abstract fun bindsUserDataRepository(it: OfflineFirstUserDataRepository): UserDataRepository
userDataRepository: OfflineFirstUserDataRepository,
): UserDataRepository
@Binds @Binds
internal abstract fun bindsRecentSearchRepository( internal abstract fun bindsRecentSearchRepository(it: DefaultRecentSearchRepository): RecentSearchRepository
recentSearchRepository: DefaultRecentSearchRepository,
): RecentSearchRepository
@Binds @Binds
internal abstract fun bindsSearchContentsRepository( internal abstract fun bindsSearchContentsRepository(it: DefaultSearchContentsRepository): SearchContentsRepository
searchContentsRepository: DefaultSearchContentsRepository,
): SearchContentsRepository
@Binds @Binds
internal abstract fun bindsNetworkMonitor( internal abstract fun bindsNetworkMonitor(it: ConnectivityManagerNetworkMonitor): NetworkMonitor
networkMonitor: ConnectivityManagerNetworkMonitor,
): NetworkMonitor
@Binds @Binds
internal abstract fun binds(impl: TimeZoneBroadcastMonitor): TimeZoneMonitor internal abstract fun bindsTimeZoneMonitor(it: TimeZoneBroadcastMonitor): TimeZoneMonitor
} }

@ -18,12 +18,12 @@ package com.google.samples.apps.nowinandroid.core.data
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
import com.google.samples.apps.nowinandroid.core.data.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.repository.emptyUserData
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.emptyUserData
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant import kotlinx.datetime.Instant

@ -32,12 +32,12 @@ import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsRes
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore import com.google.samples.apps.nowinandroid.core.datastore.di.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
import com.google.samples.apps.nowinandroid.core.testing.notifications.TestNotifier import com.google.samples.apps.nowinandroid.core.notifications.TestNotifier
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher

@ -25,7 +25,7 @@ import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity import com.google.samples.apps.nowinandroid.core.database.model.TopicEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore import com.google.samples.apps.nowinandroid.core.datastore.di.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first

@ -18,7 +18,7 @@ package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore import com.google.samples.apps.nowinandroid.core.datastore.di.testUserPreferencesDataStore
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.google.samples.apps.nowinandroid.core.model.data.UserData

@ -14,19 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test package com.google.samples.apps.nowinandroid.core.data.di
import com.google.samples.apps.nowinandroid.core.data.di.DataModule import com.google.samples.apps.nowinandroid.core.data.repository.FakeNewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.FakeRecentSearchRepository
import com.google.samples.apps.nowinandroid.core.data.repository.FakeSearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.FakeTopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeNewsRepository import com.google.samples.apps.nowinandroid.core.data.util.AlwaysOnlineNetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeRecentSearchRepository import com.google.samples.apps.nowinandroid.core.data.util.DefaultZoneIdTimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeSearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeTopicsRepository
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import dagger.Binds import dagger.Binds
@ -39,37 +40,25 @@ import dagger.hilt.testing.TestInstallIn
components = [SingletonComponent::class], components = [SingletonComponent::class],
replaces = [DataModule::class], replaces = [DataModule::class],
) )
internal interface TestDataModule { interface TestDataModule {
@Binds @Binds
fun bindsTopicRepository( fun bindsTopicRepository(it: FakeTopicsRepository): TopicsRepository
fakeTopicsRepository: FakeTopicsRepository,
): TopicsRepository
@Binds @Binds
fun bindsNewsResourceRepository( fun bindsNewsResourceRepository(it: FakeNewsRepository): NewsRepository
fakeNewsRepository: FakeNewsRepository,
): NewsRepository
@Binds @Binds
fun bindsUserDataRepository( fun bindsUserDataRepository(it: FakeUserDataRepository): UserDataRepository
userDataRepository: FakeUserDataRepository,
): UserDataRepository
@Binds @Binds
fun bindsRecentSearchRepository( fun bindsRecentSearchRepository(it: FakeRecentSearchRepository): RecentSearchRepository
recentSearchRepository: FakeRecentSearchRepository,
): RecentSearchRepository
@Binds @Binds
fun bindsSearchContentsRepository( fun bindsSearchContentsRepository(it: FakeSearchContentsRepository): SearchContentsRepository
searchContentsRepository: FakeSearchContentsRepository,
): SearchContentsRepository
@Binds @Binds
fun bindsNetworkMonitor( fun bindsNetworkMonitor(it: AlwaysOnlineNetworkMonitor): NetworkMonitor
networkMonitor: AlwaysOnlineNetworkMonitor,
): NetworkMonitor
@Binds @Binds
fun binds(impl: DefaultZoneIdTimeZoneMonitor): TimeZoneMonitor fun bindsTimeZoneMonitor(it: DefaultZoneIdTimeZoneMonitor): TimeZoneMonitor
} }

@ -14,12 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.Synchronizer
import com.google.samples.apps.nowinandroid.core.data.model.asEntity import com.google.samples.apps.nowinandroid.core.data.model.asEntity
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.model.RecentSearchQuery import com.google.samples.apps.nowinandroid.core.data.model.RecentSearchQuery
import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject import javax.inject.Inject
@ -25,7 +24,7 @@ import javax.inject.Inject
/** /**
* Fake implementation of the [RecentSearchRepository] * Fake implementation of the [RecentSearchRepository]
*/ */
internal class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository { class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository {
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) = Unit override suspend fun insertOrReplaceRecentSearch(searchQuery: String) = Unit
override fun getRecentSearchQueries(limit: Int): Flow<List<RecentSearchQuery>> = override fun getRecentSearchQueries(limit: Int): Flow<List<RecentSearchQuery>> =

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import com.google.samples.apps.nowinandroid.core.model.data.SearchResult import com.google.samples.apps.nowinandroid.core.model.data.SearchResult
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
@ -25,7 +24,7 @@ import javax.inject.Inject
/** /**
* Fake implementation of the [SearchContentsRepository] * Fake implementation of the [SearchContentsRepository]
*/ */
internal class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository { class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository {
override suspend fun populateFtsData() = Unit override suspend fun populateFtsData() = Unit
override fun searchContents(searchQuery: String): Flow<SearchResult> = flowOf() override fun searchContents(searchQuery: String): Flow<SearchResult> = flowOf()

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.Synchronizer
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.Dispatcher import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
@ -36,7 +35,7 @@ import javax.inject.Inject
* This allows us to run the app with fake data, without needing an internet connection or working * This allows us to run the app with fake data, without needing an internet connection or working
* backend. * backend.
*/ */
internal class FakeTopicsRepository @Inject constructor( class FakeTopicsRepository @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val datasource: DemoNiaNetworkDataSource, private val datasource: DemoNiaNetworkDataSource,
) : TopicsRepository { ) : TopicsRepository {

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand

@ -14,11 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.Synchronizer
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.model.RecentSearchQuery import com.google.samples.apps.nowinandroid.core.data.model.RecentSearchQuery
import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.SearchResult import com.google.samples.apps.nowinandroid.core.model.data.SearchResult
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.Synchronizer
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.repository package com.google.samples.apps.nowinandroid.core.data.repository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.google.samples.apps.nowinandroid.core.model.data.UserData

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test package com.google.samples.apps.nowinandroid.core.data.util
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject import javax.inject.Inject

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.data.test package com.google.samples.apps.nowinandroid.core.data.util
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.util package com.google.samples.apps.nowinandroid.core.data.util
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.util package com.google.samples.apps.nowinandroid.core.data.util
import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.util package com.google.samples.apps.nowinandroid.core.data.util
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone

@ -1,3 +0,0 @@
# :core:datastore-test module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_test.svg)

@ -1,29 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.hilt)
}
android {
namespace = "com.google.samples.apps.nowinandroid.core.datastore.test"
}
dependencies {
implementation(libs.hilt.android.testing)
implementation(projects.core.common)
implementation(projects.core.datastore)
}

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest />

@ -30,6 +30,7 @@ android {
isReturnDefaultValues = true isReturnDefaultValues = true
} }
} }
testFixtures.enable = true
} }
dependencies { dependencies {
@ -39,6 +40,10 @@ dependencies {
implementation(projects.core.common) implementation(projects.core.common)
testImplementation(projects.core.datastoreTest)
testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.kotlinx.coroutines.test)
kspTestFixtures(libs.hilt.compiler)
testFixturesImplementation(libs.hilt.android.testing)
testFixturesImplementation(projects.core.common)
testFixturesImplementation(projects.core.datastore)
} }

@ -16,7 +16,7 @@
package com.google.samples.apps.nowinandroid.core.datastore package com.google.samples.apps.nowinandroid.core.datastore
import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore import com.google.samples.apps.nowinandroid.core.datastore.di.testUserPreferencesDataStore
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher

@ -14,13 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.datastore.test package com.google.samples.apps.nowinandroid.core.datastore.di
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.DataStoreFactory
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer
import com.google.samples.apps.nowinandroid.core.datastore.di.DataStoreModule
import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -35,7 +34,7 @@ import javax.inject.Singleton
components = [SingletonComponent::class], components = [SingletonComponent::class],
replaces = [DataStoreModule::class], replaces = [DataStoreModule::class],
) )
internal object TestDataStoreModule { object TestDataStoreModule {
@Provides @Provides
@Singleton @Singleton

@ -30,4 +30,5 @@ dependencies {
implementation(libs.javax.inject) implementation(libs.javax.inject)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
} }

@ -16,11 +16,11 @@
package com.google.samples.apps.nowinandroid.core.domain package com.google.samples.apps.nowinandroid.core.domain
import com.google.samples.apps.nowinandroid.core.data.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.domain.TopicSortField.NAME import com.google.samples.apps.nowinandroid.core.domain.TopicSortField.NAME
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest

@ -16,6 +16,7 @@
plugins { plugins {
alias(libs.plugins.nowinandroid.jvm.library) alias(libs.plugins.nowinandroid.jvm.library)
`java-test-fixtures`
} }
dependencies { dependencies {

@ -16,10 +16,7 @@
@file:Suppress("ktlint:standard:max-line-length") @file:Suppress("ktlint:standard:max-line-length")
package com.google.samples.apps.nowinandroid.core.testing.data package com.google.samples.apps.nowinandroid.core.model.data
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.Topic
val followableTopicTestData: List<FollowableTopic> = listOf( val followableTopicTestData: List<FollowableTopic> = listOf(
FollowableTopic( FollowableTopic(

@ -16,9 +16,8 @@
@file:Suppress("ktlint:standard:max-line-length") @file:Suppress("ktlint:standard:max-line-length")
package com.google.samples.apps.nowinandroid.core.testing.data package com.google.samples.apps.nowinandroid.core.model.data
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
val newsResourcesTestData: List<NewsResource> = listOf( val newsResourcesTestData: List<NewsResource> = listOf(

@ -16,9 +16,7 @@
@file:Suppress("ktlint:standard:max-line-length") @file:Suppress("ktlint:standard:max-line-length")
package com.google.samples.apps.nowinandroid.core.testing.data package com.google.samples.apps.nowinandroid.core.model.data
import com.google.samples.apps.nowinandroid.core.model.data.Topic
val topicsTestData: List<Topic> = listOf( val topicsTestData: List<Topic> = listOf(
Topic( Topic(

@ -16,13 +16,8 @@
@file:Suppress("ktlint:standard:max-line-length") @file:Suppress("ktlint:standard:max-line-length")
package com.google.samples.apps.nowinandroid.core.testing.data package com.google.samples.apps.nowinandroid.core.model.data
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone

@ -20,6 +20,7 @@ plugins {
android { android {
namespace = "com.google.samples.apps.nowinandroid.core.notifications" namespace = "com.google.samples.apps.nowinandroid.core.notifications"
testFixtures.enable = true
} }
dependencies { dependencies {

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.testing.notifications package com.google.samples.apps.nowinandroid.core.notifications
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.notifications.Notifier
/** /**
* Aggregates news resources that have been notified for addition * Aggregates news resources that have been notified for addition

@ -39,4 +39,7 @@ dependencies {
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.core.designsystem) implementation(projects.core.designsystem)
implementation(testFixtures(projects.core.data))
implementation(testFixtures(projects.core.datastore))
implementation(testFixtures(projects.sync))
} }

@ -0,0 +1,70 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.testing.di
import androidx.datastore.core.DataStore
import com.google.samples.apps.nowinandroid.core.data.di.DataModule
import com.google.samples.apps.nowinandroid.core.data.di.TestDataModule
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences
import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer
import com.google.samples.apps.nowinandroid.core.datastore.di.DataStoreModule
import com.google.samples.apps.nowinandroid.core.datastore.di.TestDataStoreModule
import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope
import com.google.samples.apps.nowinandroid.core.sync.di.TestSyncModule
import com.google.samples.apps.nowinandroid.sync.di.SyncModule
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import kotlinx.coroutines.CoroutineScope
import org.junit.rules.TemporaryFolder
import javax.inject.Singleton
/**
* KSP is currently not supported on Android testFixtures ([issuetracker](https://issuetracker.google.com/issues/259523353#comment32)).
*
* Including [TestDataStoreModule] in [Module.includes] leads to an unexpected compilation error (maybe due to datastore-proto and KSP ordering, certainly related to the initial issue).
*
* ```
* > Task :app:hiltJavaCompileDemoDebugAndroidTest FAILED
* nowinandroid\app\build\generated\hilt\component_sources\demoDebugAndroidTest\dagger\hilt\android\internal\testing\root\DaggerNavigationTest_HiltComponents_SingletonC.java:38: error: cannot find symbol
* import com.google.samples.apps.nowinandroid.core.datastore.di.TestDataStoreModule_ProvidesUserPreferencesDataStoreFactory;
* ^
* symbol: class TestDataStoreModule_ProvidesUserPreferencesDataStoreFactory
* location: package com.google.samples.apps.nowinandroid.core.datastore.di
* ```
*
* Therefore, a [providesUserPreferencesDataStore] delegate is added in this module.
*/
@Module(includes = [TestDataModule::class, TestSyncModule::class])
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [DataModule::class, SyncModule::class, DataStoreModule::class],
)
internal object TestingModule {
@Provides
@Singleton
fun providesUserPreferencesDataStore(
@ApplicationScope scope: CoroutineScope,
userPreferencesSerializer: UserPreferencesSerializer,
tmpFolder: TemporaryFolder,
): DataStore<UserPreferences> = TestDataStoreModule.providesUserPreferencesDataStore(
scope,
userPreferencesSerializer,
tmpFolder,
)
}

@ -37,4 +37,5 @@ dependencies {
implementation(libs.coil.kt.compose) implementation(libs.coil.kt.compose)
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(testFixtures(projects.core.model))
} }

@ -22,8 +22,8 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData import com.google.samples.apps.nowinandroid.core.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData import com.google.samples.apps.nowinandroid.core.model.data.userNewsResourcesTestData
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test

@ -1,133 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="791pt" height="260pt" viewBox="0.00 0.00 791.30 260.00">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-256 787.3,-256 787.3,4 -4,4"/>
<g id="node1" class="node">
<title>:core:data-test</title>
<ellipse fill="none" stroke="black" cx="327.95" cy="-234" rx="65.73" ry="18"/>
<text text-anchor="middle" x="327.95" y="-229.8" font-family="Times,serif" font-size="14.00">:core:data-test</text>
</g>
<g id="node2" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="327.95" cy="-162" rx="49.1" ry="18"/>
<text text-anchor="middle" x="327.95" y="-157.8" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<g id="edge1" class="edge">
<title>:core:data-test-&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M327.95,-215.7C327.95,-208.41 327.95,-199.73 327.95,-191.54"/>
<polygon fill="red" stroke="red" stroke-width="2" points="331.45,-193.13 327.95,-183.13 324.45,-193.13 331.45,-193.13"/>
</g>
<g id="node3" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="91.95" cy="-18" rx="66.81" ry="18"/>
<text text-anchor="middle" x="91.95" y="-13.8" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<g id="edge2" class="edge">
<title>:core:data-&gt;:core:common</title>
<path fill="none" stroke="black" d="M278.66,-160.32C197.49,-158.04 41.21,-148.38 6.95,-108 -3.4,-95.8 -0.81,-85.99 6.95,-72 15.05,-57.42 29,-46.33 43.29,-38.16"/>
<polygon fill="black" stroke="black" points="44.61,-41.42 51.83,-33.66 41.35,-35.22 44.61,-41.42"/>
</g>
<g id="node4" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="563.95" cy="-90" rx="66.26" ry="18"/>
<text text-anchor="middle" x="563.95" y="-85.8" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<g id="edge3" class="edge">
<title>:core:data-&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M365.35,-149.91C404.17,-138.39 465.29,-120.27 509.48,-107.16"/>
<polygon fill="red" stroke="red" stroke-width="2" points="508.83,-111 517.42,-104.8 506.84,-104.29 508.83,-111"/>
</g>
<g id="node5" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="411.95" cy="-90" rx="67.87" ry="18"/>
<text text-anchor="middle" x="411.95" y="-85.8" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<g id="edge4" class="edge">
<title>:core:data-&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M347.02,-145.12C357.74,-136.18 371.34,-124.85 383.32,-114.86"/>
<polygon fill="black" stroke="black" points="385.49,-117.61 390.93,-108.52 381.01,-112.23 385.49,-117.61"/>
</g>
<g id="node6" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="80.95" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="80.95" y="-85.8" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<g id="edge5" class="edge">
<title>:core:data-&gt;:core:network</title>
<path fill="none" stroke="black" d="M289.9,-150.22C248.69,-138.53 182.56,-119.8 135.75,-106.53"/>
<polygon fill="black" stroke="black" points="136.92,-103.22 126.35,-103.86 135.01,-109.96 136.92,-103.22"/>
</g>
<g id="node7" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="715.95" cy="-90" rx="67.34" ry="18"/>
<text text-anchor="middle" x="715.95" y="-85.8" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<g id="edge6" class="edge">
<title>:core:data-&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M372.63,-154.09C433.15,-144.56 544.48,-126.47 638.95,-108 644.14,-106.99 649.51,-105.89 654.9,-104.76"/>
<polygon fill="black" stroke="black" points="655.38,-108.24 664.43,-102.73 653.92,-101.39 655.38,-108.24"/>
</g>
<g id="node8" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="244.95" cy="-90" rx="81.29" ry="18"/>
<text text-anchor="middle" x="244.95" y="-85.8" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<g id="edge7" class="edge">
<title>:core:data-&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M309.12,-145.12C298.6,-136.24 285.28,-125.01 273.5,-115.08"/>
<polygon fill="black" stroke="black" points="275.93,-112.54 266.03,-108.77 271.42,-117.9 275.93,-112.54"/>
</g>
<g id="node9" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="244.95" cy="-18" rx="57.16" ry="18"/>
<text text-anchor="middle" x="244.95" y="-13.8" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<g id="edge8" class="edge">
<title>:core:database-&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M514.77,-77.69C506.17,-75.76 497.31,-73.8 488.95,-72 410.47,-55.13 390.22,-53.88 311.95,-36 307.67,-35.02 303.24,-33.97 298.79,-32.9"/>
<polygon fill="red" stroke="red" stroke-width="2" points="301.26,-29.9 290.71,-30.91 299.59,-36.7 301.26,-29.9"/>
</g>
<g id="edge11" class="edge">
<title>:core:datastore-&gt;:core:common</title>
<path fill="none" stroke="black" d="M361.01,-77.73C352.32,-75.82 343.38,-73.85 334.95,-72 273.33,-58.48 202.89,-43.12 153.77,-32.44"/>
<polygon fill="black" stroke="black" points="154.69,-29.05 144.17,-30.35 153.2,-35.89 154.69,-29.05"/>
</g>
<g id="edge10" class="edge">
<title>:core:datastore-&gt;:core:model</title>
<path fill="none" stroke="black" d="M376.95,-74.33C351.28,-63.57 316.25,-48.89 288.73,-37.35"/>
<polygon fill="black" stroke="black" points="290.18,-34.16 279.61,-33.52 287.48,-40.62 290.18,-34.16"/>
</g>
<g id="node10" class="node">
<title>:core:datastore-proto</title>
<ellipse fill="none" stroke="black" cx="411.95" cy="-18" rx="91.47" ry="18"/>
<text text-anchor="middle" x="411.95" y="-13.8" font-family="Times,serif" font-size="14.00">:core:datastore-proto</text>
</g>
<g id="edge9" class="edge">
<title>:core:datastore-&gt;:core:datastore-proto</title>
<path fill="none" stroke="black" d="M411.95,-71.7C411.95,-64.41 411.95,-55.73 411.95,-47.54"/>
<polygon fill="black" stroke="black" points="415.45,-47.62 411.95,-37.62 408.45,-47.62 415.45,-47.62"/>
</g>
<g id="edge12" class="edge">
<title>:core:network-&gt;:core:common</title>
<path fill="none" stroke="black" d="M83.67,-71.7C84.83,-64.32 86.22,-55.52 87.52,-47.25"/>
<polygon fill="black" stroke="black" points="90.94,-48.02 89.03,-37.6 84.02,-46.93 90.94,-48.02"/>
</g>
<g id="edge13" class="edge">
<title>:core:network-&gt;:core:model</title>
<path fill="none" stroke="black" d="M115.33,-74.33C140.46,-63.6 174.71,-48.98 201.7,-37.46"/>
<polygon fill="black" stroke="black" points="202.8,-40.8 210.63,-33.65 200.06,-34.36 202.8,-40.8"/>
</g>
<g id="edge15" class="edge">
<title>:core:notifications-&gt;:core:common</title>
<path fill="none" stroke="black" d="M210.62,-73.29C188.29,-63.07 158.98,-49.66 135.08,-38.73"/>
<polygon fill="black" stroke="black" points="136.79,-35.66 126.24,-34.69 133.88,-42.03 136.79,-35.66"/>
</g>
<g id="edge14" class="edge">
<title>:core:notifications-&gt;:core:model</title>
<path fill="none" stroke="black" d="M244.95,-71.7C244.95,-64.41 244.95,-55.73 244.95,-47.54"/>
<polygon fill="black" stroke="black" points="248.45,-47.62 244.95,-37.62 241.45,-47.62 248.45,-47.62"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.1 KiB

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="475pt" height="188pt" viewBox="0.00 0.00 474.97 188.00">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-184 470.97,-184 470.97,4 -4,4"/>
<g id="node1" class="node">
<title>:core:datastore-test</title>
<ellipse fill="none" stroke="black" cx="154.81" cy="-162" rx="84.5" ry="18"/>
<text text-anchor="middle" x="154.81" y="-157.8" font-family="Times,serif" font-size="14.00">:core:datastore-test</text>
</g>
<g id="node2" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="66.81" cy="-18" rx="66.81" ry="18"/>
<text text-anchor="middle" x="66.81" y="-13.8" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<g id="edge1" class="edge">
<title>:core:datastore-test-&gt;:core:common</title>
<path fill="none" stroke="black" d="M144.2,-143.87C129.07,-119.46 101.16,-74.43 83.32,-45.64"/>
<polygon fill="black" stroke="black" points="86.51,-44.13 78.26,-37.48 80.56,-47.82 86.51,-44.13"/>
</g>
<g id="node3" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="222.81" cy="-90" rx="67.87" ry="18"/>
<text text-anchor="middle" x="222.81" y="-85.8" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<g id="edge2" class="edge">
<title>:core:datastore-test-&gt;:core:datastore</title>
<path fill="none" stroke="red" stroke-width="2" d="M171.27,-144.05C179.46,-135.63 189.51,-125.28 198.56,-115.97"/>
<polygon fill="red" stroke="red" stroke-width="2" points="199.94,-119.57 204.4,-109.96 194.92,-114.69 199.94,-119.57"/>
</g>
<g id="edge5" class="edge">
<title>:core:datastore-&gt;:core:common</title>
<path fill="none" stroke="black" d="M189.35,-73.98C166.32,-63.65 135.5,-49.82 110.53,-38.61"/>
<polygon fill="black" stroke="black" points="112.16,-35.51 101.61,-34.61 109.3,-41.9 112.16,-35.51"/>
</g>
<g id="node4" class="node">
<title>:core:datastore-proto</title>
<ellipse fill="none" stroke="black" cx="242.81" cy="-18" rx="91.47" ry="18"/>
<text text-anchor="middle" x="242.81" y="-13.8" font-family="Times,serif" font-size="14.00">:core:datastore-proto</text>
</g>
<g id="edge3" class="edge">
<title>:core:datastore-&gt;:core:datastore-proto</title>
<path fill="none" stroke="red" stroke-width="2" d="M227.76,-71.7C229.89,-64.24 232.44,-55.32 234.82,-46.97"/>
<polygon fill="red" stroke="red" stroke-width="2" points="237.71,-49.59 237.1,-39.01 230.98,-47.67 237.71,-49.59"/>
</g>
<g id="node5" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="409.81" cy="-18" rx="57.16" ry="18"/>
<text text-anchor="middle" x="409.81" y="-13.8" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<g id="edge4" class="edge">
<title>:core:datastore-&gt;:core:model</title>
<path fill="none" stroke="black" d="M260.66,-74.83C290.25,-63.76 331.54,-48.3 363.18,-36.46"/>
<polygon fill="black" stroke="black" points="364.38,-39.75 372.51,-32.96 361.92,-33.19 364.38,-39.75"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

@ -1,153 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="791pt" height="332pt" viewBox="0.00 0.00 791.39 332.00">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-328 787.39,-328 787.39,4 -4,4"/>
<g id="node1" class="node">
<title>:sync:sync-test</title>
<ellipse fill="none" stroke="black" cx="453.04" cy="-306" rx="68.43" ry="18"/>
<text text-anchor="middle" x="453.04" y="-301.8" font-family="Times,serif" font-size="14.00">:sync:sync-test</text>
</g>
<g id="node2" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="412.04" cy="-162" rx="49.1" ry="18"/>
<text text-anchor="middle" x="412.04" y="-157.8" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<g id="edge1" class="edge">
<title>:sync:sync-test-&gt;:core:data</title>
<path fill="none" stroke="black" d="M444.89,-287.63C440.34,-277.43 434.84,-264.18 431.04,-252 424.86,-232.14 420.01,-209.18 416.76,-191.55"/>
<polygon fill="black" stroke="black" points="420.22,-191.03 415.03,-181.79 413.33,-192.25 420.22,-191.03"/>
</g>
<g id="node3" class="node">
<title>:sync:work</title>
<ellipse fill="none" stroke="black" cx="494.04" cy="-234" rx="53.95" ry="18"/>
<text text-anchor="middle" x="494.04" y="-229.8" font-family="Times,serif" font-size="14.00">:sync:work</text>
</g>
<g id="edge2" class="edge">
<title>:sync:sync-test-&gt;:sync:work</title>
<path fill="none" stroke="red" stroke-width="2" d="M462.97,-288.05C467.6,-280.14 473.23,-270.54 478.41,-261.69"/>
<polygon fill="red" stroke="red" stroke-width="2" points="480.65,-264.8 482.68,-254.4 474.61,-261.26 480.65,-264.8"/>
</g>
<g id="node4" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="92.04" cy="-18" rx="66.81" ry="18"/>
<text text-anchor="middle" x="92.04" y="-13.8" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<g id="edge3" class="edge">
<title>:core:data-&gt;:core:common</title>
<path fill="none" stroke="black" d="M363.83,-158.21C261.55,-151.79 30.29,-134.48 7.04,-108 -3.51,-95.97 -0.72,-85.99 7.04,-72 15.13,-57.42 29.09,-46.33 43.38,-38.16"/>
<polygon fill="black" stroke="black" points="44.7,-41.42 51.91,-33.66 41.44,-35.22 44.7,-41.42"/>
</g>
<g id="node5" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="564.04" cy="-90" rx="66.26" ry="18"/>
<text text-anchor="middle" x="564.04" y="-85.8" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<g id="edge4" class="edge">
<title>:core:data-&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M441.71,-147.34C464.24,-136.96 495.55,-122.54 520.85,-110.89"/>
<polygon fill="red" stroke="red" stroke-width="2" points="520.92,-114.71 528.54,-107.35 517.99,-108.35 520.92,-114.71"/>
</g>
<g id="node6" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="412.04" cy="-90" rx="67.87" ry="18"/>
<text text-anchor="middle" x="412.04" y="-85.8" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<g id="edge5" class="edge">
<title>:core:data-&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M412.04,-143.7C412.04,-136.41 412.04,-127.73 412.04,-119.54"/>
<polygon fill="black" stroke="black" points="415.54,-119.62 412.04,-109.62 408.54,-119.62 415.54,-119.62"/>
</g>
<g id="node7" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="81.04" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="81.04" y="-85.8" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<g id="edge6" class="edge">
<title>:core:data-&gt;:core:network</title>
<path fill="none" stroke="black" d="M369.69,-152.57C318.7,-142.35 230.49,-124.44 155.04,-108 150.41,-106.99 145.61,-105.93 140.79,-104.85"/>
<polygon fill="black" stroke="black" points="141.57,-101.44 131.05,-102.65 140.03,-108.26 141.57,-101.44"/>
</g>
<g id="node8" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="716.04" cy="-90" rx="67.34" ry="18"/>
<text text-anchor="middle" x="716.04" y="-85.8" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<g id="edge7" class="edge">
<title>:core:data-&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M452.73,-151.63C504.74,-139.66 595.25,-118.81 655.41,-104.96"/>
<polygon fill="black" stroke="black" points="655.84,-108.45 664.8,-102.8 654.27,-101.63 655.84,-108.45"/>
</g>
<g id="node9" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="245.04" cy="-90" rx="81.29" ry="18"/>
<text text-anchor="middle" x="245.04" y="-85.8" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<g id="edge8" class="edge">
<title>:core:data-&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M380.63,-147.83C355.75,-137.4 320.59,-122.67 292.31,-110.81"/>
<polygon fill="black" stroke="black" points="293.82,-107.65 283.24,-107.01 291.11,-114.1 293.82,-107.65"/>
</g>
<g id="edge18" class="edge">
<title>:sync:work-&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M475.03,-216.76C464.39,-207.69 450.96,-196.22 439.24,-186.21"/>
<polygon fill="red" stroke="red" stroke-width="2" points="442.86,-184.7 432.98,-180.87 438.31,-190.03 442.86,-184.7"/>
</g>
<g id="edge17" class="edge">
<title>:sync:work-&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M518.22,-217.53C557.69,-192.29 636.1,-142.13 681.45,-113.13"/>
<polygon fill="black" stroke="black" points="683.06,-116.25 689.6,-107.92 679.29,-110.36 683.06,-116.25"/>
</g>
<g id="node10" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="245.04" cy="-18" rx="57.16" ry="18"/>
<text text-anchor="middle" x="245.04" y="-13.8" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<g id="edge9" class="edge">
<title>:core:database-&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M514.86,-77.69C506.26,-75.76 497.4,-73.8 489.04,-72 410.56,-55.13 390.31,-53.88 312.04,-36 307.76,-35.02 303.32,-33.97 298.88,-32.9"/>
<polygon fill="red" stroke="red" stroke-width="2" points="301.35,-29.9 290.8,-30.91 299.67,-36.7 301.35,-29.9"/>
</g>
<g id="edge12" class="edge">
<title>:core:datastore-&gt;:core:common</title>
<path fill="none" stroke="black" d="M361.1,-77.73C352.41,-75.82 343.47,-73.85 335.04,-72 273.42,-58.48 202.98,-43.12 153.86,-32.44"/>
<polygon fill="black" stroke="black" points="154.78,-29.05 144.26,-30.35 153.29,-35.89 154.78,-29.05"/>
</g>
<g id="edge11" class="edge">
<title>:core:datastore-&gt;:core:model</title>
<path fill="none" stroke="black" d="M377.03,-74.33C351.37,-63.57 316.34,-48.89 288.82,-37.35"/>
<polygon fill="black" stroke="black" points="290.27,-34.16 279.7,-33.52 287.57,-40.62 290.27,-34.16"/>
</g>
<g id="node11" class="node">
<title>:core:datastore-proto</title>
<ellipse fill="none" stroke="black" cx="412.04" cy="-18" rx="91.47" ry="18"/>
<text text-anchor="middle" x="412.04" y="-13.8" font-family="Times,serif" font-size="14.00">:core:datastore-proto</text>
</g>
<g id="edge10" class="edge">
<title>:core:datastore-&gt;:core:datastore-proto</title>
<path fill="none" stroke="black" d="M412.04,-71.7C412.04,-64.41 412.04,-55.73 412.04,-47.54"/>
<polygon fill="black" stroke="black" points="415.54,-47.62 412.04,-37.62 408.54,-47.62 415.54,-47.62"/>
</g>
<g id="edge13" class="edge">
<title>:core:network-&gt;:core:common</title>
<path fill="none" stroke="black" d="M83.76,-71.7C84.92,-64.32 86.3,-55.52 87.6,-47.25"/>
<polygon fill="black" stroke="black" points="91.03,-48.02 89.12,-37.6 84.11,-46.93 91.03,-48.02"/>
</g>
<g id="edge14" class="edge">
<title>:core:network-&gt;:core:model</title>
<path fill="none" stroke="black" d="M115.42,-74.33C140.55,-63.6 174.8,-48.98 201.78,-37.46"/>
<polygon fill="black" stroke="black" points="202.89,-40.8 210.72,-33.65 200.14,-34.36 202.89,-40.8"/>
</g>
<g id="edge16" class="edge">
<title>:core:notifications-&gt;:core:common</title>
<path fill="none" stroke="black" d="M210.71,-73.29C188.37,-63.07 159.07,-49.66 135.17,-38.73"/>
<polygon fill="black" stroke="black" points="136.88,-35.66 126.33,-34.69 133.97,-42.03 136.88,-35.66"/>
</g>
<g id="edge15" class="edge">
<title>:core:notifications-&gt;:core:model</title>
<path fill="none" stroke="black" d="M245.04,-71.7C245.04,-64.41 245.04,-55.73 245.04,-47.54"/>
<polygon fill="black" stroke="black" points="248.54,-47.62 245.04,-37.62 241.54,-47.62 248.54,-47.62"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.3 KiB

@ -1,138 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="813pt" height="260pt" viewBox="0.00 0.00 812.60 260.00">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-256 808.6,-256 808.6,4 -4,4"/>
<g id="node1" class="node">
<title>:sync:work</title>
<ellipse fill="none" stroke="black" cx="161.34" cy="-234" rx="53.95" ry="18"/>
<text text-anchor="middle" x="161.34" y="-229.8" font-family="Times,serif" font-size="14.00">:sync:work</text>
</g>
<g id="node2" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="67.34" cy="-90" rx="67.34" ry="18"/>
<text text-anchor="middle" x="67.34" y="-85.8" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<g id="edge1" class="edge">
<title>:sync:work-&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M150.19,-216.15C134.01,-191.71 103.86,-146.17 84.74,-117.29"/>
<polygon fill="black" stroke="black" points="87.75,-115.49 79.32,-109.09 81.92,-119.36 87.75,-115.49"/>
</g>
<g id="node3" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="255.34" cy="-162" rx="49.1" ry="18"/>
<text text-anchor="middle" x="255.34" y="-157.8" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<g id="edge2" class="edge">
<title>:sync:work-&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M182.67,-217.12C195.26,-207.74 211.39,-195.73 225.25,-185.41"/>
<polygon fill="red" stroke="red" stroke-width="2" points="226.06,-189.17 231.99,-180.39 221.88,-183.55 226.06,-189.17"/>
</g>
<g id="edge7" class="edge">
<title>:core:data-&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M221.73,-148.49C192.28,-137.52 149.12,-121.45 115.92,-109.09"/>
<polygon fill="black" stroke="black" points="117.53,-105.95 106.93,-105.74 115.08,-112.51 117.53,-105.95"/>
</g>
<g id="node4" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="266.34" cy="-18" rx="66.81" ry="18"/>
<text text-anchor="middle" x="266.34" y="-13.8" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<g id="edge3" class="edge">
<title>:core:data-&gt;:core:common</title>
<path fill="none" stroke="black" d="M224.51,-147.58C208.51,-138.83 190.44,-125.71 181.34,-108 174.03,-93.77 173.58,-85.99 181.34,-72 189.43,-57.42 203.38,-46.33 217.67,-38.16"/>
<polygon fill="black" stroke="black" points="218.99,-41.42 226.21,-33.66 215.73,-35.22 218.99,-41.42"/>
</g>
<g id="node5" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="738.34" cy="-90" rx="66.26" ry="18"/>
<text text-anchor="middle" x="738.34" y="-85.8" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<g id="edge4" class="edge">
<title>:core:data-&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M302.37,-156.34C378.53,-148.47 533.42,-131.11 663.34,-108 668.49,-107.08 673.82,-106.05 679.16,-104.96"/>
<polygon fill="red" stroke="red" stroke-width="2" points="678.05,-108.77 687.1,-103.27 676.59,-101.92 678.05,-108.77"/>
</g>
<g id="node6" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="586.34" cy="-90" rx="67.87" ry="18"/>
<text text-anchor="middle" x="586.34" y="-85.8" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<g id="edge5" class="edge">
<title>:core:data-&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M297.62,-152.33C348.06,-141.96 434.91,-123.98 509.34,-108 514.22,-106.95 519.26,-105.86 524.33,-104.76"/>
<polygon fill="black" stroke="black" points="524.92,-108.21 533.94,-102.65 523.42,-101.37 524.92,-108.21"/>
</g>
<g id="node7" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="255.34" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="255.34" y="-85.8" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<g id="edge6" class="edge">
<title>:core:data-&gt;:core:network</title>
<path fill="none" stroke="black" d="M255.34,-143.7C255.34,-136.41 255.34,-127.73 255.34,-119.54"/>
<polygon fill="black" stroke="black" points="258.84,-119.62 255.34,-109.62 251.84,-119.62 258.84,-119.62"/>
</g>
<g id="node8" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="419.34" cy="-90" rx="81.29" ry="18"/>
<text text-anchor="middle" x="419.34" y="-85.8" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<g id="edge8" class="edge">
<title>:core:data-&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M286.58,-147.67C310.86,-137.3 344.94,-122.76 372.49,-111"/>
<polygon fill="black" stroke="black" points="373.86,-114.22 381.69,-107.07 371.12,-107.78 373.86,-114.22"/>
</g>
<g id="node9" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="419.34" cy="-18" rx="57.16" ry="18"/>
<text text-anchor="middle" x="419.34" y="-13.8" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<g id="edge9" class="edge">
<title>:core:database-&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M689.16,-77.69C680.56,-75.76 671.7,-73.8 663.34,-72 584.86,-55.13 564.6,-53.88 486.34,-36 482.06,-35.02 477.62,-33.97 473.18,-32.9"/>
<polygon fill="red" stroke="red" stroke-width="2" points="475.65,-29.9 465.1,-30.91 473.97,-36.7 475.65,-29.9"/>
</g>
<g id="edge12" class="edge">
<title>:core:datastore-&gt;:core:common</title>
<path fill="none" stroke="black" d="M535.4,-77.73C526.71,-75.82 517.77,-73.85 509.34,-72 447.72,-58.48 377.27,-43.12 328.16,-32.44"/>
<polygon fill="black" stroke="black" points="329.07,-29.05 318.56,-30.35 327.59,-35.89 329.07,-29.05"/>
</g>
<g id="edge11" class="edge">
<title>:core:datastore-&gt;:core:model</title>
<path fill="none" stroke="black" d="M551.33,-74.33C525.66,-63.57 490.64,-48.89 463.12,-37.35"/>
<polygon fill="black" stroke="black" points="464.57,-34.16 453.99,-33.52 461.86,-40.62 464.57,-34.16"/>
</g>
<g id="node10" class="node">
<title>:core:datastore-proto</title>
<ellipse fill="none" stroke="black" cx="586.34" cy="-18" rx="91.47" ry="18"/>
<text text-anchor="middle" x="586.34" y="-13.8" font-family="Times,serif" font-size="14.00">:core:datastore-proto</text>
</g>
<g id="edge10" class="edge">
<title>:core:datastore-&gt;:core:datastore-proto</title>
<path fill="none" stroke="black" d="M586.34,-71.7C586.34,-64.41 586.34,-55.73 586.34,-47.54"/>
<polygon fill="black" stroke="black" points="589.84,-47.62 586.34,-37.62 582.84,-47.62 589.84,-47.62"/>
</g>
<g id="edge13" class="edge">
<title>:core:network-&gt;:core:common</title>
<path fill="none" stroke="black" d="M258.06,-71.7C259.22,-64.32 260.6,-55.52 261.9,-47.25"/>
<polygon fill="black" stroke="black" points="265.32,-48.02 263.42,-37.6 258.41,-46.93 265.32,-48.02"/>
</g>
<g id="edge14" class="edge">
<title>:core:network-&gt;:core:model</title>
<path fill="none" stroke="black" d="M289.72,-74.33C314.85,-63.6 349.1,-48.98 376.08,-37.46"/>
<polygon fill="black" stroke="black" points="377.19,-40.8 385.01,-33.65 374.44,-34.36 377.19,-40.8"/>
</g>
<g id="edge16" class="edge">
<title>:core:notifications-&gt;:core:common</title>
<path fill="none" stroke="black" d="M385.01,-73.29C362.67,-63.07 333.37,-49.66 309.47,-38.73"/>
<polygon fill="black" stroke="black" points="311.18,-35.66 300.63,-34.69 308.27,-42.03 311.18,-35.66"/>
</g>
<g id="edge15" class="edge">
<title>:core:notifications-&gt;:core:model</title>
<path fill="none" stroke="black" d="M419.34,-71.7C419.34,-64.41 419.34,-55.73 419.34,-47.54"/>
<polygon fill="black" stroke="black" points="422.84,-47.62 419.34,-37.62 415.84,-47.62 422.84,-47.62"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.4 KiB

@ -28,6 +28,9 @@ dependencies {
implementation(projects.core.data) implementation(projects.core.data)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.core.model))
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(testFixtures(projects.core.model))
} }

@ -34,7 +34,7 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.performScrollToNode
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.testing.TestLifecycleOwner import androidx.lifecycle.testing.TestLifecycleOwner
import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData import com.google.samples.apps.nowinandroid.core.model.data.userNewsResourcesTestData
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule

@ -17,9 +17,9 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks package com.google.samples.apps.nowinandroid.feature.bookmarks
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.testing.data.newsResourcesTestData import com.google.samples.apps.nowinandroid.core.data.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.model.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success

@ -34,7 +34,10 @@ dependencies {
testImplementation(libs.robolectric) testImplementation(libs.robolectric)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(projects.core.screenshotTesting) testImplementation(projects.core.screenshotTesting)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.core.analytics))
testDemoImplementation(libs.roborazzi) testDemoImplementation(libs.roborazzi)
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(testFixtures(projects.core.model))
} }

@ -28,9 +28,9 @@ import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.performScrollToNode
import com.google.samples.apps.nowinandroid.core.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.model.data.userNewsResourcesTestData
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test

@ -19,20 +19,20 @@ package com.google.samples.apps.nowinandroid.feature.foryou
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param
import com.google.samples.apps.nowinandroid.core.analytics.TestAnalyticsHelper
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.repository.emptyUserData
import com.google.samples.apps.nowinandroid.core.data.util.TestSyncManager
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.emptyUserData
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.core.testing.util.TestAnalyticsHelper
import com.google.samples.apps.nowinandroid.core.testing.util.TestSyncManager
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.LINKED_NEWS_RESOURCE_ID import com.google.samples.apps.nowinandroid.feature.foryou.navigation.LINKED_NEWS_RESOURCE_ID
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect

@ -28,6 +28,8 @@ dependencies {
implementation(projects.core.domain) implementation(projects.core.domain)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(testFixtures(projects.core.model))
} }

@ -24,7 +24,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithContentDescription import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData import com.google.samples.apps.nowinandroid.core.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.feature.interests.InterestsScreen import com.google.samples.apps.nowinandroid.feature.interests.InterestsScreen
import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState
import org.junit.Before import org.junit.Before

@ -17,11 +17,11 @@
package com.google.samples.apps.nowinandroid.interests package com.google.samples.apps.nowinandroid.interests
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import com.google.samples.apps.nowinandroid.core.data.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState
import com.google.samples.apps.nowinandroid.feature.interests.InterestsViewModel import com.google.samples.apps.nowinandroid.feature.interests.InterestsViewModel

@ -30,7 +30,10 @@ dependencies {
implementation(projects.core.ui) implementation(projects.core.ui)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.core.model))
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(testFixtures(projects.core.model))
} }

@ -33,8 +33,8 @@ import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.DARK
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID
import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.google.samples.apps.nowinandroid.core.model.data.UserData
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData import com.google.samples.apps.nowinandroid.core.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.testing.data.newsResourcesTestData import com.google.samples.apps.nowinandroid.core.model.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.ui.R.string import com.google.samples.apps.nowinandroid.core.ui.R.string
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule

@ -18,14 +18,14 @@ package com.google.samples.apps.nowinandroid.feature.search
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper
import com.google.samples.apps.nowinandroid.core.data.repository.TestRecentSearchRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestSearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.repository.emptyUserData
import com.google.samples.apps.nowinandroid.core.domain.GetRecentSearchQueriesUseCase import com.google.samples.apps.nowinandroid.core.domain.GetRecentSearchQueriesUseCase
import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsUseCase import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsUseCase
import com.google.samples.apps.nowinandroid.core.testing.data.newsResourcesTestData import com.google.samples.apps.nowinandroid.core.model.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.testing.data.topicsTestData import com.google.samples.apps.nowinandroid.core.model.data.topicsTestData
import com.google.samples.apps.nowinandroid.core.testing.repository.TestRecentSearchRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestSearchContentsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.emptyUserData
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.feature.search.RecentSearchQueriesUiState.Success import com.google.samples.apps.nowinandroid.feature.search.RecentSearchQueriesUiState.Success
import com.google.samples.apps.nowinandroid.feature.search.SearchResultUiState.EmptyQuery import com.google.samples.apps.nowinandroid.feature.search.SearchResultUiState.EmptyQuery

@ -30,6 +30,7 @@ dependencies {
implementation(projects.core.data) implementation(projects.core.data)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
} }

@ -16,9 +16,9 @@
package com.google.samples.apps.nowinandroid.feature.settings package com.google.samples.apps.nowinandroid.feature.settings
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.DARK import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.DARK
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Loading import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Loading
import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success

@ -28,6 +28,8 @@ dependencies {
implementation(projects.core.data) implementation(projects.core.data)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
androidTestImplementation(testFixtures(projects.core.model))
} }

@ -24,8 +24,8 @@ import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.performScrollToNode
import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData import com.google.samples.apps.nowinandroid.core.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData import com.google.samples.apps.nowinandroid.core.model.data.userNewsResourcesTestData
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test

@ -18,12 +18,12 @@ package com.google.samples.apps.nowinandroid.feature.topic
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ID_ARG import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ID_ARG
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect

@ -40,5 +40,9 @@ kotlin.code.style=official
android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false android.defaults.buildfeatures.shaders=false
# Android Test Fixtures
android.experimental.enableTestFixturesKotlinSupport=true
android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.experimental.enableTestFixturesKotlinSupport
# Run Roborazzi screenshot tests with the local tests # Run Roborazzi screenshot tests with the local tests
roborazzi.test.verify=true roborazzi.test.verify=true

@ -2,7 +2,7 @@
accompanist = "0.34.0" accompanist = "0.34.0"
androidDesugarJdkLibs = "2.0.4" androidDesugarJdkLibs = "2.0.4"
# AGP and tools should be updated together # AGP and tools should be updated together
androidGradlePlugin = "8.4.0" androidGradlePlugin = "8.5.0"
androidTools = "31.4.0" androidTools = "31.4.0"
androidxActivity = "1.8.2" androidxActivity = "1.8.2"
androidxAppCompat = "1.6.1" androidxAppCompat = "1.6.1"

@ -39,11 +39,9 @@ include(":benchmarks")
include(":core:analytics") include(":core:analytics")
include(":core:common") include(":core:common")
include(":core:data") include(":core:data")
include(":core:data-test")
include(":core:database") include(":core:database")
include(":core:datastore") include(":core:datastore")
include(":core:datastore-proto") include(":core:datastore-proto")
include(":core:datastore-test")
include(":core:designsystem") include(":core:designsystem")
include(":core:domain") include(":core:domain")
include(":core:model") include(":core:model")
@ -60,6 +58,5 @@ include(":feature:topic")
include(":feature:search") include(":feature:search")
include(":feature:settings") include(":feature:settings")
include(":lint") include(":lint")
include(":sync:work") include(":sync")
include(":sync:sync-test")
include(":ui-test-hilt-manifest") include(":ui-test-hilt-manifest")

@ -23,6 +23,7 @@ android {
defaultConfig { defaultConfig {
testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner" testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
} }
testFixtures.enable = true
namespace = "com.google.samples.apps.nowinandroid.sync" namespace = "com.google.samples.apps.nowinandroid.sync"
} }
@ -42,4 +43,8 @@ dependencies {
androidTestImplementation(libs.hilt.android.testing) androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(libs.kotlinx.coroutines.guava) androidTestImplementation(libs.kotlinx.coroutines.guava)
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
kspTestFixtures(libs.hilt.compiler)
testFixturesImplementation(libs.hilt.android.testing)
testFixturesImplementation(projects.core.data)
} }

@ -23,11 +23,13 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.testing.SynchronousExecutor import androidx.work.testing.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper import androidx.work.testing.WorkManagerTestInitHelper
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertEquals import kotlin.test.assertEquals
@HiltAndroidTest @HiltAndroidTest
@ -36,6 +38,10 @@ class SyncWorkerTest {
@get:Rule(order = 0) @get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this) val hiltRule = HiltAndroidRule(this)
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
private val context get() = InstrumentationRegistry.getInstrumentation().context private val context get() = InstrumentationRegistry.getInstrumentation().context
@Before @Before

@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.sync.test package com.google.samples.apps.nowinandroid.core.sync.di
import com.google.samples.apps.nowinandroid.core.data.util.SyncManager import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import com.google.samples.apps.nowinandroid.sync.di.SyncModule import com.google.samples.apps.nowinandroid.sync.di.SyncModule
import com.google.samples.apps.nowinandroid.sync.status.NeverSyncingSyncManager
import com.google.samples.apps.nowinandroid.sync.status.StubSyncSubscriber import com.google.samples.apps.nowinandroid.sync.status.StubSyncSubscriber
import com.google.samples.apps.nowinandroid.sync.status.SyncSubscriber import com.google.samples.apps.nowinandroid.sync.status.SyncSubscriber
import dagger.Binds import dagger.Binds
@ -30,14 +31,10 @@ import dagger.hilt.testing.TestInstallIn
components = [SingletonComponent::class], components = [SingletonComponent::class],
replaces = [SyncModule::class], replaces = [SyncModule::class],
) )
internal interface TestSyncModule { interface TestSyncModule {
@Binds @Binds
fun bindsSyncStatusMonitor( fun bindsSyncManager(it: NeverSyncingSyncManager): SyncManager
syncStatusMonitor: NeverSyncingSyncManager,
): SyncManager
@Binds @Binds
fun bindsSyncSubscriber( fun bindsSyncSubscriber(it: StubSyncSubscriber): SyncSubscriber
syncSubscriber: StubSyncSubscriber,
): SyncSubscriber
} }

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package com.google.samples.apps.nowinandroid.core.sync.test package com.google.samples.apps.nowinandroid.sync.status
import com.google.samples.apps.nowinandroid.core.data.util.SyncManager import com.google.samples.apps.nowinandroid.core.data.util.SyncManager
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject import javax.inject.Inject
internal class NeverSyncingSyncManager @Inject constructor() : SyncManager { class NeverSyncingSyncManager @Inject constructor() : SyncManager {
override val isSyncing: Flow<Boolean> = flowOf(false) override val isSyncing: Flow<Boolean> = flowOf(false)
override fun requestSync() = Unit override fun requestSync() = Unit
} }

@ -1 +0,0 @@
/build

@ -1,3 +0,0 @@
# :sync:sync-test module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_sync_sync_test.svg)

@ -1,29 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.hilt)
}
android {
namespace = "com.google.samples.apps.nowinandroid.core.sync.test"
}
dependencies {
implementation(libs.hilt.android.testing)
implementation(projects.core.data)
implementation(projects.sync.work)
}

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest />

@ -1 +0,0 @@
/build
Loading…
Cancel
Save