@tldraw/editor 4.3.0 → 4.4.0-canary.09e80a09d230
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 +1 -1
- package/dist-cjs/index.d.ts +180 -11
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/LiveCollaborators.js +14 -24
- package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +201 -0
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +30 -16
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +3 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js +13 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +58 -6
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +13 -21
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +378 -89
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js +144 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js.map +7 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +180 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +8 -3
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +29 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +29 -0
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/options.js +1 -0
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/collaboratorState.js +42 -0
- package/dist-cjs/lib/utils/collaboratorState.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +180 -11
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/LiveCollaborators.mjs +17 -24
- package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +181 -0
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +30 -16
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +3 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs +13 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +58 -6
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +13 -21
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +378 -89
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs +114 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +160 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +8 -3
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +29 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +33 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +1 -0
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/collaboratorState.mjs +22 -0
- package/dist-esm/lib/utils/collaboratorState.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +6 -0
- package/package.json +10 -8
- package/src/index.ts +3 -0
- package/src/lib/components/LiveCollaborators.tsx +26 -37
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +244 -0
- package/src/lib/components/default-components/DefaultCanvas.tsx +16 -6
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +6 -1
- package/src/lib/components/default-components/DefaultShapeIndicators.tsx +16 -1
- package/src/lib/config/TLUserPreferences.test.ts +1 -0
- package/src/lib/config/TLUserPreferences.ts +8 -0
- package/src/lib/editor/Editor.ts +84 -6
- package/src/lib/editor/derivations/notVisibleShapes.ts +15 -41
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +491 -106
- package/src/lib/editor/managers/SpatialIndexManager/RBushIndex.ts +144 -0
- package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +214 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +24 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +44 -0
- package/src/lib/hooks/usePeerIds.ts +46 -1
- package/src/lib/options.ts +7 -0
- package/src/lib/utils/collaboratorState.ts +54 -0
- package/src/version.ts +3 -3
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -621
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
import { TLScribble } from '@tldraw/tlschema'
|
|
2
|
-
import { Mock, Mocked, vi } from 'vitest'
|
|
3
|
-
import { Editor } from '../../Editor'
|
|
4
|
-
import { ScribbleItem, ScribbleManager } from './ScribbleManager'
|
|
5
|
-
|
|
6
|
-
// Mock the Editor class
|
|
7
|
-
vi.mock('../../Editor')
|
|
8
|
-
vi.mock('@tldraw/utils', () => ({
|
|
9
|
-
uniqueId: vi.fn(() => 'test-id'),
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
describe('ScribbleManager', () => {
|
|
13
|
-
let editor: Mocked<Editor>
|
|
14
|
-
let scribbleManager: ScribbleManager
|
|
15
|
-
let mockUniqueId: Mock
|
|
16
|
-
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
editor = {
|
|
19
|
-
updateInstanceState: vi.fn(),
|
|
20
|
-
run: vi.fn((fn) => fn()),
|
|
21
|
-
} as any
|
|
22
|
-
|
|
23
|
-
const { uniqueId } = await vi.importMock('@tldraw/utils')
|
|
24
|
-
mockUniqueId = uniqueId as Mock
|
|
25
|
-
mockUniqueId.mockReturnValue('test-id')
|
|
26
|
-
|
|
27
|
-
scribbleManager = new ScribbleManager(editor)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
vi.clearAllMocks()
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
describe('constructor and initialization', () => {
|
|
35
|
-
it('should initialize with empty scribble items and paused state', () => {
|
|
36
|
-
expect(scribbleManager.scribbleItems.size).toBe(0)
|
|
37
|
-
expect(scribbleManager.state).toBe('paused')
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('addScribble', () => {
|
|
42
|
-
it('should add a new scribble with default values', () => {
|
|
43
|
-
const result = scribbleManager.addScribble({})
|
|
44
|
-
|
|
45
|
-
expect(result).toBeDefined()
|
|
46
|
-
expect(result.id).toBe('test-id')
|
|
47
|
-
expect(result.scribble).toMatchObject({
|
|
48
|
-
id: 'test-id',
|
|
49
|
-
size: 20,
|
|
50
|
-
color: 'accent',
|
|
51
|
-
opacity: 0.8,
|
|
52
|
-
delay: 0,
|
|
53
|
-
points: [],
|
|
54
|
-
shrink: 0.1,
|
|
55
|
-
taper: true,
|
|
56
|
-
state: 'starting',
|
|
57
|
-
})
|
|
58
|
-
expect(result.timeoutMs).toBe(0)
|
|
59
|
-
expect(result.delayRemaining).toBe(0)
|
|
60
|
-
expect(result.prev).toBeNull()
|
|
61
|
-
expect(result.next).toBeNull()
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should add a scribble with custom properties', () => {
|
|
65
|
-
const customScribble: Partial<TLScribble> = {
|
|
66
|
-
size: 30,
|
|
67
|
-
color: 'black',
|
|
68
|
-
opacity: 0.5,
|
|
69
|
-
delay: 1000,
|
|
70
|
-
shrink: 0.2,
|
|
71
|
-
taper: false,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const result = scribbleManager.addScribble(customScribble)
|
|
75
|
-
|
|
76
|
-
expect(result.scribble).toMatchObject({
|
|
77
|
-
...customScribble,
|
|
78
|
-
id: 'test-id',
|
|
79
|
-
points: [],
|
|
80
|
-
state: 'starting',
|
|
81
|
-
})
|
|
82
|
-
expect(result.delayRemaining).toBe(1000)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('should add scribble with custom id', () => {
|
|
86
|
-
const customId = 'custom-scribble-id'
|
|
87
|
-
const result = scribbleManager.addScribble({}, customId)
|
|
88
|
-
|
|
89
|
-
expect(result.id).toBe(customId)
|
|
90
|
-
expect(result.scribble.id).toBe(customId)
|
|
91
|
-
expect(scribbleManager.scribbleItems.has(customId)).toBe(true)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should store scribble in scribbleItems map', () => {
|
|
95
|
-
const result = scribbleManager.addScribble({})
|
|
96
|
-
|
|
97
|
-
expect(scribbleManager.scribbleItems.size).toBe(1)
|
|
98
|
-
expect(scribbleManager.scribbleItems.get('test-id')).toBe(result)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('should handle multiple scribbles', () => {
|
|
102
|
-
mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2').mockReturnValueOnce('id3')
|
|
103
|
-
|
|
104
|
-
const scribble1 = scribbleManager.addScribble({ color: 'black' })
|
|
105
|
-
const scribble2 = scribbleManager.addScribble({ color: 'white' })
|
|
106
|
-
const scribble3 = scribbleManager.addScribble({ color: 'accent' })
|
|
107
|
-
|
|
108
|
-
expect(scribbleManager.scribbleItems.size).toBe(3)
|
|
109
|
-
expect(scribble1.scribble.color).toBe('black')
|
|
110
|
-
expect(scribble2.scribble.color).toBe('white')
|
|
111
|
-
expect(scribble3.scribble.color).toBe('accent')
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
describe('reset', () => {
|
|
116
|
-
it('should clear all scribble items and update instance state', () => {
|
|
117
|
-
mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2')
|
|
118
|
-
scribbleManager.addScribble({})
|
|
119
|
-
scribbleManager.addScribble({})
|
|
120
|
-
expect(scribbleManager.scribbleItems.size).toBe(2)
|
|
121
|
-
|
|
122
|
-
scribbleManager.reset()
|
|
123
|
-
|
|
124
|
-
expect(scribbleManager.scribbleItems.size).toBe(0)
|
|
125
|
-
expect(editor.updateInstanceState).toHaveBeenCalledWith({ scribbles: [] })
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('should work when no scribbles exist', () => {
|
|
129
|
-
expect(() => scribbleManager.reset()).not.toThrow()
|
|
130
|
-
expect(scribbleManager.scribbleItems.size).toBe(0)
|
|
131
|
-
expect(editor.updateInstanceState).toHaveBeenCalledWith({ scribbles: [] })
|
|
132
|
-
})
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
describe('stop', () => {
|
|
136
|
-
it('should stop an existing scribble', () => {
|
|
137
|
-
const item = scribbleManager.addScribble({ delay: 1000 })
|
|
138
|
-
item.delayRemaining = 500
|
|
139
|
-
|
|
140
|
-
const result = scribbleManager.stop(item.id)
|
|
141
|
-
|
|
142
|
-
expect(result).toBe(item)
|
|
143
|
-
expect(result.scribble.state).toBe('stopping')
|
|
144
|
-
expect(result.delayRemaining).toBe(200) // min(500, 200)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should cap delay at 200ms when stopping', () => {
|
|
148
|
-
const item = scribbleManager.addScribble({ delay: 50 })
|
|
149
|
-
item.delayRemaining = 50
|
|
150
|
-
|
|
151
|
-
scribbleManager.stop(item.id)
|
|
152
|
-
|
|
153
|
-
expect(item.delayRemaining).toBe(50) // min(50, 200)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('should throw error for non-existent scribble', () => {
|
|
157
|
-
expect(() => scribbleManager.stop('non-existent-id')).toThrow(
|
|
158
|
-
'Scribble with id non-existent-id not found'
|
|
159
|
-
)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should handle stopping multiple scribbles', () => {
|
|
163
|
-
mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2')
|
|
164
|
-
|
|
165
|
-
const item1 = scribbleManager.addScribble({})
|
|
166
|
-
const item2 = scribbleManager.addScribble({})
|
|
167
|
-
|
|
168
|
-
scribbleManager.stop('id1')
|
|
169
|
-
scribbleManager.stop('id2')
|
|
170
|
-
|
|
171
|
-
expect(item1.scribble.state).toBe('stopping')
|
|
172
|
-
expect(item2.scribble.state).toBe('stopping')
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe('addPoint', () => {
|
|
177
|
-
it('should add point to existing scribble', () => {
|
|
178
|
-
const item = scribbleManager.addScribble({})
|
|
179
|
-
|
|
180
|
-
const result = scribbleManager.addPoint(item.id, 10, 20, 0.7)
|
|
181
|
-
|
|
182
|
-
expect(result).toBe(item)
|
|
183
|
-
expect(result.next).toEqual({ x: 10, y: 20, z: 0.7 })
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('should use default z value of 0.5', () => {
|
|
187
|
-
const item = scribbleManager.addScribble({})
|
|
188
|
-
|
|
189
|
-
scribbleManager.addPoint(item.id, 10, 20)
|
|
190
|
-
|
|
191
|
-
expect(item.next).toEqual({ x: 10, y: 20, z: 0.5 })
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('should only set next if distance from prev is >= 1', () => {
|
|
195
|
-
const item = scribbleManager.addScribble({})
|
|
196
|
-
item.prev = { x: 10, y: 20, z: 0.5 }
|
|
197
|
-
|
|
198
|
-
// Distance < 1 (should not set next)
|
|
199
|
-
scribbleManager.addPoint(item.id, 10.5, 20.3)
|
|
200
|
-
expect(item.next).toBeNull()
|
|
201
|
-
|
|
202
|
-
// Distance >= 1 (should set next)
|
|
203
|
-
scribbleManager.addPoint(item.id, 11, 21)
|
|
204
|
-
expect(item.next).toEqual({ x: 11, y: 21, z: 0.5 })
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
it('should set next when prev is null', () => {
|
|
208
|
-
const item = scribbleManager.addScribble({})
|
|
209
|
-
expect(item.prev).toBeNull()
|
|
210
|
-
|
|
211
|
-
scribbleManager.addPoint(item.id, 5, 5)
|
|
212
|
-
|
|
213
|
-
expect(item.next).toEqual({ x: 5, y: 5, z: 0.5 })
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('should throw error for non-existent scribble', () => {
|
|
217
|
-
expect(() => scribbleManager.addPoint('non-existent-id', 10, 20)).toThrow(
|
|
218
|
-
'Scribble with id non-existent-id not found'
|
|
219
|
-
)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('should handle multiple points', () => {
|
|
223
|
-
const item = scribbleManager.addScribble({})
|
|
224
|
-
|
|
225
|
-
scribbleManager.addPoint(item.id, 0, 0)
|
|
226
|
-
expect(item.next).toEqual({ x: 0, y: 0, z: 0.5 })
|
|
227
|
-
|
|
228
|
-
item.prev = item.next
|
|
229
|
-
scribbleManager.addPoint(item.id, 10, 10)
|
|
230
|
-
expect(item.next).toEqual({ x: 10, y: 10, z: 0.5 })
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
describe('tick', () => {
|
|
235
|
-
it('should return early when no scribble items exist', () => {
|
|
236
|
-
scribbleManager.tick(16)
|
|
237
|
-
|
|
238
|
-
expect(editor.run).not.toHaveBeenCalled()
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it('should wrap tick operations in editor.run', () => {
|
|
242
|
-
scribbleManager.addScribble({})
|
|
243
|
-
|
|
244
|
-
scribbleManager.tick(16)
|
|
245
|
-
|
|
246
|
-
expect(editor.run).toHaveBeenCalledWith(expect.any(Function))
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
describe('starting state behavior', () => {
|
|
250
|
-
it('should add points to scribble in starting state', () => {
|
|
251
|
-
const item = scribbleManager.addScribble({})
|
|
252
|
-
item.next = { x: 10, y: 20, z: 0.5 }
|
|
253
|
-
|
|
254
|
-
scribbleManager.tick(16)
|
|
255
|
-
|
|
256
|
-
expect(item.prev).toEqual({ x: 10, y: 20, z: 0.5 })
|
|
257
|
-
expect(item.scribble.points).toHaveLength(1)
|
|
258
|
-
expect(item.scribble.points[0]).toEqual({ x: 10, y: 20, z: 0.5 })
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('should not add point if next equals prev', () => {
|
|
262
|
-
const item = scribbleManager.addScribble({})
|
|
263
|
-
const point = { x: 10, y: 20, z: 0.5 }
|
|
264
|
-
item.next = point
|
|
265
|
-
item.prev = point
|
|
266
|
-
|
|
267
|
-
scribbleManager.tick(16)
|
|
268
|
-
|
|
269
|
-
expect(item.scribble.points).toHaveLength(0)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('should transition to active after 8 points', () => {
|
|
273
|
-
const item = scribbleManager.addScribble({})
|
|
274
|
-
|
|
275
|
-
// Add 9 points to trigger transition
|
|
276
|
-
for (let i = 0; i < 9; i++) {
|
|
277
|
-
item.next = { x: i, y: i, z: 0.5 }
|
|
278
|
-
item.prev = null // Reset prev to ensure point is added
|
|
279
|
-
scribbleManager.tick(16)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
expect(item.scribble.state).toBe('active')
|
|
283
|
-
expect(item.scribble.points).toHaveLength(9)
|
|
284
|
-
})
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
describe('active state behavior', () => {
|
|
288
|
-
let item: ScribbleItem
|
|
289
|
-
|
|
290
|
-
beforeEach(() => {
|
|
291
|
-
item = scribbleManager.addScribble({})
|
|
292
|
-
item.scribble.state = 'active'
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('should add new points when next differs from prev', () => {
|
|
296
|
-
item.next = { x: 10, y: 20, z: 0.5 }
|
|
297
|
-
item.prev = { x: 0, y: 0, z: 0.5 }
|
|
298
|
-
|
|
299
|
-
scribbleManager.tick(16)
|
|
300
|
-
|
|
301
|
-
expect(item.prev).toEqual({ x: 10, y: 20, z: 0.5 })
|
|
302
|
-
expect(item.scribble.points).toContainEqual({ x: 10, y: 20, z: 0.5 })
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
it('should shrink from start when delay is finished and points > 8', () => {
|
|
306
|
-
// Set up scribble with > 8 points and no delay
|
|
307
|
-
for (let i = 0; i < 10; i++) {
|
|
308
|
-
item.scribble.points.push({ x: i, y: i, z: 0.5 })
|
|
309
|
-
}
|
|
310
|
-
item.delayRemaining = 0
|
|
311
|
-
item.next = { x: 50, y: 50, z: 0.5 }
|
|
312
|
-
|
|
313
|
-
scribbleManager.tick(16)
|
|
314
|
-
|
|
315
|
-
expect(item.scribble.points).toHaveLength(10) // Added one, removed one
|
|
316
|
-
expect(item.scribble.points[0]).toEqual({ x: 1, y: 1, z: 0.5 }) // First was removed
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
it('should shrink when not moving and timeout reached', () => {
|
|
320
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
321
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
322
|
-
item.timeoutMs = 16 // Will reset to 0, triggering shrink
|
|
323
|
-
|
|
324
|
-
scribbleManager.tick(16)
|
|
325
|
-
|
|
326
|
-
expect(item.scribble.points).toHaveLength(1)
|
|
327
|
-
expect(item.scribble.points[0]).toEqual({ x: 2, y: 2, z: 0.5 })
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
it('should reset delay when down to single point while stationary', () => {
|
|
331
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
332
|
-
item.scribble.delay = 500
|
|
333
|
-
item.delayRemaining = 0
|
|
334
|
-
item.timeoutMs = 16
|
|
335
|
-
|
|
336
|
-
scribbleManager.tick(16)
|
|
337
|
-
|
|
338
|
-
expect(item.delayRemaining).toBe(500)
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
it('should update timeout correctly', () => {
|
|
342
|
-
item.timeoutMs = 10
|
|
343
|
-
|
|
344
|
-
scribbleManager.tick(5)
|
|
345
|
-
expect(item.timeoutMs).toBe(15)
|
|
346
|
-
|
|
347
|
-
scribbleManager.tick(2)
|
|
348
|
-
expect(item.timeoutMs).toBe(0) // Reset when >= 16 (15 + 2 = 17)
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
it('should reduce delay remaining', () => {
|
|
352
|
-
item.delayRemaining = 100
|
|
353
|
-
|
|
354
|
-
scribbleManager.tick(30)
|
|
355
|
-
|
|
356
|
-
expect(item.delayRemaining).toBeLessThan(100)
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
it('should not reduce delay below 0', () => {
|
|
360
|
-
item.delayRemaining = 10
|
|
361
|
-
|
|
362
|
-
scribbleManager.tick(30)
|
|
363
|
-
|
|
364
|
-
expect(item.delayRemaining).toBe(0)
|
|
365
|
-
})
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
describe('stopping state behavior', () => {
|
|
369
|
-
let item: ScribbleItem
|
|
370
|
-
|
|
371
|
-
beforeEach(() => {
|
|
372
|
-
item = scribbleManager.addScribble({})
|
|
373
|
-
item.scribble.state = 'stopping'
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
it('should remove points when delay is finished and timeout reached', () => {
|
|
377
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
378
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
379
|
-
item.delayRemaining = 0
|
|
380
|
-
item.timeoutMs = 16
|
|
381
|
-
|
|
382
|
-
scribbleManager.tick(16)
|
|
383
|
-
|
|
384
|
-
expect(item.scribble.points).toHaveLength(1)
|
|
385
|
-
expect(item.scribble.points[0]).toEqual({ x: 2, y: 2, z: 0.5 })
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
it('should shrink scribble size when shrink is enabled', () => {
|
|
389
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
390
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
391
|
-
item.scribble.size = 20
|
|
392
|
-
item.scribble.shrink = 0.1
|
|
393
|
-
item.delayRemaining = 0
|
|
394
|
-
item.timeoutMs = 16
|
|
395
|
-
|
|
396
|
-
scribbleManager.tick(16)
|
|
397
|
-
|
|
398
|
-
expect(item.scribble.size).toBe(18) // 20 * (1 - 0.1)
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
it('should not shrink size below 1', () => {
|
|
402
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
403
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
404
|
-
item.scribble.size = 1.5
|
|
405
|
-
item.scribble.shrink = 0.8
|
|
406
|
-
item.delayRemaining = 0
|
|
407
|
-
item.timeoutMs = 16
|
|
408
|
-
|
|
409
|
-
scribbleManager.tick(16)
|
|
410
|
-
|
|
411
|
-
expect(item.scribble.size).toBe(1) // Math.max(1, 1.5 * 0.2)
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
it('should remove scribble when down to one point', () => {
|
|
415
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
416
|
-
item.delayRemaining = 0
|
|
417
|
-
item.timeoutMs = 16
|
|
418
|
-
|
|
419
|
-
scribbleManager.tick(16)
|
|
420
|
-
|
|
421
|
-
expect(scribbleManager.scribbleItems.has(item.id)).toBe(false)
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
it('should not process when delay remaining > 0', () => {
|
|
425
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
426
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
427
|
-
item.delayRemaining = 100
|
|
428
|
-
item.timeoutMs = 16
|
|
429
|
-
|
|
430
|
-
scribbleManager.tick(16)
|
|
431
|
-
|
|
432
|
-
expect(item.scribble.points).toHaveLength(2) // No change
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
it('should not process when timeout < 16', () => {
|
|
436
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
437
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
438
|
-
item.delayRemaining = 0
|
|
439
|
-
item.timeoutMs = 10
|
|
440
|
-
|
|
441
|
-
scribbleManager.tick(5)
|
|
442
|
-
|
|
443
|
-
expect(item.scribble.points).toHaveLength(2) // No change
|
|
444
|
-
expect(item.timeoutMs).toBe(15)
|
|
445
|
-
})
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
describe('paused state behavior', () => {
|
|
449
|
-
it('should do nothing when scribble is paused', () => {
|
|
450
|
-
const item = scribbleManager.addScribble({})
|
|
451
|
-
item.scribble.state = 'paused'
|
|
452
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
453
|
-
const originalPoints = [...item.scribble.points]
|
|
454
|
-
|
|
455
|
-
scribbleManager.tick(16)
|
|
456
|
-
|
|
457
|
-
expect(item.scribble.points).toEqual(originalPoints)
|
|
458
|
-
})
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
describe('instance state updates', () => {
|
|
462
|
-
it('should update instance state with scribbles', () => {
|
|
463
|
-
mockUniqueId.mockReturnValueOnce('id1').mockReturnValueOnce('id2')
|
|
464
|
-
scribbleManager.addScribble({ color: 'black' })
|
|
465
|
-
scribbleManager.addScribble({ color: 'white' })
|
|
466
|
-
|
|
467
|
-
scribbleManager.tick(16)
|
|
468
|
-
|
|
469
|
-
expect(editor.updateInstanceState).toHaveBeenCalledWith({
|
|
470
|
-
scribbles: expect.arrayContaining([
|
|
471
|
-
expect.objectContaining({
|
|
472
|
-
color: 'black',
|
|
473
|
-
points: expect.any(Array),
|
|
474
|
-
}),
|
|
475
|
-
expect.objectContaining({
|
|
476
|
-
color: 'white',
|
|
477
|
-
points: expect.any(Array),
|
|
478
|
-
}),
|
|
479
|
-
]),
|
|
480
|
-
})
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
it('should create copies of scribbles for instance state', () => {
|
|
484
|
-
const item = scribbleManager.addScribble({})
|
|
485
|
-
item.scribble.points.push({ x: 1, y: 1, z: 0.5 })
|
|
486
|
-
|
|
487
|
-
scribbleManager.tick(16)
|
|
488
|
-
|
|
489
|
-
const call = editor.updateInstanceState.mock.calls[0][0]
|
|
490
|
-
const scribbleInState = call.scribbles![0]
|
|
491
|
-
|
|
492
|
-
// Modify the original
|
|
493
|
-
item.scribble.points.push({ x: 2, y: 2, z: 0.5 })
|
|
494
|
-
|
|
495
|
-
// State copy should be unaffected
|
|
496
|
-
expect(scribbleInState.points).toHaveLength(1)
|
|
497
|
-
})
|
|
498
|
-
|
|
499
|
-
it('should limit scribbles to 5 items', () => {
|
|
500
|
-
// Add 7 scribbles
|
|
501
|
-
const colors = ['accent', 'black', 'white', 'laser', 'muted-1', 'accent', 'black'] as const
|
|
502
|
-
for (let i = 0; i < 7; i++) {
|
|
503
|
-
mockUniqueId.mockReturnValueOnce(`id${i}`)
|
|
504
|
-
scribbleManager.addScribble({ color: colors[i] })
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
scribbleManager.tick(16)
|
|
508
|
-
|
|
509
|
-
const call = editor.updateInstanceState.mock.calls[0][0]
|
|
510
|
-
expect(call.scribbles).toHaveLength(5)
|
|
511
|
-
})
|
|
512
|
-
})
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
describe('edge cases and error handling', () => {
|
|
516
|
-
it('should handle Vec.Dist calculation edge cases', () => {
|
|
517
|
-
const item = scribbleManager.addScribble({})
|
|
518
|
-
item.prev = { x: 0, y: 0, z: 0 }
|
|
519
|
-
|
|
520
|
-
// Exactly distance 1
|
|
521
|
-
scribbleManager.addPoint(item.id, 1, 0)
|
|
522
|
-
expect(item.next).toEqual({ x: 1, y: 0, z: 0.5 })
|
|
523
|
-
|
|
524
|
-
// Reset and test just under distance 1
|
|
525
|
-
item.next = null
|
|
526
|
-
scribbleManager.addPoint(item.id, 0.9, 0)
|
|
527
|
-
expect(item.next).toBeNull()
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
it('should handle multiple scribbles in different states', () => {
|
|
531
|
-
mockUniqueId
|
|
532
|
-
.mockReturnValueOnce('starting')
|
|
533
|
-
.mockReturnValueOnce('active')
|
|
534
|
-
.mockReturnValueOnce('stopping')
|
|
535
|
-
|
|
536
|
-
const startingItem = scribbleManager.addScribble({})
|
|
537
|
-
const activeItem = scribbleManager.addScribble({})
|
|
538
|
-
const stoppingItem = scribbleManager.addScribble({})
|
|
539
|
-
|
|
540
|
-
activeItem.scribble.state = 'active'
|
|
541
|
-
stoppingItem.scribble.state = 'stopping'
|
|
542
|
-
|
|
543
|
-
startingItem.next = { x: 1, y: 1, z: 0.5 }
|
|
544
|
-
activeItem.next = { x: 2, y: 2, z: 0.5 }
|
|
545
|
-
stoppingItem.scribble.points.push({ x: 3, y: 3, z: 0.5 })
|
|
546
|
-
stoppingItem.delayRemaining = 0
|
|
547
|
-
stoppingItem.timeoutMs = 16
|
|
548
|
-
|
|
549
|
-
scribbleManager.tick(16)
|
|
550
|
-
|
|
551
|
-
expect(startingItem.scribble.points).toHaveLength(1)
|
|
552
|
-
expect(activeItem.scribble.points).toHaveLength(1)
|
|
553
|
-
expect(scribbleManager.scribbleItems.has('stopping')).toBe(false) // Removed
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
it('should handle tick with 0 elapsed time', () => {
|
|
557
|
-
const item = scribbleManager.addScribble({})
|
|
558
|
-
item.delayRemaining = 100
|
|
559
|
-
|
|
560
|
-
expect(() => scribbleManager.tick(0)).not.toThrow()
|
|
561
|
-
expect(item.delayRemaining).toBe(100) // Should remain unchanged
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
it('should handle negative elapsed time', () => {
|
|
565
|
-
const item = scribbleManager.addScribble({})
|
|
566
|
-
item.delayRemaining = 100
|
|
567
|
-
|
|
568
|
-
scribbleManager.tick(-50)
|
|
569
|
-
|
|
570
|
-
expect(item.delayRemaining).toBe(100) // Should remain unchanged or handle gracefully
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
it('should handle empty points array operations', () => {
|
|
574
|
-
const item = scribbleManager.addScribble({})
|
|
575
|
-
item.scribble.state = 'active'
|
|
576
|
-
item.timeoutMs = 16
|
|
577
|
-
|
|
578
|
-
expect(() => scribbleManager.tick(16)).not.toThrow()
|
|
579
|
-
})
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
describe('integration scenarios', () => {
|
|
583
|
-
it('should handle complete scribble lifecycle', () => {
|
|
584
|
-
const item = scribbleManager.addScribble({ delay: 100 })
|
|
585
|
-
|
|
586
|
-
// Starting state - add points
|
|
587
|
-
for (let i = 0; i < 10; i++) {
|
|
588
|
-
item.next = { x: i, y: i, z: 0.5 }
|
|
589
|
-
item.prev = null
|
|
590
|
-
scribbleManager.tick(16)
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
expect(item.scribble.state).toBe('active')
|
|
594
|
-
expect(item.scribble.points).toHaveLength(10)
|
|
595
|
-
|
|
596
|
-
// Stop the scribble
|
|
597
|
-
scribbleManager.stop(item.id)
|
|
598
|
-
expect(item.scribble.state).toBe('stopping')
|
|
599
|
-
|
|
600
|
-
// Process until removed
|
|
601
|
-
let iterations = 0
|
|
602
|
-
while (scribbleManager.scribbleItems.has(item.id) && iterations < 20) {
|
|
603
|
-
scribbleManager.tick(16)
|
|
604
|
-
iterations++
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
expect(scribbleManager.scribbleItems.has(item.id)).toBe(false)
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
it('should handle rapid point additions', () => {
|
|
611
|
-
const item = scribbleManager.addScribble({})
|
|
612
|
-
|
|
613
|
-
// Add many points rapidly
|
|
614
|
-
for (let i = 0; i < 100; i++) {
|
|
615
|
-
scribbleManager.addPoint(item.id, i * 2, i * 2) // Ensure distance > 1
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
expect(item.next).toEqual({ x: 198, y: 198, z: 0.5 })
|
|
619
|
-
})
|
|
620
|
-
})
|
|
621
|
-
})
|