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 package com.google.jetpackcamera.feature.preview.ui
17 
18 import androidx.compose.runtime.MutableState
19 import androidx.compose.runtime.mutableStateOf
20 import androidx.compose.ui.graphics.Color
21 import androidx.compose.ui.graphics.toArgb
22 import androidx.compose.ui.test.assertHeightIsAtLeast
23 import androidx.compose.ui.test.assertWidthIsAtLeast
24 import androidx.compose.ui.test.getBoundsInRoot
25 import androidx.compose.ui.test.hasTestTag
26 import androidx.compose.ui.test.junit4.createComposeRule
27 import androidx.compose.ui.test.onRoot
28 import androidx.compose.ui.unit.height
29 import androidx.compose.ui.unit.width
30 import com.google.jetpackcamera.feature.preview.ScreenFlash
31 import com.google.jetpackcamera.feature.preview.rules.MainDispatcherRule
32 import com.google.jetpackcamera.feature.preview.workaround.captureToImage
33 import kotlinx.coroutines.ExperimentalCoroutinesApi
34 import kotlinx.coroutines.test.StandardTestDispatcher
35 import kotlinx.coroutines.test.TestScope
36 import kotlinx.coroutines.test.advanceUntilIdle
37 import kotlinx.coroutines.test.runTest
38 import org.junit.Assert.assertEquals
39 import org.junit.Before
40 import org.junit.Rule
41 import org.junit.Test
42 import org.junit.runner.RunWith
43 import org.robolectric.RobolectricTestRunner
44 import org.robolectric.annotation.Config
45 import org.robolectric.annotation.GraphicsMode
46 import org.robolectric.shadows.ShadowPixelCopy
47 
48 // TODO: After device tests are added to github workflow, remove the tests here since they are
49 //  duplicated in androidTest and fits there better
50 @OptIn(ExperimentalCoroutinesApi::class)
51 @RunWith(RobolectricTestRunner::class)
52 class ScreenFlashComponentsKtTest {
53     private val testScope = TestScope()
54     private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
55 
56     @get:Rule
57     val mainDispatcherRule = MainDispatcherRule(testDispatcher)
58 
59     @get:Rule
60     val composeTestRule = createComposeRule()
61 
62     private val screenFlashUiState: MutableState<ScreenFlash.ScreenFlashUiState> =
63         mutableStateOf(ScreenFlash.ScreenFlashUiState())
64 
65     @Before
setUpnull66     fun setUp() {
67         composeTestRule.setContent {
68             ScreenFlashScreen(
69                 screenFlashUiState = screenFlashUiState.value,
70                 onInitialBrightnessCalculated = {}
71             )
72         }
73     }
74 
75     @Test
<lambda>null76     fun screenFlashOverlay_doesNotExistByDefault() = runTest {
77         advanceUntilIdle()
78         composeTestRule.onNode(hasTestTag("ScreenFlashOverlay")).assertDoesNotExist()
79     }
80 
81     @Test
<lambda>null82     fun screenFlashOverlay_existsAfterStateIsEnabled() = runTest {
83         screenFlashUiState.value = ScreenFlash.ScreenFlashUiState(enabled = true)
84 
85         advanceUntilIdle()
86         composeTestRule.onNode(hasTestTag("ScreenFlashOverlay")).assertExists()
87     }
88 
89     @Test
<lambda>null90     fun screenFlashOverlay_doesNotExistWhenDisabledAfterEnabled() = runTest {
91         screenFlashUiState.value = ScreenFlash.ScreenFlashUiState(enabled = true)
92         screenFlashUiState.value = ScreenFlash.ScreenFlashUiState(enabled = false)
93 
94         advanceUntilIdle()
95         composeTestRule.onNode(hasTestTag("ScreenFlashOverlay")).assertDoesNotExist()
96     }
97 
98     @Test
<lambda>null99     fun screenFlashOverlay_sizeFillsMaxSize() = runTest {
100         screenFlashUiState.value = ScreenFlash.ScreenFlashUiState(enabled = true)
101 
102         advanceUntilIdle()
103         val rootBounds = composeTestRule.onRoot().getBoundsInRoot()
104         composeTestRule.onNode(hasTestTag("ScreenFlashOverlay"))
105             .assertWidthIsAtLeast(rootBounds.width)
106         composeTestRule.onNode(hasTestTag("ScreenFlashOverlay"))
107             .assertHeightIsAtLeast(rootBounds.height)
108     }
109 
110     @Test
111     @GraphicsMode(GraphicsMode.Mode.NATIVE)
112     @Config(shadows = [ShadowPixelCopy::class])
<lambda>null113     fun screenFlashOverlay_fullWhiteWhenEnabled() = runTest {
114         screenFlashUiState.value = ScreenFlash.ScreenFlashUiState(enabled = true)
115 
116         advanceUntilIdle()
117         val overlayScreenShot =
118             composeTestRule.onNode(hasTestTag("ScreenFlashOverlay")).captureToImage()
119 
120         // check a few pixels near center instead of whole image to save time
121         val overlayPixels = IntArray(4)
122         overlayScreenShot.readPixels(
123             overlayPixels,
124             overlayScreenShot.width / 2,
125             overlayScreenShot.height / 2,
126             2,
127             2
128         )
129         overlayPixels.forEach {
130             assertEquals(Color.White.toArgb(), it)
131         }
132     }
133 }
134