@blotch/dolan 22.2.3 → 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 (185) 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 +36 -3
  4. package/dist/commands/add.test.js.map +1 -1
  5. package/dist/commands/doctor.js +187 -7
  6. package/dist/commands/doctor.js.map +1 -1
  7. package/dist/commands/doctor.test.js +280 -7
  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 +107 -1
  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 +5 -0
  17. package/dist/commands/update.js +126 -45
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/commands/update.test.js +314 -2
  20. package/dist/commands/update.test.js.map +1 -1
  21. package/dist/lockfile.d.ts +7 -0
  22. package/dist/lockfile.js +36 -0
  23. package/dist/lockfile.js.map +1 -1
  24. package/dist/lockfile.test.d.ts +1 -0
  25. package/dist/lockfile.test.js +25 -0
  26. package/dist/lockfile.test.js.map +1 -0
  27. package/dist/registry.d.ts +6 -0
  28. package/dist/registry.js +48 -2
  29. package/dist/registry.js.map +1 -1
  30. package/dist/registry.test.js +45 -3
  31. package/dist/registry.test.js.map +1 -1
  32. package/dist/test-utils.d.ts +5 -0
  33. package/dist/test-utils.js +6 -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/components/accordion/accordion.test.ts +158 -144
  51. package/dist/ui/components/accordion/accordion.ts +19 -14
  52. package/dist/ui/components/action-sheet/action-sheet.test.ts +272 -187
  53. package/dist/ui/components/action-sheet/action-sheet.ts +40 -34
  54. package/dist/ui/components/alert/alert.test.ts +67 -19
  55. package/dist/ui/components/alert/alert.ts +14 -16
  56. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +206 -139
  57. package/dist/ui/components/alert-dialog/alert-dialog.ts +32 -26
  58. package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
  59. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  60. package/dist/ui/components/badge/badge.test.ts +57 -23
  61. package/dist/ui/components/badge/badge.ts +12 -14
  62. package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
  63. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +373 -297
  64. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +34 -33
  65. package/dist/ui/components/button/button.test.ts +611 -2
  66. package/dist/ui/components/button/button.ts +431 -62
  67. package/dist/ui/components/button/index.ts +2 -0
  68. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  69. package/dist/ui/components/button-group/button-group.ts +380 -68
  70. package/dist/ui/components/button-group/index.ts +3 -0
  71. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
  72. package/dist/ui/components/card/card.test.ts +82 -62
  73. package/dist/ui/components/chart/chart-presets.test.ts +197 -1
  74. package/dist/ui/components/chart/chart-presets.ts +8 -1
  75. package/dist/ui/components/chart/chart-theme.ts +12 -0
  76. package/dist/ui/components/chart/chart.test.ts +1017 -0
  77. package/dist/ui/components/chart/chart.ts +56 -43
  78. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  79. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  80. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  81. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  82. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  83. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  84. package/dist/ui/components/context-menu/index.ts +1 -0
  85. package/dist/ui/components/dialog/dialog.test.ts +210 -152
  86. package/dist/ui/components/dialog/dialog.ts +29 -23
  87. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  88. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  89. package/dist/ui/components/dock/dock.test.ts +749 -0
  90. package/dist/ui/components/dock/dock.ts +960 -0
  91. package/dist/ui/components/dock/index.ts +2 -0
  92. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  93. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  94. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  95. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  96. package/dist/ui/components/glass/glass-design.ts +119 -0
  97. package/dist/ui/components/glass/glass.test.ts +338 -0
  98. package/dist/ui/components/glass/glass.ts +360 -0
  99. package/dist/ui/components/glass/index.ts +18 -0
  100. package/dist/ui/components/icon/icon-theme.test.ts +36 -0
  101. package/dist/ui/components/icon/icon-theme.ts +36 -13
  102. package/dist/ui/components/icon/icon.test.ts +52 -41
  103. package/dist/ui/components/input/input.test.ts +178 -101
  104. package/dist/ui/components/input/input.ts +15 -12
  105. package/dist/ui/components/label/label.test.ts +11 -24
  106. package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
  107. package/dist/ui/components/list/list.test.ts +80 -49
  108. package/dist/ui/components/menu/index.ts +25 -0
  109. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  110. package/dist/ui/components/menu/menu-placement.ts +159 -0
  111. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  112. package/dist/ui/components/menu/menu-popup.ts +544 -0
  113. package/dist/ui/components/menu/menu.test.ts +429 -0
  114. package/dist/ui/components/menu/menu.ts +415 -0
  115. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
  116. package/dist/ui/components/nav-drawer/nav-drawer.ts +72 -56
  117. package/dist/ui/components/pagination/pagination.test.ts +136 -86
  118. package/dist/ui/components/progress/progress.test.ts +102 -46
  119. package/dist/ui/components/progress/progress.ts +52 -54
  120. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  121. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  122. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
  123. package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
  124. package/dist/ui/components/select/select.test.ts +138 -78
  125. package/dist/ui/components/select/select.ts +17 -11
  126. package/dist/ui/components/separator/separator.test.ts +18 -17
  127. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  128. package/dist/ui/components/sheet/sheet.ts +9 -1
  129. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  130. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  131. package/dist/ui/components/spinner/spinner.test.ts +64 -46
  132. package/dist/ui/components/spinner/spinner.ts +16 -19
  133. package/dist/ui/components/switch/switch.test.ts +116 -46
  134. package/dist/ui/components/switch/switch.ts +8 -13
  135. package/dist/ui/components/tab-bar/index.ts +1 -0
  136. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  137. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  138. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  139. package/dist/ui/components/tabs/tabs.ts +110 -118
  140. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  141. package/dist/ui/components/textarea/textarea.ts +14 -12
  142. package/dist/ui/components/toast/toast.test.ts +434 -350
  143. package/dist/ui/components/toast/toast.ts +42 -51
  144. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  145. package/dist/ui/theme/chart-colors.test.ts +8 -3
  146. package/dist/ui/theme/dark.css +38 -2
  147. package/dist/ui/theme/default.css +55 -7
  148. package/dist/ui/theme/icon-colors.test.ts +8 -3
  149. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  150. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  151. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  152. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  153. package/dist/ui/theme/tailwind-plugin.ts +93 -29
  154. package/dist/ui/theme/tailwind.css +113 -0
  155. package/dist/ui/utils/animate.test.ts +13 -1
  156. package/dist/utils/analyze.d.ts +14 -0
  157. package/dist/utils/analyze.js +15 -0
  158. package/dist/utils/analyze.js.map +1 -1
  159. package/dist/utils/analyze.test.js +14 -1
  160. package/dist/utils/analyze.test.js.map +1 -1
  161. package/dist/utils/installed.d.ts +12 -0
  162. package/dist/utils/installed.js +16 -0
  163. package/dist/utils/installed.js.map +1 -1
  164. package/dist/utils/installed.test.js +17 -1
  165. package/dist/utils/installed.test.js.map +1 -1
  166. package/dist/utils/resolve-paths.d.ts +4 -4
  167. package/dist/utils/resolve-paths.js +5 -7
  168. package/dist/utils/resolve-paths.js.map +1 -1
  169. package/dist/utils/resolve-paths.test.js +9 -5
  170. package/dist/utils/resolve-paths.test.js.map +1 -1
  171. package/dist/utils/tailwind.d.ts +25 -0
  172. package/dist/utils/tailwind.js +51 -0
  173. package/dist/utils/tailwind.js.map +1 -0
  174. package/dist/utils/tailwind.test.d.ts +1 -0
  175. package/dist/utils/tailwind.test.js +71 -0
  176. package/dist/utils/tailwind.test.js.map +1 -0
  177. package/package.json +9 -1
  178. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
  179. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
  180. package/dist/ui/components/button/button-render.test.ts +0 -132
  181. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  182. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
  183. package/dist/ui/components/chart/chart-render.test.ts +0 -730
  184. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
  185. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
