Tldraw/packages/tlschema/src/migrations.test.ts

1577 wiersze
36 KiB
TypeScript

import { createRecordType } from '@tldraw/store'
import { getTestMigration, testSchema } from './__tests__/migrationTestUtils'
import { bookmarkAssetVersions } from './assets/TLBookmarkAsset'
import { imageAssetVersions } from './assets/TLImageAsset'
import { videoAssetVersions } from './assets/TLVideoAsset'
import { assetVersions } from './records/TLAsset'
import { cameraVersions } from './records/TLCamera'
import { documentVersions } from './records/TLDocument'
import { instanceVersions } from './records/TLInstance'
import { pageVersions } from './records/TLPage'
import { instancePageStateVersions } from './records/TLPageState'
import { pointerVersions } from './records/TLPointer'
import { instancePresenceVersions } from './records/TLPresence'
import { TLShape, rootShapeVersions } from './records/TLShape'
import { arrowShapeVersions } from './shapes/TLArrowShape'
import { bookmarkShapeVersions } from './shapes/TLBookmarkShape'
import { drawShapeVersions } from './shapes/TLDrawShape'
import { embedShapeVersions } from './shapes/TLEmbedShape'
import { geoShapeVersions } from './shapes/TLGeoShape'
import { imageShapeVersions } from './shapes/TLImageShape'
import { lineShapeVersions } from './shapes/TLLineShape'
import { noteShapeVersions } from './shapes/TLNoteShape'
import { textShapeVersions } from './shapes/TLTextShape'
import { videoShapeVersions } from './shapes/TLVideoShape'
import { storeVersions } from './store-migrations'
/* --- PUT YOUR MIGRATIONS TESTS BELOW HERE --- */
describe('TLVideoAsset AddIsAnimated', () => {
const oldAsset = {
id: '1',
type: 'video',
props: {
src: 'https://www.youtube.com/watch?v=1',
name: 'video',
width: 100,
height: 100,
mimeType: 'video/mp4',
},
}
const newAsset = {
id: '1',
type: 'video',
props: {
src: 'https://www.youtube.com/watch?v=1',
name: 'video',
width: 100,
height: 100,
mimeType: 'video/mp4',
isAnimated: false,
},
}
const { up, down } = getTestMigration(videoAssetVersions.AddIsAnimated)
test('up works as expected', () => {
expect(up(oldAsset)).toEqual(newAsset)
})
test('down works as expected', () => {
expect(down(newAsset)).toEqual(oldAsset)
})
})
describe('TLImageAsset AddIsAnimated', () => {
const oldAsset = {
id: '1',
type: 'image',
props: {
src: 'https://www.youtube.com/watch?v=1',
name: 'image',
width: 100,
height: 100,
mimeType: 'image/gif',
},
}
const newAsset = {
id: '1',
type: 'image',
props: {
src: 'https://www.youtube.com/watch?v=1',
name: 'image',
width: 100,
height: 100,
mimeType: 'image/gif',
isAnimated: false,
},
}
const { up, down } = getTestMigration(imageAssetVersions.AddIsAnimated)
test('up works as expected', () => {
expect(up(oldAsset)).toEqual(newAsset)
})
test('down works as expected', () => {
expect(down(newAsset)).toEqual(oldAsset)
})
})
const ShapeRecord = createRecordType('shape', {
validator: { validate: (record) => record as TLShape },
scope: 'document',
})
describe('Store removing Icon and Code shapes', () => {
const { up } = getTestMigration(storeVersions.RemoveCodeAndIconShapeTypes)
test('up works as expected', () => {
const snapshot = Object.fromEntries(
[
ShapeRecord.create({
type: 'icon',
parentId: 'page:any',
index: 'a0',
props: { name: 'a' },
} as any),
ShapeRecord.create({
type: 'icon',
parentId: 'page:any',
index: 'a0',
props: { name: 'b' },
} as any),
ShapeRecord.create({
type: 'code',
parentId: 'page:any',
index: 'a0',
props: { name: 'c' },
} as any),
ShapeRecord.create({
type: 'code',
parentId: 'page:any',
index: 'a0',
props: { name: 'd' },
} as any),
ShapeRecord.create({
type: 'geo',
parentId: 'page:any',
index: 'a0',
props: { geo: 'rectangle', w: 1, h: 1, growY: 1, text: '' },
} as any),
].map((shape) => [shape.id, shape])
)
const fixed = up(snapshot)
expect(Object.entries(fixed)).toHaveLength(1)
})
})
describe('Adding export background', () => {
const { up } = getTestMigration(instanceVersions.AddTransparentExportBgs)
test('up works as expected', () => {
const before = {}
const after = { exportBackground: true }
expect(up(before)).toEqual(after)
})
})
describe('Removing dialogs from instance', () => {
const { up } = getTestMigration(instanceVersions.RemoveDialog)
test('up works as expected', () => {
const before = { dialog: null }
const after = {}
expect(up(before)).toEqual(after)
})
})
describe('Adding url props', () => {
for (const [name, { up }] of [
['video shape', getTestMigration(videoShapeVersions.AddUrlProp)],
['note shape', getTestMigration(noteShapeVersions.AddUrlProp)],
['geo shape', getTestMigration(geoShapeVersions.AddUrlProp)],
['image shape', getTestMigration(imageShapeVersions.AddUrlProp)],
] as const) {
test(`${name}: up works as expected`, () => {
expect(up({ props: {} })).toEqual({ props: { url: '' } })
})
}
})
describe('Bookmark null asset id', () => {
const { up } = getTestMigration(bookmarkShapeVersions.NullAssetId)
test('up works as expected', () => {
expect(up({ props: {} })).toEqual({ props: { assetId: null } })
})
})
describe('Renaming asset props', () => {
for (const [name, { up, down }] of [
['image shape', getTestMigration(imageAssetVersions.RenameWidthHeight)],
['video shape', getTestMigration(videoAssetVersions.RenameWidthHeight)],
] as const) {
test(`${name}: up works as expected`, () => {
const before = { props: { width: 100, height: 100 } }
const after = { props: { w: 100, h: 100 } }
expect(up(before)).toEqual(after)
})
test(`${name}: down works as expected`, () => {
const before = { props: { w: 100, h: 100 } }
const after = { props: { width: 100, height: 100 } }
expect(down(before)).toEqual(after)
})
}
})
describe('Adding instance.isToolLocked', () => {
const { up } = getTestMigration(instanceVersions.AddToolLockMode)
test('up works as expected', () => {
expect(up({})).toMatchObject({ isToolLocked: false })
expect(up({ isToolLocked: true })).toMatchObject({ isToolLocked: false })
})
})
describe('Cleaning up junk data in instance.propsForNextShape', () => {
const { up } = getTestMigration(instanceVersions.RemoveExtraPropsForNextShape)
test('up works as expected', () => {
expect(up({ propsForNextShape: { color: 'red', unknown: 'gone' } })).toEqual({
propsForNextShape: {
color: 'red',
},
})
})
})
describe('Generating original URL from embed URL in GenOriginalUrlInEmbed', () => {
const { up } = getTestMigration(embedShapeVersions.GenOriginalUrlInEmbed)
test('up works as expected', () => {
expect(up({ props: { url: 'https://codepen.io/Rplus/embed/PWZYRM' } })).toEqual({
props: {
url: 'https://codepen.io/Rplus/pen/PWZYRM',
tmpOldUrl: 'https://codepen.io/Rplus/embed/PWZYRM',
},
})
})
test('invalid up works as expected', () => {
expect(up({ props: { url: 'https://example.com' } })).toEqual({
props: {
url: '',
tmpOldUrl: 'https://example.com',
},
})
})
})
describe('Adding isPen prop', () => {
const { up } = getTestMigration(drawShapeVersions.AddInPen)
test('up works as expected with a shape that is not a pen shape', () => {
expect(
up({
props: {
segments: [
{
type: 'free',
points: [
{ x: 0, y: 0, z: 0.5 },
{ x: 1, y: 1, z: 0.5 },
],
},
],
},
})
).toEqual({
props: {
isPen: false,
segments: [
{
type: 'free',
points: [
{ x: 0, y: 0, z: 0.5 },
{ x: 1, y: 1, z: 0.5 },
],
},
],
},
})
})
test('up works as expected when converting to pen', () => {
expect(
up({
props: {
segments: [
{
type: 'free',
points: [
{ x: 0, y: 0, z: 0.2315 },
{ x: 1, y: 1, z: 0.2421 },
],
},
],
},
})
).toEqual({
props: {
isPen: true,
segments: [
{
type: 'free',
points: [
{ x: 0, y: 0, z: 0.2315 },
{ x: 1, y: 1, z: 0.2421 },
],
},
],
},
})
})
})
describe('Adding isLocked prop', () => {
const { up, down } = getTestMigration(rootShapeVersions.AddIsLocked)
test('up works as expected', () => {
expect(up({})).toEqual({ isLocked: false })
})
test('down works as expected', () => {
expect(down({ isLocked: false })).toEqual({})
})
})
describe('Adding labelColor prop to geo / arrow shapes', () => {
for (const [name, { up }] of [
['arrow shape', getTestMigration(arrowShapeVersions.AddLabelColor)],
['geo shape', getTestMigration(geoShapeVersions.AddLabelColor)],
] as const) {
test(`${name}: up works as expected`, () => {
expect(up({ props: { color: 'red' } })).toEqual({
props: { color: 'red', labelColor: 'black' },
})
})
}
})
describe('Adding labelColor prop to propsForNextShape', () => {
const { up } = getTestMigration(instanceVersions.AddLabelColor)
test('up works as expected', () => {
expect(up({ propsForNextShape: { color: 'red' } })).toEqual({
propsForNextShape: { color: 'red', labelColor: 'black' },
})
})
})
describe('Adding croppingShapeId to instancePageState', () => {
const { up } = getTestMigration(instancePageStateVersions.AddCroppingId)
test('up works as expected', () => {
expect(up({})).toEqual({
croppingShapeId: null,
})
})
})
describe('Renaming properties in instancePageState', () => {
const { up, down } = getTestMigration(instancePageStateVersions.RenameProperties)
test('up works as expected', () => {
expect(
up({
selectedShapeIds: [],
hintingShapeIds: [],
erasingShapeIds: [],
hoveredShapeId: null,
editingShapeId: null,
croppingShapeId: null,
focusedGroupId: null,
meta: {
name: 'hallo',
},
})
).toEqual({
selectedShapeIds: [],
hintingShapeIds: [],
erasingShapeIds: [],
hoveredShapeId: null,
editingShapeId: null,
croppingShapeId: null,
focusedGroupId: null,
meta: {
name: 'hallo',
},
})
})
test('down works as expected', () => {
expect(
down({
selectedShapeIds: [],
hintingShapeIds: [],
erasingShapeIds: [],
hoveredShapeId: null,
editingShapeId: null,
croppingShapeId: null,
focusedGroupId: null,
meta: {
name: 'hallo',
},
})
).toEqual({
selectedShapeIds: [],
hintingShapeIds: [],
erasingShapeIds: [],
hoveredShapeId: null,
editingShapeId: null,
croppingShapeId: null,
focusedGroupId: null,
meta: {
name: 'hallo',
},
})
})
})
describe('Renaming properties again in instancePageState', () => {
const { up, down } = getTestMigration(instancePageStateVersions.RenamePropertiesAgain)
test('up works as expected', () => {
expect(
up({
selectedIds: [],
hintingIds: [],
erasingIds: [],
hoveredId: null,
editingId: null,
croppingId: null,
focusLayerId: null,
meta: {
name: 'hallo',
},
})
).toEqual({
selectedShapeIds: [],
hintingShapeIds: [],
erasingShapeIds: [],
hoveredShapeId: null,
editingShapeId: null,
croppingShapeId: null,
focusedGroupId: null,
meta: {
name: 'hallo',
},
})
})
test('down works as expected', () => {
expect(
down({
selectedShapeIds: [],
hintingShapeIds: [],
erasingShapeIds: [],
hoveredShapeId: null,
editingShapeId: null,
croppingShapeId: null,
focusedGroupId: null,
meta: {
name: 'hallo',
},
})
).toEqual({
selectedIds: [],
hintingIds: [],
erasingIds: [],
hoveredId: null,
editingId: null,
croppingId: null,
focusLayerId: null,
meta: {
name: 'hallo',
},
})
})
})
describe('Adding followingUserId prop to instance', () => {
const { up } = getTestMigration(instanceVersions.AddFollowingUserId)
test('up works as expected', () => {
expect(up({})).toEqual({ followingUserId: null })
})
})
describe('Removing align=justify from propsForNextShape', () => {
const { up } = getTestMigration(instanceVersions.RemoveAlignJustify)
test('up works as expected', () => {
expect(up({ propsForNextShape: { color: 'black', align: 'justify' } })).toEqual({
propsForNextShape: { color: 'black', align: 'start' },
})
expect(up({ propsForNextShape: { color: 'black', align: 'end' } })).toEqual({
propsForNextShape: { color: 'black', align: 'end' },
})
})
})
describe('Adding zoomBrush prop to instance', () => {
const { up } = getTestMigration(instanceVersions.AddZoom)
test('up works as expected', () => {
expect(up({})).toEqual({ zoomBrush: null })
})
})
describe('Removing align=justify from shape align props', () => {
for (const [name, { up }] of [
['text', getTestMigration(textShapeVersions.RemoveJustify)],
['note', getTestMigration(noteShapeVersions.RemoveJustify)],
['geo', getTestMigration(geoShapeVersions.RemoveJustify)],
] as const) {
test(`${name}: up works as expected`, () => {
expect(up({ props: { align: 'justify' } })).toEqual({ props: { align: 'start' } })
expect(up({ props: { align: 'end' } })).toEqual({ props: { align: 'end' } })
})
}
})
describe('Add crop=null to image shapes', () => {
const { up, down } = getTestMigration(imageShapeVersions.AddCropProp)
test('up works as expected', () => {
expect(up({ props: { w: 100 } })).toEqual({ props: { w: 100, crop: null } })
})
test('down works as expected', () => {
expect(down({ props: { w: 100, crop: null } })).toEqual({ props: { w: 100 } })
})
})
describe('Adding instance_presence to the schema', () => {
const { up } = getTestMigration(storeVersions.AddInstancePresenceType)
test('up works as expected', () => {
expect(up({})).toEqual({})
})
})
describe('Adding name to document', () => {
const { up, down } = getTestMigration(documentVersions.AddName)
test('up works as expected', () => {
expect(up({})).toEqual({ name: '' })
})
test('down works as expected', () => {
expect(down({ name: '' })).toEqual({})
})
})
describe('Adding check-box to geo shape', () => {
const { up } = getTestMigration(geoShapeVersions.AddCheckBox)
test('up works as expected', () => {
expect(up({ props: { geo: 'rectangle' } })).toEqual({ props: { geo: 'rectangle' } })
})
})
describe('Add verticalAlign to geo shape', () => {
const { up } = getTestMigration(geoShapeVersions.AddVerticalAlign)
test('up works as expected', () => {
expect(up({ props: { type: 'ellipse' } })).toEqual({
props: { type: 'ellipse', verticalAlign: 'middle' },
})
})
})
describe('Add verticalAlign to props for next shape', () => {
const { up } = getTestMigration(instanceVersions.AddVerticalAlign)
test('up works as expected', () => {
expect(up({ propsForNextShape: { color: 'red' } })).toEqual({
propsForNextShape: {
color: 'red',
verticalAlign: 'middle',
},
})
})
})
describe('Migrate GeoShape legacy horizontal alignment', () => {
const { up } = getTestMigration(geoShapeVersions.MigrateLegacyAlign)
test('up works as expected', () => {
expect(up({ props: { align: 'start', type: 'ellipse' } })).toEqual({
props: {
align: 'start-legacy',
type: 'ellipse',
},
})
expect(up({ props: { align: 'middle', type: 'ellipse' } })).toEqual({
props: {
align: 'middle-legacy',
type: 'ellipse',
},
})
expect(up({ props: { align: 'end', type: 'ellipse' } })).toEqual({
props: { align: 'end-legacy', type: 'ellipse' },
})
})
})
describe('adding cloud shape', () => {
const { up } = getTestMigration(geoShapeVersions.AddCloud)
test('up does nothing', () => {
expect(up({ props: { geo: 'rectangle' } })).toEqual({ props: { geo: 'rectangle' } })
})
})
describe('Migrate NoteShape legacy horizontal alignment', () => {
const { up } = getTestMigration(noteShapeVersions.MigrateLegacyAlign)
test('up works as expected', () => {
expect(up({ props: { align: 'start', color: 'red' } })).toEqual({
props: { align: 'start-legacy', color: 'red' },
})
expect(up({ props: { align: 'middle', color: 'red' } })).toEqual({
props: { align: 'middle-legacy', color: 'red' },
})
expect(up({ props: { align: 'end', color: 'red' } })).toEqual({
props: { align: 'end-legacy', color: 'red' },
})
})
})
describe('Adds delay to scribble', () => {
const { up } = getTestMigration(instanceVersions.AddScribbleDelay)
test('up has no effect when scribble is null', () => {
expect(
up({
scribble: null,
})
).toEqual({ scribble: null })
})
test('up adds the delay property', () => {
expect(
up({
scribble: {
points: [{ x: 0, y: 0 }],
size: 4,
color: 'black',
opacity: 1,
state: 'starting',
},
})
).toEqual({
scribble: {
points: [{ x: 0, y: 0 }],
size: 4,
color: 'black',
opacity: 1,
state: 'starting',
delay: 0,
},
})
})
})
describe('Adds delay to scribble', () => {
const { up } = getTestMigration(instancePresenceVersions.AddScribbleDelay)
test('up has no effect when scribble is null', () => {
expect(
up({
scribble: null,
})
).toEqual({ scribble: null })
})
test('up adds the delay property', () => {
expect(
up({
scribble: {
points: [{ x: 0, y: 0 }],
size: 4,
color: 'black',
opacity: 1,
state: 'starting',
},
})
).toEqual({
scribble: {
points: [{ x: 0, y: 0 }],
size: 4,
color: 'black',
opacity: 1,
state: 'starting',
delay: 0,
},
})
})
})
describe('user config refactor', () => {
test('removes user and user_presence types from snapshots', () => {
const { up } = getTestMigration(storeVersions.RemoveTLUserAndPresenceAndAddPointer)
const prevSnapshot = {
'user:123': {
id: 'user:123',
typeName: 'user',
},
'user_presence:123': {
id: 'user_presence:123',
typeName: 'user_presence',
},
'instance:123': {
id: 'instance:123',
typeName: 'instance',
},
}
const nextSnapshot = {
'instance:123': {
id: 'instance:123',
typeName: 'instance',
},
}
// up removes the user and user_presence types
expect(up(prevSnapshot)).toEqual(nextSnapshot)
})
test('removes userId from the instance state', () => {
const { up } = getTestMigration(instanceVersions.RemoveUserId)
const prev = {
id: 'instance:123',
typeName: 'instance',
userId: 'user:123',
}
const next = {
id: 'instance:123',
typeName: 'instance',
}
expect(up(prev)).toEqual(next)
})
})
describe('making instance state independent', () => {
it('adds isPenMode and isGridMode to instance state', () => {
const { up } = getTestMigration(instanceVersions.AddIsPenModeAndIsGridMode)
const prev = {
id: 'instance:123',
typeName: 'instance',
}
const next = {
id: 'instance:123',
typeName: 'instance',
isPenMode: false,
isGridMode: false,
}
expect(up(prev)).toEqual(next)
})
it('removes instanceId and cameraId from instancePageState', () => {
const { up } = getTestMigration(instancePageStateVersions.RemoveInstanceIdAndCameraId)
const prev = {
id: 'instance_page_state:123',
typeName: 'instance_page_state',
instanceId: 'instance:123',
cameraId: 'camera:123',
selectedShapeIds: [],
}
const next = {
id: 'instance_page_state:123',
typeName: 'instance_page_state',
selectedShapeIds: [],
}
expect(up(prev)).toEqual(next)
})
it('removes instanceId from instancePresence', () => {
const { up } = getTestMigration(instancePresenceVersions.RemoveInstanceId)
const prev = {
id: 'instance_presence:123',
typeName: 'instance_presence',
instanceId: 'instance:123',
selectedShapeIds: [],
}
const next = {
id: 'instance_presence:123',
typeName: 'instance_presence',
selectedShapeIds: [],
}
expect(up(prev)).toEqual(next)
})
it('removes userDocument from the schema', () => {
const { up } = getTestMigration(storeVersions.RemoveUserDocument)
const prev = {
'user_document:123': {
id: 'user_document:123',
typeName: 'user_document',
},
'instance:123': {
id: 'instance:123',
typeName: 'instance',
},
}
const next = {
'instance:123': {
id: 'instance:123',
typeName: 'instance',
},
}
expect(up(prev)).toEqual(next)
})
})
describe('Adds NoteShape vertical alignment', () => {
const { up } = getTestMigration(noteShapeVersions.AddVerticalAlign)
test('up works as expected', () => {
expect(up({ props: { color: 'red' } })).toEqual({
props: { color: 'red', verticalAlign: 'middle' },
})
})
})
describe('hoist opacity', () => {
test('hoists opacity from props to the root of the shape', () => {
const { up, down } = getTestMigration(rootShapeVersions.HoistOpacity)
const before = {
type: 'myShape',
x: 0,
y: 0,
props: {
color: 'red',
opacity: '0.5',
},
}
const after = {
type: 'myShape',
x: 0,
y: 0,
opacity: 0.5,
props: {
color: 'red',
},
}
expect(up(before)).toEqual(after)
expect(down(after)).toEqual(before)
const afterWithNonMatchingOpacity = {
type: 'myShape',
x: 0,
y: 0,
opacity: 0.6,
props: {
color: 'red',
},
}
expect(down(afterWithNonMatchingOpacity)).toEqual(before)
})
test('hoists opacity from propsForNextShape', () => {
const { up } = getTestMigration(instanceVersions.HoistOpacity)
const before = {
isToolLocked: true,
propsForNextShape: {
color: 'black',
opacity: '0.5',
},
}
const after = {
isToolLocked: true,
opacityForNextShape: 0.5,
propsForNextShape: {
color: 'black',
},
}
expect(up(before)).toEqual(after)
})
})
describe('Adds highlightedUserIds to instance', () => {
const { up } = getTestMigration(instanceVersions.AddHighlightedUserIds)
test('up works as expected', () => {
expect(up({})).toEqual({ highlightedUserIds: [] })
})
})
describe('Adds chat message to presence', () => {
const { up } = getTestMigration(instancePresenceVersions.AddChatMessage)
test('up adds the chatMessage property', () => {
expect(up({})).toEqual({ chatMessage: '' })
})
})
describe('Adds chat properties to instance', () => {
const { up } = getTestMigration(instanceVersions.AddChat)
test('up adds the chatMessage property', () => {
expect(up({})).toEqual({ chatMessage: '', isChatting: false })
})
})
describe('Removes does resize from embed', () => {
const { up } = getTestMigration(embedShapeVersions.RemoveDoesResize)
test('up works as expected', () => {
expect(up({ props: { url: 'https://tldraw.com', doesResize: true } })).toEqual({
props: {
url: 'https://tldraw.com',
},
})
})
})
describe('Removes tmpOldUrl from embed', () => {
const { up } = getTestMigration(embedShapeVersions.RemoveTmpOldUrl)
test('up works as expected', () => {
expect(up({ props: { url: 'https://tldraw.com', tmpOldUrl: 'https://tldraw.com' } })).toEqual({
props: {
url: 'https://tldraw.com',
},
})
})
})
describe('Removes overridePermissions from embed', () => {
const { up } = getTestMigration(embedShapeVersions.RemovePermissionOverrides)
test('up works as expected', () => {
expect(
up({ props: { url: 'https://tldraw.com', overridePermissions: { display: 'maybe' } } })
).toEqual({
props: {
url: 'https://tldraw.com',
},
})
})
})
describe('propsForNextShape -> stylesForNextShape', () => {
test('deletes propsForNextShape and adds stylesForNextShape without trying to bring across contents', () => {
const { up } = getTestMigration(instanceVersions.ReplacePropsForNextShapeWithStylesForNextShape)
const beforeUp = {
isToolLocked: true,
propsForNextShape: {
color: 'red',
size: 'm',
},
}
const afterUp = {
isToolLocked: true,
stylesForNextShape: {},
}
expect(up(beforeUp)).toEqual(afterUp)
})
})
describe('adds meta ', () => {
const metaMigrations = [
getTestMigration(assetVersions.AddMeta),
getTestMigration(cameraVersions.AddMeta),
getTestMigration(documentVersions.AddMeta),
getTestMigration(instanceVersions.AddMeta),
getTestMigration(instancePageStateVersions.AddMeta),
getTestMigration(instancePresenceVersions.AddMeta),
getTestMigration(pageVersions.AddMeta),
getTestMigration(pointerVersions.AddMeta),
getTestMigration(rootShapeVersions.AddMeta),
]
for (const { up, id } of metaMigrations) {
test('up works as expected for ' + id, () => {
expect(up({})).toEqual({ meta: {} })
})
}
})
describe('removes cursor color', () => {
const { up } = getTestMigration(instanceVersions.RemoveCursorColor)
test('up works as expected', () => {
expect(
up({
cursor: {
type: 'default',
rotation: 0.1,
color: 'black',
},
})
).toEqual({
cursor: {
type: 'default',
rotation: 0.1,
},
})
})
})
describe('adds lonely properties', () => {
const { up } = getTestMigration(instanceVersions.AddLonelyProperties)
test('up works as expected', () => {
expect(up({})).toEqual({
canMoveCamera: true,
isFocused: false,
devicePixelRatio: 1,
isCoarsePointer: false,
openMenus: [],
isChangingStyle: false,
isReadOnly: false,
})
})
})
describe('rename isReadOnly to isReadonly', () => {
const { up } = getTestMigration(instanceVersions.ReadOnlyReadonly)
test('up works as expected', () => {
expect(up({ isReadOnly: false })).toEqual({
isReadonly: false,
})
})
})
describe('Renames selectedShapeIds in presence', () => {
const { up } = getTestMigration(instancePresenceVersions.RenameSelectedShapeIds)
test('up adds the chatMessage property', () => {
expect(up({ selectedShapeIds: [] })).toEqual({ selectedShapeIds: [] })
})
})
describe('Adding canSnap to line handles', () => {
const { up } = getTestMigration(lineShapeVersions.AddSnapHandles)
test(`up works as expected`, () => {
expect(
up({
props: {
handles: {
start: {
id: 'start',
type: 'vertex',
canBind: false,
index: 'a1',
x: 0,
y: 0,
},
end: {
id: 'end',
type: 'vertex',
canBind: false,
index: 'a2',
x: 100.66015625,
y: -22.07421875,
},
},
},
})
).toEqual({
props: {
handles: {
start: {
id: 'start',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a1',
x: 0,
y: 0,
},
end: {
id: 'end',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a2',
x: 100.66015625,
y: -22.07421875,
},
},
},
})
})
})
describe('add isHoveringCanvas to TLInstance', () => {
const { up } = getTestMigration(instanceVersions.AddHoveringCanvas)
test('up works as expected', () => {
expect(up({})).toEqual({ isHoveringCanvas: null })
})
})
describe('add isInset to TLInstance', () => {
const { up, down } = getTestMigration(instanceVersions.AddInset)
test('up works as expected', () => {
expect(up({})).toEqual({ insets: [false, false, false, false] })
})
test('down works as expected', () => {
expect(down({ insets: [false, false, false, false] })).toEqual({})
})
})
describe('add scribbles to TLInstance', () => {
const { up } = getTestMigration(instanceVersions.AddScribbles)
test('up works as expected', () => {
expect(
up({
scribble: null,
})
).toEqual({ scribbles: [] })
})
})
describe('add isPrecise to arrow handles', () => {
const { up, down } = getTestMigration(arrowShapeVersions.AddIsPrecise)
test('up works as expected', () => {
expect(
up({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
},
},
})
).toEqual({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
isPrecise: false,
},
},
})
expect(
up({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.15, y: 0.15 },
},
},
})
).toEqual({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.15, y: 0.15 },
isPrecise: true,
},
},
})
})
test('down works as expected', () => {
expect(
down({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
isPrecise: true,
},
},
})
).toEqual({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
},
},
})
expect(
down({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.25, y: 0.25 },
isPrecise: true,
},
},
})
).toEqual({
props: {
start: {
type: 'point',
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.25, y: 0.25 },
},
},
})
expect(
down({
props: {
start: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
isPrecise: false,
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.15, y: 0.15 },
isPrecise: false,
},
},
})
).toEqual({
props: {
start: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
},
end: {
type: 'binding',
normalizedAnchor: { x: 0.5, y: 0.5 },
},
},
})
})
})
describe('add AddLabelPosition to arrow handles', () => {
const { up, down } = getTestMigration(arrowShapeVersions.AddLabelPosition)
test('up works as expected', () => {
expect(up({ props: {} })).toEqual({ props: { labelPosition: 0.5 } })
})
test('down works as expected', () => {
expect(
down({
props: {
labelPosition: 0.5,
},
})
).toEqual({ props: {} })
})
})
const invalidUrl = 'invalid-url'
const validUrl = ''
describe('Make urls valid for all the shapes', () => {
const migrations = [
['bookmark shape', getTestMigration(bookmarkShapeVersions.MakeUrlsValid)],
['geo shape', getTestMigration(geoShapeVersions.MakeUrlsValid)],
['image shape', getTestMigration(imageShapeVersions.MakeUrlsValid)],
['note shape', getTestMigration(noteShapeVersions.MakeUrlsValid)],
['video shape', getTestMigration(videoShapeVersions.MakeUrlsValid)],
] as const
for (const [shapeName, { up, down }] of migrations) {
it(`works for ${shapeName}`, () => {
const shape = { props: { url: invalidUrl } }
expect(up(shape)).toEqual({ props: { url: validUrl } })
expect(down(shape)).toEqual(shape)
})
}
})
describe('Make urls valid for all the assets', () => {
const migrations = [
['bookmark asset', getTestMigration(bookmarkAssetVersions.MakeUrlsValid)],
['image asset', getTestMigration(imageAssetVersions.MakeUrlsValid)],
['video asset', getTestMigration(videoAssetVersions.MakeUrlsValid)],
] as const
for (const [assetName, { up, down }] of migrations) {
it(`works for ${assetName}`, () => {
const asset = { props: { src: invalidUrl } }
expect(up(asset)).toEqual({ props: { src: validUrl } })
expect(down(asset)).toEqual(asset)
})
}
})
describe('Add duplicate props to instance', () => {
const { up, down } = getTestMigration(instanceVersions.AddDuplicateProps)
it('up works as expected', () => {
expect(up({})).toEqual({ duplicateProps: null })
})
it('down works as expected', () => {
expect(down({ duplicateProps: null })).toEqual({})
})
})
describe('Remove extra handle props', () => {
const { up, down } = getTestMigration(lineShapeVersions.RemoveExtraHandleProps)
it('up works as expected', () => {
expect(
up({
props: {
handles: {
start: {
id: 'start',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a1',
x: 0,
y: 0,
},
end: {
id: 'end',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a2',
x: 190,
y: -62,
},
'handle:a1V': {
id: 'handle:a1V',
type: 'vertex',
canBind: false,
index: 'a1V',
x: 76,
y: 60,
},
},
},
})
).toEqual({
props: {
handles: {
a1: { x: 0, y: 0 },
a1V: { x: 76, y: 60 },
a2: { x: 190, y: -62 },
},
},
})
})
it('down works as expected', () => {
expect(
down({
props: {
handles: {
a1: { x: 0, y: 0 },
a1V: { x: 76, y: 60 },
a2: { x: 190, y: -62 },
},
},
})
).toEqual({
props: {
handles: {
start: {
id: 'start',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a1',
x: 0,
y: 0,
},
end: {
id: 'end',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a2',
x: 190,
y: -62,
},
'handle:a1V': {
id: 'handle:a1V',
type: 'vertex',
canBind: false,
canSnap: true,
index: 'a1V',
x: 76,
y: 60,
},
},
},
})
})
})
describe('Restore some handle props', () => {
const { up, down } = getTestMigration(lineShapeVersions.HandlesToPoints)
it('up works as expected', () => {
expect(
up({
props: {
handles: {
a1: { x: 0, y: 0 },
a1V: { x: 76, y: 60 },
a2: { x: 190, y: -62 },
},
},
})
).toEqual({
props: {
points: [
{ x: 0, y: 0 },
{ x: 76, y: 60 },
{ x: 190, y: -62 },
],
},
})
})
it('down works as expected', () => {
expect(
down({
props: {
points: [
{ x: 0, y: 0 },
{ x: 76, y: 60 },
{ x: 190, y: -62 },
],
},
})
).toEqual({
props: {
handles: {
a1: { x: 0, y: 0 },
a2: { x: 76, y: 60 },
a3: { x: 190, y: -62 },
},
},
})
})
})
describe('Fractional indexing for line points', () => {
const { up, down } = getTestMigration(lineShapeVersions.PointIndexIds)
it('up works as expected', () => {
expect(
up({
props: {
points: [
{ x: 0, y: 0 },
{ x: 76, y: 60 },
{ x: 190, y: -62 },
],
},
})
).toEqual({
props: {
points: {
a1: { id: 'a1', index: 'a1', x: 0, y: 0 },
a2: { id: 'a2', index: 'a2', x: 76, y: 60 },
a3: { id: 'a3', index: 'a3', x: 190, y: -62 },
},
},
})
})
it('down works as expected', () => {
expect(
down({
props: {
points: {
a1: { id: 'a1', index: 'a1', x: 0, y: 0 },
a3: { id: 'a3', index: 'a3', x: 190, y: -62 },
a2: { id: 'a2', index: 'a2', x: 76, y: 60 },
},
},
})
).toEqual({
props: {
points: [
{ x: 0, y: 0 },
{ x: 76, y: 60 },
{ x: 190, y: -62 },
],
},
})
})
})
describe('add white', () => {
const { up, down } = getTestMigration(rootShapeVersions.AddWhite)
test('up works as expected', () => {
expect(
up({
props: {},
})
).toEqual({
props: {},
})
})
test('down works as expected', () => {
expect(
down({
props: {
color: 'white',
},
})
).toEqual({
props: {
color: 'black',
},
})
})
})
describe('Add font size adjustment to notes', () => {
const { up, down } = getTestMigration(noteShapeVersions.AddFontSizeAdjustment)
test('up works as expected', () => {
expect(up({ props: {} })).toEqual({ props: { fontSizeAdjustment: 0 } })
})
test('down works as expected', () => {
expect(down({ props: { fontSizeAdjustment: 0 } })).toEqual({ props: {} })
})
})
describe('removes can move camera', () => {
const { up, down } = getTestMigration(instanceVersions.RemoveCanMoveCamera)
test('up works as expected', () => {
expect(up({ canMoveCamera: true })).toStrictEqual({})
})
test('down works as expected', () => {
expect(down({})).toStrictEqual({ canMoveCamera: true })
})
})
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
// check that all migrator fns were called at least once
describe('all migrator fns were called at least once', () => {
for (const migration of testSchema.sortedMigrations) {
it(`migration ${migration.id}`, () => {
expect((migration as any).up).toHaveBeenCalled()
if (typeof migration.down === 'function') {
expect((migration as any).down).toHaveBeenCalled()
}
})
}
})