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

@ -27,10 +27,10 @@ import androidx.navigation.compose.composable
import androidx.navigation.createGraph
import androidx.navigation.testing.TestNavHostController
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.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor
import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor
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.util.TestNetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TestTimeZoneMonitor
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher

@ -34,9 +34,9 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowSizeClass
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.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.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme

@ -21,6 +21,7 @@ plugins {
android {
namespace = "com.google.samples.apps.nowinandroid.core.analytics"
testFixtures.enable = true
}
dependencies {
@ -28,4 +29,8 @@ dependencies {
prodImplementation(platform(libs.firebase.bom))
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.
*/
package com.google.samples.apps.nowinandroid.core.testing.util
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
package com.google.samples.apps.nowinandroid.core.analytics
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
}
}
testFixtures.enable = true
}
dependencies {
@ -41,6 +42,10 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.serialization.json)
testImplementation(projects.core.datastoreTest)
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 {
@Binds
internal abstract fun bindsTopicRepository(
topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository
internal abstract fun bindsTopicRepository(it: OfflineFirstTopicsRepository): TopicsRepository
@Binds
internal abstract fun bindsNewsResourceRepository(
newsRepository: OfflineFirstNewsRepository,
): NewsRepository
internal abstract fun bindsNewsResourceRepository(it: OfflineFirstNewsRepository): NewsRepository
@Binds
internal abstract fun bindsUserDataRepository(
userDataRepository: OfflineFirstUserDataRepository,
): UserDataRepository
internal abstract fun bindsUserDataRepository(it: OfflineFirstUserDataRepository): UserDataRepository
@Binds
internal abstract fun bindsRecentSearchRepository(
recentSearchRepository: DefaultRecentSearchRepository,
): RecentSearchRepository
internal abstract fun bindsRecentSearchRepository(it: DefaultRecentSearchRepository): RecentSearchRepository
@Binds
internal abstract fun bindsSearchContentsRepository(
searchContentsRepository: DefaultSearchContentsRepository,
): SearchContentsRepository
internal abstract fun bindsSearchContentsRepository(it: DefaultSearchContentsRepository): SearchContentsRepository
@Binds
internal abstract fun bindsNetworkMonitor(
networkMonitor: ConnectivityManagerNetworkMonitor,
): NetworkMonitor
internal abstract fun bindsNetworkMonitor(it: ConnectivityManagerNetworkMonitor): NetworkMonitor
@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.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.Topic
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.test.runTest
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.asExternalModel
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.Topic
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.testing.notifications.TestNotifier
import com.google.samples.apps.nowinandroid.core.notifications.TestNotifier
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
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.asExternalModel
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.network.model.NetworkTopic
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.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.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData

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

@ -14,12 +14,10 @@
* 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.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.asExternalModel
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource

@ -14,10 +14,9 @@
* 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.repository.RecentSearchRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject
@ -25,7 +24,7 @@ import javax.inject.Inject
/**
* Fake implementation of the [RecentSearchRepository]
*/
internal class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository {
class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository {
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) = Unit
override fun getRecentSearchQueries(limit: Int): Flow<List<RecentSearchQuery>> =

@ -14,9 +14,8 @@
* 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
@ -25,7 +24,7 @@ import javax.inject.Inject
/**
* Fake implementation of the [SearchContentsRepository]
*/
internal class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository {
class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository {
override suspend fun populateFtsData() = Unit
override fun searchContents(searchQuery: String): Flow<SearchResult> = flowOf()

@ -14,10 +14,9 @@
* 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.repository.TopicsRepository
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.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
* backend.
*/
internal class FakeTopicsRepository @Inject constructor(
class FakeTopicsRepository @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val datasource: DemoNiaNetworkDataSource,
) : TopicsRepository {

@ -14,9 +14,8 @@
* 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.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand

@ -14,11 +14,9 @@
* 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.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.Topic
import kotlinx.coroutines.channels.BufferOverflow

@ -14,10 +14,9 @@
* 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.repository.RecentSearchRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

@ -14,9 +14,8 @@
* 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.SearchResult
import com.google.samples.apps.nowinandroid.core.model.data.Topic

@ -14,10 +14,9 @@
* 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.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow

@ -14,9 +14,8 @@
* 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.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData

@ -14,9 +14,8 @@
* 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.flowOf
import javax.inject.Inject

@ -14,9 +14,8 @@
* 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.flowOf
import kotlinx.datetime.TimeZone

@ -14,9 +14,8 @@
* 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.MutableStateFlow

@ -14,9 +14,8 @@
* 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.MutableStateFlow

@ -14,9 +14,8 @@
* 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.MutableStateFlow
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
}
}
testFixtures.enable = true
}
dependencies {
@ -39,6 +40,10 @@ dependencies {
implementation(projects.core.common)
testImplementation(projects.core.datastoreTest)
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
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.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher

@ -14,13 +14,12 @@
* 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.DataStoreFactory
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.network.di.ApplicationScope
import dagger.Module
import dagger.Provides
@ -35,7 +34,7 @@ import javax.inject.Singleton
components = [SingletonComponent::class],
replaces = [DataStoreModule::class],
)
internal object TestDataStoreModule {
object TestDataStoreModule {
@Provides
@Singleton

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

@ -16,11 +16,11 @@
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.model.data.FollowableTopic
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 kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest

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

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

@ -16,9 +16,8 @@
@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
val newsResourcesTestData: List<NewsResource> = listOf(

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

@ -16,13 +16,8 @@
@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.LocalDateTime
import kotlinx.datetime.TimeZone

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

@ -14,10 +14,9 @@
* 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.notifications.Notifier
/**
* Aggregates news resources that have been notified for addition

@ -39,4 +39,7 @@ dependencies {
implementation(libs.kotlinx.datetime)
implementation(projects.core.common)
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)
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.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
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.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.model.data.userNewsResourcesTestData
import org.junit.Rule
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)
testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.core.model))
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.lifecycle.Lifecycle
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 kotlinx.coroutines.test.runTest
import org.junit.Rule

@ -17,9 +17,9 @@
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.testing.data.newsResourcesTestData
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.data.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.data.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.ui.NewsFeedUiState.Loading
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success

@ -34,7 +34,10 @@ dependencies {
testImplementation(libs.robolectric)
testImplementation(projects.core.testing)
testImplementation(projects.core.screenshotTesting)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.core.analytics))
testDemoImplementation(libs.roborazzi)
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.onNodeWithText
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.testing.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import org.junit.Rule
import org.junit.Test

@ -19,20 +19,20 @@ package com.google.samples.apps.nowinandroid.feature.foryou
import androidx.lifecycle.SavedStateHandle
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.TestAnalyticsHelper
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.model.data.FollowableTopic
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.UserNewsResource
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.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.feature.foryou.navigation.LINKED_NEWS_RESOURCE_ID
import kotlinx.coroutines.flow.collect

@ -28,6 +28,8 @@ dependencies {
implementation(projects.core.domain)
testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
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.onNodeWithContentDescription
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.InterestsUiState
import org.junit.Before

@ -17,11 +17,11 @@
package com.google.samples.apps.nowinandroid.interests
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.model.data.FollowableTopic
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.feature.interests.InterestsUiState
import com.google.samples.apps.nowinandroid.feature.interests.InterestsViewModel

@ -30,7 +30,10 @@ dependencies {
implementation(projects.core.ui)
testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
testImplementation(testFixtures(projects.core.model))
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.UserData
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.testing.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.model.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.ui.R.string
import org.junit.Before
import org.junit.Rule

@ -18,14 +18,14 @@ package com.google.samples.apps.nowinandroid.feature.search
import androidx.lifecycle.SavedStateHandle
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.GetSearchContentsUseCase
import com.google.samples.apps.nowinandroid.core.testing.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.testing.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.model.data.newsResourcesTestData
import com.google.samples.apps.nowinandroid.core.model.data.topicsTestData
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.SearchResultUiState.EmptyQuery

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

@ -16,9 +16,9 @@
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.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.feature.settings.SettingsUiState.Loading
import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success

@ -28,6 +28,8 @@ dependencies {
implementation(projects.core.data)
testImplementation(projects.core.testing)
testImplementation(testFixtures(projects.core.data))
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.onNodeWithText
import androidx.compose.ui.test.performScrollToNode
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.model.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.model.data.userNewsResourcesTestData
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ -18,12 +18,12 @@ package com.google.samples.apps.nowinandroid.feature.topic
import androidx.lifecycle.SavedStateHandle
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.NewsResource
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.feature.topic.navigation.TOPIC_ID_ARG
import kotlinx.coroutines.flow.collect

@ -40,5 +40,9 @@ kotlin.code.style=official
android.defaults.buildfeatures.resvalues=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
roborazzi.test.verify=true

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

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

@ -23,6 +23,7 @@ android {
defaultConfig {
testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
}
testFixtures.enable = true
namespace = "com.google.samples.apps.nowinandroid.sync"
}
@ -42,4 +43,8 @@ dependencies {
androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(libs.kotlinx.coroutines.guava)
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.testing.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertEquals
@HiltAndroidTest
@ -36,6 +38,10 @@ class SyncWorkerTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
private val context get() = InstrumentationRegistry.getInstrumentation().context
@Before

@ -14,10 +14,11 @@
* 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.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.SyncSubscriber
import dagger.Binds
@ -30,14 +31,10 @@ import dagger.hilt.testing.TestInstallIn
components = [SingletonComponent::class],
replaces = [SyncModule::class],
)
internal interface TestSyncModule {
interface TestSyncModule {
@Binds
fun bindsSyncStatusMonitor(
syncStatusMonitor: NeverSyncingSyncManager,
): SyncManager
fun bindsSyncManager(it: NeverSyncingSyncManager): SyncManager
@Binds
fun bindsSyncSubscriber(
syncSubscriber: StubSyncSubscriber,
): SyncSubscriber
fun bindsSyncSubscriber(it: StubSyncSubscriber): SyncSubscriber
}

@ -14,14 +14,14 @@
* 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject
internal class NeverSyncingSyncManager @Inject constructor() : SyncManager {
class NeverSyncingSyncManager @Inject constructor() : SyncManager {
override val isSyncing: Flow<Boolean> = flowOf(false)
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