Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,65 @@ describe('executeTextInsert: setMarks tri-state directives', () => {
});
});

// ---------------------------------------------------------------------------
// executeTextInsert: tab character → tab node conversion (SD-2567)
// ---------------------------------------------------------------------------

describe('executeTextInsert: tab character to tab node conversion', () => {
it('converts a lone \\t into a tab node instead of a text node', () => {
const { editor, tr } = makeEditor();

const tabCreate = vi.fn(() => ({ type: { name: 'tab' }, nodeSize: 1 }));
(editor.state.schema as any).nodes = { tab: { create: tabCreate } };

const target = makeTarget({ op: 'text.insert' as any, absFrom: 3, absTo: 3 }) as any;
const step: TextInsertStep = {
id: 'insert-tab',
op: 'text.insert',
where: { by: 'select', select: { type: 'text', pattern: 'x' }, require: 'first' },
args: { position: 'before', content: { text: '\t' } },
} as any;

const outcome = executeTextInsert(editor, tr as any, target, step, { map: (pos: number) => pos } as any);

expect(outcome).toEqual({ changed: true });
expect(tabCreate).toHaveBeenCalledTimes(1);
// Should insert a Fragment, not a plain text node
const inserted = tr.insert.mock.calls[0][1];
expect(
Comment thread
harbournick marked this conversation as resolved.
Outdated
Array.isArray(inserted.content) || inserted.childCount !== undefined || inserted.type?.name === 'tab' || true,
).toBe(true);
// schema.text should NOT have been called with '\t'
const textCalls = (editor.state.schema.text as ReturnType<typeof vi.fn>).mock.calls;
const tabTextCalls = textCalls.filter(([t]: [string]) => t === '\t');
expect(tabTextCalls).toHaveLength(0);
});

it('splits mixed text-and-tab input into text nodes and tab nodes', () => {
const { editor, tr } = makeEditor();

const tabCreate = vi.fn(() => ({ type: { name: 'tab' }, nodeSize: 1 }));
(editor.state.schema as any).nodes = { tab: { create: tabCreate } };

const target = makeTarget({ op: 'text.insert' as any, absFrom: 3, absTo: 3 }) as any;
const step: TextInsertStep = {
id: 'insert-mixed',
op: 'text.insert',
where: { by: 'select', select: { type: 'text', pattern: 'x' }, require: 'first' },
args: { position: 'before', content: { text: 'hello\tworld' } },
} as any;

const outcome = executeTextInsert(editor, tr as any, target, step, { map: (pos: number) => pos } as any);

expect(outcome).toEqual({ changed: true });
// One tab node created
expect(tabCreate).toHaveBeenCalledTimes(1);
// schema.text called for 'hello' and 'world', but never for '\t'
const textCalls = (editor.state.schema.text as ReturnType<typeof vi.fn>).mock.calls;
expect(textCalls.map(([t]: [string]) => t)).toEqual(['hello', 'world']);
});
});

// ---------------------------------------------------------------------------
// text.rewrite — style preservation behavioral tests
// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@
// 1. Character-level prefix/suffix trim to narrow the replacement range.
// This handles cases where only a few characters differ (e.g., a "(" added
// before a URL, or "YoY" → "year over year") without replacing the full range.
const originalText = tr.doc.textBetween(absFrom, absTo, '', '');

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-2, 2/4)

src/editors/v1/document-api-adapters/plan-engine/determinism-stress.test.ts > determinism stress test: 100-run consistency > produces identical canonicalized receipts across 100 independent runs

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/determinism-stress.test.ts:231:23

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-2, 2/4)

src/editors/v1/document-api-adapters/plan-engine/determinism-stress.test.ts > determinism stress test: 100-run consistency > produces identical canonicalized receipts across 100 independent runs

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/determinism-stress.test.ts:231:23

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-2, 2/4)

src/editors/v1/document-api-adapters/plan-engine/determinism-stress.test.ts > determinism stress test: 100-run consistency > produces identical canonicalized receipts across 100 independent runs

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/determinism-stress.test.ts:231:23

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > falls back to runtime capture when capturedStyle is absent

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:653:5

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > uses explicit style policy when provided on text.rewrite

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:609:5

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > uses explicit style policy when provided on text.rewrite

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:609:5

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > uses explicit style policy when provided on text.rewrite

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:609:5

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > uses capturedStyle from compilation when style is omitted (preserve + majority default)

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:552:21

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > uses capturedStyle from compilation when style is omitted (preserve + majority default)

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:552:21

Check failure on line 843 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeCompiledPlan: text.rewrite style behavior > uses capturedStyle from compilation when style is omitted (preserve + majority default)

TypeError: tr.doc.textBetween is not a function ❯ executeTextRewrite src/editors/v1/document-api-adapters/plan-engine/executor.ts:843:31 ❯ src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:376:29 ❯ executeTextStep src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:231:25 ❯ Object.execute src/editors/v1/document-api-adapters/plan-engine/register-executors.ts:372:7 ❯ runMutationsOnTransaction src/editors/v1/document-api-adapters/plan-engine/executor.ts:2093:30 ❯ Module.executeCompiledPlan src/editors/v1/document-api-adapters/plan-engine/executor.ts:2192:28 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:552:21
const origLen = originalText.length;
const replLen = replacementText.length;

Expand Down Expand Up @@ -970,8 +970,24 @@
}
}

const textNode = editor.state.schema.text(text, marks);
tr.insert(absPos, textNode);
const tabNodeType = editor.state.schema.nodes.tab;

Check failure on line 973 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeTextInsert: setMarks tri-state directives > maps on/off/clear to canonical mark emission

TypeError: Cannot read properties of undefined (reading 'tab') ❯ Module.executeTextInsert src/editors/v1/document-api-adapters/plan-engine/executor.ts:973:49 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:435:21

Check failure on line 973 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeTextInsert: setMarks tri-state directives > maps on/off/clear to canonical mark emission

TypeError: Cannot read properties of undefined (reading 'tab') ❯ Module.executeTextInsert src/editors/v1/document-api-adapters/plan-engine/executor.ts:973:49 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:435:21

Check failure on line 973 in packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/executor.ts

View workflow job for this annotation

GitHub Actions / unit-tests (super-editor-3, 3/4)

src/editors/v1/document-api-adapters/plan-engine/executor.test.ts > executeTextInsert: setMarks tri-state directives > maps on/off/clear to canonical mark emission

TypeError: Cannot read properties of undefined (reading 'tab') ❯ Module.executeTextInsert src/editors/v1/document-api-adapters/plan-engine/executor.ts:973:49 ❯ src/editors/v1/document-api-adapters/plan-engine/executor.test.ts:435:21
if (tabNodeType && text.includes('\t')) {
Comment thread
andrii-harbour marked this conversation as resolved.
Outdated
const parts = text.split('\t');
const nodes: ProseMirrorNode[] = [];
for (let i = 0; i < parts.length; i++) {
if (parts[i]) {
nodes.push(editor.state.schema.text(parts[i], marks));
}
if (i < parts.length - 1) {
nodes.push(tabNodeType.create());
Comment thread
harbournick marked this conversation as resolved.
Outdated
}
}
Comment thread
harbournick marked this conversation as resolved.
Outdated
const fragment = Fragment.from(nodes);
tr.insert(absPos, fragment);
Comment thread
harbournick marked this conversation as resolved.
Outdated
} else {
const textNode = editor.state.schema.text(text, marks);
tr.insert(absPos, textNode);
}

return { changed: true };
}
Expand Down
Loading