import { test, expect, type Locator, type Page } from '@playwright/test' import { createFixtureVaultCopy, openFixtureVaultDesktopHarness, removeFixtureVaultCopy, } from '../helpers/fixtureVault' import { executeCommand, openCommandPalette } from './helpers' let tempVaultDir: string function isMissingStringMetadataCrash(message: string): boolean { return ( message.includes("Cannot read properties of undefined (reading 'replace')") || message.includes('undefined is not an object') || /undefined.*\.replace|\.replace.*undefined/.test(message) ) } function collectMissingMetadataCrashes(page: Page): string[] { const errors: string[] = [] page.on('pageerror', (error) => { if (isMissingStringMetadataCrash(error.message)) errors.push(error.message) }) page.on('console', (message) => { if (message.type() === 'error' && isMissingStringMetadataCrash(message.text())) { errors.push(message.text()) } }) return errors } function removeAlphaProjectStringMetadata(entries: Array>) { return entries.map((entry) => { const entryPath = typeof entry.path === 'string' ? entry.path : '' const title = typeof entry.title === 'string' ? entry.title : '' if (title !== 'Alpha Project' && !entryPath.endsWith('/alpha-project.md')) return entry return { ...entry, title: undefined, filename: undefined, aliases: undefined, outgoingLinks: undefined, relationships: undefined, properties: undefined, snippet: undefined, } }) } function appendMalformedReloadEntry(entries: Array>) { return entries.concat({ filename: 'phantom-from-reload.md', title: 'Phantom From Reload', aliases: [], outgoingLinks: [], relationships: {}, properties: {}, snippet: '', }) } function readRouteJsonBody(route: { request: () => { postDataJSON: () => unknown } }): Record { try { const body = route.request().postDataJSON() return body && typeof body === 'object' && !Array.isArray(body) ? body as Record : {} } catch { return {} } } async function reloadVaultFromCommandPalette(page: Page): Promise { await openCommandPalette(page) await executeCommand(page, 'Reload Vault') await expect(page.locator('input[placeholder="Type a command..."]')).not.toBeVisible() } async function openNoteFromList(noteList: Locator, title: string): Promise { await noteList.getByText(title, { exact: true }).click() } async function expectAlphaProjectHeading(page: Page): Promise { await expect(page.getByRole('heading', { name: 'Alpha Project', level: 1 })).toBeVisible({ timeout: 5_000 }) } async function switchFromNoteBBackToAlpha(page: Page, noteList: Locator): Promise { await openNoteFromList(noteList, 'Note B') await openNoteFromList(noteList, 'alpha-project') await expectAlphaProjectHeading(page) } test.beforeEach(async ({ page }, testInfo) => { testInfo.setTimeout(60_000) tempVaultDir = createFixtureVaultCopy() await page.route('**/*', async (route) => { const requestUrl = new URL(route.request().url()) if (!requestUrl.pathname.endsWith('/api/vault/list')) { await route.continue() return } const response = await route.fetch() const entries = await response.json() as Array> const scrubbedEntries = removeAlphaProjectStringMetadata(entries) const body = readRouteJsonBody(route) const isReload = requestUrl.searchParams.get('reload') === '1' || body.reload === true await route.fulfill({ response, json: isReload ? appendMalformedReloadEntry(scrubbedEntries) : scrubbedEntries, }) }) await openFixtureVaultDesktopHarness(page, tempVaultDir, { expectedReadyTitle: 'alpha-project', }) await page.setViewportSize({ width: 1180, height: 760 }) }) test.afterEach(() => { removeFixtureVaultCopy(tempVaultDir) }) test('@smoke note open tolerates missing string metadata from the vault scan', async ({ page }) => { const errors = collectMissingMetadataCrashes(page) const noteList = page.getByTestId('note-list-container') await openNoteFromList(noteList, 'alpha-project') await expectAlphaProjectHeading(page) await switchFromNoteBBackToAlpha(page, noteList) expect(errors).toHaveLength(0) }) test('@smoke note open after vault reload tolerates missing suggestion metadata', async ({ page }) => { const errors = collectMissingMetadataCrashes(page) const noteList = page.getByTestId('note-list-container') await reloadVaultFromCommandPalette(page) await expect(noteList.getByText('Phantom From Reload', { exact: true })).toHaveCount(0) await switchFromNoteBBackToAlpha(page, noteList) expect(errors).toHaveLength(0) })