P5m made the model accept blocks in cells, but the atomic-insert path
(`insertAtomicBlock`) only special-cased columns — with the caret in a cell it
fell through to the top-level split, dropping the block NEXT TO the table
instead of inside the cell. So "blocks in cells" was model-only, not reachable.
Adds an `insertIntoCell` branch mirroring `insertIntoColumn`, one level deeper
(container path [tableIdx, rowIdx, cellIdx, blockInCell, ...]): empty-cell
paragraph → replace in place (+ trailing paragraph); non-empty → split the
cell's block around the inserted one. So insert-callout / divider / image (and
any atomic insert) with the caret in a cell now land inside that cell.
New test `insert-block-types.test.ts` (3/3): inserts into the cell (single
top-level table preserved), callout lands inside, non-empty cell splits
(para("a") · divider · para("b")). npm run check: 0 new errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
parent
aac80f7630
commit
c4acfa4460
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* P5m — inserting a block into a table CELL (cells hold blocks like
|
||||
* columns). The atomic-insert path must target the cell's children, not the
|
||||
* document root next to the table.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { insertCallout, insertDivider } from './insert-block-types';
|
||||
import { createState } from './text';
|
||||
import { createCollapsedSelection } from '../selection';
|
||||
import { resolveTextNode } from './helpers';
|
||||
import { WORDS_VERSION, type WordsDocument } from '../types';
|
||||
|
||||
function tableDoc(cellText = ''): WordsDocument {
|
||||
return {
|
||||
version: WORDS_VERSION,
|
||||
children: [
|
||||
{
|
||||
type: 'table',
|
||||
rows: [
|
||||
{
|
||||
cells: [
|
||||
{ children: [{ type: 'paragraph', children: [{ type: 'text', text: cellText }] }] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
describe('insert block into a table cell (P5m)', () => {
|
||||
it('inserts INTO the cell, not beside the table', () => {
|
||||
const d = tableDoc();
|
||||
// Caret in the cell's empty paragraph: [table 0, row 0, cell 0, para 0, text 0].
|
||||
const sel = createCollapsedSelection([0, 0, 0, 0, 0], 0);
|
||||
const r = insertDivider(createState(d, sel));
|
||||
expect(r.changed).toBe(true);
|
||||
// Still a SINGLE top-level block (the table) — the divider did not land
|
||||
// at the document root next to the table.
|
||||
expect(r.state.document.children).toHaveLength(1);
|
||||
const table = r.state.document.children[0];
|
||||
if (table.type !== 'table') throw new Error('expected table');
|
||||
const cell = table.rows[0].cells[0];
|
||||
// The empty paragraph was replaced by [divider, trailing paragraph].
|
||||
expect(cell.children.map((b) => b.type)).toEqual(['divider', 'paragraph']);
|
||||
});
|
||||
|
||||
it('inserts a callout inside the cell', () => {
|
||||
const d = tableDoc();
|
||||
const sel = createCollapsedSelection([0, 0, 0, 0, 0], 0);
|
||||
const r = insertCallout(createState(d, sel), 'affirm');
|
||||
expect(r.changed).toBe(true);
|
||||
expect(r.state.document.children).toHaveLength(1);
|
||||
const table = r.state.document.children[0];
|
||||
if (table.type !== 'table') throw new Error('expected table');
|
||||
const cell = table.rows[0].cells[0];
|
||||
expect(cell.children.some((b) => b.type === 'callout')).toBe(true);
|
||||
});
|
||||
|
||||
it('splits a non-empty cell paragraph around the inserted block', () => {
|
||||
const d = tableDoc('ab');
|
||||
// Caret between "a" and "b": [table 0, row 0, cell 0, para 0, text 0], offset 1.
|
||||
const sel = createCollapsedSelection([0, 0, 0, 0, 0], 1);
|
||||
const r = insertDivider(createState(d, sel));
|
||||
expect(r.changed).toBe(true);
|
||||
expect(r.state.document.children).toHaveLength(1);
|
||||
const table = r.state.document.children[0];
|
||||
if (table.type !== 'table') throw new Error('expected table');
|
||||
const cell = table.rows[0].cells[0];
|
||||
// para("a") · divider · para("b")
|
||||
expect(cell.children.map((b) => b.type)).toEqual(['paragraph', 'divider', 'paragraph']);
|
||||
expect(resolveTextNode(r.state.document, [0, 0, 0, 0, 0])!.text).toBe('a');
|
||||
expect(resolveTextNode(r.state.document, [0, 0, 0, 2, 0])!.text).toBe('b');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in new issue