'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const { JSDOM } = require('jsdom'); const ts = require('typescript'); const source = fs.readFileSync(path.join(__dirname, '../src/editor-runtime.ts'), 'utf8'); const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const runtime = {}; new Function('exports', compiled)(runtime); const { removeAdjacentBlock } = runtime; const serializeSource = fs.readFileSync(path.join(__dirname, '../src/utils/serialize.ts'), 'utf8'); const serializeCompiled = ts.transpileModule(serializeSource, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const serializer = {}; new Function('exports', serializeCompiled)(serializer); function fixture(html, nodeSelector, offset = 0) { const dom = new JSDOM(`
${html}`); const body = dom.window.document.body; const node = nodeSelector === 'body' ? body : body.querySelector(nodeSelector); let range = dom.window.document.createRange(); range.setStart(node, offset); range.collapse(true); const events = []; const editor = { getBody: () => body, selection: { isCollapsed: () => range.collapsed, getRng: () => range, setRng: (next) => { range = next; }, setCursorLocation: (target, at) => { range = dom.window.document.createRange(); range.setStart(target, at); range.collapse(true); }, }, dom: { remove: (target) => target.remove() }, undoManager: { transact: (action) => { events.push('undo'); action(); } }, nodeChanged: () => events.push('node'), fire: (name) => events.push(name), }; const press = (key, options = {}) => { const event = { key, prevented: false, preventDefault() { this.prevented = true; }, ...options, }; removeAdjacentBlock(editor, event); return event; }; return { body, editor, events, press }; } test('Backspace deletes the adjacent table from a paragraph boundary and records one edit', () => { const f = fixture('| data |
after
', 'strong', 0); const event = f.press('Backspace'); assert.equal(event.prevented, true); assert.equal(f.body.innerHTML, 'after
'); assert.deepEqual(f.events, ['undo', 'node', 'change']); }); test('Delete removes a following list only at the end of ordinary text', () => { const f = fixture('before
before
'); }); test('a displayed formula is removed with its paragraph wrapper', () => { const f = fixture('x
next
', 'p', 1); assert.equal(f.press('Backspace').prevented, true); assert.equal(f.body.innerHTML, 'next
'); assert.equal(f.editor.selection.getRng().startContainer, f.body.querySelector('p')); }); test('Delete before a displayed formula removes only its paragraph', () => { const f = fixture('before
x
', 'p:nth-child(2)', 0); assert.equal(f.press('Delete').prevented, true); assert.equal(f.body.innerHTML, 'before
'); }); test('Backspace on the line after a formula removes the empty line first', () => { const f = fixture('x
| data |
x2
'); dom.window.document.body.innerHTML = serializer.hydrateBlocks(saved); assert.equal(dom.window.document.body.querySelectorAll('p').length, 1); assert.equal(dom.window.document.body.querySelectorAll('.jio-math--block').length, 1); } finally { global.document = previousDocument; } }); test('a real line break inside a paragraph is not a block boundary', () => { const f = fixture('
text
quote
secret
next
`, '[contenteditable="true"] p', 0); assert.equal(f.press('Backspace').prevented, true); assert.equal(f.body.innerHTML, 'next
'); } }); test('deleting the last structure creates an editable caret home without resetting content', () => { const f = fixture('code', 'pre', 0); assert.equal(f.press('Backspace').prevented, true); assert.equal(f.body.children.length, 1); assert.equal(f.body.firstElementChild.tagName, 'P'); assert.equal(f.editor.selection.getRng().startContainer, f.body.firstElementChild); }); test('body-level caret can delete an adjacent divider', () => { const f = fixture('
after
', 'body', 1); assert.equal(f.press('Backspace').prevented, true); assert.equal(f.body.innerHTML, 'after
'); }); test('body-level deletion of the only divider leaves a caret home', () => { const f = fixture('code', 'body', 1); assert.equal(f.press('Backspace').prevented, true); assert.equal(f.body.firstElementChild.tagName, 'P'); assert.equal(f.body.lastElementChild.tagName, 'PRE'); assert.equal(f.editor.selection.getRng().startContainer, f.body.firstElementChild); }); test('media-only paragraphs are removed with their media', () => { const f = fixture('

next
', 'p', 1); assert.equal(f.press('Backspace').prevented, true); assert.equal(f.body.innerHTML, 'next
'); }); test('editing inside a table or list keeps the native key behavior', () => { for (const html of [ '| cell |