@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.
Files changed (117) hide show
  1. package/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
  2. package/dist-cjs/createTLSchema.js.map +2 -2
  3. package/dist-cjs/index.d.ts +242 -71
  4. package/dist-cjs/index.js +4 -1
  5. package/dist-cjs/index.js.map +2 -2
  6. package/dist-cjs/misc/TLOpacity.js +1 -5
  7. package/dist-cjs/misc/TLOpacity.js.map +2 -2
  8. package/dist-cjs/misc/TLRichText.js +5 -1
  9. package/dist-cjs/misc/TLRichText.js.map +2 -2
  10. package/dist-cjs/misc/b64Vecs.js +224 -0
  11. package/dist-cjs/misc/b64Vecs.js.map +7 -0
  12. package/dist-cjs/records/TLAsset.js.map +1 -1
  13. package/dist-cjs/records/TLBinding.js.map +2 -2
  14. package/dist-cjs/records/TLShape.js.map +2 -2
  15. package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
  16. package/dist-cjs/shapes/TLArrowShape.js +26 -13
  17. package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
  18. package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
  19. package/dist-cjs/shapes/TLDrawShape.js +37 -4
  20. package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
  21. package/dist-cjs/shapes/TLEmbedShape.js +17 -0
  22. package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
  23. package/dist-cjs/shapes/TLGeoShape.js +12 -1
  24. package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
  25. package/dist-cjs/shapes/TLHighlightShape.js +29 -2
  26. package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
  27. package/dist-cjs/shapes/TLNoteShape.js +12 -1
  28. package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
  29. package/dist-cjs/shapes/TLTextShape.js +12 -1
  30. package/dist-cjs/shapes/TLTextShape.js.map +2 -2
  31. package/dist-cjs/store-migrations.js +15 -15
  32. package/dist-cjs/store-migrations.js.map +2 -2
  33. package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
  34. package/dist-esm/createTLSchema.mjs.map +2 -2
  35. package/dist-esm/index.d.mts +242 -71
  36. package/dist-esm/index.mjs +5 -1
  37. package/dist-esm/index.mjs.map +2 -2
  38. package/dist-esm/misc/TLOpacity.mjs +1 -5
  39. package/dist-esm/misc/TLOpacity.mjs.map +2 -2
  40. package/dist-esm/misc/TLRichText.mjs +5 -1
  41. package/dist-esm/misc/TLRichText.mjs.map +2 -2
  42. package/dist-esm/misc/b64Vecs.mjs +204 -0
  43. package/dist-esm/misc/b64Vecs.mjs.map +7 -0
  44. package/dist-esm/records/TLAsset.mjs.map +1 -1
  45. package/dist-esm/records/TLBinding.mjs.map +2 -2
  46. package/dist-esm/records/TLShape.mjs.map +2 -2
  47. package/dist-esm/shapes/TLArrowShape.mjs +26 -13
  48. package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
  49. package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
  50. package/dist-esm/shapes/TLDrawShape.mjs +37 -4
  51. package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
  52. package/dist-esm/shapes/TLEmbedShape.mjs +17 -0
  53. package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
  54. package/dist-esm/shapes/TLGeoShape.mjs +12 -1
  55. package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
  56. package/dist-esm/shapes/TLHighlightShape.mjs +29 -2
  57. package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
  58. package/dist-esm/shapes/TLNoteShape.mjs +12 -1
  59. package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
  60. package/dist-esm/shapes/TLTextShape.mjs +12 -1
  61. package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
  62. package/dist-esm/store-migrations.mjs +15 -15
  63. package/dist-esm/store-migrations.mjs.map +2 -2
  64. package/package.json +8 -8
  65. package/src/__tests__/migrationTestUtils.ts +9 -3
  66. package/src/bindings/TLBaseBinding.ts +25 -14
  67. package/src/createTLSchema.ts +8 -2
  68. package/src/index.ts +9 -0
  69. package/src/migrations.test.ts +149 -1
  70. package/src/misc/TLOpacity.ts +1 -5
  71. package/src/misc/TLRichText.ts +6 -1
  72. package/src/misc/b64Vecs.ts +308 -0
  73. package/src/records/TLAsset.ts +2 -2
  74. package/src/records/TLBinding.ts +65 -23
  75. package/src/records/TLShape.ts +100 -5
  76. package/src/shapes/ShapeWithCrop.ts +2 -2
  77. package/src/shapes/TLArrowShape.ts +28 -14
  78. package/src/shapes/TLBaseShape.ts +34 -10
  79. package/src/shapes/TLDrawShape.ts +59 -12
  80. package/src/shapes/TLEmbedShape.ts +17 -0
  81. package/src/shapes/TLGeoShape.ts +14 -1
  82. package/src/shapes/TLHighlightShape.ts +37 -0
  83. package/src/shapes/TLNoteShape.ts +15 -1
  84. package/src/shapes/TLTextShape.ts +16 -2
  85. package/src/store-migrations.ts +17 -16
  86. package/src/assets/TLBookmarkAsset.test.ts +0 -96
  87. package/src/assets/TLImageAsset.test.ts +0 -213
  88. package/src/assets/TLVideoAsset.test.ts +0 -105
  89. package/src/bindings/TLArrowBinding.test.ts +0 -55
  90. package/src/misc/id-validator.test.ts +0 -50
  91. package/src/records/TLAsset.test.ts +0 -234
  92. package/src/records/TLBinding.test.ts +0 -22
  93. package/src/records/TLCamera.test.ts +0 -19
  94. package/src/records/TLDocument.test.ts +0 -35
  95. package/src/records/TLInstance.test.ts +0 -201
  96. package/src/records/TLPage.test.ts +0 -110
  97. package/src/records/TLPageState.test.ts +0 -228
  98. package/src/records/TLPointer.test.ts +0 -63
  99. package/src/records/TLPresence.test.ts +0 -190
  100. package/src/records/TLRecord.test.ts +0 -70
  101. package/src/records/TLShape.test.ts +0 -232
  102. package/src/shapes/ShapeWithCrop.test.ts +0 -18
  103. package/src/shapes/TLArrowShape.test.ts +0 -505
  104. package/src/shapes/TLBaseShape.test.ts +0 -142
  105. package/src/shapes/TLBookmarkShape.test.ts +0 -122
  106. package/src/shapes/TLDrawShape.test.ts +0 -177
  107. package/src/shapes/TLEmbedShape.test.ts +0 -286
  108. package/src/shapes/TLFrameShape.test.ts +0 -71
  109. package/src/shapes/TLGeoShape.test.ts +0 -247
  110. package/src/shapes/TLGroupShape.test.ts +0 -59
  111. package/src/shapes/TLHighlightShape.test.ts +0 -325
  112. package/src/shapes/TLImageShape.test.ts +0 -534
  113. package/src/shapes/TLLineShape.test.ts +0 -269
  114. package/src/shapes/TLNoteShape.test.ts +0 -1568
  115. package/src/shapes/TLTextShape.test.ts +0 -407
  116. package/src/shapes/TLVideoShape.test.ts +0 -112
  117. 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
- })