一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
153 行
4.7 KiB
TypeScript
153 行
4.7 KiB
TypeScript
import { EditorSelection } from '@codemirror/state';
|
|
import createTestEditor from '../../testing/createTestEditor';
|
|
import toggleCheckbox, { isChecklistLine, reorderChecklistItems, ChecklistItemBlock } from './toggleCheckboxAt';
|
|
|
|
describe('toggleCheckboxAt', () => {
|
|
describe('isChecklistLine', () => {
|
|
it('identifies standard checklist formats correctly', () => {
|
|
expect(isChecklistLine('- [ ] Task 1')).toBe(true);
|
|
expect(isChecklistLine('- [x] Task 2')).toBe(true);
|
|
expect(isChecklistLine('* [ ] Task 3')).toBe(true);
|
|
expect(isChecklistLine('+ [ ] Task 4')).toBe(true);
|
|
expect(isChecklistLine('1. [ ] Task 5')).toBe(true);
|
|
expect(isChecklistLine(' - [ ] Subtask')).toBe(true);
|
|
|
|
expect(isChecklistLine('- Just bullet')).toBe(false);
|
|
expect(isChecklistLine('# Heading')).toBe(false);
|
|
expect(isChecklistLine('Plain text')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('reorderChecklistItems', () => {
|
|
it('moves checked target to the bottom of completed items', () => {
|
|
const items: ChecklistItemBlock[] = [
|
|
{ lines: ['- [ ] Task 1'], isChecked: false, isTarget: true },
|
|
{ lines: ['- [ ] Task 2'], isChecked: false, isTarget: false },
|
|
{ lines: ['- [x] Task 3'], isChecked: true, isTarget: false },
|
|
];
|
|
|
|
const reordered = reorderChecklistItems(items, true);
|
|
expect(reordered.map(item => item.lines[0])).toEqual([
|
|
'- [ ] Task 2',
|
|
'- [x] Task 3',
|
|
'- [x] Task 1',
|
|
]);
|
|
});
|
|
|
|
it('moves unchecked target to the bottom of active items', () => {
|
|
const items: ChecklistItemBlock[] = [
|
|
{ lines: ['- [ ] Task 1'], isChecked: false, isTarget: false },
|
|
{ lines: ['- [x] Task 2'], isChecked: true, isTarget: false },
|
|
{ lines: ['- [x] Task 3'], isChecked: true, isTarget: true },
|
|
];
|
|
|
|
const reordered = reorderChecklistItems(items, false);
|
|
expect(reordered.map(item => item.lines[0])).toEqual([
|
|
'- [ ] Task 1',
|
|
'- [ ] Task 3',
|
|
'- [x] Task 2',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('toggleCheckbox editor command', () => {
|
|
it('toggles a single item in place without moving', async () => {
|
|
const initialText = '- [ ] Single item';
|
|
const editor = await createTestEditor(initialText, EditorSelection.cursor(2), []);
|
|
|
|
const handled = toggleCheckbox(2)(editor);
|
|
expect(handled).toBe(true);
|
|
expect(editor.state.doc.toString()).toBe('- [x] Single item');
|
|
|
|
toggleCheckbox(2)(editor);
|
|
expect(editor.state.doc.toString()).toBe('- [ ] Single item');
|
|
});
|
|
|
|
it('auto-sorts checked item to the bottom of the list block', async () => {
|
|
const initialText = [
|
|
'- [ ] Item 1',
|
|
'- [ ] Item 2',
|
|
'- [x] Item 3',
|
|
'- [ ] Item 4',
|
|
].join('\n');
|
|
|
|
const editor = await createTestEditor(initialText, EditorSelection.cursor(2), []);
|
|
|
|
// Click first item (Item 1)
|
|
toggleCheckbox(2)(editor);
|
|
|
|
expect(editor.state.doc.toString()).toBe([
|
|
'- [ ] Item 2',
|
|
'- [ ] Item 4',
|
|
'- [x] Item 3',
|
|
'- [x] Item 1',
|
|
].join('\n'));
|
|
});
|
|
|
|
it('auto-sorts unchecked item back to the bottom of active items', async () => {
|
|
const initialText = [
|
|
'- [ ] Item 1',
|
|
'- [x] Item 2',
|
|
'- [x] Item 3',
|
|
].join('\n');
|
|
|
|
const editor = await createTestEditor(initialText, EditorSelection.cursor(initialText.indexOf('Item 2')), []);
|
|
|
|
// Uncheck Item 2
|
|
toggleCheckbox(initialText.indexOf('Item 2'))(editor);
|
|
|
|
expect(editor.state.doc.toString()).toBe([
|
|
'- [ ] Item 1',
|
|
'- [ ] Item 2',
|
|
'- [x] Item 3',
|
|
].join('\n'));
|
|
});
|
|
|
|
it('preserves attached child subtasks when moving a parent item', async () => {
|
|
const initialText = [
|
|
'- [ ] Parent 1',
|
|
' - sub-detail line 1',
|
|
' - sub-detail line 2',
|
|
'- [ ] Parent 2',
|
|
].join('\n');
|
|
|
|
const editor = await createTestEditor(initialText, EditorSelection.cursor(2), []);
|
|
|
|
// Check Parent 1
|
|
toggleCheckbox(2)(editor);
|
|
|
|
expect(editor.state.doc.toString()).toBe([
|
|
'- [ ] Parent 2',
|
|
'- [x] Parent 1',
|
|
' - sub-detail line 1',
|
|
' - sub-detail line 2',
|
|
].join('\n'));
|
|
});
|
|
|
|
it('respects autoSort: false option to toggle strictly in place', async () => {
|
|
const initialText = [
|
|
'- [ ] Item 1',
|
|
'- [ ] Item 2',
|
|
].join('\n');
|
|
|
|
const editor = await createTestEditor(initialText, EditorSelection.cursor(2), []);
|
|
|
|
toggleCheckbox(2, { autoSort: false })(editor);
|
|
|
|
expect(editor.state.doc.toString()).toBe([
|
|
'- [x] Item 1',
|
|
'- [ ] Item 2',
|
|
].join('\n'));
|
|
});
|
|
|
|
it('returns false for lines without any checkbox', async () => {
|
|
const initialText = 'Just a regular markdown line';
|
|
const editor = await createTestEditor(initialText, EditorSelection.cursor(5), []);
|
|
|
|
const result = toggleCheckbox(5)(editor);
|
|
expect(result).toBe(false);
|
|
expect(editor.state.doc.toString()).toBe(initialText);
|
|
});
|
|
});
|
|
});
|