fix: repair malformed editor block entries

This commit is contained in:
lucaronin
2026-04-29 05:27:39 +02:00
parent 4a62008b1f
commit f9b155ba6e
3 changed files with 61 additions and 2 deletions

View File

@@ -16,8 +16,17 @@ function hasUsableBlockId(block: Record<string, unknown>): boolean {
return typeof block.id === 'string' && block.id.trim().length > 0
}
function fallbackParagraphBlock(): Record<string, unknown> {
return {
id: createEditorBlockId(),
type: 'paragraph',
content: [],
children: [],
}
}
function repairEditorBlock(block: unknown): unknown {
if (!isEditorBlockRecord(block)) return block
if (!isEditorBlockRecord(block)) return fallbackParagraphBlock()
const children = Array.isArray(block.children)
? repairMalformedEditorBlocks(block.children)

View File

@@ -542,6 +542,55 @@ describe('useEditorTabSwap raw mode sync', () => {
])
})
it('replaces non-object malformed parsed blocks before applying them to the editor', async () => {
const tabA = makeTab('a.md', 'Note A')
const malformedTab = {
...makeTab('malformed.md', 'Malformed'),
content: '---\ntitle: Malformed\n---\n\n# Malformed\n\nRecovered body.',
}
const { mockEditor, rerenderWith } = await createSwapHarness({
initialProps: { tabs: [tabA], activeTabPath: 'a.md', rawMode: false },
setupEditor: (editor) => {
editor.tryParseMarkdownToBlocks.mockReturnValue([
null,
'dangling content',
{
type: 'paragraph',
content: [{ type: 'text', text: 'Parent', styles: {} }],
children: [
undefined,
{
type: 'paragraph',
content: [{ type: 'text', text: 'Child', styles: {} }],
children: [],
},
],
},
])
},
})
mockEditor.replaceBlocks.mockClear()
await rerenderWith({ tabs: [malformedTab], activeTabPath: 'malformed.md' })
const appliedBlocks = mockEditor.replaceBlocks.mock.calls[0][1]
expect(appliedBlocks).toEqual([
expect.objectContaining({ id: expect.any(String), type: 'paragraph', content: [], children: [] }),
expect.objectContaining({ id: expect.any(String), type: 'paragraph', content: [], children: [] }),
expect.objectContaining({
id: expect.any(String),
children: [
expect.objectContaining({ id: expect.any(String), type: 'paragraph', content: [], children: [] }),
expect.objectContaining({
id: expect.any(String),
content: [{ type: 'text', text: 'Child', styles: {} }],
}),
],
}),
])
})
it('ignores editor change events before the pending tab swap applies a new untitled note', async () => {
vi.spyOn(document, 'querySelector').mockReturnValue({ scrollTop: 0 } as unknown as Element)
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { cb(0); return 0 })

View File

@@ -174,8 +174,9 @@ async function resolveBlocksForTarget(
}
const parsed = normalizeParsedImageBlocks(await parseMarkdownBlocks(editor, preprocessed)) as EditorBlocks
const parseSafeBlocks = repairMalformedEditorBlocks(parsed) as EditorBlocks
const nextState = {
blocks: repairMalformedEditorBlocks(injectEditorMarkdownBlocks(parsed)) as EditorBlocks,
blocks: repairMalformedEditorBlocks(injectEditorMarkdownBlocks(parseSafeBlocks)) as EditorBlocks,
scrollTop: 0,
sourceContent: content,
}