Skip to content

Commit ccaef16

Browse files
committed
chore: updated getListItemsToTransform
1 parent 0a72984 commit ccaef16

File tree

3 files changed

+216
-35
lines changed

3 files changed

+216
-35
lines changed

src/extensions/markdown/Lists/commands.test.ts

Lines changed: 124 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,34 @@
11
// eslint-disable-next-line import/no-extraneous-dependencies
2-
import ist from 'ist';
3-
import type {Node} from 'prosemirror-model';
2+
import {Schema} from 'prosemirror-model';
3+
import {builders, li, ul} from 'prosemirror-test-builder';
4+
45
import {
5-
type Command,
6-
EditorState,
7-
NodeSelection,
8-
Selection,
9-
TextSelection,
10-
} from 'prosemirror-state';
11-
import {doc, eq, li, p, schema, ul} from 'prosemirror-test-builder';
12-
13-
import {sinkOnlySelectedListItem} from 'src/extensions/markdown/Lists/commands';
14-
15-
function selFor(doc: Node) {
16-
const a = (doc as any).tag.a,
17-
b = (doc as any).tag.b;
18-
if (a !== null) {
19-
const $a = doc.resolve(a);
20-
if ($a.parent.inlineContent)
21-
return new TextSelection($a, b !== null ? doc.resolve(b) : undefined);
22-
else return new NodeSelection($a);
23-
}
24-
return Selection.atStart(doc);
25-
}
26-
27-
function apply(doc: Node, command: Command, result: Node | null) {
28-
let state = EditorState.create({doc, selection: selFor(doc)});
29-
// eslint-disable-next-line no-return-assign
30-
command(state, (tr) => (state = state.apply(tr)));
31-
ist(state.doc, result || doc, eq);
32-
// eslint-disable-next-line no-eq-null
33-
if (result && (result as any).tag.a != null) ist(state.selection, selFor(result), eq);
34-
}
6+
getListItemsToTransform,
7+
sinkOnlySelectedListItem,
8+
} from 'src/extensions/markdown/Lists/commands';
9+
import {apply, assertMapEntries, getParams} from 'src/extensions/markdown/Lists/testUtils';
10+
import {getSchemaSpecs as getYfmNoteSchemaSpecs} from 'src/extensions/yfm/YfmNote/YfmNoteSpecs/schema';
11+
12+
const schema = new Schema({
13+
nodes: {
14+
doc: {content: 'block+'},
15+
text: {group: 'inline'},
16+
paragraph: {
17+
group: 'block',
18+
content: 'inline*',
19+
toDOM: () => ['p', 0],
20+
},
21+
...getYfmNoteSchemaSpecs(),
22+
},
23+
});
24+
25+
const {
26+
doc,
27+
paragraph: p,
28+
yfm_note: note,
29+
yfm_note_title: noteTitle,
30+
yfm_note_content: noteContent,
31+
} = builders(schema);
3532

3633
describe('sinkOnlySelectedListItem', () => {
3734
const sink = sinkOnlySelectedListItem(schema.nodes.list_item);
@@ -289,3 +286,98 @@ describe('sinkOnlySelectedListItem', () => {
289286
doc(ul(li(p('11'), ul(li(p('22')))), li(p('33')))),
290287
));
291288
});
289+
290+
describe('getListItemsToTransform (using getParams helper)', () => {
291+
it('1', () => {
292+
const testDoc = doc(ul(li(p('11')), li(p('2<a><b>2')), li(p('33'))));
293+
294+
const resultMap = getListItemsToTransform(...getParams(testDoc));
295+
296+
assertMapEntries(resultMap, [[7, 13]]);
297+
});
298+
299+
it('2', () => {
300+
const testDoc = doc(ul(li(p('11')), li(p('2<a>2')), li(p('3<b>3'))));
301+
302+
const resultMap = getListItemsToTransform(...getParams(testDoc));
303+
304+
assertMapEntries(resultMap, [
305+
[7, 13],
306+
[13, 19],
307+
]);
308+
});
309+
310+
it('3', () => {
311+
const testDoc = doc(ul(li(p('11')), li(p('2<a>2'), ul(li(p('3<b>3'))))));
312+
const resultMap = getListItemsToTransform(...getParams(testDoc));
313+
314+
assertMapEntries(resultMap, [
315+
[7, 21],
316+
[13, 19],
317+
]);
318+
});
319+
320+
it('4', () => {
321+
const testDoc = doc(ul(li(p('11')), li(p('2<a>2'), ul(li(p('33')))), li(p('4<b>4'))));
322+
const resultMap = getListItemsToTransform(...getParams(testDoc));
323+
324+
assertMapEntries(resultMap, [
325+
[7, 21],
326+
[13, 19],
327+
[21, 27],
328+
]);
329+
});
330+
331+
it('5', () => {
332+
const testDoc = doc(ul(li(p('11'), ul(li(p('22')), li(p('3<a>3')))), li(p('4<b>4'))));
333+
const resultMap = getListItemsToTransform(...getParams(testDoc));
334+
335+
assertMapEntries(resultMap, [
336+
[13, 19],
337+
[21, 27],
338+
]);
339+
});
340+
341+
it('6', () => {
342+
const testDoc = doc(
343+
ul(
344+
li(p('11')),
345+
li(p('2<a>2'), ul(li(p('33'), ul(li(p('44')))), li(p('55')))),
346+
li(p('6<b>6')),
347+
),
348+
);
349+
350+
const resultMap = getListItemsToTransform(...getParams(testDoc));
351+
352+
assertMapEntries(resultMap, [
353+
[7, 35],
354+
[13, 27],
355+
[19, 25],
356+
[27, 33],
357+
[35, 41],
358+
]);
359+
});
360+
361+
it('7', () => {
362+
const testDoc = doc(
363+
ul(
364+
li(p('11')),
365+
li(
366+
p('2<a>2'),
367+
note(noteTitle('Note'), noteContent(ul(li(p('33')), li(p('44')), li(p('55'))))),
368+
),
369+
li(p('6<b>6')),
370+
),
371+
);
372+
373+
const resultMap = getListItemsToTransform(...getParams(testDoc));
374+
375+
assertMapEntries(resultMap, [
376+
[7, 43],
377+
[43, 49],
378+
[21, 27],
379+
[27, 33],
380+
[33, 39],
381+
]);
382+
});
383+
});

