@blotch/dolan 22.2.3 → 22.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/commands/add.js +9 -1
  2. package/dist/commands/add.js.map +1 -1
  3. package/dist/commands/add.test.js +38 -3
  4. package/dist/commands/add.test.js.map +1 -1
  5. package/dist/commands/doctor.js +258 -21
  6. package/dist/commands/doctor.js.map +1 -1
  7. package/dist/commands/doctor.test.js +425 -11
  8. package/dist/commands/doctor.test.js.map +1 -1
  9. package/dist/commands/init.d.ts +2 -2
  10. package/dist/commands/init.js +38 -18
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/init.test.js +105 -2
  13. package/dist/commands/init.test.js.map +1 -1
  14. package/dist/commands/theme.js +1 -1
  15. package/dist/commands/theme.js.map +1 -1
  16. package/dist/commands/update.d.ts +7 -0
  17. package/dist/commands/update.js +194 -50
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/commands/update.test.js +570 -3
  20. package/dist/commands/update.test.js.map +1 -1
  21. package/dist/lockfile.d.ts +17 -0
  22. package/dist/lockfile.js +56 -1
  23. package/dist/lockfile.js.map +1 -1
  24. package/dist/lockfile.test.d.ts +1 -0
  25. package/dist/lockfile.test.js +65 -0
  26. package/dist/lockfile.test.js.map +1 -0
  27. package/dist/registry.d.ts +6 -0
  28. package/dist/registry.js +119 -11
  29. package/dist/registry.js.map +1 -1
  30. package/dist/registry.test.js +61 -6
  31. package/dist/registry.test.js.map +1 -1
  32. package/dist/test-utils.d.ts +27 -0
  33. package/dist/test-utils.js +45 -1
  34. package/dist/test-utils.js.map +1 -1
  35. package/dist/testing/fire-lynx-event.d.ts +14 -0
  36. package/dist/testing/fire-lynx-event.js +19 -0
  37. package/dist/testing/fire-lynx-event.js.map +1 -0
  38. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  39. package/dist/testing/fire-lynx-event.test.js +44 -0
  40. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  41. package/dist/testing/lynx-main-thread.d.ts +22 -0
  42. package/dist/testing/lynx-main-thread.js +11 -0
  43. package/dist/testing/lynx-main-thread.js.map +1 -0
  44. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  45. package/dist/testing/lynx-main-thread.test.js +24 -0
  46. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  47. package/dist/testing/set-input-signal.d.ts +5 -4
  48. package/dist/testing/set-input-signal.js +5 -4
  49. package/dist/testing/set-input-signal.js.map +1 -1
  50. package/dist/ui/boolean-inputs.test.ts +76 -0
  51. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  52. package/dist/ui/components/accordion/accordion.ts +19 -14
  53. package/dist/ui/components/action-sheet/action-sheet.test.ts +637 -183
  54. package/dist/ui/components/action-sheet/action-sheet.ts +339 -55
  55. package/dist/ui/components/action-sheet/index.ts +1 -0
  56. package/dist/ui/components/alert/alert.test.ts +67 -19
  57. package/dist/ui/components/alert/alert.ts +14 -16
  58. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +740 -129
  59. package/dist/ui/components/alert-dialog/alert-dialog.ts +672 -58
  60. package/dist/ui/components/alert-dialog/index.ts +5 -0
  61. package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
  62. package/dist/ui/components/area-chart/area-chart.ts +3 -2
  63. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  64. package/dist/ui/components/badge/badge.test.ts +57 -23
  65. package/dist/ui/components/badge/badge.ts +14 -15
  66. package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
  67. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +1314 -273
  68. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1046 -119
  69. package/dist/ui/components/bottom-sheet/index.ts +7 -1
  70. package/dist/ui/components/button/button.test.ts +646 -2
  71. package/dist/ui/components/button/button.ts +443 -64
  72. package/dist/ui/components/button/index.ts +2 -0
  73. package/dist/ui/components/button-group/button-group.test.ts +528 -2
  74. package/dist/ui/components/button-group/button-group.ts +428 -70
  75. package/dist/ui/components/button-group/index.ts +3 -0
  76. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
  77. package/dist/ui/components/card/card.test.ts +97 -61
  78. package/dist/ui/components/card/card.ts +2 -1
  79. package/dist/ui/components/chart/chart-presets.test.ts +197 -1
  80. package/dist/ui/components/chart/chart-presets.ts +14 -7
  81. package/dist/ui/components/chart/chart-theme.ts +12 -0
  82. package/dist/ui/components/chart/chart.test.ts +1031 -0
  83. package/dist/ui/components/chart/chart.ts +66 -46
  84. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  85. package/dist/ui/components/checkbox/checkbox.ts +25 -24
  86. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  87. package/dist/ui/components/collapsible/collapsible.ts +21 -15
  88. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  89. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  90. package/dist/ui/components/context-menu/index.ts +1 -0
  91. package/dist/ui/components/dialog/dialog.test.ts +210 -152
  92. package/dist/ui/components/dialog/dialog.ts +35 -29
  93. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  94. package/dist/ui/components/dock/dock-scroll.ts +142 -0
  95. package/dist/ui/components/dock/dock-search.test.ts +594 -0
  96. package/dist/ui/components/dock/dock.test.ts +752 -0
  97. package/dist/ui/components/dock/dock.ts +1438 -0
  98. package/dist/ui/components/dock/index.ts +2 -0
  99. package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
  100. package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
  101. package/dist/ui/components/dynamic-color/index.ts +5 -0
  102. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  103. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  104. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  105. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  106. package/dist/ui/components/glass/glass-design.ts +119 -0
  107. package/dist/ui/components/glass/glass.test.ts +376 -0
  108. package/dist/ui/components/glass/glass.ts +430 -0
  109. package/dist/ui/components/glass/index.ts +19 -0
  110. package/dist/ui/components/icon/icon-theme.test.ts +64 -0
  111. package/dist/ui/components/icon/icon-theme.ts +70 -16
  112. package/dist/ui/components/icon/icon.test.ts +52 -41
  113. package/dist/ui/components/input/input.test.ts +178 -101
  114. package/dist/ui/components/input/input.ts +17 -13
  115. package/dist/ui/components/label/label.test.ts +11 -24
  116. package/dist/ui/components/label/label.ts +8 -2
  117. package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
  118. package/dist/ui/components/line-chart/line-chart.ts +3 -2
  119. package/dist/ui/components/list/list.test.ts +80 -49
  120. package/dist/ui/components/list/list.ts +5 -4
  121. package/dist/ui/components/menu/index.ts +25 -0
  122. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  123. package/dist/ui/components/menu/menu-placement.ts +159 -0
  124. package/dist/ui/components/menu/menu-popup.test.ts +636 -0
  125. package/dist/ui/components/menu/menu-popup.ts +565 -0
  126. package/dist/ui/components/menu/menu.test.ts +523 -0
  127. package/dist/ui/components/menu/menu.ts +455 -0
  128. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
  129. package/dist/ui/components/nav-drawer/nav-drawer.ts +75 -58
  130. package/dist/ui/components/navigation-bar/index.ts +1 -0
  131. package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
  132. package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
  133. package/dist/ui/components/pagination/pagination.test.ts +136 -86
  134. package/dist/ui/components/popover/index.ts +9 -0
  135. package/dist/ui/components/popover/popover-panel.test.ts +735 -0
  136. package/dist/ui/components/popover/popover-panel.ts +603 -0
  137. package/dist/ui/components/popover/popover.test.ts +286 -0
  138. package/dist/ui/components/popover/popover.ts +207 -0
  139. package/dist/ui/components/progress/progress.test.ts +102 -46
  140. package/dist/ui/components/progress/progress.ts +54 -55
  141. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  142. package/dist/ui/components/radio-group/radio-group.ts +26 -25
  143. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
  144. package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
  145. package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
  146. package/dist/ui/components/search-field/index.ts +15 -0
  147. package/dist/ui/components/search-field/keyboard.test.ts +147 -0
  148. package/dist/ui/components/search-field/keyboard.ts +123 -0
  149. package/dist/ui/components/search-field/search-field.test.ts +549 -0
  150. package/dist/ui/components/search-field/search-field.ts +628 -0
  151. package/dist/ui/components/segmented-control/index.ts +8 -0
  152. package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
  153. package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
  154. package/dist/ui/components/select/select.test.ts +138 -78
  155. package/dist/ui/components/select/select.ts +19 -12
  156. package/dist/ui/components/separator/separator.test.ts +18 -17
  157. package/dist/ui/components/sheet/sheet.test.ts +151 -11
  158. package/dist/ui/components/sheet/sheet.ts +68 -5
  159. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  160. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  161. package/dist/ui/components/slider/index.ts +1 -0
  162. package/dist/ui/components/slider/slider.test.ts +695 -0
  163. package/dist/ui/components/slider/slider.ts +901 -0
  164. package/dist/ui/components/spinner/spinner.test.ts +64 -46
  165. package/dist/ui/components/spinner/spinner.ts +16 -19
  166. package/dist/ui/components/switch/index.ts +1 -1
  167. package/dist/ui/components/switch/switch.test.ts +534 -48
  168. package/dist/ui/components/switch/switch.ts +613 -48
  169. package/dist/ui/components/tab-bar/index.ts +1 -0
  170. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  171. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  172. package/dist/ui/components/tabs/index.ts +7 -1
  173. package/dist/ui/components/tabs/tabs.test.ts +484 -150
  174. package/dist/ui/components/tabs/tabs.ts +344 -149
  175. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  176. package/dist/ui/components/textarea/textarea.ts +16 -13
  177. package/dist/ui/components/toast/toast.test.ts +434 -350
  178. package/dist/ui/components/toast/toast.ts +42 -51
  179. package/dist/ui/components/toggle/index.ts +1 -0
  180. package/dist/ui/components/toggle/toggle.test.ts +559 -146
  181. package/dist/ui/components/toggle/toggle.ts +241 -26
  182. package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
  183. package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
  184. package/dist/ui/components/toolbar/index.ts +12 -0
  185. package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
  186. package/dist/ui/components/toolbar/toolbar.ts +535 -0
  187. package/dist/ui/theme/chart-colors.test.ts +8 -3
  188. package/dist/ui/theme/dark.css +38 -2
  189. package/dist/ui/theme/default.css +55 -7
  190. package/dist/ui/theme/icon-colors.test.ts +8 -3
  191. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  192. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  193. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  194. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  195. package/dist/ui/theme/tailwind-plugin.ts +93 -29
  196. package/dist/ui/theme/tailwind.css +113 -0
  197. package/dist/ui/utils/animate.test.ts +13 -1
  198. package/dist/utils/analyze.d.ts +19 -0
  199. package/dist/utils/analyze.js +16 -0
  200. package/dist/utils/analyze.js.map +1 -1
  201. package/dist/utils/analyze.test.js +14 -1
  202. package/dist/utils/analyze.test.js.map +1 -1
  203. package/dist/utils/installed.d.ts +25 -1
  204. package/dist/utils/installed.js +33 -0
  205. package/dist/utils/installed.js.map +1 -1
  206. package/dist/utils/installed.test.js +55 -1
  207. package/dist/utils/installed.test.js.map +1 -1
  208. package/dist/utils/resolve-paths.d.ts +4 -4
  209. package/dist/utils/resolve-paths.js +5 -7
  210. package/dist/utils/resolve-paths.js.map +1 -1
  211. package/dist/utils/resolve-paths.test.js +9 -5
  212. package/dist/utils/resolve-paths.test.js.map +1 -1
  213. package/dist/utils/tailwind.d.ts +45 -0
  214. package/dist/utils/tailwind.js +90 -0
  215. package/dist/utils/tailwind.js.map +1 -0
  216. package/dist/utils/tailwind.test.d.ts +1 -0
  217. package/dist/utils/tailwind.test.js +134 -0
  218. package/dist/utils/tailwind.test.js.map +1 -0
  219. package/package.json +9 -1
  220. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
  221. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
  222. package/dist/ui/components/button/button-render.test.ts +0 -132
  223. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  224. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
  225. package/dist/ui/components/chart/chart-render.test.ts +0 -730
  226. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
  227. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
