@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,122 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { getTestMigration } from '../__tests__/migrationTestUtils'
3
- import { bookmarkShapeProps, bookmarkShapeVersions } from './TLBookmarkShape'
4
-
5
- describe('TLBookmarkShape', () => {
6
- describe('bookmarkShapeProps validation', () => {
7
- it('should validate width as nonZeroNumber', () => {
8
- // Valid non-zero positive numbers
9
- expect(() => bookmarkShapeProps.w.validate(0.1)).not.toThrow()
10
- expect(() => bookmarkShapeProps.w.validate(100)).not.toThrow()
11
-
12
- // Invalid: zero and negative
13
- expect(() => bookmarkShapeProps.w.validate(0)).toThrow()
14
- expect(() => bookmarkShapeProps.w.validate(-1)).toThrow()
15
- })
16
-
17
- it('should validate height as nonZeroNumber', () => {
18
- // Valid non-zero positive numbers
19
- expect(() => bookmarkShapeProps.h.validate(0.1)).not.toThrow()
20
- expect(() => bookmarkShapeProps.h.validate(100)).not.toThrow()
21
-
22
- // Invalid: zero and negative
23
- expect(() => bookmarkShapeProps.h.validate(0)).toThrow()
24
- expect(() => bookmarkShapeProps.h.validate(-1)).toThrow()
25
- })
26
-
27
- it('should validate assetId as nullable asset ID', () => {
28
- // Valid asset IDs
29
- expect(() => bookmarkShapeProps.assetId.validate(null)).not.toThrow()
30
- expect(() => bookmarkShapeProps.assetId.validate('asset:bookmark123')).not.toThrow()
31
-
32
- // Invalid asset IDs
33
- expect(() => bookmarkShapeProps.assetId.validate('shape:notasset')).toThrow()
34
- expect(() => bookmarkShapeProps.assetId.validate('bookmark123')).toThrow()
35
- expect(() => bookmarkShapeProps.assetId.validate(undefined)).toThrow()
36
- })
37
-
38
- it('should validate url as linkUrl', () => {
39
- // Valid URLs
40
- expect(() => bookmarkShapeProps.url.validate('')).not.toThrow()
41
- expect(() => bookmarkShapeProps.url.validate('https://example.com')).not.toThrow()
42
-
43
- // Invalid URLs
44
- expect(() => bookmarkShapeProps.url.validate('not-a-url')).toThrow()
45
- expect(() => bookmarkShapeProps.url.validate('javascript:alert("xss")')).toThrow()
46
- })
47
- })
48
-
49
- describe('NullAssetId migration', () => {
50
- const { up, down } = getTestMigration(bookmarkShapeVersions.NullAssetId)
51
-
52
- it('should add assetId as null when undefined', () => {
53
- const oldRecord = {
54
- props: {
55
- w: 300,
56
- h: 320,
57
- url: 'https://example.com',
58
- // assetId undefined
59
- },
60
- }
61
-
62
- const result = up(oldRecord)
63
- expect(result.props.assetId).toBeNull()
64
- })
65
-
66
- it('should preserve existing assetId when present', () => {
67
- const oldRecord = {
68
- props: {
69
- w: 300,
70
- h: 320,
71
- url: 'https://example.com',
72
- assetId: 'asset:existing123',
73
- },
74
- }
75
-
76
- const result = up(oldRecord)
77
- expect(result.props.assetId).toBe('asset:existing123')
78
- })
79
-
80
- it('should throw on retired down migration', () => {
81
- expect(() => down({})).toThrow()
82
- })
83
- })
84
-
85
- describe('MakeUrlsValid migration', () => {
86
- const { up, down } = getTestMigration(bookmarkShapeVersions.MakeUrlsValid)
87
-
88
- it('should set invalid URLs to empty string', () => {
89
- const oldRecord = {
90
- props: {
91
- w: 300,
92
- h: 320,
93
- assetId: null,
94
- url: 'not-a-valid-url',
95
- },
96
- }
97
-
98
- const result = up(oldRecord)
99
- expect(result.props.url).toBe('')
100
- })
101
-
102
- it('should preserve valid URLs', () => {
103
- const oldRecord = {
104
- props: {
105
- w: 300,
106
- h: 320,
107
- assetId: null,
108
- url: 'https://example.com',
109
- },
110
- }
111
-
112
- const result = up(oldRecord)
113
- expect(result.props.url).toBe('https://example.com')
114
- })
115
-
116
- it('should be noop for down migration', () => {
117
- const newRecord = { props: { url: 'https://example.com' } }
118
- const result = down(newRecord)
119
- expect(result).toEqual(newRecord)
120
- })
121
- })
122
- })
@@ -1,177 +0,0 @@
1
- import { T } from '@tldraw/validate'
2
- import { describe, expect, it } from 'vitest'
3
- import { getTestMigration } from '../__tests__/migrationTestUtils'
4
- import { VecModel } from '../misc/geometry-types'
5
- import { DefaultColorStyle } from '../styles/TLColorStyle'
6
- import { DefaultDashStyle } from '../styles/TLDashStyle'
7
- import { DefaultFillStyle } from '../styles/TLFillStyle'
8
- import { DefaultSizeStyle } from '../styles/TLSizeStyle'
9
- import { DrawShapeSegment, drawShapeProps, drawShapeVersions } from './TLDrawShape'
10
-
11
- describe('TLDrawShape', () => {
12
- describe('DrawShapeSegment validator', () => {
13
- it('should validate valid segment structures', () => {
14
- const validSegments = [
15
- { type: 'free', points: [{ x: 0, y: 0 }] },
16
- { type: 'straight', points: [{ x: 0, y: 0, z: 0.5 }] },
17
- { type: 'free', points: [] },
18
- ]
19
-
20
- validSegments.forEach((segment) => {
21
- expect(() => DrawShapeSegment.validate(segment)).not.toThrow()
22
- })
23
- })
24
-
25
- it('should reject invalid segment types and points', () => {
26
- const invalidSegments = [
27
- { type: 'invalid', points: [{ x: 0, y: 0 }] },
28
- { type: 'free', points: [{ x: 'invalid', y: 0 }] },
29
- { type: 'free', points: 'not-array' },
30
- {}, // Missing required fields
31
- ]
32
-
33
- invalidSegments.forEach((segment) => {
34
- expect(() => DrawShapeSegment.validate(segment)).toThrow()
35
- })
36
- })
37
- })
38
-
39
- describe('drawShapeProps validation schema', () => {
40
- it('should validate complete valid props object', () => {
41
- const fullValidator = T.object(drawShapeProps)
42
-
43
- const validProps = {
44
- color: 'red' as const,
45
- fill: 'solid' as const,
46
- dash: 'dashed' as const,
47
- size: 'l' as const,
48
- segments: [{ type: 'free' as const, points: [{ x: 0, y: 0, z: 0.5 }] as VecModel[] }],
49
- isComplete: true,
50
- isClosed: true,
51
- isPen: true,
52
- scale: 1.5,
53
- }
54
-
55
- expect(() => fullValidator.validate(validProps)).not.toThrow()
56
- })
57
-
58
- it('should reject invalid property values', () => {
59
- // Test key invalid cases that matter for business logic
60
- expect(() => drawShapeProps.scale.validate(0)).toThrow() // zero scale invalid
61
- expect(() => drawShapeProps.scale.validate(-1)).toThrow() // negative scale invalid
62
- expect(() => drawShapeProps.segments.validate('not-array')).toThrow()
63
- expect(() => drawShapeProps.segments.validate([{ type: 'invalid' }])).toThrow()
64
- })
65
-
66
- it('should use correct default style validators', () => {
67
- expect(drawShapeProps.color).toBe(DefaultColorStyle)
68
- expect(drawShapeProps.fill).toBe(DefaultFillStyle)
69
- expect(drawShapeProps.dash).toBe(DefaultDashStyle)
70
- expect(drawShapeProps.size).toBe(DefaultSizeStyle)
71
- })
72
- })
73
-
74
- describe('AddInPen migration', () => {
75
- const { up } = getTestMigration(drawShapeVersions.AddInPen)
76
-
77
- it('should detect pen from non-standard pressure values', () => {
78
- const recordWithPen = {
79
- props: {
80
- segments: [
81
- {
82
- type: 'free',
83
- points: [
84
- { x: 0, y: 0, z: 0.3 }, // Non-standard pressure
85
- { x: 10, y: 10, z: 0.7 }, // Non-standard pressure
86
- ],
87
- },
88
- ],
89
- },
90
- }
91
-
92
- const result = up(recordWithPen)
93
- expect(result.props.isPen).toBe(true)
94
- })
95
-
96
- it('should not detect pen from standard pressure values', () => {
97
- const recordWithoutPen = {
98
- props: {
99
- segments: [
100
- {
101
- type: 'free',
102
- points: [
103
- { x: 0, y: 0, z: 0 }, // Standard mouse pressure
104
- { x: 10, y: 10, z: 0.5 }, // Standard touch pressure
105
- ],
106
- },
107
- ],
108
- },
109
- }
110
-
111
- const result = up(recordWithoutPen)
112
- expect(result.props.isPen).toBe(false)
113
- })
114
-
115
- it('should handle empty segments', () => {
116
- const recordEmpty = {
117
- props: {
118
- segments: [{ type: 'free', points: [] }],
119
- },
120
- }
121
-
122
- const result = up(recordEmpty)
123
- expect(result.props.isPen).toBe(false)
124
- })
125
-
126
- it('should require both points to have non-standard pressure', () => {
127
- const recordMixed = {
128
- props: {
129
- segments: [
130
- {
131
- type: 'free',
132
- points: [
133
- { x: 0, y: 0, z: 0.3 }, // Non-standard
134
- { x: 10, y: 10, z: 0.5 }, // Standard
135
- ],
136
- },
137
- ],
138
- },
139
- }
140
-
141
- const result = up(recordMixed)
142
- expect(result.props.isPen).toBe(false)
143
- })
144
- })
145
-
146
- describe('AddScale migration', () => {
147
- const { up, down } = getTestMigration(drawShapeVersions.AddScale)
148
-
149
- it('should add scale property with default value 1', () => {
150
- const oldRecord = {
151
- props: {
152
- color: 'blue',
153
- segments: [{ type: 'free', points: [{ x: 0, y: 0 }] }],
154
- isPen: false,
155
- },
156
- }
157
-
158
- const result = up(oldRecord)
159
- expect(result.props.scale).toBe(1)
160
- })
161
-
162
- it('should remove scale property on down migration', () => {
163
- const newRecord = {
164
- props: {
165
- color: 'blue',
166
- segments: [{ type: 'free', points: [{ x: 0, y: 0 }] }],
167
- isPen: false,
168
- scale: 1.5,
169
- },
170
- }
171
-
172
- const result = down(newRecord)
173
- expect(result.props.scale).toBeUndefined()
174
- expect(result.props.color).toBe('blue') // Other props preserved
175
- })
176
- })
177
- })
@@ -1,286 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { getTestMigration } from '../__tests__/migrationTestUtils'
3
- import { embedShapeProps, embedShapeVersions } from './TLEmbedShape'
4
-
5
- describe('TLEmbedShape', () => {
6
- describe('embedShapeProps validation schema', () => {
7
- it('should validate width as nonZeroNumber', () => {
8
- const validWidths = [0.1, 0.5, 1, 10, 100, 560, 1920, 1000.5, 9999.99]
9
-
10
- validWidths.forEach((w) => {
11
- expect(() => embedShapeProps.w.validate(w)).not.toThrow()
12
- })
13
-
14
- const invalidWidths = [0, -1, -10, -0.1, 'not-number', null, undefined, {}, [], true, false]
15
-
16
- invalidWidths.forEach((w) => {
17
- expect(() => embedShapeProps.w.validate(w)).toThrow()
18
- })
19
- })
20
-
21
- it('should validate height as nonZeroNumber', () => {
22
- const validHeights = [0.1, 0.5, 1, 10, 100, 315, 1080, 1000.5, 9999.99]
23
-
24
- validHeights.forEach((h) => {
25
- expect(() => embedShapeProps.h.validate(h)).not.toThrow()
26
- })
27
-
28
- const invalidHeights = [0, -1, -10, -0.1, 'not-number', null, undefined, {}, [], true, false]
29
-
30
- invalidHeights.forEach((h) => {
31
- expect(() => embedShapeProps.h.validate(h)).toThrow()
32
- })
33
- })
34
-
35
- it('should validate url as string', () => {
36
- const validUrls = [
37
- '',
38
- 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
39
- 'https://codepen.io/team/codepen/pen/PNaGbb',
40
- 'https://codesandbox.io/s/new',
41
- 'https://vimeo.com/123456789',
42
- 'https://tldraw.com/r/room123',
43
- 'invalid-url-format', // Still valid as string
44
- 'javascript:alert("test")', // Still valid as string
45
- 'file:///local/file', // Still valid as string
46
- 'relative/path',
47
- 'text without protocol',
48
- ]
49
-
50
- validUrls.forEach((url) => {
51
- expect(() => embedShapeProps.url.validate(url)).not.toThrow()
52
- })
53
-
54
- const invalidUrls = [123, null, undefined, {}, [], true, false]
55
-
56
- invalidUrls.forEach((url) => {
57
- expect(() => embedShapeProps.url.validate(url)).toThrow()
58
- })
59
- })
60
- })
61
-
62
- describe('embedShapeMigrations - GenOriginalUrlInEmbed migration', () => {
63
- const { up, down } = getTestMigration(embedShapeVersions.GenOriginalUrlInEmbed)
64
-
65
- describe('GenOriginalUrlInEmbed up migration', () => {
66
- it('should extract original URL from tldraw embed URLs', () => {
67
- const tldrawUrls = [
68
- 'https://tldraw.com/r/room123',
69
- 'https://beta.tldraw.com/r/room456',
70
- 'http://localhost:3000/r/local-room',
71
- ]
72
-
73
- tldrawUrls.forEach((url) => {
74
- const oldRecord = {
75
- id: 'shape:embed1',
76
- props: {
77
- w: 560,
78
- h: 315,
79
- url,
80
- },
81
- }
82
-
83
- const result = up(oldRecord)
84
- expect(result.props.url).toBe(url) // Should keep the URL as-is for tldraw
85
- expect(result.props.tmpOldUrl).toBe(url)
86
- })
87
- })
88
-
89
- it('should extract original URL from YouTube embed URLs', () => {
90
- const testCases = [
91
- {
92
- embed: 'https://www.youtube.com/embed/dQw4w9WgXcQ',
93
- expected: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
94
- },
95
- {
96
- embed: 'https://youtube.com/embed/abc123',
97
- expected: 'https://www.youtube.com/watch?v=abc123',
98
- },
99
- ]
100
-
101
- testCases.forEach(({ embed, expected }) => {
102
- const oldRecord = {
103
- id: 'shape:embed1',
104
- props: {
105
- w: 560,
106
- h: 315,
107
- url: embed,
108
- },
109
- }
110
-
111
- const result = up(oldRecord)
112
- expect(result.props.url).toBe(expected)
113
- expect(result.props.tmpOldUrl).toBe(embed)
114
- })
115
- })
116
-
117
- it('should extract original URL from CodePen embed URLs', () => {
118
- const oldRecord = {
119
- id: 'shape:embed1',
120
- props: {
121
- w: 560,
122
- h: 315,
123
- url: 'https://codepen.io/user/embed/abcdef',
124
- },
125
- }
126
-
127
- const result = up(oldRecord)
128
- expect(result.props.url).toBe('https://codepen.io/user/pen/abcdef')
129
- expect(result.props.tmpOldUrl).toBe('https://codepen.io/user/embed/abcdef')
130
- })
131
-
132
- it('should handle Google Maps embed URLs (documents hostname matching limitation)', () => {
133
- const oldRecord = {
134
- id: 'shape:embed1',
135
- props: {
136
- w: 560,
137
- h: 315,
138
- url: 'https://www.google.com/maps/embed/v1/view?center=40.7128,-74.0060&zoom=10',
139
- },
140
- }
141
-
142
- const result = up(oldRecord)
143
- // NOTE: The wildcard 'google.*' doesn't match 'google.com' due to exact string matching
144
- // The URL is valid and parseable, so it goes through normal flow but doesn't match any hostname
145
- expect(result.props.url).toBe('') // originalUrl is undefined, so becomes empty string
146
- expect(result.props.tmpOldUrl).toBe(
147
- 'https://www.google.com/maps/embed/v1/view?center=40.7128,-74.0060&zoom=10'
148
- )
149
- })
150
-
151
- it('should extract original URL from Vimeo embed URLs', () => {
152
- const oldRecord = {
153
- id: 'shape:embed1',
154
- props: {
155
- w: 560,
156
- h: 315,
157
- url: 'https://player.vimeo.com/video/123456789',
158
- },
159
- }
160
-
161
- const result = up(oldRecord)
162
- expect(result.props.url).toBe('https://vimeo.com/123456789')
163
- expect(result.props.tmpOldUrl).toBe('https://player.vimeo.com/video/123456789')
164
- })
165
- })
166
-
167
- describe('GenOriginalUrlInEmbed down migration', () => {
168
- it('should be retired (no down migration)', () => {
169
- expect(() => {
170
- down({})
171
- }).toThrow('Migration com.tldraw.shape.embed/1 does not have a down function')
172
- })
173
- })
174
- })
175
-
176
- describe('embedShapeMigrations - RemoveDoesResize migration', () => {
177
- const { up, down } = getTestMigration(embedShapeVersions.RemoveDoesResize)
178
-
179
- describe('RemoveDoesResize up migration', () => {
180
- it('should remove doesResize property', () => {
181
- const oldRecord = {
182
- id: 'shape:embed1',
183
- props: {
184
- w: 560,
185
- h: 315,
186
- url: 'https://example.com',
187
- doesResize: true,
188
- },
189
- }
190
-
191
- const result = up(oldRecord)
192
- expect(result.props.doesResize).toBeUndefined()
193
- })
194
- })
195
-
196
- describe('RemoveDoesResize down migration', () => {
197
- it('should be retired (no down migration)', () => {
198
- expect(() => {
199
- down({})
200
- }).toThrow('Migration com.tldraw.shape.embed/2 does not have a down function')
201
- })
202
- })
203
- })
204
-
205
- describe('embedShapeMigrations - RemoveTmpOldUrl migration', () => {
206
- const { up, down } = getTestMigration(embedShapeVersions.RemoveTmpOldUrl)
207
-
208
- describe('RemoveTmpOldUrl up migration', () => {
209
- it('should remove tmpOldUrl property', () => {
210
- const oldRecord = {
211
- id: 'shape:embed1',
212
- props: {
213
- w: 560,
214
- h: 315,
215
- url: 'https://example.com',
216
- tmpOldUrl: 'https://old-url.com',
217
- },
218
- }
219
-
220
- const result = up(oldRecord)
221
- expect(result.props.tmpOldUrl).toBeUndefined()
222
- })
223
- })
224
-
225
- describe('RemoveTmpOldUrl down migration', () => {
226
- it('should be retired (no down migration)', () => {
227
- expect(() => {
228
- down({})
229
- }).toThrow('Migration com.tldraw.shape.embed/3 does not have a down function')
230
- })
231
- })
232
- })
233
-
234
- describe('embedShapeMigrations - RemovePermissionOverrides migration', () => {
235
- const { up, down } = getTestMigration(embedShapeVersions.RemovePermissionOverrides)
236
-
237
- describe('RemovePermissionOverrides up migration', () => {
238
- it('should remove overridePermissions property', () => {
239
- const oldRecord = {
240
- id: 'shape:embed1',
241
- props: {
242
- w: 560,
243
- h: 315,
244
- url: 'https://example.com',
245
- overridePermissions: { allowScripts: true },
246
- },
247
- }
248
-
249
- const result = up(oldRecord)
250
- expect(result.props.overridePermissions).toBeUndefined()
251
- })
252
- })
253
-
254
- describe('RemovePermissionOverrides down migration', () => {
255
- it('should be retired (no down migration)', () => {
256
- expect(() => {
257
- down({})
258
- }).toThrow('Migration com.tldraw.shape.embed/4 does not have a down function')
259
- })
260
- })
261
- })
262
-
263
- describe('edge cases and error handling', () => {
264
- it('should handle zero dimension validation correctly', () => {
265
- // Zero should be invalid for width and height (nonZeroNumber)
266
- expect(() => embedShapeProps.w.validate(0)).toThrow()
267
- expect(() => embedShapeProps.h.validate(0)).toThrow()
268
-
269
- // Negative numbers should also be invalid
270
- expect(() => embedShapeProps.w.validate(-1)).toThrow()
271
- expect(() => embedShapeProps.h.validate(-10.5)).toThrow()
272
- })
273
-
274
- it('should handle migration errors when props is null', () => {
275
- const malformedRecord = {
276
- id: 'shape:malformed',
277
- props: null,
278
- }
279
-
280
- expect(() => {
281
- const migration = getTestMigration(embedShapeVersions.GenOriginalUrlInEmbed)
282
- migration.up(malformedRecord)
283
- }).toThrow('Cannot set properties of null')
284
- })
285
- })
286
- })
@@ -1,71 +0,0 @@
1
- import { T } from '@tldraw/validate'
2
- import { describe, expect, it } from 'vitest'
3
- import { getTestMigration } from '../__tests__/migrationTestUtils'
4
- import { frameShapeProps, frameShapeVersions } from './TLFrameShape'
5
-
6
- describe('TLFrameShape', () => {
7
- describe('frameShapeProps validation', () => {
8
- it('should validate valid frame props', () => {
9
- const validProps = {
10
- w: 400,
11
- h: 300,
12
- name: 'Test Frame',
13
- color: 'blue' as const,
14
- }
15
-
16
- const validator = T.object(frameShapeProps)
17
- expect(() => validator.validate(validProps)).not.toThrow()
18
- })
19
-
20
- it('should reject invalid dimensions', () => {
21
- // Zero and negative values should be rejected
22
- expect(() => frameShapeProps.w.validate(0)).toThrow()
23
- expect(() => frameShapeProps.h.validate(-1)).toThrow()
24
- })
25
-
26
- it('should reject invalid colors', () => {
27
- // Invalid color values
28
- expect(() => frameShapeProps.color.validate('invalid-color')).toThrow()
29
- expect(() => frameShapeProps.color.validate('')).toThrow()
30
- })
31
- })
32
-
33
- describe('AddColorProp migration', () => {
34
- const { up, down } = getTestMigration(frameShapeVersions.AddColorProp)
35
-
36
- it('should add color property with default value "black"', () => {
37
- const oldRecord = {
38
- id: 'shape:frame1',
39
- props: {
40
- w: 400,
41
- h: 300,
42
- name: 'Test Frame',
43
- },
44
- }
45
-
46
- const result = up(oldRecord)
47
- expect(result.props.color).toBe('black')
48
- expect(result.props.w).toBe(400)
49
- expect(result.props.h).toBe(300)
50
- expect(result.props.name).toBe('Test Frame')
51
- })
52
-
53
- it('should remove color property on down migration', () => {
54
- const newRecord = {
55
- id: 'shape:frame1',
56
- props: {
57
- w: 400,
58
- h: 300,
59
- name: 'Test Frame',
60
- color: 'blue',
61
- },
62
- }
63
-
64
- const result = down(newRecord)
65
- expect(result.props.color).toBeUndefined()
66
- expect(result.props.w).toBe(400)
67
- expect(result.props.h).toBe(300)
68
- expect(result.props.name).toBe('Test Frame')
69
- })
70
- })
71
- })