From ba41854f0e8022cb8db18e9e666785d467461378 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Sat, 2 May 2026 10:00:50 +0200 Subject: [PATCH] fix: ignore malformed reload entries --- src/utils/vaultMetadataNormalization.test.ts | 40 ++++++++++++++++ src/utils/vaultMetadataNormalization.ts | 9 +++- .../missing-string-metadata-open-note.spec.ts | 47 ++++++++++++++----- 3 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/utils/vaultMetadataNormalization.test.ts diff --git a/src/utils/vaultMetadataNormalization.test.ts b/src/utils/vaultMetadataNormalization.test.ts new file mode 100644 index 00000000..d865fe36 --- /dev/null +++ b/src/utils/vaultMetadataNormalization.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest' +import { normalizeVaultEntries } from './vaultMetadataNormalization' + +describe('normalizeVaultEntries', () => { + it('repairs missing string metadata when the entry path is present', () => { + const entries = normalizeVaultEntries([ + { + path: '/vault/alpha-project.md', + filename: undefined, + title: undefined, + aliases: undefined, + }, + ], '/vault') + + expect(entries).toHaveLength(1) + expect(entries[0]).toMatchObject({ + path: '/vault/alpha-project.md', + filename: 'alpha-project.md', + title: 'alpha-project', + aliases: [], + }) + }) + + it('drops malformed reload entries that do not include a usable path', () => { + const entries = normalizeVaultEntries([ + { path: '/vault/valid.md', filename: 'valid.md', title: 'Valid' }, + { filename: 'missing-path.md', title: 'Missing Path' }, + { path: '', filename: 'empty-path.md', title: 'Empty Path' }, + { path: 42, filename: 'numeric-path.md', title: 'Numeric Path' }, + null, + ], '/vault') + + expect(entries).toHaveLength(1) + expect(entries[0]).toMatchObject({ + path: '/vault/valid.md', + filename: 'valid.md', + title: 'Valid', + }) + }) +}) diff --git a/src/utils/vaultMetadataNormalization.ts b/src/utils/vaultMetadataNormalization.ts index 7f688c56..5d7f31c3 100644 --- a/src/utils/vaultMetadataNormalization.ts +++ b/src/utils/vaultMetadataNormalization.ts @@ -62,6 +62,11 @@ function stringArrayFrom(value: unknown): string[] { return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [] } +function hasUsablePath(rawEntry: unknown): boolean { + const source = recordFrom(rawEntry) + return typeof source.path === 'string' && source.path.trim().length > 0 +} + function filenameFromPath(path: string): string { const normalizedPath = path.replace(/\\/g, '/') return normalizedPath.split('/').filter(Boolean).pop() ?? '' @@ -209,7 +214,9 @@ function normalizeViewFile({ rawView, index }: ViewNormalizationArgs): ViewFile export function normalizeVaultEntries(rawEntries: unknown, vaultPath: string): VaultEntry[] { if (!Array.isArray(rawEntries)) return [] - return rawEntries.map((rawEntry, index) => normalizeVaultEntry(rawEntry, vaultPath, index)) + return rawEntries + .filter(hasUsablePath) + .map((rawEntry, index) => normalizeVaultEntry(rawEntry, vaultPath, index)) } export function normalizeVaultEntry(rawEntry: unknown, vaultPath = '', index = 0): VaultEntry { diff --git a/tests/smoke/missing-string-metadata-open-note.spec.ts b/tests/smoke/missing-string-metadata-open-note.spec.ts index 8c7ef6ac..27b70726 100644 --- a/tests/smoke/missing-string-metadata-open-note.spec.ts +++ b/tests/smoke/missing-string-metadata-open-note.spec.ts @@ -1,4 +1,4 @@ -import { test, expect, type Page } from '@playwright/test' +import { test, expect, type Locator, type Page } from '@playwright/test' import { createFixtureVaultCopy, openFixtureVaultDesktopHarness, @@ -47,12 +47,38 @@ function removeAlphaProjectStringMetadata(entries: Array }) } +function appendMalformedReloadEntry(entries: Array>) { + return entries.concat({ + filename: 'phantom-from-reload.md', + title: 'Phantom From Reload', + aliases: [], + outgoingLinks: [], + relationships: {}, + properties: {}, + snippet: '', + }) +} + 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() @@ -64,9 +90,12 @@ test.beforeEach(async ({ page }, testInfo) => { } const response = await route.fetch() const entries = await response.json() as Array> + const scrubbedEntries = removeAlphaProjectStringMetadata(entries) await route.fulfill({ response, - json: removeAlphaProjectStringMetadata(entries), + json: requestUrl.searchParams.get('reload') === '1' + ? appendMalformedReloadEntry(scrubbedEntries) + : scrubbedEntries, }) }) await openFixtureVaultDesktopHarness(page, tempVaultDir, { @@ -83,12 +112,9 @@ test('@smoke note open tolerates missing string metadata from the vault scan', a const errors = collectMissingMetadataCrashes(page) const noteList = page.getByTestId('note-list-container') - await noteList.getByText('alpha-project', { exact: true }).click() - await expect(page.getByRole('heading', { name: 'Alpha Project', level: 1 })).toBeVisible({ timeout: 5_000 }) - - await noteList.getByText('Note B', { exact: true }).click() - await noteList.getByText('alpha-project', { exact: true }).click() - await expect(page.getByRole('heading', { name: 'Alpha Project', level: 1 })).toBeVisible({ timeout: 5_000 }) + await openNoteFromList(noteList, 'alpha-project') + await expectAlphaProjectHeading(page) + await switchFromNoteBBackToAlpha(page, noteList) expect(errors).toHaveLength(0) }) @@ -99,9 +125,8 @@ test('note open after vault reload tolerates missing suggestion metadata', async await reloadVaultFromCommandPalette(page) - await noteList.getByText('Note B', { exact: true }).click() - await noteList.getByText('alpha-project', { exact: true }).click() - await expect(page.getByRole('heading', { name: 'Alpha Project', level: 1 })).toBeVisible({ timeout: 5_000 }) + await expect(noteList.getByText('Phantom From Reload', { exact: true })).toHaveCount(0) + await switchFromNoteBBackToAlpha(page, noteList) expect(errors).toHaveLength(0) })