@blotch/dolan 22.2.1 → 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.
Files changed (156) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +32 -10
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +129 -9
  5. package/dist/commands/add.test.js.map +1 -1
  6. package/dist/commands/diff.d.ts +2 -1
  7. package/dist/commands/diff.js +38 -25
  8. package/dist/commands/diff.js.map +1 -1
  9. package/dist/commands/diff.test.js +72 -11
  10. package/dist/commands/diff.test.js.map +1 -1
  11. package/dist/commands/doctor.js +41 -27
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +93 -9
  14. package/dist/commands/doctor.test.js.map +1 -1
  15. package/dist/commands/eject.js +7 -0
  16. package/dist/commands/eject.js.map +1 -1
  17. package/dist/commands/eject.test.js +39 -0
  18. package/dist/commands/eject.test.js.map +1 -1
  19. package/dist/commands/info.js +3 -2
  20. package/dist/commands/info.js.map +1 -1
  21. package/dist/commands/init.d.ts +37 -1
  22. package/dist/commands/init.js +149 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +285 -26
  25. package/dist/commands/init.test.js.map +1 -1
  26. package/dist/commands/install-packages.d.ts +6 -0
  27. package/dist/commands/install-packages.js +20 -0
  28. package/dist/commands/install-packages.js.map +1 -0
  29. package/dist/commands/install-packages.test.d.ts +1 -0
  30. package/dist/commands/install-packages.test.js +55 -0
  31. package/dist/commands/install-packages.test.js.map +1 -0
  32. package/dist/commands/list.js +21 -14
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/list.test.js +62 -5
  35. package/dist/commands/list.test.js.map +1 -1
  36. package/dist/commands/outdated.js +11 -10
  37. package/dist/commands/outdated.js.map +1 -1
  38. package/dist/commands/outdated.test.js +20 -5
  39. package/dist/commands/outdated.test.js.map +1 -1
  40. package/dist/commands/remove.d.ts +4 -0
  41. package/dist/commands/remove.js +17 -4
  42. package/dist/commands/remove.js.map +1 -1
  43. package/dist/commands/remove.test.js +53 -2
  44. package/dist/commands/remove.test.js.map +1 -1
  45. package/dist/commands/update.d.ts +6 -0
  46. package/dist/commands/update.js +59 -24
  47. package/dist/commands/update.js.map +1 -1
  48. package/dist/commands/update.test.js +273 -31
  49. package/dist/commands/update.test.js.map +1 -1
  50. package/dist/index.js +10 -3
  51. package/dist/index.js.map +1 -1
  52. package/dist/registry.d.ts +8 -0
  53. package/dist/registry.js +32 -24
  54. package/dist/registry.js.map +1 -1
  55. package/dist/registry.test.js +76 -37
  56. package/dist/registry.test.js.map +1 -1
  57. package/dist/testing/set-input-signal.d.ts +13 -0
  58. package/dist/testing/set-input-signal.js +20 -0
  59. package/dist/testing/set-input-signal.js.map +1 -0
  60. package/dist/testing/set-input-signal.test.d.ts +1 -0
  61. package/dist/testing/set-input-signal.test.js +20 -0
  62. package/dist/testing/set-input-signal.test.js.map +1 -0
  63. package/dist/ui/components/action-sheet/action-sheet.test.ts +48 -1
  64. package/dist/ui/components/action-sheet/action-sheet.ts +37 -9
  65. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +49 -2
  66. package/dist/ui/components/alert-dialog/alert-dialog.ts +43 -12
  67. package/dist/ui/components/area-chart/area-chart-render.test.ts +52 -110
  68. package/dist/ui/components/area-chart/area-chart.test.ts +59 -146
  69. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  70. package/dist/ui/components/area-chart/index.ts +2 -3
  71. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +47 -163
  72. package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -58
  73. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  74. package/dist/ui/components/bar-chart/index.ts +3 -3
  75. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +191 -9
  76. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +136 -20
  77. package/dist/ui/components/button/button-render.test.ts +19 -0
  78. package/dist/ui/components/button/button.test.ts +2 -15
  79. package/dist/ui/components/button/button.ts +18 -2
  80. package/dist/ui/components/button-group/button-group.test.ts +2 -21
  81. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +48 -163
  82. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +107 -85
  83. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  84. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  85. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  86. package/dist/ui/components/chart/chart-focus.ts +115 -0
  87. package/dist/ui/components/chart/chart-presets.test.ts +139 -0
  88. package/dist/ui/components/chart/chart-presets.ts +271 -0
  89. package/dist/ui/components/chart/chart-render.test.ts +730 -0
  90. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  91. package/dist/ui/components/chart/chart-scene.ts +483 -0
  92. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  93. package/dist/ui/components/chart/chart-theme.ts +198 -0
  94. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  95. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  96. package/dist/ui/components/chart/chart.ts +921 -0
  97. package/dist/ui/components/chart/index.ts +24 -0
  98. package/dist/ui/components/dialog/dialog.test.ts +49 -2
  99. package/dist/ui/components/dialog/dialog.ts +43 -12
  100. package/dist/ui/components/icon/icon-theme.test.ts +133 -0
  101. package/dist/ui/components/icon/icon-theme.ts +204 -0
  102. package/dist/ui/components/icon/icon.test.ts +51 -2
  103. package/dist/ui/components/icon/icon.ts +38 -21
  104. package/dist/ui/components/line-chart/index.ts +2 -3
  105. package/dist/ui/components/line-chart/line-chart-render.test.ts +69 -107
  106. package/dist/ui/components/line-chart/line-chart.test.ts +63 -67
  107. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  108. package/dist/ui/components/list/list.test.ts +69 -2
  109. package/dist/ui/components/list/list.ts +21 -15
  110. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +80 -1
  111. package/dist/ui/components/nav-drawer/nav-drawer.ts +66 -11
  112. package/dist/ui/components/pagination/pagination.test.ts +62 -0
  113. package/dist/ui/components/pagination/pagination.ts +55 -8
  114. package/dist/ui/components/scatter-chart/index.ts +2 -3
  115. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +51 -144
  116. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -47
  117. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  118. package/dist/ui/components/scroll-area/scroll-area.test.ts +37 -2
  119. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  120. package/dist/ui/components/spinner/spinner.test.ts +27 -0
  121. package/dist/ui/components/spinner/spinner.ts +33 -23
  122. package/dist/ui/components/toast/toast.test.ts +37 -0
  123. package/dist/ui/components/toast/toast.ts +20 -4
  124. package/dist/ui/theme/chart-colors.test.ts +30 -0
  125. package/dist/ui/theme/dark.css +8 -0
  126. package/dist/ui/theme/default.css +8 -0
  127. package/dist/ui/theme/icon-colors.test.ts +30 -0
  128. package/dist/ui/theme/tailwind-plugin.ts +10 -1
  129. package/dist/utils/installed.d.ts +17 -0
  130. package/dist/utils/installed.js +27 -0
  131. package/dist/utils/installed.js.map +1 -0
  132. package/dist/utils/installed.test.d.ts +1 -0
  133. package/dist/utils/installed.test.js +41 -0
  134. package/dist/utils/installed.test.js.map +1 -0
  135. package/dist/utils/interactive.d.ts +9 -0
  136. package/dist/utils/interactive.js +10 -0
  137. package/dist/utils/interactive.js.map +1 -0
  138. package/dist/utils/interactive.test.d.ts +1 -0
  139. package/dist/utils/interactive.test.js +24 -0
  140. package/dist/utils/interactive.test.js.map +1 -0
  141. package/dist/utils/packages.d.ts +36 -0
  142. package/dist/utils/packages.js +70 -0
  143. package/dist/utils/packages.js.map +1 -0
  144. package/dist/utils/packages.test.d.ts +1 -0
  145. package/dist/utils/packages.test.js +89 -0
  146. package/dist/utils/packages.test.js.map +1 -0
  147. package/dist/utils/resolve-paths.d.ts +17 -1
  148. package/dist/utils/resolve-paths.js +23 -3
  149. package/dist/utils/resolve-paths.js.map +1 -1
  150. package/dist/utils/resolve-paths.test.js +34 -2
  151. package/dist/utils/resolve-paths.test.js.map +1 -1
  152. package/package.json +5 -1
  153. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  154. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -549
  155. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  156. package/dist/ui/components/cartesian-chart/index.ts +0 -18
