prosemirror-flat-list
Version:
Powerful list support for ProseMirror
750 lines (633 loc) • 12.1 kB
text/typescript
import type { TaggedProsemirrorNode } from 'jest-remirror'
import type { Node as ProsemirrorNode } from 'prosemirror-model'
import { describe, expect, it } from 'vitest'
import { setupTestingEditor } from '../../test/setup-editor'
import { createIndentListCommand } from './indent-list'
describe('indentList', () => {
const t = setupTestingEditor()
const markdown = t.markdown
const indentList = createIndentListCommand()
it('can indent a list node and append it to the previous list node', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A<cursor>2
`,
markdown`
- A1
- A<cursor>2
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- ## A<cursor>2
`,
markdown`
- A1
- ## A<cursor>2
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- > ## A<cursor>2
`,
markdown`
- A1
- > ## A<cursor>2
`,
)
})
it('can indent multiple list nodes and append them to the previous list node', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A2<start>
- A3<end>
`,
markdown`
- A1
- A2<start>
- A3<end>
`,
)
})
it('should not wrap a paragraph with a new list node when it will bring a new visual bullet', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A2a
A2b<cursor>
`,
markdown`
- A1
- A2a
A2b<cursor>
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- A2a
A2b<cursor>
A2c
`,
markdown`
- A1
- A2a
A2b<cursor>
A2c
`,
)
})
it('can indent a paragraph and append it to the previous sibling list node', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A2a
- B1
A2b<cursor>
`,
markdown`
- A1
- A2a
- B1
A2b<cursor>
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- A2a
- B1
A2b<cursor>
- B2
`,
markdown`
- A1
- A2a
- B1
A2b<cursor>
- B2
`,
)
})
it('can only indent selected part when the selection across multiple depth of a nested lists', () => {
t.applyCommand(
indentList,
markdown`
- A1a
- B1a
- C1
B1b<start>
A1b<end>
`,
markdown`
- A1a
- B1a
- C1
B1b<start>
A1b<end>
`,
)
t.applyCommand(
indentList,
markdown`
- A1a
- B1a
- C1
B1b<start>
- B2
- B3
- B4
A1b<end>
`,
markdown`
- A1a
- B1a
- C1
B1b<start>
- B2
- B3
- B4
A1b<end>
`,
)
})
it('can indent multiple list nodes and append them to the previous list node', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A<start>2
- A<end>3
`,
markdown`
- A1
- A<start>2
- A<end>3
`,
)
})
it('can add ambitious indentations', () => {
t.applyCommand(
indentList,
markdown`
- A1
- B<cursor>2
`,
markdown`
- A1
- - B<cursor>2
`,
)
})
it('can split the list when necessary', () => {
t.applyCommand(
indentList,
markdown`
- A1
- B<cursor>2a
B2b
B2c
`,
markdown`
- A1
- - B<cursor>2a
- B2b
B2c
`,
)
})
it('can keep attributes', () => {
t.applyCommand(
indentList,
markdown`
- [ ] A1
- [x] A<cursor>2
`,
markdown`
- [ ] A1
- [x] A<cursor>2
`,
)
t.applyCommand(
indentList,
markdown`
1. A1
2. A<cursor>2
- B1
`,
markdown`
1. A1
1. A<cursor>2
- B1
`,
)
t.applyCommand(
indentList,
markdown`
- [x] A1
- B1
- [x] A<cursor>2
1. B2
`,
markdown`
- [x] A1
- B1
- [x] A<cursor>2
1. B2
`,
)
})
it('can keep the indentation of sub list nodes', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A2
- A3<cursor>
- B1
- B2
- B3
`,
markdown`
- A1
- A2
- A3<cursor>
- B1
- B2
- B3
`,
)
})
it('can move all collapsed content', () => {
t.applyCommand(
indentList,
t.doc(
t.bulletList(t.p('A1')),
t.bulletList(t.p('A2')),
t.collapsedToggleList(
t.p('A3<cursor>'),
t.bulletList(t.p('B1')),
t.bulletList(t.p('B2')),
t.bulletList(t.p('B3')),
),
),
t.doc(
t.bulletList(t.p('A1')),
t.bulletList(
t.p('A2'),
t.collapsedToggleList(
t.p('A3<cursor>'),
t.bulletList(t.p('B1')),
t.bulletList(t.p('B2')),
t.bulletList(t.p('B3')),
),
),
),
)
})
it('can expand a collapsed list node if something is indent into it', () => {
t.applyCommand(
indentList,
t.doc(
t.collapsedToggleList(
t.p('A1'),
t.bulletList(t.p('B1')),
t.bulletList(t.p('B2')),
t.bulletList(t.p('B3')),
),
t.p('<cursor>'),
),
t.doc(
t.expandedToggleList(
t.p('A1'),
t.bulletList(t.p('B1')),
t.bulletList(t.p('B2')),
t.bulletList(t.p('B3')),
t.p('<cursor>'),
),
),
)
})
it('can keep the indentation of sub list nodes when moving multiple list', () => {
t.applyCommand(
indentList,
markdown`
- A1
- <start>A2
- A3<end>
- B1
- B2
- B3
`,
markdown`
- A1
- <start>A2
- A3<end>
- B1
- B2
- B3
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- B1
- A2<start>
- B2<end>
- C1
`,
markdown`
- A1
- B1
- A2<start>
- B2<end>
- C1
`,
)
})
it('can keep the indentation of siblings around the indented item', () => {
t.applyCommand(
indentList,
markdown`
- A1
- A2<cursor>
A2
`,
markdown`
- A1
- A2<cursor>
A2
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- A2<cursor>
A2
- B1
`,
markdown`
- A1
- A2<cursor>
A2
- B1
`,
)
t.applyCommand(
indentList,
markdown`
- A1
- A2
- B1
A2<cursor>
A2
- B1
`,
markdown`
- A1
- A2
- B1
A2<cursor>
A2
- B1
`,
)
t.applyCommand(
indentList,
markdown`
- A1
A1
- <cursor>A2
`,
markdown`
- A1
A1
- <cursor>A2
`,
)
})
it('can indent a paragraph that not inside a list node', () => {
t.applyCommand(
indentList,
markdown`
- A1
P1<cursor>
`,
markdown`
- A1
P1<cursor>
`,
)
t.applyCommand(
indentList,
markdown`
- A1
P1<start>
P2<end>
P3
`,
markdown`
- A1
P1<start>
P2<end>
P3
`,
)
})
it('can accept custom positions', () => {
t.applyCommand(
createIndentListCommand({ from: 13, to: 17 }),
t.doc(
/*0*/
t.bulletList(/*1*/ t.p('A1') /*5*/),
/*6*/
t.bulletList(/*7*/ t.p('A2<cursor>') /*11*/),
/*12*/
t.bulletList(/*13*/ t.p('A3') /*17*/),
/*18*/
),
t.doc(
t.bulletList(t.p('A1')),
t.bulletList(t.p('A2'), t.bulletList(t.p('A3'))),
),
)
t.applyCommand(
createIndentListCommand({ from: 10, to: 17 }),
t.doc(
/*0*/
t.bulletList(/*1*/ t.p('A1') /*5*/),
/*6*/
t.bulletList(/*7*/ t.p('A2<cursor>') /*11*/),
/*12*/
t.bulletList(/*13*/ t.p('A3') /*17*/),
/*18*/
),
t.doc(
t.bulletList(
t.p('A1'),
t.bulletList(t.p('A2')),
t.bulletList(t.p('A3')),
),
),
)
})
it('can handle some complex nested lists', () => {
t.applyCommand(
indentList,
markdown`
- A1
- B1
- <start>B2
- A2
- B3
- C1<end>
- D1
- B4
`,
markdown`
- A1
- B1
- <start>B2
- A2
- B3
- C1<end>
- D1
- B4
`,
)
})
it('only needs one step for some of the most comment indent action', () => {
const countSteps = (
doc: TaggedProsemirrorNode,
expected: TaggedProsemirrorNode,
) => {
t.add(doc)
const state = t.view.state
const command = createIndentListCommand()
let count = -1
let actual: ProsemirrorNode | null = null
command(state, (tr) => {
count = tr.steps.length
actual = tr.doc
})
expect(actual).not.equal(null)
expect(actual).toEqualRemirrorDocument(expected)
return count
}
expect(
countSteps(
markdown`
- A1
- A2<cursor>
`,
markdown`
- A1
- A2<cursor>
`,
),
).toBe(1)
expect(
countSteps(
markdown`
- A1
- [ ] A2<cursor>
- [x] A3
`,
markdown`
- A1
- [ ] A2<cursor>
- [x] A3
`,
),
).toBe(1)
expect(
countSteps(
markdown`
1. A1
2. <start>A2
3. A3<end>
4. A4
`,
markdown`
1. A1
1. <start>A2
2. A3<end>
2. A4
`,
),
).toBe(1)
expect(
countSteps(
markdown`
1. A1
- B1
- <start>B2
- B3
- B4<end>
`,
markdown`
1. A1
- B1
- <start>B2
- B3
- B4<end>
`,
),
).toBe(1)
// For more complex (and less common) cases, more steps is acceptable
expect(
countSteps(
markdown`
- A1
- B1
- C1
- D1
D1b
- <start>D2
C1b
C1c
- B2
- C2
- A2
- A3
- B3
B3b
- B4<end>
A3b
`,
markdown`
- A1
- B1
- C1
- D1
D1b
- <start>D2
C1b
C1c
- B2
- C2
- A2
- A3
- B3
B3b
- B4<end>
A3b
`,
),
).toBeGreaterThan(1)
})
})