Merge remote-tracking branch 'upstream/main'

pull/2064/head
lihenggui 1 year ago
commit d5f9b10f99

@ -43,9 +43,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
validate-wrappers: true
gradle-home-cache-cleanup: true
- name: Setup Android SDK
uses: android-actions/setup-android@v3

@ -18,8 +18,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
ls /dev/kvm
- name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
@ -30,6 +34,18 @@ jobs:
distribution: 'zulu'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Accept licenses
run: yes | sdkmanager --licenses || true
- name: Check build-logic
run: ./gradlew check -p build-logic
- name: Setup GMD
run: ./gradlew :benchmarks:pixel6Api33Setup
--info

@ -33,9 +33,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
validate-wrappers: true
gradle-home-cache-cleanup: true
- name: Setup Android SDK
uses: android-actions/setup-android@v3

@ -1,2 +1,2 @@
# This file can be used to trigger an internal build by changing the number below
3
2

@ -48,7 +48,7 @@ android {
release {
isMinifyEnabled = true
applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
// To publish on the Play store a private signing key is required, but to allow anyone
// who clones the code to sign and run the release variant, use the debug signing key.

@ -1,9 +0,0 @@
# Fix for Retrofit issue https://github.com/square/retrofit/issues/3751
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation

@ -77,7 +77,7 @@ class MainActivity : ComponentActivity(), ApplicationComponentProvider {
@Inject
lateinit var userNewsResourceRepository: UserNewsResourceRepository
val viewModel: MainActivityViewModel by viewModels()
private val viewModel: MainActivityViewModel by viewModels()
override val component by lazy(LazyThreadSafetyMode.NONE) {
ApplicationComponent::class.create(applicationContext)

@ -61,20 +61,18 @@ class BookmarksViewModelTest {
@Test
fun oneBookmark_showsInFeed() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() }
newsRepository.sendNewsResources(newsResourcesTestData)
userDataRepository.setNewsResourceBookmarked(newsResourcesTestData[0].id, true)
val item = viewModel.feedUiState.value
assertIs<Success>(item)
assertEquals(item.feed.size, 1)
collectJob.cancel()
}
@Test
fun oneBookmark_whenRemoving_removesFromFeed() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() }
// Set the news resources to be used by this test
newsRepository.sendNewsResources(newsResourcesTestData)
// Start with the resource saved
@ -85,7 +83,5 @@ class BookmarksViewModelTest {
val item = viewModel.feedUiState.value
assertIs<Success>(item)
assertEquals(item.feed.size, 0)
collectJob.cancel()
}
}

@ -98,9 +98,8 @@ class ForYouViewModelTest {
@Test
fun stateIsLoadingWhenFollowedTopicsAreLoading() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
@ -109,31 +108,24 @@ class ForYouViewModelTest {
viewModel.onboardingUiState.value,
)
assertEquals(NewsFeedUiState.Loading, viewModel.feedState.value)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun stateIsLoadingWhenAppIsSyncingWithNoInterests() = runTest {
syncManager.setSyncing(true)
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.isSyncing.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.isSyncing.collect() }
assertEquals(
true,
viewModel.isSyncing.value,
)
collectJob.cancel()
}
@Test
fun onboardingStateIsLoadingWhenTopicsAreLoading() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
userDataRepository.setFollowedTopicIds(emptySet())
@ -142,16 +134,12 @@ class ForYouViewModelTest {
viewModel.onboardingUiState.value,
)
assertEquals(NewsFeedUiState.Success(emptyList()), viewModel.feedState.value)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun onboardingIsShownWhenNewsResourcesAreLoading() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
@ -202,16 +190,12 @@ class ForYouViewModelTest {
),
viewModel.feedState.value,
)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun onboardingIsShownAfterLoadingEmptyFollowedTopics() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
@ -263,16 +247,12 @@ class ForYouViewModelTest {
),
viewModel.feedState.value,
)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun onboardingIsNotShownAfterUserDismissesOnboarding() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
@ -299,16 +279,12 @@ class ForYouViewModelTest {
),
viewModel.feedState.value,
)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun topicSelectionUpdatesAfterSelectingTopic() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
@ -352,16 +328,12 @@ class ForYouViewModelTest {
),
viewModel.feedState.value,
)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun topicSelectionUpdatesAfterUnselectingTopic() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
@ -416,16 +388,12 @@ class ForYouViewModelTest {
),
viewModel.feedState.value,
)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun newsResourceSelectionUpdatesAfterLoadingFollowedTopics() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
val followedTopicIds = setOf("1")
val userData = emptyUserData.copy(
@ -460,15 +428,11 @@ class ForYouViewModelTest {
),
viewModel.feedState.value,
)
collectJob1.cancel()
collectJob2.cancel()
}
@Test
fun deepLinkedNewsResourceIsFetchedAndResetAfterViewing() = runTest {
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.deepLinkedNewsResource.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.deepLinkedNewsResource.collect() }
newsRepository.sendNewsResources(sampleNewsResources)
userDataRepository.setUserData(emptyUserData)
@ -503,8 +467,6 @@ class ForYouViewModelTest {
),
),
)
collectJob.cancel()
}
@Test

