test(markdown-editor): close four coverage gaps found in review

- history: assert resetUndoHistory actually empties the undo stack
  (editor.can().undo() === false), not just that it doesn't throw — a silent
  no-op regression would otherwise pass.
- field: assert insertAtCursor is a no-op (no onChange) when the field is
  disabled — the documented "a mid-save variable click can't dirty the form"
  guard was untested.
- content-integrity: the pipe-table generative test now also asserts each
  pipe-bearing atom survives as an escaped code span, not just the row sentinels.
- code-fence: fix the pinned-limitation comment — an upstream fix turns this
  test RED (the canary), it does not "flip green".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-07-07 20:45:32 +07:00
co-authored by Claude Opus 4.8
parent 786e416dc5
commit 2835fd3302
4 changed files with 46 additions and 2 deletions
@@ -33,8 +33,9 @@ describe('code-block fence lengthening — a block documenting a ``` fence stays
// delimiter by rendering it around a fixed placeholder (getMarkOpening/getMarkClosing), so the `code` mark's
// real content is never seen at delimiter time and it always emits a SINGLE backtick. A code span whose
// content contains a backtick therefore collapses on save and never converges. A NODE (codeBlock, above) DOES
// receive its real content and is content-aware — hence the fence widening is fixable but this is not. Pinned so the gap stays
// visible and this test flips green if a future @tiptap/markdown makes mark delimiters content-aware.
// receive its real content and is content-aware — hence the fence widening is fixable but this is not. Pinned so
// the gap stays visible: this test turns RED (the canary to revisit it) if a future @tiptap/markdown makes mark
// delimiters content-aware and the span starts round-tripping.
describe('inline code containing a backtick — known lossy on save (upstream mark-delimiter limitation)', () => {
it('collapses a backtick-containing code span (does not round-trip)', () => {
const out = roundTrip('x ``a `b` c`` y');
@@ -151,12 +151,14 @@ describe('generative content-integrity — atoms survive load↔serialize across
const wrap = (cells: string) => (outerPipe ? `| ${cells} |` : cells);
const rows: string[] = [];
const sentinels: string[] = [];
const atoms: string[] = [];
for (let row = 0; row < rowCount; row++) {
const atom = pipeAtoms[Math.floor(rng() * pipeAtoms.length)] as string;
const sentinel = `${WORDS[Math.floor(rng() * WORDS.length)]}${row}`;
sentinels.push(sentinel);
atoms.push(atom);
rows.push(wrap(`\`${atom}\` | ${sentinel}`));
}
@@ -168,6 +170,14 @@ describe('generative content-integrity — atoms survive load↔serialize across
for (const sentinel of sentinels) {
expect(out.includes(sentinel), `cell "${sentinel}" dropped (i=${i}):\n${doc}\n-->\n${out}`).toBe(true);
}
// The atom itself must survive as a code span with its structural pipes escaped for the table — the
// sentinel guards the row's cell count, this guards the pipe-bearing content from silent corruption.
for (const atom of atoms) {
const span = `\`${atom.replace(/\|/g, '\\|')}\``;
expect(out.includes(span), `atom ${span} corrupted (i=${i}):\n${doc}\n-->\n${out}`).toBe(true);
}
}
});
});
@@ -54,6 +54,25 @@ describe('MarkdownEditorField raw-mode handle', () => {
ref.current!.insertAtCursor('{{.Bar}}');
expect(onChange).toHaveBeenCalledWith(expect.stringContaining('{{.Bar}}'));
});
it('insertAtCursor is a no-op when the field is disabled (a mid-save variable click cannot dirty it)', () => {
const onChange = vi.fn();
const ref = createRef<MarkdownEditorFieldHandle>();
const { container } = render(
<MarkdownEditorField
disabled
mode="raw"
onChange={onChange}
ref={ref}
value="seed"
/>,
);
container.querySelector('textarea')!.setSelectionRange(0, 0);
ref.current!.insertAtCursor('{{.Bar}}');
expect(onChange).not.toHaveBeenCalled();
});
});
describe('MarkdownEditorField rich-mode handle', () => {
@@ -64,3 +64,17 @@ it('resetUndoHistory survives view.state advancing during updateState', () => {
editor.destroy();
});
it('resetUndoHistory actually clears the undo stack', () => {
const editor = new Editor({ content: 'seed', contentType: 'markdown', extensions: createMarkdownExtensions() });
editor.commands.insertContent(' more');
expect(editor.can().undo()).toBe(true);
resetUndoHistory(editor);
expect(editor.can().undo()).toBe(false);
editor.destroy();
});