From ed1ffc1cbcc3da95fb674a1857f916f04b2ff6e0 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Thu, 6 Nov 2025 18:23:11 +0000 Subject: [PATCH 1/5] Add pixels serp_settings_open_hide-ai-generated-images is going to replace an existing pixel name serp_settings_open_duck-ai is new --- PixelDefinitions/pixels/serp_settings.json5 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 PixelDefinitions/pixels/serp_settings.json5 diff --git a/PixelDefinitions/pixels/serp_settings.json5 b/PixelDefinitions/pixels/serp_settings.json5 new file mode 100644 index 000000000000..d6d28b95d26a --- /dev/null +++ b/PixelDefinitions/pixels/serp_settings.json5 @@ -0,0 +1,19 @@ +// See https://app.asana.com/1/137249556945/project/1207908166761516/task/1211846254913290 +{ + "serp_settings_open_hide-ai-generated-images": { + // See DuckChatSettingsViewModel.kt for usage. + "description": "When the `Hide Ai Generated` option is opened and highlighted in SERP settings.", + "owners": ["mikescamell"], + "triggers": ["other"], + "suffixes": ["form_factor"], + "parameters": ["appVersion"] + }, + "serp_settings_open_duck-ai": { + // See OpenNativeSettingsHandler.kt for usage. + "description": "When a user clicks the 'Open Duck.AI button' in the SERP settings menu which takes them to AI Features settings page.", + "owners": ["mikescamell"], + "triggers": ["other"], + "suffixes": ["form_factor"], + "parameters": ["appVersion"] + } +} From cd6577c76c1ee1d3d8c0946d04bee1517ead8391 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Thu, 6 Nov 2025 18:25:20 +0000 Subject: [PATCH 2/5] SERP Settings Sync: Implement pixel tracking for Open Duck AI settings Add our own plugin so we're not using app's PixelInterceptor --- .../messaging/OpenNativeSettingsHandler.kt | 4 ++ .../serpsettings/pixel/SerpSettingsPixels.kt | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/pixel/SerpSettingsPixels.kt diff --git a/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandler.kt b/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandler.kt index 591bc5335546..e14048fc9d0b 100644 --- a/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandler.kt +++ b/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandler.kt @@ -19,6 +19,7 @@ package com.duckduckgo.settings.impl.serpsettings.messaging import android.content.Context import android.content.Intent import com.duckduckgo.app.di.AppCoroutineScope +import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.common.utils.AppUrl import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.contentscopescripts.api.ContentScopeJsMessageHandlersPlugin @@ -30,6 +31,7 @@ import com.duckduckgo.js.messaging.api.JsMessageHandler import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.settings.api.SettingsPageFeature +import com.duckduckgo.settings.impl.serpsettings.pixel.SerpSettingsPixelName.SERP_SETTINGS_OPEN_DUCK_AI import com.squareup.anvil.annotations.ContributesMultibinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -47,6 +49,7 @@ class OpenNativeSettingsHandler @Inject constructor( private val context: Context, private val globalActivityStarter: GlobalActivityStarter, private val settingsPageFeature: SettingsPageFeature, + private val pixel: Pixel, ) : ContentScopeJsMessageHandlersPlugin { override fun getJsMessageHandler(): JsMessageHandler = @@ -63,6 +66,7 @@ class OpenNativeSettingsHandler @Inject constructor( when (val screenParam = params.optString("screen", "")) { AI_FEATURES_SCREEN_NAME -> { + pixel.fire(SERP_SETTINGS_OPEN_DUCK_AI) val intent = globalActivityStarter.startIntent(context, DuckChatNativeSettingsNoParams) intent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) diff --git a/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/pixel/SerpSettingsPixels.kt b/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/pixel/SerpSettingsPixels.kt new file mode 100644 index 000000000000..0b1f69a43772 --- /dev/null +++ b/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/serpsettings/pixel/SerpSettingsPixels.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * 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. + */ + +package com.duckduckgo.settings.impl.serpsettings.pixel + +import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin +import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin.PixelParameter +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.settings.impl.serpsettings.pixel.SerpSettingsPixelName.SERP_SETTINGS_OPEN_DUCK_AI +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +enum class SerpSettingsPixelName(override val pixelName: String) : Pixel.PixelName { + SERP_SETTINGS_OPEN_DUCK_AI("serp_settings_open_duck-ai"), +} + +@ContributesMultibinding(AppScope::class) +class SerpSettingsPixelParamRemovalPlugin @Inject constructor() : PixelParamRemovalPlugin { + override fun names(): List>> { + return listOf( + SERP_SETTINGS_OPEN_DUCK_AI.pixelName to PixelParameter.removeAtb(), + ) + } +} From 9149a4b5e416aa9dfb02b92c382eae16e0a0e712 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Thu, 6 Nov 2025 18:56:16 +0000 Subject: [PATCH 3/5] Add and update tests for pixel and screen launching --- .../fakes/FakeGlobalActivityStarter.kt | 58 ++++++++ .../impl/serpsettings/fakes/FakePixel.kt | 57 ++++++++ .../OpenNativeSettingsHandlerTest.kt | 124 ++++++++++++++++-- 3 files changed, 225 insertions(+), 14 deletions(-) create mode 100644 settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakeGlobalActivityStarter.kt create mode 100644 settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakePixel.kt diff --git a/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakeGlobalActivityStarter.kt b/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakeGlobalActivityStarter.kt new file mode 100644 index 000000000000..e043caf07fe8 --- /dev/null +++ b/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakeGlobalActivityStarter.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * 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. + */ + +package com.duckduckgo.settings.impl.serpsettings.fakes + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams + +class FakeGlobalActivityStarter : GlobalActivityStarter { + val startedActivities = mutableListOf() + val startedDeeplinkActivities = mutableListOf() + var intentToReturn: Intent? = null + + override fun start( + context: Context, + params: ActivityParams, + options: Bundle?, + ) { + startedActivities.add(params) + } + + override fun start( + context: Context, + deeplinkActivityParams: GlobalActivityStarter.DeeplinkActivityParams, + options: Bundle?, + ) { + startedDeeplinkActivities.add(deeplinkActivityParams) + } + + override fun startIntent(context: Context, params: ActivityParams): Intent? { + startedActivities.add(params) + return intentToReturn + } + + override fun startIntent( + context: Context, + deeplinkActivityParams: GlobalActivityStarter.DeeplinkActivityParams, + ): Intent? { + startedDeeplinkActivities.add(deeplinkActivityParams) + return intentToReturn + } +} diff --git a/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakePixel.kt b/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakePixel.kt new file mode 100644 index 000000000000..1d8ca01e7419 --- /dev/null +++ b/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/fakes/FakePixel.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * 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. + */ + +package com.duckduckgo.settings.impl.serpsettings.fakes + +import com.duckduckgo.app.statistics.pixels.Pixel + +class FakePixel : Pixel { + val firedPixels = mutableListOf() + + override fun fire( + pixel: Pixel.PixelName, + parameters: Map, + encodedParameters: Map, + type: Pixel.PixelType, + ) { + firedPixels.add(pixel.pixelName) + } + + override fun fire( + pixelName: String, + parameters: Map, + encodedParameters: Map, + type: Pixel.PixelType, + ) { + firedPixels.add(pixelName) + } + + override fun enqueueFire( + pixel: Pixel.PixelName, + parameters: Map, + encodedParameters: Map, + ) { + firedPixels.add(pixel.pixelName) + } + + override fun enqueueFire( + pixelName: String, + parameters: Map, + encodedParameters: Map, + ) { + firedPixels.add(pixelName) + } +} diff --git a/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandlerTest.kt b/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandlerTest.kt index a9cd5c24f343..c46121b367f7 100644 --- a/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandlerTest.kt +++ b/settings/settings-impl/src/test/java/com/duckduckgo/settings/impl/serpsettings/messaging/OpenNativeSettingsHandlerTest.kt @@ -16,44 +16,140 @@ package com.duckduckgo.settings.impl.serpsettings.messaging -import android.content.Context +import android.annotation.SuppressLint +import android.content.Intent +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.duckduckgo.common.test.CoroutineTestRule -import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.duckchat.api.DuckChatNativeSettingsNoParams +import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory +import com.duckduckgo.feature.toggles.api.Toggle +import com.duckduckgo.js.messaging.api.JsMessage import com.duckduckgo.settings.api.SettingsPageFeature -import org.junit.Assert.* +import com.duckduckgo.settings.impl.serpsettings.fakes.FakeGlobalActivityStarter +import com.duckduckgo.settings.impl.serpsettings.fakes.FakeJsMessaging +import com.duckduckgo.settings.impl.serpsettings.fakes.FakePixel +import com.duckduckgo.settings.impl.serpsettings.pixel.SerpSettingsPixelName.SERP_SETTINGS_OPEN_DUCK_AI +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.kotlin.mock +import org.junit.runner.RunWith +@SuppressLint("DenyListedApi") +@RunWith(AndroidJUnit4::class) class OpenNativeSettingsHandlerTest { @get:Rule val coroutineTestRule: CoroutineTestRule = CoroutineTestRule() - private val handler = OpenNativeSettingsHandler( - dispatcherProvider = coroutineTestRule.testDispatcherProvider, - appScope = coroutineTestRule.testScope, - context = mock(), - globalActivityStarter = mock(), - settingsPageFeature = mock(), - ).getJsMessageHandler() + private val fakeSettingsPageFeature: SettingsPageFeature = + FakeFeatureToggleFactory.create(SettingsPageFeature::class.java) + private lateinit var fakePixel: FakePixel + private lateinit var fakeJsMessaging: FakeJsMessaging + private lateinit var fakeGlobalActivityStarter: FakeGlobalActivityStarter + private lateinit var handler: OpenNativeSettingsHandler + + @Before + fun setUp() { + fakePixel = FakePixel() + fakeJsMessaging = FakeJsMessaging() + fakeGlobalActivityStarter = FakeGlobalActivityStarter() + + handler = OpenNativeSettingsHandler( + dispatcherProvider = coroutineTestRule.testDispatcherProvider, + appScope = coroutineTestRule.testScope, + context = ApplicationProvider.getApplicationContext(), + globalActivityStarter = fakeGlobalActivityStarter, + settingsPageFeature = fakeSettingsPageFeature, + pixel = fakePixel, + ) + } @Test fun `only allow duckduckgo dot com domains`() { - val domains = handler.allowedDomains + val domains = handler.getJsMessageHandler().allowedDomains assertEquals(1, domains.size) assertEquals("duckduckgo.com", domains.first()) } @Test fun `feature name is serpSettings`() { - assertEquals("serpSettings", handler.featureName) + assertEquals("serpSettings", handler.getJsMessageHandler().featureName) } @Test fun `only contains openNativeSettings method`() { - val methods = handler.methods + val methods = handler.getJsMessageHandler().methods assertEquals(1, methods.size) assertEquals("openNativeSettings", methods[0]) } + + @Test + fun `when openNativeSettings called with aiFeatures screen then pixel is fired`() = runTest { + fakeSettingsPageFeature.serpSettingsSync().setRawStoredState(Toggle.State(enable = true)) + val jsMessage = createJsMessage(screenParam = "aiFeatures") + fakeGlobalActivityStarter.intentToReturn = Intent() + + handler.getJsMessageHandler().process( + jsMessage = jsMessage, + jsMessaging = fakeJsMessaging, + jsMessageCallback = null, + ) + + advanceUntilIdle() + + assertEquals(1, fakePixel.firedPixels.size) + assertEquals(SERP_SETTINGS_OPEN_DUCK_AI.pixelName, fakePixel.firedPixels.first()) + } + + @Test + fun `when openNativeSettings called with aiFeatures screen then launches DuckChat settings`() = runTest { + fakeSettingsPageFeature.serpSettingsSync().setRawStoredState(Toggle.State(enable = true)) + val jsMessage = createJsMessage(screenParam = "aiFeatures") + fakeGlobalActivityStarter.intentToReturn = Intent() + + handler.getJsMessageHandler().process( + jsMessage = jsMessage, + jsMessaging = fakeJsMessaging, + jsMessageCallback = null, + ) + + advanceUntilIdle() + + assertEquals(1, fakeGlobalActivityStarter.startedActivities.size) + assertEquals(DuckChatNativeSettingsNoParams, fakeGlobalActivityStarter.startedActivities.first()) + } + + @Test + fun `when feature flag is disabled then no pixel is fired and no activity is launched`() = runTest { + fakeSettingsPageFeature.serpSettingsSync().setRawStoredState(Toggle.State(enable = false)) + val jsMessage = createJsMessage(screenParam = "aiFeatures") + + handler.getJsMessageHandler().process( + jsMessage = jsMessage, + jsMessaging = fakeJsMessaging, + jsMessageCallback = null, + ) + + advanceUntilIdle() + + assertEquals(0, fakePixel.firedPixels.size) + assertEquals(0, fakeGlobalActivityStarter.startedActivities.size) + } + + private fun createJsMessage(screenParam: String): JsMessage { + return JsMessage( + context = "test", + featureName = "serpSettings", + method = "openNativeSettings", + id = "123", + params = JSONObject().apply { + put("screen", screenParam) + }, + ) + } } From 702694252883ac8f5e7e85ca6a76862aa67f3a80 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Thu, 6 Nov 2025 19:13:10 +0000 Subject: [PATCH 4/5] Update pixel tracking for hiding AI generated images --- .../com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt | 6 +++--- .../duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt index 1ef602dd486a..fc709c2c6868 100644 --- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt +++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt @@ -64,7 +64,6 @@ import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENT import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_SHOWN_COUNT import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_SHOWN_DAILY import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_TEXT_AREA_FOCUSED -import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_IS_ENABLED_DAILY import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_KEYBOARD_RETURN_PRESSED import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_MENU_SETTING_OFF @@ -92,6 +91,7 @@ import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_START_NEW_ import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_USER_DISABLED import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_USER_ENABLED +import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES import com.duckduckgo.duckchat.impl.repository.DuckChatFeatureRepository import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesMultibinding @@ -154,7 +154,7 @@ enum class DuckChatPixelName(override val pixelName: String) : Pixel.PixelName { DUCK_CHAT_ADDRESS_BAR_IS_ENABLED_DAILY("aichat_address_bar_is_enabled_daily"), DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_IS_ENABLED_DAILY("aichat_experimental_address_bar_is_enabled_daily"), DUCK_CHAT_SEARCH_ASSIST_SETTINGS_BUTTON_CLICKED("aichat_search_assist_settings_button_clicked"), - DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED("aichat_hide_ai_generated_images_button_clicked"), + SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES("serp_settings_open_hide-ai-generated-images"), DUCK_CHAT_START_NEW_CONVERSATION("aichat_start_new_conversation"), DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED("aichat_start_new_conversation_button_clicked"), DUCK_CHAT_OPEN_HISTORY("aichat_open_history"), @@ -233,7 +233,7 @@ class DuckChatParamRemovalPlugin @Inject constructor() : PixelParamRemovalPlugin DUCK_CHAT_ADDRESS_BAR_IS_ENABLED_DAILY.pixelName to PixelParameter.removeAtb(), DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_IS_ENABLED_DAILY.pixelName to PixelParameter.removeAtb(), DUCK_CHAT_SEARCH_ASSIST_SETTINGS_BUTTON_CLICKED.pixelName to PixelParameter.removeAtb(), - DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED.pixelName to PixelParameter.removeAtb(), + SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES.pixelName to PixelParameter.removeAtb(), DUCK_CHAT_START_NEW_CONVERSATION.pixelName to PixelParameter.removeAtb(), DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED.pixelName to PixelParameter.removeAtb(), DUCK_CHAT_OPEN_HISTORY.pixelName to PixelParameter.removeAtb(), diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt index c3ea5ea23601..86be6aaa43aa 100644 --- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt +++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt @@ -168,7 +168,7 @@ class DuckChatSettingsViewModel @AssistedInject constructor( titleRes = R.string.duckAiSerpSettingsTitle, ), ) - pixel.fire(DuckChatPixelName.DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED) + pixel.fire(DuckChatPixelName.SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES) } } From 824bbce513a67295bc5bc74fef23d42b17dccec3 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Thu, 6 Nov 2025 19:13:17 +0000 Subject: [PATCH 5/5] Add test --- .../impl/ui/settings/DuckChatSettingsViewModelTest.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt index 98f6aa0288fa..487f91f6b52e 100644 --- a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt +++ b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt @@ -550,4 +550,11 @@ class DuckChatSettingsViewModelTest { assertFalse(state.isFullScreenModeEnabled) } } + + @Test + fun `when onDuckAiHideAiGeneratedImagesClicked then pixel is fired`() = + runTest { + testee.onDuckAiHideAiGeneratedImagesClicked() + verify(mockPixel).fire(DuckChatPixelName.SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES) + } }