openpencil/tests/e2e/autosave.spec.ts
Danila Poyarkov 631fc25ee6
fix(app): retain recovery after closing unsaved tabs (#505)
* fix(app): retain recovery after closing unsaved tabs

- Persist source-less tab snapshots before disposing editors

- Keep retained snapshots available for startup restore or explicit discard

- Cover close, reload, and restore behavior in Playwright

* fix(app): surface recovery persistence failures

- Propagate explicit close and reload snapshot failures

- Keep background debounce failures logged without unhandled rejections

- Exercise close-time persistence in recovery coverage

* test(app): cover recovery persistence retry

* docs: restore recovery retention note
2026-08-13 21:39:35 +03:00

93 lines
2.8 KiB
TypeScript

import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { CanvasHelper } from '#tests/helpers/canvas'
const editor = useEditorSetup()
test('autosave triggers after scene changes with a file handle', async () => {
const writeCount = await editor.page.evaluate(() => {
let writes = 0
const mockWritable = {
write: async () => {
writes++
},
close: async () => undefined
}
const mockHandle = {
createWritable: async () => mockWritable
} as FileSystemFileHandle
window.openPencil ??= {}
window.openPencil.test = { writeCount: () => writes, mockHandle }
return writes
})
expect(writeCount).toBe(0)
// Inject the file handle into the store's internal state
// We do this by calling a save first to establish the handle
await editor.page.evaluate(() => {
// Directly set the fileHandle via a test hook
// Since fileHandle is a closure variable, we need to trigger the save path
// The cleanest way: mock showSaveFilePicker to return our handle
const mockWritable = {
write: async () => undefined,
close: async () => undefined
}
const mockHandle = {
createWritable: async () => mockWritable
}
window.showSaveFilePicker = async () => mockHandle as FileSystemFileHandle
})
// Trigger Save As to establish the file handle
await editor.page.keyboard.press('Meta+Shift+s')
await editor.page.waitForTimeout(500)
// Now draw a shape — this should trigger autosave after 3s
await editor.canvas.drawRect(400, 400, 60, 60)
// Check that the scene version changed
const versionAfterDraw = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.state.sceneVersion
})
expect(versionAfterDraw).toBeGreaterThan(0)
// Wait for autosave debounce (3s) + buffer
await editor.page.waitForTimeout(4000)
// Verify a write happened by checking the mock was called
const writeHappened = await editor.page.evaluate(() => {
// The handle's createWritable should have been called
const handle = window.showSaveFilePicker
return handle !== undefined
})
expect(writeHappened).toBe(true)
})
test('no autosave without file handle', async ({ browser, baseURL }) => {
const context = await browser.newContext({
baseURL,
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 2
})
const freshPage = await context.newPage()
await freshPage.goto('/')
const freshCanvas = new CanvasHelper(freshPage)
await freshCanvas.waitForInit()
await freshPage.evaluate(() => {
Reflect.deleteProperty(window, 'showSaveFilePicker')
})
await freshCanvas.drawRect(100, 100, 50, 50)
await freshPage.waitForTimeout(4000)
freshCanvas.assertNoErrors()
await freshPage.close()
await context.close()
})