1 /*
2  * Copyright (C) 2024 The Android Open Source Project
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.android.systemui.haptics.slider.compose.ui
18 
19 import androidx.compose.foundation.gestures.Orientation
20 import androidx.compose.foundation.interaction.DragInteraction
21 import androidx.compose.foundation.interaction.InteractionSource
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.SmallTest
24 import com.android.systemui.SysuiTestCase
25 import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig
26 import com.android.systemui.haptics.slider.SliderEventType
27 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
28 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
29 import com.android.systemui.kosmos.testScope
30 import com.android.systemui.lifecycle.activateIn
31 import com.android.systemui.testKosmos
32 import com.google.common.truth.Truth.assertThat
33 import kotlinx.coroutines.ExperimentalCoroutinesApi
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.asStateFlow
36 import kotlinx.coroutines.test.runCurrent
37 import kotlinx.coroutines.test.runTest
38 import org.junit.Before
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 
42 @OptIn(ExperimentalCoroutinesApi::class)
43 @SmallTest
44 @RunWith(AndroidJUnit4::class)
45 class SliderHapticsViewModelTest : SysuiTestCase() {
46 
47     private val kosmos = testKosmos()
48     private val testScope = kosmos.testScope
49     private val interactionSource = DragInteractionSourceTest()
50     private val underTest =
51         kosmos.sliderHapticsViewModelFactory.create(
52             interactionSource,
53             0f..1f,
54             Orientation.Horizontal,
55             SliderHapticFeedbackConfig(),
56             SeekableSliderTrackerConfig(),
57         )
58 
59     @Before
setUpnull60     fun setUp() {
61         underTest.activateIn(testScope)
62     }
63 
64     @Test
onActivated_startsRunningnull65     fun onActivated_startsRunning() =
66         testScope.runTest {
67             // WHEN the view-model is activated
68             testScope.runCurrent()
69 
70             // THEN the view-model starts running
71             assertThat(underTest.isRunning).isTrue()
72         }
73 
74     @Test
onDragStart_goesToUserStartedDraggingnull75     fun onDragStart_goesToUserStartedDragging() =
76         testScope.runTest {
77             // WHEN a drag interaction starts
78             interactionSource.setDragInteraction(DragInteraction.Start())
79             runCurrent()
80 
81             // THEN the current slider event type shows that the user started dragging
82             assertThat(underTest.currentSliderEventType)
83                 .isEqualTo(SliderEventType.STARTED_TRACKING_TOUCH)
84         }
85 
86     @Test
onValueChange_whileUserStartedDragging_goesToUserDraggingnull87     fun onValueChange_whileUserStartedDragging_goesToUserDragging() =
88         testScope.runTest {
89             // WHEN a drag interaction starts
90             interactionSource.setDragInteraction(DragInteraction.Start())
91             runCurrent()
92 
93             // WHEN a value changes in the slider
94             underTest.onValueChange(0.5f)
95 
96             // THEN the current slider event type shows that the user is dragging
97             assertThat(underTest.currentSliderEventType)
98                 .isEqualTo(SliderEventType.PROGRESS_CHANGE_BY_USER)
99         }
100 
101     @Test
onValueChange_whileUserDragging_staysInUserDraggingnull102     fun onValueChange_whileUserDragging_staysInUserDragging() =
103         testScope.runTest {
104             // WHEN a drag interaction starts and the user keeps dragging
105             interactionSource.setDragInteraction(DragInteraction.Start())
106             runCurrent()
107             underTest.onValueChange(0.5f)
108 
109             // WHEN value changes continue to occur due to dragging
110             underTest.onValueChange(0.6f)
111 
112             // THEN the current slider event type reflects that the user continues to drag
113             assertThat(underTest.currentSliderEventType)
114                 .isEqualTo(SliderEventType.PROGRESS_CHANGE_BY_USER)
115         }
116 
117     @Test
onValueChange_whileNOTHING_goesToProgramStartedDraggingnull118     fun onValueChange_whileNOTHING_goesToProgramStartedDragging() =
119         testScope.runTest {
120             // WHEN a value change occurs without a drag interaction
121             underTest.onValueChange(0.5f)
122 
123             // THEN the current slider event type shows that the program started dragging
124             assertThat(underTest.currentSliderEventType)
125                 .isEqualTo(SliderEventType.STARTED_TRACKING_PROGRAM)
126         }
127 
128     @Test
onValueChange_whileProgramStartedDragging_goesToProgramDraggingnull129     fun onValueChange_whileProgramStartedDragging_goesToProgramDragging() =
130         testScope.runTest {
131             // WHEN the program starts dragging
132             underTest.onValueChange(0.5f)
133 
134             // WHEN the program continues to make value changes
135             underTest.onValueChange(0.6f)
136 
137             // THEN the current slider event type shows that program is dragging
138             assertThat(underTest.currentSliderEventType)
139                 .isEqualTo(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM)
140         }
141 
142     @Test
onValueChange_whileProgramDragging_staysInProgramDraggingnull143     fun onValueChange_whileProgramDragging_staysInProgramDragging() =
144         testScope.runTest {
145             // WHEN the program starts and continues to drag
146             underTest.onValueChange(0.5f)
147             underTest.onValueChange(0.6f)
148 
149             // WHEN value changes continue to occur
150             underTest.onValueChange(0.7f)
151 
152             // THEN the current slider event type shows that the program is dragging the slider
153             assertThat(underTest.currentSliderEventType)
154                 .isEqualTo(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM)
155         }
156 
157     @Test
onValueChangeEnded_goesToNOTHINGnull158     fun onValueChangeEnded_goesToNOTHING() =
159         testScope.runTest {
160             // WHEN changes end in the slider
161             underTest.onValueChangeEnded()
162 
163             // THEN the current slider event type always resets to NOTHING
164             assertThat(underTest.currentSliderEventType).isEqualTo(SliderEventType.NOTHING)
165         }
166 
167     private class DragInteractionSourceTest : InteractionSource {
168         private val _interactions = MutableStateFlow<DragInteraction>(IdleDrag)
169         override val interactions = _interactions.asStateFlow()
170 
setDragInteractionnull171         fun setDragInteraction(interaction: DragInteraction) {
172             _interactions.value = interaction
173         }
174     }
175 
176     private object IdleDrag : DragInteraction
177 }
178