@@ -1,174 +1,59 @@
1
- import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
1
+ import { render } from '@blotch/angular-lynx-testing-library';
2
+ import { createChartRuntime } from '@tanstack/charts/runtime';
3
+ import type {
4
+ ChartDefinition,
5
+ ChartPoint,
6
+ SceneNode,
7
+ } from '@tanstack/charts/types';
2
8
  import { describe, expect, it, vi } from 'vitest';
3
9
  import { setInputSignal } from '../../../test-utils/set-input-signal';
4
- import { UiCartesianChart } from '../cartesian-chart/cartesian-chart';
5
- import {
6
- UiCandlestickChart,
7
- UiCandlestickSeries,
8
- type CandlestickPoint,
9
- } from './candlestick-chart';
10
-
11
- const period = (
12
- x: number,
13
- o: number,
14
- h: number,
15
- l: number,
16
- c: number,
17
- ): CandlestickPoint => ({ x, open: o, high: h, low: l, close: c });
10
+ import { UiCandlestickChart } from './candlestick-chart';
11
+
12
+ const data = [
13
+ { x: 0, open: 100, high: 104, low: 98, close: 102 },
14
+ { x: 1, open: 102, high: 103, low: 97, close: 99 },
15
+ ];
16
+
17
+ const render2 = (definition: ChartDefinition<any, any, any>) => {
18
+ const scene = createChartRuntime().render(definition, {
19
+ width: 300,
20
+ height: 200,
21
+ });
22
+ const found: SceneNode[] = [];
23
+ const walk = (nodes: readonly SceneNode[]) => {
24
+ for (const node of nodes) {
25
+ if (node.kind === 'group') {
26
+ if (!node.focus) walk(node.children);
27
+ } else if (node.kind !== 'label') found.push(node);
28
+ }
29
+ };
30
+ walk(scene.nodes);
31
+ return { scene, found };
32
+ };
18
33
 
