From f0fb7b7418c17d593bbfe8b0814a184368c8e061 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Sat, 23 May 2026 16:11:20 +0200 Subject: [PATCH] fix: recover invalid insertion depth transforms --- ...torTransformErrorRecoveryExtension.test.ts | 53 +++++++++++-------- ...chEditorTransformErrorRecoveryExtension.ts | 10 +++- tests/smoke/mixed-rich-text-keypress.spec.ts | 2 + 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/components/richEditorTransformErrorRecoveryExtension.test.ts b/src/components/richEditorTransformErrorRecoveryExtension.test.ts index ff23ab51..116bc91a 100644 --- a/src/components/richEditorTransformErrorRecoveryExtension.test.ts +++ b/src/components/richEditorTransformErrorRecoveryExtension.test.ts @@ -32,6 +32,19 @@ function createView(error?: Error) { return { currentDoc, dispatch, view } } +function expectDocumentRepairRecovery(error: Error, reason: string) { + const { currentDoc, view } = createView(error) + const recoverDocument = vi.fn() + + installRichEditorTransformErrorRecovery(view, { recoverDocument }) + + expect(() => view.dispatch({ before: currentDoc })).not.toThrow() + expect(recoverDocument).toHaveBeenCalledTimes(1) + expect(trackEvent).toHaveBeenCalledWith('rich_editor_transform_error_recovered', { + reason, + }) +} + beforeEach(() => { vi.spyOn(console, 'warn').mockImplementation(() => {}) }) @@ -48,6 +61,9 @@ describe('isRecoverableEditorTransformError', () => { expect(isRecoverableEditorTransformError(new RangeError( 'Invalid content for node blockContainer: ', ))).toBe(true) + expect(isRecoverableEditorTransformError(new RangeError( + 'Inserted content deeper than insertion position', + ))).toBe(true) expect(isRecoverableEditorTransformError(new RangeError( 'Index 1 out of range for ', ))).toBe(true) @@ -84,35 +100,26 @@ describe('installRichEditorTransformErrorRecovery', () => { }) it('recovers invalid-content schema transactions from mixed paragraph and list editing', () => { - const schemaError = new RangeError( - 'Invalid content for node blockContainer: ', + expectDocumentRepairRecovery( + new RangeError( + 'Invalid content for node blockContainer: ', + ), + 'transform_error', ) - const { currentDoc, view } = createView(schemaError) - const recoverDocument = vi.fn() - - installRichEditorTransformErrorRecovery(view, { recoverDocument }) - - expect(() => view.dispatch({ before: currentDoc })).not.toThrow() - expect(recoverDocument).toHaveBeenCalledTimes(1) - expect(trackEvent).toHaveBeenCalledWith('rich_editor_transform_error_recovered', { - reason: 'transform_error', - }) }) it('recovers table selection transactions whose target row changed underneath BlockNote', () => { - const tableError = new RangeError( - 'Index 1 out of range for ', + expectDocumentRepairRecovery( + new RangeError('Index 1 out of range for '), + 'table_position_out_of_range', ) - const { currentDoc, view } = createView(tableError) - const recoverDocument = vi.fn() + }) - installRichEditorTransformErrorRecovery(view, { recoverDocument }) - - expect(() => view.dispatch({ before: currentDoc })).not.toThrow() - expect(recoverDocument).toHaveBeenCalledTimes(1) - expect(trackEvent).toHaveBeenCalledWith('rich_editor_transform_error_recovered', { - reason: 'table_position_out_of_range', - }) + it('recovers invalid insertion-depth transactions after note switching and saves', () => { + expectDocumentRepairRecovery( + new RangeError('Inserted content deeper than insertion position'), + 'invalid_insertion_depth', + ) }) it('keeps non-ProseMirror dispatch failures visible', () => { diff --git a/src/components/richEditorTransformErrorRecoveryExtension.ts b/src/components/richEditorTransformErrorRecoveryExtension.ts index a9069f1b..d92e2b5d 100644 --- a/src/components/richEditorTransformErrorRecoveryExtension.ts +++ b/src/components/richEditorTransformErrorRecoveryExtension.ts @@ -36,6 +36,7 @@ interface RepairableBlockNoteEditor { } type RecoveryReason = + | 'invalid_insertion_depth' | 'mismatched_transaction' | 'stale_transaction' | 'table_position_out_of_range' @@ -71,6 +72,10 @@ function isInvalidContentTransactionError(error: unknown): boolean { return error instanceof RangeError && error.message.startsWith('Invalid content for node ') } +function isInvalidInsertionDepthError(error: unknown): boolean { + return error instanceof RangeError && error.message.includes('Inserted content deeper than insertion position') +} + function isTablePositionOutOfRangeError(error: unknown): boolean { return error instanceof RangeError && /^Index \d+ out of range for