@tldraw/tlschema 4.1.0-canary.bace23a29058 → 4.1.0-canary.bf8b596d2b31
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/TLStore.js +3 -10
- package/dist-cjs/TLStore.js.map +2 -2
- package/dist-cjs/assets/TLBaseAsset.js.map +2 -2
- package/dist-cjs/assets/TLBookmarkAsset.js.map +2 -2
- package/dist-cjs/assets/TLImageAsset.js.map +2 -2
- package/dist-cjs/assets/TLVideoAsset.js.map +2 -2
- package/dist-cjs/bindings/TLArrowBinding.js.map +2 -2
- package/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
- package/dist-cjs/createPresenceStateDerivation.js.map +2 -2
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +4416 -223
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLColor.js.map +2 -2
- package/dist-cjs/misc/TLCursor.js.map +2 -2
- package/dist-cjs/misc/TLHandle.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/misc/TLScribble.js.map +2 -2
- package/dist-cjs/misc/geometry-types.js.map +2 -2
- package/dist-cjs/misc/id-validator.js.map +2 -2
- package/dist-cjs/records/TLAsset.js.map +2 -2
- package/dist-cjs/records/TLBinding.js.map +2 -2
- package/dist-cjs/records/TLCamera.js.map +2 -2
- package/dist-cjs/records/TLDocument.js.map +2 -2
- package/dist-cjs/records/TLInstance.js.map +2 -2
- package/dist-cjs/records/TLPage.js.map +2 -2
- package/dist-cjs/records/TLPageState.js.map +2 -2
- package/dist-cjs/records/TLPointer.js.map +2 -2
- package/dist-cjs/records/TLPresence.js.map +2 -2
- package/dist-cjs/records/TLRecord.js.map +1 -1
- package/dist-cjs/records/TLShape.js.map +2 -2
- package/dist-cjs/recordsWithProps.js.map +2 -2
- package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLBookmarkShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLFrameShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLGroupShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLImageShape.js.map +2 -2
- package/dist-cjs/shapes/TLLineShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/shapes/TLVideoShape.js.map +2 -2
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-cjs/styles/TLColorStyle.js.map +2 -2
- package/dist-cjs/styles/TLDashStyle.js.map +2 -2
- package/dist-cjs/styles/TLFillStyle.js.map +2 -2
- package/dist-cjs/styles/TLFontStyle.js.map +2 -2
- package/dist-cjs/styles/TLHorizontalAlignStyle.js.map +2 -2
- package/dist-cjs/styles/TLSizeStyle.js.map +2 -2
- package/dist-cjs/styles/TLTextAlignStyle.js.map +2 -2
- package/dist-cjs/styles/TLVerticalAlignStyle.js.map +2 -2
- package/dist-cjs/translations/translations.js +1 -1
- package/dist-cjs/translations/translations.js.map +2 -2
- package/dist-cjs/util-types.js.map +1 -1
- package/dist-esm/TLStore.mjs +3 -10
- package/dist-esm/TLStore.mjs.map +2 -2
- package/dist-esm/assets/TLBaseAsset.mjs.map +2 -2
- package/dist-esm/assets/TLBookmarkAsset.mjs.map +2 -2
- package/dist-esm/assets/TLImageAsset.mjs.map +2 -2
- package/dist-esm/assets/TLVideoAsset.mjs.map +2 -2
- package/dist-esm/bindings/TLArrowBinding.mjs.map +2 -2
- package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
- package/dist-esm/createPresenceStateDerivation.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +4416 -223
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLColor.mjs.map +2 -2
- package/dist-esm/misc/TLCursor.mjs.map +2 -2
- package/dist-esm/misc/TLHandle.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/misc/TLScribble.mjs.map +2 -2
- package/dist-esm/misc/geometry-types.mjs.map +2 -2
- package/dist-esm/misc/id-validator.mjs.map +2 -2
- package/dist-esm/records/TLAsset.mjs.map +2 -2
- package/dist-esm/records/TLBinding.mjs.map +2 -2
- package/dist-esm/records/TLCamera.mjs.map +2 -2
- package/dist-esm/records/TLDocument.mjs.map +2 -2
- package/dist-esm/records/TLInstance.mjs.map +2 -2
- package/dist-esm/records/TLPage.mjs.map +2 -2
- package/dist-esm/records/TLPageState.mjs.map +2 -2
- package/dist-esm/records/TLPointer.mjs.map +2 -2
- package/dist-esm/records/TLPresence.mjs.map +2 -2
- package/dist-esm/records/TLShape.mjs.map +2 -2
- package/dist-esm/recordsWithProps.mjs.map +2 -2
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBookmarkShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLFrameShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGroupShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLImageShape.mjs.map +2 -2
- package/dist-esm/shapes/TLLineShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/shapes/TLVideoShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/dist-esm/styles/TLColorStyle.mjs.map +2 -2
- package/dist-esm/styles/TLDashStyle.mjs.map +2 -2
- package/dist-esm/styles/TLFillStyle.mjs.map +2 -2
- package/dist-esm/styles/TLFontStyle.mjs.map +2 -2
- package/dist-esm/styles/TLHorizontalAlignStyle.mjs.map +2 -2
- package/dist-esm/styles/TLSizeStyle.mjs.map +2 -2
- package/dist-esm/styles/TLTextAlignStyle.mjs.map +2 -2
- package/dist-esm/styles/TLVerticalAlignStyle.mjs.map +2 -2
- package/dist-esm/translations/translations.mjs +1 -1
- package/dist-esm/translations/translations.mjs.map +2 -2
- package/package.json +5 -5
- package/src/TLStore.test.ts +644 -0
- package/src/TLStore.ts +205 -20
- package/src/assets/TLBaseAsset.ts +90 -7
- package/src/assets/TLBookmarkAsset.test.ts +96 -0
- package/src/assets/TLBookmarkAsset.ts +52 -2
- package/src/assets/TLImageAsset.test.ts +213 -0
- package/src/assets/TLImageAsset.ts +60 -2
- package/src/assets/TLVideoAsset.test.ts +105 -0
- package/src/assets/TLVideoAsset.ts +93 -4
- package/src/bindings/TLArrowBinding.test.ts +55 -0
- package/src/bindings/TLArrowBinding.ts +132 -10
- package/src/bindings/TLBaseBinding.ts +140 -3
- package/src/createPresenceStateDerivation.test.ts +158 -0
- package/src/createPresenceStateDerivation.ts +71 -2
- package/src/createTLSchema.test.ts +181 -0
- package/src/createTLSchema.ts +164 -7
- package/src/index.ts +32 -0
- package/src/misc/TLColor.ts +50 -6
- package/src/misc/TLCursor.ts +110 -8
- package/src/misc/TLHandle.ts +86 -6
- package/src/misc/TLOpacity.ts +51 -2
- package/src/misc/TLRichText.ts +56 -3
- package/src/misc/TLScribble.ts +105 -5
- package/src/misc/geometry-types.ts +30 -2
- package/src/misc/id-validator.test.ts +50 -0
- package/src/misc/id-validator.ts +20 -1
- package/src/records/TLAsset.test.ts +234 -0
- package/src/records/TLAsset.ts +165 -8
- package/src/records/TLBinding.test.ts +22 -0
- package/src/records/TLBinding.ts +277 -11
- package/src/records/TLCamera.test.ts +19 -0
- package/src/records/TLCamera.ts +118 -7
- package/src/records/TLDocument.test.ts +35 -0
- package/src/records/TLDocument.ts +148 -8
- package/src/records/TLInstance.test.ts +201 -0
- package/src/records/TLInstance.ts +117 -9
- package/src/records/TLPage.test.ts +110 -0
- package/src/records/TLPage.ts +106 -8
- package/src/records/TLPageState.test.ts +228 -0
- package/src/records/TLPageState.ts +88 -7
- package/src/records/TLPointer.test.ts +63 -0
- package/src/records/TLPointer.ts +105 -7
- package/src/records/TLPresence.test.ts +190 -0
- package/src/records/TLPresence.ts +99 -5
- package/src/records/TLRecord.test.ts +70 -0
- package/src/records/TLRecord.ts +43 -1
- package/src/records/TLShape.test.ts +232 -0
- package/src/records/TLShape.ts +289 -12
- package/src/recordsWithProps.test.ts +188 -0
- package/src/recordsWithProps.ts +131 -2
- package/src/shapes/ShapeWithCrop.test.ts +18 -0
- package/src/shapes/ShapeWithCrop.ts +64 -2
- package/src/shapes/TLArrowShape.test.ts +505 -0
- package/src/shapes/TLArrowShape.ts +188 -10
- package/src/shapes/TLBaseShape.test.ts +142 -0
- package/src/shapes/TLBaseShape.ts +103 -4
- package/src/shapes/TLBookmarkShape.test.ts +122 -0
- package/src/shapes/TLBookmarkShape.ts +58 -4
- package/src/shapes/TLDrawShape.test.ts +177 -0
- package/src/shapes/TLDrawShape.ts +97 -6
- package/src/shapes/TLEmbedShape.test.ts +286 -0
- package/src/shapes/TLEmbedShape.ts +57 -4
- package/src/shapes/TLFrameShape.test.ts +71 -0
- package/src/shapes/TLFrameShape.ts +59 -4
- package/src/shapes/TLGeoShape.test.ts +247 -0
- package/src/shapes/TLGeoShape.ts +103 -7
- package/src/shapes/TLGroupShape.test.ts +59 -0
- package/src/shapes/TLGroupShape.ts +52 -4
- package/src/shapes/TLHighlightShape.test.ts +325 -0
- package/src/shapes/TLHighlightShape.ts +79 -4
- package/src/shapes/TLImageShape.test.ts +534 -0
- package/src/shapes/TLImageShape.ts +105 -5
- package/src/shapes/TLLineShape.test.ts +269 -0
- package/src/shapes/TLLineShape.ts +128 -8
- package/src/shapes/TLNoteShape.test.ts +1568 -0
- package/src/shapes/TLNoteShape.ts +97 -4
- package/src/shapes/TLTextShape.test.ts +407 -0
- package/src/shapes/TLTextShape.ts +94 -4
- package/src/shapes/TLVideoShape.test.ts +112 -0
- package/src/shapes/TLVideoShape.ts +99 -4
- package/src/store-migrations.test.ts +88 -0
- package/src/store-migrations.ts +47 -1
- package/src/styles/TLColorStyle.test.ts +439 -0
- package/src/styles/TLColorStyle.ts +228 -10
- package/src/styles/TLDashStyle.ts +54 -2
- package/src/styles/TLFillStyle.ts +54 -2
- package/src/styles/TLFontStyle.ts +72 -3
- package/src/styles/TLHorizontalAlignStyle.ts +55 -2
- package/src/styles/TLSizeStyle.ts +54 -2
- package/src/styles/TLTextAlignStyle.ts +52 -2
- package/src/styles/TLVerticalAlignStyle.ts +52 -2
- package/src/translations/translations.test.ts +378 -35
- package/src/translations/translations.ts +157 -10
- package/src/util-types.ts +51 -1
|
@@ -5,18 +5,62 @@ import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from
|
|
|
5
5
|
import { RecordProps } from '../recordsWithProps'
|
|
6
6
|
import { TLBaseShape } from './TLBaseShape'
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Properties for the bookmark shape, which displays website bookmarks as interactive cards.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
9
13
|
export interface TLBookmarkShapeProps {
|
|
14
|
+
/** Width of the bookmark shape in pixels */
|
|
10
15
|
w: number
|
|
16
|
+
/** Height of the bookmark shape in pixels */
|
|
11
17
|
h: number
|
|
18
|
+
/** Asset ID for the bookmark's preview image, or null if no image is available */
|
|
12
19
|
assetId: TLAssetId | null
|
|
20
|
+
/** The URL that this bookmark points to */
|
|
13
21
|
url: string
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* A bookmark shape represents a website link with optional preview content.
|
|
26
|
+
* Bookmark shapes display as cards showing the page title, description, and preview image.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const bookmarkShape: TLBookmarkShape = {
|
|
32
|
+
* id: createShapeId(),
|
|
33
|
+
* typeName: 'shape',
|
|
34
|
+
* type: 'bookmark',
|
|
35
|
+
* x: 100,
|
|
36
|
+
* y: 100,
|
|
37
|
+
* rotation: 0,
|
|
38
|
+
* index: 'a1',
|
|
39
|
+
* parentId: 'page:page1',
|
|
40
|
+
* isLocked: false,
|
|
41
|
+
* opacity: 1,
|
|
42
|
+
* props: {
|
|
43
|
+
* w: 300,
|
|
44
|
+
* h: 320,
|
|
45
|
+
* assetId: 'asset:bookmark123',
|
|
46
|
+
* url: 'https://www.example.com'
|
|
47
|
+
* },
|
|
48
|
+
* meta: {}
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
17
52
|
export type TLBookmarkShape = TLBaseShape<'bookmark', TLBookmarkShapeProps>
|
|
18
53
|
|
|
19
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Validation schema for bookmark shape properties.
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Validates bookmark shape properties
|
|
61
|
+
* const isValid = bookmarkShapeProps.url.isValid('https://example.com')
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
20
64
|
export const bookmarkShapeProps: RecordProps<TLBookmarkShape> = {
|
|
21
65
|
w: T.nonZeroNumber,
|
|
22
66
|
h: T.nonZeroNumber,
|
|
@@ -29,9 +73,19 @@ const Versions = createShapePropsMigrationIds('bookmark', {
|
|
|
29
73
|
MakeUrlsValid: 2,
|
|
30
74
|
})
|
|
31
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Version identifiers for bookmark shape migrations.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
32
81
|
export { Versions as bookmarkShapeVersions }
|
|
33
82
|
|
|
34
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Migration sequence for bookmark shape properties across different schema versions.
|
|
85
|
+
* Handles backwards compatibility when bookmark shape structure changes.
|
|
86
|
+
*
|
|
87
|
+
* @public
|
|
88
|
+
*/
|
|
35
89
|
export const bookmarkShapeMigrations = createShapePropsMigrationSequence({
|
|
36
90
|
sequence: [
|
|
37
91
|
{
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
})
|
|
@@ -8,35 +8,116 @@ import { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle'
|
|
|
8
8
|
import { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'
|
|
9
9
|
import { TLBaseShape } from './TLBaseShape'
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* A segment of a draw shape representing either freehand drawing or straight line segments.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
12
16
|
export interface TLDrawShapeSegment {
|
|
17
|
+
/** Type of drawing segment - 'free' for freehand curves, 'straight' for line segments */
|
|
13
18
|
type: 'free' | 'straight'
|
|
19
|
+
/** Array of points defining the segment path with x, y coordinates and pressure (z) */
|
|
14
20
|
points: VecModel[]
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Validator for draw shape segments ensuring proper structure and data types.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const segment: TLDrawShapeSegment = {
|
|
30
|
+
* type: 'free',
|
|
31
|
+
* points: [{ x: 0, y: 0, z: 0.5 }, { x: 10, y: 10, z: 0.7 }]
|
|
32
|
+
* }
|
|
33
|
+
* const isValid = DrawShapeSegment.isValid(segment)
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
18
36
|
export const DrawShapeSegment: T.ObjectValidator<TLDrawShapeSegment> = T.object({
|
|
19
37
|
type: T.literalEnum('free', 'straight'),
|
|
20
38
|
points: T.arrayOf(vecModelValidator),
|
|
21
39
|
})
|
|
22
40
|
|
|
23
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Properties for the draw shape, which represents freehand drawing and sketching.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
24
46
|
export interface TLDrawShapeProps {
|
|
47
|
+
/** Color style for the drawing stroke */
|
|
25
48
|
color: TLDefaultColorStyle
|
|
49
|
+
/** Fill style for closed drawing shapes */
|
|
26
50
|
fill: TLDefaultFillStyle
|
|
51
|
+
/** Dash pattern style for the stroke */
|
|
27
52
|
dash: TLDefaultDashStyle
|
|
53
|
+
/** Size/thickness of the drawing stroke */
|
|
28
54
|
size: TLDefaultSizeStyle
|
|
55
|
+
/** Array of segments that make up the complete drawing path */
|
|
29
56
|
segments: TLDrawShapeSegment[]
|
|
57
|
+
/** Whether the drawing is complete (user finished drawing) */
|
|
30
58
|
isComplete: boolean
|
|
59
|
+
/** Whether the drawing path forms a closed shape */
|
|
31
60
|
isClosed: boolean
|
|
61
|
+
/** Whether this drawing was created with a pen/stylus device */
|
|
32
62
|
isPen: boolean
|
|
63
|
+
/** Scale factor applied to the drawing */
|
|
33
64
|
scale: number
|
|
34
65
|
}
|
|
35
66
|
|
|
36
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* A draw shape represents freehand drawing, sketching, and pen input on the canvas.
|
|
69
|
+
* Draw shapes are composed of segments that can be either smooth curves or straight lines.
|
|
70
|
+
*
|
|
71
|
+
* @public
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const drawShape: TLDrawShape = {
|
|
75
|
+
* id: createShapeId(),
|
|
76
|
+
* typeName: 'shape',
|
|
77
|
+
* type: 'draw',
|
|
78
|
+
* x: 50,
|
|
79
|
+
* y: 50,
|
|
80
|
+
* rotation: 0,
|
|
81
|
+
* index: 'a1',
|
|
82
|
+
* parentId: 'page:page1',
|
|
83
|
+
* isLocked: false,
|
|
84
|
+
* opacity: 1,
|
|
85
|
+
* props: {
|
|
86
|
+
* color: 'black',
|
|
87
|
+
* fill: 'none',
|
|
88
|
+
* dash: 'solid',
|
|
89
|
+
* size: 'm',
|
|
90
|
+
* segments: [{
|
|
91
|
+
* type: 'free',
|
|
92
|
+
* points: [{ x: 0, y: 0, z: 0.5 }, { x: 20, y: 15, z: 0.6 }]
|
|
93
|
+
* }],
|
|
94
|
+
* isComplete: true,
|
|
95
|
+
* isClosed: false,
|
|
96
|
+
* isPen: false,
|
|
97
|
+
* scale: 1
|
|
98
|
+
* },
|
|
99
|
+
* meta: {}
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
37
103
|
export type TLDrawShape = TLBaseShape<'draw', TLDrawShapeProps>
|
|
38
104
|
|
|
39
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Validation schema for draw shape properties.
|
|
107
|
+
*
|
|
108
|
+
* @public
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* // Validate draw shape properties
|
|
112
|
+
* const props = {
|
|
113
|
+
* color: 'red',
|
|
114
|
+
* fill: 'solid',
|
|
115
|
+
* segments: [{ type: 'free', points: [] }],
|
|
116
|
+
* isComplete: true
|
|
117
|
+
* }
|
|
118
|
+
* const isValid = drawShapeProps.color.isValid(props.color)
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
40
121
|
export const drawShapeProps: RecordProps<TLDrawShape> = {
|
|
41
122
|
color: DefaultColorStyle,
|
|
42
123
|
fill: DefaultFillStyle,
|
|
@@ -54,9 +135,19 @@ const Versions = createShapePropsMigrationIds('draw', {
|
|
|
54
135
|
AddScale: 2,
|
|
55
136
|
})
|
|
56
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Version identifiers for draw shape migrations.
|
|
140
|
+
*
|
|
141
|
+
* @public
|
|
142
|
+
*/
|
|
57
143
|
export { Versions as drawShapeVersions }
|
|
58
144
|
|
|
59
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Migration sequence for draw shape properties across different schema versions.
|
|
147
|
+
* Handles adding pen detection and scale properties to existing draw shapes.
|
|
148
|
+
*
|
|
149
|
+
* @public
|
|
150
|
+
*/
|
|
60
151
|
export const drawShapeMigrations = createShapePropsMigrationSequence({
|
|
61
152
|
sequence: [
|
|
62
153
|
{
|
|
@@ -0,0 +1,286 @@
|
|
|
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
|
+
})
|