fix: normalize stale vault entry metadata
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
"playwright:smoke": "playwright test --config playwright.smoke.config.ts tests/smoke/create-note-backing-file.spec.ts tests/smoke/delete-note-nonblocking.spec.ts tests/smoke/example.spec.ts tests/smoke/fix-ai-chat-empty-body-v3.spec.ts tests/smoke/fix-crash-create-note.spec.ts tests/smoke/fresh-start-telemetry-onboarding.spec.ts tests/smoke/frontmatter-date-picker.spec.ts tests/smoke/getting-started-template.spec.ts tests/smoke/h1-title-decoupled.spec.ts tests/smoke/h1-untitled-auto-rename.spec.ts tests/smoke/keyboard-command-routing.spec.ts tests/smoke/linkify-init-warnings.spec.ts tests/smoke/multi-selection-shortcuts.spec.ts tests/smoke/wikilink-path-fix.spec.ts",
|
||||
"playwright:smoke": "playwright test --config playwright.smoke.config.ts tests/smoke/create-note-backing-file.spec.ts tests/smoke/delete-note-nonblocking.spec.ts tests/smoke/example.spec.ts tests/smoke/fix-ai-chat-empty-body-v3.spec.ts tests/smoke/fix-crash-create-note.spec.ts tests/smoke/fresh-start-telemetry-onboarding.spec.ts tests/smoke/frontmatter-date-picker.spec.ts tests/smoke/getting-started-template.spec.ts tests/smoke/h1-title-decoupled.spec.ts tests/smoke/h1-untitled-auto-rename.spec.ts tests/smoke/keyboard-command-routing.spec.ts tests/smoke/linkify-init-warnings.spec.ts tests/smoke/missing-string-metadata-open-note.spec.ts tests/smoke/multi-selection-shortcuts.spec.ts tests/smoke/wikilink-path-fix.spec.ts",
|
||||
"playwright:regression": "playwright test tests/smoke/",
|
||||
"playwright:integration": "playwright test --config playwright.integration.config.ts",
|
||||
"test:coverage": "node scripts/run-vitest-coverage.mjs",
|
||||
|
||||
@@ -395,6 +395,32 @@ describe('useVaultLoader', () => {
|
||||
|
||||
expect(result.current.entries).toBe(entriesBefore)
|
||||
})
|
||||
|
||||
it('keeps entry metadata safe when a stale reload patch has undefined fields', async () => {
|
||||
const { result } = await renderVaultLoader()
|
||||
|
||||
act(() => {
|
||||
result.current.updateEntry('/vault/note/hello.md', {
|
||||
title: undefined,
|
||||
filename: undefined,
|
||||
aliases: undefined,
|
||||
outgoingLinks: undefined,
|
||||
relationships: undefined,
|
||||
properties: undefined,
|
||||
snippet: undefined,
|
||||
} as unknown as Partial<VaultEntry>)
|
||||
})
|
||||
|
||||
expect(result.current.entries[0]).toEqual(expect.objectContaining({
|
||||
title: 'hello',
|
||||
filename: 'hello.md',
|
||||
aliases: [],
|
||||
outgoingLinks: [],
|
||||
relationships: {},
|
||||
properties: {},
|
||||
snippet: '',
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNoteStatus', () => {
|
||||
@@ -754,6 +780,34 @@ describe('useVaultLoader', () => {
|
||||
title: 'Renamed',
|
||||
}))
|
||||
})
|
||||
|
||||
it('normalizes stale replacement metadata during reload-heavy note switching', async () => {
|
||||
const { result } = await renderVaultLoader()
|
||||
|
||||
act(() => {
|
||||
result.current.replaceEntry('/vault/note/hello.md', {
|
||||
path: '/vault/note/reloaded.md',
|
||||
title: undefined,
|
||||
filename: undefined,
|
||||
aliases: undefined,
|
||||
outgoingLinks: undefined,
|
||||
relationships: undefined,
|
||||
properties: undefined,
|
||||
snippet: undefined,
|
||||
} as unknown as Partial<VaultEntry> & { path: string })
|
||||
})
|
||||
|
||||
expect(result.current.entries[0]).toEqual(expect.objectContaining({
|
||||
path: '/vault/note/reloaded.md',
|
||||
filename: 'reloaded.md',
|
||||
title: 'reloaded',
|
||||
aliases: [],
|
||||
outgoingLinks: [],
|
||||
relationships: {},
|
||||
properties: {},
|
||||
snippet: '',
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('reloadVault', () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
reloadVaultEntries,
|
||||
tauriCall,
|
||||
} from './vaultLoaderCommands'
|
||||
import { normalizeVaultEntry } from '../utils/vaultMetadataNormalization'
|
||||
|
||||
function resetVaultState(options: {
|
||||
clearNewPaths: () => void
|
||||
@@ -273,19 +274,23 @@ function useEntryMutations(
|
||||
trackNew: (path: string) => void,
|
||||
) {
|
||||
const addEntry = useCallback((entry: VaultEntry) => {
|
||||
const normalizedEntry = normalizeVaultEntry(entry)
|
||||
setEntries((prev) => {
|
||||
if (prev.some(e => e.path === entry.path)) return prev
|
||||
return [entry, ...prev]
|
||||
if (prev.some(e => e.path === normalizedEntry.path)) return prev
|
||||
return [normalizedEntry, ...prev]
|
||||
})
|
||||
trackNew(entry.path)
|
||||
trackNew(normalizedEntry.path)
|
||||
}, [setEntries, trackNew])
|
||||
|
||||
const updateEntry = useCallback((path: string, patch: Partial<VaultEntry>) => {
|
||||
setEntries((prev) => {
|
||||
let changed = false
|
||||
const next = prev.map((e) => {
|
||||
if (e.path === path) { changed = true; return { ...e, ...patch } }
|
||||
return e
|
||||
const next = prev.map((entry, index) => {
|
||||
if (entry.path === path) {
|
||||
changed = true
|
||||
return normalizeVaultEntry({ ...entry, ...patch }, '', index)
|
||||
}
|
||||
return entry
|
||||
})
|
||||
return changed ? next : prev
|
||||
})
|
||||
@@ -302,7 +307,9 @@ function useEntryMutations(
|
||||
}, [setEntries])
|
||||
|
||||
const replaceEntry = useCallback((oldPath: string, patch: Partial<VaultEntry> & { path: string }) => {
|
||||
setEntries((prev) => prev.map((e) => e.path === oldPath ? { ...e, ...patch } : e))
|
||||
setEntries((prev) => prev.map((entry, index) =>
|
||||
entry.path === oldPath ? normalizeVaultEntry({ ...entry, ...patch }, '', index) : entry,
|
||||
))
|
||||
}, [setEntries])
|
||||
|
||||
return { addEntry, updateEntry, removeEntry, removeEntries, replaceEntry }
|
||||
|
||||
@@ -124,7 +124,7 @@ function fallbackViewName(filename: string, index: number): string {
|
||||
return stem && stem !== `view-${index + 1}` ? stem : `View ${index + 1}`
|
||||
}
|
||||
|
||||
function normalizeVaultEntry({ rawEntry, vaultPath, index }: EntryNormalizationArgs): VaultEntry {
|
||||
function normalizeVaultEntryRecord({ rawEntry, vaultPath, index }: EntryNormalizationArgs): VaultEntry {
|
||||
const source = recordFrom(rawEntry)
|
||||
const filename = fallbackEntryFilename(source, index)
|
||||
const path = resolveEntryPath({
|
||||
@@ -209,7 +209,11 @@ 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.map((rawEntry, index) => normalizeVaultEntry(rawEntry, vaultPath, index))
|
||||
}
|
||||
|
||||
export function normalizeVaultEntry(rawEntry: unknown, vaultPath = '', index = 0): VaultEntry {
|
||||
return normalizeVaultEntryRecord({ rawEntry, vaultPath, index })
|
||||
}
|
||||
|
||||
export function normalizeViewFiles(rawViews: unknown): ViewFile[] {
|
||||
|
||||
Reference in New Issue
Block a user