@363045841yyt/klinechart 0.7.3-alpha.5 → 0.7.5-alpha.2
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/README.md +149 -153
- package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +617 -569
- package/dist/klinechart.css +1 -1
- package/package.json +82 -76
- package/src/__tests__/_mockController.ts +192 -192
- package/src/__tests__/contract.test.ts +132 -132
- package/src/components/DrawingStyleToolbar.vue +199 -199
- package/src/components/IndicatorParams.vue +570 -570
- package/src/components/IndicatorSelector.vue +1169 -1042
- package/src/components/KLineChart.vue +1570 -1570
- package/src/components/KLineTooltip.vue +200 -200
- package/src/components/LeftToolbar.vue +844 -844
- package/src/components/MarkerTooltip.vue +155 -155
- package/src/components/index.ts +7 -7
- package/src/composables/useFullscreenTeleportTarget.ts +18 -18
- package/src/debug/canvasProfiler.ts +296 -296
- package/src/index.ts +401 -401
- package/src/version.ts +3 -3
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock ChartController for Vue adapter tests.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the framework-agnostic signal-bearing shape from
|
|
5
|
-
* @363045841yyt/klinechart-core without spinning up the real Chart engine.
|
|
6
|
-
*
|
|
7
|
-
* To keep this test file runnable from the repo root vitest (which does not
|
|
8
|
-
* alias @363045841yyt/klinechart-core), we inline a tiny `Signal` implementation
|
|
9
|
-
* that is shape-compatible with `packages/core/src/reactivity/signal.ts`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { Signal } from '@363045841yyt/klinechart-core/reactivity'
|
|
13
|
-
import type {
|
|
14
|
-
ActiveIndicator,
|
|
15
|
-
ChartController,
|
|
16
|
-
ChartMountOptions,
|
|
17
|
-
ChartViewport,
|
|
18
|
-
DrawingController,
|
|
19
|
-
DrawingState,
|
|
20
|
-
IndicatorDefinition,
|
|
21
|
-
IndicatorSelectorController,
|
|
22
|
-
KLineData,
|
|
23
|
-
ToolbarController,
|
|
24
|
-
ToolDefinition,
|
|
25
|
-
ToolId,
|
|
26
|
-
} from '@363045841yyt/klinechart-core'
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Inline mini-signal �?Object.is-equality, sync notify. Drop-in compatible
|
|
30
|
-
// with `@363045841yyt/klinechart-core/reactivity` for shape-only test purposes.
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
function createSignal<T>(initial: T): Signal<T> {
|
|
34
|
-
let value = initial
|
|
35
|
-
const subs = new Set<() => void>()
|
|
36
|
-
const read = (): T => value
|
|
37
|
-
const peek = (): T => value
|
|
38
|
-
const set = (next: T): void => {
|
|
39
|
-
if (Object.is(value, next)) return
|
|
40
|
-
value = next
|
|
41
|
-
for (const listener of [...subs]) listener()
|
|
42
|
-
}
|
|
43
|
-
const subscribe = (listener: () => void): (() => void) => {
|
|
44
|
-
subs.add(listener)
|
|
45
|
-
return () => {
|
|
46
|
-
subs.delete(listener)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return Object.assign(read, { peek, set, subscribe }) as Signal<T>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface MockChartController extends ChartController {
|
|
53
|
-
/** spy: how many times `dispose` was called */
|
|
54
|
-
disposeCalls: () => number
|
|
55
|
-
/** test-only signal mutators */
|
|
56
|
-
_setViewport: (vp: ChartViewport) => void
|
|
57
|
-
_setData: (data: ReadonlyArray<KLineData>) => void
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function createMockIndicatorSelector(): IndicatorSelectorController {
|
|
61
|
-
const catalog = createSignal<ReadonlyArray<IndicatorDefinition>>([])
|
|
62
|
-
const active = createSignal<ReadonlyArray<ActiveIndicator>>([])
|
|
63
|
-
const menuOpen = createSignal<boolean>(false)
|
|
64
|
-
const searchQuery = createSignal<string>('')
|
|
65
|
-
const filteredMain = createSignal<ReadonlyArray<IndicatorDefinition>>([])
|
|
66
|
-
const filteredSub = createSignal<ReadonlyArray<IndicatorDefinition>>([])
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
catalog,
|
|
70
|
-
active,
|
|
71
|
-
menuOpen,
|
|
72
|
-
searchQuery,
|
|
73
|
-
filteredMain,
|
|
74
|
-
filteredSub,
|
|
75
|
-
add: () => null,
|
|
76
|
-
remove: () => false,
|
|
77
|
-
updateParams: () => false,
|
|
78
|
-
reorder: () => false,
|
|
79
|
-
openMenu: () => menuOpen.set(true),
|
|
80
|
-
closeMenu: () => menuOpen.set(false),
|
|
81
|
-
toggleMenu: () => menuOpen.set(!menuOpen.peek()),
|
|
82
|
-
setSearchQuery: (q: string) => searchQuery.set(q),
|
|
83
|
-
isActive: () => false,
|
|
84
|
-
dispose: () => {},
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function createMockToolbar(): ToolbarController {
|
|
89
|
-
const tools = createSignal<ReadonlyArray<ToolDefinition>>([])
|
|
90
|
-
const activeTool = createSignal<ToolId | null>(null)
|
|
91
|
-
const disabledTools = createSignal<ReadonlySet<ToolId>>(new Set())
|
|
92
|
-
return {
|
|
93
|
-
tools,
|
|
94
|
-
activeTool,
|
|
95
|
-
disabledTools,
|
|
96
|
-
selectTool: (id) => activeTool.set(id),
|
|
97
|
-
clearSelection: () => activeTool.set(null),
|
|
98
|
-
setDisabled: () => {},
|
|
99
|
-
dispose: () => {},
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function createMockDrawing(): DrawingController {
|
|
104
|
-
const state = createSignal<DrawingState>({
|
|
105
|
-
activeTool: null,
|
|
106
|
-
drawingCount: 0,
|
|
107
|
-
})
|
|
108
|
-
return {
|
|
109
|
-
state,
|
|
110
|
-
setActiveTool: (tool) =>
|
|
111
|
-
state.set({ ...state.peek(), activeTool: tool }),
|
|
112
|
-
clearAll: () => state.set({ ...state.peek(), drawingCount: 0 }),
|
|
113
|
-
deleteLast: () =>
|
|
114
|
-
state.set({
|
|
115
|
-
...state.peek(),
|
|
116
|
-
drawingCount: Math.max(0, state.peek().drawingCount - 1),
|
|
117
|
-
}),
|
|
118
|
-
dispose: () => {},
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function createMockChartController(
|
|
123
|
-
opts: Partial<ChartMountOptions> = {},
|
|
124
|
-
): MockChartController {
|
|
125
|
-
let disposeCalls = 0
|
|
126
|
-
|
|
127
|
-
const viewport = createSignal<ChartViewport>({
|
|
128
|
-
zoomLevel: opts.initialZoomLevel ?? 3,
|
|
129
|
-
kWidth: 6,
|
|
130
|
-
visibleFrom: 0,
|
|
131
|
-
visibleTo: 0,
|
|
132
|
-
})
|
|
133
|
-
const data = createSignal<ReadonlyArray<KLineData>>(opts.data ?? [])
|
|
134
|
-
const theme = createSignal<'light' | 'dark'>(opts.theme ?? 'light')
|
|
135
|
-
const indicatorSelector = createMockIndicatorSelector()
|
|
136
|
-
const toolbar = createMockToolbar()
|
|
137
|
-
const drawing = createMockDrawing()
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
viewport,
|
|
141
|
-
data,
|
|
142
|
-
theme,
|
|
143
|
-
indicatorSelector,
|
|
144
|
-
toolbar,
|
|
145
|
-
drawing,
|
|
146
|
-
setData: (next) => data.set(next),
|
|
147
|
-
appendData: (next) => data.set([...data.peek(), ...next]),
|
|
148
|
-
updateData: (next) => data.set(next),
|
|
149
|
-
setTheme: (next) => theme.set(next),
|
|
150
|
-
zoomToLevel: (level) =>
|
|
151
|
-
viewport.set({ ...viewport.peek(), zoomLevel: level }),
|
|
152
|
-
zoomIn: () =>
|
|
153
|
-
viewport.set({
|
|
154
|
-
...viewport.peek(),
|
|
155
|
-
zoomLevel: viewport.peek().zoomLevel + 1,
|
|
156
|
-
}),
|
|
157
|
-
zoomOut: () =>
|
|
158
|
-
viewport.set({
|
|
159
|
-
...viewport.peek(),
|
|
160
|
-
zoomLevel: viewport.peek().zoomLevel - 1,
|
|
161
|
-
}),
|
|
162
|
-
handlePointerEvent: () => false,
|
|
163
|
-
handleWheelEvent: () => {},
|
|
164
|
-
handleScrollEvent: () => {},
|
|
165
|
-
handlePinchZoom: () => {},
|
|
166
|
-
addIndicator: () => null,
|
|
167
|
-
removeIndicator: () => false,
|
|
168
|
-
updateIndicatorParams: () => false,
|
|
169
|
-
updateRendererConfig: () => {},
|
|
170
|
-
setDrawingTool: (tool) => drawing.setActiveTool(tool),
|
|
171
|
-
clearDrawings: () => drawing.clearAll(),
|
|
172
|
-
removeDrawing: () => {},
|
|
173
|
-
resizeSubPane: () => false,
|
|
174
|
-
createSubPane: () => false,
|
|
175
|
-
clearSubPanes: () => {},
|
|
176
|
-
updateCustomMarkers: () => {},
|
|
177
|
-
clearCustomMarkers: () => {},
|
|
178
|
-
updateSettingsFacade: () => {},
|
|
179
|
-
updateOptionsFacade: () => {},
|
|
180
|
-
dispose: () => {
|
|
181
|
-
disposeCalls += 1
|
|
182
|
-
},
|
|
183
|
-
disposeCalls: () => disposeCalls,
|
|
184
|
-
_setViewport: (vp) => viewport.set(vp),
|
|
185
|
-
_setData: (next) => data.set(next),
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Signal helper used by reactivity bridge tests. */
|
|
190
|
-
export function createTestSignal<T>(initial: T): Signal<T> {
|
|
191
|
-
return createSignal(initial)
|
|
192
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mock ChartController for Vue adapter tests.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the framework-agnostic signal-bearing shape from
|
|
5
|
+
* @363045841yyt/klinechart-core without spinning up the real Chart engine.
|
|
6
|
+
*
|
|
7
|
+
* To keep this test file runnable from the repo root vitest (which does not
|
|
8
|
+
* alias @363045841yyt/klinechart-core), we inline a tiny `Signal` implementation
|
|
9
|
+
* that is shape-compatible with `packages/core/src/reactivity/signal.ts`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Signal } from '@363045841yyt/klinechart-core/reactivity'
|
|
13
|
+
import type {
|
|
14
|
+
ActiveIndicator,
|
|
15
|
+
ChartController,
|
|
16
|
+
ChartMountOptions,
|
|
17
|
+
ChartViewport,
|
|
18
|
+
DrawingController,
|
|
19
|
+
DrawingState,
|
|
20
|
+
IndicatorDefinition,
|
|
21
|
+
IndicatorSelectorController,
|
|
22
|
+
KLineData,
|
|
23
|
+
ToolbarController,
|
|
24
|
+
ToolDefinition,
|
|
25
|
+
ToolId,
|
|
26
|
+
} from '@363045841yyt/klinechart-core'
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Inline mini-signal �?Object.is-equality, sync notify. Drop-in compatible
|
|
30
|
+
// with `@363045841yyt/klinechart-core/reactivity` for shape-only test purposes.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
function createSignal<T>(initial: T): Signal<T> {
|
|
34
|
+
let value = initial
|
|
35
|
+
const subs = new Set<() => void>()
|
|
36
|
+
const read = (): T => value
|
|
37
|
+
const peek = (): T => value
|
|
38
|
+
const set = (next: T): void => {
|
|
39
|
+
if (Object.is(value, next)) return
|
|
40
|
+
value = next
|
|
41
|
+
for (const listener of [...subs]) listener()
|
|
42
|
+
}
|
|
43
|
+
const subscribe = (listener: () => void): (() => void) => {
|
|
44
|
+
subs.add(listener)
|
|
45
|
+
return () => {
|
|
46
|
+
subs.delete(listener)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return Object.assign(read, { peek, set, subscribe }) as Signal<T>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MockChartController extends ChartController {
|
|
53
|
+
/** spy: how many times `dispose` was called */
|
|
54
|
+
disposeCalls: () => number
|
|
55
|
+
/** test-only signal mutators */
|
|
56
|
+
_setViewport: (vp: ChartViewport) => void
|
|
57
|
+
_setData: (data: ReadonlyArray<KLineData>) => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createMockIndicatorSelector(): IndicatorSelectorController {
|
|
61
|
+
const catalog = createSignal<ReadonlyArray<IndicatorDefinition>>([])
|
|
62
|
+
const active = createSignal<ReadonlyArray<ActiveIndicator>>([])
|
|
63
|
+
const menuOpen = createSignal<boolean>(false)
|
|
64
|
+
const searchQuery = createSignal<string>('')
|
|
65
|
+
const filteredMain = createSignal<ReadonlyArray<IndicatorDefinition>>([])
|
|
66
|
+
const filteredSub = createSignal<ReadonlyArray<IndicatorDefinition>>([])
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
catalog,
|
|
70
|
+
active,
|
|
71
|
+
menuOpen,
|
|
72
|
+
searchQuery,
|
|
73
|
+
filteredMain,
|
|
74
|
+
filteredSub,
|
|
75
|
+
add: () => null,
|
|
76
|
+
remove: () => false,
|
|
77
|
+
updateParams: () => false,
|
|
78
|
+
reorder: () => false,
|
|
79
|
+
openMenu: () => menuOpen.set(true),
|
|
80
|
+
closeMenu: () => menuOpen.set(false),
|
|
81
|
+
toggleMenu: () => menuOpen.set(!menuOpen.peek()),
|
|
82
|
+
setSearchQuery: (q: string) => searchQuery.set(q),
|
|
83
|
+
isActive: () => false,
|
|
84
|
+
dispose: () => {},
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createMockToolbar(): ToolbarController {
|
|
89
|
+
const tools = createSignal<ReadonlyArray<ToolDefinition>>([])
|
|
90
|
+
const activeTool = createSignal<ToolId | null>(null)
|
|
91
|
+
const disabledTools = createSignal<ReadonlySet<ToolId>>(new Set())
|
|
92
|
+
return {
|
|
93
|
+
tools,
|
|
94
|
+
activeTool,
|
|
95
|
+
disabledTools,
|
|
96
|
+
selectTool: (id) => activeTool.set(id),
|
|
97
|
+
clearSelection: () => activeTool.set(null),
|
|
98
|
+
setDisabled: () => {},
|
|
99
|
+
dispose: () => {},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createMockDrawing(): DrawingController {
|
|
104
|
+
const state = createSignal<DrawingState>({
|
|
105
|
+
activeTool: null,
|
|
106
|
+
drawingCount: 0,
|
|
107
|
+
})
|
|
108
|
+
return {
|
|
109
|
+
state,
|
|
110
|
+
setActiveTool: (tool) =>
|
|
111
|
+
state.set({ ...state.peek(), activeTool: tool }),
|
|
112
|
+
clearAll: () => state.set({ ...state.peek(), drawingCount: 0 }),
|
|
113
|
+
deleteLast: () =>
|
|
114
|
+
state.set({
|
|
115
|
+
...state.peek(),
|
|
116
|
+
drawingCount: Math.max(0, state.peek().drawingCount - 1),
|
|
117
|
+
}),
|
|
118
|
+
dispose: () => {},
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function createMockChartController(
|
|
123
|
+
opts: Partial<ChartMountOptions> = {},
|
|
124
|
+
): MockChartController {
|
|
125
|
+
let disposeCalls = 0
|
|
126
|
+
|
|
127
|
+
const viewport = createSignal<ChartViewport>({
|
|
128
|
+
zoomLevel: opts.initialZoomLevel ?? 3,
|
|
129
|
+
kWidth: 6,
|
|
130
|
+
visibleFrom: 0,
|
|
131
|
+
visibleTo: 0,
|
|
132
|
+
})
|
|
133
|
+
const data = createSignal<ReadonlyArray<KLineData>>(opts.data ?? [])
|
|
134
|
+
const theme = createSignal<'light' | 'dark'>(opts.theme ?? 'light')
|
|
135
|
+
const indicatorSelector = createMockIndicatorSelector()
|
|
136
|
+
const toolbar = createMockToolbar()
|
|
137
|
+
const drawing = createMockDrawing()
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
viewport,
|
|
141
|
+
data,
|
|
142
|
+
theme,
|
|
143
|
+
indicatorSelector,
|
|
144
|
+
toolbar,
|
|
145
|
+
drawing,
|
|
146
|
+
setData: (next) => data.set(next),
|
|
147
|
+
appendData: (next) => data.set([...data.peek(), ...next]),
|
|
148
|
+
updateData: (next) => data.set(next),
|
|
149
|
+
setTheme: (next) => theme.set(next),
|
|
150
|
+
zoomToLevel: (level) =>
|
|
151
|
+
viewport.set({ ...viewport.peek(), zoomLevel: level }),
|
|
152
|
+
zoomIn: () =>
|
|
153
|
+
viewport.set({
|
|
154
|
+
...viewport.peek(),
|
|
155
|
+
zoomLevel: viewport.peek().zoomLevel + 1,
|
|
156
|
+
}),
|
|
157
|
+
zoomOut: () =>
|
|
158
|
+
viewport.set({
|
|
159
|
+
...viewport.peek(),
|
|
160
|
+
zoomLevel: viewport.peek().zoomLevel - 1,
|
|
161
|
+
}),
|
|
162
|
+
handlePointerEvent: () => false,
|
|
163
|
+
handleWheelEvent: () => {},
|
|
164
|
+
handleScrollEvent: () => {},
|
|
165
|
+
handlePinchZoom: () => {},
|
|
166
|
+
addIndicator: () => null,
|
|
167
|
+
removeIndicator: () => false,
|
|
168
|
+
updateIndicatorParams: () => false,
|
|
169
|
+
updateRendererConfig: () => {},
|
|
170
|
+
setDrawingTool: (tool) => drawing.setActiveTool(tool),
|
|
171
|
+
clearDrawings: () => drawing.clearAll(),
|
|
172
|
+
removeDrawing: () => {},
|
|
173
|
+
resizeSubPane: () => false,
|
|
174
|
+
createSubPane: () => false,
|
|
175
|
+
clearSubPanes: () => {},
|
|
176
|
+
updateCustomMarkers: () => {},
|
|
177
|
+
clearCustomMarkers: () => {},
|
|
178
|
+
updateSettingsFacade: () => {},
|
|
179
|
+
updateOptionsFacade: () => {},
|
|
180
|
+
dispose: () => {
|
|
181
|
+
disposeCalls += 1
|
|
182
|
+
},
|
|
183
|
+
disposeCalls: () => disposeCalls,
|
|
184
|
+
_setViewport: (vp) => viewport.set(vp),
|
|
185
|
+
_setData: (next) => data.set(next),
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Signal helper used by reactivity bridge tests. */
|
|
190
|
+
export function createTestSignal<T>(initial: T): Signal<T> {
|
|
191
|
+
return createSignal(initial)
|
|
192
|
+
}
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Contract test for @363045841yyt/klinechart.
|
|
3
|
-
*
|
|
4
|
-
* Phase 1D agent's brief: make these pass without weakening assertions,
|
|
5
|
-
* preserving the legacy KMapPlugin.install signature.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
9
|
-
import { defineComponent, h, nextTick, ref, shallowRef } from 'vue'
|
|
10
|
-
import { mount } from '@vue/test-utils'
|
|
11
|
-
import * as VueAdapter from '../index'
|
|
12
|
-
import { coreSignalToVueRef } from '../index'
|
|
13
|
-
import { createMockChartController, createTestSignal } from './_mockController'
|
|
14
|
-
|
|
15
|
-
describe('@363045841yyt/klinechart �?public API surface', () => {
|
|
16
|
-
it('exports createChart, useChart, useIndicatorSelector, KLineChart, KMapPlugin', () => {
|
|
17
|
-
expect(typeof VueAdapter.createChart).toBe('function')
|
|
18
|
-
expect(typeof VueAdapter.useChart).toBe('function')
|
|
19
|
-
expect(typeof VueAdapter.useIndicatorSelector).toBe('function')
|
|
20
|
-
expect(VueAdapter.KLineChart).toBeDefined()
|
|
21
|
-
expect(typeof VueAdapter.KMapPlugin.install).toBe('function')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('KMapPlugin.install is callable with a mock app and registers KLineChart', () => {
|
|
25
|
-
const registered: Record<string, unknown> = {}
|
|
26
|
-
const mockApp = {
|
|
27
|
-
component(name: string, comp: unknown) {
|
|
28
|
-
registered[name] = comp
|
|
29
|
-
},
|
|
30
|
-
} as unknown as Parameters<typeof VueAdapter.KMapPlugin.install>[0]
|
|
31
|
-
VueAdapter.KMapPlugin.install(mockApp)
|
|
32
|
-
expect(registered.KLineChart).toBe(VueAdapter.KLineChart)
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe('@363045841yyt/klinechart �?SSR safety', () => {
|
|
37
|
-
it('module import does not touch window or document', () => {
|
|
38
|
-
// Import above ran in node env without jsdom. If it touched window, this
|
|
39
|
-
// file would not have loaded. Test documents the contract.
|
|
40
|
-
expect(true).toBe(true)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
describe('@363045841yyt/klinechart �?useChart lifecycle', () => {
|
|
45
|
-
afterEach(() => {
|
|
46
|
-
// Reset the injected factory so other tests start clean.
|
|
47
|
-
VueAdapter.__setControllerFactory(null)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('mounts on first render via template ref', async () => {
|
|
51
|
-
const mockController = createMockChartController({ data: [] })
|
|
52
|
-
const factorySpy = vi.fn(() => mockController)
|
|
53
|
-
VueAdapter.__setControllerFactory(factorySpy)
|
|
54
|
-
|
|
55
|
-
const HostComponent = defineComponent({
|
|
56
|
-
name: 'Host',
|
|
57
|
-
setup() {
|
|
58
|
-
const containerRef = ref<HTMLElement | null>(null)
|
|
59
|
-
const { chart } = VueAdapter.useChart(containerRef, { data: [] })
|
|
60
|
-
return { containerRef, chart }
|
|
61
|
-
},
|
|
62
|
-
render() {
|
|
63
|
-
return h('div', { ref: 'containerRef' })
|
|
64
|
-
},
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const wrapper = mount(HostComponent, { attachTo: document.body })
|
|
68
|
-
await nextTick()
|
|
69
|
-
|
|
70
|
-
expect(factorySpy).toHaveBeenCalledTimes(1)
|
|
71
|
-
const factoryArg = factorySpy.mock.calls[0]?.[0]
|
|
72
|
-
expect(factoryArg?.container).toBeInstanceOf(HTMLElement)
|
|
73
|
-
expect(wrapper.vm.chart).toBe(mockController)
|
|
74
|
-
|
|
75
|
-
wrapper.unmount()
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('disposes on unmount', async () => {
|
|
79
|
-
const mockController = createMockChartController({ data: [] })
|
|
80
|
-
VueAdapter.__setControllerFactory(() => mockController)
|
|
81
|
-
|
|
82
|
-
const HostComponent = defineComponent({
|
|
83
|
-
name: 'Host',
|
|
84
|
-
setup() {
|
|
85
|
-
const containerRef = ref<HTMLElement | null>(null)
|
|
86
|
-
const { chart } = VueAdapter.useChart(containerRef, { data: [] })
|
|
87
|
-
return { containerRef, chart }
|
|
88
|
-
},
|
|
89
|
-
render() {
|
|
90
|
-
return h('div', { ref: 'containerRef' })
|
|
91
|
-
},
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const wrapper = mount(HostComponent, { attachTo: document.body })
|
|
95
|
-
await nextTick()
|
|
96
|
-
|
|
97
|
-
expect(mockController.disposeCalls()).toBe(0)
|
|
98
|
-
wrapper.unmount()
|
|
99
|
-
// Allow lifecycle hooks to settle.
|
|
100
|
-
await nextTick()
|
|
101
|
-
expect(mockController.disposeCalls()).toBe(1)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('reactivity bridge: signal change updates returned ref', async () => {
|
|
105
|
-
// Mount a tiny scoped component so coreSignalToVueRef can register
|
|
106
|
-
// its onScopeDispose cleanup. Without a setup scope the ref is still
|
|
107
|
-
// wired up correctly, but cleanup would not be automatic.
|
|
108
|
-
const signal = createTestSignal<number>(1)
|
|
109
|
-
const bridgedRef = shallowRef<{ value: number } | null>(null)
|
|
110
|
-
|
|
111
|
-
const HostComponent = defineComponent({
|
|
112
|
-
name: 'BridgeHost',
|
|
113
|
-
setup() {
|
|
114
|
-
const r = coreSignalToVueRef(signal)
|
|
115
|
-
bridgedRef.value = r as unknown as { value: number }
|
|
116
|
-
return () => h('div', String(r.value))
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
const wrapper = mount(HostComponent, { attachTo: document.body })
|
|
121
|
-
expect(bridgedRef.value?.value).toBe(1)
|
|
122
|
-
expect(wrapper.text()).toBe('1')
|
|
123
|
-
|
|
124
|
-
signal.set(42)
|
|
125
|
-
await nextTick()
|
|
126
|
-
|
|
127
|
-
expect(bridgedRef.value?.value).toBe(42)
|
|
128
|
-
expect(wrapper.text()).toBe('42')
|
|
129
|
-
|
|
130
|
-
wrapper.unmount()
|
|
131
|
-
})
|
|
132
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Contract test for @363045841yyt/klinechart.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1D agent's brief: make these pass without weakening assertions,
|
|
5
|
+
* preserving the legacy KMapPlugin.install signature.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
9
|
+
import { defineComponent, h, nextTick, ref, shallowRef } from 'vue'
|
|
10
|
+
import { mount } from '@vue/test-utils'
|
|
11
|
+
import * as VueAdapter from '../index'
|
|
12
|
+
import { coreSignalToVueRef } from '../index'
|
|
13
|
+
import { createMockChartController, createTestSignal } from './_mockController'
|
|
14
|
+
|
|
15
|
+
describe('@363045841yyt/klinechart �?public API surface', () => {
|
|
16
|
+
it('exports createChart, useChart, useIndicatorSelector, KLineChart, KMapPlugin', () => {
|
|
17
|
+
expect(typeof VueAdapter.createChart).toBe('function')
|
|
18
|
+
expect(typeof VueAdapter.useChart).toBe('function')
|
|
19
|
+
expect(typeof VueAdapter.useIndicatorSelector).toBe('function')
|
|
20
|
+
expect(VueAdapter.KLineChart).toBeDefined()
|
|
21
|
+
expect(typeof VueAdapter.KMapPlugin.install).toBe('function')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('KMapPlugin.install is callable with a mock app and registers KLineChart', () => {
|
|
25
|
+
const registered: Record<string, unknown> = {}
|
|
26
|
+
const mockApp = {
|
|
27
|
+
component(name: string, comp: unknown) {
|
|
28
|
+
registered[name] = comp
|
|
29
|
+
},
|
|
30
|
+
} as unknown as Parameters<typeof VueAdapter.KMapPlugin.install>[0]
|
|
31
|
+
VueAdapter.KMapPlugin.install(mockApp)
|
|
32
|
+
expect(registered.KLineChart).toBe(VueAdapter.KLineChart)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('@363045841yyt/klinechart �?SSR safety', () => {
|
|
37
|
+
it('module import does not touch window or document', () => {
|
|
38
|
+
// Import above ran in node env without jsdom. If it touched window, this
|
|
39
|
+
// file would not have loaded. Test documents the contract.
|
|
40
|
+
expect(true).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('@363045841yyt/klinechart �?useChart lifecycle', () => {
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
// Reset the injected factory so other tests start clean.
|
|
47
|
+
VueAdapter.__setControllerFactory(null)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('mounts on first render via template ref', async () => {
|
|
51
|
+
const mockController = createMockChartController({ data: [] })
|
|
52
|
+
const factorySpy = vi.fn(() => mockController)
|
|
53
|
+
VueAdapter.__setControllerFactory(factorySpy)
|
|
54
|
+
|
|
55
|
+
const HostComponent = defineComponent({
|
|
56
|
+
name: 'Host',
|
|
57
|
+
setup() {
|
|
58
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
59
|
+
const { chart } = VueAdapter.useChart(containerRef, { data: [] })
|
|
60
|
+
return { containerRef, chart }
|
|
61
|
+
},
|
|
62
|
+
render() {
|
|
63
|
+
return h('div', { ref: 'containerRef' })
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const wrapper = mount(HostComponent, { attachTo: document.body })
|
|
68
|
+
await nextTick()
|
|
69
|
+
|
|
70
|
+
expect(factorySpy).toHaveBeenCalledTimes(1)
|
|
71
|
+
const factoryArg = factorySpy.mock.calls[0]?.[0]
|
|
72
|
+
expect(factoryArg?.container).toBeInstanceOf(HTMLElement)
|
|
73
|
+
expect(wrapper.vm.chart).toBe(mockController)
|
|
74
|
+
|
|
75
|
+
wrapper.unmount()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('disposes on unmount', async () => {
|
|
79
|
+
const mockController = createMockChartController({ data: [] })
|
|
80
|
+
VueAdapter.__setControllerFactory(() => mockController)
|
|
81
|
+
|
|
82
|
+
const HostComponent = defineComponent({
|
|
83
|
+
name: 'Host',
|
|
84
|
+
setup() {
|
|
85
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
86
|
+
const { chart } = VueAdapter.useChart(containerRef, { data: [] })
|
|
87
|
+
return { containerRef, chart }
|
|
88
|
+
},
|
|
89
|
+
render() {
|
|
90
|
+
return h('div', { ref: 'containerRef' })
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const wrapper = mount(HostComponent, { attachTo: document.body })
|
|
95
|
+
await nextTick()
|
|
96
|
+
|
|
97
|
+
expect(mockController.disposeCalls()).toBe(0)
|
|
98
|
+
wrapper.unmount()
|
|
99
|
+
// Allow lifecycle hooks to settle.
|
|
100
|
+
await nextTick()
|
|
101
|
+
expect(mockController.disposeCalls()).toBe(1)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('reactivity bridge: signal change updates returned ref', async () => {
|
|
105
|
+
// Mount a tiny scoped component so coreSignalToVueRef can register
|
|
106
|
+
// its onScopeDispose cleanup. Without a setup scope the ref is still
|
|
107
|
+
// wired up correctly, but cleanup would not be automatic.
|
|
108
|
+
const signal = createTestSignal<number>(1)
|
|
109
|
+
const bridgedRef = shallowRef<{ value: number } | null>(null)
|
|
110
|
+
|
|
111
|
+
const HostComponent = defineComponent({
|
|
112
|
+
name: 'BridgeHost',
|
|
113
|
+
setup() {
|
|
114
|
+
const r = coreSignalToVueRef(signal)
|
|
115
|
+
bridgedRef.value = r as unknown as { value: number }
|
|
116
|
+
return () => h('div', String(r.value))
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const wrapper = mount(HostComponent, { attachTo: document.body })
|
|
121
|
+
expect(bridgedRef.value?.value).toBe(1)
|
|
122
|
+
expect(wrapper.text()).toBe('1')
|
|
123
|
+
|
|
124
|
+
signal.set(42)
|
|
125
|
+
await nextTick()
|
|
126
|
+
|
|
127
|
+
expect(bridgedRef.value?.value).toBe(42)
|
|
128
|
+
expect(wrapper.text()).toBe('42')
|
|
129
|
+
|
|
130
|
+
wrapper.unmount()
|
|
131
|
+
})
|
|
132
|
+
})
|