@tldraw/tlschema 4.2.1 → 4.2.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/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +242 -71
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +1 -5
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +5 -1
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/misc/b64Vecs.js +224 -0
- package/dist-cjs/misc/b64Vecs.js.map +7 -0
- package/dist-cjs/records/TLAsset.js.map +1 -1
- package/dist-cjs/records/TLBinding.js.map +2 -2
- package/dist-cjs/records/TLShape.js.map +2 -2
- package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
- package/dist-cjs/shapes/TLArrowShape.js +26 -13
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +37 -4
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +17 -0
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +12 -1
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +29 -2
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +12 -1
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +12 -1
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/store-migrations.js +15 -15
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +242 -71
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +1 -5
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +5 -1
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/misc/b64Vecs.mjs +204 -0
- package/dist-esm/misc/b64Vecs.mjs.map +7 -0
- package/dist-esm/records/TLAsset.mjs.map +1 -1
- package/dist-esm/records/TLBinding.mjs.map +2 -2
- package/dist-esm/records/TLShape.mjs.map +2 -2
- package/dist-esm/shapes/TLArrowShape.mjs +26 -13
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +37 -4
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +17 -0
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +12 -1
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +29 -2
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +12 -1
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +12 -1
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs +15 -15
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/package.json +8 -8
- package/src/__tests__/migrationTestUtils.ts +9 -3
- package/src/bindings/TLBaseBinding.ts +25 -14
- package/src/createTLSchema.ts +8 -2
- package/src/index.ts +9 -0
- package/src/migrations.test.ts +149 -1
- package/src/misc/TLOpacity.ts +1 -5
- package/src/misc/TLRichText.ts +6 -1
- package/src/misc/b64Vecs.ts +308 -0
- package/src/records/TLAsset.ts +2 -2
- package/src/records/TLBinding.ts +65 -23
- package/src/records/TLShape.ts +100 -5
- package/src/shapes/ShapeWithCrop.ts +2 -2
- package/src/shapes/TLArrowShape.ts +28 -14
- package/src/shapes/TLBaseShape.ts +34 -10
- package/src/shapes/TLDrawShape.ts +59 -12
- package/src/shapes/TLEmbedShape.ts +17 -0
- package/src/shapes/TLGeoShape.ts +14 -1
- package/src/shapes/TLHighlightShape.ts +37 -0
- package/src/shapes/TLNoteShape.ts +15 -1
- package/src/shapes/TLTextShape.ts +16 -2
- package/src/store-migrations.ts +17 -16
- package/src/assets/TLBookmarkAsset.test.ts +0 -96
- package/src/assets/TLImageAsset.test.ts +0 -213
- package/src/assets/TLVideoAsset.test.ts +0 -105
- package/src/bindings/TLArrowBinding.test.ts +0 -55
- package/src/misc/id-validator.test.ts +0 -50
- package/src/records/TLAsset.test.ts +0 -234
- package/src/records/TLBinding.test.ts +0 -22
- package/src/records/TLCamera.test.ts +0 -19
- package/src/records/TLDocument.test.ts +0 -35
- package/src/records/TLInstance.test.ts +0 -201
- package/src/records/TLPage.test.ts +0 -110
- package/src/records/TLPageState.test.ts +0 -228
- package/src/records/TLPointer.test.ts +0 -63
- package/src/records/TLPresence.test.ts +0 -190
- package/src/records/TLRecord.test.ts +0 -70
- package/src/records/TLShape.test.ts +0 -232
- package/src/shapes/ShapeWithCrop.test.ts +0 -18
- package/src/shapes/TLArrowShape.test.ts +0 -505
- package/src/shapes/TLBaseShape.test.ts +0 -142
- package/src/shapes/TLBookmarkShape.test.ts +0 -122
- package/src/shapes/TLDrawShape.test.ts +0 -177
- package/src/shapes/TLEmbedShape.test.ts +0 -286
- package/src/shapes/TLFrameShape.test.ts +0 -71
- package/src/shapes/TLGeoShape.test.ts +0 -247
- package/src/shapes/TLGroupShape.test.ts +0 -59
- package/src/shapes/TLHighlightShape.test.ts +0 -325
- package/src/shapes/TLImageShape.test.ts +0 -534
- package/src/shapes/TLLineShape.test.ts +0 -269
- package/src/shapes/TLNoteShape.test.ts +0 -1568
- package/src/shapes/TLTextShape.test.ts +0 -407
- package/src/shapes/TLVideoShape.test.ts +0 -112
- package/src/styles/TLColorStyle.test.ts +0 -439
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
instancePageStateMigrations,
|
|
4
|
-
InstancePageStateRecordType,
|
|
5
|
-
instancePageStateValidator,
|
|
6
|
-
instancePageStateVersions,
|
|
7
|
-
TLInstancePageStateId,
|
|
8
|
-
} from './TLPageState'
|
|
9
|
-
|
|
10
|
-
describe('instancePageStateValidator', () => {
|
|
11
|
-
it('should reject invalid typeName', () => {
|
|
12
|
-
const invalidPageState = {
|
|
13
|
-
typeName: 'not-instance-page-state',
|
|
14
|
-
id: 'instance_page_state:test' as TLInstancePageStateId,
|
|
15
|
-
pageId: 'page:test' as any,
|
|
16
|
-
selectedShapeIds: [],
|
|
17
|
-
hintingShapeIds: [],
|
|
18
|
-
erasingShapeIds: [],
|
|
19
|
-
hoveredShapeId: null,
|
|
20
|
-
editingShapeId: null,
|
|
21
|
-
croppingShapeId: null,
|
|
22
|
-
focusedGroupId: null,
|
|
23
|
-
meta: {},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
expect(() => instancePageStateValidator.validate(invalidPageState)).toThrow()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('should reject invalid id format', () => {
|
|
30
|
-
const invalidPageState = {
|
|
31
|
-
typeName: 'instance_page_state',
|
|
32
|
-
id: 'not-valid-id' as TLInstancePageStateId,
|
|
33
|
-
pageId: 'page:test' as any,
|
|
34
|
-
selectedShapeIds: [],
|
|
35
|
-
hintingShapeIds: [],
|
|
36
|
-
erasingShapeIds: [],
|
|
37
|
-
hoveredShapeId: null,
|
|
38
|
-
editingShapeId: null,
|
|
39
|
-
croppingShapeId: null,
|
|
40
|
-
focusedGroupId: null,
|
|
41
|
-
meta: {},
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
expect(() => instancePageStateValidator.validate(invalidPageState)).toThrow()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should reject missing required fields', () => {
|
|
48
|
-
const incompletePageState = {
|
|
49
|
-
typeName: 'instance_page_state',
|
|
50
|
-
id: 'instance_page_state:test' as TLInstancePageStateId,
|
|
51
|
-
pageId: 'page:test' as any,
|
|
52
|
-
// missing required array fields
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
expect(() => instancePageStateValidator.validate(incompletePageState)).toThrow()
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
describe('instancePageStateMigrations', () => {
|
|
60
|
-
it('should migrate AddCroppingId correctly', () => {
|
|
61
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
62
|
-
(m) => m.id === instancePageStateVersions.AddCroppingId
|
|
63
|
-
)!
|
|
64
|
-
const oldRecord: any = { id: 'instance_page_state:test', typeName: 'instance_page_state' }
|
|
65
|
-
migration.up(oldRecord)
|
|
66
|
-
|
|
67
|
-
expect(oldRecord.croppingShapeId).toBe(null)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should migrate RemoveInstanceIdAndCameraId correctly', () => {
|
|
71
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
72
|
-
(m) => m.id === instancePageStateVersions.RemoveInstanceIdAndCameraId
|
|
73
|
-
)!
|
|
74
|
-
const oldRecord: any = {
|
|
75
|
-
id: 'instance_page_state:test',
|
|
76
|
-
typeName: 'instance_page_state',
|
|
77
|
-
instanceId: 'instance:removed',
|
|
78
|
-
cameraId: 'camera:removed',
|
|
79
|
-
otherProp: 'keep-me',
|
|
80
|
-
}
|
|
81
|
-
migration.up(oldRecord)
|
|
82
|
-
|
|
83
|
-
expect(oldRecord.instanceId).toBeUndefined()
|
|
84
|
-
expect(oldRecord.cameraId).toBeUndefined()
|
|
85
|
-
expect(oldRecord.otherProp).toBe('keep-me')
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('should migrate AddMeta correctly', () => {
|
|
89
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
90
|
-
(m) => m.id === instancePageStateVersions.AddMeta
|
|
91
|
-
)!
|
|
92
|
-
const oldRecord: any = { id: 'instance_page_state:test', typeName: 'instance_page_state' }
|
|
93
|
-
migration.up(oldRecord)
|
|
94
|
-
|
|
95
|
-
expect(oldRecord.meta).toEqual({})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('should handle RenameProperties migration (noop)', () => {
|
|
99
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
100
|
-
(m) => m.id === instancePageStateVersions.RenameProperties
|
|
101
|
-
)!
|
|
102
|
-
const oldRecord: any = {
|
|
103
|
-
id: 'instance_page_state:test',
|
|
104
|
-
typeName: 'instance_page_state',
|
|
105
|
-
selectedIds: ['shape:1'],
|
|
106
|
-
}
|
|
107
|
-
const originalRecord = { ...oldRecord }
|
|
108
|
-
migration.up(oldRecord)
|
|
109
|
-
|
|
110
|
-
// Should be a noop
|
|
111
|
-
expect(oldRecord).toEqual(originalRecord)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('should migrate RenamePropertiesAgain correctly', () => {
|
|
115
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
116
|
-
(m) => m.id === instancePageStateVersions.RenamePropertiesAgain
|
|
117
|
-
)!
|
|
118
|
-
const oldRecord: any = {
|
|
119
|
-
id: 'instance_page_state:test',
|
|
120
|
-
typeName: 'instance_page_state',
|
|
121
|
-
selectedIds: ['shape:1', 'shape:2'],
|
|
122
|
-
hintingIds: ['shape:3'],
|
|
123
|
-
erasingIds: ['shape:4'],
|
|
124
|
-
hoveredId: 'shape:5',
|
|
125
|
-
editingId: 'shape:6',
|
|
126
|
-
croppingId: 'shape:7',
|
|
127
|
-
focusLayerId: 'shape:8',
|
|
128
|
-
}
|
|
129
|
-
migration.up(oldRecord)
|
|
130
|
-
|
|
131
|
-
expect(oldRecord.selectedShapeIds).toEqual(['shape:1', 'shape:2'])
|
|
132
|
-
expect(oldRecord.hintingShapeIds).toEqual(['shape:3'])
|
|
133
|
-
expect(oldRecord.erasingShapeIds).toEqual(['shape:4'])
|
|
134
|
-
expect(oldRecord.hoveredShapeId).toBe('shape:5')
|
|
135
|
-
expect(oldRecord.editingShapeId).toBe('shape:6')
|
|
136
|
-
expect(oldRecord.croppingShapeId).toBe('shape:7')
|
|
137
|
-
expect(oldRecord.focusedGroupId).toBe('shape:8')
|
|
138
|
-
|
|
139
|
-
// Old properties should be removed
|
|
140
|
-
expect(oldRecord.selectedIds).toBeUndefined()
|
|
141
|
-
expect(oldRecord.hintingIds).toBeUndefined()
|
|
142
|
-
expect(oldRecord.erasingIds).toBeUndefined()
|
|
143
|
-
expect(oldRecord.hoveredId).toBeUndefined()
|
|
144
|
-
expect(oldRecord.editingId).toBeUndefined()
|
|
145
|
-
expect(oldRecord.croppingId).toBeUndefined()
|
|
146
|
-
expect(oldRecord.focusLayerId).toBeUndefined()
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('should handle down migration for RenamePropertiesAgain', () => {
|
|
150
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
151
|
-
(m) => m.id === instancePageStateVersions.RenamePropertiesAgain
|
|
152
|
-
)!
|
|
153
|
-
expect(migration.down).toBeDefined()
|
|
154
|
-
|
|
155
|
-
const record: any = {
|
|
156
|
-
id: 'instance_page_state:test',
|
|
157
|
-
typeName: 'instance_page_state',
|
|
158
|
-
selectedShapeIds: ['shape:1', 'shape:2'],
|
|
159
|
-
hintingShapeIds: ['shape:3'],
|
|
160
|
-
erasingShapeIds: ['shape:4'],
|
|
161
|
-
hoveredShapeId: 'shape:5',
|
|
162
|
-
editingShapeId: 'shape:6',
|
|
163
|
-
croppingShapeId: 'shape:7',
|
|
164
|
-
focusedGroupId: 'shape:8',
|
|
165
|
-
}
|
|
166
|
-
migration.down!(record)
|
|
167
|
-
|
|
168
|
-
expect(record.selectedIds).toEqual(['shape:1', 'shape:2'])
|
|
169
|
-
expect(record.hintingIds).toEqual(['shape:3'])
|
|
170
|
-
expect(record.erasingIds).toEqual(['shape:4'])
|
|
171
|
-
expect(record.hoveredId).toBe('shape:5')
|
|
172
|
-
expect(record.editingId).toBe('shape:6')
|
|
173
|
-
expect(record.croppingId).toBe('shape:7')
|
|
174
|
-
expect(record.focusLayerId).toBe('shape:8')
|
|
175
|
-
|
|
176
|
-
// New properties should be removed
|
|
177
|
-
expect(record.selectedShapeIds).toBeUndefined()
|
|
178
|
-
expect(record.hintingShapeIds).toBeUndefined()
|
|
179
|
-
expect(record.erasingShapeIds).toBeUndefined()
|
|
180
|
-
expect(record.hoveredShapeId).toBeUndefined()
|
|
181
|
-
expect(record.editingShapeId).toBeUndefined()
|
|
182
|
-
expect(record.croppingShapeId).toBeUndefined()
|
|
183
|
-
expect(record.focusedGroupId).toBeUndefined()
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('should handle croppingShapeId fallback in RenamePropertiesAgain', () => {
|
|
187
|
-
const migration = instancePageStateMigrations.sequence.find(
|
|
188
|
-
(m) => m.id === instancePageStateVersions.RenamePropertiesAgain
|
|
189
|
-
)!
|
|
190
|
-
|
|
191
|
-
// Test with existing croppingShapeId
|
|
192
|
-
const recordWithCroppingShapeId: any = {
|
|
193
|
-
croppingShapeId: 'shape:existing',
|
|
194
|
-
croppingId: 'shape:fallback',
|
|
195
|
-
}
|
|
196
|
-
migration.up(recordWithCroppingShapeId)
|
|
197
|
-
expect(recordWithCroppingShapeId.croppingShapeId).toBe('shape:existing')
|
|
198
|
-
|
|
199
|
-
// Test with only croppingId
|
|
200
|
-
const recordWithCroppingId: any = {
|
|
201
|
-
croppingId: 'shape:fallback',
|
|
202
|
-
}
|
|
203
|
-
migration.up(recordWithCroppingId)
|
|
204
|
-
expect(recordWithCroppingId.croppingShapeId).toBe('shape:fallback')
|
|
205
|
-
|
|
206
|
-
// Test with neither
|
|
207
|
-
const recordWithNeither: any = {}
|
|
208
|
-
migration.up(recordWithNeither)
|
|
209
|
-
expect(recordWithNeither.croppingShapeId).toBe(null)
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
describe('InstancePageStateRecordType', () => {
|
|
214
|
-
it('should have correct ephemeral keys configuration', () => {
|
|
215
|
-
// Non-ephemeral keys (persistent)
|
|
216
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.pageId).toBe(false)
|
|
217
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.selectedShapeIds).toBe(false)
|
|
218
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.editingShapeId).toBe(false)
|
|
219
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.croppingShapeId).toBe(false)
|
|
220
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.meta).toBe(false)
|
|
221
|
-
|
|
222
|
-
// Ephemeral keys (temporary)
|
|
223
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.hintingShapeIds).toBe(true)
|
|
224
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.erasingShapeIds).toBe(true)
|
|
225
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.hoveredShapeId).toBe(true)
|
|
226
|
-
expect(InstancePageStateRecordType.ephemeralKeys?.focusedGroupId).toBe(true)
|
|
227
|
-
})
|
|
228
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { pointerMigrations, pointerValidator, pointerVersions, TLPOINTER_ID } from './TLPointer'
|
|
3
|
-
|
|
4
|
-
describe('pointerValidator', () => {
|
|
5
|
-
it('should validate valid pointer records', () => {
|
|
6
|
-
const validPointer = {
|
|
7
|
-
typeName: 'pointer',
|
|
8
|
-
id: TLPOINTER_ID,
|
|
9
|
-
x: 100,
|
|
10
|
-
y: 200,
|
|
11
|
-
lastActivityTimestamp: Date.now(),
|
|
12
|
-
meta: {},
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
expect(() => pointerValidator.validate(validPointer)).not.toThrow()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should reject pointers with invalid typeName', () => {
|
|
19
|
-
const invalidPointer = {
|
|
20
|
-
typeName: 'not-pointer',
|
|
21
|
-
id: TLPOINTER_ID,
|
|
22
|
-
x: 0,
|
|
23
|
-
y: 0,
|
|
24
|
-
lastActivityTimestamp: 0,
|
|
25
|
-
meta: {},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
expect(() => pointerValidator.validate(invalidPointer)).toThrow()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should reject pointers with missing required fields', () => {
|
|
32
|
-
const incompletePointer = {
|
|
33
|
-
typeName: 'pointer',
|
|
34
|
-
id: TLPOINTER_ID,
|
|
35
|
-
x: 0,
|
|
36
|
-
// missing y, lastActivityTimestamp, meta
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
expect(() => pointerValidator.validate(incompletePointer)).toThrow()
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
describe('pointerMigrations', () => {
|
|
44
|
-
it('should apply AddMeta migration correctly', () => {
|
|
45
|
-
const addMetaMigration = pointerMigrations.sequence.find(
|
|
46
|
-
(m) => m.id === pointerVersions.AddMeta
|
|
47
|
-
)!
|
|
48
|
-
|
|
49
|
-
const oldRecord: any = {
|
|
50
|
-
typeName: 'pointer',
|
|
51
|
-
id: TLPOINTER_ID,
|
|
52
|
-
x: 100,
|
|
53
|
-
y: 200,
|
|
54
|
-
lastActivityTimestamp: 123456,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
addMetaMigration.up(oldRecord)
|
|
58
|
-
expect(oldRecord.meta).toEqual({})
|
|
59
|
-
expect(oldRecord.x).toBe(100)
|
|
60
|
-
expect(oldRecord.y).toBe(200)
|
|
61
|
-
expect(oldRecord.lastActivityTimestamp).toBe(123456)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
instancePresenceMigrations,
|
|
4
|
-
instancePresenceValidator,
|
|
5
|
-
instancePresenceVersions,
|
|
6
|
-
TLInstancePresenceID,
|
|
7
|
-
} from './TLPresence'
|
|
8
|
-
|
|
9
|
-
describe('instancePresenceValidator', () => {
|
|
10
|
-
it('should validate valid instance presence records', () => {
|
|
11
|
-
const validPresence = {
|
|
12
|
-
typeName: 'instance_presence',
|
|
13
|
-
id: 'instance_presence:test' as TLInstancePresenceID,
|
|
14
|
-
userId: 'user123',
|
|
15
|
-
userName: 'Test User',
|
|
16
|
-
lastActivityTimestamp: null,
|
|
17
|
-
color: '#007AFF',
|
|
18
|
-
camera: null,
|
|
19
|
-
selectedShapeIds: [],
|
|
20
|
-
currentPageId: 'page:main' as any,
|
|
21
|
-
brush: null,
|
|
22
|
-
scribbles: [],
|
|
23
|
-
screenBounds: null,
|
|
24
|
-
followingUserId: null,
|
|
25
|
-
cursor: null,
|
|
26
|
-
chatMessage: '',
|
|
27
|
-
meta: {},
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
expect(() => instancePresenceValidator.validate(validPresence)).not.toThrow()
|
|
31
|
-
const validated = instancePresenceValidator.validate(validPresence)
|
|
32
|
-
expect(validated).toEqual(validPresence)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('should validate presence with complete data', () => {
|
|
36
|
-
const complexPresence = {
|
|
37
|
-
typeName: 'instance_presence',
|
|
38
|
-
id: 'instance_presence:complex' as TLInstancePresenceID,
|
|
39
|
-
userId: 'user456',
|
|
40
|
-
userName: 'Complex User',
|
|
41
|
-
lastActivityTimestamp: Date.now(),
|
|
42
|
-
color: '#FF3B30',
|
|
43
|
-
camera: { x: -100, y: 200, z: 0.75 },
|
|
44
|
-
selectedShapeIds: ['shape:1' as any, 'shape:2' as any],
|
|
45
|
-
currentPageId: 'page:design' as any,
|
|
46
|
-
brush: { x: 50, y: 75, w: 150, h: 100 },
|
|
47
|
-
scribbles: [
|
|
48
|
-
{
|
|
49
|
-
id: 'scribble:1',
|
|
50
|
-
points: [{ x: 0, y: 0, z: 0.5 }],
|
|
51
|
-
size: 4,
|
|
52
|
-
color: 'black',
|
|
53
|
-
opacity: 1,
|
|
54
|
-
state: 'starting',
|
|
55
|
-
delay: 0,
|
|
56
|
-
shrink: 0,
|
|
57
|
-
taper: false,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
screenBounds: { x: 0, y: 0, w: 2560, h: 1440 },
|
|
61
|
-
followingUserId: 'leader123',
|
|
62
|
-
cursor: { x: 300, y: 400, type: 'pointer', rotation: 45 },
|
|
63
|
-
chatMessage: 'Working on design!',
|
|
64
|
-
meta: { team: 'design', role: 'designer' },
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
expect(() => instancePresenceValidator.validate(complexPresence)).not.toThrow()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should reject invalid typeName', () => {
|
|
71
|
-
const invalidPresence = {
|
|
72
|
-
typeName: 'not-instance-presence',
|
|
73
|
-
id: 'instance_presence:test' as TLInstancePresenceID,
|
|
74
|
-
userId: 'user123',
|
|
75
|
-
userName: 'Test',
|
|
76
|
-
lastActivityTimestamp: null,
|
|
77
|
-
color: '#000000',
|
|
78
|
-
camera: null,
|
|
79
|
-
selectedShapeIds: [],
|
|
80
|
-
currentPageId: 'page:main' as any,
|
|
81
|
-
brush: null,
|
|
82
|
-
scribbles: [],
|
|
83
|
-
screenBounds: null,
|
|
84
|
-
followingUserId: null,
|
|
85
|
-
cursor: null,
|
|
86
|
-
chatMessage: '',
|
|
87
|
-
meta: {},
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
expect(() => instancePresenceValidator.validate(invalidPresence)).toThrow()
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
describe('instancePresenceMigrations', () => {
|
|
95
|
-
it('should migrate AddScribbleDelay correctly', () => {
|
|
96
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
97
|
-
(m) => m.id === instancePresenceVersions.AddScribbleDelay
|
|
98
|
-
)!
|
|
99
|
-
|
|
100
|
-
const oldRecordWithScribble: any = {
|
|
101
|
-
scribble: { points: [], size: 4, color: 'black' },
|
|
102
|
-
}
|
|
103
|
-
migration.up(oldRecordWithScribble)
|
|
104
|
-
expect(oldRecordWithScribble.scribble.delay).toBe(0)
|
|
105
|
-
|
|
106
|
-
const oldRecordWithoutScribble: any = {
|
|
107
|
-
scribble: null,
|
|
108
|
-
}
|
|
109
|
-
migration.up(oldRecordWithoutScribble)
|
|
110
|
-
expect(oldRecordWithoutScribble.scribble).toBe(null)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('should migrate RemoveInstanceId correctly', () => {
|
|
114
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
115
|
-
(m) => m.id === instancePresenceVersions.RemoveInstanceId
|
|
116
|
-
)!
|
|
117
|
-
const oldRecord: any = {
|
|
118
|
-
instanceId: 'instance:removed',
|
|
119
|
-
otherProp: 'keep-me',
|
|
120
|
-
}
|
|
121
|
-
migration.up(oldRecord)
|
|
122
|
-
|
|
123
|
-
expect(oldRecord.instanceId).toBeUndefined()
|
|
124
|
-
expect(oldRecord.otherProp).toBe('keep-me')
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('should migrate AddChatMessage correctly', () => {
|
|
128
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
129
|
-
(m) => m.id === instancePresenceVersions.AddChatMessage
|
|
130
|
-
)!
|
|
131
|
-
const oldRecord: any = { id: 'instance_presence:test' }
|
|
132
|
-
migration.up(oldRecord)
|
|
133
|
-
|
|
134
|
-
expect(oldRecord.chatMessage).toBe('')
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('should migrate AddMeta correctly', () => {
|
|
138
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
139
|
-
(m) => m.id === instancePresenceVersions.AddMeta
|
|
140
|
-
)!
|
|
141
|
-
const oldRecord: any = { id: 'instance_presence:test' }
|
|
142
|
-
migration.up(oldRecord)
|
|
143
|
-
|
|
144
|
-
expect(oldRecord.meta).toEqual({})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should handle RenameSelectedShapeIds migration (noop)', () => {
|
|
148
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
149
|
-
(m) => m.id === instancePresenceVersions.RenameSelectedShapeIds
|
|
150
|
-
)!
|
|
151
|
-
const oldRecord: any = { selectedShapeIds: ['shape:1'] }
|
|
152
|
-
const originalRecord = { ...oldRecord }
|
|
153
|
-
migration.up(oldRecord)
|
|
154
|
-
|
|
155
|
-
// Should be a noop
|
|
156
|
-
expect(oldRecord).toEqual(originalRecord)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('should handle NullableCameraCursor migration up (noop)', () => {
|
|
160
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
161
|
-
(m) => m.id === instancePresenceVersions.NullableCameraCursor
|
|
162
|
-
)!
|
|
163
|
-
const record: any = { camera: null, cursor: null }
|
|
164
|
-
const originalRecord = { ...record }
|
|
165
|
-
migration.up(record)
|
|
166
|
-
|
|
167
|
-
// Should be a noop
|
|
168
|
-
expect(record).toEqual(originalRecord)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('should handle NullableCameraCursor migration down', () => {
|
|
172
|
-
const migration = instancePresenceMigrations.sequence.find(
|
|
173
|
-
(m) => m.id === instancePresenceVersions.NullableCameraCursor
|
|
174
|
-
)!
|
|
175
|
-
expect(migration.down).toBeDefined()
|
|
176
|
-
|
|
177
|
-
const record: any = {
|
|
178
|
-
camera: null,
|
|
179
|
-
lastActivityTimestamp: null,
|
|
180
|
-
cursor: null,
|
|
181
|
-
screenBounds: null,
|
|
182
|
-
}
|
|
183
|
-
migration.down!(record)
|
|
184
|
-
|
|
185
|
-
expect(record.camera).toEqual({ x: 0, y: 0, z: 1 })
|
|
186
|
-
expect(record.lastActivityTimestamp).toBe(0)
|
|
187
|
-
expect(record.cursor).toEqual({ type: 'default', x: 0, y: 0, rotation: 0 })
|
|
188
|
-
expect(record.screenBounds).toEqual({ x: 0, y: 0, w: 1, h: 1 })
|
|
189
|
-
})
|
|
190
|
-
})
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { TLRecord } from './TLRecord'
|
|
3
|
-
|
|
4
|
-
describe('TLRecord', () => {
|
|
5
|
-
it('should support type discrimination by typeName', () => {
|
|
6
|
-
function processRecord(record: TLRecord): string {
|
|
7
|
-
// TypeScript should be able to narrow types based on typeName
|
|
8
|
-
switch (record.typeName) {
|
|
9
|
-
case 'shape':
|
|
10
|
-
return `Shape at (${record.x}, ${record.y})`
|
|
11
|
-
case 'page':
|
|
12
|
-
return `Page: ${record.name}`
|
|
13
|
-
case 'asset':
|
|
14
|
-
return `Asset: ${record.type}`
|
|
15
|
-
case 'binding':
|
|
16
|
-
return `Binding from ${record.fromId} to ${record.toId}`
|
|
17
|
-
case 'camera':
|
|
18
|
-
return `Camera at (${record.x}, ${record.y}) zoom: ${record.z}`
|
|
19
|
-
case 'document':
|
|
20
|
-
return `Document: ${record.name}, grid: ${record.gridSize}`
|
|
21
|
-
case 'instance':
|
|
22
|
-
return `Instance on page ${record.currentPageId}`
|
|
23
|
-
case 'instance_page_state':
|
|
24
|
-
return `Page state for ${record.pageId}`
|
|
25
|
-
case 'instance_presence':
|
|
26
|
-
return `Presence of ${record.userName}`
|
|
27
|
-
case 'pointer':
|
|
28
|
-
return `Pointer at (${record.x}, ${record.y})`
|
|
29
|
-
default:
|
|
30
|
-
// This should never be reached if all cases are handled
|
|
31
|
-
return 'Unknown record type'
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Test that the discriminated union works correctly
|
|
36
|
-
const shapeRecord: TLRecord = {
|
|
37
|
-
id: 'shape:test' as any,
|
|
38
|
-
typeName: 'shape',
|
|
39
|
-
type: 'geo',
|
|
40
|
-
x: 50,
|
|
41
|
-
y: 100,
|
|
42
|
-
rotation: 0,
|
|
43
|
-
index: 'a1' as any,
|
|
44
|
-
parentId: 'page:main' as any,
|
|
45
|
-
isLocked: false,
|
|
46
|
-
opacity: 1,
|
|
47
|
-
props: {
|
|
48
|
-
geo: 'rectangle',
|
|
49
|
-
w: 80,
|
|
50
|
-
h: 80,
|
|
51
|
-
color: 'black',
|
|
52
|
-
fill: 'none',
|
|
53
|
-
dash: 'draw',
|
|
54
|
-
size: 'm',
|
|
55
|
-
},
|
|
56
|
-
meta: {},
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const pageRecord: TLRecord = {
|
|
60
|
-
id: 'page:test' as any,
|
|
61
|
-
typeName: 'page',
|
|
62
|
-
name: 'Test Page',
|
|
63
|
-
index: 'a1' as any,
|
|
64
|
-
meta: {},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
expect(processRecord(shapeRecord)).toBe('Shape at (50, 100)')
|
|
68
|
-
expect(processRecord(pageRecord)).toBe('Page: Test Page')
|
|
69
|
-
})
|
|
70
|
-
})
|