@ -80,17 +80,15 @@ class InterestsViewModelTest {
@Test
fun uiState_whenFollowedTopicsAreLoading_thenShowLoading() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
userDataRepository.setFollowedTopicIds(emptySet())
assertEquals(InterestsUiState.Loading, viewModel.uiState.value)
collectJob.cancel()
}
@Test
fun uiState_whenFollowingNewTopic_thenShowUpdatedTopics() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
val toggleTopicId = testOutputTopics[1].topic.id
topicsRepository.sendTopics(testInputTopics.map { it.topic })
@ -114,13 +112,11 @@ class InterestsViewModelTest {
),
viewModel.uiState.value,
)
collectJob.cancel()
}
@Test
fun uiState_whenUnfollowingTopics_thenShowUpdatedTopics() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
val toggleTopicId = testOutputTopics[1].topic.id
@ -147,8 +143,6 @@ class InterestsViewModelTest {
),
viewModel.uiState.value,
)
collectJob.cancel()
}
}

@ -85,20 +85,16 @@ class SearchViewModelTest {
fun stateIsEmptyQuery_withEmptySearchQuery() = runTest {
searchContentsRepository.addNewsResources(newsResourcesTestData)
searchContentsRepository.addTopics(topicsTestData)
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() }
viewModel.onSearchQueryChanged("")
assertEquals(EmptyQuery, viewModel.searchResultUiState.value)
collectJob.cancel()
}
@Test
fun emptyResultIsReturned_withNotMatchingQuery() = runTest {
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() }
viewModel.onSearchQueryChanged("XXX")
searchContentsRepository.addNewsResources(newsResourcesTestData)
@ -106,32 +102,24 @@ class SearchViewModelTest {
val result = viewModel.searchResultUiState.value
assertIs<SearchResultUiState.Success>(result)
collectJob.cancel()
}
@Test
fun recentSearches_verifyUiStateIsSuccess() = runTest {
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.recentSearchQueriesUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.recentSearchQueriesUiState.collect() }
viewModel.onSearchTriggered("kotlin")
val result = viewModel.recentSearchQueriesUiState.value
assertIs<Success>(result)
collectJob.cancel()
}
@Test
fun searchNotReady_withNoFtsTableEntity() = runTest {
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() }
viewModel.onSearchQueryChanged("")
assertEquals(SearchNotReady, viewModel.searchResultUiState.value)
collectJob.cancel()
}
@Test

