@tldraw/tlschema 4.2.2 → 4.2.3
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 +71 -242
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +5 -1
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +1 -5
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- 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 +13 -26
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +4 -37
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +0 -17
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +1 -12
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +2 -29
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +1 -12
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +1 -12
- 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 +71 -242
- package/dist-esm/index.mjs +1 -5
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +5 -1
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +1 -5
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- 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 +13 -26
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +4 -37
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +0 -17
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +1 -12
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +2 -29
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +1 -12
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +1 -12
- 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 +3 -9
- package/src/assets/TLBookmarkAsset.test.ts +96 -0
- package/src/assets/TLImageAsset.test.ts +213 -0
- package/src/assets/TLVideoAsset.test.ts +105 -0
- package/src/bindings/TLArrowBinding.test.ts +55 -0
- package/src/bindings/TLBaseBinding.ts +14 -25
- package/src/createTLSchema.ts +2 -8
- package/src/index.ts +0 -9
- package/src/migrations.test.ts +1 -149
- package/src/misc/TLOpacity.ts +5 -1
- package/src/misc/TLRichText.ts +1 -6
- package/src/misc/id-validator.test.ts +50 -0
- package/src/records/TLAsset.test.ts +234 -0
- package/src/records/TLAsset.ts +2 -2
- package/src/records/TLBinding.test.ts +22 -0
- package/src/records/TLBinding.ts +23 -65
- package/src/records/TLCamera.test.ts +19 -0
- package/src/records/TLDocument.test.ts +35 -0
- package/src/records/TLInstance.test.ts +201 -0
- package/src/records/TLPage.test.ts +110 -0
- package/src/records/TLPageState.test.ts +228 -0
- package/src/records/TLPointer.test.ts +63 -0
- package/src/records/TLPresence.test.ts +190 -0
- package/src/records/TLRecord.test.ts +70 -0
- package/src/records/TLShape.test.ts +232 -0
- package/src/records/TLShape.ts +5 -100
- package/src/shapes/ShapeWithCrop.test.ts +18 -0
- package/src/shapes/ShapeWithCrop.ts +2 -2
- package/src/shapes/TLArrowShape.test.ts +505 -0
- package/src/shapes/TLArrowShape.ts +14 -28
- package/src/shapes/TLBaseShape.test.ts +142 -0
- package/src/shapes/TLBaseShape.ts +10 -34
- package/src/shapes/TLBookmarkShape.test.ts +122 -0
- package/src/shapes/TLDrawShape.test.ts +177 -0
- package/src/shapes/TLDrawShape.ts +12 -59
- package/src/shapes/TLEmbedShape.test.ts +286 -0
- package/src/shapes/TLEmbedShape.ts +0 -17
- package/src/shapes/TLFrameShape.test.ts +71 -0
- package/src/shapes/TLGeoShape.test.ts +247 -0
- package/src/shapes/TLGeoShape.ts +1 -14
- package/src/shapes/TLGroupShape.test.ts +59 -0
- package/src/shapes/TLHighlightShape.test.ts +325 -0
- package/src/shapes/TLHighlightShape.ts +0 -37
- package/src/shapes/TLImageShape.test.ts +534 -0
- package/src/shapes/TLLineShape.test.ts +269 -0
- package/src/shapes/TLNoteShape.test.ts +1568 -0
- package/src/shapes/TLNoteShape.ts +1 -15
- package/src/shapes/TLTextShape.test.ts +407 -0
- package/src/shapes/TLTextShape.ts +2 -16
- package/src/shapes/TLVideoShape.test.ts +112 -0
- package/src/store-migrations.ts +16 -17
- package/src/styles/TLColorStyle.test.ts +439 -0
- package/dist-cjs/misc/b64Vecs.js +0 -224
- package/dist-cjs/misc/b64Vecs.js.map +0 -7
- package/dist-esm/misc/b64Vecs.mjs +0 -204
- package/dist-esm/misc/b64Vecs.mjs.map +0 -7
- package/src/misc/b64Vecs.ts +0 -308
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { describe, expect, it, test } from 'vitest'
|
|
2
|
+
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
+
import { arrowShapeMigrations, arrowShapeProps, arrowShapeVersions } from './TLArrowShape'
|
|
4
|
+
|
|
5
|
+
describe('TLArrowShape', () => {
|
|
6
|
+
describe('arrowShapeMigrations - AddLabelColor migration', () => {
|
|
7
|
+
const { up, down } = getTestMigration(arrowShapeVersions.AddLabelColor)
|
|
8
|
+
|
|
9
|
+
describe('AddLabelColor up migration', () => {
|
|
10
|
+
it('should add labelColor property with default value "black"', () => {
|
|
11
|
+
const oldRecord = {
|
|
12
|
+
id: 'shape:arrow1',
|
|
13
|
+
typeName: 'shape',
|
|
14
|
+
type: 'arrow',
|
|
15
|
+
x: 100,
|
|
16
|
+
y: 200,
|
|
17
|
+
rotation: 0,
|
|
18
|
+
index: 'a1',
|
|
19
|
+
parentId: 'page:main',
|
|
20
|
+
isLocked: false,
|
|
21
|
+
opacity: 1,
|
|
22
|
+
props: {
|
|
23
|
+
color: 'blue',
|
|
24
|
+
fill: 'none',
|
|
25
|
+
dash: 'solid',
|
|
26
|
+
size: 'm',
|
|
27
|
+
arrowheadStart: 'none',
|
|
28
|
+
arrowheadEnd: 'arrow',
|
|
29
|
+
font: 'draw',
|
|
30
|
+
start: { x: 0, y: 0 },
|
|
31
|
+
end: { x: 100, y: 100 },
|
|
32
|
+
},
|
|
33
|
+
meta: {},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = up(oldRecord)
|
|
37
|
+
expect(result.props.labelColor).toBe('black')
|
|
38
|
+
expect(result.props.color).toBe('blue') // Preserve other props
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should preserve all existing properties during migration', () => {
|
|
42
|
+
const oldRecord = {
|
|
43
|
+
id: 'shape:arrow2',
|
|
44
|
+
typeName: 'shape',
|
|
45
|
+
type: 'arrow',
|
|
46
|
+
x: 50,
|
|
47
|
+
y: 75,
|
|
48
|
+
rotation: 0.5,
|
|
49
|
+
index: 'b1',
|
|
50
|
+
parentId: 'page:test',
|
|
51
|
+
isLocked: true,
|
|
52
|
+
opacity: 0.8,
|
|
53
|
+
props: {
|
|
54
|
+
color: 'red',
|
|
55
|
+
fill: 'solid',
|
|
56
|
+
dash: 'dashed',
|
|
57
|
+
size: 'l',
|
|
58
|
+
arrowheadStart: 'triangle',
|
|
59
|
+
arrowheadEnd: 'diamond',
|
|
60
|
+
font: 'sans',
|
|
61
|
+
start: { x: 25, y: 50 },
|
|
62
|
+
end: { x: 200, y: 150 },
|
|
63
|
+
},
|
|
64
|
+
meta: { custom: 'data' },
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const result = up(oldRecord)
|
|
68
|
+
expect(result.props.labelColor).toBe('black')
|
|
69
|
+
expect(result.props.color).toBe('red')
|
|
70
|
+
expect(result.props.fill).toBe('solid')
|
|
71
|
+
expect(result.props.dash).toBe('dashed')
|
|
72
|
+
expect(result.props.size).toBe('l')
|
|
73
|
+
expect(result.props.arrowheadStart).toBe('triangle')
|
|
74
|
+
expect(result.props.arrowheadEnd).toBe('diamond')
|
|
75
|
+
expect(result.props.font).toBe('sans')
|
|
76
|
+
expect(result.props.start).toEqual({ x: 25, y: 50 })
|
|
77
|
+
expect(result.props.end).toEqual({ x: 200, y: 150 })
|
|
78
|
+
expect(result.meta).toEqual({ custom: 'data' })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('should not modify labelColor if it already exists', () => {
|
|
82
|
+
const recordWithLabelColor = {
|
|
83
|
+
id: 'shape:arrow3',
|
|
84
|
+
typeName: 'shape',
|
|
85
|
+
type: 'arrow',
|
|
86
|
+
props: {
|
|
87
|
+
labelColor: 'red', // Already has labelColor
|
|
88
|
+
color: 'blue',
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = up(recordWithLabelColor)
|
|
93
|
+
expect(result.props.labelColor).toBe('black') // Migration sets default regardless
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('AddLabelColor down migration', () => {
|
|
98
|
+
it('should be retired (no down migration)', () => {
|
|
99
|
+
// Based on the source code, the down migration is 'retired'
|
|
100
|
+
// The getTestMigration utility should throw when trying to access down migration
|
|
101
|
+
expect(() => {
|
|
102
|
+
// This should throw since the migration is retired
|
|
103
|
+
down({})
|
|
104
|
+
}).toThrow('Migration com.tldraw.shape.arrow/1 does not have a down function')
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('arrowShapeMigrations - AddIsPrecise migration', () => {
|
|
110
|
+
const { up, down } = getTestMigration(arrowShapeVersions.AddIsPrecise)
|
|
111
|
+
|
|
112
|
+
describe('AddIsPrecise up migration', () => {
|
|
113
|
+
it('should add isPrecise property to binding start and end', () => {
|
|
114
|
+
const oldRecord = {
|
|
115
|
+
id: 'shape:arrow1',
|
|
116
|
+
props: {
|
|
117
|
+
start: {
|
|
118
|
+
type: 'binding',
|
|
119
|
+
boundShapeId: 'shape:rect1',
|
|
120
|
+
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
121
|
+
},
|
|
122
|
+
end: {
|
|
123
|
+
type: 'binding',
|
|
124
|
+
boundShapeId: 'shape:rect2',
|
|
125
|
+
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = up(oldRecord)
|
|
131
|
+
expect(result.props.start.isPrecise).toBe(false) // 0.5, 0.5 is not precise
|
|
132
|
+
expect(result.props.end.isPrecise).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should set isPrecise to true for non-center anchors', () => {
|
|
136
|
+
const oldRecord = {
|
|
137
|
+
id: 'shape:arrow1',
|
|
138
|
+
props: {
|
|
139
|
+
start: {
|
|
140
|
+
type: 'binding',
|
|
141
|
+
boundShapeId: 'shape:rect1',
|
|
142
|
+
normalizedAnchor: { x: 0.25, y: 0.75 }, // Not center
|
|
143
|
+
},
|
|
144
|
+
end: {
|
|
145
|
+
type: 'binding',
|
|
146
|
+
boundShapeId: 'shape:rect2',
|
|
147
|
+
normalizedAnchor: { x: 1, y: 0 }, // Not center
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = up(oldRecord)
|
|
153
|
+
expect(result.props.start.isPrecise).toBe(true)
|
|
154
|
+
expect(result.props.end.isPrecise).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should not modify non-binding terminals', () => {
|
|
158
|
+
const oldRecord = {
|
|
159
|
+
id: 'shape:arrow1',
|
|
160
|
+
props: {
|
|
161
|
+
start: { type: 'point', x: 0, y: 0 },
|
|
162
|
+
end: { type: 'point', x: 100, y: 100 },
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result = up(oldRecord)
|
|
167
|
+
expect(result.props.start.isPrecise).toBeUndefined()
|
|
168
|
+
expect(result.props.end.isPrecise).toBeUndefined()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should handle mixed binding and point terminals', () => {
|
|
172
|
+
const oldRecord = {
|
|
173
|
+
id: 'shape:arrow1',
|
|
174
|
+
props: {
|
|
175
|
+
start: {
|
|
176
|
+
type: 'binding',
|
|
177
|
+
boundShapeId: 'shape:rect1',
|
|
178
|
+
normalizedAnchor: { x: 0, y: 0 }, // Precise
|
|
179
|
+
},
|
|
180
|
+
end: { type: 'point', x: 100, y: 100 },
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const result = up(oldRecord)
|
|
185
|
+
expect(result.props.start.isPrecise).toBe(true)
|
|
186
|
+
expect(result.props.end.isPrecise).toBeUndefined()
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
describe('AddIsPrecise down migration', () => {
|
|
191
|
+
it('should remove isPrecise property and adjust normalizedAnchor if not precise', () => {
|
|
192
|
+
const newRecord = {
|
|
193
|
+
id: 'shape:arrow1',
|
|
194
|
+
props: {
|
|
195
|
+
start: {
|
|
196
|
+
type: 'binding',
|
|
197
|
+
boundShapeId: 'shape:rect1',
|
|
198
|
+
normalizedAnchor: { x: 0.25, y: 0.75 },
|
|
199
|
+
isPrecise: false,
|
|
200
|
+
},
|
|
201
|
+
end: {
|
|
202
|
+
type: 'binding',
|
|
203
|
+
boundShapeId: 'shape:rect2',
|
|
204
|
+
normalizedAnchor: { x: 0.1, y: 0.9 },
|
|
205
|
+
isPrecise: true,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const result = down(newRecord)
|
|
211
|
+
expect(result.props.start.isPrecise).toBeUndefined()
|
|
212
|
+
expect(result.props.start.normalizedAnchor).toEqual({ x: 0.5, y: 0.5 }) // Reset to center
|
|
213
|
+
expect(result.props.end.isPrecise).toBeUndefined()
|
|
214
|
+
expect(result.props.end.normalizedAnchor).toEqual({ x: 0.1, y: 0.9 }) // Keep precise anchor
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should not modify non-binding terminals', () => {
|
|
218
|
+
const newRecord = {
|
|
219
|
+
id: 'shape:arrow1',
|
|
220
|
+
props: {
|
|
221
|
+
start: { type: 'point', x: 0, y: 0 },
|
|
222
|
+
end: { type: 'point', x: 100, y: 100 },
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = down(newRecord)
|
|
227
|
+
expect(result.props.start).toEqual({ type: 'point', x: 0, y: 0 })
|
|
228
|
+
expect(result.props.end).toEqual({ type: 'point', x: 100, y: 100 })
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('arrowShapeMigrations - AddLabelPosition migration', () => {
|
|
234
|
+
const { up, down } = getTestMigration(arrowShapeVersions.AddLabelPosition)
|
|
235
|
+
|
|
236
|
+
describe('AddLabelPosition up migration', () => {
|
|
237
|
+
it('should add labelPosition property with default value 0.5', () => {
|
|
238
|
+
const oldRecord = {
|
|
239
|
+
id: 'shape:arrow1',
|
|
240
|
+
props: {
|
|
241
|
+
color: 'blue',
|
|
242
|
+
start: { x: 0, y: 0 },
|
|
243
|
+
end: { x: 100, y: 100 },
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const result = up(oldRecord)
|
|
248
|
+
expect(result.props.labelPosition).toBe(0.5)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should preserve existing properties during migration', () => {
|
|
252
|
+
const oldRecord = {
|
|
253
|
+
id: 'shape:arrow1',
|
|
254
|
+
props: {
|
|
255
|
+
color: 'red',
|
|
256
|
+
fill: 'solid',
|
|
257
|
+
start: { x: 25, y: 50 },
|
|
258
|
+
end: { x: 200, y: 150 },
|
|
259
|
+
bend: 0.3,
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const result = up(oldRecord)
|
|
264
|
+
expect(result.props.labelPosition).toBe(0.5)
|
|
265
|
+
expect(result.props.color).toBe('red')
|
|
266
|
+
expect(result.props.fill).toBe('solid')
|
|
267
|
+
expect(result.props.start).toEqual({ x: 25, y: 50 })
|
|
268
|
+
expect(result.props.end).toEqual({ x: 200, y: 150 })
|
|
269
|
+
expect(result.props.bend).toBe(0.3)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe('AddLabelPosition down migration', () => {
|
|
274
|
+
it('should remove labelPosition property', () => {
|
|
275
|
+
const newRecord = {
|
|
276
|
+
id: 'shape:arrow1',
|
|
277
|
+
props: {
|
|
278
|
+
color: 'blue',
|
|
279
|
+
start: { x: 0, y: 0 },
|
|
280
|
+
end: { x: 100, y: 100 },
|
|
281
|
+
labelPosition: 0.7,
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const result = down(newRecord)
|
|
286
|
+
expect(result.props.labelPosition).toBeUndefined()
|
|
287
|
+
expect(result.props.color).toBe('blue') // Preserve other props
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('arrowShapeMigrations - ExtractBindings migration', () => {
|
|
293
|
+
const migration = arrowShapeMigrations.sequence.find(
|
|
294
|
+
(m) => 'id' in m && m.id === arrowShapeVersions.ExtractBindings
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
it('should be a store-scope migration', () => {
|
|
298
|
+
expect(migration).toBeDefined()
|
|
299
|
+
if (migration && 'scope' in migration) {
|
|
300
|
+
expect(migration.scope).toBe('store')
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('should have up function for extracting bindings', () => {
|
|
305
|
+
if (migration && 'up' in migration) {
|
|
306
|
+
expect(migration.up).toBeDefined()
|
|
307
|
+
expect(typeof migration.up).toBe('function')
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Note: This migration is complex and modifies the entire store
|
|
312
|
+
// Testing the full migration would require setting up a mock store
|
|
313
|
+
// The migration extracts binding information from arrow terminals
|
|
314
|
+
// and creates separate binding records
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
describe('arrowShapeMigrations - AddScale migration', () => {
|
|
318
|
+
const { up, down } = getTestMigration(arrowShapeVersions.AddScale)
|
|
319
|
+
|
|
320
|
+
describe('AddScale up migration', () => {
|
|
321
|
+
it('should add scale property with default value 1', () => {
|
|
322
|
+
const oldRecord = {
|
|
323
|
+
id: 'shape:arrow1',
|
|
324
|
+
props: {
|
|
325
|
+
color: 'blue',
|
|
326
|
+
start: { x: 0, y: 0 },
|
|
327
|
+
end: { x: 100, y: 100 },
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const result = up(oldRecord)
|
|
332
|
+
expect(result.props.scale).toBe(1)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('should preserve existing properties during migration', () => {
|
|
336
|
+
const oldRecord = {
|
|
337
|
+
id: 'shape:arrow1',
|
|
338
|
+
props: {
|
|
339
|
+
color: 'red',
|
|
340
|
+
labelPosition: 0.3,
|
|
341
|
+
start: { x: 10, y: 20 },
|
|
342
|
+
end: { x: 200, y: 150 },
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const result = up(oldRecord)
|
|
347
|
+
expect(result.props.scale).toBe(1)
|
|
348
|
+
expect(result.props.color).toBe('red')
|
|
349
|
+
expect(result.props.labelPosition).toBe(0.3)
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
describe('AddScale down migration', () => {
|
|
354
|
+
it('should remove scale property', () => {
|
|
355
|
+
const newRecord = {
|
|
356
|
+
id: 'shape:arrow1',
|
|
357
|
+
props: {
|
|
358
|
+
color: 'blue',
|
|
359
|
+
start: { x: 0, y: 0 },
|
|
360
|
+
end: { x: 100, y: 100 },
|
|
361
|
+
scale: 1.5,
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const result = down(newRecord)
|
|
366
|
+
expect(result.props.scale).toBeUndefined()
|
|
367
|
+
expect(result.props.color).toBe('blue') // Preserve other props
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
describe('arrowShapeMigrations - AddElbow migration', () => {
|
|
373
|
+
const { up, down } = getTestMigration(arrowShapeVersions.AddElbow)
|
|
374
|
+
|
|
375
|
+
describe('AddElbow up migration', () => {
|
|
376
|
+
it('should add kind and elbowMidPoint properties with default values', () => {
|
|
377
|
+
const oldRecord = {
|
|
378
|
+
id: 'shape:arrow1',
|
|
379
|
+
props: {
|
|
380
|
+
color: 'blue',
|
|
381
|
+
start: { x: 0, y: 0 },
|
|
382
|
+
end: { x: 100, y: 100 },
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const result = up(oldRecord)
|
|
387
|
+
expect(result.props.kind).toBe('arc')
|
|
388
|
+
expect(result.props.elbowMidPoint).toBe(0.5)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it('should preserve existing properties during migration', () => {
|
|
392
|
+
const oldRecord = {
|
|
393
|
+
id: 'shape:arrow1',
|
|
394
|
+
props: {
|
|
395
|
+
color: 'red',
|
|
396
|
+
scale: 1.2,
|
|
397
|
+
labelPosition: 0.7,
|
|
398
|
+
start: { x: 25, y: 50 },
|
|
399
|
+
end: { x: 200, y: 150 },
|
|
400
|
+
},
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const result = up(oldRecord)
|
|
404
|
+
expect(result.props.kind).toBe('arc')
|
|
405
|
+
expect(result.props.elbowMidPoint).toBe(0.5)
|
|
406
|
+
expect(result.props.color).toBe('red')
|
|
407
|
+
expect(result.props.scale).toBe(1.2)
|
|
408
|
+
expect(result.props.labelPosition).toBe(0.7)
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('AddElbow down migration', () => {
|
|
413
|
+
it('should remove kind and elbowMidPoint properties', () => {
|
|
414
|
+
const newRecord = {
|
|
415
|
+
id: 'shape:arrow1',
|
|
416
|
+
props: {
|
|
417
|
+
kind: 'elbow',
|
|
418
|
+
elbowMidPoint: 0.3,
|
|
419
|
+
color: 'blue',
|
|
420
|
+
start: { x: 0, y: 0 },
|
|
421
|
+
end: { x: 100, y: 100 },
|
|
422
|
+
},
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const result = down(newRecord)
|
|
426
|
+
expect(result.props.kind).toBeUndefined()
|
|
427
|
+
expect(result.props.elbowMidPoint).toBeUndefined()
|
|
428
|
+
expect(result.props.color).toBe('blue') // Preserve other props
|
|
429
|
+
})
|
|
430
|
+
})
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
describe('arrowShapeMigrations - AddRichText migration', () => {
|
|
434
|
+
const { up } = getTestMigration(arrowShapeVersions.AddRichText)
|
|
435
|
+
|
|
436
|
+
describe('AddRichText up migration', () => {
|
|
437
|
+
it('should convert text property to richText', () => {
|
|
438
|
+
const oldRecord = {
|
|
439
|
+
id: 'shape:arrow1',
|
|
440
|
+
props: {
|
|
441
|
+
text: 'Simple text label',
|
|
442
|
+
color: 'blue',
|
|
443
|
+
start: { x: 0, y: 0 },
|
|
444
|
+
end: { x: 100, y: 100 },
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const result = up(oldRecord)
|
|
449
|
+
expect(result.props.richText).toBeDefined()
|
|
450
|
+
expect(result.props.text).toBeUndefined()
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('should handle empty text', () => {
|
|
454
|
+
const oldRecord = {
|
|
455
|
+
id: 'shape:arrow1',
|
|
456
|
+
props: {
|
|
457
|
+
text: '',
|
|
458
|
+
color: 'red',
|
|
459
|
+
start: { x: 10, y: 20 },
|
|
460
|
+
end: { x: 200, y: 150 },
|
|
461
|
+
},
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const result = up(oldRecord)
|
|
465
|
+
expect(result.props.richText).toBeDefined()
|
|
466
|
+
expect(result.props.text).toBeUndefined()
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('should preserve other properties during migration', () => {
|
|
470
|
+
const oldRecord = {
|
|
471
|
+
id: 'shape:arrow1',
|
|
472
|
+
props: {
|
|
473
|
+
text: 'Label text',
|
|
474
|
+
kind: 'elbow',
|
|
475
|
+
elbowMidPoint: 0.3,
|
|
476
|
+
color: 'green',
|
|
477
|
+
scale: 1.5,
|
|
478
|
+
},
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const result = up(oldRecord)
|
|
482
|
+
expect(result.props.richText).toBeDefined()
|
|
483
|
+
expect(result.props.text).toBeUndefined()
|
|
484
|
+
expect(result.props.kind).toBe('elbow')
|
|
485
|
+
expect(result.props.elbowMidPoint).toBe(0.3)
|
|
486
|
+
expect(result.props.color).toBe('green')
|
|
487
|
+
expect(result.props.scale).toBe(1.5)
|
|
488
|
+
})
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
// Note: The down migration is explicitly not defined (forced client update)
|
|
492
|
+
// so we don't test it
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
describe('edge cases and error handling', () => {
|
|
496
|
+
it('should handle zero scale validation correctly', () => {
|
|
497
|
+
// Zero should be invalid for scale (nonZeroNumber)
|
|
498
|
+
expect(() => arrowShapeProps.scale.validate(0)).toThrow()
|
|
499
|
+
|
|
500
|
+
// Very small positive numbers should be valid, but negative numbers should be invalid
|
|
501
|
+
expect(() => arrowShapeProps.scale.validate(0.0001)).not.toThrow()
|
|
502
|
+
expect(() => arrowShapeProps.scale.validate(-0.0001)).toThrow()
|
|
503
|
+
})
|
|
504
|
+
})
|
|
505
|
+
})
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createMigrationSequence } from '@tldraw/store'
|
|
2
|
-
import { structuredClone } from '@tldraw/utils'
|
|
3
2
|
import { T } from '@tldraw/validate'
|
|
4
3
|
import { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'
|
|
5
4
|
import { VecModel, vecModelValidator } from '../misc/geometry-types'
|
|
6
5
|
import { createBindingId } from '../records/TLBinding'
|
|
7
|
-
import {
|
|
6
|
+
import { TLShapeId, createShapePropsMigrationIds } from '../records/TLShape'
|
|
8
7
|
import { RecordProps, TLPropsMigration, createPropsMigration } from '../recordsWithProps'
|
|
9
8
|
import { StyleProp } from '../styles/StyleProp'
|
|
10
9
|
import {
|
|
@@ -277,7 +276,6 @@ export const arrowShapeVersions = createShapePropsMigrationIds('arrow', {
|
|
|
277
276
|
AddScale: 5,
|
|
278
277
|
AddElbow: 6,
|
|
279
278
|
AddRichText: 7,
|
|
280
|
-
AddRichTextAttrs: 8,
|
|
281
279
|
})
|
|
282
280
|
|
|
283
281
|
function propsMigration(migration: TLPropsMigration) {
|
|
@@ -343,8 +341,8 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
343
341
|
|
|
344
342
|
{
|
|
345
343
|
id: arrowShapeVersions.ExtractBindings,
|
|
346
|
-
scope: '
|
|
347
|
-
up: (
|
|
344
|
+
scope: 'store',
|
|
345
|
+
up: (oldStore) => {
|
|
348
346
|
type OldArrowTerminal =
|
|
349
347
|
| {
|
|
350
348
|
type: 'point'
|
|
@@ -363,10 +361,11 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
363
361
|
|
|
364
362
|
type OldArrow = TLBaseShape<'arrow', { start: OldArrowTerminal; end: OldArrowTerminal }>
|
|
365
363
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
364
|
+
const arrows = Object.values(oldStore).filter(
|
|
365
|
+
(r: any): r is OldArrow => r.typeName === 'shape' && r.type === 'arrow'
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
for (const arrow of arrows) {
|
|
370
369
|
const { start, end } = arrow.props
|
|
371
370
|
if (start.type === 'binding') {
|
|
372
371
|
const id = createBindingId()
|
|
@@ -385,10 +384,10 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
385
384
|
},
|
|
386
385
|
}
|
|
387
386
|
|
|
388
|
-
|
|
389
|
-
|
|
387
|
+
oldStore[id] = binding
|
|
388
|
+
arrow.props.start = { x: 0, y: 0 }
|
|
390
389
|
} else {
|
|
391
|
-
delete
|
|
390
|
+
delete arrow.props.start.type
|
|
392
391
|
}
|
|
393
392
|
if (end.type === 'binding') {
|
|
394
393
|
const id = createBindingId()
|
|
@@ -407,12 +406,11 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
407
406
|
},
|
|
408
407
|
}
|
|
409
408
|
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
oldStore[id] = binding
|
|
410
|
+
arrow.props.end = { x: 0, y: 0 }
|
|
412
411
|
} else {
|
|
413
|
-
delete
|
|
412
|
+
delete arrow.props.end.type
|
|
414
413
|
}
|
|
415
|
-
storage.set(arrow.id, newArrow)
|
|
416
414
|
}
|
|
417
415
|
},
|
|
418
416
|
},
|
|
@@ -447,17 +445,5 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
447
445
|
// delete props.richText
|
|
448
446
|
// },
|
|
449
447
|
}),
|
|
450
|
-
propsMigration({
|
|
451
|
-
id: arrowShapeVersions.AddRichTextAttrs,
|
|
452
|
-
up: (_props) => {
|
|
453
|
-
// noop - attrs is optional so old records are valid
|
|
454
|
-
},
|
|
455
|
-
down: (props) => {
|
|
456
|
-
// Remove attrs from richText when migrating down
|
|
457
|
-
if (props.richText && 'attrs' in props.richText) {
|
|
458
|
-
delete props.richText.attrs
|
|
459
|
-
}
|
|
460
|
-
},
|
|
461
|
-
}),
|
|
462
448
|
],
|
|
463
449
|
})
|