@@ -1,8 +1,11 @@
1
- import { Component } from '@angular/core';
2
- import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
1
+ import { Component, signal } 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
8
  import { describe, expect, it } from 'vitest';
5
- import { setInputSignal } from '../../../test-utils/set-input-signal';
6
9
  import {
7
10
  UiCard,
8
11
  UiCardContent,
@@ -13,7 +16,6 @@ import {
13
16
  } from './card';
14
17
 
15
18
  @Component({
16
- standalone: true,
17
19
  imports: [
18
20
  UiCard,
19
21
  UiCardHeader,
@@ -24,20 +26,47 @@ import {
24
26
  LYNX_ELEMENTS,
25
27
  ],
26
28
  template: `
27
- <ui-card>
28
- <ui-card-header>
29
- <ui-card-title>Title</ui-card-title>
30
- <ui-card-description>Desc</ui-card-description>
29
+ <ui-card [pressable]="pressable()" class="w-full">
30
+ <ui-card-header class="gap-1">
31
+ <ui-card-title class="text-lg">Title</ui-card-title>
32
+ <ui-card-description class="italic">Desc</ui-card-description>
31
33
  </ui-card-header>
32
- <ui-card-content><text>Body</text></ui-card-content>
33
- <ui-card-footer><text>Actions</text></ui-card-footer>
34
+ <ui-card-content class="pt-2"><text>Body</text></ui-card-content>
35
+ <ui-card-footer class="justify-end"><text>Actions</text></ui-card-footer>
34
36
  </ui-card>
35
37
  `,
36
38
  })
37
- class CardHost {}
39
+ class CardHost {
40
+ readonly pressable = signal(false);
41
+ }
42
+
43
+ /**
44
+ * The card's own view. Each component's host element renders as a view too,
45
+ * so it's found by the card's class rather than by being the first.
46
+ */
47
+ const cardView = (container: Element) => container.querySelector('.bg-card')!;
48
+
49
+ /**
50
+ * Fires a Lynx event `fireEvent` has no shortcut for, as Lynx dispatches it:
51
+ * `mouseleave` is the mouse's `touchcancel`.
52
+ */
53
+ const fireLynxEvent = (element: Element, name: string) =>
54
+ fireEvent(
55
+ element,
56
+ Object.assign(new Event(`bindEvent:${name}`), {
57
+ eventType: 'bindEvent',
58
+ eventName: name,
59
+ }),
60
+ );
61
+
62
+ /**
63
+ * The scale the card's inline style holds, if any.
64
+ */
65
+ const scaleOf = (view: Element) =>
66
+ (view as HTMLElement).style.getPropertyValue('transform');
38
67
 
39
68
  describe('UiCard', () => {
40
- it('renders each sub-component with projected content', async () => {
69
+ it('renders each part with its projected content', async () => {
41
70
  const { getByText } = await render(CardHost);
42
71
  expect(getByText('Title')).toBeTruthy();
43
72
  expect(getByText('Desc')).toBeTruthy();
@@ -45,66 +74,57 @@ describe('UiCard', () => {
45
74
  expect(getByText('Actions')).toBeTruthy();
46
75
  });
47
76
 
48
- it('renders without a host wrapper', async () => {
49
- const { container } = await render(UiCard);
50
- expect(container).toBeTruthy();
77
+ it("merges each part's class with its own", async () => {
78
+ const { container, getByText } = await render(CardHost);
79
+ expect(cardView(container).getAttribute('class')).toContain('bg-card');
80
+ expect(cardView(container).getAttribute('class')).toContain('w-full');
81
+ expect(getByText('Title').getAttribute('class')).toContain('text-lg');
82
+ expect(getByText('Desc').getAttribute('class')).toContain('italic');
51
83
  });
52
84
 
53
- it('press handlers are no-ops when pressable is false', async () => {
54
- const { componentRef } = await render(UiCard);
55
- const inst = componentRef.instance as unknown as {
56
- onPressStart: () => void;
57
- onPressEnd: () => void;
58
- onPressCancel: () => void;
59
- };
60
- expect(() => inst.onPressStart()).not.toThrow();
61
- expect(() => inst.onPressEnd()).not.toThrow();
62
- expect(() => inst.onPressCancel()).not.toThrow();
85
+ it("doesn't respond to presses unless it's pressable", async () => {
86
+ const { container } = await render(CardHost);
87
+ const view = cardView(container);
88
+ fireEvent.touchstart(view);
89
+ fireEvent.mousedown(view);
90
+ expect(scaleOf(view)).toBe('');
91
+ fireEvent.touchend(view);
92
+ fireEvent.touchcancel(view);
93
+ fireEvent.mouseup(view);
94
+ fireLynxEvent(view, 'mouseleave');
95
+ expect(scaleOf(view)).toBe('');
63
96
  });
64
97
 
65
- it('press handlers fire animations when pressable is true', async () => {
66
- const { componentRef } = await render(UiCard);
67
- const inst = componentRef.instance as UiCard;
68
- setInputSignal(inst.pressable, true);
98
+ it('scales down while pressed by touch, and back on release', async () => {
99
+ const { container, componentRef } = await render(CardHost);
100
+ (componentRef.instance as CardHost).pressable.set(true);
69
101
  await waitForUpdate();
70
- const cast = inst as unknown as {
71
- onPressStart: () => void;
72
- onPressEnd: () => void;
73
- onPressCancel: () => void;
74
- };
75
- // Run twice so `#pressAnim?.cancel()` gets exercised on the second call.
76
- cast.onPressStart();
77
- cast.onPressStart();
78
- cast.onPressEnd();
79
- cast.onPressCancel();
80
- // No assertion — coverage-only paths. Confirm no throw.
81
- expect(true).toBe(true);
82
- });
83
- });
102
+ const view = cardView(container);
84
103
 
85
- describe('Card sub-components', () => {
86
- it('UiCardHeader renders projected content', async () => {
87
- const { container } = await render(UiCardHeader);
88
- expect(container.querySelector('view')).toBeTruthy();
89
- });
104
+ fireEvent.touchstart(view);
105
+ expect(scaleOf(view)).toBe('scale(0.985)');
106
+ fireEvent.touchend(view);
107
+ expect(scaleOf(view)).toBe('scale(1)');
90
108
 
91
- it('UiCardTitle renders projected content', async () => {
92
- const { container } = await render(UiCardTitle);
93
- expect(container.querySelector('text')).toBeTruthy();
109
+ // A scroll that takes the touch over cancels it: the card scales back.
110
+ fireEvent.touchstart(view);
111
+ fireEvent.touchcancel(view);
112
+ expect(scaleOf(view)).toBe('scale(1)');
94
113
  });
95
114
 
96
- it('UiCardDescription renders projected content', async () => {
97
- const { container } = await render(UiCardDescription);
98
- expect(container.querySelector('text')).toBeTruthy();
99
- });
115
+ it('scales down while pressed by mouse, and back on release or leave', async () => {
116
+ const { container, componentRef } = await render(CardHost);
117
+ (componentRef.instance as CardHost).pressable.set(true);
118
+ await waitForUpdate();
119
+ const view = cardView(container);
100
120
 
101
- it('UiCardContent renders projected content', async () => {
102
- const { container } = await render(UiCardContent);
103
- expect(container.querySelector('view')).toBeTruthy();
104
- });
121
+ fireEvent.mousedown(view);
122
+ expect(scaleOf(view)).toBe('scale(0.985)');
123
+ fireEvent.mouseup(view);
124
+ expect(scaleOf(view)).toBe('scale(1)');
105
125
 
106
- it('UiCardFooter renders projected content', async () => {
107
- const { container } = await render(UiCardFooter);
108
- expect(container.querySelector('view')).toBeTruthy();
126
+ fireEvent.mousedown(view);
127
+ fireLynxEvent(view, 'mouseleave');
128
+ expect(scaleOf(view)).toBe('scale(1)');
109
129
  });
110
130
  });
@@ -1,5 +1,30 @@
1
+ import {
2
+ Component,
3
+ Injector,
4
+ type Type,
5
+ computed,
6
+ input,
7
+ output,
8
+ signal,
9
+ viewChild,
10
+ } from '@angular/core';
11
+ import {
12
+ fireEvent,
13
+ render,
14
+ waitForUpdate,
15
+ } from '@blotch/angular-lynx-testing-library';
16
+ import { defineChart, dot } from '@tanstack/charts/universal';
1
17
  import { describe, expect, it } from 'vitest';
2
- import { linearAxis, minSpacing, presetAxes } from './chart-presets';
18
+ import { UiChart, tooltip } from './chart';
19
+ import {
20
+ type CartesianPresetOptions,
21
+ PRESET_CHART_BINDINGS,
22
+ UiCartesianPreset,
23
+ type XYDatum,
24
+ linearAxis,
25
+ minSpacing,
26
+ presetAxes,
27
+ } from './chart-presets';
3
28
 
4
29
  /**
5
30
  * The configured domain of an axis config's scale instance.
@@ -137,3 +162,174 @@ describe('presetAxes', () => {
137
162
  expect(domainOf(axes.y)).toEqual([-10, 10]);
138
163
  });
139
164
  });
165
+
166
+ /**
167
+ * The smallest preset: a dot per datum on the shared axes, built the way the
168
+ * library's presets (`<ui-line-chart>`, …) build theirs.
169
+ */
170
+ @Component({
171
+ selector: 'test-dot-chart',
172
+ imports: [UiChart],
173
+ template: `
174
+ <ui-chart
175
+ #chart
176
+ [definition]="definition()"
177
+ ${PRESET_CHART_BINDINGS}
178
+ (pointSelect)="pointTap.emit(datumOf($event))"
179
+ />
180
+ `,
181
+ })
182
+ class DotChart extends UiCartesianPreset<XYDatum> {
183
+ readonly data = input.required<readonly XYDatum[]>();
184
+ /** The tapped datum, or `null` for a tap on empty space. */
185
+ readonly pointTap = output<XYDatum | null>();
186
+
187
+ protected readonly definition = computed(() => {
188
+ const options = this.commonOptions();
189
+ const data = this.data();
190
+ return defineChart({
191
+ marks: [dot(data, { x: 'x', y: 'y' })],
192
+ scales: presetAxes(options, {
193
+ x: data.map((d) => d.x),
194
+ y: data.map((d) => d.y),
195
+ }),
196
+ tooltip: options.tooltip === false ? false : tooltip,
197
+ });
198
+ });
199
+ }
200
+
201
+ @Component({
202
+ imports: [DotChart],
203
+ template: `
204
+ <test-dot-chart
205
+ [data]="data"
206
+ [width]="300"
207
+ [height]="160"
208
+ label="Sales"
209
+ class="mt-2"
210
+ [xAxisLabel]="xAxisLabel()"
211
+ yAxisLabel="Units"
212
+ [zoomable]="true"
213
+ zoomAxes="x"
214
+ (pointTap)="tapped.push($event)"
215
+ />
216
+ `,
217
+ })
218
+ class PresetHost {
219
+ readonly data: readonly XYDatum[] = [
220
+ { x: 0, y: 100 },
221
+ { x: 5, y: 300 },
222
+ { x: 10, y: 500 },
223
+ ];
224
+ readonly xAxisLabel = signal('Day');
225
+ /** Every `pointTap`, in order. */
226
+ readonly tapped: (XYDatum | null)[] = [];
227
+ readonly preset = viewChild.required(DotChart);
228
+ }
229
+
230
+ /**
231
+ * The chart's own view, which takes every touch.
232
+ */
233
+ const chartRoot = (container: Element) =>
234
+ container.querySelector('[accessibility-label]')!;
235
+
236
+ /**
237
+ * Where the chart drew each dot, read from its marks layer.
238
+ */
239
+ const dotsDrawn = (container: Element) =>
240
+ [
241
+ ...container
242
+ .querySelectorAll('svg')[1]!
243
+ .getAttribute('content')!
244
+ .matchAll(/<circle cx="([\d.-]+)" cy="([\d.-]+)"/g),
245
+ ].map(([, x, y]) => ({ x: Number(x), y: Number(y) }));
246
+
247
+ /**
248
+ * Touches a spot and lifts the finger right there: a tap.
249
+ */
250
+ const tap = async (root: Element, at: { x: number; y: number }) => {
251
+ fireEvent.touchstart(root, { touches: [at] });
252
+ fireEvent.touchend(root, { touches: [at] });
253
+ await waitForUpdate();
254
+ };
255
+
256
+ describe('UiCartesianPreset', () => {
257
+ it('starts every preset from the same defaults', () => {
258
+ // Angular builds a preset through the base's factory, with the preset's
259
+ // own type, so no preset runs that factory's default: build the base alone.
260
+ const base = Injector.create({
261
+ providers: [
262
+ {
263
+ provide: UiCartesianPreset,
264
+ useClass: UiCartesianPreset as unknown as Type<
265
+ UiCartesianPreset<XYDatum>
266
+ >,
267
+ },
268
+ ],
269
+ }).get(UiCartesianPreset);
270
+ const options = (
271
+ base as unknown as { commonOptions(): CartesianPresetOptions }
272
+ ).commonOptions();
273
+ expect(options).toEqual({
274
+ tickCount: 5,
275
+ showGrid: true,
276
+ showXGrid: false,
277
+ xAxisLabel: '',
278
+ yAxisLabel: '',
279
+ padding: {},
280
+ xMin: undefined,
281
+ xMax: undefined,
282
+ yMin: undefined,
283
+ yMax: undefined,
284
+ xTickFormat: undefined,
285
+ yTickFormat: undefined,
286
+ tooltip: true,
287
+ });
288
+ expect(base.height()).toBe(200);
289
+ expect(base.label()).toBe('Chart');
290
+ expect(base.zoomable()).toBe(false);
291
+ expect(base.zoomAxes()).toBe('xy');
292
+ });
293
+
294
+ it('passes the shared inputs through to its chart', async () => {
295
+ const { container, componentRef, getByText } = await render(PresetHost);
296
+ const root = chartRoot(container);
297
+ expect(root.getAttribute('accessibility-label')).toBe('Sales');
298
+ expect(root.getAttribute('class')).toContain('mt-2');
299
+ expect(root.getAttribute('style')).toContain('height: 160px');
300
+ expect(getByText('Units')).toBeTruthy();
301
+ expect(getByText('Day')).toBeTruthy();
302
+
303
+ (componentRef.instance as PresetHost).xAxisLabel.set('Week');
304
+ await waitForUpdate();
305
+ expect(getByText('Week')).toBeTruthy();
306
+ });
307
+
308
+ it('hands a tap over as the datum, or null off the points', async () => {
309
+ const { container, componentRef } = await render(PresetHost);
310
+ const host = componentRef.instance as PresetHost;
311
+ const root = chartRoot(container);
312
+ const middle = dotsDrawn(container)[1]!;
313
+ await tap(root, { x: middle.x + 1, y: middle.y + 1 });
314
+ await tap(root, { x: -100, y: -100 });
315
+ expect(host.tapped).toEqual([host.data[1], null]);
316
+ });
317
+
318
+ it('zooms its chart in and out, and resets it', async () => {
319
+ const { componentRef, queryByText } = await render(PresetHost);
320
+ const preset = (componentRef.instance as PresetHost).preset();
321
+ // The x axis spans 0–10; zoomed in around the middle, its last tick goes.
322
+ expect(queryByText('10')).not.toBeNull();
323
+ preset.zoomIn();
324
+ await waitForUpdate();
325
+ expect(queryByText('10')).toBeNull();
326
+ preset.zoomOut();
327
+ await waitForUpdate();
328
+ expect(queryByText('10')).not.toBeNull();
329
+
330
+ preset.zoomIn();
331
+ preset.resetZoom();
332
+ await waitForUpdate();
333
+ expect(queryByText('10')).not.toBeNull();
334
+ });
335
+ });
@@ -173,6 +173,13 @@ export const minSpacing = (values: readonly number[]): number => {
173
173
  return Number.isFinite(spacing) && spacing > 0 ? spacing : 1;
174
174
  };
175
175
 
176
+ /**
177
+ * `UiChart`, for `viewChild()`. A call rather than the class itself: for a
178
+ * class, Angular's test build keeps an arrow function that nothing calls,
179
+ * which an app's coverage report would show as uncovered.
180
+ */
181
+ const chartType = () => UiChart;
182
+
176
183
  /**
177
184
  * The inputs and zoom API every cartesian preset shares. A preset extends this
178
185
  * and adds its own data and mark inputs, and renders `<ui-chart #chart>`
@@ -212,7 +219,7 @@ export abstract class UiCartesianPreset<TDatum> {
212
219
  readonly zoomStep = input(1.4);
213
220
  readonly userClass = input<string>('', { alias: 'class' });
214
221
 
215
- protected readonly chart = viewChild.required(UiChart);
222
+ protected readonly chart = viewChild.required(chartType());
216
223
 
217
224
  /**
218
225
  * The shared inputs, as the options every preset's definition takes.
@@ -79,6 +79,12 @@ export const CHART_LIGHT_COLORS: ChartColorTokens = {
79
79
  'chart-3': 'rgba(39, 71, 84, 1)',
80
80
  'chart-4': 'rgba(232, 196, 104, 1)',
81
81
  'chart-5': 'rgba(244, 164, 98, 1)',
82
+ 'glass-tint': 'rgba(24, 24, 27, 1)',
83
+ 'glass-tint-foreground': 'rgba(250, 250, 250, 1)',
84
+ 'glass-foreground': 'rgba(9, 9, 11, 1)',
85
+ 'glass-muted-foreground': 'rgba(63, 63, 70, 1)',
86
+ 'glass-clear-foreground': 'rgba(255, 255, 255, 1)',
87
+ 'glass-dim': 'rgba(0, 0, 0, 0.35)',
82
88
  };
83
89
 
84
90
  export const CHART_DARK_COLORS: ChartColorTokens = {
@@ -106,6 +112,12 @@ export const CHART_DARK_COLORS: ChartColorTokens = {
106
112
  'chart-3': 'rgba(232, 140, 48, 1)',
107
113
  'chart-4': 'rgba(175, 87, 219, 1)',
108
114
  'chart-5': 'rgba(226, 54, 112, 1)',
115
+ 'glass-tint': 'rgba(250, 250, 250, 1)',
116
+ 'glass-tint-foreground': 'rgba(24, 24, 27, 1)',
117
+ 'glass-foreground': 'rgba(250, 250, 250, 1)',
118
+ 'glass-muted-foreground': 'rgba(212, 212, 216, 1)',
119
+ 'glass-clear-foreground': 'rgba(255, 255, 255, 1)',
120
+ 'glass-dim': 'rgba(0, 0, 0, 0.35)',
109
121
  };
110
122
 
111
123
  /**