@@ -0,0 +1,376 @@
1
+ import { Component, type Provider, signal, viewChild } from '@angular/core';
2
+ import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
3
+ import { LYNX_ELEMENTS, LynxAccessibilitySettings } from '@blotch/angular-lynx';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { UiIcon } from '../icon/icon';
6
+ import {
7
+ ICON_DARK_COLORS,
8
+ ICON_LIGHT_COLORS,
9
+ provideIconTheme,
10
+ } from '../icon/icon-theme';
11
+ import {
12
+ UiGlass,
13
+ UiGlassGroup,
14
+ type GlassMaterial,
15
+ type GlassShape,
16
+ type GlassVariant,
17
+ } from './glass';
18
+ import { provideGlass } from './glass-design';
19
+
20
+ @Component({
21
+ imports: [LYNX_ELEMENTS, UiGlass, UiGlassGroup, UiIcon],
22
+ template: `
23
+ <blur-view ui-glass-group [spacing]="spacing()" class="flex-row gap-2">
24
+ <blur-view
25
+ ui-glass
26
+ id="glass"
27
+ class="px-4 py-2"
28
+ [variant]="variant()"
29
+ [shape]="shape()"
30
+ [radius]="radius()"
31
+ [tint]="tint()"
32
+ [interactive]="interactive()"
33
+ [material]="material()"
34
+ [glassId]="glassId()"
35
+ >
36
+ <text class="text-glass-foreground">Now playing</text>
37
+ <ui-icon name="play" />
38
+ </blur-view>
39
+ </blur-view>
40
+ `,
41
+ })
42
+ class GlassHost {
43
+ readonly spacing = signal(12);
44
+ readonly variant = signal<GlassVariant>('regular');
45
+ readonly shape = signal<GlassShape>('capsule');
46
+ readonly radius = signal(20);
47
+ readonly tint = signal<string | undefined>(undefined);
48
+ readonly interactive = signal(false);
49
+ readonly material = signal<GlassMaterial>('regular');
50
+ readonly glassId = signal<string | undefined>(undefined);
51
+ readonly glass = viewChild.required(UiGlass);
52
+ }
53
+
54
+ /** Accessibility settings a test can change, as the host app would. */
55
+ const settings = {
56
+ reduceMotion: signal(false),
57
+ reduceTransparency: signal(false),
58
+ increaseContrast: signal(false),
59
+ };
60
+
61
+ /**
62
+ * Renders the host on a platform, as `SystemInfo` reports it, or with no
63
+ * SystemInfo at all.
64
+ */
65
+ const renderOn = async (
66
+ platform: { platform: string; osVersion: string } | undefined,
67
+ providers: Provider[] = [],
68
+ ) => {
69
+ vi.stubGlobal('SystemInfo', platform);
70
+ const result = await render(GlassHost, {
71
+ providers: [
72
+ { provide: LynxAccessibilitySettings, useValue: settings },
73
+ ...providers,
74
+ ],
75
+ });
76
+ const host = result.componentRef.instance as GlassHost;
77
+ const glass = result.container.querySelector('#glass')!;
78
+ const group = glass.parentElement!;
79
+ const update = async (change: () => void) => {
80
+ change();
81
+ await waitForUpdate();
82
+ };
83
+ return { ...result, host, glass, group, update };
84
+ };
85
+
86
+ const IOS_26 = { platform: 'iOS', osVersion: '26.0' };
87
+ const IOS_18 = { platform: 'iOS', osVersion: '18.5' };
88
+ const ANDROID = { platform: 'Android', osVersion: '15' };
89
+ const WEB = { platform: 'web', osVersion: '' };
90
+
91
+ // The icon's selector isn't a Lynx element, so the renderer falls back to a
92
+ // <view> for it and warns.
93
+ beforeEach(() => {
94
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
95
+ });
96
+
97
+ describe('UiGlass', () => {
98
+ afterEach(() => {
99
+ vi.restoreAllMocks();
100
+ vi.unstubAllGlobals();
101
+ settings.reduceTransparency.set(false);
102
+ settings.increaseContrast.set(false);
103
+ });
104
+
105
+ it('draws Liquid Glass on iOS 26, in the app color scheme', async () => {
106
+ const { glass } = await renderOn(IOS_26);
107
+
108
+ expect(glass.tagName.toLowerCase()).toBe('blur-view');
109
+ expect(glass.getAttribute('blur-effect')).toBe('glass');
110
+ expect(glass.getAttribute('glass-style')).toBe('regular');
111
+ expect(glass.getAttribute('blur-radius')).toBe('16px');
112
+ expect(glass.getAttribute('enable-auto-blur')).toBe('true');
113
+ expect(glass.getAttribute('ios-user-interface-style')).toBe('light');
114
+ expect(glass.hasAttribute('glass-tint-color')).toBe(false);
115
+ expect(glass.hasAttribute('glass-interactive')).toBe(false);
116
+ // Liquid Glass has no materials.
117
+ expect(glass.hasAttribute('blur-material')).toBe(false);
118
+ // The app's own classes stay on the glass, beside its shape.
119
+ expect(glass.classList).toContain('px-4');
120
+ expect(glass.classList).toContain('rounded-full');
121
+ expect(glass.classList).not.toContain('bg-secondary');
122
+ });
123
+
124
+ it('pairs the shape under its glass id, or the one a component pairs it under', async () => {
125
+ const { glass, host, update } = await renderOn(IOS_26);
126
+ expect(glass.hasAttribute('glass-effect-id')).toBe(false);
127
+
128
+ await update(() => host.glassId.set('search'));
129
+ expect(glass.getAttribute('glass-effect-id')).toBe('search');
130
+
131
+ // A menu that grows out of the shape pairs it under its own id, until
132
+ // it lets go.
133
+ await update(() => host.glass().pair('ui-menu-1'));
134
+ expect(glass.getAttribute('glass-effect-id')).toBe('ui-menu-1');
135
+ await update(() => host.glass().pair(null));
136
+ expect(glass.getAttribute('glass-effect-id')).toBe('search');
137
+
138
+ await update(() => host.glassId.set(undefined));
139
+ expect(glass.hasAttribute('glass-effect-id')).toBe(false);
140
+ });
141
+
142
+ it('keeps its content inside the glass', async () => {
143
+ const { glass } = await renderOn(IOS_26);
144
+
145
+ expect(glass.querySelector('text')?.textContent).toBe('Now playing');
146
+ });
147
+
148
+ it('takes the variant, tint and interactivity', async () => {
149
+ const { glass, host, update } = await renderOn(IOS_26);
150
+
151
+ await update(() => {
152
+ host.variant.set('clear');
153
+ host.tint.set('#2563eb');
154
+ host.interactive.set(true);
155
+ });
156
+
157
+ expect(glass.getAttribute('glass-style')).toBe('clear');
158
+ expect(glass.getAttribute('blur-radius')).toBe('6px');
159
+ expect(glass.getAttribute('glass-tint-color')).toBe('#2563eb');
160
+ expect(glass.getAttribute('glass-interactive')).toBe('true');
161
+
162
+ await update(() => host.interactive.set(false));
163
+ expect(glass.hasAttribute('glass-interactive')).toBe(false);
164
+ });
165
+
166
+ it("resolves a theme token tint to the scheme's value, and forces the scheme", async () => {
167
+ const scheme = signal<'light' | 'dark'>('light');
168
+ const { glass, host, update } = await renderOn(IOS_26, [
169
+ provideIconTheme({ colorScheme: () => scheme() }),
170
+ ]);
171
+
172
+ await update(() => host.tint.set('var(--glass-tint)'));
173
+ expect(glass.getAttribute('glass-tint-color')).toBe(
174
+ ICON_LIGHT_COLORS['glass-tint'],
175
+ );
176
+
177
+ await update(() => scheme.set('dark'));
178
+ expect(glass.getAttribute('glass-tint-color')).toBe(
179
+ ICON_DARK_COLORS['glass-tint'],
180
+ );
181
+ expect(glass.getAttribute('ios-user-interface-style')).toBe('dark');
182
+ });
183
+
184
+ it('turns the foreground white on clear glass, which sits over media', async () => {
185
+ const { glass, host, update } = await renderOn(IOS_26);
186
+ const icon = () => glass.querySelector('svg')!.getAttribute('content');
187
+
188
+ expect(icon()).toContain(ICON_LIGHT_COLORS['glass-foreground']);
189
+
190
+ await update(() => host.variant.set('clear'));
191
+
192
+ expect(glass.classList).toContain(
193
+ '[--glass-foreground:var(--glass-clear-foreground)]',
194
+ );
195
+ expect(glass.classList).toContain(
196
+ '[--glass-muted-foreground:var(--glass-clear-foreground)]',
197
+ );
198
+ expect(icon()).toContain(ICON_LIGHT_COLORS['glass-clear-foreground']);
199
+ });
200
+
201
+ it('gives a rounded shape its radius, and capsules and circles round ends', async () => {
202
+ const { glass, host, update } = await renderOn(IOS_26);
203
+ const radius = () =>
204
+ (glass as HTMLElement).style.getPropertyValue('border-radius');
205
+
206
+ expect(radius()).toBe('');
207
+
208
+ await update(() => {
209
+ host.shape.set('rounded');
210
+ host.radius.set(24);
211
+ });
212
+ expect(glass.classList).not.toContain('rounded-full');
213
+ expect(radius()).toBe('24px');
214
+
215
+ await update(() => host.shape.set('circle'));
216
+ expect(glass.classList).toContain('rounded-full');
217
+ expect(radius()).toBe('');
218
+ });
219
+
220
+ it('leaves Reduce Transparency and Increase Contrast to iOS, except for muted text', async () => {
221
+ const { glass, update } = await renderOn(IOS_26);
222
+
223
+ await update(() => {
224
+ settings.reduceTransparency.set(true);
225
+ settings.increaseContrast.set(true);
226
+ });
227
+
228
+ // UIKit adapts its own glass: no opaque surface or border here.
229
+ expect(glass.getAttribute('blur-effect')).toBe('glass');
230
+ expect(glass.classList).not.toContain('border');
231
+ expect(glass.classList).toContain(
232
+ '[--glass-muted-foreground:var(--glass-foreground)]',
233
+ );
234
+ });
235
+
236
+ it('draws a frosted material before iOS 26, which has no glass', async () => {
237
+ const scheme = signal<'light' | 'dark'>('light');
238
+ const { glass, host, update } = await renderOn(IOS_18, [
239
+ provideIconTheme({ colorScheme: () => scheme() }),
240
+ ]);
241
+
242
+ // UIKit's material, which the iOS shell's blur-view draws, in the app's
243
+ // scheme. The style before iOS 13 is for hosts without it.
244
+ expect(glass.getAttribute('blur-material')).toBe('regular');
245
+ expect(glass.getAttribute('ios-user-interface-style')).toBe('light');
246
+ expect(glass.getAttribute('blur-effect')).toBe('extra-light');
247
+ expect(glass.getAttribute('blur-radius')).toBe('20px');
248
+
249
+ await update(() => host.material.set('chrome'));
250
+ expect(glass.getAttribute('blur-material')).toBe('chrome');
251
+
252
+ await update(() => scheme.set('dark'));
253
+ expect(glass.getAttribute('ios-user-interface-style')).toBe('dark');
254
+ expect(glass.getAttribute('blur-effect')).toBe('dark');
255
+
256
+ // Clear glass draws the dark material in either scheme.
257
+ await update(() => {
258
+ scheme.set('light');
259
+ host.variant.set('clear');
260
+ });
261
+ expect(glass.getAttribute('blur-material')).toBe('chrome');
262
+ expect(glass.getAttribute('ios-user-interface-style')).toBe('dark');
263
+ expect(glass.getAttribute('blur-effect')).toBe('dark');
264
+ });
265
+
266
+ it('fills a frosted material with its tint, under the content', async () => {
267
+ const { glass, host, update } = await renderOn(IOS_18);
268
+ /**
269
+ * The fill: the view that draws a background, before the content.
270
+ */
271
+ const fill = () =>
272
+ Array.from(glass.children).find(
273
+ (child) => (child as HTMLElement).style.backgroundColor,
274
+ ) as HTMLElement;
275
+
276
+ expect(fill()).toBeUndefined();
277
+
278
+ await update(() => host.tint.set('#2563eb'));
279
+ // Drawn first, so the content sits on it.
280
+ expect(Array.from(glass.children).indexOf(fill())).toBeLessThan(
281
+ Array.from(glass.children).indexOf(glass.querySelector('text')!),
282
+ );
283
+ expect(fill().style.getPropertyValue('background-color')).toBe(
284
+ 'rgb(37, 99, 235)',
285
+ );
286
+ expect(fill().classList).toContain('rounded-full');
287
+
288
+ await update(() => host.shape.set('rounded'));
289
+ expect(fill().classList).not.toContain('rounded-full');
290
+ expect(fill().style.getPropertyValue('border-radius')).toBe('20px');
291
+ });
292
+
293
+ it('draws an opaque Material surface on Android by default', async () => {
294
+ const { glass, group, host, update } = await renderOn(ANDROID);
295
+
296
+ expect(glass.getAttribute('blur-effect')).toBe('light');
297
+ expect(glass.getAttribute('blur-radius')).toBe('0px');
298
+ // A blur-view redraws what's behind it on every frame on Android, even
299
+ // without a radius, unless this is off.
300
+ expect(glass.getAttribute('enable-auto-blur')).toBe('false');
301
+ expect(glass.classList).toContain('bg-secondary');
302
+ expect(glass.hasAttribute('blur-material')).toBe(false);
303
+ expect(group.getAttribute('blur-effect')).toBe('light');
304
+
305
+ // Clear glass is opaque here too, so its foreground stays dark.
306
+ await update(() => host.variant.set('clear'));
307
+ expect(glass.classList).not.toContain(
308
+ '[--glass-foreground:var(--glass-clear-foreground)]',
309
+ );
310
+
311
+ // A tint fills the surface, as Material fills a button.
312
+ await update(() => host.tint.set('#2563eb'));
313
+ expect(glass.classList).not.toContain('bg-secondary');
314
+ expect(
315
+ (glass as HTMLElement).style.getPropertyValue('background-color'),
316
+ ).toBe('rgb(37, 99, 235)');
317
+ expect(
318
+ (glass as HTMLElement).style.getPropertyValue('box-shadow'),
319
+ ).not.toBe('');
320
+ });
321
+
322
+ it('draws glass on Android when the app opts in, and follows the settings there', async () => {
323
+ const { glass, update } = await renderOn(ANDROID, [
324
+ provideGlass({ android: 'glass' }),
325
+ ]);
326
+
327
+ expect(glass.getAttribute('blur-effect')).toBe('glass');
328
+ expect(glass.classList).not.toContain('bg-secondary');
329
+
330
+ await update(() => settings.increaseContrast.set(true));
331
+ expect(glass.classList).toContain('border');
332
+ expect(glass.classList).toContain('border-glass-foreground');
333
+
334
+ await update(() => settings.reduceTransparency.set(true));
335
+ expect(glass.getAttribute('blur-effect')).toBe('light');
336
+ expect(glass.classList).toContain('bg-secondary');
337
+ });
338
+
339
+ it('draws glass in the web preview', async () => {
340
+ const { glass } = await renderOn(WEB);
341
+
342
+ expect(glass.getAttribute('blur-effect')).toBe('glass');
343
+ });
344
+
345
+ it('draws a Material surface where there is no SystemInfo to read', async () => {
346
+ const { glass } = await renderOn(undefined);
347
+
348
+ expect(glass.classList).toContain('bg-secondary');
349
+ });
350
+ });
351
+
352
+ describe('UiGlassGroup', () => {
353
+ afterEach(() => {
354
+ vi.restoreAllMocks();
355
+ vi.unstubAllGlobals();
356
+ });
357
+
358
+ it('merges the glass in it within spacing', async () => {
359
+ const { group, host, update } = await renderOn(IOS_26);
360
+
361
+ expect(group.getAttribute('blur-effect')).toBe('glass-container');
362
+ expect(group.getAttribute('spacing')).toBe('12');
363
+ expect(group.getAttribute('enable-auto-blur')).toBe('false');
364
+ expect(group.classList).toContain('flex-row');
365
+
366
+ await update(() => host.spacing.set(24));
367
+ expect(group.getAttribute('spacing')).toBe('24');
368
+ });
369
+
370
+ it('is a plain container where there is no glass to merge', async () => {
371
+ const { group } = await renderOn(IOS_18);
372
+
373
+ expect(group.getAttribute('blur-effect')).toBe('light');
374
+ expect(group.getAttribute('blur-radius')).toBe('0px');
375
+ });
376
+ });