1 /*
2 * Copyright 2022 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 * https://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("DEPRECATION")
18
19 package com.google.accompanist.themeadapter.material
20
21 import android.view.ContextThemeWrapper
22 import androidx.annotation.StyleRes
23 import androidx.appcompat.app.AppCompatActivity
24 import androidx.compose.foundation.shape.CornerSize
25 import androidx.compose.foundation.shape.CutCornerShape
26 import androidx.compose.foundation.shape.RoundedCornerShape
27 import androidx.compose.material.LocalContentColor
28 import androidx.compose.material.MaterialTheme
29 import androidx.compose.material.Typography
30 import androidx.compose.runtime.Composable
31 import androidx.compose.runtime.CompositionLocalProvider
32 import androidx.compose.ui.geometry.Size
33 import androidx.compose.ui.platform.LocalContext
34 import androidx.compose.ui.platform.LocalDensity
35 import androidx.compose.ui.res.colorResource
36 import androidx.compose.ui.test.junit4.createAndroidComposeRule
37 import androidx.compose.ui.text.font.Font
38 import androidx.compose.ui.text.font.FontFamily
39 import androidx.compose.ui.text.font.FontWeight
40 import androidx.compose.ui.text.font.toFontFamily
41 import androidx.compose.ui.unit.Density
42 import androidx.compose.ui.unit.Dp
43 import androidx.compose.ui.unit.TextUnit
44 import androidx.compose.ui.unit.dp
45 import androidx.compose.ui.unit.em
46 import androidx.compose.ui.unit.sp
47 import androidx.test.filters.SdkSuppress
48 import com.google.accompanist.themeadapter.core.FontFamilyWithWeight
49 import com.google.accompanist.themeadapter.material.test.R
50 import org.junit.Assert.assertEquals
51 import org.junit.Assert.assertNotEquals
52 import org.junit.Assert.assertNotNull
53 import org.junit.Assert.assertNull
54 import org.junit.Assert.assertTrue
55 import org.junit.Rule
56 import org.junit.Test
57
58 /**
59 * Class which contains the majority of the tests. This class is extended
60 * in both the `androidTest` and `test` source sets for setup of the relevant
61 * test runner.
62 */
63 abstract class BaseMdcThemeTest<T : AppCompatActivity>(
64 activityClass: Class<T>
65 ) {
66 @get:Rule
67 val composeTestRule = createAndroidComposeRule(activityClass)
68
69 @Test
<lambda>null70 fun colors() = composeTestRule.setContent {
71 MdcTheme {
72 val color = MaterialTheme.colors
73
74 assertEquals(colorResource(R.color.aquamarine), color.primary)
75 assertEquals(colorResource(R.color.royal_blue), color.primaryVariant)
76 assertEquals(colorResource(R.color.midnight_blue), color.onPrimary)
77
78 assertEquals(colorResource(R.color.dark_golden_rod), color.secondary)
79 assertEquals(colorResource(R.color.slate_gray), color.onSecondary)
80 assertEquals(colorResource(R.color.blue_violet), color.secondaryVariant)
81
82 assertEquals(colorResource(R.color.spring_green), color.surface)
83 assertEquals(colorResource(R.color.navy), color.onSurface)
84
85 assertEquals(colorResource(R.color.dark_salmon), color.error)
86 assertEquals(colorResource(R.color.beige), color.onError)
87
88 assertEquals(colorResource(R.color.light_coral), color.background)
89 assertEquals(colorResource(R.color.orchid), color.onBackground)
90
91 // MdcTheme updates the LocalContentColor to match the calculated onBackground
92 assertEquals(colorResource(R.color.orchid), LocalContentColor.current)
93 }
94 }
95
96 @Test
<lambda>null97 fun shapes() = composeTestRule.setContent {
98 MdcTheme {
99 val shapes = MaterialTheme.shapes
100 val density = LocalDensity.current
101
102 shapes.small.run {
103 assertTrue(this is CutCornerShape)
104 assertEquals(4f, topStart.toPx(density))
105 assertEquals(9.dp.scaleToPx(density), topEnd.toPx(density))
106 assertEquals(5f, bottomEnd.toPx(density))
107 assertEquals(3.dp.scaleToPx(density), bottomStart.toPx(density))
108 }
109 shapes.medium.run {
110 assertTrue(this is RoundedCornerShape)
111 assertEquals(12.dp.scaleToPx(density), topStart.toPx(density))
112 assertEquals(12.dp.scaleToPx(density), topEnd.toPx(density))
113 assertEquals(12.dp.scaleToPx(density), bottomEnd.toPx(density))
114 assertEquals(12.dp.scaleToPx(density), bottomStart.toPx(density))
115 }
116 shapes.large.run {
117 assertTrue(this is CutCornerShape)
118 assertEquals(0f, topStart.toPx(density))
119 assertEquals(0f, topEnd.toPx(density))
120 assertEquals(0f, bottomEnd.toPx(density))
121 assertEquals(0f, bottomStart.toPx(density))
122 }
123 }
124 }
125
126 @Test
<lambda>null127 fun type() = composeTestRule.setContent {
128 MdcTheme {
129 val typography = MaterialTheme.typography
130 val density = LocalDensity.current
131
132 val rubik300 = Font(R.font.rubik_300).toFontFamily()
133 val rubik400 = Font(R.font.rubik_400).toFontFamily()
134 val rubik500 = Font(R.font.rubik_500).toFontFamily()
135 val sansSerif = FontFamilyWithWeight(FontFamily.SansSerif)
136 val sansSerifLight = FontFamilyWithWeight(FontFamily.SansSerif, FontWeight.Light)
137 val sansSerifBlack = FontFamilyWithWeight(FontFamily.SansSerif, FontWeight.Black)
138 val serif = FontFamilyWithWeight(FontFamily.Serif)
139 val cursive = FontFamilyWithWeight(FontFamily.Cursive)
140 val monospace = FontFamilyWithWeight(FontFamily.Monospace)
141
142 typography.h1.run {
143 assertTextUnitEquals(97.54.sp, fontSize, density)
144 assertTextUnitEquals((-0.0015).em, letterSpacing, density)
145 assertEquals(rubik300, fontFamily)
146 }
147
148 assertNotNull(typography.h2.shadow)
149 typography.h2.shadow!!.run {
150 assertEquals(colorResource(R.color.olive_drab), color)
151 assertEquals(4.43f, offset.x)
152 assertEquals(8.19f, offset.y)
153 assertEquals(2.13f, blurRadius)
154 }
155
156 typography.h3.run {
157 assertEquals(sansSerif.fontFamily, fontFamily)
158 assertEquals(sansSerif.weight, fontWeight)
159 }
160
161 typography.h4.run {
162 assertEquals(sansSerifLight.fontFamily, fontFamily)
163 assertEquals(sansSerifLight.weight, fontWeight)
164 }
165
166 typography.h5.run {
167 assertEquals(sansSerifBlack.fontFamily, fontFamily)
168 assertEquals(sansSerifBlack.weight, fontWeight)
169 }
170
171 typography.h6.run {
172 assertEquals(serif.fontFamily, fontFamily)
173 assertEquals(serif.weight, fontWeight)
174 }
175
176 typography.body1.run {
177 assertTextUnitEquals(16.26.sp, fontSize, density)
178 assertTextUnitEquals(0.005.em, letterSpacing, density)
179 assertEquals(rubik400, fontFamily)
180 assertNull(shadow)
181 }
182
183 typography.body2.run {
184 assertEquals(cursive.fontFamily, fontFamily)
185 assertEquals(cursive.weight, fontWeight)
186 }
187
188 typography.subtitle1.run {
189 assertEquals(monospace.fontFamily, fontFamily)
190 assertEquals(monospace.weight, fontWeight)
191 assertTextUnitEquals(0.em, letterSpacing, density)
192 }
193
194 typography.subtitle2.run {
195 assertEquals(FontFamily.SansSerif, fontFamily)
196 }
197
198 typography.button.run {
199 assertEquals(rubik500, fontFamily)
200 }
201
202 typography.caption.run {
203 assertEquals(FontFamily.SansSerif, fontFamily)
204 assertTextUnitEquals(0.04.em, letterSpacing, density)
205 }
206
207 typography.overline.run {
208 assertEquals(FontFamily.SansSerif, fontFamily)
209 }
210 }
211 }
212
213 @Test
214 @SdkSuppress(minSdkVersion = 23) // XML font families with >1 fonts are only supported on API 23+
<lambda>null215 fun type_rubik_family_api23() = composeTestRule.setContent {
216 val rubik = FontFamily(
217 Font(R.font.rubik_300, FontWeight.W300),
218 Font(R.font.rubik_400, FontWeight.W400),
219 Font(R.font.rubik_500, FontWeight.W500),
220 Font(R.font.rubik_700, FontWeight.W700),
221 )
222 WithThemeOverlay(R.style.ThemeOverlay_MdcThemeTest_DefaultFontFamily_Rubik) {
223 MdcTheme(setDefaultFontFamily = true) {
224 MaterialTheme.typography.assertFontFamilies(expected = rubik)
225 }
226 }
227 WithThemeOverlay(R.style.ThemeOverlay_MdcThemeTest_DefaultAndroidFontFamily_Rubik) {
228 MdcTheme(setDefaultFontFamily = true) {
229 MaterialTheme.typography.assertFontFamilies(expected = rubik)
230 }
231 }
232 }
233
234 @Test
<lambda>null235 fun type_rubik_fixed400() = composeTestRule.setContent {
236 val rubik400 = Font(R.font.rubik_400, FontWeight.W400).toFontFamily()
237 WithThemeOverlay(R.style.ThemeOverlay_MdcThemeTest_DefaultFontFamily_Rubik400) {
238 MdcTheme(setDefaultFontFamily = true) {
239 MaterialTheme.typography.assertFontFamilies(expected = rubik400)
240 }
241 }
242 WithThemeOverlay(R.style.ThemeOverlay_MdcThemeTest_DefaultAndroidFontFamily_Rubik400) {
243 MdcTheme(setDefaultFontFamily = true) {
244 MaterialTheme.typography.assertFontFamilies(expected = rubik400)
245 }
246 }
247 }
248
249 @Test
<lambda>null250 fun type_rubik_fixed700_withTextAppearances() = composeTestRule.setContent {
251 val rubik700 = Font(R.font.rubik_700, FontWeight.W700).toFontFamily()
252 WithThemeOverlay(
253 R.style.ThemeOverlay_MdcThemeTest_DefaultFontFamilies_Rubik700_WithTextAppearances
254 ) {
255 MdcTheme {
256 MaterialTheme.typography.assertFontFamilies(
257 expected = rubik700,
258 notEquals = true
259 )
260 }
261 }
262 }
263 }
264
Dpnull265 private fun Dp.scaleToPx(density: Density): Float {
266 val dp = this
267 return with(density) { dp.toPx() }
268 }
269
assertTextUnitEqualsnull270 private fun assertTextUnitEquals(expected: TextUnit, actual: TextUnit, density: Density) {
271 if (expected.javaClass == actual.javaClass) {
272 // If the expected and actual are the same type, compare the raw values with a
273 // delta to account for float inaccuracy
274 assertEquals(expected.value, actual.value, 0.001f)
275 } else {
276 // Otherwise we need to flatten to a px to compare the values. Again using a
277 // delta to account for float inaccuracy
278 with(density) { assertEquals(expected.toPx(), actual.toPx(), 0.001f) }
279 }
280 }
281
toPxnull282 private fun CornerSize.toPx(density: Density) = toPx(Size.Unspecified, density)
283
284 internal fun Typography.assertFontFamilies(
285 expected: FontFamily,
286 notEquals: Boolean = false
287 ) {
288 if (notEquals) assertNotEquals(expected, h1.fontFamily) else assertEquals(expected, h1.fontFamily)
289 if (notEquals) assertNotEquals(expected, h2.fontFamily) else assertEquals(expected, h2.fontFamily)
290 if (notEquals) assertNotEquals(expected, h3.fontFamily) else assertEquals(expected, h3.fontFamily)
291 if (notEquals) assertNotEquals(expected, h4.fontFamily) else assertEquals(expected, h4.fontFamily)
292 if (notEquals) assertNotEquals(expected, h5.fontFamily) else assertEquals(expected, h5.fontFamily)
293 if (notEquals) assertNotEquals(expected, h6.fontFamily) else assertEquals(expected, h6.fontFamily)
294 if (notEquals) assertNotEquals(expected, subtitle1.fontFamily) else assertEquals(expected, subtitle1.fontFamily)
295 if (notEquals) assertNotEquals(expected, subtitle2.fontFamily) else assertEquals(expected, subtitle2.fontFamily)
296 if (notEquals) assertNotEquals(expected, body1.fontFamily) else assertEquals(expected, body1.fontFamily)
297 if (notEquals) assertNotEquals(expected, body2.fontFamily) else assertEquals(expected, body2.fontFamily)
298 if (notEquals) assertNotEquals(expected, button.fontFamily) else assertEquals(expected, button.fontFamily)
299 if (notEquals) assertNotEquals(expected, caption.fontFamily) else assertEquals(expected, caption.fontFamily)
300 if (notEquals) assertNotEquals(expected, overline.fontFamily) else assertEquals(expected, overline.fontFamily)
301 }
302
303 /**
304 * Function which applies an Android theme overlay to the current context.
305 */
306 @Composable
WithThemeOverlaynull307 fun WithThemeOverlay(
308 @StyleRes themeOverlayId: Int,
309 content: @Composable () -> Unit,
310 ) {
311 val themedContext = ContextThemeWrapper(LocalContext.current, themeOverlayId)
312 CompositionLocalProvider(LocalContext provides themedContext, content = content)
313 }
314