1 /*
<lambda>null2  * Copyright 2021 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 androidx.glance.appwidget
18 
19 import android.app.Activity
20 import android.graphics.drawable.BitmapDrawable
21 import androidx.compose.runtime.Composable
22 import androidx.compose.ui.graphics.Color
23 import androidx.compose.ui.unit.DpSize
24 import androidx.compose.ui.unit.dp
25 import androidx.compose.ui.unit.sp
26 import androidx.glance.Button
27 import androidx.glance.ButtonDefaults
28 import androidx.glance.ColorFilter
29 import androidx.glance.GlanceModifier
30 import androidx.glance.GlanceTheme
31 import androidx.glance.Image
32 import androidx.glance.ImageProvider
33 import androidx.glance.LocalContext
34 import androidx.glance.action.actionStartActivity
35 import androidx.glance.appwidget.components.CircleIconButton
36 import androidx.glance.appwidget.components.FilledButton
37 import androidx.glance.appwidget.components.OutlineButton
38 import androidx.glance.appwidget.components.Scaffold
39 import androidx.glance.appwidget.components.SquareIconButton
40 import androidx.glance.appwidget.components.TitleBar
41 import androidx.glance.appwidget.lazy.LazyColumn
42 import androidx.glance.appwidget.test.R
43 import androidx.glance.background
44 import androidx.glance.color.ColorProvider
45 import androidx.glance.color.colorProviders
46 import androidx.glance.layout.Alignment
47 import androidx.glance.layout.Box
48 import androidx.glance.layout.Column
49 import androidx.glance.layout.ContentScale
50 import androidx.glance.layout.Row
51 import androidx.glance.layout.Spacer
52 import androidx.glance.layout.fillMaxHeight
53 import androidx.glance.layout.fillMaxSize
54 import androidx.glance.layout.fillMaxWidth
55 import androidx.glance.layout.height
56 import androidx.glance.layout.padding
57 import androidx.glance.layout.size
58 import androidx.glance.layout.width
59 import androidx.glance.layout.wrapContentSize
60 import androidx.glance.text.FontStyle
61 import androidx.glance.text.FontWeight
62 import androidx.glance.text.Text
63 import androidx.glance.text.TextAlign
64 import androidx.glance.text.TextDecoration
65 import androidx.glance.text.TextStyle
66 import androidx.glance.unit.ColorProvider
67 import androidx.test.filters.MediumTest
68 import androidx.test.filters.SdkSuppress
69 import kotlinx.coroutines.ExperimentalCoroutinesApi
70 import kotlinx.coroutines.test.runTest
71 import org.junit.Rule
72 import org.junit.Test
73 import org.junit.rules.RuleChain
74 import org.junit.rules.TestRule
75 
76 @SdkSuppress(minSdkVersion = 29)
77 @OptIn(ExperimentalCoroutinesApi::class)
78 @MediumTest
79 class GlanceAppWidgetReceiverScreenshotTest {
80     private val mScreenshotRule = screenshotRule()
81     private val mHostRule = AppWidgetHostRule()
82 
83     @Rule
84     @JvmField
85     val mRule: TestRule =
86         RuleChain.outerRule(mHostRule)
87             .around(mScreenshotRule)
88             .around(WithRtlRule)
89             .around(WithNightModeRule)
90 
91     @Test
92     fun createSimpleAppWidget() {
93         TestGlanceAppWidget.uiDefinition = {
94             Text(
95                 "text",
96                 style =
97                     TextStyle(
98                         textDecoration = TextDecoration.Underline,
99                         fontWeight = FontWeight.Medium,
100                         fontStyle = FontStyle.Italic,
101                     )
102             )
103         }
104 
105         mHostRule.startHost()
106 
107         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "simpleAppWidget")
108     }
109 
110     @Test
111     fun createCheckBoxAppWidget() {
112         TestGlanceAppWidget.uiDefinition = { CheckBoxScreenshotTest() }
113 
114         mHostRule.startHost()
115 
116         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "checkBoxWidget")
117     }
118 
119     @WithNightMode
120     @Test
121     fun createCheckBoxAppWidget_dark() {
122         TestGlanceAppWidget.uiDefinition = { CheckBoxScreenshotTest() }
123 
124         mHostRule.startHost()
125 
126         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "checkBoxWidget_dark")
127     }
128 
129     @Test
130     fun createCheckSwitchAppWidget() {
131         TestGlanceAppWidget.uiDefinition = { SwitchTest() }
132 
133         mHostRule.startHost()
134 
135         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "switchWidget")
136     }
137 
138     @WithNightMode
139     @Test
140     fun createCheckSwitchAppWidget_dark() {
141         TestGlanceAppWidget.uiDefinition = { SwitchTest() }
142 
143         mHostRule.startHost()
144 
145         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "switchWidget_dark")
146     }
147 
148     @Test
149     fun createRadioButtonAppWidget() {
150         TestGlanceAppWidget.uiDefinition = { RadioButtonScreenshotTest() }
151 
152         mHostRule.startHost()
153 
154         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "radioButtonWidget")
155     }
156 
157     @WithNightMode
158     @Test
159     fun createRadioButtonAppWidget_dark() {
160         TestGlanceAppWidget.uiDefinition = { RadioButtonScreenshotTest() }
161 
162         mHostRule.startHost()
163 
164         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "radioButtonWidget_dark")
165     }
166 
167     @Test
168     fun createRowWidget() {
169         TestGlanceAppWidget.uiDefinition = { RowTest() }
170 
171         mHostRule.startHost()
172 
173         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "rowWidget")
174     }
175 
176     @WithRtl
177     @Test
178     fun createRowWidget_rtl() {
179         TestGlanceAppWidget.uiDefinition = { RowTest() }
180 
181         mHostRule.startHost()
182 
183         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "rowWidget_rtl")
184     }
185 
186     @Test
187     fun checkTextAlignment() {
188         TestGlanceAppWidget.uiDefinition = { TextAlignmentTest() }
189 
190         mHostRule.startHost()
191 
192         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "textAlignment")
193     }
194 
195     @WithRtl
196     @Test
197     fun checkTextAlignment_rtl() {
198         TestGlanceAppWidget.uiDefinition = { TextAlignmentTest() }
199 
200         mHostRule.startHost()
201 
202         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "textAlignment_rtl")
203     }
204 
205     @Test
206     fun checkBackgroundColor_light() {
207         TestGlanceAppWidget.uiDefinition = { BackgroundTest() }
208 
209         mHostRule.startHost()
210 
211         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "backgroundColor")
212     }
213 
214     @Test
215     @WithNightMode
216     fun checkBackgroundColor_dark() {
217         TestGlanceAppWidget.uiDefinition = { BackgroundTest() }
218 
219         mHostRule.startHost()
220 
221         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "backgroundColor_dark")
222     }
223 
224     @Test
225     fun checkTextColor_light() {
226         TestGlanceAppWidget.uiDefinition = { TextColorTest() }
227 
228         mHostRule.startHost()
229 
230         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "textColor")
231     }
232 
233     @Test
234     @WithNightMode
235     fun checkTextColor_dark() {
236         TestGlanceAppWidget.uiDefinition = { TextColorTest() }
237 
238         mHostRule.startHost()
239 
240         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "textColor_dark")
241     }
242 
243     @Test
244     fun checkButtonRoundedCorners_light() {
245         TestGlanceAppWidget.uiDefinition = { RoundedButtonScreenshotTest() }
246 
247         mHostRule.startHost()
248 
249         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "roundedButton_light")
250     }
251 
252     @Test
253     @WithNightMode
254     fun checkButtonRoundedCorners_dark() {
255         TestGlanceAppWidget.uiDefinition = { RoundedButtonScreenshotTest() }
256 
257         mHostRule.startHost()
258 
259         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "roundedButton_dark")
260     }
261 
262     @Test
263     fun checkButtonTextAlignment() {
264         TestGlanceAppWidget.uiDefinition = {
265             Column(modifier = GlanceModifier.fillMaxSize()) {
266                 Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth()) {
267                     Button(
268                         "Start",
269                         onClick = actionStartActivity<Activity>(),
270                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
271                         colors =
272                             ButtonDefaults.buttonColors(
273                                 backgroundColor = ColorProvider(Color.Transparent),
274                                 contentColor = ColorProvider(Color.DarkGray)
275                             ),
276                         style = TextStyle(textAlign = TextAlign.Start)
277                     )
278                     Button(
279                         "End",
280                         onClick = actionStartActivity<Activity>(),
281                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
282                         colors =
283                             ButtonDefaults.buttonColors(
284                                 backgroundColor = ColorProvider(Color.Transparent),
285                                 contentColor = ColorProvider(Color.DarkGray)
286                             ),
287                         style = TextStyle(textAlign = TextAlign.End)
288                     )
289                 }
290                 Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth()) {
291                     CheckBox(
292                         checked = false,
293                         onCheckedChange = null,
294                         text = "Start",
295                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
296                         style = TextStyle(textAlign = TextAlign.Start)
297                     )
298                     CheckBox(
299                         checked = true,
300                         onCheckedChange = null,
301                         text = "End",
302                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
303                         style = TextStyle(textAlign = TextAlign.End)
304                     )
305                 }
306                 Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth()) {
307                     Switch(
308                         checked = false,
309                         onCheckedChange = null,
310                         text = "Start",
311                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
312                         style = TextStyle(textAlign = TextAlign.Start)
313                     )
314                     Switch(
315                         checked = true,
316                         onCheckedChange = null,
317                         text = "End",
318                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
319                         style = TextStyle(textAlign = TextAlign.End)
320                     )
321                 }
322                 Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth()) {
323                     RadioButton(
324                         checked = false,
325                         onClick = null,
326                         text = "Start",
327                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
328                         style = TextStyle(textAlign = TextAlign.Start)
329                     )
330                     RadioButton(
331                         checked = true,
332                         onClick = null,
333                         text = "End",
334                         modifier = GlanceModifier.defaultWeight().fillMaxHeight(),
335                         style = TextStyle(textAlign = TextAlign.End)
336                     )
337                 }
338             }
339         }
340 
341         mHostRule.setSizes(DpSize(300.dp, 400.dp))
342         mHostRule.startHost()
343 
344         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "button_text_align")
345     }
346 
347     @Test
348     fun checkFixTopLevelSize() {
349         TestGlanceAppWidget.uiDefinition = {
350             Column(
351                 modifier = GlanceModifier.size(100.dp).background(Color.DarkGray),
352                 horizontalAlignment = Alignment.End,
353             ) {
354                 Text(
355                     "Upper half",
356                     modifier = GlanceModifier.defaultWeight().fillMaxWidth().background(Color.Green)
357                 )
358                 Text(
359                     "Lower right half",
360                     modifier = GlanceModifier.defaultWeight().width(50.dp).background(Color.Cyan)
361                 )
362             }
363         }
364 
365         mHostRule.startHost()
366 
367         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "fixed_top_level_size")
368     }
369 
370     @Test
371     fun checkTopLevelFill() {
372         TestGlanceAppWidget.uiDefinition = {
373             Column(
374                 modifier = GlanceModifier.fillMaxSize().background(Color.DarkGray),
375                 horizontalAlignment = Alignment.End,
376             ) {
377                 Text(
378                     "Upper half",
379                     modifier = GlanceModifier.defaultWeight().fillMaxWidth().background(Color.Green)
380                 )
381                 Text(
382                     "Lower right half",
383                     modifier = GlanceModifier.defaultWeight().width(50.dp).background(Color.Cyan)
384                 )
385             }
386         }
387 
388         mHostRule.startHost()
389 
390         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "fill_top_level_size")
391     }
392 
393     @Test
394     fun checkTopLevelWrap() {
395         TestGlanceAppWidget.uiDefinition = {
396             Column(
397                 modifier = GlanceModifier.wrapContentSize().background(Color.DarkGray),
398                 horizontalAlignment = Alignment.CenterHorizontally,
399             ) {
400                 Text("Above", modifier = GlanceModifier.background(Color.Green))
401                 Text("Larger below", modifier = GlanceModifier.background(Color.Cyan))
402             }
403         }
404 
405         mHostRule.startHost()
406 
407         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "wrap_top_level_size")
408     }
409 
410     @Test
411     fun drawableBackground() {
412         TestGlanceAppWidget.uiDefinition = {
413             Box(
414                 modifier = GlanceModifier.fillMaxSize().background(Color.Green).padding(8.dp),
415                 contentAlignment = Alignment.Center
416             ) {
417                 Text(
418                     "Some useful text",
419                     modifier =
420                         GlanceModifier.fillMaxWidth()
421                             .height(220.dp)
422                             .background(ImageProvider(R.drawable.filled_oval))
423                 )
424             }
425         }
426 
427         mHostRule.startHost()
428 
429         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "drawable_background")
430     }
431 
432     @Test
433     fun drawableBackgroundWithColorAndAlpha() {
434         TestGlanceAppWidget.uiDefinition = {
435             Box(
436                 modifier = GlanceModifier.fillMaxSize().background(Color.Green).padding(8.dp),
437                 contentAlignment = Alignment.Center
438             ) {
439                 Text(
440                     text = "Some useful text",
441                     modifier =
442                         GlanceModifier.fillMaxWidth()
443                             .height(220.dp)
444                             .background(
445                                 ImageProvider(R.drawable.filled_oval),
446                                 colorFilter = ColorFilter.tint(GlanceTheme.colors.secondary),
447                                 alpha = 0.5f
448                             )
449                 )
450             }
451         }
452 
453         mHostRule.startHost()
454 
455         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "drawable_background_color_alpha")
456     }
457 
458     @Test
459     fun drawableFitBackground() {
460         TestGlanceAppWidget.uiDefinition = {
461             Box(
462                 modifier = GlanceModifier.fillMaxSize().background(Color.Green).padding(8.dp),
463                 contentAlignment = Alignment.Center
464             ) {
465                 Text(
466                     "Some useful text",
467                     modifier =
468                         GlanceModifier.fillMaxWidth()
469                             .height(220.dp)
470                             .background(
471                                 ImageProvider(R.drawable.filled_oval),
472                                 contentScale = ContentScale.Fit
473                             )
474                 )
475             }
476         }
477 
478         mHostRule.startHost()
479 
480         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "drawable_fit_background")
481     }
482 
483     @Test
484     fun bitmapBackground() {
485         TestGlanceAppWidget.uiDefinition = {
486             val context = LocalContext.current
487             val bitmap = context.resources.getDrawable(R.drawable.compose, null) as BitmapDrawable
488             Box(
489                 modifier = GlanceModifier.fillMaxSize().background(Color.Green).padding(8.dp),
490                 contentAlignment = Alignment.Center
491             ) {
492                 Text(
493                     "Some useful text",
494                     modifier =
495                         GlanceModifier.fillMaxWidth()
496                             .height(220.dp)
497                             .background(ImageProvider(bitmap.bitmap!!))
498                 )
499             }
500         }
501 
502         mHostRule.startHost()
503 
504         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "bitmap_background")
505     }
506 
507     @Test
508     fun alignment() {
509         TestGlanceAppWidget.uiDefinition = {
510             Row(
511                 modifier = GlanceModifier.fillMaxSize(),
512                 horizontalAlignment = Alignment.End,
513                 verticalAlignment = Alignment.Bottom,
514             ) {
515                 Text("##")
516                 Column(
517                     horizontalAlignment = Alignment.CenterHorizontally,
518                     verticalAlignment = Alignment.CenterVertically,
519                     modifier = GlanceModifier.fillMaxHeight(),
520                 ) {
521                     Box(
522                         contentAlignment = Alignment.Center,
523                         modifier = GlanceModifier.height(80.dp),
524                     ) {
525                         Text("Center")
526                     }
527                     Box(
528                         contentAlignment = Alignment.Center,
529                         modifier = GlanceModifier.height(80.dp),
530                     ) {
531                         Text("BottomCenter")
532                     }
533                     Box(
534                         contentAlignment = Alignment.Center,
535                         modifier = GlanceModifier.height(80.dp),
536                     ) {
537                         Text("CenterStart")
538                     }
539                 }
540                 Column(
541                     horizontalAlignment = Alignment.CenterHorizontally,
542                     verticalAlignment = Alignment.CenterVertically,
543                     modifier = GlanceModifier.fillMaxHeight(),
544                 ) {
545                     Box(contentAlignment = Alignment.Center) {
546                         Image(
547                             ImageProvider(R.drawable.compose),
548                             "Compose",
549                             modifier = GlanceModifier.size(80.dp),
550                         )
551                         Text("OXO", style = TextStyle(fontSize = 18.sp))
552                     }
553                     Box(contentAlignment = Alignment.BottomCenter) {
554                         Image(
555                             ImageProvider(R.drawable.compose),
556                             "Compose",
557                             modifier = GlanceModifier.size(80.dp),
558                         )
559                         Text("OXO", style = TextStyle(fontSize = 18.sp))
560                     }
561                     Box(contentAlignment = Alignment.CenterStart) {
562                         Image(
563                             ImageProvider(R.drawable.compose),
564                             "Compose",
565                             modifier = GlanceModifier.size(80.dp),
566                         )
567                         Text("OXO", style = TextStyle(fontSize = 18.sp))
568                     }
569                 }
570             }
571         }
572 
573         mHostRule.startHost()
574 
575         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "alignment")
576     }
577 
578     @Test
579     fun lazyColumn_alignment_end() = runTest {
580         val count = 5
581         TestGlanceAppWidget.uiDefinition = {
582             LazyColumnAlignmentTest(count = count, horizontalAlignment = Alignment.End)
583         }
584 
585         mHostRule.startHost()
586 
587         mHostRule.waitForListViewChildCount(count)
588         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "lazyColumn_alignment_end")
589     }
590 
591     @Test
592     fun lazyColumn_alignment_center() = runTest {
593         val count = 5
594         TestGlanceAppWidget.uiDefinition = {
595             LazyColumnAlignmentTest(
596                 count = count,
597                 horizontalAlignment = Alignment.CenterHorizontally
598             )
599         }
600 
601         mHostRule.startHost()
602 
603         mHostRule.waitForListViewChildCount(count)
604         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "lazyColumn_alignment_center")
605     }
606 
607     @Test
608     fun lazyColumn_alignment_start() = runTest {
609         val count = 5
610         TestGlanceAppWidget.uiDefinition = {
611             LazyColumnAlignmentTest(count = count, horizontalAlignment = Alignment.Start)
612         }
613 
614         mHostRule.startHost()
615 
616         mHostRule.waitForListViewChildCount(count)
617         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "lazyColumn_alignment_start")
618     }
619 
620     @Test
621     fun linearProgressIndicator_colors() {
622         TestGlanceAppWidget.uiDefinition = { LinearProgressIndicatorColorsTest() }
623 
624         mHostRule.startHost()
625 
626         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "linearProgressIndicator_colors")
627     }
628 
629     @Test
630     fun buttonTests_createFilledButton() {
631         TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.FilledButtonTest() }
632         mHostRule.startHost()
633         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createFilledButton")
634     }
635 
636     @Test
637     fun buttonTests_createOutlineButton() {
638         TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.OutlineButtonTest() }
639         mHostRule.startHost()
640         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createOutlineButton")
641     }
642 
643     @Test
644     fun buttonTests_createSquareButton() {
645         TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.SquareButtonTest() }
646         mHostRule.startHost()
647         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createSquareButton")
648     }
649 
650     @Test
651     fun buttonTests_createCircleButton() {
652         TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.CircleButtonTest() }
653         mHostRule.startHost()
654         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createCircleButton")
655     }
656 
657     @Test
658     fun buttonTests_buttonDefaultColors() {
659         TestGlanceAppWidget.uiDefinition = {
660             ButtonComponentsScreenshotTests.ButtonDefaultColorsTest()
661         }
662         mHostRule.startHost()
663         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_buttonDefaultColors")
664     }
665 
666     /**
667      * Button should ignore [androidx.glance.BackgroundModifier]. It does not support background
668      * images, and background color should be set via [androidx.glance.ButtonColors].
669      */
670     @Test
671     fun buttonTests_buttonShouldIgnoreBackgroundModifiers() {
672         @Composable
673         fun Ui() {
674             Column {
675                 // the bg image modifier should be stripped.
676                 Button(
677                     text = "Button w/incorrect bg modifier: image",
678                     onClick = {},
679                     modifier =
680                         GlanceModifier.background(
681                             imageProvider = ImageProvider(R.drawable.compose),
682                         ),
683                 )
684                 Spacer(GlanceModifier.size(4.dp))
685 
686                 // Bg modifiers should be stripped
687                 Button(
688                     text = "Button w/incorrect modifiers: Image, Color",
689                     onClick = {},
690                     modifier =
691                         GlanceModifier.background(Color.Cyan)
692                             .background(
693                                 imageProvider = ImageProvider(R.drawable.compose),
694                             ),
695                 )
696                 Spacer(GlanceModifier.size(4.dp))
697 
698                 // Bg color modifier should be stripped.
699                 Button(
700                     text = "Button w/incorrect bg modifier: color",
701                     onClick = {},
702                     modifier = GlanceModifier.background(Color.Cyan),
703                 )
704                 Spacer(GlanceModifier.size(4.dp))
705 
706                 Button(
707                     text = "Button with no modifier",
708                     onClick = {},
709                 )
710                 Spacer(GlanceModifier.size(4.dp))
711                 Button(
712                     text = "Button with proper color",
713                     colors =
714                         ButtonDefaults.buttonColors(
715                             backgroundColor = ColorProvider(Color.Cyan),
716                             contentColor = ColorProvider(Color.Magenta)
717                         ),
718                     onClick = {},
719                 )
720             }
721         }
722 
723         TestGlanceAppWidget.uiDefinition = { Ui() }
724         mHostRule.startHost()
725         mScreenshotRule.checkScreenshot(
726             mHostRule.mHostView,
727             "buttonTests_buttonIgnoresBgImageModifier"
728         )
729     }
730 
731     @Test
732     fun topbarTests_createBarWithIconTextTwoActions() {
733         TestGlanceAppWidget.uiDefinition = { TopBarUi() }
734         mHostRule.startHost()
735         mScreenshotRule.checkScreenshot(
736             mHostRule.mHostView,
737             "topbarTests_createBarWithIconTextTwoActions"
738         )
739     }
740 
741     @WithRtl
742     @Test
743     fun topbarTests_createBarWithIconTextTwoActions_rtl() {
744         TestGlanceAppWidget.uiDefinition = { TopBarUi() }
745         mHostRule.startHost()
746         mScreenshotRule.checkScreenshot(
747             mHostRule.mHostView,
748             "topbarTests_createBarWithIconTextTwoActions_rtl"
749         )
750     }
751 
752     @Composable
753     private fun TopBarUi() {
754         val fg = ColorProvider(Color.Magenta)
755         val bg = ColorProvider(Color.Cyan)
756 
757         val actionButton =
758             @Composable {
759                 CircleIconButton(
760                     imageProvider = ImageProvider(R.drawable.filled_oval),
761                     contentDescription = null,
762                     backgroundColor = null,
763                     contentColor = fg,
764                     onClick = {}
765                 )
766             }
767 
768         Box(GlanceModifier.padding(4.dp).background(Color.Black)) {
769             TitleBar(
770                 modifier = GlanceModifier.background(bg).fillMaxWidth(),
771                 startIcon = ImageProvider(R.drawable.filled_oval),
772                 title = "Lead icon; title; 2 btns",
773                 textColor = fg,
774                 iconColor = fg,
775                 actions = {
776                     actionButton()
777                     Spacer(GlanceModifier.size(8.dp))
778                     actionButton()
779                 }
780             )
781         }
782     }
783 
784     @Test
785     fun scaffoldTests_basicScaffold() {
786         TestGlanceAppWidget.uiDefinition = { ScaffoldTestUi.BasicScaffold() }
787         mHostRule.startHost()
788         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "scaffoldTests_basicScaffold")
789     }
790 
791     @Test
792     fun scaffoldTests_scaffoldWithPaddingOverride() {
793         TestGlanceAppWidget.uiDefinition = { ScaffoldTestUi.PaddedScaffold() }
794         mHostRule.startHost()
795         mScreenshotRule.checkScreenshot(
796             mHostRule.mHostView,
797             "scaffoldTests_scaffoldWithPaddingOverride"
798         )
799     }
800 }
801 
802 private object ScaffoldTestUi {
803     private val fg = ColorProvider(Color.Magenta)
804     private val bg = ColorProvider(Color.Cyan)
805     private val widgetBg = ColorProvider(Color.Yellow)
806 
807     @Composable
BasicScaffoldnull808     fun BasicScaffold() {
809         Scaffold(
810             titleBar = { ScaffoldTitleBar() },
811             modifier = GlanceModifier,
812             backgroundColor = widgetBg,
813             content = { ScaffoldContent() }
814         )
815     }
816 
817     @Composable
PaddedScaffoldnull818     fun PaddedScaffold() {
819         Scaffold(
820             titleBar = { ScaffoldTitleBar() },
821             modifier = GlanceModifier,
822             backgroundColor = widgetBg,
823             horizontalPadding = 32.dp,
824             content = { ScaffoldContent() }
825         )
826     }
827 
828     @Composable
ScaffoldTitleBarnull829     private fun ScaffoldTitleBar() {
830         TitleBar(
831             modifier = GlanceModifier.background(bg).fillMaxWidth(),
832             startIcon = ImageProvider(R.drawable.filled_oval),
833             title = "Lead icon; title; no btns",
834             textColor = fg,
835             iconColor = fg
836         )
837     }
838 
839     @Composable
ScaffoldContentnull840     private fun ScaffoldContent() {
841         Text(
842             text = "text, fill-max-size\nbg cyan, fg magenta",
843             modifier = GlanceModifier.fillMaxSize()
844         )
845     }
846 }
847 
848 @Composable
LazyColumnAlignmentTestnull849 private fun LazyColumnAlignmentTest(count: Int, horizontalAlignment: Alignment.Horizontal) {
850     val columnColors = listOf(Color(0xffffdbcd), Color(0xff7d2d00))
851     val textBgColors = listOf(Color(0xffa33e00), Color(0xffffb596))
852 
853     LazyColumn(
854         modifier = GlanceModifier.fillMaxSize().background(columnColors[0], columnColors[1]),
855         horizontalAlignment = horizontalAlignment
856     ) {
857         items(count) { index ->
858             Text(
859                 text = "item $index",
860                 modifier =
861                     GlanceModifier.padding(horizontal = 16.dp, vertical = 8.dp)
862                         .background(textBgColors[0], textBgColors[1])
863             )
864         }
865     }
866 }
867 
868 @Composable
TextAlignmentTestnull869 private fun TextAlignmentTest() {
870     Column(modifier = GlanceModifier.fillMaxWidth()) {
871         Text(
872             "Center",
873             style = TextStyle(textAlign = TextAlign.Center),
874             modifier = GlanceModifier.fillMaxWidth()
875         )
876         Text(
877             "Left",
878             style = TextStyle(textAlign = TextAlign.Left),
879             modifier = GlanceModifier.fillMaxWidth()
880         )
881         Text(
882             "Right",
883             style = TextStyle(textAlign = TextAlign.Right),
884             modifier = GlanceModifier.fillMaxWidth()
885         )
886         Text(
887             "Start",
888             style = TextStyle(textAlign = TextAlign.Start),
889             modifier = GlanceModifier.fillMaxWidth()
890         )
891         Text(
892             "End",
893             style = TextStyle(textAlign = TextAlign.End),
894             modifier = GlanceModifier.fillMaxWidth()
895         )
896     }
897 }
898 
899 @Composable
RowTestnull900 private fun RowTest() {
901     Row(modifier = GlanceModifier.fillMaxWidth()) {
902         Text(
903             "Start",
904             style = TextStyle(textAlign = TextAlign.Start),
905             modifier = GlanceModifier.defaultWeight()
906         )
907         Text(
908             "Center",
909             style = TextStyle(textAlign = TextAlign.Center),
910             modifier = GlanceModifier.defaultWeight()
911         )
912         Text(
913             "End",
914             style = TextStyle(textAlign = TextAlign.End),
915             modifier = GlanceModifier.defaultWeight()
916         )
917     }
918 }
919 
920 @Composable
BackgroundTestnull921 private fun BackgroundTest() {
922     Column(modifier = GlanceModifier.background(R.color.background_color)) {
923         Text(
924             "100x50 and cyan",
925             modifier = GlanceModifier.width(100.dp).height(50.dp).background(Color.Cyan)
926         )
927         Text(
928             "Transparent background",
929             modifier = GlanceModifier.height(50.dp).background(Color.Transparent)
930         )
931         Text(
932             "wrapx30 and red (light), yellow (dark)",
933             modifier =
934                 GlanceModifier.height(30.dp).background(day = Color.Red, night = Color.Yellow)
935         )
936         Text("Below this should be 4 color boxes")
937         Row(modifier = GlanceModifier.padding(8.dp)) {
938             Box(
939                 modifier =
940                     GlanceModifier.width(32.dp)
941                         .height(32.dp)
942                         .background(day = Color.Black, night = Color.White)
943             ) {}
944             val colors = listOf(Color.Red, Color.Green, Color.Blue)
945             repeat(3) {
946                 Box(modifier = GlanceModifier.width(8.dp).height(1.dp)) {}
947                 Box(modifier = GlanceModifier.width(32.dp).height(32.dp).background(colors[it])) {}
948             }
949         }
950     }
951 }
952 
953 @Composable
TextColorTestnull954 private fun TextColorTest() {
955     Column(modifier = GlanceModifier.background(R.color.background_color)) {
956         Text("Cyan", style = TextStyle(color = ColorProvider(Color.Cyan)))
957         Text(
958             "Red (light) or yellow (dark)",
959             style = TextStyle(color = ColorProvider(day = Color.Red, night = Color.Yellow))
960         )
961         Text(
962             "Resource (inverse of background color)",
963             style = TextStyle(color = ColorProvider(R.color.text_color))
964         )
965     }
966 }
967 
968 @Composable
RoundedButtonScreenshotTestnull969 private fun RoundedButtonScreenshotTest() {
970     val columnColors = listOf(Color(0xffffdbcd), Color(0xff7d2d00))
971     val buttonBgColors = listOf(Color(0xffa33e00), Color(0xffffb596))
972     val buttonTextColors = listOf(Color(0xffffffff), Color(0xff581e00))
973 
974     Column(
975         modifier =
976             GlanceModifier.padding(10.dp).background(day = columnColors[0], night = columnColors[1])
977     ) {
978         Button(
979             "Button with textAlign = Start",
980             onClick = actionStartActivity<Activity>(),
981             colors =
982                 ButtonDefaults.buttonColors(
983                     backgroundColor =
984                         ColorProvider(day = buttonBgColors[0], night = buttonBgColors[1]),
985                     contentColor =
986                         ColorProvider(day = buttonTextColors[0], night = buttonTextColors[1])
987                 ),
988             style = TextStyle(textAlign = TextAlign.Start)
989         )
990         Spacer(modifier = GlanceModifier.height(5.dp).fillMaxWidth())
991         Button(
992             "Button with textAlign = Center and padding (30dp, 30dp)",
993             onClick = actionStartActivity<Activity>(),
994             modifier = GlanceModifier.padding(horizontal = 30.dp, vertical = 30.dp),
995             colors =
996                 ButtonDefaults.buttonColors(
997                     backgroundColor =
998                         ColorProvider(day = buttonBgColors[0], night = buttonBgColors[1]),
999                     contentColor =
1000                         ColorProvider(day = buttonTextColors[0], night = buttonTextColors[1])
1001                 ),
1002             style = TextStyle(textAlign = TextAlign.Center)
1003         )
1004         Spacer(modifier = GlanceModifier.height(5.dp).fillMaxWidth())
1005         Button(
1006             "Button with textAlign = End",
1007             onClick = actionStartActivity<Activity>(),
1008             colors =
1009                 ButtonDefaults.buttonColors(
1010                     backgroundColor =
1011                         ColorProvider(day = buttonBgColors[0], night = buttonBgColors[1]),
1012                     contentColor =
1013                         ColorProvider(day = buttonTextColors[0], night = buttonTextColors[1])
1014                 ),
1015             style = TextStyle(textAlign = TextAlign.End)
1016         )
1017     }
1018 }
1019 
1020 @Composable
CheckBoxScreenshotTestnull1021 private fun CheckBoxScreenshotTest() {
1022     Column(modifier = GlanceModifier.background(day = Color.White, night = Color.Black)) {
1023         CheckBox(
1024             checked = true,
1025             onCheckedChange = null,
1026             text =
1027                 "Hello Checked Checkbox (text: day=black, night=white| box: day=magenta, " +
1028                     "night=yellow)",
1029             style =
1030                 TextStyle(
1031                     color = ColorProvider(day = Color.Black, night = Color.White),
1032                     fontWeight = FontWeight.Bold,
1033                     fontStyle = FontStyle.Normal,
1034                 ),
1035             colors =
1036                 CheckboxDefaults.colors(
1037                     checkedColor = ColorProvider(day = Color.Magenta, night = Color.Yellow),
1038                     uncheckedColor = ColorProvider(day = Color.Black, night = Color.Gray)
1039                 )
1040         )
1041 
1042         CheckBox(
1043             checked = false,
1044             onCheckedChange = null,
1045             text = "Hello Unchecked Checkbox (text: day=dark gray, night=light gray, green box)",
1046             style =
1047                 TextStyle(
1048                     color = ColorProvider(day = Color.DarkGray, night = Color.LightGray),
1049                     textDecoration = TextDecoration.Underline,
1050                     fontWeight = FontWeight.Medium,
1051                     fontStyle = FontStyle.Italic,
1052                 ),
1053             colors = CheckboxDefaults.colors(checkedColor = Color.Red, uncheckedColor = Color.Green)
1054         )
1055     }
1056 }
1057 
1058 @Composable
SwitchTestnull1059 private fun SwitchTest() {
1060     Column(modifier = GlanceModifier.background(day = Color.White, night = Color.Black)) {
1061         Switch(
1062             checked = true,
1063             onCheckedChange = null,
1064             text = "Hello Checked Switch (day: Blue/Green, night: Red/Yellow)",
1065             style =
1066                 TextStyle(
1067                     color = ColorProvider(day = Color.Black, night = Color.White),
1068                     fontWeight = FontWeight.Bold,
1069                     fontStyle = FontStyle.Normal,
1070                 ),
1071             colors =
1072                 SwitchDefaults.colors(
1073                     checkedThumbColor = ColorProvider(day = Color.Blue, night = Color.Red),
1074                     uncheckedThumbColor = ColorProvider(Color.Magenta),
1075                     checkedTrackColor = ColorProvider(day = Color.Green, night = Color.Yellow),
1076                     uncheckedTrackColor = ColorProvider(Color.Magenta)
1077                 )
1078         )
1079 
1080         Switch(
1081             checked = false,
1082             onCheckedChange = null,
1083             text = "Hello Unchecked Switch. day: thumb magenta / track cyan, night: thumb cyan",
1084             style =
1085                 TextStyle(
1086                     color = ColorProvider(day = Color.Black, night = Color.White),
1087                     textDecoration = TextDecoration.Underline,
1088                     fontWeight = FontWeight.Medium,
1089                     fontStyle = FontStyle.Italic,
1090                 ),
1091             colors =
1092                 SwitchDefaults.colors(
1093                     checkedThumbColor = ColorProvider(Color.Blue),
1094                     uncheckedThumbColor = ColorProvider(day = Color.Magenta, night = Color.Cyan),
1095                     checkedTrackColor = ColorProvider(Color.Blue),
1096                     uncheckedTrackColor = ColorProvider(day = Color.Cyan, night = Color.Magenta)
1097                 )
1098         )
1099     }
1100 }
1101 
1102 @Composable
RadioButtonScreenshotTestnull1103 private fun RadioButtonScreenshotTest() {
1104     Column(modifier = GlanceModifier.background(day = Color.White, night = Color.Black)) {
1105         RadioButton(
1106             checked = true,
1107             onClick = null,
1108             text =
1109                 "Hello Checked Radio (text: day=black, night=white| radio: day=magenta, " +
1110                     "night=yellow)",
1111             style =
1112                 TextStyle(
1113                     color = ColorProvider(day = Color.Black, night = Color.White),
1114                     fontWeight = FontWeight.Bold,
1115                     fontStyle = FontStyle.Normal,
1116                 ),
1117             colors =
1118                 RadioButtonDefaults.colors(
1119                     checkedColor = ColorProvider(day = Color.Magenta, night = Color.Yellow),
1120                     uncheckedColor = ColorProvider(day = Color.Yellow, night = Color.Magenta)
1121                 )
1122         )
1123 
1124         RadioButton(
1125             checked = false,
1126             onClick = null,
1127             text = "Hello Unchecked Radio (text: day=dark gray, night=light gray| radio: green)",
1128             style =
1129                 TextStyle(
1130                     color = ColorProvider(day = Color.DarkGray, night = Color.LightGray),
1131                     textDecoration = TextDecoration.Underline,
1132                     fontWeight = FontWeight.Medium,
1133                     fontStyle = FontStyle.Italic,
1134                 ),
1135             colors =
1136                 RadioButtonDefaults.colors(checkedColor = Color.Red, uncheckedColor = Color.Green)
1137         )
1138     }
1139 }
1140 
1141 @Composable
LinearProgressIndicatorColorsTestnull1142 private fun LinearProgressIndicatorColorsTest() {
1143     Column(
1144         modifier =
1145             GlanceModifier.padding(16.dp)
1146                 .background(colorProvider = GlanceTheme.colors.widgetBackground)
1147     ) {
1148         Text("Default colors")
1149         LinearProgressIndicator(progress = 0.5f)
1150         Spacer(modifier = GlanceModifier.height(8.dp))
1151         Text("Colors set to theme")
1152         LinearProgressIndicator(
1153             progress = 0.5f,
1154             color = GlanceTheme.colors.onTertiary,
1155             backgroundColor = GlanceTheme.colors.tertiary
1156         )
1157         Spacer(modifier = GlanceModifier.height(8.dp))
1158         Text("Fixed colors")
1159         LinearProgressIndicator(
1160             progress = 0.5f,
1161             color = ColorProvider(Color.Green),
1162             backgroundColor = ColorProvider(Color.Gray)
1163         )
1164     }
1165 }
1166 
1167 // Tests the opinionated button components
1168 private object ButtonComponentsScreenshotTests {
1169 
<lambda>null1170     private val onClick: () -> Unit = {}
1171 
1172     // a filled square
1173     private val icon = ImageProvider(R.drawable.filled_oval)
1174 
1175     private val buttonBg = ColorProvider(Color.Black)
1176     private val buttonFg = ColorProvider(Color.White)
1177 
1178     @Composable
colorsnull1179     private fun colors() =
1180         ButtonDefaults.buttonColors(backgroundColor = buttonBg, contentColor = buttonFg)
1181 
1182     @Composable private fun Space() = Spacer(GlanceModifier.size(16.dp))
1183 
1184     /** A rectangular magenta background */
1185     @Composable
1186     private fun Background(content: @Composable () -> Unit) {
1187         Box(
1188             modifier = GlanceModifier.wrapContentSize().padding(16.dp).background(Color.Magenta),
1189             content = content
1190         )
1191     }
1192 
1193     @Composable
FilledButtonTestnull1194     fun FilledButtonTest() {
1195         Background {
1196             Column {
1197                 FilledButton(
1198                     text = "Filled button\nbg 0x00, fg 0xff",
1199                     onClick = onClick,
1200                     icon = null,
1201                     colors = colors()
1202                 )
1203                 Space()
1204                 FilledButton(
1205                     text = "Filled btn + icon\nbg 0x00, fg 0xff",
1206                     onClick = onClick,
1207                     icon = icon,
1208                     colors = colors()
1209                 )
1210             }
1211         }
1212     }
1213 
1214     @Composable
OutlineButtonTestnull1215     fun OutlineButtonTest() {
1216         Background {
1217             Column {
1218                 OutlineButton(
1219                     text = "Outline Button\nfg 0xff",
1220                     onClick = onClick,
1221                     icon = null,
1222                     contentColor = buttonFg
1223                 )
1224                 Space()
1225                 OutlineButton(
1226                     text = "Outline btn + icon\nfg 0xff",
1227                     onClick = onClick,
1228                     icon = icon,
1229                     contentColor = buttonFg
1230                 )
1231             }
1232         }
1233     }
1234 
1235     @Composable
SquareButtonTestnull1236     fun SquareButtonTest() {
1237         Background {
1238             // square button with rounded corners, icon (a square w/sharp corners)  in center.
1239             SquareIconButton(
1240                 imageProvider = icon,
1241                 contentDescription = null,
1242                 onClick = onClick,
1243                 backgroundColor = buttonBg,
1244                 contentColor = buttonFg
1245             )
1246         }
1247     }
1248 
1249     @Composable
CircleButtonTestnull1250     fun CircleButtonTest() {
1251         Background {
1252             Column {
1253                 // Circle button with icon
1254                 CircleIconButton(
1255                     imageProvider = icon,
1256                     contentDescription = null,
1257                     onClick = onClick,
1258                     backgroundColor = buttonBg,
1259                     contentColor = buttonFg
1260                 )
1261                 Space()
1262                 // Icon only, no background
1263                 CircleIconButton(
1264                     imageProvider = icon,
1265                     contentDescription = null,
1266                     onClick = onClick,
1267                     backgroundColor = null,
1268                     contentColor = buttonFg
1269                 )
1270             }
1271         }
1272     }
1273 
1274     /** Tests that buttons inherit the expected colors from their theme. */
1275     @Composable
ButtonDefaultColorsTestnull1276     fun ButtonDefaultColorsTest() {
1277         val unused = ColorProvider(Color.Cyan)
1278 
1279         val colors =
1280             colorProviders(
1281                 primary = ColorProvider(Color.Green),
1282                 onPrimary = ColorProvider(Color.Black),
1283                 surface = ColorProvider(Color.Gray),
1284                 onSurface = ColorProvider(Color.Red),
1285                 background = ColorProvider(Color.DarkGray),
1286                 error = unused,
1287                 errorContainer = unused,
1288                 inverseOnSurface = unused,
1289                 inversePrimary = unused,
1290                 inverseSurface = unused,
1291                 onBackground = unused,
1292                 onError = unused,
1293                 onErrorContainer = unused,
1294                 onPrimaryContainer = unused,
1295                 onSecondary = unused,
1296                 onSecondaryContainer = unused,
1297                 onSurfaceVariant = unused,
1298                 onTertiary = unused,
1299                 onTertiaryContainer = unused,
1300                 outline = unused,
1301                 primaryContainer = unused,
1302                 secondary = unused,
1303                 secondaryContainer = unused,
1304                 surfaceVariant = unused,
1305                 tertiary = unused,
1306                 tertiaryContainer = unused,
1307             )
1308 
1309         GlanceTheme(colors = colors) {
1310             Column {
1311                 FilledButton("Filled button", icon = icon, onClick = onClick)
1312                 // [OutlineButton] does not have a default color, so not important to test here
1313                 Space()
1314                 SquareIconButton(imageProvider = icon, contentDescription = null, onClick = onClick)
1315                 Space()
1316                 CircleIconButton(imageProvider = icon, contentDescription = null, onClick = onClick)
1317             }
1318         }
1319     }
1320 }
1321