// Top-level block reuse (Phase 16.2 D-03): after an edit, every top-level // block the edit did not touch is emitted as its original source bytes, // with the white space and comments between blocks, whatever the edit was // (one changed block, a deletion, an insertion). import { afterEach, describe, expect, it } from 'vitest' import { getSchema } from '@tiptap/core' import { preservationExtensions } from '../../assets/src/blogeditor/extensions' import { captureBlocks } from '../../assets/src/blogeditor/serializer' import { insertAtBlock, mount } from './helpers' afterEach(() => { document.body.replaceChildren() }) // Source formatting the schema serializer would not reproduce: upper-case // tags, single quotes, unquoted and reordered attributes, entities, // self-closing void tags and odd white space. const blocks = [ "
Pierwszy akapit & więcej
", 'Drugi link
', 'Trzeci
wiersz
XDrugi') expect(value.endsWith('\n\n')).toBe(true) // Only the edited block differs from the source. expect(value.replace(/
XDrugi[^]*?<\/p>/, blocks[2]!)).toBe(source) }) it('keeps the remaining blocks after a deletion', () => { const { el, editor } = mount(source) let from = 0 let to = 0 editor.state.doc.forEach((node, offset, index) => { if (index === 1) { from = offset to = offset + node.nodeSize } }) editor.view.dispatch(editor.state.tr.delete(from, to)) const value = el.value expect(value).not.toContain('Wstęp') for (const [i, block] of blocks.entries()) { if (i !== 1) expect(value).toContain(block) } expect(value.startsWith(blocks[0]!)).toBe(true) }) it('keeps every block around an insertion and serializes only the new one', () => { const { el, editor } = mount(source) let at = 0 editor.state.doc.forEach((_node, offset, index) => { if (index === 3) at = offset }) const paragraph = editor.schema.nodes.paragraph!.create(null, editor.schema.text('Nowy')) editor.view.dispatch(editor.state.tr.insert(at, paragraph)) const value = el.value for (const block of blocks) expect(value).toContain(block) const inserted = value.indexOf('
Nowy
') expect(inserted).toBeGreaterThan(value.indexOf(blocks[2]!)) expect(inserted).toBeLessThan(value.indexOf(blocks[3]!)) }) it('keeps reused blocks after several edits and an undo returns the exact source', () => { const { el, editor } = mount(source) insertAtBlock(editor, 0, 'A') insertAtBlock(editor, 4, 'B') const value = el.value for (const i of [1, 2, 3, 5]) expect(value).toContain(blocks[i]!) editor.commands.undo() editor.commands.undo() expect(el.value).toBe(source) }) it('serializes every block when the document is replaced entirely', () => { const { el, editor } = mount(source) editor.commands.setContent('Inny
') expect(el.value).toBe('Inny
') }) }) describe('captureBlocks', () => { const schema = getSchema(preservationExtensions()) it('records each block with the text before it and the trailing text', () => { const captured = captureBlocks('\na
\n\na
'], ['\n\n', 'b
c
' const captured = captureBlocks(html, schema)! expect(captured.blocks.map((b) => b.html)).toEqual(['', 'b
c
']) }) it.each([ ['an unclosed comment', 'a