19
34
  describe('UiCandlestickChart', () => {
20
- it('renders with default configuration', async () => {
21
- const { container } = await render(UiCandlestickChart);
22
- expect(container).toBeTruthy();
23
- });
24
-
25
- it('derives the y-axis from the lowest low and highest high', async () => {
26
- const { componentRef } = await render(UiCandlestickChart);
27
- const inst = componentRef.instance as UiCandlestickChart;
28
- setInputSignal(inst.data, [
29
- period(0, 10, 15, 5, 12),
30
- period(1, 12, 20, 8, 18),
31
- period(2, 18, 22, 14, 16),
32
- ]);
33
- await waitForUpdate();
34
- const cast = inst as unknown as {
35
- xAxis: () => { domain: readonly [number, number] };
36
- yAxis: () => { domain: readonly [number, number] };
37
- effectivePadding: () => { left?: number; right?: number };
38
- };
39
- expect(cast.xAxis().domain[0]).toBeLessThanOrEqual(0);
40
- expect(cast.xAxis().domain[1]).toBeGreaterThanOrEqual(2);
41
- expect(cast.yAxis().domain[0]).toBeLessThanOrEqual(5);
42
- expect(cast.yAxis().domain[1]).toBeGreaterThanOrEqual(22);
43
- // 3 candles → half = 0.5 / 2 = 0.25
44
- expect(cast.effectivePadding().left).toBeCloseTo(0.25);
45
- expect(cast.effectivePadding().right).toBeCloseTo(0.25);
46
- });
47
-
48
- it('honors explicit xMin / xMax / yMin / yMax overrides', async () => {
49
- const { componentRef } = await render(UiCandlestickChart);
50
- const inst = componentRef.instance as UiCandlestickChart;
51
- setInputSignal(inst.data, [period(0, 10, 15, 5, 12)]);
52
- setInputSignal(inst.xMin, -5);
53
- setInputSignal(inst.xMax, 20);
54
- setInputSignal(inst.yMin, 0);
55
- setInputSignal(inst.yMax, 100);
56
- await waitForUpdate();
57
- const cast = inst as unknown as {
58
- xAxis: () => { domain: readonly [number, number] };
59
- yAxis: () => { domain: readonly [number, number] };
60
- };
61
- expect(cast.xAxis().domain[0]).toBeLessThanOrEqual(-5);
62
- expect(cast.xAxis().domain[1]).toBeGreaterThanOrEqual(20);
63
- expect(cast.yAxis().domain[0]).toBeLessThanOrEqual(0);
64
- expect(cast.yAxis().domain[1]).toBeGreaterThanOrEqual(100);
65
- });
66
-
67
- it('single-datum series drops the half-candle padding', async () => {
68
- const { componentRef } = await render(UiCandlestickChart);
69
- const inst = componentRef.instance as UiCandlestickChart;
70
- setInputSignal(inst.data, [period(0, 10, 15, 5, 12)]);
71
- await waitForUpdate();
72
- const cast = inst as unknown as {
73
- effectivePadding: () => { left?: number; right?: number };
74
- };
75
- expect(cast.effectivePadding().left).toBe(0);
76
- expect(cast.effectivePadding().right).toBe(0);
77
- });
35
+ type Internals = {
36
+ definition: () => ChartDefinition<any, any, any>;
37
+ onSelect(point: ChartPoint | null): void;
38
+ };
78
39
 
79
- it('user padding overrides the auto half-candle inset per side', async () => {
40
+ it('builds its definition from its inputs', async () => {
80
41
  const { componentRef } = await render(UiCandlestickChart);
81
- const inst = componentRef.instance as UiCandlestickChart;
82
- setInputSignal(inst.data, [
83
- period(0, 10, 15, 5, 12),
84
- period(1, 12, 20, 8, 18),
85
- ]);
86
- setInputSignal(inst.padding, { right: 0.05 });
87
- await waitForUpdate();
88
- const cast = inst as unknown as {
89
- effectivePadding: () => { left?: number; right?: number };
90
- };
91
- expect(cast.effectivePadding().right).toBeCloseTo(0.05);
92
- expect(cast.effectivePadding().left).toBeCloseTo(0.5);
42
+ const chart = componentRef.instance as UiCandlestickChart;
43
+ setInputSignal(chart.data, data);
44
+ setInputSignal(chart.upColor, 'blue');
45
+ const { found } = render2((chart as unknown as Internals).definition());
46
+ expect(found.filter((n) => n.kind === 'rect')[0]!.style?.fill).toBe('blue');
93
47
  });
94
48
 
95
- it('exposes the default tick formatter used by the child cartesian chart', async () => {
49
+ it('emits the tapped datum', async () => {
96
50
  const { componentRef } = await render(UiCandlestickChart);
97
- const inst = componentRef.instance as UiCandlestickChart;
98
- expect(inst.xTickFormat()(3.14159)).toBe('3.14');
99
- expect(inst.yTickFormat()(1.005)).toBe('1');
100
- });
101
- });
102
-
103
- describe('UiCandlestickSeries', () => {
104
- const renderSeries = async () => {
105
- const parentResult = await render(UiCartesianChart);
106
- const parent = parentResult.componentRef.instance as UiCartesianChart;
107
- setInputSignal(parent.xDomain, [0, 10]);
108
- setInputSignal(parent.yDomain, [0, 100]);
109
- await waitForUpdate();
110
- const seriesResult = await render(UiCandlestickSeries, {
111
- providers: [{ provide: UiCartesianChart, useValue: parent }],
112
- });
113
- const series = seriesResult.componentRef.instance as UiCandlestickSeries;
114
- setInputSignal(series.data, [
115
- // up candle (close >= open)
116
- period(0, 10, 20, 5, 15),
117
- // down candle (close < open)
118
- period(5, 60, 70, 50, 55),
119
- // doji (close === open)
120
- period(10, 40, 50, 30, 40),
121
- ]);
122
- await waitForUpdate();
123
- return { parent, series };
124
- };
125
-
126
- it('renders a wick + body for each period, tinted by direction', async () => {
127
- const { series } = await renderSeries();
128
- const cast = series as unknown as {
129
- candles: () => {
130
- wickStyle: string;
131
- bodyStyle: string;
132
- point: CandlestickPoint;
133
- }[];
134
- };
135
- const candles = cast.candles();
136
- expect(candles.length).toBe(3);
137
- // First: up candle → uses upColor (green rgba)
138
- expect(candles[0].bodyStyle).toContain('rgba(34, 197, 94, 1)');
139
- // Second: down candle → uses downColor (red rgba)
140
- expect(candles[1].bodyStyle).toContain('rgba(239, 68, 68, 1)');
141
- // Third: doji (open === close) is `up: true` (>= comparison)
142
- expect(candles[2].bodyStyle).toContain('rgba(34, 197, 94, 1)');
143
- });
144
-
145
- it('emits candleTap with the source point when a body is tapped', async () => {
146
- const { series } = await renderSeries();
147
- const cb = vi.fn();
148
- series.candleTap.subscribe(cb);
149
- const p = period(1, 2, 3, 1, 2);
150
- (
151
- series as unknown as { onCandleTap: (point: CandlestickPoint) => void }
152
- ).onCandleTap(p);
153
- expect(cb).toHaveBeenCalledWith(p);
154
- });
155
-
156
- it('single-datum series uses the fallback body width', async () => {
157
- const parentResult = await render(UiCartesianChart);
158
- const parent = parentResult.componentRef.instance as UiCartesianChart;
159
- setInputSignal(parent.xDomain, [0, 10]);
160
- setInputSignal(parent.yDomain, [0, 100]);
161
- await waitForUpdate();
162
- const seriesResult = await render(UiCandlestickSeries, {
163
- providers: [{ provide: UiCartesianChart, useValue: parent }],
164
- });
165
- const series = seriesResult.componentRef.instance as UiCandlestickSeries;
166
- setInputSignal(series.data, [period(5, 10, 15, 5, 12)]);
167
- await waitForUpdate();
168
- const cast = series as unknown as {
169
- candles: () => { bodyStyle: string }[];
170
- };
171
- // fallback body width = 8px
172
- expect(cast.candles()[0].bodyStyle).toContain('width: 8.00px');
51
+ const chart = componentRef.instance as UiCandlestickChart;
52
+ const tapped = vi.fn();
53
+ chart.candleTap.subscribe(tapped);
54
+ const internals = chart as unknown as Internals;
55
+ internals.onSelect({ datum: data[0] } as ChartPoint);
56
+ internals.onSelect(null);
57
+ expect(tapped.mock.calls).toEqual([[data[0]]]);
173
58
  });
174
59
  });
@@ -1,96 +1,118 @@
1
- import { describe, expect, it, vi } from 'vitest';
1
+ import { createChartRuntime } from '@tanstack/charts/runtime';
2
+ import { createChartTooltipContent } from '@tanstack/charts/tooltip/model';
3
+ import type {
4
+ ChartDefinition,
5
+ ChartTooltipOptions,
6
+ SceneNode,
7
+ } from '@tanstack/charts/types';
8
+ import { describe, expect, it } from 'vitest';
9
+ import { candlestickChartDefinition } from './candlestick-chart';
2
10
 
3
- // Mock Angular (and the transitively-loaded cartesian-chart Angular usage) so
4
- // importing the module needs no framework runtime. Only the exported pure
5
- // `computeCandles` is exercised; the components are never built.
6
- vi.mock('@angular/core', () => ({
7
- Component: () => () => {},
8
- ViewEncapsulation: { None: 0 },
9
- computed: () => () => {},
10
- inject: () => ({}),
11
- input: () => () => {},
12
- output: () => () => {},
13
- }));
11
+ const data = [
12
+ { x: 0, open: 100, high: 104, low: 98, close: 102 },
13
+ { x: 1, open: 102, high: 103, low: 97, close: 99 },
14
+ ];
14
15
 
15
- vi.mock('@blotch/angular-lynx', () => ({
16
- LYNX_ELEMENTS: [],
17
- // cartesian-chart (imported transitively) registers these in its decorator.
18
- LynxGestureDetector: class {},
19
- PanGesture: class {},
20
- PinchGesture: class {},
21
- Gesture: { Simultaneous: () => ({}) },
22
- }));
23
-
24
- const { computeCandles } = await import('./candlestick-chart');
25
-
26
- // A rising candle centered at cx=50. Screen y grows downward, so the projected
27
- // highY (top of the wick) is the smallest pixel value and lowY the largest.
28
- // close is above open (up), so closeY < openY in pixels.
29
- const upCandle = {
30
- cx: 50,
31
- highY: 10, // high price → top of plot
32
- lowY: 90, // low price → bottom of plot
33
- openY: 70,
34
- closeY: 30,
35
- up: true,
36
- };
37
-
38
- describe('computeCandles', () => {
39
- it('draws the wick spanning high to low, centered on cx', () => {
40
- const [shape] = computeCandles([upCandle], 12);
41
- expect(shape.wick.top).toBe(10); // min(highY, lowY)
42
- expect(shape.wick.height).toBe(80); // |lowY - highY|
43
- expect(shape.wick.width).toBe(1); // default wickWidth
44
- // Centered: left = cx - wickWidth/2.
45
- expect(shape.wick.left).toBe(50 - 0.5);
46
- });
47
-
48
- it('draws the body between open and close, centered on cx', () => {
49
- const [shape] = computeCandles([upCandle], 12);
50
- expect(shape.body.top).toBe(30); // min(openY, closeY) — the higher price
51
- expect(shape.body.height).toBe(40); // |closeY - openY|
52
- expect(shape.body.width).toBe(12);
53
- expect(shape.body.left).toBe(50 - 6); // cx - bodyWidth/2
54
- expect(shape.up).toBe(true);
16
+ const render2 = (definition: ChartDefinition<any, any, any>) => {
17
+ const scene = createChartRuntime().render(definition, {
18
+ width: 300,
19
+ height: 200,
55
20
  });
21
+ const found: SceneNode[] = [];
22
+ const walk = (nodes: readonly SceneNode[]) => {
23
+ for (const node of nodes) {
24
+ if (node.kind === 'group') {
25
+ if (!node.focus) walk(node.children);
26
+ } else if (node.kind !== 'label') found.push(node);
27
+ }
28
+ };
29
+ walk(scene.nodes);
30
+ return { scene, found };
31
+ };
56
32
 
57
- it('marks a falling candle as down and still tops the body at the higher price', () => {
58
- // close below open: closeY (pixels) is BELOW openY, i.e. larger.
59
- const downCandle = {
60
- cx: 20,
61
- highY: 5,
62
- lowY: 95,
63
- openY: 30,
64
- closeY: 60,
65
- up: false,
66
- };
67
- const [shape] = computeCandles([downCandle], 10);
68
- expect(shape.up).toBe(false);
69
- expect(shape.body.top).toBe(30); // min(openY, closeY) = openY here
70
- expect(shape.body.height).toBe(30);
33
+ describe('candlestickChartDefinition', () => {
34
+ it('draws a wick and a body per candle, colored by direction', () => {
35
+ const { scene, found } = render2(candlestickChartDefinition({ data }));
36
+ const bodies = found.filter((n) => n.kind === 'rect');
37
+ expect(bodies.map((body) => body.style?.fill)).toEqual([
38
+ 'rgba(34, 197, 94, 1)',
39
+ 'rgba(239, 68, 68, 1)',
40
+ ]);
41
+ // The y axis spans the lows and highs.
42
+ expect(scene.scales['y']!.domain[0]).toBeLessThanOrEqual(97);
43
+ expect(scene.scales['y']!.domain[1]).toBeGreaterThanOrEqual(104);
44
+ expect(found.some((n) => n.kind === 'rule' || n.kind === 'polyline')).toBe(
45
+ true,
46
+ );
71
47
  });
72
48
 
73
- it('clamps a doji (open === close) to the minimum body height', () => {
74
- const doji = {
75
- cx: 0,
76
- highY: 0,
77
- lowY: 100,
78
- openY: 50,
79
- closeY: 50,
80
- up: true,
49
+ it('lists all four prices in the tooltip', () => {
50
+ const definition = candlestickChartDefinition({
51
+ data,
52
+ upColor: 'green',
53
+ downColor: 'red',
54
+ });
55
+ const { scene } = render2(definition);
56
+ const point = scene.points.find((p) => (p.datum as { x: number }).x === 1)!;
57
+ const content = createChartTooltipContent(
58
+ [point],
59
+ scene,
60
+ false,
61
+ definition.tooltip as unknown as ChartTooltipOptions,
62
+ point,
63
+ );
64
+ expect(content).toEqual({
65
+ title: '1',
66
+ color: 'red',
67
+ rows: [
68
+ { label: 'Open', value: '102' },
69
+ { label: 'High', value: '103' },
70
+ { label: 'Low', value: '97' },
71
+ { label: 'Close', value: '99' },
72
+ ],
73
+ });
74
+ const custom = (
75
+ definition.tooltip as unknown as Required<ChartTooltipOptions>
76
+ ).content;
77
+ const context = {
78
+ pinned: false,
79
+ xLabel: 'x',
80
+ yLabel: 'y',
81
+ formatX: (v: unknown) => `x${v}`,
82
+ formatY: (v: unknown) => `y${v}`,
81
83
  };
82
- // Zero-height body would be invisible; the floor keeps it a thin line.
83
- expect(computeCandles([doji], 12)[0].body.height).toBe(1);
84
- expect(computeCandles([doji], 12, 1, 3)[0].body.height).toBe(3);
85
- });
86
-
87
- it('honors a custom wick width', () => {
88
- const [shape] = computeCandles([upCandle], 12, 3);
89
- expect(shape.wick.width).toBe(3);
90
- expect(shape.wick.left).toBe(50 - 1.5);
84
+ expect(custom([], context)).toEqual({ title: undefined, rows: [] });
85
+ // Points of other marks (say, a moving average added on top) get x/y rows.
86
+ expect(
87
+ custom(
88
+ [{ datum: { x: 1, y: 2 }, xValue: 1, yValue: 2 } as never],
89
+ context,
90
+ ),
91
+ ).toEqual({ title: 'x1', rows: [{ label: 'y', value: 'y2' }] });
92
+ expect(
93
+ custom([{ datum: null, xValue: 3, yValue: 4 } as never], context),
94
+ ).toMatchObject({
95
+ title: 'x3',
96
+ });
97
+ const up = scene.points.find((p) => (p.datum as { x: number }).x === 0)!;
98
+ expect(
99
+ createChartTooltipContent(
100
+ [up],
101
+ scene,
102
+ false,
103
+ definition.tooltip as unknown as ChartTooltipOptions,
104
+ up,
105
+ ),
106
+ ).toMatchObject({ color: 'green' });
91
107
  });
92
108
 
93
- it('returns an empty array for no candles', () => {
94
- expect(computeCandles([], 12)).toEqual([]);
109
+ it('can drop the tooltip, and clamps the body ratio', () => {
110
+ const definition = candlestickChartDefinition({
111
+ data,
112
+ tooltip: false,
113
+ bodyWidthRatio: 5,
114
+ });
115
+ expect(definition.tooltip).toBe(false);
116
+ expect(render2(definition).found.length).toBeGreaterThan(0);
95
117
  });
96
118
  });