import fs from 'fs' import path from 'path' import { test, expect, type Page } from '@playwright/test' import { createFixtureVaultCopy, openFixtureVault, removeFixtureVaultCopy, } from '../helpers/fixtureVault' import { executeCommand, openCommandPalette } from './helpers' interface AutosaveProbeWindow { __autosaveProbe?: Array<{ path: string; content: string }> } let tempVaultDir: string async function openNote(page: Page, title: string) { await page.getByTestId('note-list-container').getByText(title, { exact: true }).click() } async function openRawMode(page: Page) { await openCommandPalette(page) await executeCommand(page, 'Toggle Raw') await expect(page.locator('.cm-content')).toBeVisible({ timeout: 5_000 }) } async function setRawEditorContent(page: Page, content: string) { await page.evaluate((nextContent) => { const el = document.querySelector('.cm-content') if (!el) throw new Error('CodeMirror content element is missing') // eslint-disable-next-line @typescript-eslint/no-explicit-any const view = (el as any).cmTile?.view if (!view) throw new Error('CodeMirror view is missing') view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: nextContent }, }) }, content) } async function installAutosaveProbe(page: Page) { await page.evaluate(() => { const probeWindow = window as typeof window & AutosaveProbeWindow const nativeFetch = window.fetch.bind(window) const calls: Array<{ path: string; content: string }> = [] probeWindow.__autosaveProbe = calls window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { const requestUrl = typeof input === 'string' ? input : input instanceof Request ? input.url : input.toString() if (requestUrl.endsWith('/api/vault/save')) { const bodyText = init?.body === undefined && input instanceof Request ? await input.clone().text() : String(init?.body ?? '') const body = JSON.parse(bodyText) as { path?: unknown; content?: unknown } calls.push({ path: String(body.path ?? ''), content: String(body.content ?? ''), }) } return nativeFetch(input, init) } }) } async function readAutosaveProbe(page: Page) { return page.evaluate(() => { const probeWindow = window as typeof window & AutosaveProbeWindow return probeWindow.__autosaveProbe ?? [] }) } test.beforeEach(async ({ page }, testInfo) => { testInfo.setTimeout(60_000) tempVaultDir = createFixtureVaultCopy() await openFixtureVault(page, tempVaultDir) await installAutosaveProbe(page) }) test.afterEach(() => { removeFixtureVaultCopy(tempVaultDir) }) test('@smoke autosave waits for idle typing and persists the latest draft only', async ({ page }) => { const notePath = path.join(tempVaultDir, 'note', 'note-b.md') const firstDraft = `# Note B\n\nLow-end autosave first draft ${Date.now()}` const latestDraft = `${firstDraft}\n\nLatest draft after continued typing` await openNote(page, 'Note B') await openRawMode(page) await setRawEditorContent(page, firstDraft) await page.waitForTimeout(900) await setRawEditorContent(page, latestDraft) await page.waitForTimeout(450) expect(await readAutosaveProbe(page)).toEqual([]) await expect.poll(() => readAutosaveProbe(page), { timeout: 5_000 }).toEqual([ expect.objectContaining({ path: notePath, content: latestDraft }), ]) expect(fs.readFileSync(notePath, 'utf8')).toBe(latestDraft) })