Merge branch 'main' into pr-736
This commit is contained in:
@@ -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: <paragraph("Procedures are long-running"), blockGroup(blockContainer(bulletListItem("Step")))>',
|
||||
))).toBe(true)
|
||||
expect(isRecoverableEditorTransformError(new RangeError(
|
||||
'Inserted content deeper than insertion position',
|
||||
))).toBe(true)
|
||||
expect(isRecoverableEditorTransformError(new RangeError(
|
||||
'Index 1 out of range for <tableRow(tableCell(tableParagraph("A")))>',
|
||||
))).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: <paragraph("Procedures are long-running"), blockGroup(blockContainer(bulletListItem("Step")))>',
|
||||
expectDocumentRepairRecovery(
|
||||
new RangeError(
|
||||
'Invalid content for node blockContainer: <paragraph("Procedures are long-running"), blockGroup(blockContainer(bulletListItem("Step")))>',
|
||||
),
|
||||
'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 <tableRow(tableCell(tableParagraph("A")))>',
|
||||
expectDocumentRepairRecovery(
|
||||
new RangeError('Index 1 out of range for <tableRow(tableCell(tableParagraph("A")))>'),
|
||||
'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', () => {
|
||||
|
||||
@@ -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 <tableRow\(/.test(error.message)
|
||||
}
|
||||
@@ -80,7 +85,9 @@ function isTransformError(error: unknown): boolean {
|
||||
}
|
||||
|
||||
function isRecoverableRangeError(error: unknown): boolean {
|
||||
return isInvalidContentTransactionError(error) || isTablePositionOutOfRangeError(error)
|
||||
return isInvalidContentTransactionError(error)
|
||||
|| isInvalidInsertionDepthError(error)
|
||||
|| isTablePositionOutOfRangeError(error)
|
||||
}
|
||||
|
||||
export function isRecoverableEditorTransformError(error: unknown): boolean {
|
||||
@@ -94,6 +101,7 @@ function recoveryReason(
|
||||
): RecoveryReason {
|
||||
if (transactionDocIsStale(transaction, view)) return 'stale_transaction'
|
||||
if (isMismatchedTransactionError(error)) return 'mismatched_transaction'
|
||||
if (isInvalidInsertionDepthError(error)) return 'invalid_insertion_depth'
|
||||
if (isTablePositionOutOfRangeError(error)) return 'table_position_out_of_range'
|
||||
return 'transform_error'
|
||||
}
|
||||
|
||||
@@ -154,12 +154,14 @@ test('mixed rich-text blocks with Korean list content stay editable after action
|
||||
await expect(bulletBlock).toBeVisible({ timeout: 5_000 })
|
||||
|
||||
await page.locator('.bn-block-content', { hasText: '인용문' }).first().click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.insertText(' 추가')
|
||||
await bulletBlock.hover()
|
||||
await expect(page.locator('.bn-side-menu').first()).toBeVisible({ timeout: 5_000 })
|
||||
await page.locator('.bn-side-menu').first().click({ force: true })
|
||||
await page.keyboard.press('Escape')
|
||||
await bulletBlock.click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.insertText(' 계속')
|
||||
await page.waitForTimeout(700)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user