@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
@@ -1,10 +1,26 @@
1
- import { Component, signal } from '@angular/core';
2
- import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
1
+ import { Component, signal, viewChild } from '@angular/core';
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';
8
+ import {
9
+ type MockInstance,
10
+ afterEach,
11
+ beforeEach,
12
+ describe,
13
+ expect,
14
+ it,
15
+ vi,
16
+ } from 'vitest';
5
17
  import { DURATION } from '../../utils/animate';
6
- import { setInputSignal } from '../../../test-utils/set-input-signal';
7
- import { UI_ICON_COLOR } from '../icon/icon-theme';
18
+ import { fireLynxEvent } from '../../../test-utils/fire-lynx-event';
19
+ import {
20
+ type LynxMainThread,
21
+ lynxMainThread,
22
+ } from '../../../test-utils/lynx-main-thread';
23
+ import { UiIcon } from '../icon/icon';
8
24
  import {
9
25
  UiNavDrawer,
10
26
  UiNavDrawerContent,
@@ -22,325 +38,474 @@ import {
22
38
  UiNavDrawerItem,
23
39
  UiNavDrawerFooter,
24
40
  UiNavDrawerTrigger,
41
+ UiIcon,
25
42
  LYNX_ELEMENTS,
26
43
  ],
27
44
  template: `
28
- <ui-nav-drawer [(open)]="open">
29
- <ui-nav-drawer-header><text>Nav</text></ui-nav-drawer-header>
30
- <ui-nav-drawer-content>
31
- <ui-nav-drawer-item>Home</ui-nav-drawer-item>
32
- </ui-nav-drawer-content>
33
- <ui-nav-drawer-footer><text>Foot</text></ui-nav-drawer-footer>
34
- </ui-nav-drawer>
35
- <ui-nav-drawer-trigger>Open</ui-nav-drawer-trigger>
45
+ <ui-nav-drawer-trigger class="ml-2" (pressed)="open.set(true)" />
46
+ @if (shown()) {
47
+ <ui-nav-drawer
48
+ [(open)]="open"
49
+ [side]="side()"
50
+ class="shadow-lg"
51
+ (closed)="closes.set(closes() + 1)"
52
+ >
53
+ <ui-nav-drawer-header class="gap-3">
54
+ <text>Menu</text>
55
+ </ui-nav-drawer-header>
56
+ <ui-nav-drawer-content class="pt-2">
57
+ <ui-nav-drawer-item
58
+ [active]="active() === 'home'"
59
+ (pressed)="presses.set(presses() + 1)"
60
+ >
61
+ <text class="text-foreground">Home</text>
62
+ </ui-nav-drawer-item>
63
+ <!-- A group: items nested in other elements count too. -->
64
+ <view class="group">
65
+ <ui-nav-drawer-item #settings [active]="active() === 'settings'">
66
+ <text class="text-foreground">Settings</text>
67
+ </ui-nav-drawer-item>
68
+ </view>
69
+ <ui-nav-drawer-item
70
+ [variant]="deleteVariant()"
71
+ [autoClose]="false"
72
+ class="mt-4"
73
+ >
74
+ <ui-icon name="trash" size="sm" />
75
+ <text class="text-foreground">Delete account</text>
76
+ </ui-nav-drawer-item>
77
+ </ui-nav-drawer-content>
78
+ <ui-nav-drawer-footer class="gap-4">
79
+ <text>Version 1</text>
80
+ </ui-nav-drawer-footer>
81
+ </ui-nav-drawer>
82
+ }
83
+ <!-- What the trash icon looks like plain, and in the destructive color. -->
84
+ <ui-icon name="trash" size="sm" class="plain-trash" />
85
+ <ui-icon
86
+ name="trash"
87
+ size="sm"
88
+ color="var(--destructive)"
89
+ class="destructive-trash"
90
+ />
36
91
  `,
37
92
  })
38
93
  class NavDrawerHost {
39
- open = true;
94
+ readonly open = signal(false);
95
+ readonly side = signal<'left' | 'right'>('left');
96
+ readonly active = signal('settings');
97
+ readonly deleteVariant = signal<'default' | 'destructive'>('destructive');
98
+ // Takes the drawer out of the tree, as an `@if` or a route change would.
99
+ readonly shown = signal(true);
100
+ readonly closes = signal(0);
101
+ readonly presses = signal(0);
102
+ readonly drawer = viewChild.required(UiNavDrawer);
103
+ readonly settings = viewChild.required<UiNavDrawerItem>('settings');
40
104
  }
