@blotch/dolan 22.2.2 → 22.2.4

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 (259) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +41 -11
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +162 -9
  5. package/dist/commands/add.test.js.map +1 -1
  6. package/dist/commands/diff.d.ts +2 -1
  7. package/dist/commands/diff.js +38 -25
  8. package/dist/commands/diff.js.map +1 -1
  9. package/dist/commands/diff.test.js +72 -11
  10. package/dist/commands/diff.test.js.map +1 -1
  11. package/dist/commands/doctor.js +228 -34
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +367 -10
  14. package/dist/commands/doctor.test.js.map +1 -1
  15. package/dist/commands/eject.js +7 -0
  16. package/dist/commands/eject.js.map +1 -1
  17. package/dist/commands/eject.test.js +39 -0
  18. package/dist/commands/eject.test.js.map +1 -1
  19. package/dist/commands/info.js +3 -2
  20. package/dist/commands/info.js.map +1 -1
  21. package/dist/commands/init.d.ts +37 -1
  22. package/dist/commands/init.js +169 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +392 -27
  25. package/dist/commands/init.test.js.map +1 -1
  26. package/dist/commands/install-packages.d.ts +6 -0
  27. package/dist/commands/install-packages.js +20 -0
  28. package/dist/commands/install-packages.js.map +1 -0
  29. package/dist/commands/install-packages.test.d.ts +1 -0
  30. package/dist/commands/install-packages.test.js +55 -0
  31. package/dist/commands/install-packages.test.js.map +1 -0
  32. package/dist/commands/list.js +21 -14
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/list.test.js +62 -5
  35. package/dist/commands/list.test.js.map +1 -1
  36. package/dist/commands/outdated.js +11 -10
  37. package/dist/commands/outdated.js.map +1 -1
  38. package/dist/commands/outdated.test.js +20 -5
  39. package/dist/commands/outdated.test.js.map +1 -1
  40. package/dist/commands/remove.d.ts +4 -0
  41. package/dist/commands/remove.js +17 -4
  42. package/dist/commands/remove.js.map +1 -1
  43. package/dist/commands/remove.test.js +53 -2
  44. package/dist/commands/remove.test.js.map +1 -1
  45. package/dist/commands/theme.js +1 -1
  46. package/dist/commands/theme.js.map +1 -1
  47. package/dist/commands/update.d.ts +11 -0
  48. package/dist/commands/update.js +174 -58
  49. package/dist/commands/update.js.map +1 -1
  50. package/dist/commands/update.test.js +587 -33
  51. package/dist/commands/update.test.js.map +1 -1
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/lockfile.d.ts +7 -0
  55. package/dist/lockfile.js +36 -0
  56. package/dist/lockfile.js.map +1 -1
  57. package/dist/lockfile.test.d.ts +1 -0
  58. package/dist/lockfile.test.js +25 -0
  59. package/dist/lockfile.test.js.map +1 -0
  60. package/dist/registry.d.ts +14 -0
  61. package/dist/registry.js +78 -24
  62. package/dist/registry.js.map +1 -1
  63. package/dist/registry.test.js +119 -38
  64. package/dist/registry.test.js.map +1 -1
  65. package/dist/test-utils.d.ts +5 -0
  66. package/dist/test-utils.js +6 -1
  67. package/dist/test-utils.js.map +1 -1
  68. package/dist/testing/fire-lynx-event.d.ts +14 -0
  69. package/dist/testing/fire-lynx-event.js +19 -0
  70. package/dist/testing/fire-lynx-event.js.map +1 -0
  71. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  72. package/dist/testing/fire-lynx-event.test.js +44 -0
  73. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  74. package/dist/testing/lynx-main-thread.d.ts +22 -0
  75. package/dist/testing/lynx-main-thread.js +11 -0
  76. package/dist/testing/lynx-main-thread.js.map +1 -0
  77. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  78. package/dist/testing/lynx-main-thread.test.js +24 -0
  79. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  80. package/dist/testing/set-input-signal.d.ts +14 -0
  81. package/dist/testing/set-input-signal.js +21 -0
  82. package/dist/testing/set-input-signal.js.map +1 -0
  83. package/dist/testing/set-input-signal.test.d.ts +1 -0
  84. package/dist/testing/set-input-signal.test.js +20 -0
  85. package/dist/testing/set-input-signal.test.js.map +1 -0
  86. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  87. package/dist/ui/components/accordion/accordion.ts +19 -14
  88. package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
  89. package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
  90. package/dist/ui/components/alert/alert.test.ts +67 -19
  91. package/dist/ui/components/alert/alert.ts +14 -16
  92. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
  93. package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
  94. package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
  95. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  96. package/dist/ui/components/area-chart/index.ts +2 -3
  97. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  98. package/dist/ui/components/badge/badge.test.ts +57 -23
  99. package/dist/ui/components/badge/badge.ts +12 -14
  100. package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
  101. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  102. package/dist/ui/components/bar-chart/index.ts +3 -3
  103. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
  104. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
  105. package/dist/ui/components/button/button.test.ts +611 -2
  106. package/dist/ui/components/button/button.ts +434 -49
  107. package/dist/ui/components/button/index.ts +2 -0
  108. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  109. package/dist/ui/components/button-group/button-group.ts +380 -68
  110. package/dist/ui/components/button-group/index.ts +3 -0
  111. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
  112. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  113. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  114. package/dist/ui/components/card/card.test.ts +82 -62
  115. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  116. package/dist/ui/components/chart/chart-focus.ts +115 -0
  117. package/dist/ui/components/chart/chart-presets.test.ts +335 -0
  118. package/dist/ui/components/chart/chart-presets.ts +278 -0
  119. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  120. package/dist/ui/components/chart/chart-scene.ts +483 -0
  121. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  122. package/dist/ui/components/chart/chart-theme.ts +210 -0
  123. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  124. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  125. package/dist/ui/components/chart/chart.test.ts +1017 -0
  126. package/dist/ui/components/chart/chart.ts +934 -0
  127. package/dist/ui/components/chart/index.ts +24 -0
  128. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  129. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  130. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  131. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  132. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  133. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  134. package/dist/ui/components/context-menu/index.ts +1 -0
  135. package/dist/ui/components/dialog/dialog.test.ts +213 -108
  136. package/dist/ui/components/dialog/dialog.ts +72 -35
  137. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  138. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  139. package/dist/ui/components/dock/dock.test.ts +749 -0
  140. package/dist/ui/components/dock/dock.ts +960 -0
  141. package/dist/ui/components/dock/index.ts +2 -0
  142. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  143. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  144. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  145. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  146. package/dist/ui/components/glass/glass-design.ts +119 -0
  147. package/dist/ui/components/glass/glass.test.ts +338 -0
  148. package/dist/ui/components/glass/glass.ts +360 -0
  149. package/dist/ui/components/glass/index.ts +18 -0
  150. package/dist/ui/components/icon/icon-theme.test.ts +169 -0
  151. package/dist/ui/components/icon/icon-theme.ts +227 -0
  152. package/dist/ui/components/icon/icon.test.ts +85 -25
  153. package/dist/ui/components/icon/icon.ts +38 -21
  154. package/dist/ui/components/input/input.test.ts +178 -101
  155. package/dist/ui/components/input/input.ts +15 -12
  156. package/dist/ui/components/label/label.test.ts +11 -24
  157. package/dist/ui/components/line-chart/index.ts +2 -3
  158. package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
  159. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  160. package/dist/ui/components/list/list.test.ts +119 -21
  161. package/dist/ui/components/list/list.ts +21 -15
  162. package/dist/ui/components/menu/index.ts +25 -0
  163. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  164. package/dist/ui/components/menu/menu-placement.ts +159 -0
  165. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  166. package/dist/ui/components/menu/menu-popup.ts +544 -0
  167. package/dist/ui/components/menu/menu.test.ts +429 -0
  168. package/dist/ui/components/menu/menu.ts +415 -0
  169. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
  170. package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
  171. package/dist/ui/components/pagination/pagination.test.ts +177 -65
  172. package/dist/ui/components/pagination/pagination.ts +55 -8
  173. package/dist/ui/components/progress/progress.test.ts +102 -46
  174. package/dist/ui/components/progress/progress.ts +52 -54
  175. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  176. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  177. package/dist/ui/components/scatter-chart/index.ts +2 -3
  178. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
  179. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  180. package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
  181. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  182. package/dist/ui/components/select/select.test.ts +138 -78
  183. package/dist/ui/components/select/select.ts +17 -11
  184. package/dist/ui/components/separator/separator.test.ts +18 -17
  185. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  186. package/dist/ui/components/sheet/sheet.ts +9 -1
  187. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  188. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  189. package/dist/ui/components/spinner/spinner.test.ts +67 -22
  190. package/dist/ui/components/spinner/spinner.ts +48 -41
  191. package/dist/ui/components/switch/switch.test.ts +116 -46
  192. package/dist/ui/components/switch/switch.ts +8 -13
  193. package/dist/ui/components/tab-bar/index.ts +1 -0
  194. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  195. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  196. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  197. package/dist/ui/components/tabs/tabs.ts +110 -118
  198. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  199. package/dist/ui/components/textarea/textarea.ts +14 -12
  200. package/dist/ui/components/toast/toast.test.ts +436 -315
  201. package/dist/ui/components/toast/toast.ts +62 -55
  202. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  203. package/dist/ui/theme/chart-colors.test.ts +35 -0
  204. package/dist/ui/theme/dark.css +46 -2
  205. package/dist/ui/theme/default.css +63 -7
  206. package/dist/ui/theme/icon-colors.test.ts +35 -0
  207. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  208. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  209. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  210. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  211. package/dist/ui/theme/tailwind-plugin.ts +98 -25
  212. package/dist/ui/theme/tailwind.css +113 -0
  213. package/dist/ui/utils/animate.test.ts +13 -1
  214. package/dist/utils/analyze.d.ts +14 -0
  215. package/dist/utils/analyze.js +15 -0
  216. package/dist/utils/analyze.js.map +1 -1
  217. package/dist/utils/analyze.test.js +14 -1
  218. package/dist/utils/analyze.test.js.map +1 -1
  219. package/dist/utils/installed.d.ts +29 -0
  220. package/dist/utils/installed.js +43 -0
  221. package/dist/utils/installed.js.map +1 -0
  222. package/dist/utils/installed.test.d.ts +1 -0
  223. package/dist/utils/installed.test.js +57 -0
  224. package/dist/utils/installed.test.js.map +1 -0
  225. package/dist/utils/interactive.d.ts +9 -0
  226. package/dist/utils/interactive.js +10 -0
  227. package/dist/utils/interactive.js.map +1 -0
  228. package/dist/utils/interactive.test.d.ts +1 -0
  229. package/dist/utils/interactive.test.js +24 -0
  230. package/dist/utils/interactive.test.js.map +1 -0
  231. package/dist/utils/packages.d.ts +36 -0
  232. package/dist/utils/packages.js +70 -0
  233. package/dist/utils/packages.js.map +1 -0
  234. package/dist/utils/packages.test.d.ts +1 -0
  235. package/dist/utils/packages.test.js +89 -0
  236. package/dist/utils/packages.test.js.map +1 -0
  237. package/dist/utils/resolve-paths.d.ts +17 -1
  238. package/dist/utils/resolve-paths.js +21 -3
  239. package/dist/utils/resolve-paths.js.map +1 -1
  240. package/dist/utils/resolve-paths.test.js +38 -2
  241. package/dist/utils/resolve-paths.test.js.map +1 -1
  242. package/dist/utils/tailwind.d.ts +25 -0
  243. package/dist/utils/tailwind.js +51 -0
  244. package/dist/utils/tailwind.js.map +1 -0
  245. package/dist/utils/tailwind.test.d.ts +1 -0
  246. package/dist/utils/tailwind.test.js +71 -0
  247. package/dist/utils/tailwind.test.js.map +1 -0
  248. package/package.json +13 -1
  249. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
  250. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
  251. package/dist/ui/components/button/button-render.test.ts +0 -113
  252. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  253. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
  254. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  255. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
  256. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  257. package/dist/ui/components/cartesian-chart/index.ts +0 -18
  258. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
  259. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