src/extensions/markdown/Lists/commands.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const sink = (tr: Transaction, range: NodeRange, itemType: NodeType) => {
7474
return true;
7575
};
7676

77-
const isListItemNode = (node: Node, itemType: NodeType) =>
77+
export const isNotFirstListItemNode = (node: Node, itemType: NodeType) =>
7878
node.childCount > 0 && node.firstChild!.type === itemType;
7979

8080
function findDeepestListItem(tr: Transaction, itemType: NodeType, start: number): [number, number] {
@@ -99,7 +99,7 @@ function findDeepestListItem(tr: Transaction, itemType: NodeType, start: number)
9999
/**
100100
* Returns a map of list item positions that should be transformed (e.g., sink or lift).
101101
*/
102-
function getListItemsToTransform(
102+
export function getListItemsToTransform(
103103
tr: Transaction,
104104
itemType: NodeType,
105105
{
@@ -209,7 +209,7 @@ export function sinkOnlySelectedListItem(itemType: NodeType): Command {
209209
return ({tr, selection}, dispatch) => {
210210
const {$from, $to, from, to} = selection;
211211
const listItemSelectionRange = $from.blockRange($to, (node) =>
212-
isListItemNode(node, itemType),
212+
isNotFirstListItemNode(node, itemType),
213213
);
214214

215215
if (!listItemSelectionRange) {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import ist from 'ist';
2+
import type {Node} from 'prosemirror-model';
3+
import {
4+
type Command,
5+
EditorState,
6+
NodeSelection,
7+
Selection,
8+
TextSelection,
9+
} from 'prosemirror-state';
10+
import {eq, schema} from 'prosemirror-test-builder';
11+
12+
import {liType} from 'src/extensions';
13+
import {isNotFirstListItemNode} from 'src/extensions/markdown/Lists/commands';
14+
15+
export function selFor(doc: Node) {
16+
const a = (doc as any).tag.a,
17+
b = (doc as any).tag.b;
18+
if (a !== null) {
19+
const $a = doc.resolve(a);
20+
if ($a.parent.inlineContent)
21+
return new TextSelection($a, b !== null ? doc.resolve(b) : undefined);
22+
else return new NodeSelection($a);
23+
}
24+
return Selection.atStart(doc);
25+
}
26+
27+
export function apply(doc: Node, command: Command, result: Node | null) {
28+
let state = EditorState.create({doc, selection: selFor(doc)});
29+
30+
// eslint-disable-next-line no-return-assign
31+
command(state, (tr) => (state = state.apply(tr)));
32+
ist(state.doc, result || doc, eq);
33+
34+
if (result && (result as any).tag.a !== null) {
35+
ist(state.selection, selFor(result), eq);
36+
}
37+
}
38+
39+
type Tags = {[tag: string]: number};
40+
41+
export function getParams(docNode: Node & {tag: Tags}) {
42+
const state = EditorState.create({
43+
doc: docNode,
44+
selection: TextSelection.create(docNode, docNode.tag.a, docNode.tag.b),
45+
});
46+
const {tr, selection} = state;
47+
const {$from, $to, from, to} = selection;
48+
const itemType = liType(schema);
49+
const range = $from.blockRange($to, (node) => isNotFirstListItemNode(node, itemType));
50+
51+
if (!range) {
52+
return [tr, schema.nodes.list_item, {start: 0, end: 0, from: 0, to: 0}] as const;
53+
}
54+
55+
return [tr, schema.nodes.list_item, {start: range.start, end: range.end, from, to}] as const;
56+
}
57+
58+
export function assertMapEntries(
59+
resultMap: Map<number, number>,
60+
expectedEntries: Array<[number, number]>,
61+
) {
62+
// 1) Check that there are no extra or missing entries.
63+
console.log('111-3', resultMap.size);
64+
console.log('111-4', expectedEntries.length);
65+
66+
console.log('111-5', resultMap.size === expectedEntries.length);
67+
68+
const x = resultMap.size;
69+
const y = expectedEntries.length;
70+
71+
ist(x, y);
72+
73+
// 2) For each expected [key, value], verify presence and equality.
74+
for (const [key, value] of expectedEntries) {
75+
// Ensure the key exists
76+
console.log('==--==--==-->', resultMap);
77+
console.log('222-1', key);
78+
console.log('222-2', resultMap.has(key));
79+
console.log(
80+
'222-3',
81+
resultMap.forEach((key) => console.log(key)),
82+
);
83+
ist(resultMap.has(key), true);
84+
85+
// Ensure the stored value matches
86+
ist(resultMap.get(key), value);
87+
console.log('333-1', ist(resultMap.get(key), value));
88+
}
89+
}

0 commit comments

Comments
 (0)