@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
242 lines (210 loc) • 5.5 kB
text/typescript
import { createTLSchema } from '@tldraw/tlschema'
import { openDB } from 'idb'
import { hardReset } from './hardReset'
import { getAllIndexDbNames, LocalIndexedDb } from './LocalIndexedDb'
const schema = createTLSchema({ shapes: {}, bindings: {} })
describe('LocalIndexedDb', () => {
beforeEach(() => {
jest.useRealTimers()
})
afterEach(async () => {
await hardReset({ shouldReload: false })
})
describe('#storeSnapshot', () => {
it("creates documents if they don't exist", async () => {
const db = new LocalIndexedDb('test-0')
await db.storeSnapshot({
schema,
snapshot: {},
})
expect(getAllIndexDbNames()).toEqual(['TLDRAW_DOCUMENT_v2test-0'])
const db2 = new LocalIndexedDb('test-1')
await db2.storeSnapshot({
schema,
snapshot: {},
})
expect(getAllIndexDbNames()).toEqual(['TLDRAW_DOCUMENT_v2test-0', 'TLDRAW_DOCUMENT_v2test-1'])
await db2.storeSnapshot({
schema,
snapshot: {},
})
expect(getAllIndexDbNames()).toEqual(['TLDRAW_DOCUMENT_v2test-0', 'TLDRAW_DOCUMENT_v2test-1'])
await db2.close()
})
it('allows reading back the snapshot', async () => {
const db = new LocalIndexedDb('test-0')
await db.storeSnapshot({
schema,
snapshot: {
'shape:1': {
id: 'shape:1',
type: 'rectangle',
},
'page:1': {
id: 'page:1',
name: 'steve',
},
},
})
expect(getAllIndexDbNames()).toEqual(['TLDRAW_DOCUMENT_v2test-0'])
const records = (await db.load())?.records
expect(records).toEqual([
{ id: 'page:1', name: 'steve' },
{ id: 'shape:1', type: 'rectangle' },
])
})
it('allows storing a session under a particular ID and reading it back', async () => {
const db = new LocalIndexedDb('test-0')
const snapshot = {
'shape:1': {
id: 'shape:1',
type: 'rectangle',
},
}
await db.storeSnapshot({
sessionId: 'session-0',
schema,
snapshot,
sessionStateSnapshot: {
foo: 'bar',
} as any,
})
expect((await db.load({ sessionId: 'session-0' }))?.sessionStateSnapshot).toEqual({
foo: 'bar',
})
await db.storeSnapshot({
sessionId: 'session-1',
schema,
snapshot,
sessionStateSnapshot: {
hello: 'world',
} as any,
})
expect((await db.load({ sessionId: 'session-0' }))?.sessionStateSnapshot).toEqual({
foo: 'bar',
})
expect((await db.load({ sessionId: 'session-1' }))?.sessionStateSnapshot).toEqual({
hello: 'world',
})
})
})
describe('#storeChanges', () => {
it('allows merging changes into an existing store', async () => {
const db = new LocalIndexedDb('test-0')
await db.storeSnapshot({
schema,
snapshot: {
'shape:1': {
id: 'shape:1',
version: 0,
},
'page:1': {
id: 'page:1',
version: 0,
},
'asset:1': {
id: 'asset:1',
version: 0,
},
},
})
await db.storeChanges({
schema,
changes: {
added: {
'asset:2': {
id: 'asset:2',
version: 0,
},
},
updated: {
'page:1': [
{
id: 'page:1',
version: 0,
},
{
id: 'page:1',
version: 1,
},
],
},
removed: {
'shape:1': {
id: 'shape:1',
version: 0,
},
},
},
})
expect((await db.load())?.records).toEqual([
{
id: 'asset:1',
version: 0,
},
{
id: 'asset:2',
version: 0,
},
{
id: 'page:1',
version: 1,
},
])
})
})
it('migrates legacy asset storage into the new format', async () => {
const legacyAssetsDb = await openDB('TLDRAW_ASSET_STORE_v1test-0', 1, {
upgrade(database) {
if (!database.objectStoreNames.contains('assets')) {
database.createObjectStore('assets')
}
},
})
const file = { contents: 'hello, world!' } // new File(['Hello, world!'], 'hello.txt', { type: 'text/plain' })
const fileId = `asset:file`
{
const tx = legacyAssetsDb.transaction(['assets'], 'readwrite')
const store = tx.objectStore('assets')
await store.put(file, fileId)
await tx.done
}
legacyAssetsDb.close()
const mainDb = await openDB('TLDRAW_DOCUMENT_v2test-0', 3, {
upgrade(database) {
if (!database.objectStoreNames.contains('records')) {
database.createObjectStore('records')
}
if (!database.objectStoreNames.contains('schema')) {
database.createObjectStore('schema')
}
if (!database.objectStoreNames.contains('session_state')) {
database.createObjectStore('session_state')
}
},
})
{
const tx = mainDb.transaction(['records'], 'readwrite')
const store = tx.objectStore('records')
await store.put({ id: fileId, props: { src: fileId } }, fileId)
await tx.done
}
mainDb.close()
// before migration the legacy database should exist:
expect(
(await window.indexedDB.databases()).find((db) => db.name === 'TLDRAW_ASSET_STORE_v1test-0')
).toBeDefined()
// migration happens transparently:
const db = new LocalIndexedDb('test-0')
const snapshot = await db.load()
// records should be unaffected:
expect(snapshot?.records).toEqual([{ id: fileId, props: { src: fileId } }])
// assets should be accessible:
expect(await db.getAsset(fileId)).toEqual(file)
await db.close()
// the old asset DB should have been removed:
expect(
(await window.indexedDB.databases()).find((db) => db.name === 'TLDRAW_ASSET_STORE_v1test-0')
).toBeUndefined()
})
})