kopia lustrzana https://github.com/Tldraw/Tldraw
1121 wiersze
28 KiB
TypeScript
1121 wiersze
28 KiB
TypeScript
import { Migrations, Store, createRecordType } from '@tldraw/store'
|
|
import fs from 'fs'
|
|
import { imageAssetMigrations } from './assets/TLImageAsset'
|
|
import { videoAssetMigrations } from './assets/TLVideoAsset'
|
|
import { documentMigrations } from './records/TLDocument'
|
|
import { instanceMigrations, instanceTypeVersions } from './records/TLInstance'
|
|
import { instancePageStateMigrations, instancePageStateVersions } from './records/TLPageState'
|
|
import { instancePresenceMigrations, instancePresenceVersions } from './records/TLPresence'
|
|
import { TLShape, rootShapeMigrations, Versions as rootShapeVersions } from './records/TLShape'
|
|
import { arrowShapeMigrations } from './shapes/TLArrowShape'
|
|
import { bookmarkShapeMigrations } from './shapes/TLBookmarkShape'
|
|
import { drawShapeMigrations } from './shapes/TLDrawShape'
|
|
import { embedShapeMigrations } from './shapes/TLEmbedShape'
|
|
import { geoShapeMigrations } from './shapes/TLGeoShape'
|
|
import { imageShapeMigrations } from './shapes/TLImageShape'
|
|
import { noteShapeMigrations } from './shapes/TLNoteShape'
|
|
import { textShapeMigrations } from './shapes/TLTextShape'
|
|
import { videoShapeMigrations } from './shapes/TLVideoShape'
|
|
import { storeMigrations, storeVersions } from './store-migrations'
|
|
|
|
const assetModules = fs
|
|
.readdirSync('src/assets')
|
|
.filter((n) => n.match(/^TL.*\.ts$/))
|
|
.map((f) => [f, require(`./assets/${f.slice(0, -3)}`)])
|
|
const shapeModules = fs
|
|
.readdirSync('src/shapes')
|
|
.filter((n) => n.match(/^TL.*\.ts$/))
|
|
.map((f) => [f, require(`./shapes/${f.slice(0, -3)}`)])
|
|
const recordModules = fs
|
|
.readdirSync('src/records')
|
|
.filter((n) => n.match(/^TL.*\.ts$/))
|
|
.map((f) => [f, require(`./records/${f.slice(0, -3)}`)])
|
|
|
|
const allModules = [
|
|
...assetModules,
|
|
...shapeModules,
|
|
...recordModules,
|
|
['store-migrations.ts', require('./store-migrations')],
|
|
]
|
|
|
|
const allMigrators: Array<{
|
|
fileName: string
|
|
version: number
|
|
up: jest.SpyInstance
|
|
down: jest.SpyInstance
|
|
}> = []
|
|
|
|
for (const [fileName, module] of allModules) {
|
|
const migrationsKey = Object.keys(module).find((k) => k.endsWith('igrations'))
|
|
|
|
if (!migrationsKey) continue
|
|
|
|
const migrations: Migrations = module[migrationsKey]
|
|
|
|
for (const version of Object.keys(migrations.migrators)) {
|
|
const originalUp = migrations.migrators[version as any].up
|
|
const originalDown = migrations.migrators[version as any].down
|
|
const up = jest
|
|
.spyOn(migrations.migrators[version as any], 'up')
|
|
.mockImplementation((initialRecord) => {
|
|
if (initialRecord instanceof Store) return originalUp(initialRecord)
|
|
|
|
const clonedRecord = structuredClone(initialRecord)
|
|
const result = originalUp(initialRecord)
|
|
// mutations should never mutate their input
|
|
expect(initialRecord).toEqual(clonedRecord)
|
|
return result
|
|
})
|
|
const down = jest
|
|
.spyOn(migrations.migrators[version as any], 'down')
|
|
.mockImplementation((initialRecord) => {
|
|
if (initialRecord instanceof Store) return originalDown(initialRecord)
|
|
|
|
const clonedRecord = structuredClone(initialRecord)
|
|
const result = originalDown(initialRecord)
|
|
// mutations should never mutate their input
|
|
expect(initialRecord).toEqual(clonedRecord)
|
|
return result
|
|
})
|
|
allMigrators.push({
|
|
fileName,
|
|
version: Number(version),
|
|
up,
|
|
down,
|
|
})
|
|
}
|
|
}
|
|
|
|
test('all modules export migrations', () => {
|
|
const modulesWithoutMigrations = allModules
|
|
.filter(([, module]) => {
|
|
return !Object.keys(module).find((k) => k.endsWith('igrations'))
|
|
})
|
|
.map(([fileName]) => fileName)
|
|
.filter((n) => !(n === 'TLBaseAsset.ts' || n === 'TLBaseShape.ts' || n === 'TLRecord.ts'))
|
|
|
|
// IF THIS LINE IS FAILING YOU NEED TO MAKE SURE THE MIGRATIONS ARE EXPORTED
|
|
expect(modulesWithoutMigrations).toHaveLength(0)
|
|
})
|
|
|
|
/* --- 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 } = videoAssetMigrations.migrators[1]
|
|
|
|
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 } = imageAssetMigrations.migrators[1]
|
|
|
|
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', () => {
|
|
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 = storeMigrations.migrators[storeVersions.RemoveCodeAndIconShapeTypes].up(snapshot)
|
|
expect(Object.entries(fixed)).toHaveLength(1)
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const snapshot = Object.fromEntries(
|
|
[
|
|
ShapeRecord.create({
|
|
type: 'geo',
|
|
parentId: 'page:any',
|
|
index: 'a0',
|
|
props: { geo: 'rectangle', name: 'e', w: 1, h: 1, growY: 1, text: '' },
|
|
} as any),
|
|
].map((shape) => [shape.id, shape])
|
|
)
|
|
|
|
storeMigrations.migrators[storeVersions.RemoveCodeAndIconShapeTypes].down(snapshot)
|
|
expect(Object.entries(snapshot)).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('Adding export background', () => {
|
|
const { up, down } = instanceMigrations.migrators[1]
|
|
test('up works as expected', () => {
|
|
const before = {}
|
|
const after = { exportBackground: true }
|
|
expect(up(before)).toStrictEqual(after)
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const before = { exportBackground: true }
|
|
const after = {}
|
|
expect(down(before)).toStrictEqual(after)
|
|
})
|
|
})
|
|
|
|
describe('Removing dialogs from instance', () => {
|
|
const { up, down } = instanceMigrations.migrators[2]
|
|
test('up works as expected', () => {
|
|
const before = { dialog: null }
|
|
const after = {}
|
|
expect(up(before)).toStrictEqual(after)
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const before = {}
|
|
const after = { dialog: null }
|
|
expect(down(before)).toStrictEqual(after)
|
|
})
|
|
})
|
|
|
|
describe('Adding url props', () => {
|
|
for (const [name, { up, down }] of [
|
|
['video shape', videoShapeMigrations.migrators[1]],
|
|
['note shape', noteShapeMigrations.migrators[1]],
|
|
['geo shape', geoShapeMigrations.migrators[1]],
|
|
['image shape', imageShapeMigrations.migrators[1]],
|
|
] as const) {
|
|
test(`${name}: up works as expected`, () => {
|
|
const before = { props: {} }
|
|
const after = { props: { url: '' } }
|
|
expect(up(before)).toStrictEqual(after)
|
|
})
|
|
|
|
test(`${name}: down works as expected`, () => {
|
|
const before = { props: { url: '' } }
|
|
const after = { props: {} }
|
|
expect(down(before)).toStrictEqual(after)
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('Bookmark null asset id', () => {
|
|
const { up, down } = bookmarkShapeMigrations.migrators[1]
|
|
test('up works as expected', () => {
|
|
const before = { props: {} }
|
|
const after = { props: { assetId: null } }
|
|
expect(up(before)).toStrictEqual(after)
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const before = { props: { assetId: null } }
|
|
const after = { props: {} }
|
|
expect(down(before)).toStrictEqual(after)
|
|
})
|
|
})
|
|
|
|
describe('Renaming asset props', () => {
|
|
for (const [name, { up, down }] of [
|
|
['image shape', imageAssetMigrations.migrators[2]],
|
|
['video shape', videoAssetMigrations.migrators[2]],
|
|
] 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)).toStrictEqual(after)
|
|
})
|
|
|
|
test(`${name}: down works as expected`, () => {
|
|
const before = { props: { w: 100, h: 100 } }
|
|
const after = { props: { width: 100, height: 100 } }
|
|
expect(down(before)).toStrictEqual(after)
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('Adding instance.isToolLocked', () => {
|
|
const { up, down } = instanceMigrations.migrators[3]
|
|
test('up works as expected', () => {
|
|
expect(up({})).toMatchObject({ isToolLocked: false })
|
|
expect(up({ isToolLocked: true })).toMatchObject({ isToolLocked: false })
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ isToolLocked: true })).toStrictEqual({})
|
|
expect(down({ isToolLocked: false })).toStrictEqual({})
|
|
})
|
|
})
|
|
|
|
describe('Cleaning up junk data in instance.propsForNextShape', () => {
|
|
const { up, down } = instanceMigrations.migrators[4]
|
|
test('up works as expected', () => {
|
|
expect(up({ propsForNextShape: { color: 'red', unknown: 'gone' } })).toEqual({
|
|
propsForNextShape: {
|
|
color: 'red',
|
|
},
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const instance = { propsForNextShape: { color: 'red' } }
|
|
expect(down(instance)).toBe(instance)
|
|
})
|
|
})
|
|
|
|
describe('Generating original URL from embed URL in GenOriginalUrlInEmbed', () => {
|
|
const { up, down } = embedShapeMigrations.migrators[1]
|
|
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',
|
|
},
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const instance = {
|
|
props: {
|
|
url: 'https://codepen.io/Rplus/pen/PWZYRM',
|
|
tmpOldUrl: 'https://codepen.io/Rplus/embed/PWZYRM',
|
|
},
|
|
}
|
|
expect(down(instance)).toEqual({ props: { url: 'https://codepen.io/Rplus/embed/PWZYRM' } })
|
|
})
|
|
|
|
test('invalid down works as expected', () => {
|
|
const instance = {
|
|
props: {
|
|
url: 'https://example.com',
|
|
tmpOldUrl: '',
|
|
},
|
|
}
|
|
expect(down(instance)).toEqual({ props: { url: '' } })
|
|
})
|
|
})
|
|
|
|
describe('Adding isPen prop', () => {
|
|
const { up, down } = drawShapeMigrations.migrators[1]
|
|
|
|
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 },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ props: { isPen: false } })).toEqual({
|
|
props: {},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Adding isLocked prop', () => {
|
|
const { up, down } = rootShapeMigrations.migrators[1]
|
|
|
|
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, down }] of [
|
|
['arrow shape', arrowShapeMigrations.migrators[1]],
|
|
['geo shape', geoShapeMigrations.migrators[2]],
|
|
] as const) {
|
|
test(`${name}: up works as expected`, () => {
|
|
expect(up({ props: { color: 'red' } })).toEqual({
|
|
props: { color: 'red', labelColor: 'black' },
|
|
})
|
|
})
|
|
|
|
test(`${name}: down works as expected`, () => {
|
|
expect(down({ props: { color: 'red', labelColor: 'blue' } })).toEqual({
|
|
props: { color: 'red' },
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('Adding labelColor prop to propsForNextShape', () => {
|
|
const { up, down } = instanceMigrations.migrators[5]
|
|
test('up works as expected', () => {
|
|
expect(up({ propsForNextShape: { color: 'red' } })).toEqual({
|
|
propsForNextShape: { color: 'red', labelColor: 'black' },
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ propsForNextShape: { color: 'red', labelColor: 'blue' } })).toEqual({
|
|
propsForNextShape: { color: 'red' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Adding croppingId to instancePageState', () => {
|
|
const { up, down } = instancePageStateMigrations.migrators[1]
|
|
test('up works as expected', () => {
|
|
expect(up({})).toEqual({
|
|
croppingId: null,
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ croppingId: null })).toEqual({})
|
|
})
|
|
})
|
|
|
|
describe('Adding followingUserId prop to instance', () => {
|
|
const { up, down } = instanceMigrations.migrators[6]
|
|
test('up works as expected', () => {
|
|
expect(up({})).toEqual({ followingUserId: null })
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ followingUserId: '123' })).toEqual({})
|
|
})
|
|
})
|
|
|
|
describe('Removing align=justify from propsForNextShape', () => {
|
|
const { up, down } = instanceMigrations.migrators[7]
|
|
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' },
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ propsForNextShape: { color: 'black', align: 'end' } })).toEqual({
|
|
propsForNextShape: { color: 'black', align: 'end' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Adding zoomBrush prop to instance', () => {
|
|
const { up, down } = instanceMigrations.migrators[8]
|
|
test('up works as expected', () => {
|
|
expect(up({})).toEqual({ zoomBrush: null })
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
expect(down({ zoomBrush: { x: 1, y: 2, w: 3, h: 4 } })).toEqual({})
|
|
})
|
|
})
|
|
|
|
describe('Removing align=justify from shape align props', () => {
|
|
for (const [name, { up, down }] of [
|
|
['text', textShapeMigrations.migrators[1]],
|
|
['note', noteShapeMigrations.migrators[2]],
|
|
['geo', geoShapeMigrations.migrators[3]],
|
|
] 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' },
|
|
})
|
|
})
|
|
|
|
test(`${name}: down works as expected`, () => {
|
|
expect(down({ props: { align: 'start' } })).toEqual({
|
|
props: { align: 'start' },
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('Add crop=null to image shapes', () => {
|
|
const { up, down } = imageShapeMigrations.migrators[2]
|
|
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, down } = storeMigrations.migrators[storeVersions.AddInstancePresenceType]
|
|
|
|
test('up works as expected', () => {
|
|
expect(up({})).toEqual({})
|
|
})
|
|
test('down works as expected', () => {
|
|
expect(
|
|
down({
|
|
'instance_presence:123': { id: 'instance_presence:123', typeName: 'instance_presence' },
|
|
'instance:123': { id: 'instance:123', typeName: 'instance' },
|
|
})
|
|
).toEqual({
|
|
'instance:123': { id: 'instance:123', typeName: 'instance' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Adding name to document', () => {
|
|
const { up, down } = documentMigrations.migrators[1]
|
|
|
|
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, down } = geoShapeMigrations.migrators[4]
|
|
|
|
test('up works as expected', () => {
|
|
expect(up({ props: { geo: 'rectangle' } })).toEqual({ props: { geo: 'rectangle' } })
|
|
})
|
|
test('down works as expected', () => {
|
|
expect(down({ props: { geo: 'rectangle' } })).toEqual({ props: { geo: 'rectangle' } })
|
|
expect(down({ props: { geo: 'check-box' } })).toEqual({ props: { geo: 'rectangle' } })
|
|
})
|
|
})
|
|
|
|
describe('Add verticalAlign to geo shape', () => {
|
|
const { up, down } = geoShapeMigrations.migrators[5]
|
|
|
|
test('up works as expected', () => {
|
|
expect(up({ props: { type: 'ellipse' } })).toEqual({
|
|
props: { type: 'ellipse', verticalAlign: 'middle' },
|
|
})
|
|
})
|
|
test('down works as expected', () => {
|
|
expect(down({ props: { verticalAlign: 'middle', type: 'ellipse' } })).toEqual({
|
|
props: { type: 'ellipse' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Add verticalAlign to props for next shape', () => {
|
|
const { up, down } = instanceMigrations.migrators[9]
|
|
test('up works as expected', () => {
|
|
expect(up({ propsForNextShape: { color: 'red' } })).toEqual({
|
|
propsForNextShape: {
|
|
color: 'red',
|
|
verticalAlign: 'middle',
|
|
},
|
|
})
|
|
})
|
|
|
|
test('down works as expected', () => {
|
|
const instance = { propsForNextShape: { color: 'red', verticalAlign: 'middle' } }
|
|
expect(down(instance)).toEqual({
|
|
propsForNextShape: {
|
|
color: 'red',
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Migrate GeoShape legacy horizontal alignment', () => {
|
|
const { up, down } = geoShapeMigrations.migrators[6]
|
|
|
|
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' },
|
|
})
|
|
})
|
|
test('down works as expected', () => {
|
|
expect(down({ props: { align: 'start-legacy', type: 'ellipse' } })).toEqual({
|
|
props: { align: 'start', type: 'ellipse' },
|
|
})
|
|
expect(down({ props: { align: 'middle-legacy', type: 'ellipse' } })).toEqual({
|
|
props: { align: 'middle', type: 'ellipse' },
|
|
})
|
|
expect(down({ props: { align: 'end-legacy', type: 'ellipse' } })).toEqual({
|
|
props: { align: 'end', type: 'ellipse' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Migrate NoteShape legacy horizontal alignment', () => {
|
|
const { up, down } = noteShapeMigrations.migrators[3]
|
|
|
|
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' },
|
|
})
|
|
})
|
|
test('down works as expected', () => {
|
|
expect(down({ props: { align: 'start-legacy', color: 'red' } })).toEqual({
|
|
props: { align: 'start', color: 'red' },
|
|
})
|
|
expect(down({ props: { align: 'middle-legacy', color: 'red' } })).toEqual({
|
|
props: { align: 'middle', color: 'red' },
|
|
})
|
|
expect(down({ props: { align: 'end-legacy', color: 'red' } })).toEqual({
|
|
props: { align: 'end', color: 'red' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Adds delay to scribble', () => {
|
|
const { up, down } = instanceMigrations.migrators[10]
|
|
|
|
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,
|
|
},
|
|
})
|
|
})
|
|
|
|
test('down has no effect when scribble is null', () => {
|
|
expect(down({ scribble: null })).toEqual({ scribble: null })
|
|
})
|
|
|
|
test('removes the delay property', () => {
|
|
expect(
|
|
down({
|
|
scribble: {
|
|
points: [{ x: 0, y: 0 }],
|
|
size: 4,
|
|
color: 'black',
|
|
opacity: 1,
|
|
state: 'starting',
|
|
delay: 0,
|
|
},
|
|
})
|
|
).toEqual({
|
|
scribble: {
|
|
points: [{ x: 0, y: 0 }],
|
|
size: 4,
|
|
color: 'black',
|
|
opacity: 1,
|
|
state: 'starting',
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Adds delay to scribble', () => {
|
|
const { up, down } = instancePresenceMigrations.migrators[1]
|
|
|
|
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,
|
|
},
|
|
})
|
|
})
|
|
|
|
test('down has no effect when scribble is null', () => {
|
|
expect(down({ scribble: null })).toEqual({ scribble: null })
|
|
})
|
|
|
|
test('removes the delay property', () => {
|
|
expect(
|
|
down({
|
|
scribble: {
|
|
points: [{ x: 0, y: 0 }],
|
|
size: 4,
|
|
color: 'black',
|
|
opacity: 1,
|
|
state: 'starting',
|
|
delay: 0,
|
|
},
|
|
})
|
|
).toEqual({
|
|
scribble: {
|
|
points: [{ x: 0, y: 0 }],
|
|
size: 4,
|
|
color: 'black',
|
|
opacity: 1,
|
|
state: 'starting',
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('user config refactor', () => {
|
|
test('removes user and user_presence types from snapshots', () => {
|
|
const { up, down } =
|
|
storeMigrations.migrators[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)
|
|
// down cannot add them back so it should be a no-op
|
|
expect(
|
|
down({
|
|
...nextSnapshot,
|
|
'pointer:134': {
|
|
id: 'pointer:134',
|
|
typeName: 'pointer',
|
|
},
|
|
})
|
|
).toEqual(nextSnapshot)
|
|
})
|
|
|
|
test('removes userId from the instance state', () => {
|
|
const { up, down } = instanceMigrations.migrators[instanceTypeVersions.RemoveUserId]
|
|
|
|
const prev = {
|
|
id: 'instance:123',
|
|
typeName: 'instance',
|
|
userId: 'user:123',
|
|
}
|
|
|
|
const next = {
|
|
id: 'instance:123',
|
|
typeName: 'instance',
|
|
}
|
|
|
|
expect(up(prev)).toEqual(next)
|
|
// it cannot be added back so it should add some meaningless id in there
|
|
// in practice, because we bumped the store version, this down migrator will never be used
|
|
expect(down(next)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"id": "instance:123",
|
|
"typeName": "instance",
|
|
"userId": "user:none",
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('making instance state independent', () => {
|
|
it('adds isPenMode and isGridMode to instance state', () => {
|
|
const { up, down } =
|
|
instanceMigrations.migrators[instanceTypeVersions.AddIsPenModeAndIsGridMode]
|
|
|
|
const prev = {
|
|
id: 'instance:123',
|
|
typeName: 'instance',
|
|
}
|
|
const next = {
|
|
id: 'instance:123',
|
|
typeName: 'instance',
|
|
isPenMode: false,
|
|
isGridMode: false,
|
|
}
|
|
|
|
expect(up(prev)).toEqual(next)
|
|
expect(down(next)).toEqual(prev)
|
|
})
|
|
|
|
it('removes instanceId and cameraId from instancePageState', () => {
|
|
const { up, down } =
|
|
instancePageStateMigrations.migrators[instancePageStateVersions.RemoveInstanceIdAndCameraId]
|
|
|
|
const prev = {
|
|
id: 'instance_page_state:123',
|
|
typeName: 'instance_page_state',
|
|
instanceId: 'instance:123',
|
|
cameraId: 'camera:123',
|
|
selectedIds: [],
|
|
}
|
|
|
|
const next = {
|
|
id: 'instance_page_state:123',
|
|
typeName: 'instance_page_state',
|
|
selectedIds: [],
|
|
}
|
|
|
|
expect(up(prev)).toEqual(next)
|
|
// down should never be called
|
|
expect(down(next)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"cameraId": "camera:void",
|
|
"id": "instance_page_state:123",
|
|
"instanceId": "instance:instance",
|
|
"selectedIds": Array [],
|
|
"typeName": "instance_page_state",
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('removes instanceId from instancePresence', () => {
|
|
const { up, down } =
|
|
instancePresenceMigrations.migrators[instancePresenceVersions.RemoveInstanceId]
|
|
|
|
const prev = {
|
|
id: 'instance_presence:123',
|
|
typeName: 'instance_presence',
|
|
instanceId: 'instance:123',
|
|
selectedIds: [],
|
|
}
|
|
|
|
const next = {
|
|
id: 'instance_presence:123',
|
|
typeName: 'instance_presence',
|
|
selectedIds: [],
|
|
}
|
|
|
|
expect(up(prev)).toEqual(next)
|
|
|
|
// down should never be called
|
|
expect(down(next)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"id": "instance_presence:123",
|
|
"instanceId": "instance:instance",
|
|
"selectedIds": Array [],
|
|
"typeName": "instance_presence",
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('removes userDocument from the schema', () => {
|
|
const { up, down } = storeMigrations.migrators[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)
|
|
expect(down(next)).toEqual(next)
|
|
})
|
|
})
|
|
|
|
describe('Adds NoteShape vertical alignment', () => {
|
|
const { up, down } = noteShapeMigrations.migrators[4]
|
|
|
|
test('up works as expected', () => {
|
|
expect(up({ props: { color: 'red' } })).toEqual({
|
|
props: { verticalAlign: 'middle', color: 'red' },
|
|
})
|
|
})
|
|
test('down works as expected', () => {
|
|
expect(down({ props: { verticalAlign: 'top', color: 'red' } })).toEqual({
|
|
props: { color: 'red' },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('hoist opacity', () => {
|
|
test('hoists opacity from a shape to another', () => {
|
|
const { up, down } = rootShapeMigrations.migrators[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',
|
|
},
|
|
}
|
|
const afterWithNonMatchingOpacity = {
|
|
type: 'myShape',
|
|
x: 0,
|
|
y: 0,
|
|
opacity: 0.6,
|
|
props: {
|
|
color: 'red',
|
|
},
|
|
}
|
|
|
|
expect(up(before)).toEqual(after)
|
|
expect(down(after)).toEqual(before)
|
|
expect(down(afterWithNonMatchingOpacity)).toEqual(before)
|
|
})
|
|
|
|
test('hoists opacity from propsForNextShape', () => {
|
|
const { up, down } = instanceMigrations.migrators[instanceTypeVersions.HoistOpacity]
|
|
const before = {
|
|
isToolLocked: true,
|
|
propsForNextShape: {
|
|
color: 'black',
|
|
opacity: '0.5',
|
|
},
|
|
}
|
|
const after = {
|
|
isToolLocked: true,
|
|
opacityForNextShape: 0.5,
|
|
propsForNextShape: {
|
|
color: 'black',
|
|
},
|
|
}
|
|
const afterWithNonMatchingOpacity = {
|
|
isToolLocked: true,
|
|
opacityForNextShape: 0.6,
|
|
propsForNextShape: {
|
|
color: 'black',
|
|
},
|
|
}
|
|
|
|
expect(up(before)).toEqual(after)
|
|
expect(down(after)).toEqual(before)
|
|
expect(down(afterWithNonMatchingOpacity)).toEqual(before)
|
|
})
|
|
})
|
|
|
|
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
|
|
|
for (const migrator of allMigrators) {
|
|
test(`[${migrator.fileName} v${migrator.version}] up and down migrations have both been tested`, () => {
|
|
expect(migrator.up).toHaveBeenCalled()
|
|
expect(migrator.down).toHaveBeenCalled()
|
|
})
|
|
}
|