@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,247 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
3
|
-
import { toRichText } from '../misc/TLRichText'
|
|
4
|
-
import { GeoShapeGeoStyle, geoShapeProps, geoShapeVersions } from './TLGeoShape'
|
|
5
|
-
|
|
6
|
-
describe('TLGeoShape', () => {
|
|
7
|
-
describe('GeoShapeGeoStyle', () => {
|
|
8
|
-
it('should validate all geometric shape types', () => {
|
|
9
|
-
const validGeoTypes = [
|
|
10
|
-
'cloud',
|
|
11
|
-
'rectangle',
|
|
12
|
-
'ellipse',
|
|
13
|
-
'triangle',
|
|
14
|
-
'diamond',
|
|
15
|
-
'pentagon',
|
|
16
|
-
'hexagon',
|
|
17
|
-
'octagon',
|
|
18
|
-
'star',
|
|
19
|
-
'rhombus',
|
|
20
|
-
'rhombus-2',
|
|
21
|
-
'oval',
|
|
22
|
-
'trapezoid',
|
|
23
|
-
'arrow-right',
|
|
24
|
-
'arrow-left',
|
|
25
|
-
'arrow-up',
|
|
26
|
-
'arrow-down',
|
|
27
|
-
'x-box',
|
|
28
|
-
'check-box',
|
|
29
|
-
'heart',
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
validGeoTypes.forEach((geoType) => {
|
|
33
|
-
expect(() => GeoShapeGeoStyle.validate(geoType)).not.toThrow()
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should reject invalid geometric shape types', () => {
|
|
38
|
-
const invalidGeoTypes = ['square', 'circle', 'invalid-shape', null, undefined]
|
|
39
|
-
|
|
40
|
-
invalidGeoTypes.forEach((geoType) => {
|
|
41
|
-
expect(() => GeoShapeGeoStyle.validate(geoType)).toThrow()
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
describe('geoShapeProps validation schema', () => {
|
|
47
|
-
it('should validate numeric constraints', () => {
|
|
48
|
-
// Test nonZeroNumber validation for w, h, scale
|
|
49
|
-
expect(() => geoShapeProps.w.validate(100)).not.toThrow()
|
|
50
|
-
expect(() => geoShapeProps.h.validate(50)).not.toThrow()
|
|
51
|
-
expect(() => geoShapeProps.scale.validate(1.5)).not.toThrow()
|
|
52
|
-
|
|
53
|
-
expect(() => geoShapeProps.w.validate(0)).toThrow()
|
|
54
|
-
expect(() => geoShapeProps.h.validate(0)).toThrow()
|
|
55
|
-
expect(() => geoShapeProps.scale.validate(0)).toThrow()
|
|
56
|
-
|
|
57
|
-
// Test positiveNumber validation for growY
|
|
58
|
-
expect(() => geoShapeProps.growY.validate(0)).not.toThrow()
|
|
59
|
-
expect(() => geoShapeProps.growY.validate(10)).not.toThrow()
|
|
60
|
-
expect(() => geoShapeProps.growY.validate(-1)).toThrow()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('should validate rich text property', () => {
|
|
64
|
-
expect(() => geoShapeProps.richText.validate(toRichText('test'))).not.toThrow()
|
|
65
|
-
expect(() => geoShapeProps.richText.validate('plain string')).toThrow()
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
describe('geoShapeMigrations - AddUrlProp migration', () => {
|
|
70
|
-
const { up } = getTestMigration(geoShapeVersions.AddUrlProp)
|
|
71
|
-
|
|
72
|
-
it('should add url property with empty string default', () => {
|
|
73
|
-
const oldRecord = {
|
|
74
|
-
props: {
|
|
75
|
-
geo: 'rectangle',
|
|
76
|
-
w: 100,
|
|
77
|
-
h: 80,
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const result = up(oldRecord)
|
|
82
|
-
expect(result.props.url).toBe('')
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
describe('geoShapeMigrations - AddLabelColor migration', () => {
|
|
87
|
-
const { up } = getTestMigration(geoShapeVersions.AddLabelColor)
|
|
88
|
-
|
|
89
|
-
it('should add labelColor property with default value "black"', () => {
|
|
90
|
-
const oldRecord = {
|
|
91
|
-
props: {
|
|
92
|
-
geo: 'triangle',
|
|
93
|
-
color: 'blue',
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const result = up(oldRecord)
|
|
98
|
-
expect(result.props.labelColor).toBe('black')
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe('geoShapeMigrations - RemoveJustify migration', () => {
|
|
103
|
-
const { up } = getTestMigration(geoShapeVersions.RemoveJustify)
|
|
104
|
-
|
|
105
|
-
it('should convert justify alignment to start', () => {
|
|
106
|
-
const oldRecord = {
|
|
107
|
-
props: {
|
|
108
|
-
align: 'justify',
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const result = up(oldRecord)
|
|
113
|
-
expect(result.props.align).toBe('start')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('should preserve non-justify alignments', () => {
|
|
117
|
-
const oldRecord = {
|
|
118
|
-
props: {
|
|
119
|
-
align: 'middle',
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const result = up(oldRecord)
|
|
124
|
-
expect(result.props.align).toBe('middle')
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
describe('geoShapeMigrations - AddVerticalAlign migration', () => {
|
|
129
|
-
const { up } = getTestMigration(geoShapeVersions.AddVerticalAlign)
|
|
130
|
-
|
|
131
|
-
it('should add verticalAlign property with default value "middle"', () => {
|
|
132
|
-
const oldRecord = {
|
|
133
|
-
props: {
|
|
134
|
-
geo: 'rectangle',
|
|
135
|
-
},
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const result = up(oldRecord)
|
|
139
|
-
expect(result.props.verticalAlign).toBe('middle')
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
describe('geoShapeMigrations - MigrateLegacyAlign migration', () => {
|
|
144
|
-
const { up } = getTestMigration(geoShapeVersions.MigrateLegacyAlign)
|
|
145
|
-
|
|
146
|
-
it('should convert alignment values to legacy versions', () => {
|
|
147
|
-
const testCases = [
|
|
148
|
-
{ input: 'start', expected: 'start-legacy' },
|
|
149
|
-
{ input: 'end', expected: 'end-legacy' },
|
|
150
|
-
{ input: 'middle', expected: 'middle-legacy' },
|
|
151
|
-
{ input: 'unknown-align', expected: 'middle-legacy' },
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
testCases.forEach(({ input, expected }) => {
|
|
155
|
-
const oldRecord = {
|
|
156
|
-
props: {
|
|
157
|
-
align: input,
|
|
158
|
-
},
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const result = up(oldRecord)
|
|
162
|
-
expect(result.props.align).toBe(expected)
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
describe('geoShapeMigrations - MakeUrlsValid migration', () => {
|
|
168
|
-
const { up } = getTestMigration(geoShapeVersions.MakeUrlsValid)
|
|
169
|
-
|
|
170
|
-
it('should clear invalid URLs', () => {
|
|
171
|
-
const oldRecord = {
|
|
172
|
-
props: {
|
|
173
|
-
url: 'invalid-url',
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const result = up(oldRecord)
|
|
178
|
-
expect(result.props.url).toBe('')
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('should preserve valid URLs', () => {
|
|
182
|
-
const oldRecord = {
|
|
183
|
-
props: {
|
|
184
|
-
url: 'https://example.com',
|
|
185
|
-
},
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const result = up(oldRecord)
|
|
189
|
-
expect(result.props.url).toBe('https://example.com')
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
describe('geoShapeMigrations - AddScale migration', () => {
|
|
194
|
-
const { up, down } = getTestMigration(geoShapeVersions.AddScale)
|
|
195
|
-
|
|
196
|
-
it('should add scale property with default value 1', () => {
|
|
197
|
-
const oldRecord = {
|
|
198
|
-
props: {
|
|
199
|
-
geo: 'rectangle',
|
|
200
|
-
},
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const result = up(oldRecord)
|
|
204
|
-
expect(result.props.scale).toBe(1)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
it('should remove scale property on down migration', () => {
|
|
208
|
-
const newRecord = {
|
|
209
|
-
props: {
|
|
210
|
-
geo: 'rectangle',
|
|
211
|
-
scale: 1.5,
|
|
212
|
-
},
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const result = down(newRecord)
|
|
216
|
-
expect(result.props.scale).toBeUndefined()
|
|
217
|
-
})
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
describe('geoShapeMigrations - AddRichText migration', () => {
|
|
221
|
-
const { up } = getTestMigration(geoShapeVersions.AddRichText)
|
|
222
|
-
|
|
223
|
-
it('should convert text property to richText', () => {
|
|
224
|
-
const oldRecord = {
|
|
225
|
-
props: {
|
|
226
|
-
text: 'Simple text content',
|
|
227
|
-
},
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const result = up(oldRecord)
|
|
231
|
-
expect(result.props.richText).toBeDefined()
|
|
232
|
-
expect(result.props.text).toBeUndefined()
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('should handle empty text', () => {
|
|
236
|
-
const oldRecord = {
|
|
237
|
-
props: {
|
|
238
|
-
text: '',
|
|
239
|
-
},
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const result = up(oldRecord)
|
|
243
|
-
expect(result.props.richText).toBeDefined()
|
|
244
|
-
expect(result.props.text).toBeUndefined()
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
})
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createShapeValidator } from './TLBaseShape'
|
|
3
|
-
import { groupShapeMigrations, groupShapeProps } from './TLGroupShape'
|
|
4
|
-
|
|
5
|
-
describe('TLGroupShape', () => {
|
|
6
|
-
describe('groupShapeProps', () => {
|
|
7
|
-
it('should be an empty object', () => {
|
|
8
|
-
expect(groupShapeProps).toEqual({})
|
|
9
|
-
})
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
describe('groupShapeMigrations', () => {
|
|
13
|
-
it('should have empty migration sequence', () => {
|
|
14
|
-
expect(groupShapeMigrations.sequence).toEqual([])
|
|
15
|
-
})
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('group shape validation', () => {
|
|
19
|
-
const groupValidator = createShapeValidator('group', groupShapeProps)
|
|
20
|
-
|
|
21
|
-
it('should validate valid group shapes', () => {
|
|
22
|
-
const validGroup = {
|
|
23
|
-
id: 'shape:test',
|
|
24
|
-
typeName: 'shape',
|
|
25
|
-
type: 'group',
|
|
26
|
-
x: 0,
|
|
27
|
-
y: 0,
|
|
28
|
-
rotation: 0,
|
|
29
|
-
index: 'a1',
|
|
30
|
-
parentId: 'page:main',
|
|
31
|
-
isLocked: false,
|
|
32
|
-
opacity: 1,
|
|
33
|
-
props: {},
|
|
34
|
-
meta: {},
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
expect(() => groupValidator.validate(validGroup)).not.toThrow()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('should reject shapes with non-empty props', () => {
|
|
41
|
-
const invalidGroup = {
|
|
42
|
-
id: 'shape:invalid',
|
|
43
|
-
typeName: 'shape',
|
|
44
|
-
type: 'group',
|
|
45
|
-
x: 0,
|
|
46
|
-
y: 0,
|
|
47
|
-
rotation: 0,
|
|
48
|
-
index: 'a1',
|
|
49
|
-
parentId: 'page:main',
|
|
50
|
-
isLocked: false,
|
|
51
|
-
opacity: 1,
|
|
52
|
-
props: { invalid: 'prop' },
|
|
53
|
-
meta: {},
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
expect(() => groupValidator.validate(invalidGroup)).toThrow()
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
})
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import { T } from '@tldraw/validate'
|
|
2
|
-
import { describe, expect, it, test } from 'vitest'
|
|
3
|
-
import { getTestMigration } from '../__tests__/migrationTestUtils'
|
|
4
|
-
import {
|
|
5
|
-
highlightShapeMigrations,
|
|
6
|
-
highlightShapeProps,
|
|
7
|
-
highlightShapeVersions,
|
|
8
|
-
} from './TLHighlightShape'
|
|
9
|
-
|
|
10
|
-
describe('TLHighlightShape', () => {
|
|
11
|
-
describe('highlightShapeProps validation schema', () => {
|
|
12
|
-
it('should validate valid highlight shape properties', () => {
|
|
13
|
-
const validProps = {
|
|
14
|
-
color: 'yellow' as const,
|
|
15
|
-
size: 'l' as const,
|
|
16
|
-
segments: [
|
|
17
|
-
{
|
|
18
|
-
type: 'free' as const,
|
|
19
|
-
points: [{ x: 0, y: 0, z: 0.5 }],
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
isComplete: true,
|
|
23
|
-
isPen: false,
|
|
24
|
-
scale: 1,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const fullValidator = T.object(highlightShapeProps)
|
|
28
|
-
expect(() => fullValidator.validate(validProps)).not.toThrow()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should reject invalid color values', () => {
|
|
32
|
-
const invalidColors = ['purple', 'YELLOW', 'neon', '', null, undefined, 123, {}, []]
|
|
33
|
-
|
|
34
|
-
invalidColors.forEach((color) => {
|
|
35
|
-
expect(() => highlightShapeProps.color.validate(color)).toThrow()
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should reject invalid size values', () => {
|
|
40
|
-
const invalidSizes = ['medium', 'SM', 'large', 'xxl', '', null, undefined, 123]
|
|
41
|
-
|
|
42
|
-
invalidSizes.forEach((size) => {
|
|
43
|
-
expect(() => highlightShapeProps.size.validate(size)).toThrow()
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should validate segments array', () => {
|
|
48
|
-
const validSegmentArrays = [
|
|
49
|
-
[], // Empty array
|
|
50
|
-
[
|
|
51
|
-
{
|
|
52
|
-
type: 'free' as const,
|
|
53
|
-
points: [{ x: 0, y: 0, z: 0.5 }],
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
validSegmentArrays.forEach((segments) => {
|
|
59
|
-
expect(() => highlightShapeProps.segments.validate(segments)).not.toThrow()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const invalidSegmentArrays = [
|
|
63
|
-
'not-array',
|
|
64
|
-
null,
|
|
65
|
-
undefined,
|
|
66
|
-
[{ type: 'invalid', points: [] }], // Invalid segment type
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
invalidSegmentArrays.forEach((segments) => {
|
|
70
|
-
expect(() => highlightShapeProps.segments.validate(segments)).toThrow()
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should validate boolean properties', () => {
|
|
75
|
-
// Valid boolean values
|
|
76
|
-
expect(() => highlightShapeProps.isComplete.validate(true)).not.toThrow()
|
|
77
|
-
expect(() => highlightShapeProps.isComplete.validate(false)).not.toThrow()
|
|
78
|
-
expect(() => highlightShapeProps.isPen.validate(true)).not.toThrow()
|
|
79
|
-
expect(() => highlightShapeProps.isPen.validate(false)).not.toThrow()
|
|
80
|
-
|
|
81
|
-
// Invalid boolean values
|
|
82
|
-
const invalidBooleans = ['true', 'false', 1, 0, null, undefined, {}, []]
|
|
83
|
-
invalidBooleans.forEach((value) => {
|
|
84
|
-
expect(() => highlightShapeProps.isComplete.validate(value)).toThrow()
|
|
85
|
-
expect(() => highlightShapeProps.isPen.validate(value)).toThrow()
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('should validate scale as nonZeroNumber', () => {
|
|
90
|
-
// Valid non-zero positive numbers
|
|
91
|
-
expect(() => highlightShapeProps.scale.validate(0.1)).not.toThrow()
|
|
92
|
-
expect(() => highlightShapeProps.scale.validate(1)).not.toThrow()
|
|
93
|
-
expect(() => highlightShapeProps.scale.validate(2)).not.toThrow()
|
|
94
|
-
|
|
95
|
-
// Invalid scales (zero, negative, and non-numbers)
|
|
96
|
-
expect(() => highlightShapeProps.scale.validate(0)).toThrow()
|
|
97
|
-
expect(() => highlightShapeProps.scale.validate(-1)).toThrow()
|
|
98
|
-
expect(() => highlightShapeProps.scale.validate('not-number')).toThrow()
|
|
99
|
-
expect(() => highlightShapeProps.scale.validate(null)).toThrow()
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('should reject objects with missing properties', () => {
|
|
103
|
-
const fullValidator = T.object(highlightShapeProps)
|
|
104
|
-
|
|
105
|
-
expect(() => fullValidator.validate({})).toThrow()
|
|
106
|
-
expect(() => fullValidator.validate({ color: 'yellow' })).toThrow()
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should reject objects with extra properties', () => {
|
|
110
|
-
const fullValidator = T.object(highlightShapeProps)
|
|
111
|
-
|
|
112
|
-
const objectWithExtraProps = {
|
|
113
|
-
color: 'yellow',
|
|
114
|
-
size: 'm',
|
|
115
|
-
segments: [],
|
|
116
|
-
isComplete: true,
|
|
117
|
-
isPen: false,
|
|
118
|
-
scale: 1,
|
|
119
|
-
extraProp: 'extra',
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
expect(() => fullValidator.validate(objectWithExtraProps)).toThrow()
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
describe('highlightShapeVersions', () => {
|
|
127
|
-
it('should contain expected migration version IDs', () => {
|
|
128
|
-
expect(highlightShapeVersions).toBeDefined()
|
|
129
|
-
expect(typeof highlightShapeVersions).toBe('object')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('should have all expected migration versions', () => {
|
|
133
|
-
const expectedVersions: Array<keyof typeof highlightShapeVersions> = ['AddScale']
|
|
134
|
-
|
|
135
|
-
expectedVersions.forEach((version) => {
|
|
136
|
-
expect(highlightShapeVersions[version]).toBeDefined()
|
|
137
|
-
expect(typeof highlightShapeVersions[version]).toBe('string')
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('should have properly formatted migration IDs', () => {
|
|
142
|
-
Object.values(highlightShapeVersions).forEach((versionId) => {
|
|
143
|
-
expect(versionId).toMatch(/^com\.tldraw\.shape\.highlight\//)
|
|
144
|
-
expect(versionId).toMatch(/\/\d+$/) // Should end with /number
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
describe('highlightShapeMigrations', () => {
|
|
150
|
-
it('should be defined and have required structure', () => {
|
|
151
|
-
expect(highlightShapeMigrations).toBeDefined()
|
|
152
|
-
expect(highlightShapeMigrations.sequence).toBeDefined()
|
|
153
|
-
expect(Array.isArray(highlightShapeMigrations.sequence)).toBe(true)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('should have migrations for all version IDs', () => {
|
|
157
|
-
const migrationIds = highlightShapeMigrations.sequence
|
|
158
|
-
.filter((migration) => 'id' in migration)
|
|
159
|
-
.map((migration) => ('id' in migration ? migration.id : null))
|
|
160
|
-
.filter(Boolean)
|
|
161
|
-
|
|
162
|
-
const versionIds = Object.values(highlightShapeVersions)
|
|
163
|
-
|
|
164
|
-
versionIds.forEach((versionId) => {
|
|
165
|
-
expect(migrationIds).toContain(versionId)
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
describe('highlightShapeMigrations - AddScale migration', () => {
|
|
171
|
-
const { up, down } = getTestMigration(highlightShapeVersions.AddScale)
|
|
172
|
-
|
|
173
|
-
describe('AddScale up migration', () => {
|
|
174
|
-
it('should add scale property with default value 1', () => {
|
|
175
|
-
const oldRecord = {
|
|
176
|
-
id: 'shape:highlight1',
|
|
177
|
-
props: {
|
|
178
|
-
color: 'yellow',
|
|
179
|
-
size: 'm',
|
|
180
|
-
segments: [
|
|
181
|
-
{
|
|
182
|
-
type: 'free',
|
|
183
|
-
points: [{ x: 0, y: 0, z: 0.5 }],
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
isComplete: true,
|
|
187
|
-
isPen: false,
|
|
188
|
-
},
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const result = up(oldRecord)
|
|
192
|
-
expect(result.props.scale).toBe(1)
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('should preserve existing properties during migration', () => {
|
|
196
|
-
const oldRecord = {
|
|
197
|
-
id: 'shape:highlight1',
|
|
198
|
-
props: {
|
|
199
|
-
color: 'green',
|
|
200
|
-
size: 'l',
|
|
201
|
-
segments: [
|
|
202
|
-
{
|
|
203
|
-
type: 'straight',
|
|
204
|
-
points: [
|
|
205
|
-
{ x: 10, y: 20 },
|
|
206
|
-
{ x: 100, y: 150 },
|
|
207
|
-
],
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
isComplete: true,
|
|
211
|
-
isPen: true,
|
|
212
|
-
},
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const result = up(oldRecord)
|
|
216
|
-
expect(result.props.scale).toBe(1)
|
|
217
|
-
expect(result.props.color).toBe('green')
|
|
218
|
-
expect(result.props.size).toBe('l')
|
|
219
|
-
expect(result.props.segments).toEqual(oldRecord.props.segments)
|
|
220
|
-
expect(result.props.isComplete).toBe(true)
|
|
221
|
-
expect(result.props.isPen).toBe(true)
|
|
222
|
-
})
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
describe('AddScale down migration', () => {
|
|
226
|
-
it('should remove scale property', () => {
|
|
227
|
-
const newRecord = {
|
|
228
|
-
id: 'shape:highlight1',
|
|
229
|
-
props: {
|
|
230
|
-
color: 'yellow',
|
|
231
|
-
size: 'm',
|
|
232
|
-
segments: [
|
|
233
|
-
{
|
|
234
|
-
type: 'free',
|
|
235
|
-
points: [{ x: 0, y: 0, z: 0.5 }],
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
isComplete: true,
|
|
239
|
-
isPen: false,
|
|
240
|
-
scale: 1.5,
|
|
241
|
-
},
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const result = down(newRecord)
|
|
245
|
-
expect(result.props.scale).toBeUndefined()
|
|
246
|
-
expect(result.props.color).toBe('yellow') // Preserve other props
|
|
247
|
-
expect(result.props.isPen).toBe(false) // Preserve other props
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
it('should preserve all other properties during down migration', () => {
|
|
251
|
-
const newRecord = {
|
|
252
|
-
id: 'shape:highlight1',
|
|
253
|
-
props: {
|
|
254
|
-
color: 'red',
|
|
255
|
-
size: 'xl',
|
|
256
|
-
segments: [
|
|
257
|
-
{
|
|
258
|
-
type: 'straight',
|
|
259
|
-
points: [
|
|
260
|
-
{ x: 0, y: 0 },
|
|
261
|
-
{ x: 200, y: 0 },
|
|
262
|
-
],
|
|
263
|
-
},
|
|
264
|
-
],
|
|
265
|
-
isComplete: true,
|
|
266
|
-
isPen: false,
|
|
267
|
-
scale: 2.0,
|
|
268
|
-
},
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const result = down(newRecord)
|
|
272
|
-
expect(result.props.scale).toBeUndefined()
|
|
273
|
-
expect(result.props.color).toBe('red')
|
|
274
|
-
expect(result.props.size).toBe('xl')
|
|
275
|
-
expect(result.props.segments).toEqual(newRecord.props.segments)
|
|
276
|
-
expect(result.props.isComplete).toBe(true)
|
|
277
|
-
expect(result.props.isPen).toBe(false)
|
|
278
|
-
})
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it('should support round-trip migration (up then down)', () => {
|
|
282
|
-
const originalRecord = {
|
|
283
|
-
id: 'shape:highlight1',
|
|
284
|
-
props: {
|
|
285
|
-
color: 'green',
|
|
286
|
-
size: 'l',
|
|
287
|
-
segments: [
|
|
288
|
-
{
|
|
289
|
-
type: 'free',
|
|
290
|
-
points: [{ x: 0, y: 0, z: 0.5 }],
|
|
291
|
-
},
|
|
292
|
-
],
|
|
293
|
-
isComplete: true,
|
|
294
|
-
isPen: true,
|
|
295
|
-
},
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Apply up migration
|
|
299
|
-
const upResult = up(originalRecord)
|
|
300
|
-
expect(upResult.props.scale).toBe(1)
|
|
301
|
-
|
|
302
|
-
// Apply down migration
|
|
303
|
-
const downResult = down(upResult)
|
|
304
|
-
expect(downResult.props.scale).toBeUndefined()
|
|
305
|
-
expect(downResult.props.color).toBe('green')
|
|
306
|
-
expect(downResult.props.size).toBe('l')
|
|
307
|
-
expect(downResult.props.isComplete).toBe(true)
|
|
308
|
-
expect(downResult.props.isPen).toBe(true)
|
|
309
|
-
})
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
test('should handle all migration versions in correct order', () => {
|
|
313
|
-
const expectedOrder: Array<keyof typeof highlightShapeVersions> = ['AddScale']
|
|
314
|
-
|
|
315
|
-
const migrationIds = highlightShapeMigrations.sequence
|
|
316
|
-
.filter((migration) => 'id' in migration)
|
|
317
|
-
.map((migration) => ('id' in migration ? migration.id : ''))
|
|
318
|
-
.filter(Boolean)
|
|
319
|
-
|
|
320
|
-
expectedOrder.forEach((expectedVersion) => {
|
|
321
|
-
const versionId = highlightShapeVersions[expectedVersion]
|
|
322
|
-
expect(migrationIds).toContain(versionId)
|
|
323
|
-
})
|
|
324
|
-
})
|
|
325
|
-
})
|