Skip to content

Commit e6a46aa

Browse files
[DON-2477] Fix BpkTheme to respect provided typography/colors/shapes in LocalInspectionMode (#2592)
* [DON-2477] Added logic for BpkTheme to respect provided typography, colors, and shapes in Roborazzi screenshot tests, retaining default fallback logic if not present. * [DON-2477] Added missing test and fixed incorrect comments
1 parent 3978ae9 commit e6a46aa

File tree

2 files changed

+179
-16
lines changed

2 files changed

+179
-16
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/**
2+
* Backpack for Android - Skyscanner's Design System
3+
*
4+
* Copyright 2018 - 2026 Skyscanner Ltd
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package net.skyscanner.backpack.compose.theme
20+
21+
import androidx.compose.runtime.CompositionLocalProvider
22+
import androidx.compose.ui.platform.LocalInspectionMode
23+
import androidx.compose.ui.test.junit4.createComposeRule
24+
import androidx.compose.ui.text.font.FontFamily
25+
import net.skyscanner.backpack.compose.tokens.BpkColors
26+
import net.skyscanner.backpack.compose.tokens.BpkShapes
27+
import net.skyscanner.backpack.compose.tokens.BpkTypography
28+
import org.junit.Assert.assertEquals
29+
import org.junit.Assert.assertNotNull
30+
import org.junit.Rule
31+
import org.junit.Test
32+
33+
class BpkThemeProvidedValuesTest {
34+
35+
@get:Rule
36+
val composeTestRule = createComposeRule()
37+
38+
@Test
39+
fun givenBpkTheme_whenTypographyAccessed_thenProvidedTypographyReturned() {
40+
var capturedTypography: BpkTypography? = null
41+
42+
composeTestRule.setContent {
43+
BpkTheme {
44+
capturedTypography = BpkTheme.typography
45+
}
46+
}
47+
48+
composeTestRule.waitForIdle()
49+
assertNotNull("Typography should be provided by BpkTheme", capturedTypography)
50+
}
51+
52+
@Test
53+
fun givenBpkTheme_whenColorsAccessed_thenProvidedColorsReturned() {
54+
var capturedColors: BpkColors? = null
55+
56+
composeTestRule.setContent {
57+
BpkTheme {
58+
capturedColors = BpkTheme.colors
59+
}
60+
}
61+
62+
composeTestRule.waitForIdle()
63+
assertNotNull("Colors should be provided by BpkTheme", capturedColors)
64+
}
65+
66+
@Test
67+
fun givenBpkTheme_whenShapesAccessed_thenProvidedShapesReturned() {
68+
var capturedShapes: BpkShapes? = null
69+
70+
composeTestRule.setContent {
71+
BpkTheme {
72+
capturedShapes = BpkTheme.shapes
73+
}
74+
}
75+
76+
composeTestRule.waitForIdle()
77+
assertNotNull("Shapes should be provided by BpkTheme", capturedShapes)
78+
}
79+
80+
@Test
81+
fun givenBpkThemeProvidedValues_whenInInspectionMode_thenProvidedValuesTakePrecedenceOverDefaults() {
82+
var capturedTypography: BpkTypography? = null
83+
var capturedColors: BpkColors? = null
84+
var capturedShapes: BpkShapes? = null
85+
86+
composeTestRule.setContent {
87+
CompositionLocalProvider(LocalInspectionMode provides true) {
88+
// Wrap with BpkTheme which provides specific values
89+
BpkTheme(fontFamily = FontFamily.Cursive) {
90+
capturedTypography = BpkTheme.typography
91+
capturedColors = BpkTheme.colors
92+
capturedShapes = BpkTheme.shapes
93+
}
94+
}
95+
}
96+
97+
composeTestRule.waitForIdle()
98+
99+
// Verify provided values are used, not defaults
100+
assertNotNull("Typography should be provided", capturedTypography)
101+
assertNotNull("Colors should be provided", capturedColors)
102+
assertNotNull("Shapes should be provided", capturedShapes)
103+
104+
// Verify it's using the custom font family (Cursive), not default (SansSerif)
105+
assertEquals(FontFamily.Cursive, capturedTypography?.bodyDefault?.fontFamily)
106+
}
107+
108+
@Test
109+
fun givenNoValues_whenInInspectionMode_thenUsesDefaultValues() {
110+
var capturedTypography: BpkTypography? = null
111+
var capturedColors: BpkColors? = null
112+
var capturedShapes: BpkShapes? = null
113+
114+
composeTestRule.setContent {
115+
CompositionLocalProvider(LocalInspectionMode provides true) {
116+
capturedTypography = BpkTheme.typography
117+
capturedColors = BpkTheme.colors
118+
capturedShapes = BpkTheme.shapes
119+
}
120+
}
121+
122+
composeTestRule.waitForIdle()
123+
124+
// Verify default values are used
125+
assertNotNull("Typography should be provided", capturedTypography)
126+
assertNotNull("Colors should be provided", capturedColors)
127+
assertNotNull("Shapes should be provided", capturedShapes)
128+
129+
assertEquals(FontFamily.SansSerif, capturedTypography!!.bodyDefault.fontFamily)
130+
assertEquals(BpkColors.light().canvas, capturedColors!!.canvas)
131+
assertEquals(BpkShapes().medium, capturedShapes!!.medium)
132+
}
133+
134+
@Test(expected = IllegalStateException::class)
135+
fun givenNoThemeWrapper_whenOutsideInspectionMode_thenTypographyAccessThrowsError() {
136+
composeTestRule.setContent {
137+
CompositionLocalProvider(LocalInspectionMode provides false) {
138+
// This should throw an error
139+
BpkTheme.typography
140+
}
141+
}
142+
143+
composeTestRule.waitForIdle()
144+
}
145+
146+
@Test(expected = IllegalStateException::class)
147+
fun givenNoThemeWrapper_whenOutsideInspectionMode_thenColorsAccessThrowsError() {
148+
composeTestRule.setContent {
149+
CompositionLocalProvider(LocalInspectionMode provides false) {
150+
// This should throw an error
151+
BpkTheme.colors
152+
}
153+
}
154+
155+
composeTestRule.waitForIdle()
156+
}
157+
158+
@Test(expected = IllegalStateException::class)
159+
fun givenNoThemeWrapper_whenOutsideInspectionMode_thenShapesAccessThrowsError() {
160+
composeTestRule.setContent {
161+
CompositionLocalProvider(LocalInspectionMode provides false) {
162+
// This should throw an error
163+
BpkTheme.shapes
164+
}
165+
}
166+
167+
composeTestRule.waitForIdle()
168+
}
169+
}

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/theme/BpkTheme.kt

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,9 @@ import net.skyscanner.backpack.compose.tokens.BpkShapes
3232
import net.skyscanner.backpack.compose.tokens.BpkTypography
3333
import net.skyscanner.backpack.configuration.BpkConfiguration
3434

35-
private val LocalBpkTypography = staticCompositionLocalOf<BpkTypography> {
36-
error("Wrap you content with BpkTheme {} to get access to Backpack typography")
37-
}
38-
private val LocalBpkColors = staticCompositionLocalOf<BpkColors> {
39-
error("Wrap you content with BpkTheme {} to get access to Backpack colors")
40-
}
41-
private val LocalBpkShapes = staticCompositionLocalOf<BpkShapes> {
42-
error("Wrap you content with BpkTheme {} to get access to Backpack shapes")
43-
}
35+
private val LocalBpkTypography = staticCompositionLocalOf<BpkTypography?> { null }
36+
private val LocalBpkColors = staticCompositionLocalOf<BpkColors?> { null }
37+
private val LocalBpkShapes = staticCompositionLocalOf<BpkShapes?> { null }
4438

4539
@Composable
4640
fun BpkTheme(
@@ -69,33 +63,33 @@ object BpkTheme {
6963
val typography: BpkTypography
7064
@Composable
7165
@ReadOnlyComposable
72-
get() = if (LocalInspectionMode.current) {
66+
get() = LocalBpkTypography.current ?: if (LocalInspectionMode.current) {
7367
// when in preview mode return a default typography object to ensure previews work
7468
// without wrapping it in another composable
7569
BpkTypography(defaultFontFamily = FontFamily.SansSerif)
7670
} else {
77-
LocalBpkTypography.current
71+
error("BpkTheme not found in composition hierarchy")
7872
}
7973

8074
val colors: BpkColors
8175
@Composable
8276
@ReadOnlyComposable
83-
get() = if (LocalInspectionMode.current) {
77+
get() = LocalBpkColors.current ?: if (LocalInspectionMode.current) {
8478
// when in preview mode return a default colour object to ensure previews work
8579
// without wrapping it in another composable
8680
if (isSystemInDarkTheme()) BpkColors.dark() else BpkColors.light()
8781
} else {
88-
LocalBpkColors.current
82+
error("BpkTheme not found in composition hierarchy")
8983
}
9084

9185
val shapes: BpkShapes
9286
@Composable
9387
@ReadOnlyComposable
94-
get() = if (LocalInspectionMode.current) {
95-
// when in preview mode return a default typography object to ensure previews work
88+
get() = LocalBpkShapes.current ?: if (LocalInspectionMode.current) {
89+
// when in preview mode return a default shapes object to ensure previews work
9690
// without wrapping it in another composable
9791
BpkShapes()
9892
} else {
99-
LocalBpkShapes.current
93+
error("BpkTheme not found in composition hierarchy")
10094
}
10195
}

0 commit comments

Comments
 (0)