Skip to content

Commit b90aca4

Browse files
authored
Save and Exit: Unit tests (#6871)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1207418217763355/task/1211418992991768?focus=true ### Description This PR adds unit tests for the new functionality. ### Steps to test this PR QA-optional
1 parent 98cd8ea commit b90aca4

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import app.cash.turbine.test
2020
import com.duckduckgo.app.statistics.pixels.Pixel
2121
import com.duckduckgo.common.test.CoroutineTestRule
2222
import com.duckduckgo.duckchat.impl.DuckChatInternal
23+
import com.duckduckgo.duckchat.impl.R
2324
import com.duckduckgo.duckchat.impl.inputscreen.ui.metrics.discovery.InputScreenDiscoveryFunnel
2425
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName
2526
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.LaunchFeedback
@@ -239,6 +240,27 @@ class DuckChatSettingsViewModelTest {
239240
verify(mockPixel).fire(DuckChatPixelName.DUCK_CHAT_SEARCH_ASSIST_SETTINGS_BUTTON_CLICKED)
240241
}
241242

243+
@Test
244+
fun whenDuckChatSearchAISettingsClickedAndSaveAndExitEnabledThenOpenSettingsLinkWithReturnParamEmitted() =
245+
runTest {
246+
@Suppress("DenyListedApi")
247+
settingsPageFeature.saveAndExitSerpSettings().setRawStoredState(State(enable = true))
248+
249+
testee.duckChatSearchAISettingsClicked()
250+
251+
testee.commands.test {
252+
val command = awaitItem()
253+
assertTrue(command is OpenLink)
254+
command as OpenLink
255+
assertEquals(
256+
DuckChatSettingsViewModel.DUCK_CHAT_SEARCH_AI_SETTINGS_LINK_WITH_RETURN_PARAM,
257+
command.link,
258+
)
259+
assertEquals(R.string.duck_chat_search_assist_settings_title, command.titleRes)
260+
cancelAndIgnoreRemainingEvents()
261+
}
262+
}
263+
242264
@Test
243265
fun `when onDuckChatUserEnabledToggled true then enabled pixel fired`() =
244266
runTest {

settings/settings-impl/build.gradle

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ android {
2727
anvil {
2828
generateDaggerFactories = true // default is false
2929
}
30+
testOptions {
31+
unitTests {
32+
includeAndroidResources = true
33+
}
34+
}
3035
}
3136

3237

@@ -54,4 +59,16 @@ dependencies {
5459

5560
// Dagger
5661
implementation Google.dagger
62+
63+
testImplementation Testing.junit4
64+
testImplementation "org.mockito.kotlin:mockito-kotlin:_"
65+
testImplementation project(path: ':common-test')
66+
testImplementation CashApp.turbine
67+
testImplementation(KotlinX.coroutines.test) {
68+
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
69+
}
70+
testImplementation "androidx.test.ext:junit-ktx:_"
71+
testImplementation AndroidX.lifecycle.runtime.testing
72+
testImplementation AndroidX.archCore.testing
73+
testImplementation Testing.robolectric
5774
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.settings.impl
18+
19+
import androidx.test.ext.junit.runners.AndroidJUnit4
20+
import app.cash.turbine.test
21+
import com.duckduckgo.common.test.CoroutineTestRule
22+
import com.duckduckgo.settings.api.SettingsConstants.ID_AI_FEATURES
23+
import com.duckduckgo.settings.api.SettingsConstants.ID_PRIVATE_SEARCH
24+
import com.duckduckgo.settings.impl.SettingsWebViewViewModel.Command
25+
import com.duckduckgo.settings.impl.messaging.SettingsContentScopeJsMessageHandler.Companion.FEATURE_SERP_SETTINGS
26+
import com.duckduckgo.settings.impl.messaging.SettingsContentScopeJsMessageHandler.Companion.METHOD_OPEN_NATIVE_SETTINGS
27+
import com.duckduckgo.settings.impl.messaging.SettingsContentScopeJsMessageHandler.Companion.PARAM_RETURN
28+
import kotlinx.coroutines.ExperimentalCoroutinesApi
29+
import kotlinx.coroutines.test.advanceUntilIdle
30+
import kotlinx.coroutines.test.runTest
31+
import org.json.JSONObject
32+
import org.junit.Assert.assertEquals
33+
import org.junit.Assert.assertTrue
34+
import org.junit.Before
35+
import org.junit.Rule
36+
import org.junit.Test
37+
import org.junit.runner.RunWith
38+
39+
@OptIn(ExperimentalCoroutinesApi::class)
40+
@RunWith(AndroidJUnit4::class)
41+
class SettingsWebViewViewModelTest {
42+
43+
@get:Rule
44+
val coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
45+
46+
private lateinit var viewModel: SettingsWebViewViewModel
47+
48+
@Before
49+
fun setup() {
50+
viewModel = SettingsWebViewViewModel(coroutineTestRule.testDispatcherProvider)
51+
}
52+
53+
@Test
54+
fun whenOnStartWithUrlThenLoadUrlCommandEmitted() = runTest {
55+
val testUrl = "https://example.com/settings"
56+
viewModel.commands.test {
57+
viewModel.onStart(testUrl)
58+
59+
val command = awaitItem()
60+
assertTrue(command is Command.LoadUrl)
61+
assertEquals(testUrl, (command as Command.LoadUrl).url)
62+
}
63+
}
64+
65+
@Test
66+
fun whenOnStartWithNullUrlThenExitCommandEmitted() = runTest {
67+
viewModel.commands.test {
68+
viewModel.onStart(null)
69+
70+
val command = awaitItem()
71+
assertTrue(command is Command.Exit)
72+
}
73+
}
74+
75+
@Test
76+
fun whenProcessOpenNativeSettingsAiFeaturesReturnThenExitCommandEmitted() = runTest {
77+
viewModel.commands.test {
78+
val data = JSONObject().put(PARAM_RETURN, ID_AI_FEATURES)
79+
viewModel.processJsCallbackMessage(
80+
featureName = FEATURE_SERP_SETTINGS,
81+
method = METHOD_OPEN_NATIVE_SETTINGS,
82+
id = null,
83+
data = data,
84+
)
85+
86+
val command = awaitItem()
87+
assertTrue(command is Command.Exit)
88+
}
89+
}
90+
91+
@Test
92+
fun whenProcessOpenNativeSettingsPrivateSearchReturnThenExitCommandEmitted() = runTest {
93+
viewModel.commands.test {
94+
val data = JSONObject().put(PARAM_RETURN, ID_PRIVATE_SEARCH)
95+
viewModel.processJsCallbackMessage(
96+
featureName = FEATURE_SERP_SETTINGS,
97+
method = METHOD_OPEN_NATIVE_SETTINGS,
98+
id = null,
99+
data = data,
100+
)
101+
102+
val command = awaitItem()
103+
assertTrue(command is Command.Exit)
104+
}
105+
}
106+
107+
@Test
108+
fun whenProcessOpenNativeSettingsUnknownReturnThenNoCommandEmitted() = runTest {
109+
viewModel.commands.test {
110+
val data = JSONObject().put(PARAM_RETURN, "unknownSection")
111+
viewModel.processJsCallbackMessage(
112+
featureName = FEATURE_SERP_SETTINGS,
113+
method = METHOD_OPEN_NATIVE_SETTINGS,
114+
id = null,
115+
data = data,
116+
)
117+
// Advance to run launched coroutines
118+
advanceUntilIdle()
119+
expectNoEvents()
120+
}
121+
}
122+
123+
@Test
124+
fun whenProcessDifferentFeatureNameThenNoCommandEmitted() = runTest {
125+
viewModel.commands.test {
126+
val data = JSONObject().put(PARAM_RETURN, ID_AI_FEATURES)
127+
viewModel.processJsCallbackMessage(
128+
featureName = "otherFeature",
129+
method = METHOD_OPEN_NATIVE_SETTINGS,
130+
id = null,
131+
data = data,
132+
)
133+
advanceUntilIdle()
134+
expectNoEvents()
135+
}
136+
}
137+
138+
@Test
139+
fun whenProcessDifferentMethodThenNoCommandEmitted() = runTest {
140+
viewModel.commands.test {
141+
val data = JSONObject().put(PARAM_RETURN, ID_AI_FEATURES)
142+
viewModel.processJsCallbackMessage(
143+
featureName = FEATURE_SERP_SETTINGS,
144+
method = "someOtherMethod",
145+
id = null,
146+
data = data,
147+
)
148+
advanceUntilIdle()
149+
expectNoEvents()
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)