@akccakcctw/vue-grab 1.0.0 → 1.3.0
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/core/api.d.ts +28 -0
- package/dist/core/api.js +105 -0
- package/dist/core/identifier.d.ts +2 -0
- package/dist/core/identifier.js +101 -0
- package/dist/core/overlay.d.ts +24 -0
- package/dist/core/overlay.js +509 -0
- package/dist/core/widget.d.ts +10 -0
- package/dist/core/widget.js +251 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/nuxt/module.d.ts +8 -0
- package/dist/nuxt/module.js +40 -0
- package/dist/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/nuxt/runtime/plugin.js +14 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.js +47 -0
- package/dist/vite.d.ts +8 -0
- package/dist/vite.js +198 -0
- package/package.json +33 -8
- package/.github/release-please-config.json +0 -9
- package/.github/release-please-manifest.json +0 -3
- package/.github/workflows/release.yml +0 -37
- package/AGENTS.md +0 -75
- package/README.md +0 -116
- package/akccakcctw-vue-grab-1.0.0.tgz +0 -0
- package/docs/SDD.md +0 -188
- package/src/__tests__/plugin.spec.ts +0 -60
- package/src/core/__tests__/api.spec.ts +0 -178
- package/src/core/__tests__/identifier.spec.ts +0 -126
- package/src/core/__tests__/overlay.spec.ts +0 -431
- package/src/core/__tests__/widget.spec.ts +0 -57
- package/src/core/api.ts +0 -144
- package/src/core/identifier.ts +0 -89
- package/src/core/overlay.ts +0 -348
- package/src/core/widget.ts +0 -289
- package/src/index.ts +0 -8
- package/src/nuxt/module.ts +0 -102
- package/src/nuxt/runtime/plugin.ts +0 -13
- package/src/plugin.ts +0 -48
- package/tsconfig.json +0 -44
- package/vitest.config.ts +0 -9
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import { defineComponent, h } from 'vue'
|
|
4
|
-
import { identifyComponent, extractMetadata } from '../identifier'
|
|
5
|
-
|
|
6
|
-
const TestComponent = defineComponent({
|
|
7
|
-
name: 'TestComponent',
|
|
8
|
-
__file: '/abs/path/to/TestComponent.vue',
|
|
9
|
-
props: {
|
|
10
|
-
label: {
|
|
11
|
-
type: String,
|
|
12
|
-
default: 'Default'
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
data() {
|
|
16
|
-
return {
|
|
17
|
-
count: 0
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
setup() {
|
|
21
|
-
return () => h('div', { class: 'target' }, 'Hello')
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe('Component Identifier', () => {
|
|
26
|
-
it('should identify a component from a DOM element', () => {
|
|
27
|
-
const wrapper = mount(TestComponent)
|
|
28
|
-
const el = wrapper.find('.target').element as HTMLElement
|
|
29
|
-
|
|
30
|
-
const instance = identifyComponent(el)
|
|
31
|
-
expect(instance).toBeDefined()
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should extract metadata from a component instance', () => {
|
|
35
|
-
const wrapper = mount(TestComponent, {
|
|
36
|
-
props: {
|
|
37
|
-
label: 'Click Me'
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
const el = wrapper.find('.target').element as HTMLElement
|
|
41
|
-
|
|
42
|
-
const instance = identifyComponent(el)
|
|
43
|
-
const metadata = extractMetadata(instance)
|
|
44
|
-
|
|
45
|
-
expect(metadata).toMatchObject({
|
|
46
|
-
name: 'TestComponent',
|
|
47
|
-
file: '/abs/path/to/TestComponent.vue',
|
|
48
|
-
props: {
|
|
49
|
-
label: 'Click Me'
|
|
50
|
-
},
|
|
51
|
-
data: {
|
|
52
|
-
count: 0
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('includes vnode and location data when available', () => {
|
|
58
|
-
const instance = {
|
|
59
|
-
type: {
|
|
60
|
-
name: 'MockComponent',
|
|
61
|
-
__file: '/abs/path/to/MockComponent.vue'
|
|
62
|
-
},
|
|
63
|
-
vnode: {
|
|
64
|
-
loc: {
|
|
65
|
-
start: {
|
|
66
|
-
line: 12,
|
|
67
|
-
column: 8
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const metadata = extractMetadata(instance)
|
|
74
|
-
|
|
75
|
-
expect(metadata).toMatchObject({
|
|
76
|
-
name: 'MockComponent',
|
|
77
|
-
file: '/abs/path/to/MockComponent.vue',
|
|
78
|
-
line: 12,
|
|
79
|
-
column: 8,
|
|
80
|
-
vnode: instance.vnode
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('falls back to parent metadata when file is missing', () => {
|
|
85
|
-
const parent = {
|
|
86
|
-
type: {
|
|
87
|
-
name: 'ParentComponent',
|
|
88
|
-
__file: '/abs/path/to/ParentComponent.vue',
|
|
89
|
-
__line: 5,
|
|
90
|
-
__column: 2
|
|
91
|
-
},
|
|
92
|
-
vnode: {
|
|
93
|
-
loc: {
|
|
94
|
-
start: {
|
|
95
|
-
line: 5,
|
|
96
|
-
column: 2
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const child = {
|
|
102
|
-
type: {
|
|
103
|
-
name: 'ChildComponent'
|
|
104
|
-
},
|
|
105
|
-
parent
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const metadata = extractMetadata(child)
|
|
109
|
-
|
|
110
|
-
expect(metadata).toMatchObject({
|
|
111
|
-
name: 'ParentComponent',
|
|
112
|
-
file: '/abs/path/to/ParentComponent.vue',
|
|
113
|
-
line: 5,
|
|
114
|
-
column: 2
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('returns dom fallback when no instance is found', () => {
|
|
119
|
-
const el = document.createElement('button')
|
|
120
|
-
const metadata = extractMetadata(null, el)
|
|
121
|
-
expect(metadata).toMatchObject({
|
|
122
|
-
name: '<button>',
|
|
123
|
-
file: 'unknown'
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
})
|
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
|
-
import { createOverlayController } from '../overlay'
|
|
3
|
-
|
|
4
|
-
describe('Overlay controller', () => {
|
|
5
|
-
afterEach(() => {
|
|
6
|
-
const overlay = document.querySelector('[data-vue-grab-overlay="true"]')
|
|
7
|
-
overlay?.remove()
|
|
8
|
-
const tooltip = document.querySelector('[data-vue-grab-tooltip="true"]')
|
|
9
|
-
tooltip?.remove()
|
|
10
|
-
document.body.innerHTML = ''
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('adds and removes overlay element', () => {
|
|
14
|
-
const controller = createOverlayController(window)
|
|
15
|
-
controller.start()
|
|
16
|
-
expect(document.querySelector('[data-vue-grab-overlay="true"]')).toBeTruthy()
|
|
17
|
-
controller.stop()
|
|
18
|
-
expect(document.querySelector('[data-vue-grab-overlay="true"]')).toBeFalsy()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('applies custom overlay styles', () => {
|
|
22
|
-
const controller = createOverlayController(window, {
|
|
23
|
-
overlayStyle: {
|
|
24
|
-
border: '3px solid rgb(0, 0, 0)'
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
controller.start()
|
|
28
|
-
const overlay = document.querySelector(
|
|
29
|
-
'[data-vue-grab-overlay="true"]'
|
|
30
|
-
) as HTMLDivElement
|
|
31
|
-
expect(overlay.style.border).toBe('3px solid rgb(0, 0, 0)')
|
|
32
|
-
controller.stop()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('updates overlay styles at runtime', () => {
|
|
36
|
-
const controller = createOverlayController(window)
|
|
37
|
-
controller.start()
|
|
38
|
-
controller.setStyle({
|
|
39
|
-
border: '1px dotted rgb(10, 20, 30)'
|
|
40
|
-
})
|
|
41
|
-
const overlay = document.querySelector(
|
|
42
|
-
'[data-vue-grab-overlay="true"]'
|
|
43
|
-
) as HTMLDivElement
|
|
44
|
-
expect(overlay.style.border).toBe('1px dotted rgb(10, 20, 30)')
|
|
45
|
-
controller.stop()
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('updates overlay position on mouse move', () => {
|
|
49
|
-
const target = document.createElement('div')
|
|
50
|
-
target.className = 'target'
|
|
51
|
-
target.getBoundingClientRect = () =>
|
|
52
|
-
({
|
|
53
|
-
top: 10,
|
|
54
|
-
left: 20,
|
|
55
|
-
width: 100,
|
|
56
|
-
height: 50
|
|
57
|
-
}) as DOMRect
|
|
58
|
-
document.body.appendChild(target)
|
|
59
|
-
Object.defineProperty(document, 'elementFromPoint', {
|
|
60
|
-
value: vi.fn(() => target),
|
|
61
|
-
configurable: true
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const controller = createOverlayController(window)
|
|
65
|
-
controller.start()
|
|
66
|
-
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
|
|
67
|
-
|
|
68
|
-
const overlay = document.querySelector(
|
|
69
|
-
'[data-vue-grab-overlay="true"]'
|
|
70
|
-
) as HTMLDivElement
|
|
71
|
-
expect(overlay.style.top).toBe('10px')
|
|
72
|
-
expect(overlay.style.left).toBe('20px')
|
|
73
|
-
expect(overlay.style.width).toBe('100px')
|
|
74
|
-
expect(overlay.style.height).toBe('50px')
|
|
75
|
-
controller.stop()
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('shows file location tooltip on hover', () => {
|
|
79
|
-
const target = document.createElement('div')
|
|
80
|
-
target.className = 'target'
|
|
81
|
-
target.getBoundingClientRect = () =>
|
|
82
|
-
({
|
|
83
|
-
top: 10,
|
|
84
|
-
left: 20,
|
|
85
|
-
width: 100,
|
|
86
|
-
height: 50,
|
|
87
|
-
bottom: 60
|
|
88
|
-
}) as DOMRect
|
|
89
|
-
document.body.appendChild(target)
|
|
90
|
-
;(target as any).__vueParentComponent = {
|
|
91
|
-
type: {
|
|
92
|
-
name: 'TooltipComponent',
|
|
93
|
-
__file: '/abs/path/src/components/Tooltip.vue'
|
|
94
|
-
},
|
|
95
|
-
vnode: {
|
|
96
|
-
loc: {
|
|
97
|
-
start: {
|
|
98
|
-
line: 51,
|
|
99
|
-
column: 3
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
Object.defineProperty(document, 'elementFromPoint', {
|
|
105
|
-
value: vi.fn(() => target),
|
|
106
|
-
configurable: true
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
const controller = createOverlayController(window)
|
|
110
|
-
controller.start()
|
|
111
|
-
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
|
|
112
|
-
|
|
113
|
-
const tooltip = document.querySelector(
|
|
114
|
-
'[data-vue-grab-tooltip="true"]'
|
|
115
|
-
) as HTMLDivElement
|
|
116
|
-
expect(tooltip.textContent).toBe('components/Tooltip.vue:51:3')
|
|
117
|
-
controller.stop()
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('uses rootDir for relative paths', () => {
|
|
121
|
-
const target = document.createElement('div')
|
|
122
|
-
target.className = 'target'
|
|
123
|
-
target.getBoundingClientRect = () =>
|
|
124
|
-
({
|
|
125
|
-
top: 10,
|
|
126
|
-
left: 20,
|
|
127
|
-
width: 100,
|
|
128
|
-
height: 50,
|
|
129
|
-
bottom: 60
|
|
130
|
-
}) as DOMRect
|
|
131
|
-
document.body.appendChild(target)
|
|
132
|
-
;(target as any).__vueParentComponent = {
|
|
133
|
-
type: {
|
|
134
|
-
name: 'TooltipComponent',
|
|
135
|
-
__file: '/root/app/components/Tooltip.vue'
|
|
136
|
-
},
|
|
137
|
-
vnode: {
|
|
138
|
-
loc: {
|
|
139
|
-
start: {
|
|
140
|
-
line: 9,
|
|
141
|
-
column: 4
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
Object.defineProperty(document, 'elementFromPoint', {
|
|
147
|
-
value: vi.fn(() => target),
|
|
148
|
-
configurable: true
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const controller = createOverlayController(window, {
|
|
152
|
-
rootDir: '/root/app'
|
|
153
|
-
})
|
|
154
|
-
controller.start()
|
|
155
|
-
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
|
|
156
|
-
|
|
157
|
-
const tooltip = document.querySelector(
|
|
158
|
-
'[data-vue-grab-tooltip="true"]'
|
|
159
|
-
) as HTMLDivElement
|
|
160
|
-
expect(tooltip.textContent).toBe('components/Tooltip.vue:9:4')
|
|
161
|
-
controller.stop()
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('omits line and column when missing', () => {
|
|
165
|
-
const target = document.createElement('div')
|
|
166
|
-
target.className = 'target'
|
|
167
|
-
target.getBoundingClientRect = () =>
|
|
168
|
-
({
|
|
169
|
-
top: 10,
|
|
170
|
-
left: 20,
|
|
171
|
-
width: 100,
|
|
172
|
-
height: 50,
|
|
173
|
-
bottom: 60
|
|
174
|
-
}) as DOMRect
|
|
175
|
-
document.body.appendChild(target)
|
|
176
|
-
;(target as any).__vueParentComponent = {
|
|
177
|
-
type: {
|
|
178
|
-
name: 'TooltipComponent',
|
|
179
|
-
__file: '/abs/path/src/components/Tooltip.vue'
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
Object.defineProperty(document, 'elementFromPoint', {
|
|
183
|
-
value: vi.fn(() => target),
|
|
184
|
-
configurable: true
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
const controller = createOverlayController(window)
|
|
188
|
-
controller.start()
|
|
189
|
-
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
|
|
190
|
-
|
|
191
|
-
const tooltip = document.querySelector(
|
|
192
|
-
'[data-vue-grab-tooltip="true"]'
|
|
193
|
-
) as HTMLDivElement
|
|
194
|
-
expect(tooltip.textContent).toBe('components/Tooltip.vue')
|
|
195
|
-
controller.stop()
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('clamps tooltip position within viewport', () => {
|
|
199
|
-
Object.defineProperty(window, 'innerWidth', { value: 120, configurable: true })
|
|
200
|
-
Object.defineProperty(window, 'innerHeight', { value: 80, configurable: true })
|
|
201
|
-
const target = document.createElement('div')
|
|
202
|
-
target.className = 'target'
|
|
203
|
-
target.getBoundingClientRect = () =>
|
|
204
|
-
({
|
|
205
|
-
top: 2,
|
|
206
|
-
left: 110,
|
|
207
|
-
width: 50,
|
|
208
|
-
height: 20,
|
|
209
|
-
bottom: 22
|
|
210
|
-
}) as DOMRect
|
|
211
|
-
document.body.appendChild(target)
|
|
212
|
-
;(target as any).__vueParentComponent = {
|
|
213
|
-
type: {
|
|
214
|
-
name: 'TooltipComponent',
|
|
215
|
-
__file: '/abs/path/src/components/Tooltip.vue'
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
Object.defineProperty(document, 'elementFromPoint', {
|
|
219
|
-
value: vi.fn(() => target),
|
|
220
|
-
configurable: true
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
const controller = createOverlayController(window)
|
|
224
|
-
controller.start()
|
|
225
|
-
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
|
|
226
|
-
|
|
227
|
-
const tooltip = document.querySelector(
|
|
228
|
-
'[data-vue-grab-tooltip="true"]'
|
|
229
|
-
) as HTMLDivElement
|
|
230
|
-
const left = Number.parseFloat(tooltip.style.left)
|
|
231
|
-
const top = Number.parseFloat(tooltip.style.top)
|
|
232
|
-
expect(left).toBeGreaterThanOrEqual(0)
|
|
233
|
-
expect(top).toBeGreaterThanOrEqual(0)
|
|
234
|
-
controller.stop()
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('copies metadata on click', async () => {
|
|
238
|
-
const target = document.createElement('div')
|
|
239
|
-
target.className = 'target'
|
|
240
|
-
document.body.appendChild(target)
|
|
241
|
-
|
|
242
|
-
const writeText = vi.fn().mockResolvedValue(undefined)
|
|
243
|
-
Object.defineProperty(navigator, 'clipboard', {
|
|
244
|
-
value: { writeText },
|
|
245
|
-
configurable: true
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
const controller = createOverlayController(window)
|
|
249
|
-
controller.start()
|
|
250
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
251
|
-
|
|
252
|
-
expect(writeText).toHaveBeenCalledTimes(1)
|
|
253
|
-
const payload = writeText.mock.calls[0][0] as string
|
|
254
|
-
expect(payload).toContain('{')
|
|
255
|
-
controller.stop()
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
it('uses onCopy handler when provided', () => {
|
|
259
|
-
const target = document.createElement('div')
|
|
260
|
-
target.className = 'target'
|
|
261
|
-
document.body.appendChild(target)
|
|
262
|
-
|
|
263
|
-
const onCopy = vi.fn()
|
|
264
|
-
const controller = createOverlayController(window, { onCopy })
|
|
265
|
-
;(target as any).__vueParentComponent = {
|
|
266
|
-
type: {
|
|
267
|
-
name: 'TestComponent',
|
|
268
|
-
__file: '/abs/path/to/TestComponent.vue'
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
controller.start()
|
|
272
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
273
|
-
|
|
274
|
-
expect(onCopy).toHaveBeenCalledTimes(1)
|
|
275
|
-
controller.stop()
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
it('skips copy on click when disabled', () => {
|
|
279
|
-
const target = document.createElement('div')
|
|
280
|
-
target.className = 'target'
|
|
281
|
-
document.body.appendChild(target)
|
|
282
|
-
|
|
283
|
-
const writeText = vi.fn().mockResolvedValue(undefined)
|
|
284
|
-
Object.defineProperty(navigator, 'clipboard', {
|
|
285
|
-
value: { writeText },
|
|
286
|
-
configurable: true
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
const controller = createOverlayController(window, {
|
|
290
|
-
copyOnClick: false
|
|
291
|
-
})
|
|
292
|
-
controller.start()
|
|
293
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
294
|
-
|
|
295
|
-
expect(writeText).not.toHaveBeenCalled()
|
|
296
|
-
controller.stop()
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
it('avoids circular metadata errors on copy', () => {
|
|
300
|
-
const target = document.createElement('div')
|
|
301
|
-
target.className = 'target'
|
|
302
|
-
document.body.appendChild(target)
|
|
303
|
-
|
|
304
|
-
const instance: any = {
|
|
305
|
-
type: {
|
|
306
|
-
name: 'CycleComponent',
|
|
307
|
-
__file: '/abs/path/to/CycleComponent.vue'
|
|
308
|
-
},
|
|
309
|
-
vnode: {}
|
|
310
|
-
}
|
|
311
|
-
instance.vnode.component = instance
|
|
312
|
-
;(target as any).__vueParentComponent = instance
|
|
313
|
-
|
|
314
|
-
const onCopy = vi.fn()
|
|
315
|
-
const controller = createOverlayController(window, { onCopy })
|
|
316
|
-
controller.start()
|
|
317
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
318
|
-
|
|
319
|
-
expect(onCopy).toHaveBeenCalledTimes(1)
|
|
320
|
-
controller.stop()
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
it('handles window references in metadata', () => {
|
|
324
|
-
const target = document.createElement('div')
|
|
325
|
-
target.className = 'target'
|
|
326
|
-
document.body.appendChild(target)
|
|
327
|
-
|
|
328
|
-
;(target as any).__vueParentComponent = {
|
|
329
|
-
type: {
|
|
330
|
-
name: 'WindowComponent',
|
|
331
|
-
__file: '/abs/path/to/WindowComponent.vue'
|
|
332
|
-
},
|
|
333
|
-
props: {
|
|
334
|
-
win: window
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const onCopy = vi.fn()
|
|
339
|
-
const controller = createOverlayController(window, { onCopy })
|
|
340
|
-
controller.start()
|
|
341
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
342
|
-
|
|
343
|
-
expect(onCopy).toHaveBeenCalledTimes(1)
|
|
344
|
-
controller.stop()
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
it('limits depth and size to avoid huge payloads', () => {
|
|
348
|
-
const target = document.createElement('div')
|
|
349
|
-
target.className = 'target'
|
|
350
|
-
document.body.appendChild(target)
|
|
351
|
-
|
|
352
|
-
const large: any = {}
|
|
353
|
-
let curr = large
|
|
354
|
-
for (let i = 0; i < 20; i += 1) {
|
|
355
|
-
curr.next = { value: i }
|
|
356
|
-
curr = curr.next
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
;(target as any).__vueParentComponent = {
|
|
360
|
-
type: {
|
|
361
|
-
name: 'LargeComponent',
|
|
362
|
-
__file: '/abs/path/to/LargeComponent.vue'
|
|
363
|
-
},
|
|
364
|
-
props: {
|
|
365
|
-
data: large
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const onCopy = vi.fn()
|
|
370
|
-
const controller = createOverlayController(window, { onCopy })
|
|
371
|
-
controller.start()
|
|
372
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
373
|
-
|
|
374
|
-
const payload = onCopy.mock.calls[0][0] as string
|
|
375
|
-
expect(payload).toContain('[DepthLimit]')
|
|
376
|
-
controller.stop()
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
it('serializes vnode without component proxy', () => {
|
|
380
|
-
const target = document.createElement('div')
|
|
381
|
-
target.className = 'target'
|
|
382
|
-
document.body.appendChild(target)
|
|
383
|
-
|
|
384
|
-
const vnode = {
|
|
385
|
-
__v_isVNode: true,
|
|
386
|
-
type: 'div',
|
|
387
|
-
component: { $el: target },
|
|
388
|
-
props: { id: 'test' }
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
;(target as any).__vueParentComponent = {
|
|
392
|
-
type: {
|
|
393
|
-
name: 'VNodeComponent',
|
|
394
|
-
__file: '/abs/path/to/VNodeComponent.vue'
|
|
395
|
-
},
|
|
396
|
-
vnode
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const onCopy = vi.fn()
|
|
400
|
-
const controller = createOverlayController(window, { onCopy })
|
|
401
|
-
controller.start()
|
|
402
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
403
|
-
|
|
404
|
-
const payload = onCopy.mock.calls[0][0] as string
|
|
405
|
-
expect(payload).toContain('"props"')
|
|
406
|
-
controller.stop()
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
it('suppresses original click event on captured click', () => {
|
|
410
|
-
const target = document.createElement('div')
|
|
411
|
-
target.className = 'target'
|
|
412
|
-
document.body.appendChild(target)
|
|
413
|
-
|
|
414
|
-
const originalClickHandler = vi.fn()
|
|
415
|
-
target.addEventListener('click', originalClickHandler)
|
|
416
|
-
|
|
417
|
-
const controller = createOverlayController(window)
|
|
418
|
-
controller.start()
|
|
419
|
-
|
|
420
|
-
// Simulate a click. Since we use capture, the controller should handle it first
|
|
421
|
-
// and stop propagation, so target shouldn't receive it.
|
|
422
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
423
|
-
|
|
424
|
-
expect(originalClickHandler).not.toHaveBeenCalled()
|
|
425
|
-
|
|
426
|
-
controller.stop()
|
|
427
|
-
// Verify normal behavior resumes
|
|
428
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
429
|
-
expect(originalClickHandler).toHaveBeenCalledTimes(1)
|
|
430
|
-
})
|
|
431
|
-
})
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
-
import { createToggleWidget } from '../widget'
|
|
3
|
-
|
|
4
|
-
describe('Toggle widget', () => {
|
|
5
|
-
afterEach(() => {
|
|
6
|
-
document.querySelectorAll('[data-vue-grab-toolbar]').forEach((el) => el.remove())
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('can be dragged within the viewport', () => {
|
|
10
|
-
Object.defineProperty(window, 'innerWidth', { value: 300, configurable: true })
|
|
11
|
-
Object.defineProperty(window, 'innerHeight', { value: 200, configurable: true })
|
|
12
|
-
|
|
13
|
-
const widget = createToggleWidget(window, {
|
|
14
|
-
onToggle: () => {}
|
|
15
|
-
})
|
|
16
|
-
widget.mount()
|
|
17
|
-
|
|
18
|
-
const container = document.querySelector(
|
|
19
|
-
'[data-vue-grab-toolbar]'
|
|
20
|
-
) as HTMLDivElement
|
|
21
|
-
expect(container).toBeTruthy()
|
|
22
|
-
|
|
23
|
-
Object.defineProperty(container, 'offsetWidth', { value: 80, configurable: true })
|
|
24
|
-
Object.defineProperty(container, 'offsetHeight', { value: 30, configurable: true })
|
|
25
|
-
|
|
26
|
-
container.dispatchEvent(new MouseEvent('mousedown', { clientX: 290, clientY: 190 }))
|
|
27
|
-
window.dispatchEvent(new MouseEvent('mousemove', { clientX: 250, clientY: 150 }))
|
|
28
|
-
window.dispatchEvent(new MouseEvent('mouseup'))
|
|
29
|
-
|
|
30
|
-
const left = Number.parseFloat(container.style.left)
|
|
31
|
-
const top = Number.parseFloat(container.style.top)
|
|
32
|
-
expect(left).toBeGreaterThanOrEqual(0)
|
|
33
|
-
expect(top).toBeGreaterThanOrEqual(0)
|
|
34
|
-
// Should be near the mouse position (offset preserved)
|
|
35
|
-
// Initial: right 16, bottom 16. but logic sets left/top on drag start.
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('collapses and expands the toolbar', () => {
|
|
39
|
-
const widget = createToggleWidget(window, {
|
|
40
|
-
onToggle: () => {}
|
|
41
|
-
})
|
|
42
|
-
widget.mount()
|
|
43
|
-
|
|
44
|
-
const collapse = document.querySelector(
|
|
45
|
-
'[data-vue-grab-collapse]'
|
|
46
|
-
) as HTMLButtonElement
|
|
47
|
-
const toggleWrapper = document.querySelector(
|
|
48
|
-
'[data-vue-grab-toggle]'
|
|
49
|
-
)?.parentElement as HTMLDivElement
|
|
50
|
-
|
|
51
|
-
collapse.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
52
|
-
expect(toggleWrapper.style.maxWidth).toBe('0px')
|
|
53
|
-
|
|
54
|
-
collapse.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
55
|
-
expect(toggleWrapper.style.maxWidth).toBe('200px')
|
|
56
|
-
})
|
|
57
|
-
})
|