@ -49,8 +49,7 @@ class SettingsViewModelTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun stateIsSuccessAfterUserDataLoaded() = runTest {
val collectJob =
launch(UnconfinedTestDispatcher()) { viewModel.settingsUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.settingsUiState.collect() }
userDataRepository.setThemeBrand(ANDROID)
userDataRepository.setDarkThemeConfig(DARK)
@ -65,7 +64,5 @@ class SettingsViewModelTest {
),
viewModel.settingsUiState.value,
)
collectJob.cancel()
}
}

@ -85,7 +85,7 @@ class TopicViewModelTest {
@Test
fun uiStateTopic_whenSuccess_matchesTopicFromRepository() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
topicsRepository.sendTopics(testInputTopics.map(FollowableTopic::topic))
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
@ -97,8 +97,6 @@ class TopicViewModelTest {
).first()
assertEquals(topicFromRepository, item.followableTopic.topic)
collectJob.cancel()
}
@Test
@ -113,18 +111,16 @@ class TopicViewModelTest {
@Test
fun uiStateTopic_whenFollowedIdsSuccessAndTopicLoading_thenShowLoading() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
assertEquals(TopicUiState.Loading, viewModel.topicUiState.value)
collectJob.cancel()
}
@Test
fun uiStateTopic_whenFollowedIdsSuccessAndTopicSuccess_thenTopicSuccessAndNewsLoading() =
runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
topicsRepository.sendTopics(testInputTopics.map { it.topic })
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
@ -133,14 +129,12 @@ class TopicViewModelTest {
assertIs<TopicUiState.Success>(topicUiState)
assertIs<NewsUiState.Loading>(newsUiState)
collectJob.cancel()
}
@Test
fun uiStateTopic_whenFollowedIdsSuccessAndTopicSuccessAndNewsIsSuccess_thenAllSuccess() =
runTest {
val collectJob = launch(UnconfinedTestDispatcher()) {
backgroundScope.launch(UnconfinedTestDispatcher()) {
combine(
viewModel.topicUiState,
viewModel.newsUiState,
@ -155,13 +149,11 @@ class TopicViewModelTest {
assertIs<TopicUiState.Success>(topicUiState)
assertIs<NewsUiState.Success>(newsUiState)
collectJob.cancel()
}
@Test
fun uiStateTopic_whenFollowingTopic_thenShowUpdatedTopic() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() }
topicsRepository.sendTopics(testInputTopics.map { it.topic })
// Set which topic IDs are followed, not including 0.
@ -173,8 +165,6 @@ class TopicViewModelTest {
TopicUiState.Success(followableTopic = testOutputTopics[0]),
viewModel.topicUiState.value,
)
collectJob.cancel()
}
}

@ -8,7 +8,23 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Ensure important default jvmargs aren't overwritten. See https://github.com/gradle/gradle/issues/19750
org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g
#
# For more information about how Gradle memory options were chosen:
# - Metaspace See https://www.jasonpearson.dev/metaspace-in-jvm-builds/
# - SoftRefLRUPolicyMSPerMB would default to 1000 which with a 4gb heap translates to ~51 minutes.
# A value of 1 means ~4 seconds before SoftRefs can be collected, which means its realistic to
# collect them as needed during a build that should take seconds to minutes.
# - CodeCache normally defaults to a very small size. Increasing it from platform defaults of 32-48m
# because of how many classes can be loaded into memory and then cached as native compiled code
# for a small speed boost.
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g
# For more information about how Kotlin Daemon memory options were chosen:
# - Kotlin JVM args only inherit Xmx, ReservedCodeCache, and MaxMetaspace. Since we are specifying
# other args we need to specify all of them here.
# - We're using the Kotlin Gradle Plugin's default value for ReservedCodeCacheSize, if we do not then
# the Gradle JVM arg value for ReservedCodeCacheSize will be used.
kotlin.daemon.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=320m -XX:+HeapDumpOnOutOfMemoryError -Xmx4g -Xms4g
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit

@ -158,7 +158,7 @@ okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor",
protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" }
protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-kotlin-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerializationJson" }
retrofit-kotlin-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }

Loading…
Cancel
Save