Skip to content

Commit 9bef16b

Browse files
Drip Notifications Experiment (#786)
1 parent 03c57e9 commit 9bef16b

18 files changed

+986
-68
lines changed

app/src/androidTest/java/com/duckduckgo/app/notification/AndroidNotificationSchedulerTest.kt

Lines changed: 320 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2020 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+
@file:Suppress("RemoveExplicitTypeArguments")
18+
19+
package com.duckduckgo.app.notification.model
20+
21+
import androidx.test.platform.app.InstrumentationRegistry
22+
import com.duckduckgo.app.notification.db.NotificationDao
23+
import com.nhaarman.mockitokotlin2.any
24+
import com.nhaarman.mockitokotlin2.mock
25+
import com.nhaarman.mockitokotlin2.whenever
26+
import kotlinx.coroutines.runBlocking
27+
import org.junit.Assert.assertEquals
28+
import org.junit.Assert.assertFalse
29+
import org.junit.Assert.assertTrue
30+
import org.junit.Before
31+
import org.junit.Test
32+
33+
class AppFeatureNotificationTest {
34+
35+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
36+
private val notificationsDao: NotificationDao = mock()
37+
private lateinit var testee: AppFeatureNotification
38+
39+
@Before
40+
fun before() {
41+
testee = AppFeatureNotification(context, notificationsDao, TITLE, DESCRIPTION, PIXEL)
42+
}
43+
44+
@Test
45+
fun whenNotificationNotSeenThenCanShowIsTrue() = runBlocking<Unit> {
46+
whenever(notificationsDao.exists(any())).thenReturn(false)
47+
assertTrue(testee.canShow())
48+
}
49+
50+
@Test
51+
fun whenNotificationAlreadySeenThenCanShowIsFalse() = runBlocking<Unit> {
52+
whenever(notificationsDao.exists(any())).thenReturn(true)
53+
assertFalse(testee.canShow())
54+
}
55+
56+
@Test
57+
fun whenBuildSpecificationSetCorrectPixelSuffix() = runBlocking<Unit> {
58+
val spec = testee.buildSpecification()
59+
assertEquals(PIXEL, spec.pixelSuffix)
60+
}
61+
62+
companion object {
63+
private const val PIXEL = "pixel"
64+
private const val TITLE = AppFeatureNotification.DRIP_B_1_TITLE
65+
private const val DESCRIPTION = AppFeatureNotification.DRIP_B_1_DESCRIPTION
66+
}
67+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (c) 2020 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+
@file:Suppress("RemoveExplicitTypeArguments")
18+
19+
package com.duckduckgo.app.notification.model
20+
21+
import androidx.test.platform.app.InstrumentationRegistry
22+
import com.duckduckgo.app.notification.db.NotificationDao
23+
import com.nhaarman.mockitokotlin2.any
24+
import com.nhaarman.mockitokotlin2.mock
25+
import com.nhaarman.mockitokotlin2.whenever
26+
import kotlinx.coroutines.runBlocking
27+
import org.junit.Assert.assertEquals
28+
import org.junit.Assert.assertFalse
29+
import org.junit.Assert.assertTrue
30+
import org.junit.Before
31+
import org.junit.Test
32+
33+
class WebsiteNotificationTest {
34+
35+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
36+
private val notificationsDao: NotificationDao = mock()
37+
38+
private lateinit var testee: WebsiteNotification
39+
40+
@Before
41+
fun before() {
42+
testee = WebsiteNotification(context, notificationsDao, URL, TITLE, DESCRIPTION, PIXEL)
43+
}
44+
45+
@Test
46+
fun whenNotificationNotSeenThenCanShowIsTrue() = runBlocking<Unit> {
47+
whenever(notificationsDao.exists(any())).thenReturn(false)
48+
assertTrue(testee.canShow())
49+
}
50+
51+
@Test
52+
fun whenNotificationAlreadySeenThenCanShowIsFalse() = runBlocking<Unit> {
53+
whenever(notificationsDao.exists(any())).thenReturn(true)
54+
assertFalse(testee.canShow())
55+
}
56+
57+
@Test
58+
fun whenBuildSpecificationSetCorrectUrl() = runBlocking<Unit> {
59+
val spec = testee.buildSpecification()
60+
assertEquals(URL, spec.bundle.get(WebsiteNotificationSpecification.WEBSITE_KEY))
61+
}
62+
63+
@Test
64+
fun whenBuildSpecificationSetCorrectPixelSuffix() = runBlocking<Unit> {
65+
val spec = testee.buildSpecification()
66+
assertEquals(PIXEL, spec.pixelSuffix)
67+
}
68+
69+
companion object {
70+
private const val URL = "test"
71+
private const val PIXEL = "pixel"
72+
private const val TITLE = WebsiteNotification.DRIP_A_1_TITLE
73+
private const val DESCRIPTION = WebsiteNotification.DRIP_A_1_DESCRIPTION
74+
}
75+
}

app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,96 @@ class VariantManagerTest {
4343
assertEquals(0, variant.features.size)
4444
}
4545

46+
// Notification Drip Experiment
47+
48+
@Test
49+
fun notificationDripTestControlGroupVariantActive() {
50+
val variant = variants.firstOrNull { it.key == "za" }
51+
assertEqualsDouble(1.0, variant!!.weight)
52+
}
53+
54+
@Test
55+
fun notificationDripTestControlGroupVariantHasDay1PrivacyNotificationAndDay3ClearDataNotificationAndDripNotification() {
56+
val variant = variants.firstOrNull { it.key == "za" }
57+
assertEquals(3, variant!!.features.size)
58+
assertTrue(variant.hasFeature(Day1PrivacyNotification))
59+
assertTrue(variant.hasFeature(Day3ClearDataNotification))
60+
assertTrue(variant.hasFeature(DripNotification))
61+
}
62+
63+
@Test
64+
fun notificationDripTestNullVariantActive() {
65+
val variant = variants.firstOrNull { it.key == "zb" }
66+
assertEqualsDouble(1.0, variant!!.weight)
67+
}
68+
69+
@Test
70+
fun notificationDripTestNullVariantHasDripNotification() {
71+
val variant = variants.firstOrNull { it.key == "zb" }
72+
assertEquals(1, variant!!.features.size)
73+
assertTrue(variant.hasFeature(DripNotification))
74+
}
75+
76+
@Test
77+
fun notificationDripA1TestVariantActive() {
78+
val variant = variants.firstOrNull { it.key == "zc" }
79+
assertEqualsDouble(1.0, variant!!.weight)
80+
}
81+
82+
@Test
83+
fun notificationDripA1TestVariantHasDay1DripA1NotificationAndDay3ClearDataNotificationAndDripNotification() {
84+
val variant = variants.firstOrNull { it.key == "zc" }
85+
assertEquals(3, variant!!.features.size)
86+
assertTrue(variant.hasFeature(Day1DripA1Notification))
87+
assertTrue(variant.hasFeature(Day3ClearDataNotification))
88+
assertTrue(variant.hasFeature(DripNotification))
89+
}
90+
91+
@Test
92+
fun notificationDripA2TestVariantActive() {
93+
val variant = variants.firstOrNull { it.key == "zd" }
94+
assertEqualsDouble(1.0, variant!!.weight)
95+
}
96+
97+
@Test
98+
fun notificationDripA2TestVariantHasDay1DripA2NotificationAndDay3ClearDataNotificationAndDripNotification() {
99+
val variant = variants.firstOrNull { it.key == "zd" }
100+
assertEquals(3, variant!!.features.size)
101+
assertTrue(variant.hasFeature(Day1DripA2Notification))
102+
assertTrue(variant.hasFeature(Day3ClearDataNotification))
103+
assertTrue(variant.hasFeature(DripNotification))
104+
}
105+
106+
@Test
107+
fun notificationDripB1TestVariantActive() {
108+
val variant = variants.firstOrNull { it.key == "ze" }
109+
assertEqualsDouble(1.0, variant!!.weight)
110+
}
111+
112+
@Test
113+
fun notificationDripB1TestVariantHasDay1DripB1NotificationAndDay3ClearDataNotificationAndDripNotification() {
114+
val variant = variants.firstOrNull { it.key == "ze" }
115+
assertEquals(3, variant!!.features.size)
116+
assertTrue(variant.hasFeature(Day1DripB1Notification))
117+
assertTrue(variant.hasFeature(Day3ClearDataNotification))
118+
assertTrue(variant.hasFeature(DripNotification))
119+
}
120+
121+
@Test
122+
fun notificationDripB2TestVariantActive() {
123+
val variant = variants.firstOrNull { it.key == "zf" }
124+
assertEqualsDouble(1.0, variant!!.weight)
125+
}
126+
127+
@Test
128+
fun notificationDripB2TestVariantHasDay1DripB2NotificationAndDay3ClearDataNotificationAndDripNotification() {
129+
val variant = variants.firstOrNull { it.key == "zf" }
130+
assertEquals(3, variant!!.features.size)
131+
assertTrue(variant.hasFeature(Day1DripB2Notification))
132+
assertTrue(variant.hasFeature(Day3ClearDataNotification))
133+
assertTrue(variant.hasFeature(DripNotification))
134+
}
135+
46136
@Test
47137
fun verifyNoDuplicateVariantNames() {
48138
val existingNames = mutableSetOf<String>()

app/src/main/java/com/duckduckgo/app/di/DaggerWorkerFactory.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ import com.duckduckgo.app.job.ConfigurationDownloader
2828
import com.duckduckgo.app.notification.NotificationFactory
2929
import com.duckduckgo.app.notification.NotificationScheduler.ClearDataNotificationWorker
3030
import com.duckduckgo.app.notification.NotificationScheduler.PrivacyNotificationWorker
31+
import com.duckduckgo.app.notification.NotificationScheduler.DripA1NotificationWorker
32+
import com.duckduckgo.app.notification.NotificationScheduler.DripA2NotificationWorker
33+
import com.duckduckgo.app.notification.NotificationScheduler.DripB1NotificationWorker
34+
import com.duckduckgo.app.notification.NotificationScheduler.DripB2NotificationWorker
3135
import com.duckduckgo.app.notification.db.NotificationDao
36+
import com.duckduckgo.app.notification.model.AppFeatureNotification
37+
import com.duckduckgo.app.notification.model.WebsiteNotification
3238
import com.duckduckgo.app.notification.model.ClearDataNotification
3339
import com.duckduckgo.app.notification.model.PrivacyProtectionNotification
3440
import com.duckduckgo.app.settings.db.SettingsDataStore
@@ -47,6 +53,10 @@ class DaggerWorkerFactory(
4753
private val clearDataNotification: ClearDataNotification,
4854
private val privacyProtectionNotification: PrivacyProtectionNotification,
4955
private val configurationDownloader: ConfigurationDownloader,
56+
private val dripA1Notification: WebsiteNotification,
57+
private val dripA2Notification: WebsiteNotification,
58+
private val dripB1Notification: AppFeatureNotification,
59+
private val dripB2Notification: AppFeatureNotification,
5060
private val pixel: Pixel
5161
) : WorkerFactory() {
5262

@@ -63,6 +73,10 @@ class DaggerWorkerFactory(
6373
is ClearDataNotificationWorker -> injectClearDataNotificationWorker(instance)
6474
is PrivacyNotificationWorker -> injectPrivacyNotificationWorker(instance)
6575
is AppConfigurationWorker -> injectAppConfigurationWorker(instance)
76+
is DripA1NotificationWorker -> injectDripA1NotificationWorker(instance)
77+
is DripA2NotificationWorker -> injectDripA2NotificationWorker(instance)
78+
is DripB1NotificationWorker -> injectDripB1NotificationWorker(instance)
79+
is DripB2NotificationWorker -> injectDripB2NotificationWorker(instance)
6680
else -> Timber.i("No injection required for worker $workerClassName")
6781
}
6882

@@ -103,4 +117,35 @@ class DaggerWorkerFactory(
103117
worker.notification = privacyProtectionNotification
104118
}
105119

120+
private fun injectDripA1NotificationWorker(worker: DripA1NotificationWorker) {
121+
worker.manager = notificationManager
122+
worker.notificationDao = notificationDao
123+
worker.factory = notificationFactory
124+
worker.pixel = pixel
125+
worker.notification = dripA1Notification
126+
}
127+
128+
private fun injectDripA2NotificationWorker(worker: DripA2NotificationWorker) {
129+
worker.manager = notificationManager
130+
worker.notificationDao = notificationDao
131+
worker.factory = notificationFactory
132+
worker.pixel = pixel
133+
worker.notification = dripA2Notification
134+
}
135+
136+
private fun injectDripB1NotificationWorker(worker: DripB1NotificationWorker) {
137+
worker.manager = notificationManager
138+
worker.notificationDao = notificationDao
139+
worker.factory = notificationFactory
140+
worker.pixel = pixel
141+
worker.notification = dripB1Notification
142+
}
143+
144+
private fun injectDripB2NotificationWorker(worker: DripB2NotificationWorker) {
145+
worker.manager = notificationManager
146+
worker.notificationDao = notificationDao
147+
worker.factory = notificationFactory
148+
worker.pixel = pixel
149+
worker.notification = dripB2Notification
150+
}
106151
}

0 commit comments

Comments
 (0)