w-flow-vue 1.0.21 → 1.0.23

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.
@@ -1,102 +1,104 @@
1
- import { mount } from '@vue/test-utils'
2
- import ViewportTransform from '../src/components/canvas/ViewportTransform.vue'
3
- import SelectionBox from '../src/components/canvas/SelectionBox.vue'
4
- import BackgroundLayer from '../src/components/canvas/BackgroundLayer.vue'
5
- import FlowCanvas from '../src/components/canvas/FlowCanvas.vue'
6
-
7
- describe('ViewportTransform', () => {
8
- test('applies transform style', () => {
9
- const wrapper = mount(ViewportTransform, {
10
- propsData: { x: 100, y: 50, zoom: 1.5 },
11
- slots: { default: '<div class="child">content</div>' },
12
- })
13
- expect(wrapper.attributes('style')).toContain('translate(100px, 50px) scale(1.5)')
14
- })
15
-
16
- test('default values produce identity transform', () => {
17
- const wrapper = mount(ViewportTransform, {
18
- slots: { default: '<div>content</div>' },
19
- })
20
- expect(wrapper.attributes('style')).toContain('translate(0px, 0px) scale(1)')
21
- })
22
-
23
- test('renders slot content', () => {
24
- const wrapper = mount(ViewportTransform, {
25
- slots: { default: '<div class="child">hello</div>' },
26
- })
27
- expect(wrapper.find('.child').text()).toBe('hello')
28
- })
29
- })
30
-
31
- describe('SelectionBox', () => {
32
- test('renders when box is provided', () => {
33
- const wrapper = mount(SelectionBox, {
34
- propsData: { box: { x: 10, y: 20, width: 100, height: 50 } },
35
- })
36
- const el = wrapper.find('.vue-flow__selection-box')
37
- expect(el.exists()).toBe(true)
38
- expect(el.attributes('style')).toContain('left: 10px')
39
- expect(el.attributes('style')).toContain('top: 20px')
40
- expect(el.attributes('style')).toContain('width: 100px')
41
- expect(el.attributes('style')).toContain('height: 50px')
42
- })
43
-
44
- test('does not render when box is null', () => {
45
- const wrapper = mount(SelectionBox, {
46
- propsData: { box: null },
47
- })
48
- expect(wrapper.find('.vue-flow__selection-box').exists()).toBe(false)
49
- })
50
- })
51
-
52
- describe('FlowCanvas', () => {
53
- test('renders container with vue-flow class', () => {
54
- const wrapper = mount(FlowCanvas, {
55
- slots: { default: '<div>child</div>' },
56
- })
57
- expect(wrapper.classes()).toContain('vue-flow')
58
- })
59
-
60
- test('emits canvas-click on mousedown+mouseup', () => {
61
- const wrapper = mount(FlowCanvas)
62
- wrapper.trigger('mousedown', { clientX: 0, clientY: 0 })
63
- wrapper.trigger('mouseup', { clientX: 0, clientY: 0 })
64
- expect(wrapper.emitted('canvas-click')).toBeTruthy()
65
- })
66
-
67
- test('emits canvas-mousedown', () => {
68
- const wrapper = mount(FlowCanvas)
69
- wrapper.trigger('mousedown', { clientX: 0, clientY: 0 })
70
- expect(wrapper.emitted('canvas-mousedown')).toBeTruthy()
71
- })
72
-
73
- test('renders slot content', () => {
74
- const wrapper = mount(FlowCanvas, {
75
- slots: { default: '<div class="test-child">hello</div>' },
76
- })
77
- expect(wrapper.find('.test-child').text()).toBe('hello')
78
- })
79
- })
80
-
81
- describe('BackgroundLayer', () => {
82
- test('renders svg with pattern', () => {
83
- const wrapper = mount(BackgroundLayer)
84
- expect(wrapper.find('svg').exists()).toBe(true)
85
- expect(wrapper.find('pattern').exists()).toBe(true)
86
- })
87
-
88
- test('renders dots pattern by default', () => {
89
- const wrapper = mount(BackgroundLayer, {
90
- propsData: { variant: 'dots' },
91
- })
92
- expect(wrapper.find('circle').exists()).toBe(true)
93
- })
94
-
95
- test('renders lines pattern', () => {
96
- const wrapper = mount(BackgroundLayer, {
97
- propsData: { variant: 'lines' },
98
- })
99
- expect(wrapper.find('circle').exists()).toBe(false)
100
- expect(wrapper.find('path').exists()).toBe(true)
101
- })
102
- })
1
+ import { mount } from '@vue/test-utils'
2
+ import ViewportTransform from '../src/components/canvas/ViewportTransform.vue'
3
+ import SelectionBox from '../src/components/canvas/SelectionBox.vue'
4
+ import BackgroundLayer from '../src/components/canvas/BackgroundLayer.vue'
5
+ import FlowCanvas from '../src/components/canvas/FlowCanvas.vue'
6
+
7
+ describe('ViewportTransform', () => {
8
+ test('applies transform style', () => {
9
+ const wrapper = mount(ViewportTransform, {
10
+ //ViewportTransform 已改收單一 viewport 物件(取代 x/y/zoom 三個純量),
11
+ //目的為令宿主 render 僅讀取穩定物件參考; 斷言意圖(transform 樣式)不變
12
+ propsData: { viewport: { x: 100, y: 50, zoom: 1.5 } },
13
+ slots: { default: '<div class="child">content</div>' },
14
+ })
15
+ expect(wrapper.attributes('style')).toContain('translate(100px, 50px) scale(1.5)')
16
+ })
17
+
18
+ test('default values produce identity transform', () => {
19
+ const wrapper = mount(ViewportTransform, {
20
+ slots: { default: '<div>content</div>' },
21
+ })
22
+ expect(wrapper.attributes('style')).toContain('translate(0px, 0px) scale(1)')
23
+ })
24
+
25
+ test('renders slot content', () => {
26
+ const wrapper = mount(ViewportTransform, {
27
+ slots: { default: '<div class="child">hello</div>' },
28
+ })
29
+ expect(wrapper.find('.child').text()).toBe('hello')
30
+ })
31
+ })
32
+
33
+ describe('SelectionBox', () => {
34
+ test('renders when box is provided', () => {
35
+ const wrapper = mount(SelectionBox, {
36
+ propsData: { box: { x: 10, y: 20, width: 100, height: 50 } },
37
+ })
38
+ const el = wrapper.find('.vue-flow__selection-box')
39
+ expect(el.exists()).toBe(true)
40
+ expect(el.attributes('style')).toContain('left: 10px')
41
+ expect(el.attributes('style')).toContain('top: 20px')
42
+ expect(el.attributes('style')).toContain('width: 100px')
43
+ expect(el.attributes('style')).toContain('height: 50px')
44
+ })
45
+
46
+ test('does not render when box is null', () => {
47
+ const wrapper = mount(SelectionBox, {
48
+ propsData: { box: null },
49
+ })
50
+ expect(wrapper.find('.vue-flow__selection-box').exists()).toBe(false)
51
+ })
52
+ })
53
+
54
+ describe('FlowCanvas', () => {
55
+ test('renders container with vue-flow class', () => {
56
+ const wrapper = mount(FlowCanvas, {
57
+ slots: { default: '<div>child</div>' },
58
+ })
59
+ expect(wrapper.classes()).toContain('vue-flow')
60
+ })
61
+
62
+ test('emits canvas-click on mousedown+mouseup', () => {
63
+ const wrapper = mount(FlowCanvas)
64
+ wrapper.trigger('mousedown', { clientX: 0, clientY: 0 })
65
+ wrapper.trigger('mouseup', { clientX: 0, clientY: 0 })
66
+ expect(wrapper.emitted('canvas-click')).toBeTruthy()
67
+ })
68
+
69
+ test('emits canvas-mousedown', () => {
70
+ const wrapper = mount(FlowCanvas)
71
+ wrapper.trigger('mousedown', { clientX: 0, clientY: 0 })
72
+ expect(wrapper.emitted('canvas-mousedown')).toBeTruthy()
73
+ })
74
+
75
+ test('renders slot content', () => {
76
+ const wrapper = mount(FlowCanvas, {
77
+ slots: { default: '<div class="test-child">hello</div>' },
78
+ })
79
+ expect(wrapper.find('.test-child').text()).toBe('hello')
80
+ })
81
+ })
82
+
83
+ describe('BackgroundLayer', () => {
84
+ test('renders svg with pattern', () => {
85
+ const wrapper = mount(BackgroundLayer)
86
+ expect(wrapper.find('svg').exists()).toBe(true)
87
+ expect(wrapper.find('pattern').exists()).toBe(true)
88
+ })
89
+
90
+ test('renders dots pattern by default', () => {
91
+ const wrapper = mount(BackgroundLayer, {
92
+ propsData: { variant: 'dots' },
93
+ })
94
+ expect(wrapper.find('circle').exists()).toBe(true)
95
+ })
96
+
97
+ test('renders lines pattern', () => {
98
+ const wrapper = mount(BackgroundLayer, {
99
+ propsData: { variant: 'lines' },
100
+ })
101
+ expect(wrapper.find('circle').exists()).toBe(false)
102
+ expect(wrapper.find('path').exists()).toBe(true)
103
+ })
104
+ })
@@ -1,147 +1,157 @@
1
- import { mount } from '@vue/test-utils'
2
- import EdgeWrapper from '../src/components/edges/EdgeWrapper.vue'
3
- import EdgeLabel from '../src/components/edges/EdgeLabel.vue'
4
- import EdgeMarkerDefs from '../src/components/edges/EdgeMarkerDefs.vue'
5
-
6
- describe('EdgeWrapper', () => {
7
- const baseProps = {
8
- conn: { id: 'e1-2', source: '1', target: '2', type: 'default' },
9
- sourceX: 100,
10
- sourceY: 50,
11
- targetX: 300,
12
- targetY: 250,
13
- sourcePosition: 'bottom',
14
- targetPosition: 'top',
15
- }
16
-
17
- test('renders svg group', () => {
18
- const wrapper = mount(EdgeWrapper, {
19
- propsData: baseProps,
20
- stubs: { EdgeLabel: true },
21
- })
22
- expect(wrapper.element.tagName.toLowerCase()).toBe('g')
23
- })
24
-
25
- test('renders path with d attribute', () => {
26
- const wrapper = mount(EdgeWrapper, {
27
- propsData: baseProps,
28
- stubs: { EdgeLabel: true },
29
- })
30
- const paths = wrapper.findAll('path')
31
- expect(paths.length).toBeGreaterThanOrEqual(1)
32
- const visiblePath = paths.wrappers.find(p => !p.classes().includes('vue-flow__edge-interaction'))
33
- expect(visiblePath.attributes('d')).toMatch(/^M /)
34
- })
35
-
36
- test('applies selected class', () => {
37
- const wrapper = mount(EdgeWrapper, {
38
- propsData: { ...baseProps, selected: true },
39
- stubs: { EdgeLabel: true },
40
- })
41
- expect(wrapper.classes()).toContain('vue-flow__edge--selected')
42
- })
43
-
44
- test('applies animated class', () => {
45
- const wrapper = mount(EdgeWrapper, {
46
- propsData: {
47
- ...baseProps,
48
- conn: { ...baseProps.conn, animated: true },
49
- },
50
- stubs: { EdgeLabel: true },
51
- })
52
- expect(wrapper.classes()).toContain('vue-flow__edge--animated')
53
- })
54
-
55
- test('renders name when provided', () => {
56
- const wrapper = mount(EdgeWrapper, {
57
- propsData: {
58
- ...baseProps,
59
- conn: { ...baseProps.conn, name: 'test label' },
60
- },
61
- })
62
- expect(wrapper.find('.vue-flow__edge-label').exists()).toBe(true)
63
- })
64
-
65
- test('does not render name when not provided', () => {
66
- const wrapper = mount(EdgeWrapper, {
67
- propsData: baseProps,
68
- })
69
- expect(wrapper.find('.vue-flow__edge-label').exists()).toBe(false)
70
- })
71
-
72
- test('emits conn-click on click', () => {
73
- const wrapper = mount(EdgeWrapper, {
74
- propsData: baseProps,
75
- stubs: { EdgeLabel: true },
76
- })
77
- const interactionPath = wrapper.find('.vue-flow__edge-interaction')
78
- interactionPath.trigger('click')
79
- expect(wrapper.emitted('conn-click')).toBeTruthy()
80
- expect(wrapper.emitted('conn-click')[0][0].conn.id).toBe('e1-2')
81
- })
82
-
83
- test('renders different edge types', () => {
84
- const types = ['default', 'straight', 'step', 'smoothstep']
85
- const paths = types.map(type => {
86
- const wrapper = mount(EdgeWrapper, {
87
- propsData: {
88
- ...baseProps,
89
- conn: { ...baseProps.conn, type },
90
- },
91
- stubs: { EdgeLabel: true },
92
- })
93
- const visiblePath = wrapper.findAll('path').wrappers.find(
94
- p => !p.classes().includes('vue-flow__edge-interaction')
95
- )
96
- return visiblePath.attributes('d')
97
- })
98
- // straight should differ from bezier
99
- expect(paths[0]).not.toBe(paths[1])
100
- })
101
- })
102
-
103
- describe('EdgeLabel', () => {
104
- test('renders name text', () => {
105
- const wrapper = mount(EdgeLabel, {
106
- propsData: { name: 'hello', x: 100, y: 50 },
107
- })
108
- expect(wrapper.text()).toContain('hello')
109
- })
110
- })
111
-
112
- describe('EdgeMarkerDefs', () => {
113
- test('renders marker defs for conns with markers', () => {
114
- const conns = [
115
- { id: 'e1', source: '1', target: '2', markerEnd: 'arrowclosed' },
116
- { id: 'e2', source: '2', target: '3', markerEnd: 'arrow' },
117
- ]
118
- const wrapper = mount(EdgeMarkerDefs, {
119
- propsData: { conns },
120
- })
121
- const markers = wrapper.findAll('marker')
122
- expect(markers.length).toBe(2)
123
- })
124
-
125
- test('deduplicates same marker type', () => {
126
- const conns = [
127
- { id: 'e1', source: '1', target: '2', markerEnd: 'arrowclosed' },
128
- { id: 'e2', source: '2', target: '3', markerEnd: 'arrowclosed' },
129
- ]
130
- const wrapper = mount(EdgeMarkerDefs, {
131
- propsData: { conns },
132
- })
133
- const markers = wrapper.findAll('marker')
134
- expect(markers.length).toBe(1)
135
- })
136
-
137
- test('renders no markers when conns have none', () => {
138
- const conns = [
139
- { id: 'e1', source: '1', target: '2' },
140
- ]
141
- const wrapper = mount(EdgeMarkerDefs, {
142
- propsData: { conns },
143
- })
144
- const markers = wrapper.findAll('marker')
145
- expect(markers.length).toBe(0)
146
- })
147
- })
1
+ import { mount } from '@vue/test-utils'
2
+ import EdgeWrapper from '../src/components/edges/EdgeWrapper.vue'
3
+ import EdgeMarkerDefs from '../src/components/edges/EdgeMarkerDefs.vue'
4
+
5
+ describe('EdgeWrapper', () => {
6
+ const baseProps = {
7
+ conn: { id: 'e1-2', source: '1', target: '2', type: 'default' },
8
+ sourceX: 100,
9
+ sourceY: 50,
10
+ targetX: 300,
11
+ targetY: 250,
12
+ sourcePosition: 'bottom',
13
+ targetPosition: 'top',
14
+ }
15
+
16
+ test('renders svg group', () => {
17
+ const wrapper = mount(EdgeWrapper, {
18
+ propsData: baseProps,
19
+ })
20
+ expect(wrapper.element.tagName.toLowerCase()).toBe('g')
21
+ })
22
+
23
+ test('renders path with d attribute', () => {
24
+ const wrapper = mount(EdgeWrapper, {
25
+ propsData: baseProps,
26
+ })
27
+ const paths = wrapper.findAll('path')
28
+ expect(paths.length).toBeGreaterThanOrEqual(1)
29
+ const visiblePath = paths.wrappers.find(p => !p.classes().includes('vue-flow__edge-interaction'))
30
+ expect(visiblePath.attributes('d')).toMatch(/^M /)
31
+ })
32
+
33
+ test('applies selected class', () => {
34
+ const wrapper = mount(EdgeWrapper, {
35
+ propsData: { ...baseProps, selected: true },
36
+ })
37
+ expect(wrapper.classes()).toContain('vue-flow__edge--selected')
38
+ })
39
+
40
+ test('applies animated class', () => {
41
+ const wrapper = mount(EdgeWrapper, {
42
+ propsData: {
43
+ ...baseProps,
44
+ conn: { ...baseProps.conn, animated: true },
45
+ },
46
+ })
47
+ expect(wrapper.classes()).toContain('vue-flow__edge--animated')
48
+ })
49
+
50
+ test('renders name when provided', () => {
51
+ const wrapper = mount(EdgeWrapper, {
52
+ propsData: {
53
+ ...baseProps,
54
+ conn: { ...baseProps.conn, name: 'test label' },
55
+ },
56
+ })
57
+ expect(wrapper.find('.vue-flow__edge-label').exists()).toBe(true)
58
+ })
59
+
60
+ test('does not render name when not provided', () => {
61
+ const wrapper = mount(EdgeWrapper, {
62
+ propsData: baseProps,
63
+ })
64
+ expect(wrapper.find('.vue-flow__edge-label').exists()).toBe(false)
65
+ })
66
+
67
+ test('emits conn-click on click', () => {
68
+ const wrapper = mount(EdgeWrapper, {
69
+ propsData: baseProps,
70
+ })
71
+ const interactionPath = wrapper.find('.vue-flow__edge-interaction')
72
+ interactionPath.trigger('click')
73
+ expect(wrapper.emitted('conn-click')).toBeTruthy()
74
+ expect(wrapper.emitted('conn-click')[0][0].conn.id).toBe('e1-2')
75
+ })
76
+
77
+ test('renders different edge types', () => {
78
+ const types = ['default', 'straight', 'step', 'smoothstep']
79
+ const paths = types.map(type => {
80
+ const wrapper = mount(EdgeWrapper, {
81
+ propsData: {
82
+ ...baseProps,
83
+ conn: { ...baseProps.conn, type },
84
+ },
85
+ })
86
+ const visiblePath = wrapper.findAll('path').wrappers.find(
87
+ p => !p.classes().includes('vue-flow__edge-interaction')
88
+ )
89
+ return visiblePath.attributes('d')
90
+ })
91
+ // straight should differ from bezier
92
+ expect(paths[0]).not.toBe(paths[1])
93
+ })
94
+ })
95
+
96
+ //EdgeLabel.vue 已併入 EdgeWrapper(以 .vue-flow__edge-label 直接渲染 conn.name),
97
+ //故原「連線名稱有被渲染」之斷言改由 EdgeWrapper 驗證
98
+ describe('連線名稱標籤', () => {
99
+ //baseProps 定義於 EdgeWrapper 之 describe 內, 此處自備一份
100
+ const props = {
101
+ sourceX: 100, sourceY: 50, targetX: 300, targetY: 250,
102
+ sourcePosition: 'bottom', targetPosition: 'top',
103
+ }
104
+
105
+ test('conn.name 渲染於 .vue-flow__edge-label', () => {
106
+ const wrapper = mount(EdgeWrapper, {
107
+ propsData: { ...props, conn: { id: 'e1', source: '1', target: '2', name: 'hello' } },
108
+ })
109
+ const label = wrapper.find('.vue-flow__edge-label')
110
+ expect(label.exists()).toBe(true)
111
+ expect(label.text()).toContain('hello')
112
+ })
113
+
114
+ test('無 conn.name 時不渲染標籤', () => {
115
+ const wrapper = mount(EdgeWrapper, {
116
+ propsData: { ...props, conn: { id: 'e1', source: '1', target: '2' } },
117
+ })
118
+ expect(wrapper.find('.vue-flow__edge-label').exists()).toBe(false)
119
+ })
120
+ })
121
+
122
+ describe('EdgeMarkerDefs', () => {
123
+ test('renders marker defs for conns with markers', () => {
124
+ const conns = [
125
+ { id: 'e1', source: '1', target: '2', markerEnd: 'arrowclosed' },
126
+ { id: 'e2', source: '2', target: '3', markerEnd: 'arrow' },
127
+ ]
128
+ const wrapper = mount(EdgeMarkerDefs, {
129
+ propsData: { conns },
130
+ })
131
+ const markers = wrapper.findAll('marker')
132
+ expect(markers.length).toBe(2)
133
+ })
134
+
135
+ test('deduplicates same marker type', () => {
136
+ const conns = [
137
+ { id: 'e1', source: '1', target: '2', markerEnd: 'arrowclosed' },
138
+ { id: 'e2', source: '2', target: '3', markerEnd: 'arrowclosed' },
139
+ ]
140
+ const wrapper = mount(EdgeMarkerDefs, {
141
+ propsData: { conns },
142
+ })
143
+ const markers = wrapper.findAll('marker')
144
+ expect(markers.length).toBe(1)
145
+ })
146
+
147
+ test('renders no markers when conns have none', () => {
148
+ const conns = [
149
+ { id: 'e1', source: '1', target: '2' },
150
+ ]
151
+ const wrapper = mount(EdgeMarkerDefs, {
152
+ propsData: { conns },
153
+ })
154
+ const markers = wrapper.findAll('marker')
155
+ expect(markers.length).toBe(0)
156
+ })
157
+ })
@@ -154,16 +154,38 @@ describe('NodeWrapper', () => {
154
154
  expect(wrapper.emitted('node-click')[0][0].node.id).toBe('1')
155
155
  })