@@ -1,8 +1,25 @@
1
1
  import { Component, signal } from '@angular/core';
2
- import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
2
+ import {
3
+ fireEvent,
4
+ render,
5
+ waitForUpdate,
6
+ } from '@blotch/angular-lynx-testing-library';
3
7
  import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
4
- import { describe, expect, it, vi } from 'vitest';
5
- import { setInputSignal } from '../../../test-utils/set-input-signal';
8
+ import {
9
+ type MockInstance,
10
+ afterEach,
11
+ beforeEach,
12
+ describe,
13
+ expect,
14
+ it,
15
+ vi,
16
+ } from 'vitest';
17
+ import { DURATION } from '../../utils/animate';
18
+ import { fireLynxEvent } from '../../../test-utils/fire-lynx-event';
19
+ import {
20
+ type LynxMainThread,
21
+ lynxMainThread,
22
+ } from '../../../test-utils/lynx-main-thread';
6
23
  import {
7
24
  UiActionSheet,
8
25
  UiActionSheetCancel,
@@ -11,7 +28,6 @@ import {
11
28
  } from './action-sheet';
12
29
 
13
30
  @Component({
14
- standalone: true,
15
31
  imports: [
16
32
  UiActionSheet,
17
33
  UiActionSheetTitle,
@@ -20,192 +36,308 @@ import {
20
36
  LYNX_ELEMENTS,
21
37
  ],
22
38
  template: `
23
- <ui-action-sheet [(open)]="open">
24
- <ui-action-sheet-title>Pick</ui-action-sheet-title>
25
- <ui-action-sheet-item>Option</ui-action-sheet-item>
26
- <ui-action-sheet-cancel>Cancel</ui-action-sheet-cancel>
39
+ <ui-action-sheet [(open)]="open" class="pb-8" (closed)="closed()">
40
+ <ui-action-sheet-title class="py-4">Pick</ui-action-sheet-title>
41
+ <ui-action-sheet-item (pressed)="shared()">Share</ui-action-sheet-item>
42
+ <ui-action-sheet-item
43
+ variant="destructive"
44
+ class="bg-muted"
45
+ (pressed)="deleted()"
46
+ >
47
+ Delete
48
+ </ui-action-sheet-item>
49
+ <ui-action-sheet-cancel class="mt-2" />
27
50
  </ui-action-sheet>
28
51
  `,
29
52
  })
30
53
  class ActionSheetHost {
31
- open = true;
54
+ readonly open = signal(false);
55
+ readonly closed = vi.fn();
56
+ readonly shared = vi.fn();
57
+ readonly deleted = vi.fn();
32
58
  }
33
59
 
34
- describe('UiActionSheet', () => {
35
- const patchRefs = (inst: UiActionSheet, view: Element) => {
36
- (inst as unknown as { backdropRef: unknown }).backdropRef = signal({
37
- nativeElement: view,
38
- });
39
- (inst as unknown as { panelRef: unknown }).panelRef = signal({
40
- nativeElement: view,
41
- });
60
+ /**
61
+ * A cancel button given its own label.
62
+ */
63
+ @Component({
64
+ imports: [UiActionSheet, UiActionSheetCancel],
65
+ template: `
66
+ <ui-action-sheet>
67
+ <ui-action-sheet-cancel label="Dismiss" />
68
+ </ui-action-sheet>
69
+ `,
70
+ })
71
+ class CancelLabelHost {}
72
+
73
+ /**
74
+ * The Lynx function an element's `invoke()` calls to run one of its UI
75
+ * methods, such as moving a screen reader's focus. The testing library
76
+ * defines it on the global object.
77
+ */
78
+ type InvokeUIMethod = {
79
+ __InvokeUIMethod: (
80
+ element: Element,
81
+ method: string,
82
+ params: Record<string, unknown>,
83
+ callback: (result: { code: number }) => void,
84
+ ) => void;
85
+ };
86
+
87
+ /** `__ElementAnimate`'s first argument for starting an animation. */
88
+ const START = 0;
89
+
90
+ const renderSheet = async () => {
91
+ const result = await render(ActionSheetHost);
92
+ const { container } = result;
93
+ return {
94
+ ...result,
95
+ host: result.componentRef.instance as ActionSheetHost,
96
+ overlay: container.querySelector('overlay')!,
97
+ backdrop: container.querySelector('overlay > view')!,
98
+ panel: container.querySelector('[accessibility-exclusive-focus]')!,
99
+ // A button's pressable tile: the view around its label.
100
+ button: (label: string) => result.getByText(label).parentElement!,
42
101
  };
102
+ };
43
103
 
44
- it('renders the sheet, title, item, and cancel content', async () => {
45
- const { getByText } = await render(ActionSheetHost);
104
+ /**
105
+ * Opens the action sheet and waits out its open sequence: the overlay shows
106
+ * on one tick, and the panel slides in on the next.
107
+ */
108
+ const openSheet = async (host: ActionSheetHost) => {
109
+ host.open.set(true);
110
+ for (let i = 0; i < 3; i++) await waitForUpdate();
111
+ };
112
+
113
+ /**
114
+ * Waits for the close sequence to finish: the panel slides out, then the
115
+ * overlay hides and `closed` fires. Polled rather than timed, since the
116
+ * sequence only starts on the update after `open` changes.
117
+ */
118
+ const waitForClose = async (host: ActionSheetHost) => {
119
+ await vi.waitFor(() => expect(host.closed).toHaveBeenCalled());
120
+ await waitForUpdate();
121
+ };
122
+
123
+ /**
124
+ * The scale a button's inline style holds, if any.
125
+ */
126
+ const scaleOf = (element: Element) =>
127
+ (element as HTMLElement).style.getPropertyValue('transform');
128
+
129
+ /**
130
+ * Presses a button and lets go every way Lynx reports it, checking that it
131
+ * scales down while pressed and back up once released.
132
+ */
133
+ const expectPressFeedback = (button: Element) => {
134
+ fireEvent.touchstart(button);
135
+ expect(scaleOf(button)).toBe('scale(0.985)');
136
+ fireEvent.touchend(button);
137
+ expect(scaleOf(button)).toBe('scale(1)');
138
+
139
+ // A scroll that takes the touch over cancels it: the button scales back.
140
+ fireEvent.touchstart(button);
141
+ fireEvent.touchcancel(button);
142
+ expect(scaleOf(button)).toBe('scale(1)');
143
+
144
+ // On web, the mouse drives the same feedback.
145
+ fireEvent.mousedown(button);
146
+ expect(scaleOf(button)).toBe('scale(0.985)');
147
+ fireEvent.mouseup(button);
148
+ expect(scaleOf(button)).toBe('scale(1)');
149
+
150
+ fireEvent.mousedown(button);
151
+ fireLynxEvent(button, 'mouseleave');
152
+ expect(scaleOf(button)).toBe('scale(1)');
153
+ };
154
+
155
+ describe('UiActionSheet', () => {
156
+ // Watched per test: the testing library resets the main thread after each.
157
+ let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
158
+ beforeEach(() => {
159
+ animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
160
+ });
161
+ afterEach(() => animate.mockRestore());
162
+
163
+ it('stays hidden until opened, then shows its title and buttons', async () => {
164
+ const { host, overlay, getByText } = await renderSheet();
165
+ expect(overlay.getAttribute('style')).toContain('display: none');
166
+ expect(overlay.getAttribute('visible')).toBe('false');
167
+
168
+ await openSheet(host);
169
+ expect(overlay.getAttribute('style')).not.toContain('display: none');
170
+ expect(overlay.getAttribute('visible')).toBe('true');
46
171
  expect(getByText('Pick')).toBeTruthy();
47
- expect(getByText('Option')).toBeTruthy();
172
+ expect(getByText('Share')).toBeTruthy();
173
+ expect(getByText('Delete')).toBeTruthy();
48
174
  expect(getByText('Cancel')).toBeTruthy();
49
175
  });
50
176
 
51
- it('backdrop tap closes the sheet', async () => {
52
- const { componentRef } = await render(UiActionSheet);
53
- const inst = componentRef.instance as UiActionSheet;
54
- inst.open.set(true);
55
- await waitForUpdate();
56
- (inst as unknown as { onBackdropTap: () => void }).onBackdropTap();
57
- expect(inst.open()).toBe(false);
177
+ it("merges the class into the panel's own", async () => {
178
+ const { panel } = await renderSheet();
179
+ expect(panel.classList).toContain('pb-8');
180
+ expect(panel.classList).not.toContain('pb-6');
181
+ expect(panel.classList).toContain('px-4');
58
182
  });
59
183
 
60
- it('close() sets open to false', async () => {
61
- const { componentRef } = await render(UiActionSheet);
62
- const inst = componentRef.instance as UiActionSheet;
63
- inst.open.set(true);
64
- inst.close();
65
- expect(inst.open()).toBe(false);
184
+ it('fades the backdrop in and slides the panel up when opened', async () => {
185
+ const { host, backdrop, panel } = await renderSheet();
186
+ await openSheet(host);
187
+ expect(animate).toHaveBeenCalledWith(backdrop, [
188
+ START,
189
+ expect.any(String),
190
+ [{ opacity: 0 }, { opacity: 1 }],
191
+ expect.objectContaining({ duration: DURATION.normal }),
192
+ ]);
193
+ expect(animate).toHaveBeenCalledWith(panel, [
194
+ START,
195
+ expect.any(String),
196
+ [
197
+ { transform: 'translateY(100%)', opacity: 0.8 },
198
+ { transform: 'translateY(0%)', opacity: 1 },
199
+ ],
200
+ expect.objectContaining({ duration: DURATION.slow }),
201
+ ]);
66
202
  });
67
203
 
68
- it('panel tap is a no-op', async () => {
69
- const { componentRef } = await render(UiActionSheet);
70
- const inst = componentRef.instance as UiActionSheet;
71
- expect(() =>
72
- (inst as unknown as { onPanelTap: () => void }).onPanelTap(),
73
- ).not.toThrow();
74
- });
204
+ it('moves a screen reader into the panel and keeps it there while open', async () => {
205
+ const invoke = vi.spyOn(
206
+ globalThis as unknown as InvokeUIMethod,
207
+ '__InvokeUIMethod',
208
+ );
209
+ const { host, panel } = await renderSheet();
210
+ expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('false');
75
211
 
76
- it('open effect runs the animate-in and animate-out lifecycles', async () => {
77
- const { componentRef, container } = await render(UiActionSheet);
78
- const inst = componentRef.instance as UiActionSheet;
79
- const view = container.querySelector('view') as Element;
80
- patchRefs(inst, view);
212
+ await openSheet(host);
213
+ expect(invoke).toHaveBeenCalledWith(
214
+ panel,
215
+ 'requestAccessibilityFocus',
216
+ expect.anything(),
217
+ expect.any(Function),
218
+ );
219
+ expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('true');
220
+ invoke.mockRestore();
221
+ });
81
222
 
82
- inst.open.set(true);
83
- await waitForUpdate();
84
- await waitForUpdate();
85
- await waitForUpdate();
223
+ it('closes when the backdrop is tapped, sliding the panel down', async () => {
224
+ const { host, overlay, backdrop, panel } = await renderSheet();
225
+ await openSheet(host);
86
226
 
87
- inst.open.set(false);
88
- await new Promise((r) => setTimeout(r, 320));
89
- await waitForUpdate();
90
- expect(inst.open()).toBe(false);
227
+ fireEvent.tap(backdrop);
228
+ expect(host.open()).toBe(false);
229
+ await waitForClose(host);
230
+ expect(animate).toHaveBeenCalledWith(backdrop, [
231
+ START,
232
+ expect.any(String),
233
+ [{ opacity: 1 }, { opacity: 0 }],
234
+ expect.objectContaining({ duration: DURATION.normal }),
235
+ ]);
236
+ expect(animate).toHaveBeenCalledWith(panel, [
237
+ START,
238
+ expect.any(String),
239
+ [
240
+ { transform: 'translateY(0%)', opacity: 1 },
241
+ { transform: 'translateY(100%)', opacity: 0.8 },
242
+ ],
243
+ expect.objectContaining({ duration: DURATION.normal }),
244
+ ]);
245
+ // Once the slide is over, the overlay hides and `closed` fires, only for
246
+ // this close: an action sheet that starts closed hasn't closed before.
247
+ expect(overlay.getAttribute('style')).toContain('display: none');
248
+ expect(host.closed).toHaveBeenCalledOnce();
91
249
  });
92
250
 
93
- it('open effect bails when backdrop/panel refs are undefined', async () => {
94
- const { componentRef } = await render(UiActionSheet);
95
- const inst = componentRef.instance as UiActionSheet;
96
- inst.open.set(true);
97
- await waitForUpdate();
98
- await waitForUpdate();
99
- inst.open.set(false);
100
- await waitForUpdate();
101
- expect(inst.open()).toBe(false);
251
+ it('stays open when the panel is tapped', async () => {
252
+ const { host, panel } = await renderSheet();
253
+ await openSheet(host);
254
+ // The panel catches its taps, so they never reach the backdrop.
255
+ fireEvent.tap(panel, { eventType: 'catchEvent' });
256
+ expect(host.open()).toBe(true);
102
257
  });
103
258
 
104
- it('applies user class on the panel', async () => {
105
- const { componentRef, container } = await render(UiActionSheet);
106
- const inst = componentRef.instance as UiActionSheet;
107
- setInputSignal(inst.userClass, 'my-panel');
259
+ it('stops its close timers when it is destroyed while closing', async () => {
260
+ const warn = vi.spyOn(console, 'warn');
261
+ const { host, unmount } = await renderSheet();
262
+ await openSheet(host);
263
+ host.open.set(false);
108
264
  await waitForUpdate();
109
- expect(
110
- Array.from(container.querySelectorAll('view')).some((el) =>
111
- el.getAttribute('class')?.includes('my-panel'),
112
- ),
113
- ).toBe(true);
265
+ // Destroyed during the close animation, as an `@if` around the action
266
+ // sheet would do. Its close timer (DURATION.normal + 20) used to fire
267
+ // anyway and emit `closed` from the destroyed output.
268
+ unmount();
269
+ await new Promise((resolve) => setTimeout(resolve, DURATION.normal + 50));
270
+ const warnings = warn.mock.calls.map(([message]) => String(message));
271
+ warn.mockRestore();
272
+ expect(warnings.filter((message) => message.includes('NG0953'))).toEqual(
273
+ [],
274
+ );
275
+ expect(host.closed).not.toHaveBeenCalled();
114
276
  });
115
277
  });
116
278
 
117
279
  describe('UiActionSheetTitle', () => {
118
- it('renders projected content', async () => {
119
- const { container } = await render(UiActionSheetTitle);
120
- expect(container.querySelector('view')).toBeTruthy();
121
- expect(container.querySelector('text')).toBeTruthy();
280
+ it('shows its text muted, with the class on its container', async () => {
281
+ const { getByText } = await renderSheet();
282
+ const title = getByText('Pick');
283
+ expect(title.classList).toContain('text-muted-foreground');
284
+ expect(title.parentElement?.classList).toContain('py-4');
285
+ expect(title.parentElement?.classList).not.toContain('py-3');
122
286
  });
123
287
  });
124
288
 
125
289
  describe('UiActionSheetItem', () => {
126
- const renderItem = async () => {
127
- const parentResult = await render(UiActionSheet);
128
- const parent = parentResult.componentRef.instance as UiActionSheet;
129
- parent.open.set(true);
130
- await waitForUpdate();
290
+ it('reports the press and closes the action sheet when tapped', async () => {
291
+ const { host, button } = await renderSheet();
292
+ await openSheet(host);
131
293
 
132
- const itemResult = await render(UiActionSheetItem, {
133
- providers: [{ provide: UiActionSheet, useValue: parent }],
134
- });
135
- return { parent, itemResult };
136
- };
294
+ fireEvent.tap(button('Share'));
295
+ expect(host.shared).toHaveBeenCalledOnce();
296
+ expect(host.deleted).not.toHaveBeenCalled();
297
+ expect(host.open()).toBe(false);
298
+ });
137
299
 
138
- it('onTap emits `pressed` and closes the parent sheet', async () => {
139
- const { parent, itemResult } = await renderItem();
140
- const inst = itemResult.componentRef
141
- .instance as unknown as UiActionSheetItem & {
142
- onTap: () => void;
143
- };
144
- const emitted = vi.fn();
145
- inst.pressed.subscribe(emitted);
146
- inst.onTap();
147
- expect(emitted).toHaveBeenCalledTimes(1);
148
- expect(parent.open()).toBe(false);
149
- });
150
-
151
- it('press handlers run cleanly', async () => {
152
- const { itemResult } = await renderItem();
153
- const inst = itemResult.componentRef.instance as unknown as {
154
- onPressStart: () => void;
155
- onPressEnd: () => void;
156
- onPressCancel: () => void;
157
- };
158
- expect(() => inst.onPressStart()).not.toThrow();
159
- expect(() => inst.onPressEnd()).not.toThrow();
160
- expect(() => inst.onPressCancel()).not.toThrow();
161
- });
162
-
163
- it('applies destructive variant text class', async () => {
164
- const { itemResult } = await renderItem();
165
- const inst = itemResult.componentRef.instance as UiActionSheetItem;
166
- setInputSignal(inst.variant, 'destructive');
167
- await waitForUpdate();
168
- const text = itemResult.container.querySelector('text');
169
- expect(text?.getAttribute('class')).toContain('text-destructive');
300
+ it('colors its label by variant', async () => {
301
+ const { getByText } = await renderSheet();
302
+ expect(getByText('Share').classList).toContain('text-primary');
303
+ expect(getByText('Delete').classList).toContain('text-destructive');
304
+ });
305
+
306
+ it("merges the class into its tile's own", async () => {
307
+ const { button } = await renderSheet();
308
+ expect(button('Delete').classList).toContain('bg-muted');
309
+ expect(button('Delete').classList).toContain('border-t');
310
+ });
311
+
312
+ it('scales down while pressed, and back on release', async () => {
313
+ const { button } = await renderSheet();
314
+ expectPressFeedback(button('Share'));
170
315
  });
171
316
  });
172
317
 
173
318
  describe('UiActionSheetCancel', () => {
174
- const renderCancel = async () => {
175
- const parentResult = await render(UiActionSheet);
176
- const parent = parentResult.componentRef.instance as UiActionSheet;
177
- parent.open.set(true);
178
- await waitForUpdate();
319
+ it('closes the action sheet when tapped', async () => {
320
+ const { host, button } = await renderSheet();
321
+ await openSheet(host);
179
322
 
180
- const cancelResult = await render(UiActionSheetCancel, {
181
- providers: [{ provide: UiActionSheet, useValue: parent }],
182
- });
183
- return { parent, cancelResult };
184
- };
323
+ fireEvent.tap(button('Cancel'));
324
+ expect(host.open()).toBe(false);
325
+ });
326
+
327
+ it('shows Cancel, or the label it is given', async () => {
328
+ expect((await renderSheet()).getByText('Cancel')).toBeTruthy();
329
+ const { getByText } = await render(CancelLabelHost);
330
+ expect(getByText('Dismiss')).toBeTruthy();
331
+ });
332
+
333
+ it("merges the class into its tile's own", async () => {
334
+ const { button } = await renderSheet();
335
+ expect(button('Cancel').classList).toContain('mt-2');
336
+ expect(button('Cancel').classList).toContain('bg-card');
337
+ });
185
338
 
186
- it('renders the default cancel label', async () => {
187
- const { cancelResult } = await renderCancel();
188
- expect(cancelResult.container.textContent).toContain('Cancel');
189
- });
190
-
191
- it('onTap closes the parent sheet', async () => {
192
- const { parent, cancelResult } = await renderCancel();
193
- const inst = cancelResult.componentRef.instance as unknown as {
194
- onTap: () => void;
195
- };
196
- inst.onTap();
197
- expect(parent.open()).toBe(false);
198
- });
199
-
200
- it('press handlers run cleanly', async () => {
201
- const { cancelResult } = await renderCancel();
202
- const inst = cancelResult.componentRef.instance as unknown as {
203
- onPressStart: () => void;
204
- onPressEnd: () => void;
205
- onPressCancel: () => void;
206
- };
207
- expect(() => inst.onPressStart()).not.toThrow();
208
- expect(() => inst.onPressEnd()).not.toThrow();
209
- expect(() => inst.onPressCancel()).not.toThrow();
339
+ it('scales down while pressed, and back on release', async () => {
340
+ const { button } = await renderSheet();
341
+ expectPressFeedback(button('Cancel'));
210
342
  });
211
343
  });