41
105
 
106
+ const renderDrawer = async () => {
107
+ const result = await render(NavDrawerHost);
108
+ const { container, getByText } = result;
109
+ const panel = () =>
110
+ container.querySelector('[accessibility-exclusive-focus]')!;
111
+ return {
112
+ ...result,
113
+ host: result.componentRef.instance as NavDrawerHost,
114
+ overlay: () => container.querySelector('overlay')!,
115
+ panel,
116
+ backdrop: () => panel().parentElement!,
117
+ scrollView: () => container.querySelector('scroll-view')!,
118
+ trigger: () => container.querySelector('[accessibility-element]')!,
119
+ // An item's own view, which holds its projected label.
120
+ item: (label: string) => getByText(label).parentElement!,
121
+ svg: (selector: string) => container.querySelector(`svg${selector}`)!,
122
+ };
123
+ };
124
+
125
+ /**
126
+ * Waits for the drawer's open or close sequence, which runs on timers, then
127
+ * renders what it changed.
128
+ */
129
+ const after = async (ms: number) => {
130
+ await new Promise((resolve) => setTimeout(resolve, ms));
131
+ await waitForUpdate();
132
+ };
133
+
134
+ /**
135
+ * Long enough for the open sequence. The sequence is chained timers, and on
136
+ * Windows each can fire up to a clock tick (about 16ms) late, so 50ms was too
137
+ * short for the dialogs' sequences on CI's Windows runner.
138
+ */
139
+ const OPENED = 150;
140
+ /** Long enough for the close animation and the hide after it, as above. */
141
+ const CLOSED = DURATION.normal + 150;
142
+
143
+ /** `__ElementAnimate`'s first argument for starting an animation. */
144
+ const START = 0;
145
+ /** `__ElementAnimate`'s first argument for canceling an animation. */
146
+ const CANCEL = 3;
147
+
148
+ /**
149
+ * The scale a pressable view's inline style holds, if any.
150
+ */
151
+ const scaleOf = (view: Element) =>
152
+ (view as HTMLElement).style.getPropertyValue('transform');
153
+
42
154
  describe('UiNavDrawer', () => {
43
- it('stops its close timers when it is destroyed while closing', async () => {
44
- const warn = vi.spyOn(console, 'warn');
45
- const { componentRef, unmount } = await render(UiNavDrawer);
46
- const inst = componentRef.instance as UiNavDrawer;
47
- inst.open.set(true);
48
- for (let i = 0; i < 5; i++) await waitForUpdate();
49
- inst.open.set(false);
50
- await waitForUpdate();
51
- // Destroyed during the close animation, as an `@if` around the drawer
52
- // would do. Its close timer (DURATION.normal + 20) used to fire anyway
53
- // and emit `closed` from the destroyed output.
54
- unmount();
55
- await new Promise((r) => setTimeout(r, DURATION.normal + 50));
56
- const warnings = warn.mock.calls.map(([message]) => String(message));
57
- warn.mockRestore();
58
- expect(warnings.filter((message) => message.includes('NG0953'))).toEqual(
59
- [],
60
- );
155
+ // Watched per test: the testing library resets the main thread after each.
156
+ let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
157
+ beforeEach(() => {
158
+ animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
61
159
  });
160
+ afterEach(() => animate.mockRestore());
62
161
 
63
- it('moves a screen reader into the panel and keeps it there while open', async () => {
64
- const { componentRef, container } = await render(UiNavDrawer);
65
- const inst = componentRef.instance as UiNavDrawer;
66
- const panel = container.querySelector(
67
- '[accessibility-exclusive-focus]',
68
- ) as Element & { invoke?: unknown };
69
- expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('false');
70
- const invoke = vi.fn();
71
- panel.invoke = invoke;
72
- (inst as unknown as { backdropRef: unknown }).backdropRef = signal({
73
- nativeElement: panel,
74
- });
75
- (inst as unknown as { panelRef: unknown }).panelRef = signal({
76
- nativeElement: panel,
77
- });
78
- inst.open.set(true);
79
- for (let i = 0; i < 5; i++) await waitForUpdate();
80
- expect(invoke).toHaveBeenCalledWith('requestAccessibilityFocus');
81
- expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('true');
162
+ it('renders each part with its projected content', async () => {
163
+ const { getByText, trigger } = await renderDrawer();
164
+ expect(getByText('Menu')).toBeTruthy();
165
+ expect(getByText('Home')).toBeTruthy();
166
+ expect(getByText('Settings')).toBeTruthy();
167
+ expect(getByText('Version 1')).toBeTruthy();
168
+ expect(trigger().querySelector('svg')).toBeTruthy();
82
169
  });
83
170
 
84
- const patchRefs = (inst: UiNavDrawer, view: Element) => {
85
- (inst as unknown as { backdropRef: unknown }).backdropRef = signal({
86
- nativeElement: view,
87
- });
88
- (inst as unknown as { panelRef: unknown }).panelRef = signal({
89
- nativeElement: view,
90
- });
91
- };
92
-
93
- it('renders header, content items, footer, and trigger', async () => {
94
- const { container } = await render(NavDrawerHost);
95
- expect(container).toBeTruthy();
171
+ it("merges each part's class with its own, the given class winning", async () => {
172
+ const { container, panel, scrollView, item, trigger } =
173
+ await renderDrawer();
174
+ expect(panel().classList).toContain('bg-background');
175
+ expect(panel().classList).toContain('shadow-lg');
176
+ expect(scrollView().classList).toContain('h-full');
177
+ expect(scrollView().classList).toContain('pt-2');
178
+ expect(item('Delete account').classList).toContain('mt-4');
179
+ expect(trigger().classList).toContain('h-10');
180
+ expect(trigger().classList).toContain('ml-2');
181
+ // Each part's own view, not its host element, which keeps the bare class.
182
+ const header = container.querySelector('.border-b');
183
+ expect(header?.classList).toContain('gap-3');
184
+ expect(header?.classList).not.toContain('gap-1.5');
185
+ const footer = container.querySelector('.border-t');
186
+ expect(footer?.classList).toContain('gap-4');
187
+ expect(footer?.classList).not.toContain('gap-2');
96
188
  });
97
189
 
98
- it('backdrop tap closes the drawer', async () => {
99
- const { componentRef } = await render(UiNavDrawer);
100
- const inst = componentRef.instance as UiNavDrawer;
101
- inst.open.set(true);
102
- await waitForUpdate();
103
- (inst as unknown as { onBackdropTap: () => void }).onBackdropTap();
104
- expect(inst.open()).toBe(false);
190
+ it('stays hidden until it is opened', async () => {
191
+ const { host, overlay, panel } = await renderDrawer();
192
+ await after(CLOSED);
193
+ expect(overlay().getAttribute('style')).toContain('display: none');
194
+ expect(panel().getAttribute('accessibility-exclusive-focus')).toBe('false');
195
+ expect(animate).not.toHaveBeenCalled();
196
+ // Starting closed isn't closing: `closed` waits for a real close.
197
+ expect(host.closes()).toBe(0);
105
198
  });
106
199
 
107
- it('close() sets open to false', async () => {
108
- const { componentRef } = await render(UiNavDrawer);
109
- const inst = componentRef.instance as UiNavDrawer;
110
- inst.open.set(true);
111
- inst.close();
112
- expect(inst.open()).toBe(false);
200
+ it('opens when the trigger is tapped', async () => {
201
+ const { host, trigger, overlay } = await renderDrawer();
202
+ fireEvent.tap(trigger());
203
+ await after(OPENED);
204
+ expect(host.open()).toBe(true);
205
+ expect(overlay().getAttribute('style')).not.toContain('display: none');
113
206
  });
114
207
 
115
- it('panel tap is a no-op', async () => {
116
- const { componentRef } = await render(UiNavDrawer);
117
- const inst = componentRef.instance as UiNavDrawer;
118
- expect(() =>
119
- (inst as unknown as { onPanelTap: () => void }).onPanelTap(),
120
- ).not.toThrow();
208
+ it('slides in from the left and back out', async () => {
209
+ const { host, overlay, backdrop, panel } = await renderDrawer();
210
+ expect(panel().getAttribute('style')).toContain('left: 0');
211
+ expect(panel().classList).toContain('border-r');
212
+
213
+ host.open.set(true);
214
+ await after(OPENED);
215
+ expect(animate).toHaveBeenCalledWith(backdrop(), [
216
+ START,
217
+ expect.any(String),
218
+ [{ opacity: 0 }, { opacity: 1 }],
219
+ expect.objectContaining({ duration: DURATION.normal }),
220
+ ]);
221
+ expect(animate).toHaveBeenCalledWith(panel(), [
222
+ START,
223
+ expect.any(String),
224
+ [
225
+ { transform: 'translateX(-100%)', opacity: 0.8 },
226
+ { transform: 'translateX(0%)', opacity: 1 },
227
+ ],
228
+ expect.objectContaining({ duration: DURATION.slow }),
229
+ ]);
230
+
231
+ host.open.set(false);
232
+ await after(CLOSED);
233
+ expect(animate).toHaveBeenCalledWith(backdrop(), [
234
+ START,
235
+ expect.any(String),
236
+ [{ opacity: 1 }, { opacity: 0 }],
237
+ expect.any(Object),
238
+ ]);
239
+ expect(animate).toHaveBeenCalledWith(panel(), [
240
+ START,
241
+ expect.any(String),
242
+ [
243
+ { transform: 'translateX(0%)', opacity: 1 },
244
+ { transform: 'translateX(-100%)', opacity: 0.8 },
245
+ ],
246
+ expect.any(Object),
247
+ ]);
248
+ expect(overlay().getAttribute('style')).toContain('display: none');
249
+ expect(host.closes()).toBe(1);
121
250
  });
122
251
 
123
- it('open effect runs the animate-in and animate-out lifecycles (left side)', async () => {
124
- const { componentRef, container } = await render(UiNavDrawer);
125
- const inst = componentRef.instance as UiNavDrawer;
126
- const view = container.querySelector('view') as Element;
127
- patchRefs(inst, view);
128
- inst.open.set(true);
129
- await waitForUpdate();
130
- await waitForUpdate();
252
+ it('slides in from the right and back out', async () => {
253
+ const { host, panel } = await renderDrawer();
254
+ host.side.set('right');
131
255
  await waitForUpdate();
256
+ expect(panel().getAttribute('style')).toContain('right: 0');
257
+ expect(panel().classList).toContain('border-l');
132
258
 
133
- inst.open.set(false);
134
- await new Promise((r) => setTimeout(r, 320));
135
- await waitForUpdate();
136
- expect(inst.open()).toBe(false);
259
+ host.open.set(true);
260
+ await after(OPENED);
261
+ expect(animate).toHaveBeenCalledWith(panel(), [
262
+ START,
263
+ expect.any(String),
264
+ [
265
+ { transform: 'translateX(100%)', opacity: 0.8 },
266
+ { transform: 'translateX(0%)', opacity: 1 },
267
+ ],
268
+ expect.any(Object),
269
+ ]);
270
+
271
+ host.open.set(false);
272
+ await after(CLOSED);
273
+ expect(animate).toHaveBeenCalledWith(panel(), [
274
+ START,
275
+ expect.any(String),
276
+ [
277
+ { transform: 'translateX(0%)', opacity: 1 },
278
+ { transform: 'translateX(100%)', opacity: 0.8 },
279
+ ],
280
+ expect.any(Object),
281
+ ]);
137
282
  });
138
283
 
139
- it('open effect runs correctly for the right side', async () => {
140
- const { componentRef, container } = await render(UiNavDrawer);
141
- const inst = componentRef.instance as UiNavDrawer;
142
- setInputSignal(inst.side, 'right');
143
- await waitForUpdate();
144
- const view = container.querySelector('view') as Element;
145
- patchRefs(inst, view);
146
- inst.open.set(true);
147
- await waitForUpdate();
148
- await waitForUpdate();
149
- await waitForUpdate();
150
- inst.open.set(false);
151
- await new Promise((r) => setTimeout(r, 320));
152
- expect(inst.open()).toBe(false);
153
- expect(
154
- Array.from(container.querySelectorAll('view')).some((el) =>
155
- el.getAttribute('class')?.includes('border-l'),
156
- ),
157
- ).toBe(true);
284
+ it('cancels the previous animations when it opens again', async () => {
285
+ const { host, backdrop, panel } = await renderDrawer();
286
+ host.open.set(true);
287
+ await after(OPENED);
288
+ host.open.set(false);
289
+ await after(CLOSED);
290
+ animate.mockClear();
291
+
292
+ host.open.set(true);
293
+ await after(OPENED);
294
+ // The close animations hold their last frame (fill: forwards) until
295
+ // they're canceled, which would hide the panel being opened.
296
+ expect(animate).toHaveBeenCalledWith(backdrop(), [
297
+ CANCEL,
298
+ expect.any(String),
299
+ ]);
300
+ expect(animate).toHaveBeenCalledWith(panel(), [CANCEL, expect.any(String)]);
158
301
  });
159
302
 
160
- it('open effect bails when backdrop/panel refs are undefined', async () => {
161
- const { componentRef } = await render(UiNavDrawer);
162
- const inst = componentRef.instance as UiNavDrawer;
163
- const closed = vi.fn();
164
- inst.closed.subscribe(closed);
165
- inst.open.set(true);
166
- await waitForUpdate();
167
- await waitForUpdate();
168
- inst.open.set(false);
303
+ it('moves a screen reader into the panel and keeps it there while open', async () => {
304
+ const { host, panel } = await renderDrawer();
305
+ const invoke = vi.spyOn(host.drawer().panelRef().nativeElement, 'invoke');
306
+ host.open.set(true);
307
+ await after(OPENED);
308
+ expect(invoke).toHaveBeenCalledWith('requestAccessibilityFocus');
309
+ expect(panel().getAttribute('accessibility-exclusive-focus')).toBe('true');
310
+ });
311
+
312
+ it('closes when the backdrop is tapped', async () => {
313
+ const { host, backdrop } = await renderDrawer();
314
+ host.open.set(true);
315
+ await after(OPENED);
316
+ fireEvent.tap(backdrop());
317
+ expect(host.open()).toBe(false);
318
+ await after(CLOSED);
319
+ expect(host.closes()).toBe(1);
320
+ });
321
+
322
+ it('stays open when the panel is tapped', async () => {
323
+ const { host, panel } = await renderDrawer();
324
+ host.open.set(true);
325
+ await after(OPENED);
326
+ // The panel catches its taps, so they don't reach the backdrop.
327
+ fireEvent.tap(panel(), { eventType: 'catchEvent' });
169
328
  await waitForUpdate();
170
- // Wait out the close sequence, so #animateOut runs (and bails) while the
171
- // component is still alive. Destroying it clears the sequence's timers.
172
- await new Promise((r) => setTimeout(r, DURATION.normal + 50));
173
- expect(inst.open()).toBe(false);
174
- expect(closed).toHaveBeenCalledOnce();
329
+ expect(host.open()).toBe(true);
175
330
  });
176
- });
177
331
 
178
- describe('UiNavDrawerHeader / Footer', () => {
179
- it('render projected content', async () => {
180
- const header = await render(UiNavDrawerHeader);
181
- const footer = await render(UiNavDrawerFooter);
182
- expect(header.container.querySelector('view')).toBeTruthy();
183
- expect(footer.container.querySelector('view')).toBeTruthy();
332
+ it('stops its close sequence when it is destroyed while closing', async () => {
333
+ const warn = vi.spyOn(console, 'warn');
334
+ const { host } = await renderDrawer();
335
+ host.open.set(true);
336
+ await after(OPENED);
337
+ host.open.set(false);
338
+ await after(0);
339
+ // Destroyed during the close animation. Its close timer
340
+ // (DURATION.normal + 20) used to fire anyway and emit `closed` from the
341
+ // destroyed output, which Angular warns about in dev builds.
342
+ host.shown.set(false);
343
+ await waitForUpdate();
344
+ await after(CLOSED);
345
+ const warnings = warn.mock.calls.map(([message]) => String(message));
346
+ warn.mockRestore();
347
+ expect(warnings.filter((message) => message.includes('NG0953'))).toEqual(
348
+ [],
349
+ );
350
+ expect(host.closes()).toBe(0);
184
351
  });
185
352
  });
186
353
 
187
354
  describe('UiNavDrawerContent', () => {
188
- it('renders the scroll-view', async () => {
189
- const parentResult = await render(UiNavDrawer);
190
- const parent = parentResult.componentRef.instance as UiNavDrawer;
191
- const contentResult = await render(UiNavDrawerContent, {
192
- providers: [{ provide: UiNavDrawer, useValue: parent }],
193
- });
194
- expect(contentResult.container.querySelector('scroll-view')).toBeTruthy();
355
+ it('scrolls the active item into view when the drawer opens', async () => {
356
+ const { host } = await renderDrawer();
357
+ const scroll = vi.spyOn(
358
+ host.settings().containerRef()!.nativeElement,
359
+ 'invoke',
360
+ );
361
+ host.open.set(true);
362
+ await after(OPENED);
363
+ // Instantly and centered, so the drawer slides in already scrolled.
364
+ expect(scroll).toHaveBeenCalledWith(
365
+ 'scrollIntoView',
366
+ expect.objectContaining({ block: 'center', behavior: 'auto' }),
367
+ );
195
368
  });
196
369
 
197
- it('onContentLayout scrolls active item into view when drawer is open', async () => {
198
- const parentResult = await render(UiNavDrawer);
199
- const parent = parentResult.componentRef.instance as UiNavDrawer;
200
- parent.open.set(true);
201
- await waitForUpdate();
370
+ it('scrolls the active item into view once its content is laid out', async () => {
371
+ const { host, scrollView } = await renderDrawer();
372
+ host.open.set(true);
373
+ await after(OPENED);
374
+ const scroll = vi.spyOn(
375
+ host.settings().containerRef()!.nativeElement,
376
+ 'invoke',
377
+ );
378
+ fireEvent.contentsizechanged(scrollView());
379
+ expect(scroll).toHaveBeenCalledWith('scrollIntoView', expect.any(Object));
380
+ });
202
381
 
203
- const contentResult = await render(UiNavDrawerContent, {
204
- providers: [{ provide: UiNavDrawer, useValue: parent }],
205
- });
206
- const content = contentResult.componentRef.instance as UiNavDrawerContent;
207
- (content as unknown as { onContentLayout: () => void }).onContentLayout();
208
- expect(parent.open()).toBe(true);
382
+ it("doesn't scroll while the drawer is closed", async () => {
383
+ const { host, scrollView } = await renderDrawer();
384
+ const scroll = vi.spyOn(
385
+ host.settings().containerRef()!.nativeElement,
386
+ 'invoke',
387
+ );
388
+ // Content collapsing as the overlay hides relays out too.
389
+ fireEvent.contentsizechanged(scrollView());
390
+ await after(OPENED);
391
+ expect(scroll).not.toHaveBeenCalled();
209
392
  });
210
393
 
211
- it('onContentLayout is a no-op when drawer is closed', async () => {
212
- const parentResult = await render(UiNavDrawer);
213
- const parent = parentResult.componentRef.instance as UiNavDrawer;
214
- const contentResult = await render(UiNavDrawerContent, {
215
- providers: [{ provide: UiNavDrawer, useValue: parent }],
216
- });
217
- const content = contentResult.componentRef.instance as UiNavDrawerContent;
218
- expect(() =>
219
- (content as unknown as { onContentLayout: () => void }).onContentLayout(),
220
- ).not.toThrow();
394
+ it("doesn't scroll when no item is active", async () => {
395
+ const { host } = await renderDrawer();
396
+ host.active.set('');
397
+ const scroll = vi.spyOn(
398
+ host.settings().containerRef()!.nativeElement,
399
+ 'invoke',
400
+ );
401
+ host.open.set(true);
402
+ await after(OPENED);
403
+ expect(scroll).not.toHaveBeenCalled();
221
404
  });
222
405
  });
223
406
 
224
407
  describe('UiNavDrawerItem', () => {
225
- const renderItem = async (autoClose = true) => {
226
- const parentResult = await render(UiNavDrawer);
227
- const parent = parentResult.componentRef.instance as UiNavDrawer;
228
- parent.open.set(true);
229
- await waitForUpdate();
408
+ it('marks the active item with the accent background', async () => {
409
+ const { host, item } = await renderDrawer();
410
+ expect(item('Settings').classList).toContain('bg-accent');
411
+ expect(item('Home').classList).toContain('bg-transparent');
230
412
 
231
- const itemResult = await render(UiNavDrawerItem, {
232
- providers: [{ provide: UiNavDrawer, useValue: parent }],
233
- });
234
- const item = itemResult.componentRef.instance as UiNavDrawerItem;
235
- setInputSignal(item.autoClose, autoClose);
413
+ host.active.set('home');
236
414
  await waitForUpdate();
237
- return { parent, item, itemResult };
238
- };
415
+ expect(item('Home').classList).toContain('bg-accent');
416
+ expect(item('Settings').classList).toContain('bg-transparent');
417
+ });
239
418
 
240
- it('onTap emits pressed and auto-closes the drawer by default', async () => {
241
- const { parent, item } = await renderItem();
242
- const emitted = vi.fn();
243
- item.pressed.subscribe(emitted);
244
- (item as unknown as { onTap: () => void }).onTap();
245
- expect(emitted).toHaveBeenCalledTimes(1);
246
- expect(parent.open()).toBe(false);
419
+ it('emits pressed and closes the drawer when tapped', async () => {
420
+ const { host, item } = await renderDrawer();
421
+ host.open.set(true);
422
+ await waitForUpdate();
423
+ fireEvent.tap(item('Home'));
424
+ expect(host.presses()).toBe(1);
425
+ expect(host.open()).toBe(false);
247
426
  });
248
427
 
249
- it('onTap does not auto-close when autoClose is false', async () => {
250
- const { parent, item } = await renderItem(false);
251
- (item as unknown as { onTap: () => void }).onTap();
252
- expect(parent.open()).toBe(true);
428
+ it('leaves the drawer open when tapped without autoClose', async () => {
429
+ const { host, item } = await renderDrawer();
430
+ host.open.set(true);
431
+ await waitForUpdate();
432
+ fireEvent.tap(item('Delete account'));
433
+ expect(host.open()).toBe(true);
253
434
  });
254
435
 
255
- it('press handlers run cleanly', async () => {
256
- const { item } = await renderItem();
257
- const inst = item as unknown as {
258
- onPressStart: () => void;
259
- onPressEnd: () => void;
260
- onPressCancel: () => void;
261
- };
262
- expect(() => inst.onPressStart()).not.toThrow();
263
- expect(() => inst.onPressEnd()).not.toThrow();
264
- expect(() => inst.onPressCancel()).not.toThrow();
436
+ it('scales down while pressed, and back on release', async () => {
437
+ const { item } = await renderDrawer();
438
+ const view = item('Home');
439
+
440
+ fireEvent.touchstart(view);
441
+ expect(scaleOf(view)).toBe('scale(0.985)');
442
+ fireEvent.touchend(view);
443
+ expect(scaleOf(view)).toBe('scale(1)');
444
+
445
+ // A scroll that takes the touch over cancels it.
446
+ fireEvent.touchstart(view);
447
+ fireEvent.touchcancel(view);
448
+ expect(scaleOf(view)).toBe('scale(1)');
449
+
450
+ // The mouse, on the web.
451
+ fireEvent.mousedown(view);
452
+ expect(scaleOf(view)).toBe('scale(0.985)');
453
+ fireEvent.mouseup(view);
454
+ expect(scaleOf(view)).toBe('scale(1)');
455
+ fireEvent.mousedown(view);
456
+ fireLynxEvent(view, 'mouseleave');
457
+ expect(scaleOf(view)).toBe('scale(1)');
265
458
  });
266
459
 
267
- it("destructive points the content's foreground at the destructive color", async () => {
268
- const { item, itemResult } = await renderItem();
269
- const view = () => itemResult.container.querySelector('view');
270
- expect(view()?.getAttribute('class')).not.toContain('--foreground');
271
- setInputSignal(item.variant, 'destructive');
272
- await waitForUpdate();
273
- expect(view()?.getAttribute('class')).toContain(
460
+ it('turns its label and icons destructive', async () => {
461
+ const { host, item, svg } = await renderDrawer();
462
+ const icon = () => item('Delete account').querySelector('svg')!;
463
+ // Labels styled text-foreground follow the redirected variable.
464
+ expect(item('Delete account').classList).toContain(
274
465
  '[--foreground:var(--destructive)]',
275
466
  );
276
- });
277
-
278
- it('gives projected icons the destructive color', async () => {
279
- const { item, itemResult } = await renderItem();
280
- const iconColor = itemResult.componentRef.injector.get(UI_ICON_COLOR);
281
- expect(iconColor()).toBeUndefined();
282
- setInputSignal(item.variant, 'destructive');
283
- expect(iconColor()).toBe('var(--destructive)');
284
- });
467
+ expect(icon().getAttribute('content')).toBe(
468
+ svg('.destructive-trash').getAttribute('content'),
469
+ );
285
470
 
286
- it('active item carries the accent background', async () => {
287
- const { item, itemResult } = await renderItem();
288
- setInputSignal(item.active, true);
471
+ host.deleteVariant.set('default');
289
472
  await waitForUpdate();
290
- const view = itemResult.container.querySelector('view');
291
- expect(view?.getAttribute('class')).toContain('bg-accent');
292
- });
293
-
294
- it('scrollIntoView calls invoke() on the container ref when populated', async () => {
295
- const { item, itemResult } = await renderItem();
296
- const invoke = vi.fn();
297
- (item as unknown as { containerRef: unknown }).containerRef = signal({
298
- nativeElement: { invoke },
299
- });
300
- item.scrollIntoView();
301
- expect(invoke).toHaveBeenCalledWith('scrollIntoView', expect.any(Object));
302
-
303
- // No-container path: containerRef is undefined so `?.` short-circuits.
304
- (item as unknown as { containerRef: () => undefined }).containerRef = () =>
305
- undefined;
306
- expect(() => item.scrollIntoView()).not.toThrow();
307
- void itemResult;
473
+ expect(item('Delete account').classList).not.toContain(
474
+ '[--foreground:var(--destructive)]',
475
+ );
476
+ expect(icon().getAttribute('content')).toBe(
477
+ svg('.plain-trash').getAttribute('content'),
478
+ );
308
479
  });
309
480
  });
310
481
 
311
482
  describe('UiNavDrawerTrigger', () => {
312
483
  it('is a labeled button for screen readers', async () => {
313
- // Only the first render is checked: under JIT, the nested <ui-icon>'s
314
- // static name="menu" never reaches its signal input, so its change
315
- // detection throws NG0950 and later updates don't land.
316
- const { container } = await render(UiNavDrawerTrigger);
317
- const trigger = container.querySelector('[accessibility-element]');
318
- expect(trigger?.getAttribute('accessibility-traits')).toBe('button');
319
- expect(trigger?.getAttribute('accessibility-label')).toBe(
320
- 'Open navigation menu',
484
+ const { trigger, getByRole } = await renderDrawer();
485
+ expect(getByRole('button', { name: 'Open navigation menu' })).toBe(
486
+ trigger(),
321
487
  );
322
488
  });
323
489
 
324
- it('onTap emits pressed', async () => {
325
- const { componentRef } = await render(UiNavDrawerTrigger);
326
- const inst = componentRef.instance as unknown as UiNavDrawerTrigger & {
327
- onTap: () => void;
328
- };
329
- const emitted = vi.fn();
330
- inst.pressed.subscribe(emitted);
331
- inst.onTap();
332
- expect(emitted).toHaveBeenCalledTimes(1);
333
- });
490
+ it('scales down while pressed, and back on release', async () => {
491
+ const { trigger } = await renderDrawer();
492
+ const view = trigger();
493
+
494
+ fireEvent.touchstart(view);
495
+ expect(scaleOf(view)).toBe('scale(0.97)');
496
+ fireEvent.touchend(view);
497
+ expect(scaleOf(view)).toBe('scale(1)');
498
+
499
+ fireEvent.touchstart(view);
500
+ fireEvent.touchcancel(view);
501
+ expect(scaleOf(view)).toBe('scale(1)');
334
502
 
335
- it('press handlers run cleanly', async () => {
336
- const { componentRef } = await render(UiNavDrawerTrigger);
337
- const inst = componentRef.instance as unknown as {
338
- onPressStart: () => void;
339
- onPressEnd: () => void;
340
- onPressCancel: () => void;
341
- };
342
- expect(() => inst.onPressStart()).not.toThrow();
343
- expect(() => inst.onPressEnd()).not.toThrow();
344
- expect(() => inst.onPressCancel()).not.toThrow();
503
+ fireEvent.mousedown(view);
504
+ expect(scaleOf(view)).toBe('scale(0.97)');
505
+ fireEvent.mouseup(view);
506
+ expect(scaleOf(view)).toBe('scale(1)');
507
+ fireEvent.mousedown(view);
508
+ fireLynxEvent(view, 'mouseleave');
509
+ expect(scaleOf(view)).toBe('scale(1)');
345
510
  });
346
511
  });