@blotch/dolan 22.2.2 → 22.2.3
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.
- package/dist/commands/add.d.ts +6 -2
- package/dist/commands/add.js +32 -10
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +129 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +41 -27
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +93 -9
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +149 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +285 -26
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.test.js.map +1 -1
- package/dist/commands/update.d.ts +6 -0
- package/dist/commands/update.js +59 -24
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +273 -31
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +8 -0
- package/dist/registry.js +32 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +76 -37
- package/dist/registry.test.js.map +1 -1
- package/dist/testing/set-input-signal.d.ts +13 -0
- package/dist/testing/set-input-signal.js +20 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +48 -1
- package/dist/ui/components/action-sheet/action-sheet.ts +37 -9
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +49 -2
- package/dist/ui/components/alert-dialog/alert-dialog.ts +43 -12
- package/dist/ui/components/area-chart/area-chart-render.test.ts +52 -110
- package/dist/ui/components/area-chart/area-chart.test.ts +58 -123
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +47 -163
- package/dist/ui/components/bar-chart/bar-chart.test.ts +83 -37
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +191 -9
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +136 -20
- package/dist/ui/components/button/button-render.test.ts +19 -0
- package/dist/ui/components/button/button.ts +18 -2
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +48 -163
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +106 -62
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +139 -0
- package/dist/ui/components/chart/chart-presets.ts +271 -0
- package/dist/ui/components/chart/chart-render.test.ts +730 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +198 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.ts +921 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/dialog/dialog.test.ts +49 -2
- package/dist/ui/components/dialog/dialog.ts +43 -12
- package/dist/ui/components/icon/icon-theme.test.ts +133 -0
- package/dist/ui/components/icon/icon-theme.ts +204 -0
- package/dist/ui/components/icon/icon.test.ts +51 -2
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart-render.test.ts +69 -107
- package/dist/ui/components/line-chart/line-chart.test.ts +63 -45
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +69 -2
- package/dist/ui/components/list/list.ts +21 -15
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +80 -1
- package/dist/ui/components/nav-drawer/nav-drawer.ts +66 -11
- package/dist/ui/components/pagination/pagination.test.ts +62 -0
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +51 -144
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -25
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +37 -2
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/spinner/spinner.test.ts +27 -0
- package/dist/ui/components/spinner/spinner.ts +33 -23
- package/dist/ui/components/toast/toast.test.ts +37 -0
- package/dist/ui/components/toast/toast.ts +20 -4
- package/dist/ui/theme/chart-colors.test.ts +30 -0
- package/dist/ui/theme/dark.css +8 -0
- package/dist/ui/theme/default.css +8 -0
- package/dist/ui/theme/icon-colors.test.ts +30 -0
- package/dist/ui/theme/tailwind-plugin.ts +10 -1
- package/dist/utils/installed.d.ts +17 -0
- package/dist/utils/installed.js +27 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +41 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +23 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +34 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/package.json +5 -1
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
3
|
+
import { scaleBand } from '@tanstack/charts/scales/band';
|
|
4
|
+
import { scaleLinear } from '@tanstack/charts/scales/linear';
|
|
5
|
+
import type { ChartDefinition, ChartPoint } from '@tanstack/charts/types';
|
|
6
|
+
import {
|
|
7
|
+
barY,
|
|
8
|
+
defineChart,
|
|
9
|
+
dot,
|
|
10
|
+
lineY,
|
|
11
|
+
ruleY,
|
|
12
|
+
} from '@tanstack/charts/universal';
|
|
13
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
14
|
+
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
15
|
+
import { LynxTheme } from '@blotch/angular-lynx';
|
|
16
|
+
import {
|
|
17
|
+
UiChart,
|
|
18
|
+
UiChartTooltip,
|
|
19
|
+
type UiChartTooltipContext,
|
|
20
|
+
tooltip,
|
|
21
|
+
} from './chart';
|
|
22
|
+
import {
|
|
23
|
+
CHART_DARK_COLORS,
|
|
24
|
+
CHART_LIGHT_COLORS,
|
|
25
|
+
provideChartTheme,
|
|
26
|
+
} from './chart-theme';
|
|
27
|
+
|
|
28
|
+
const revenue = [
|
|
29
|
+
{ month: 'Jan', value: 40 },
|
|
30
|
+
{ month: 'Feb', value: 60 },
|
|
31
|
+
{ month: 'Mar', value: 80 },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const barChart = (extra: Record<string, unknown> = {}) =>
|
|
35
|
+
defineChart({
|
|
36
|
+
marks: [
|
|
37
|
+
ruleY([0]),
|
|
38
|
+
barY(revenue, { x: 'month', y: 'value', fill: 'var(--primary)' }),
|
|
39
|
+
],
|
|
40
|
+
scales: {
|
|
41
|
+
x: { scale: () => scaleBand().padding(0.2) },
|
|
42
|
+
y: {
|
|
43
|
+
scale: scaleLinear,
|
|
44
|
+
nice: true,
|
|
45
|
+
grid: true,
|
|
46
|
+
axis: { label: 'Revenue' },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
tooltip,
|
|
50
|
+
...extra,
|
|
51
|
+
}) as ChartDefinition<any, any, any>;
|
|
52
|
+
|
|
53
|
+
const series = [
|
|
54
|
+
{ x: 0, y: 1 },
|
|
55
|
+
{ x: 5, y: 3 },
|
|
56
|
+
{ x: 10, y: 2 },
|
|
57
|
+
];
|
|
58
|
+
const lineChart = (extra: Record<string, unknown> = {}) =>
|
|
59
|
+
defineChart({
|
|
60
|
+
marks: [lineY(series, { x: 'x', y: 'y' }), dot(series, { x: 'x', y: 'y' })],
|
|
61
|
+
scales: {
|
|
62
|
+
x: { scale: () => scaleLinear().domain([0, 10]) },
|
|
63
|
+
y: { scale: () => scaleLinear().domain([0, 4]) },
|
|
64
|
+
},
|
|
65
|
+
tooltip,
|
|
66
|
+
...extra,
|
|
67
|
+
}) as ChartDefinition<any, any, any>;
|
|
68
|
+
|
|
69
|
+
/** The component's protected members, as tests drive them. */
|
|
70
|
+
type ChartInternals = {
|
|
71
|
+
scene: () => {
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
points: ChartPoint[];
|
|
75
|
+
scales: Record<
|
|
76
|
+
string,
|
|
77
|
+
{ domain: unknown[]; viewport?: { domain: unknown[] } }
|
|
78
|
+
>;
|
|
79
|
+
} | null;
|
|
80
|
+
underSvg: () => string;
|
|
81
|
+
marksSvg: () => string;
|
|
82
|
+
overSvg: () => string;
|
|
83
|
+
labelSlots: () => { text: string; style: string }[];
|
|
84
|
+
tooltipView: () => {
|
|
85
|
+
style: string;
|
|
86
|
+
title: string | null;
|
|
87
|
+
titleColor?: string;
|
|
88
|
+
rows: readonly { label: string; value: string }[];
|
|
89
|
+
context: UiChartTooltipContext;
|
|
90
|
+
} | null;
|
|
91
|
+
accessibilityValue: () => string;
|
|
92
|
+
rootStyle: () => string;
|
|
93
|
+
layerStyle: () => string;
|
|
94
|
+
panGesture: { _callbacks: Record<string, (event?: unknown) => void> };
|
|
95
|
+
onLayout(event: unknown): void;
|
|
96
|
+
onTooltipLayout(event: unknown): void;
|
|
97
|
+
onTouchStart(event: unknown): void;
|
|
98
|
+
onTouchMove(event: unknown): void;
|
|
99
|
+
onTouchEnd(event: unknown): void;
|
|
100
|
+
onTouchCancel(): void;
|
|
101
|
+
onAccessibilityAction(event: unknown): void;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const touch = (x: number, y: number) => ({ touches: [{ x, y }] });
|
|
105
|
+
|
|
106
|
+
const renderChart = async (
|
|
107
|
+
definition: ChartDefinition<any, any, any> = barChart(),
|
|
108
|
+
inputs: Partial<
|
|
109
|
+
Record<
|
|
110
|
+
'width' | 'height' | 'zoomable' | 'zoomAxes' | 'colorScheme',
|
|
111
|
+
unknown
|
|
112
|
+
>
|
|
113
|
+
> = {},
|
|
114
|
+
) => {
|
|
115
|
+
const result = await render(UiChart);
|
|
116
|
+
const chart = result.componentRef.instance as UiChart;
|
|
117
|
+
setInputSignal(chart.definition, definition);
|
|
118
|
+
setInputSignal(chart.width, inputs.width ?? 300);
|
|
119
|
+
setInputSignal(chart.height, inputs.height ?? 200);
|
|
120
|
+
for (const [name, value] of Object.entries(inputs)) {
|
|
121
|
+
if (name !== 'width' && name !== 'height') {
|
|
122
|
+
setInputSignal(
|
|
123
|
+
(chart as unknown as Record<string, unknown>)[name],
|
|
124
|
+
value,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await waitForUpdate();
|
|
129
|
+
return { ...result, chart, internals: chart as unknown as ChartInternals };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A point's touch position, nudged so it's clearly a touch near it.
|
|
134
|
+
*/
|
|
135
|
+
const pointAt = (internals: ChartInternals, index: number) => {
|
|
136
|
+
const point = internals.scene()!.points[index]!;
|
|
137
|
+
return { x: point.x + 1, y: point.y + 1 };
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
afterEach(() => {
|
|
141
|
+
vi.useRealTimers();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('UiChart', () => {
|
|
145
|
+
describe('rendering', () => {
|
|
146
|
+
it('draws nothing until it has a size', async () => {
|
|
147
|
+
const result = await render(UiChart);
|
|
148
|
+
const chart = result.componentRef.instance as unknown as ChartInternals;
|
|
149
|
+
expect(chart.scene()).toBeNull();
|
|
150
|
+
expect(result.container.querySelectorAll('svg').length).toBe(0);
|
|
151
|
+
// Everything derived from the scene is empty, and input does nothing.
|
|
152
|
+
expect(chart.marksSvg()).toBe(chart.underSvg());
|
|
153
|
+
expect(chart.labelSlots()).toEqual([]);
|
|
154
|
+
expect(chart.layerStyle()).toContain('width: 0px; height: 0px;');
|
|
155
|
+
chart.onTouchStart(touch(1, 1));
|
|
156
|
+
chart.onTouchEnd(touch(1, 1));
|
|
157
|
+
expect(chart.tooltipView()).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('color scheme', () => {
|
|
161
|
+
const darkDevice = {
|
|
162
|
+
provide: LynxTheme,
|
|
163
|
+
useValue: { isDarkMode: () => true },
|
|
164
|
+
};
|
|
165
|
+
const primaryFill = async (providers: unknown[], scheme?: string) => {
|
|
166
|
+
const result = await render(UiChart, { providers: providers as never });
|
|
167
|
+
const chart = result.componentRef.instance as UiChart;
|
|
168
|
+
setInputSignal(chart.definition, barChart());
|
|
169
|
+
setInputSignal(chart.width, 300);
|
|
170
|
+
if (scheme) setInputSignal(chart.colorScheme, scheme);
|
|
171
|
+
return /fill="(rgba\([^)]*\))"/.exec(
|
|
172
|
+
(chart as unknown as ChartInternals).marksSvg(),
|
|
173
|
+
)?.[1];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
it('stays light on a dark device unless the app opts in', async () => {
|
|
177
|
+
expect(await primaryFill([darkDevice])).toBe(
|
|
178
|
+
CHART_LIGHT_COLORS['primary'],
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("follows the device with 'system'", async () => {
|
|
183
|
+
expect(await primaryFill([darkDevice], 'system')).toBe(
|
|
184
|
+
CHART_DARK_COLORS['primary'],
|
|
185
|
+
);
|
|
186
|
+
expect(
|
|
187
|
+
await primaryFill(
|
|
188
|
+
[{ provide: LynxTheme, useValue: { isDarkMode: () => false } }],
|
|
189
|
+
'system',
|
|
190
|
+
),
|
|
191
|
+
).toBe(CHART_LIGHT_COLORS['primary']);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('takes the scheme from the chart theme, a function included', async () => {
|
|
195
|
+
expect(
|
|
196
|
+
await primaryFill([
|
|
197
|
+
darkDevice,
|
|
198
|
+
provideChartTheme({ colorScheme: 'system' }),
|
|
199
|
+
]),
|
|
200
|
+
).toBe(CHART_DARK_COLORS['primary']);
|
|
201
|
+
expect(
|
|
202
|
+
await primaryFill([provideChartTheme({ colorScheme: () => 'dark' })]),
|
|
203
|
+
).toBe(CHART_DARK_COLORS['primary']);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('gives labels the foreground token when the theme sets no foreground', async () => {
|
|
208
|
+
const result = await render(UiChart, {
|
|
209
|
+
providers: [provideChartTheme({ chart: { foreground: undefined } })],
|
|
210
|
+
});
|
|
211
|
+
const chart = result.componentRef.instance as UiChart;
|
|
212
|
+
setInputSignal(chart.definition, barChart());
|
|
213
|
+
setInputSignal(chart.width, 300);
|
|
214
|
+
expect(
|
|
215
|
+
(chart as unknown as ChartInternals).labelSlots().length,
|
|
216
|
+
).toBeGreaterThan(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('exposes a tooltip token that creates nothing itself', () => {
|
|
220
|
+
expect(tooltip.create()).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('draws marks, empty focus layers and native labels', async () => {
|
|
224
|
+
const { container, internals } = await renderChart();
|
|
225
|
+
expect(container.querySelectorAll('svg').length).toBe(3);
|
|
226
|
+
expect(internals.marksSvg()).toContain('<rect');
|
|
227
|
+
expect(internals.marksSvg()).toContain(
|
|
228
|
+
`fill="${CHART_LIGHT_COLORS['primary']}"`,
|
|
229
|
+
);
|
|
230
|
+
expect(internals.underSvg()).toBe(internals.overSvg());
|
|
231
|
+
expect(internals.underSvg()).toContain('width="1"');
|
|
232
|
+
const texts = internals.labelSlots().map((label) => label.text);
|
|
233
|
+
expect(texts).toEqual(
|
|
234
|
+
expect.arrayContaining(['Jan', 'Feb', 'Mar', 'Revenue']),
|
|
235
|
+
);
|
|
236
|
+
expect(internals.rootStyle()).toBe('width: 300px; height: 200px;');
|
|
237
|
+
expect(internals.layerStyle()).toContain('width: 300px; height: 200px;');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('follows the color scheme', async () => {
|
|
241
|
+
const { internals } = await renderChart(barChart(), {
|
|
242
|
+
colorScheme: 'dark',
|
|
243
|
+
});
|
|
244
|
+
expect(internals.marksSvg()).toContain(
|
|
245
|
+
`fill="${CHART_DARK_COLORS['primary']}"`,
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('measures its layout when no size is given', async () => {
|
|
250
|
+
const result = await render(UiChart);
|
|
251
|
+
const chart = result.componentRef.instance as UiChart;
|
|
252
|
+
const internals = chart as unknown as ChartInternals;
|
|
253
|
+
setInputSignal(chart.definition, barChart());
|
|
254
|
+
expect(internals.rootStyle()).toBe('');
|
|
255
|
+
internals.onLayout({ detail: { width: 250, height: 120 } });
|
|
256
|
+
expect(internals.scene()).toMatchObject({ width: 250, height: 120 });
|
|
257
|
+
// Older Android reports the size in `params`.
|
|
258
|
+
internals.onLayout({ params: { width: 260, height: 130 } });
|
|
259
|
+
expect(internals.scene()).toMatchObject({ width: 260, height: 130 });
|
|
260
|
+
// The same size again and malformed events change nothing.
|
|
261
|
+
const scene = internals.scene();
|
|
262
|
+
internals.onLayout({ detail: { width: 260, height: 130 } });
|
|
263
|
+
internals.onLayout({ detail: {} });
|
|
264
|
+
internals.onLayout({});
|
|
265
|
+
expect(internals.scene()).toBe(scene);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('uses the default height when the layout has none', async () => {
|
|
269
|
+
const result = await render(UiChart);
|
|
270
|
+
const chart = result.componentRef.instance as UiChart;
|
|
271
|
+
const internals = chart as unknown as ChartInternals;
|
|
272
|
+
setInputSignal(chart.definition, barChart());
|
|
273
|
+
setInputSignal(chart.width, 100);
|
|
274
|
+
expect(internals.scene()).toMatchObject({ width: 100, height: 200 });
|
|
275
|
+
setInputSignal(chart.width, 0);
|
|
276
|
+
expect(internals.scene()).toBeNull();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('measures text with lynx.getTextInfo where the thread has it', async () => {
|
|
280
|
+
// render() switches to the testing environment's main thread, which
|
|
281
|
+
// copies that thread's globals over, so the stub goes there.
|
|
282
|
+
const mainLynx = (
|
|
283
|
+
globalThis as unknown as {
|
|
284
|
+
lynxTestingEnv: {
|
|
285
|
+
mainThread: { globalThis: { lynx: Record<string, unknown> } };
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
).lynxTestingEnv.mainThread.globalThis.lynx;
|
|
289
|
+
const original = mainLynx['getTextInfo'];
|
|
290
|
+
const getTextInfo = vi.fn(() => ({ width: 12 }));
|
|
291
|
+
mainLynx['getTextInfo'] = getTextInfo;
|
|
292
|
+
try {
|
|
293
|
+
const { internals } = await renderChart();
|
|
294
|
+
expect(internals.scene()).not.toBeNull();
|
|
295
|
+
expect(getTextInfo).toHaveBeenCalled();
|
|
296
|
+
} finally {
|
|
297
|
+
mainLynx['getTextInfo'] = original;
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('touch', () => {
|
|
303
|
+
it('focuses the touched point and shows the tooltip', async () => {
|
|
304
|
+
const { chart, internals } = await renderChart();
|
|
305
|
+
const focused = vi.fn();
|
|
306
|
+
chart.focusChange.subscribe(focused);
|
|
307
|
+
const at = pointAt(internals, 1);
|
|
308
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
309
|
+
expect(focused).toHaveBeenCalledWith(
|
|
310
|
+
expect.objectContaining({ xValue: 'Feb' }),
|
|
311
|
+
);
|
|
312
|
+
expect(internals.overSvg()).not.toContain('width="1"');
|
|
313
|
+
const view = internals.tooltipView()!;
|
|
314
|
+
expect(view.rows.map((row) => row.value)).toContain('60');
|
|
315
|
+
expect(view.style).toContain('opacity: 0;');
|
|
316
|
+
expect(internals.accessibilityValue()).toContain('Feb');
|
|
317
|
+
|
|
318
|
+
// Once laid out, the tooltip is placed for real and shown.
|
|
319
|
+
internals.onTooltipLayout({ detail: { width: 80, height: 40 } });
|
|
320
|
+
expect(internals.tooltipView()!.style).not.toContain('opacity');
|
|
321
|
+
internals.onTooltipLayout({ detail: { width: 80, height: 40 } });
|
|
322
|
+
internals.onTooltipLayout({});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('scrubs as the touch moves, without repeating focus events', async () => {
|
|
326
|
+
const { chart, internals } = await renderChart();
|
|
327
|
+
const focused = vi.fn();
|
|
328
|
+
chart.focusChange.subscribe(focused);
|
|
329
|
+
const first = pointAt(internals, 0);
|
|
330
|
+
const second = pointAt(internals, 2);
|
|
331
|
+
internals.onTouchStart(touch(first.x, first.y));
|
|
332
|
+
internals.onTouchMove(touch(first.x, first.y));
|
|
333
|
+
internals.onTouchMove(touch(second.x, second.y));
|
|
334
|
+
expect(focused.mock.calls.map(([p]) => p?.xValue)).toEqual([
|
|
335
|
+
'Jan',
|
|
336
|
+
'Mar',
|
|
337
|
+
]);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('ends a scrub without selecting, clearing its focus', async () => {
|
|
341
|
+
const { chart, internals } = await renderChart();
|
|
342
|
+
const selected = vi.fn();
|
|
343
|
+
chart.pointSelect.subscribe(selected);
|
|
344
|
+
const first = pointAt(internals, 0);
|
|
345
|
+
const last = pointAt(internals, 2);
|
|
346
|
+
internals.onTouchStart(touch(first.x, first.y));
|
|
347
|
+
internals.onTouchMove(touch(last.x, last.y));
|
|
348
|
+
internals.onTouchEnd({ changedTouches: [last] });
|
|
349
|
+
expect(selected).not.toHaveBeenCalled();
|
|
350
|
+
expect(internals.tooltipView()).toBeNull();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('keeps a pinned tooltip through a drag', async () => {
|
|
354
|
+
const { internals } = await renderChart();
|
|
355
|
+
const at = pointAt(internals, 1);
|
|
356
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
357
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
358
|
+
// A scroll that starts on the chart moves the finger well past a tap.
|
|
359
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
360
|
+
internals.onTouchEnd(touch(at.x, at.y - 100));
|
|
361
|
+
expect(internals.tooltipView()!.context.pinned).toBe(true);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('ignores touches without coordinates', async () => {
|
|
365
|
+
const { internals } = await renderChart();
|
|
366
|
+
internals.onTouchStart({});
|
|
367
|
+
internals.onTouchMove({ touches: [{}] });
|
|
368
|
+
internals.onTouchEnd({});
|
|
369
|
+
expect(internals.tooltipView()).toBeNull();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('pins the tooltip on tap, and unpins on the next tap', async () => {
|
|
373
|
+
const { chart, internals } = await renderChart();
|
|
374
|
+
const selected = vi.fn();
|
|
375
|
+
chart.pointSelect.subscribe(selected);
|
|
376
|
+
const at = pointAt(internals, 2);
|
|
377
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
378
|
+
internals.onTouchEnd({ changedTouches: [at] });
|
|
379
|
+
expect(selected).toHaveBeenLastCalledWith(
|
|
380
|
+
expect.objectContaining({ xValue: 'Mar' }),
|
|
381
|
+
);
|
|
382
|
+
expect(internals.tooltipView()!.context.pinned).toBe(true);
|
|
383
|
+
|
|
384
|
+
// While pinned, touches don't move focus.
|
|
385
|
+
const other = pointAt(internals, 0);
|
|
386
|
+
internals.onTouchStart(touch(other.x, other.y));
|
|
387
|
+
internals.onTouchMove(touch(other.x, other.y));
|
|
388
|
+
internals.onTouchCancel();
|
|
389
|
+
expect(
|
|
390
|
+
internals.tooltipView()!.primaryPoint ??
|
|
391
|
+
internals.tooltipView()!.context.primaryPoint,
|
|
392
|
+
).toMatchObject({ xValue: 'Mar' });
|
|
393
|
+
|
|
394
|
+
internals.onTouchEnd(touch(other.x, other.y));
|
|
395
|
+
expect(internals.tooltipView()).toBeNull();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('selects nothing for a tap on empty space', async () => {
|
|
399
|
+
const { chart, internals } = await renderChart(
|
|
400
|
+
barChart({ maxFocusDistance: 1 }),
|
|
401
|
+
);
|
|
402
|
+
const selected = vi.fn();
|
|
403
|
+
chart.pointSelect.subscribe(selected);
|
|
404
|
+
internals.onTouchStart(touch(-50, -50));
|
|
405
|
+
internals.onTouchEnd({});
|
|
406
|
+
expect(selected).toHaveBeenCalledWith(null);
|
|
407
|
+
expect(internals.tooltipView()).toBeNull();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("tells the definition's selection about taps", async () => {
|
|
411
|
+
const change = vi.fn();
|
|
412
|
+
const { internals } = await renderChart(
|
|
413
|
+
barChart({ selection: { change } }),
|
|
414
|
+
);
|
|
415
|
+
const at = pointAt(internals, 0);
|
|
416
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
417
|
+
expect(change).toHaveBeenCalledWith(
|
|
418
|
+
expect.objectContaining({ xValue: 'Jan' }),
|
|
419
|
+
'pointer',
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('keeps focus after a tap without a sticky tooltip', async () => {
|
|
424
|
+
const { internals } = await renderChart(
|
|
425
|
+
barChart({ tooltip: { use: tooltip, sticky: false } }),
|
|
426
|
+
);
|
|
427
|
+
const at = pointAt(internals, 0);
|
|
428
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
429
|
+
expect(internals.tooltipView()!.context.pinned).toBe(false);
|
|
430
|
+
internals.onTouchCancel();
|
|
431
|
+
expect(internals.tooltipView()).toBeNull();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('focuses without a tooltip when the definition has none', async () => {
|
|
435
|
+
const { internals } = await renderChart(barChart({ tooltip: false }));
|
|
436
|
+
const at = pointAt(internals, 0);
|
|
437
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
438
|
+
expect(internals.tooltipView()).toBeNull();
|
|
439
|
+
expect(internals.overSvg()).not.toContain('width="1"');
|
|
440
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
441
|
+
expect(internals.accessibilityValue()).toContain('Jan');
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('shows a pinned-only tooltip once pinned', async () => {
|
|
445
|
+
const { internals } = await renderChart(
|
|
446
|
+
barChart({ tooltip: { use: tooltip, visibility: 'pinned' } }),
|
|
447
|
+
);
|
|
448
|
+
const at = pointAt(internals, 0);
|
|
449
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
450
|
+
expect(internals.tooltipView()).toBeNull();
|
|
451
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
452
|
+
expect(internals.tooltipView()).not.toBeNull();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('shows a string tooltip as its title', async () => {
|
|
456
|
+
const { internals } = await renderChart(
|
|
457
|
+
barChart({ tooltip: { use: tooltip, format: () => 'Custom' } }),
|
|
458
|
+
);
|
|
459
|
+
const at = pointAt(internals, 0);
|
|
460
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
461
|
+
expect(internals.tooltipView()).toMatchObject({
|
|
462
|
+
title: 'Custom',
|
|
463
|
+
rows: [],
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('dismisses from the tooltip context', async () => {
|
|
468
|
+
const { internals } = await renderChart();
|
|
469
|
+
const at = pointAt(internals, 0);
|
|
470
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
471
|
+
internals.tooltipView()!.context.dismiss();
|
|
472
|
+
expect(internals.tooltipView()).toBeNull();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
describe('focus across scene changes', () => {
|
|
477
|
+
it('keeps focus on the same datum, and drops it when the datum is gone', async () => {
|
|
478
|
+
const { chart, internals } = await renderChart();
|
|
479
|
+
const at = pointAt(internals, 1);
|
|
480
|
+
internals.onTouchEnd(touch(at.x, at.y));
|
|
481
|
+
setInputSignal(chart.width, 320);
|
|
482
|
+
await waitForUpdate();
|
|
483
|
+
expect(internals.tooltipView()!.context.primaryPoint).toMatchObject({
|
|
484
|
+
xValue: 'Feb',
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
setInputSignal(
|
|
488
|
+
chart.definition,
|
|
489
|
+
defineChart({
|
|
490
|
+
marks: [
|
|
491
|
+
barY([{ month: 'Apr', value: 1 }], { x: 'month', y: 'value' }),
|
|
492
|
+
],
|
|
493
|
+
scales: {
|
|
494
|
+
x: { scale: () => scaleBand() },
|
|
495
|
+
y: { scale: scaleLinear },
|
|
496
|
+
},
|
|
497
|
+
tooltip,
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
await waitForUpdate();
|
|
501
|
+
expect(internals.tooltipView()).toBeNull();
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe('accessibility', () => {
|
|
506
|
+
it('steps through points with the screen reader actions', async () => {
|
|
507
|
+
const { chart, internals } = await renderChart();
|
|
508
|
+
const focused = vi.fn();
|
|
509
|
+
chart.focusChange.subscribe(focused);
|
|
510
|
+
const step = (name: string) =>
|
|
511
|
+
internals.onAccessibilityAction({ detail: { name } });
|
|
512
|
+
step('Next point');
|
|
513
|
+
step('Next point');
|
|
514
|
+
step('Previous point');
|
|
515
|
+
expect(focused.mock.calls.map(([p]) => p?.xValue)).toEqual([
|
|
516
|
+
'Jan',
|
|
517
|
+
'Feb',
|
|
518
|
+
'Jan',
|
|
519
|
+
]);
|
|
520
|
+
step('Activate');
|
|
521
|
+
internals.onAccessibilityAction({});
|
|
522
|
+
expect(focused).toHaveBeenCalledTimes(3);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('clears focus when there are no points to step to', async () => {
|
|
526
|
+
const { internals } = await renderChart(barChart({ focus: false }));
|
|
527
|
+
internals.onAccessibilityAction({ detail: { name: 'Next point' } });
|
|
528
|
+
expect(internals.accessibilityValue()).toBe('');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('describes values that are dates', async () => {
|
|
532
|
+
const dated = defineChart({
|
|
533
|
+
marks: [
|
|
534
|
+
dot([{ t: new Date(Date.UTC(2026, 0, 2)), v: 3 }], {
|
|
535
|
+
x: 't',
|
|
536
|
+
y: 'v',
|
|
537
|
+
}),
|
|
538
|
+
],
|
|
539
|
+
scales: { x: { scale: () => scaleBand() }, y: { scale: scaleLinear } },
|
|
540
|
+
}) as ChartDefinition<any, any, any>;
|
|
541
|
+
const { internals } = await renderChart(dated);
|
|
542
|
+
internals.onAccessibilityAction({ detail: { name: 'Next point' } });
|
|
543
|
+
expect(internals.accessibilityValue()).toContain('2026-01-02');
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe('zoom', () => {
|
|
548
|
+
const xDomain = (internals: ChartInternals) =>
|
|
549
|
+
internals.scene()!.scales['x']!.viewport?.domain ??
|
|
550
|
+
internals.scene()!.scales['x']!.domain;
|
|
551
|
+
|
|
552
|
+
it('zooms in and out with the controls, and resets', async () => {
|
|
553
|
+
const { chart, internals } = await renderChart(lineChart(), {
|
|
554
|
+
zoomable: true,
|
|
555
|
+
});
|
|
556
|
+
chart.zoomIn();
|
|
557
|
+
const [lo, hi] = xDomain(internals) as number[];
|
|
558
|
+
expect(hi! - lo!).toBeCloseTo(10 / 1.4);
|
|
559
|
+
chart.zoomOut();
|
|
560
|
+
expect(xDomain(internals)).toEqual([0, 10]);
|
|
561
|
+
chart.zoomIn();
|
|
562
|
+
chart.resetZoom();
|
|
563
|
+
expect(internals.scene()!.scales['x']!.viewport).toBeUndefined();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('only zooms the chosen axes, and only continuous ones', async () => {
|
|
567
|
+
const { chart, internals } = await renderChart(lineChart(), {
|
|
568
|
+
zoomable: true,
|
|
569
|
+
zoomAxes: 'y',
|
|
570
|
+
});
|
|
571
|
+
chart.zoomIn();
|
|
572
|
+
expect(internals.scene()!.scales['x']!.viewport).toBeUndefined();
|
|
573
|
+
expect(internals.scene()!.scales['y']!.viewport).toBeDefined();
|
|
574
|
+
|
|
575
|
+
// A band axis can't zoom.
|
|
576
|
+
const bars = await renderChart(barChart(), {
|
|
577
|
+
zoomable: true,
|
|
578
|
+
zoomAxes: 'x',
|
|
579
|
+
});
|
|
580
|
+
bars.chart.zoomIn();
|
|
581
|
+
expect(bars.internals.scene()!.scales['x']!.viewport).toBeUndefined();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('does nothing before the chart has a scene', async () => {
|
|
585
|
+
const result = await render(UiChart);
|
|
586
|
+
const chart = result.componentRef.instance as UiChart;
|
|
587
|
+
chart.zoomIn();
|
|
588
|
+
const internals = chart as unknown as ChartInternals;
|
|
589
|
+
internals.panGesture._callbacks['onBegin']!();
|
|
590
|
+
internals.panGesture._callbacks['onUpdate']!({
|
|
591
|
+
translationX: 10,
|
|
592
|
+
translationY: 0,
|
|
593
|
+
});
|
|
594
|
+
expect(internals.scene()).toBeNull();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('pans by dragging, from the window at touch-down', async () => {
|
|
598
|
+
const { chart, internals } = await renderChart(lineChart(), {
|
|
599
|
+
zoomable: true,
|
|
600
|
+
});
|
|
601
|
+
vi.useFakeTimers();
|
|
602
|
+
chart.zoomIn();
|
|
603
|
+
chart.zoomIn();
|
|
604
|
+
const start = xDomain(internals) as number[];
|
|
605
|
+
const callbacks = internals.panGesture._callbacks;
|
|
606
|
+
callbacks['onBegin']!();
|
|
607
|
+
callbacks['onStart']!();
|
|
608
|
+
const plotWidth = internals.scene()!.width;
|
|
609
|
+
callbacks['onUpdate']!({ translationX: 10, translationY: 5 });
|
|
610
|
+
callbacks['onUpdate']!({ translationX: 20, translationY: 10 });
|
|
611
|
+
const panned = xDomain(internals) as number[];
|
|
612
|
+
// Dragging right moves the window to lower values, by the same amount
|
|
613
|
+
// each frame relative to touch-down, not compounding.
|
|
614
|
+
expect(panned[0]!).toBeLessThan(start[0]!);
|
|
615
|
+
expect(panned[1]! - panned[0]!).toBeCloseTo(start[1]! - start[0]!);
|
|
616
|
+
expect(plotWidth).toBeGreaterThan(0);
|
|
617
|
+
|
|
618
|
+
// While panning, the label pool keeps its size and the tooltip hides.
|
|
619
|
+
const slots = internals.labelSlots().length;
|
|
620
|
+
callbacks['onUpdate']!({ translationX: 200, translationY: 0 });
|
|
621
|
+
expect(internals.labelSlots().length).toBe(slots);
|
|
622
|
+
callbacks['onEnd']!();
|
|
623
|
+
vi.runAllTimers();
|
|
624
|
+
expect(
|
|
625
|
+
internals
|
|
626
|
+
.labelSlots()
|
|
627
|
+
.every((slot) => !slot.style.includes('display: none')),
|
|
628
|
+
).toBe(true);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it('anchors a pan that starts without onBegin', async () => {
|
|
632
|
+
const { chart, internals } = await renderChart(lineChart(), {
|
|
633
|
+
zoomable: true,
|
|
634
|
+
});
|
|
635
|
+
vi.useFakeTimers();
|
|
636
|
+
chart.zoomIn();
|
|
637
|
+
const callbacks = internals.panGesture._callbacks;
|
|
638
|
+
callbacks['onStart']!();
|
|
639
|
+
callbacks['onEnd']!();
|
|
640
|
+
callbacks['onUpdate']!({ translationX: 0, translationY: 0 });
|
|
641
|
+
callbacks['onEnd']!();
|
|
642
|
+
vi.runAllTimers();
|
|
643
|
+
expect(xDomain(internals)).toBeDefined();
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('hides gesture-only spare labels while a pan holds the pool', async () => {
|
|
647
|
+
const { chart, internals } = await renderChart(lineChart(), {
|
|
648
|
+
zoomable: true,
|
|
649
|
+
});
|
|
650
|
+
vi.useFakeTimers();
|
|
651
|
+
const callbacks = internals.panGesture._callbacks;
|
|
652
|
+
callbacks['onBegin']!();
|
|
653
|
+
// Swap in a chart with fewer labels mid-gesture: the spare slots hide.
|
|
654
|
+
setInputSignal(
|
|
655
|
+
chart.definition,
|
|
656
|
+
lineChart({
|
|
657
|
+
scales: {
|
|
658
|
+
x: { scale: () => scaleLinear().domain([0, 10]), axis: false },
|
|
659
|
+
y: { scale: () => scaleLinear().domain([0, 4]), axis: false },
|
|
660
|
+
},
|
|
661
|
+
}),
|
|
662
|
+
);
|
|
663
|
+
expect(
|
|
664
|
+
internals
|
|
665
|
+
.labelSlots()
|
|
666
|
+
.some((slot) => slot.style.includes('display: none')),
|
|
667
|
+
).toBe(true);
|
|
668
|
+
callbacks['onEnd']!();
|
|
669
|
+
vi.runAllTimers();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("doesn't pan an axis that can't zoom", async () => {
|
|
673
|
+
const bars = await renderChart(barChart(), {
|
|
674
|
+
zoomable: true,
|
|
675
|
+
zoomAxes: 'x',
|
|
676
|
+
});
|
|
677
|
+
vi.useFakeTimers();
|
|
678
|
+
const callbacks = bars.internals.panGesture._callbacks;
|
|
679
|
+
callbacks['onBegin']!();
|
|
680
|
+
callbacks['onUpdate']!({ translationX: 30, translationY: 0 });
|
|
681
|
+
callbacks['onEnd']!();
|
|
682
|
+
vi.runAllTimers();
|
|
683
|
+
expect(bars.internals.scene()!.scales['x']!.viewport).toBeUndefined();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('treats only a short touch as a tap', async () => {
|
|
687
|
+
const { chart, internals } = await renderChart(lineChart(), {
|
|
688
|
+
zoomable: true,
|
|
689
|
+
});
|
|
690
|
+
const selected = vi.fn();
|
|
691
|
+
chart.pointSelect.subscribe(selected);
|
|
692
|
+
const at = pointAt(internals, 1);
|
|
693
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
694
|
+
internals.onTouchMove(touch(at.x + 40, at.y));
|
|
695
|
+
// A zoomable chart's drag pans: no scrubbing, no selection.
|
|
696
|
+
expect(internals.tooltipView()).toBeNull();
|
|
697
|
+
internals.onTouchEnd(touch(at.x + 40, at.y));
|
|
698
|
+
expect(selected).not.toHaveBeenCalled();
|
|
699
|
+
internals.onTouchStart(touch(at.x, at.y));
|
|
700
|
+
internals.onTouchEnd(touch(at.x + 2, at.y));
|
|
701
|
+
expect(selected).toHaveBeenCalledTimes(1);
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
describe('custom tooltip', () => {
|
|
706
|
+
@Component({
|
|
707
|
+
imports: [UiChart, UiChartTooltip],
|
|
708
|
+
template: `
|
|
709
|
+
<ui-chart>
|
|
710
|
+
<ng-template uiChartTooltip let-tooltip>
|
|
711
|
+
<text>{{ tooltip.points.length }} custom</text>
|
|
712
|
+
</ng-template>
|
|
713
|
+
</ui-chart>
|
|
714
|
+
`,
|
|
715
|
+
})
|
|
716
|
+
class Host {}
|
|
717
|
+
|
|
718
|
+
// Signal queries (contentChild) are only wired by ahead-of-time
|
|
719
|
+
// compilation, so under plain Vitest the chart never sees the template;
|
|
720
|
+
// the examples exercise it in a real build. Here: the directive mounts and
|
|
721
|
+
// types its context.
|
|
722
|
+
it('mounts the template directive', async () => {
|
|
723
|
+
const { container } = await render(Host);
|
|
724
|
+
expect(container.querySelector('view')).not.toBeNull();
|
|
725
|
+
expect(UiChartTooltip.ngTemplateContextGuard(null as never, {})).toBe(
|
|
726
|
+
true,
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
});
|