@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,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
- })