openpencil/tests/engine/app/document/autosave/create.test.ts
Danila Poyarkov e7c408a28f
perf(app): coalesce overlapping autosaves (#531)
- Serialize writable-document autosaves and retain only the newest trailing version
- Preserve saves requested while export or persistence is in flight
- Cover file, storage, retry, and recovery scheduling invariants
2026-08-15 12:23:01 +03:00

125 lines
3.3 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { reactive } from 'vue'
import { createDefaultEditorState } from '@open-pencil/core/editor'
import { createAutosave } from '@/app/document/autosave/create'
function deferred() {
let resolve: (() => void) | null = null
const promise = new Promise<void>((done) => {
resolve = done
})
return { promise, resolve: () => resolve?.() }
}
function setup(saveCurrentDocument: (version: number) => Promise<void>) {
const state = reactive({
...createDefaultEditorState('page-1'),
autosaveEnabled: true
})
let savedVersion = 0
let writable = true
const autosave = createAutosave({
state,
getSavedVersion: () => savedVersion,
hasWritableSource: () => writable,
saveCurrentDocument: async (version) => {
await saveCurrentDocument(version)
savedVersion = version
}
})
return {
state,
autosave,
setWritable: (value: boolean) => {
writable = value
}
}
}
describe('document autosave', () => {
test('skips saved versions and documents without writable sources', async () => {
const versions: number[] = []
const { state, autosave, setWritable } = setup(async (version) => {
versions.push(version)
})
await autosave.requestSave(0)
setWritable(false)
state.sceneVersion = 1
await autosave.requestSave(1)
expect(versions).toEqual([])
setWritable(true)
await autosave.requestSave(1)
state.sceneVersion = 0
await autosave.requestSave(0)
await autosave.requestSave(1)
expect(versions).toEqual([1])
autosave.disposeAutosave()
})
test('coalesces 100 edits during an in-flight save into the latest version', async () => {
const firstSave = deferred()
const started: number[] = []
const { state, autosave } = setup(async (version) => {
started.push(version)
if (started.length === 1) await firstSave.promise
})
state.sceneVersion = 1
const pending = autosave.requestSave(1)
await Promise.resolve()
for (let version = 2; version <= 101; version++) {
state.sceneVersion = version
void autosave.requestSave(version)
}
firstSave.resolve()
await pending
expect(started).toEqual([1, 101])
autosave.disposeAutosave()
})
test('retries the current version after a failed save', async () => {
let attempts = 0
const { state, autosave } = setup(async () => {
attempts++
if (attempts === 1) throw new Error('write failed')
})
state.sceneVersion = 1
await expect(autosave.requestSave(1)).rejects.toThrow('write failed')
await autosave.requestSave(1)
expect(attempts).toBe(2)
autosave.disposeAutosave()
})
test('preserves a newer requested version when the active save fails', async () => {
const firstSave = deferred()
const started: number[] = []
const { state, autosave } = setup(async (version) => {
started.push(version)
if (version === 1) {
await firstSave.promise
throw new Error('write failed')
}
})
state.sceneVersion = 1
const failed = autosave.requestSave(1)
await Promise.resolve()
state.sceneVersion = 2
void autosave.requestSave(2)
firstSave.resolve()
await expect(failed).rejects.toThrow('write failed')
await Promise.resolve()
expect(started).toEqual([1, 2])
autosave.disposeAutosave()
})
})