Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
3.8 kB · 107 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108import { describe, it, expect } from 'vitest';import { EditorState, type StateCommand } from '@codemirror/state';
import { sinkListItem, liftListItem, continueListItem } from './listKeymap';
/** Run a list-indent command headlessly and return the resulting doc + caret. */function run(cmd: StateCommand, doc: string, cursor: number) { const state = EditorState.create({ doc, selection: { anchor: cursor } }); let tr: ReturnType<EditorState['update']> | undefined; const ok = cmd({ state, dispatch: (t) => (tr = t) }); return { ok, doc: tr ? tr.newDoc.toString() : doc, head: tr ? tr.newSelection.main.head : cursor, };}
describe('continueListItem (Enter)', () => { it('continues a bullet marker with a trailing space, tightly', () => { const r = run(continueListItem, '- a', 3); expect(r.ok).toBe(true); expect(r.doc).toBe('- a\n- '); // no blank line between items });
it('auto-increments an ordered list, tightly', () => { const r = run(continueListItem, '1. a', 4); expect(r.doc).toBe('1. a\n2. '); });
it('continues at the same nested indent and increments', () => { const r = run(continueListItem, '1. a\n 1. b', 12); expect(r.doc).toBe('1. a\n 1. b\n 2. '); });
it('keeps a multi-item list tight (no blank line inserted)', () => { // The bug: the old library command inserted a blank line here. const r = run(continueListItem, '1. a\n2. b', 9); expect(r.doc).toBe('1. a\n2. b\n3. '); });
it('splits content after the cursor onto the continued line', () => { const r = run(continueListItem, '1. hello world', 9); // after "hello " expect(r.doc).toBe('1. hello \n2. world'); });
it('exits the list on an empty item', () => { expect(run(continueListItem, '- ', 2).doc).toBe(''); expect(run(continueListItem, '1. ', 3).doc).toBe(''); });
it('falls through (returns false) outside a list item', () => { expect(run(continueListItem, 'plain', 5).ok).toBe(false); });});
describe('sinkListItem (Tab)', () => { it('nests an ordered item under its sibling and resets it to 1', () => { // cursor at end of "2. b" const r = run(sinkListItem, '1. a\n2. b', 9); expect(r.ok).toBe(true); expect(r.doc).toBe('1. a\n 1. b'); });
it('closes the gap it leaves in the parent list', () => { const r = run(sinkListItem, '1. a\n2. b\n3. c', 9); // sink "b" expect(r.doc).toBe('1. a\n 1. b\n2. c'); });
it('nests a bullet item without touching a marker number', () => { const r = run(sinkListItem, '- a\n- b', 7); // end of "- b" expect(r.doc).toBe('- a\n - b'); });
it('falls through on the first item of a list (nothing to nest under)', () => { const r = run(sinkListItem, '1. a', 4); expect(r.ok).toBe(false); // default Tab handles it expect(r.doc).toBe('1. a'); });
it('falls through (returns false) outside a list item', () => { const r = run(sinkListItem, 'plain text', 5); expect(r.ok).toBe(false); });});
describe('liftListItem (Shift-Tab)', () => { it('outdents a nested ordered item and continues the parent numbering', () => { const r = run(liftListItem, '1. a\n 1. b', 12); // end of nested "1. b" expect(r.ok).toBe(true); expect(r.doc).toBe('1. a\n2. b'); });
it('increments following parent siblings when an item rejoins', () => { const r = run(liftListItem, '1. a\n 1. b\n2. c', 12); // lift "b" expect(r.doc).toBe('1. a\n2. b\n3. c'); });
it('restarts the siblings left behind in the nested list', () => { const r = run(liftListItem, '1. a\n 1. b\n 2. c', 12); // lift "b" expect(r.doc).toBe('1. a\n2. b\n 1. c'); });
it('falls through (returns false) at top level', () => { const r = run(liftListItem, '1. a', 4); expect(r.ok).toBe(false); });});