156
156
 
157
- test('emits drag-start on mousedown when draggable', () => {
157
+ //拖曳延後至跨越2px位移門檻才啟動: mousedown當下只發drag-prepare(供宿主先行選取),
158
+ //drag-start要有實際位移才發, 否則純點擊會走完endDrag→emit update:nodes而使宿主誤判有未儲存變更
159
+ test('emits drag-prepare (not drag-start) on mousedown when draggable', () => {
158
160
  const wrapper = mount(NodeWrapper, { propsData: { node, draggable: true } })
159
- wrapper.trigger('mousedown')
160
- expect(wrapper.emitted('drag-start')).toBeTruthy()
161
+ wrapper.trigger('mousedown', { clientX: 0, clientY: 0, button: 0 })
162
+ expect(wrapper.emitted('drag-prepare')).toBeTruthy()
163
+ expect(wrapper.emitted('drag-start')).toBeFalsy()
164
+ wrapper.destroy()
165
+ })
166
+
167
+ test('emits drag-start once movement crosses the threshold', async () => {
168
+ const wrapper = mount(NodeWrapper, { propsData: { node, draggable: true } })
169
+ wrapper.trigger('mousedown', { clientX: 0, clientY: 0, button: 0 })
170
+ document.dispatchEvent(new MouseEvent('mousemove', { clientX: 20, clientY: 0, buttons: 1 }))
171
+ await wrapper.vm.$nextTick()
172
+ expect(wrapper.emitted('drag-start')).toHaveLength(1)
173
+ wrapper.destroy()
161
174
  })
162
175
 
163
- test('does not emit drag-start when not draggable', () => {
176
+ test('does not emit drag-prepare or drag-start when not draggable', () => {
164
177
  const wrapper = mount(NodeWrapper, { propsData: { node, draggable: false } })
165
- wrapper.trigger('mousedown')
178
+ wrapper.trigger('mousedown', { clientX: 0, clientY: 0, button: 0 })
179
+ expect(wrapper.emitted('drag-prepare')).toBeFalsy()
166
180
  expect(wrapper.emitted('drag-start')).toBeFalsy()
181
+ wrapper.destroy()
182
+ })
183
+
184
+ test('does not emit drag-prepare on non-primary button', () => {
185
+ const wrapper = mount(NodeWrapper, { propsData: { node, draggable: true } })
186
+ wrapper.trigger('mousedown', { clientX: 0, clientY: 0, button: 2 })
187
+ expect(wrapper.emitted('drag-prepare')).toBeFalsy()
188
+ wrapper.destroy()
167
189
  })
168
190
 
169
191
  test('applies zIndex from node', () => {