hololake-platform/tests/smoke/autosave-low-end-typing.spec.ts

105 lines
3.5 KiB
TypeScript

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)
})