diff --git a/__tests__/checkboxUtils.test.ts b/__tests__/checkboxUtils.test.ts deleted file mode 100644 index c1c1c16..0000000 --- a/__tests__/checkboxUtils.test.ts +++ /dev/null @@ -1,673 +0,0 @@ -// checkboxUtils.test.ts -import { CheckboxUtils } from '../src/checkboxUtils'; -import { CheckboxSyncPluginSettings } from '../src/types'; // Предполагаем, что типы в types.ts - -// --- Mock Settings --- -const defaultSettings: Readonly = { - xOnlyMode: false, // По умолчанию: любой не-пробел считается 'checked' - enableAutomaticParentState: true, - enableAutomaticChildState: true, -}; - -const xOnlySettings: Readonly = { - xOnlyMode: true, // Только 'x' считается 'checked' - enableAutomaticParentState: true, - enableAutomaticChildState: true, -}; - -const settingsParentSyncDisabled: Readonly = { - xOnlyMode: false, // или true, если нужно тестировать комбинацию - enableAutomaticParentState: false, // Родитель НЕ обновляется от детей - enableAutomaticChildState: true, -}; - -const settingsChildSyncDisabled: Readonly = { - xOnlyMode: false, - enableAutomaticParentState: true, // Оставим true для этого варианта - enableAutomaticChildState: false, // Дети НЕ обновляются от родителя -}; - -// --- Test Suite --- -describe('CheckboxUtils', () => { - let utilsDefault: CheckboxUtils; - let utilsXOnly: CheckboxUtils; - let utilsParentSyncDisabled: CheckboxUtils; - let utilsChildSyncDisabled: CheckboxUtils - - beforeEach(() => { - // Создаем новые экземпляры перед каждым тестом для изоляции - utilsDefault = new CheckboxUtils(defaultSettings); - utilsXOnly = new CheckboxUtils(xOnlySettings); - utilsParentSyncDisabled = new CheckboxUtils(settingsParentSyncDisabled); - utilsChildSyncDisabled = new CheckboxUtils(settingsChildSyncDisabled); - }); - - // --- matchCheckboxLine --- - describe('matchCheckboxLine', () => { - it.each([ - // Простые случаи - ['- [ ] Text', { indent: 0, marker: '-', checkChar: ' ', checkboxCharPosition: 3 }], - ['* [x] Task', { indent: 0, marker: '*', checkChar: 'x', checkboxCharPosition: 3 }], - ['+ [?] Query', { indent: 0, marker: '+', checkChar: '?', checkboxCharPosition: 3 }], - ['1. [!] Num', { indent: 0, marker: '1.', checkChar: '!', checkboxCharPosition: 4 }], - // С отступами - [' - [ ] Indented', { indent: 2, marker: '-', checkChar: ' ', checkboxCharPosition: 5 }], - ['\t* [x] Tab Indent', { indent: 1, marker: '*', checkChar: 'x', checkboxCharPosition: 4 }], // Зависит от размера таба, Jest может считать его 1 - [' 10. [A] Deep', { indent: 4, marker: '10.', checkChar: 'A', checkboxCharPosition: 9 }], - ])('should match valid checkbox line: %s', (line, expected) => { - const result = utilsDefault.matchCheckboxLine(line); - expect(result).toEqual(expected); - }); - - it.each([ - 'Not a list item', - '- Just text', - '-[] No space', - '- [No Char]', - '- [ ]', // Нет пробела после скобки, как требует регекс \s - ' Non-checkbox line', - '-[ ] No space before marker', - '1.[ ] No space after numbered marker', - '- ] Missing opening bracket', - '* [x Missing closing bracket', - ])('should return null for invalid lines: %s', (line) => { - expect(utilsDefault.matchCheckboxLine(line)).toBeNull(); - }); - - it('should require a space after the closing bracket', () => { - expect(utilsDefault.matchCheckboxLine('- [ ]')).toBeNull(); // Missing trailing space - expect(utilsDefault.matchCheckboxLine('- [ ] ')).not.toBeNull(); // Has trailing space - }); - - it.each([' ', 'x', '-', '?', '!'])('should correctly parse the checkChar', (state) => { - const lineInfo = utilsDefault.matchCheckboxLine(`- [${state}] Task`); - expect(lineInfo).not.toBeNull(); - expect(lineInfo?.checkChar).toBe(state); - }); - }); - - // --- isCheckedSymbol --- - describe('isCheckedSymbol', () => { - // Default mode (xOnlyMode: false) - it('should check symbols correctly in default mode', () => { - expect(utilsDefault.isCheckedSymbol('x')).toBe(true); - expect(utilsDefault.isCheckedSymbol('X')).toBe(true); - expect(utilsDefault.isCheckedSymbol('/')).toBe(true); - expect(utilsDefault.isCheckedSymbol('-')).toBe(true); - expect(utilsDefault.isCheckedSymbol('?')).toBe(true); - expect(utilsDefault.isCheckedSymbol(' ')).toBe(false); // Только пробел - не отмечено - }); - - // xOnly mode (xOnlyMode: true) - it('should check symbols correctly in xOnly mode', () => { - expect(utilsXOnly.isCheckedSymbol('x')).toBe(true); // Только 'x' - отмечено - expect(utilsXOnly.isCheckedSymbol('X')).toBe(false); - expect(utilsXOnly.isCheckedSymbol('/')).toBe(false); - expect(utilsXOnly.isCheckedSymbol('-')).toBe(false); - expect(utilsXOnly.isCheckedSymbol('?')).toBe(false); - expect(utilsXOnly.isCheckedSymbol(' ')).toBe(false); - }); - }); - - // --- updateLineCheckboxStateWithInfo --- - describe('updateLineCheckboxStateWithInfo', () => { - - it('should check an unchecked box', () => { - const uncheckedLine = ' - [ ] Task'; - const uncheckedLineInfo = utilsDefault.matchCheckboxLine(uncheckedLine)!; - const updatedLine = utilsDefault.updateLineCheckboxStateWithInfo(uncheckedLine, true, uncheckedLineInfo); - expect(updatedLine).toBe(' - [x] Task'); - }); - - it('should uncheck a checked box', () => { - const checkedLine = ' - [x] Task'; - const checkedLineInfo = utilsDefault.matchCheckboxLine(checkedLine)!; - const updatedLine = utilsDefault.updateLineCheckboxStateWithInfo(checkedLine, false, checkedLineInfo); - expect(updatedLine).toBe(' - [ ] Task'); - }); - - it('should handle other initial check chars', () => { - const otherLine = ' - [?] Task'; - const otherLineInfo = utilsDefault.matchCheckboxLine(otherLine)!; - expect(utilsDefault.updateLineCheckboxStateWithInfo(otherLine, true, otherLineInfo)).toBe(' - [x] Task'); - expect(utilsDefault.updateLineCheckboxStateWithInfo(otherLine, false, otherLineInfo)).toBe(' - [ ] Task'); - }); - - it('should return original line if position is invalid (and warn)', () => { - const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { }); // Подавить вывод в консоль - const uncheckedLine = ' - [ ] Task'; - const uncheckedLineInfo = utilsDefault.matchCheckboxLine(uncheckedLine)!; - const invalidInfo = { ...uncheckedLineInfo, checkboxCharPosition: 100 }; - const result = utilsDefault.updateLineCheckboxStateWithInfo(uncheckedLine, true, invalidInfo); - expect(result).toBe(uncheckedLine); - expect(warnSpy).toHaveBeenCalled(); - warnSpy.mockRestore(); - }); - }); - - // --- propagateStateToChildren --- - describe('propagateStateToChildren', () => { - const text = [ - '- [ ] Parent 1', // 0 - ' - [ ] Child 1.1', // 1 - ' - [x] Child 1.2', // 2 - ' - [ ] Grandchild 1.2.1', // 3 - '- [x] Parent 2', // 4 - ' - [x] Child 2.1', // 5 - ].join('\n'); - - it('should check children when parent is checked', () => { - // Имитируем проверку Parent 1 (индекс 0) - const lines = text.split('\n'); - lines[0] = '- [x] Parent 1'; // Manually check parent for the test input - const initialTextModified = lines.join('\n'); - - const result = utilsDefault.propagateStateToChildren(initialTextModified, 0); - const expected = [ - '- [x] Parent 1', // Checked - ' - [x] Child 1.1', // Updated - ' - [x] Child 1.2', // Updated - ' - [x] Grandchild 1.2.1', // Updated - '- [x] Parent 2', // Unchanged - ' - [x] Child 2.1', // Unchanged - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should uncheck children when parent is unchecked', () => { - // Имитируем снятие отметки с Parent 2 (индекс 4) - const lines = text.split('\n'); - lines[4] = '- [ ] Parent 2'; // Manually uncheck parent for the test input - const initialTextModified = lines.join('\n'); - - const result = utilsDefault.propagateStateToChildren(initialTextModified, 4); - const expected = [ - '- [ ] Parent 1', // Unchanged - ' - [ ] Child 1.1', // Unchanged - ' - [x] Child 1.2', // Unchanged - ' - [ ] Grandchild 1.2.1', // Unchanged - '- [ ] Parent 2', // Unchecked - ' - [ ] Child 2.1', // Updated - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should do nothing if the target line is not a checkbox', () => { - const textWithNonCheckbox = [ - '# Header', - '- [ ] Item 1' - ].join('\n'); - const result = utilsDefault.propagateStateToChildren(textWithNonCheckbox, 0); - expect(result).toBe(textWithNonCheckbox); - }); - - it('should stop only when indent is less than or equal to the original parent', () => { - const textSibling = [ - '- [ ] Parent', // 0, indent 0 - ' - [ ] Child 1', // 1, indent 2 - ' - [ ] Sibling C1', // 2, indent 2 (still > 0) - '- [ ] Parent 2' // 3, indent 0 (<= 0, stop here) - ].join('\n'); - const lines = textSibling.split('\n'); - lines[0] = '- [x] Parent'; // Make parent checked - const modifiedText = lines.join('\n'); - const result = utilsDefault.propagateStateToChildren(modifiedText, 0); - const expected = [ - '- [x] Parent', - ' - [x] Child 1', // Changed (indent 2 > 0) - ' - [x] Sibling C1', // <<-- ALSO Changed (indent 2 > 0) - '- [ ] Parent 2' // Unchanged (indent 0 <= 0) - ].join('\n'); - expect(result).toBe(expected); // Проверяем обновленное ожидание - }); - }); - - // --- propagateStateFromChildren --- - describe('propagateStateFromChildren', () => { - it('should check parent if all children are checked', () => { - const text = [ - '- [ ] Parent', // Should become [x] - ' - [x] Child 1', - ' - [x] Child 2', - ' - [x] Grandchild 2.1', // Needs to be checked too - ].join('\n'); - const result = utilsDefault.propagateStateFromChildren(text); - const expected = [ - '- [x] Parent', // Updated - ' - [x] Child 1', - ' - [x] Child 2', - ' - [x] Grandchild 2.1', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should uncheck parent if any child is unchecked', () => { - const text = [ - '- [x] Parent', // Should become [ ] - ' - [x] Child 1', - ' - [ ] Child 2', // This one makes parent unchecked - ].join('\n'); - const result = utilsDefault.propagateStateFromChildren(text); - const expected = [ - '- [ ] Parent', // Updated - ' - [x] Child 1', - ' - [ ] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should handle nested propagation', () => { - const text = [ - '- [ ] Grandparent', // Should become [ ] - ' - [x] Parent 1', // Should become [ ] - ' - [x] Child 1.1', - ' - [ ] Child 1.2', // Makes Parent 1 unchecked - ' - [x] Parent 2', // Stays checked - ' - [x] Child 2.1', - ].join('\n'); - const result = utilsDefault.propagateStateFromChildren(text); - const expected = [ - '- [ ] Grandparent', // Updated (because Parent 1 is now [ ]) - ' - [ ] Parent 1', // Updated - ' - [x] Child 1.1', - ' - [ ] Child 1.2', - ' - [x] Parent 2', - ' - [x] Child 2.1', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should not change parent state if it has no children', () => { - const text = [ - '- [ ] Parent No Children', - '- [x] Parent No Children Checked', - ' Some other line', - ].join('\n'); - const result = utilsDefault.propagateStateFromChildren(text); - expect(result).toBe(text); // No changes expected - }); - - it('should work correctly with xOnlyMode', () => { - const text = [ - '- [ ] Parent', // Should become [x] in xOnly mode - ' - [x] Child 1', - ' - [x] Child 2' - ].join('\n'); - const result = utilsXOnly.propagateStateFromChildren(text); - expect(result).toContain('- [x] Parent'); // Parent checked - - const text2 = [ - '- [x] Parent', // Should become [ ] because '?' is not 'x' - ' - [x] Child 1', - ' - [?] Child 2' - ].join('\n'); - const result2 = utilsXOnly.propagateStateFromChildren(text2); - expect(result2).toContain('- [ ] Parent'); // Parent unchecked - }); - - it('should handle multiple independent parent checklists', () => { - const text = [ - '- [ ] Parent 1', - ' - [x] Child 1.1', - 'Some other text', - '* [ ] Parent 2', - ' * [x] Child 2.1', - ' * [x] Child 2.2', - ].join('\n'); - const result = utilsDefault.propagateStateFromChildren(text); - const expected = [ - '- [x] Parent 1', // Updated - ' - [x] Child 1.1', - 'Some other text', - '* [x] Parent 2', // Updated - ' * [x] Child 2.1', - ' * [x] Child 2.2', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should stop searching for children for a parent when a line with equal or lesser indent is encountered', () => { - const text = [ - '- [ ] Parent 1', // indent 0 - 'Some regular text', // indent 0. Stops search for Parent 1 children HERE. - ' - [x] Child 1.1', // indent 2. Ignored for Parent 1 because search stopped. - '- [ ] Parent 2', // indent 0. Processed independently later. - ' - [x] Child 2.1', // indent 2. Child of Parent 2. - ].join('\n'); - - const result = utilsDefault.propagateStateFromChildren(text); - - // Ожидания: - // - Parent 1 не найдет детей (из-за "Some regular text") и останется [ ]. - // - Parent 2 найдет Child 2.1 ([x]) и станет [x]. - const expected = [ - '- [ ] Parent 1', // Unchanged - 'Some regular text', // Unchanged - ' - [x] Child 1.1', // Unchanged - '- [x] Parent 2', // Updated because Child 2.1 is checked - ' - [x] Child 2.1', // Unchanged - ].join('\n'); - - expect(result).toBe(expected); - }); - - it('should stop searching for children when a sibling checkbox is encountered', () => { - const text = [ - '- [ ] Parent 1', // indent 0 - '- [ ] Sibling Parent', // indent 0. Stops search for Parent 1 children HERE. - ' - [x] Child 1.1', // indent 2. Belongs to Sibling Parent if processed. - ].join('\n'); - - const result = utilsDefault.propagateStateFromChildren(text); - - // Ожидания: - // - Parent 1 не найдет детей (из-за "Sibling Parent") и останется [ ]. - // - Sibling Parent найдет Child 1.1 ([x]) и станет [x]. - const expected = [ - '- [ ] Parent 1', // Unchanged - '- [x] Sibling Parent', // Updated because Child 1.1 is checked - ' - [x] Child 1.1', // Unchanged - ].join('\n'); - - expect(result).toBe(expected); - }); - - }); - - // --- propagateStateToChildrenFromSingleDiff --- - describe('propagateStateToChildrenFromSingleDiff', () => { - const textBefore = [ - '- [ ] Parent', // 0 - ' - [ ] Child', // 1 - ].join('\n'); - - it('should propagate down if single line changed and it was a checkbox toggle', () => { - const textAfter = [ - '- [x] Parent', // Changed state - ' - [ ] Child', - ].join('\n'); - const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore); - const expected = [ // Expect children to be updated - '- [x] Parent', - ' - [x] Child', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should NOT propagate down if single line changed but it was NOT checkbox state', () => { - const textAfter = [ - '- [ ] Parent Edited Text', // Text changed, not checkbox - ' - [ ] Child', - ].join('\n'); - const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore); - // Expect original textAfter, no downward propagation - expect(result).toBe(textAfter); - }); - - it('should NOT propagate down if single line changed into non-checkbox', () => { - const textAfter = [ - 'Parent removed checkbox', // Structure changed - ' - [ ] Child', - ].join('\n'); - const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore); - expect(result).toBe(textAfter); - }); - - - it('should NOT propagate down if multiple lines changed', () => { - const textAfter = [ - '- [x] Parent', // Changed - ' - [x] Child', // Changed - ].join('\n'); - const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore); - expect(result).toBe(textAfter); // Expect original textAfter - }); - - it('should NOT propagate down if line counts differ', () => { - const textAfter = [ - '- [x] Parent', - ' - [ ] Child', - ' - [ ] New Child', - ].join('\n'); - const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore); - expect(result).toBe(textAfter); // Expect original textAfter - }); - - it('should NOT propagate down if textBefore is undefined', () => { - const textAfter = [ - '- [x] Parent', - ' - [ ] Child', - ].join('\n'); - const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, undefined); - expect(result).toBe(textAfter); // Expect original textAfter - }); - }); - - // --- findDifferentLineIndexes --- - describe('findDifferentLineIndexes', () => { - const lines1 = ['a', 'b', 'c']; - - it('should return empty array for identical lines', () => { - const lines2 = ['a', 'b', 'c']; - expect(utilsDefault.findDifferentLineIndexes(lines1, lines2)).toEqual([]); - }); - - it('should return index of the single different line', () => { - const lines2 = ['a', 'B', 'c']; - expect(utilsDefault.findDifferentLineIndexes(lines1, lines2)).toEqual([1]); - }); - - it('should return indexes of multiple different lines', () => { - const lines2 = ['A', 'b', 'C']; - expect(utilsDefault.findDifferentLineIndexes(lines1, lines2)).toEqual([0, 2]); - }); - - it('should throw error if line lengths are different', () => { - const lines2 = ['a', 'b']; - expect(() => utilsDefault.findDifferentLineIndexes(lines1, lines2)).toThrow( - 'the length of the lines must be equal' - ); - }); - }); - - - // --- syncText (Integration) --- - describe('syncText', () => { - describe('When user changes parent state directly', () => { - const textBefore = [ - '- [ ] Parent', - ' - [ ] Child 1', - ' - [ ] Child 2', - ].join('\n'); - const textAfterUserCheck = [ - '- [x] Parent', // User checked this - ' - [ ] Child 1', - ' - [ ] Child 2', - ].join('\n'); - - it('should propagate down when child sync is ENABLED', () => { - const result = utilsDefault.syncText(textAfterUserCheck, textBefore); - const expected = [ - '- [x] Parent', - ' - [x] Child 1', // Propagated down - ' - [x] Child 2', // Propagated down - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should NOT propagate down when child sync is DISABLED', () => { - const result = utilsChildSyncDisabled.syncText(textAfterUserCheck, textBefore); - const expected = [ // Ожидаем, что дети НЕ изменились - '- [ ] Parent', // propagateStateFromChildren - ' - [ ] Child 1', - ' - [ ] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - const textBeforeUncheck = [ - '- [x] Parent', - ' - [x] Child 1', - ' - [x] Child 2', - ].join('\n'); - const textAfterUserUncheck = [ - '- [ ] Parent', // User unchecked this - ' - [x] Child 1', - ' - [x] Child 2', - ].join('\n'); - - it('should propagate down (uncheck) when child sync is ENABLED', () => { - const result = utilsDefault.syncText(textAfterUserUncheck, textBeforeUncheck); - const expected = [ - '- [ ] Parent', - ' - [ ] Child 1', // Propagated down - ' - [ ] Child 2', // Propagated down - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should NOT propagate down (uncheck) when child sync is DISABLED', () => { - const result = utilsChildSyncDisabled.syncText(textAfterUserUncheck, textBeforeUncheck); - const expected = [ // Ожидаем, что дети НЕ изменились - '- [x] Parent', // propagateStateFromChildren - ' - [x] Child 1', - ' - [x] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - }); - - it('should update parent when parent sync is ENABLED and user checks last child', () => { - const textBefore = [ - '- [ ] Parent', - ' - [x] Child 1', - ' - [ ] Child 2', // User will check this - ].join('\n'); - const textAfterUserCheck = [ - '- [ ] Parent', - ' - [x] Child 1', - ' - [x] Child 2', // User checked this - ].join('\n'); - - const result = utilsDefault.syncText(textAfterUserCheck, textBefore); - // SingleDiff won't trigger down prop. FromChildren runs. - const expected = [ - '- [x] Parent', // Updated by FromChildren - ' - [x] Child 1', - ' - [x] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should NOT update parent when parent sync is DISABLED and user checks last child', () => { - const textBefore = [ - '- [ ] Parent', - ' - [x] Child 1', - ' - [ ] Child 2', // User will check this - ].join('\n'); - const textAfterChildCheck = [ - '- [ ] Parent', // State before syncText runs propagateFromChildren - ' - [x] Child 1', - ' - [x] Child 2', // User checked this - ].join('\n'); - const result = utilsParentSyncDisabled.syncText(textAfterChildCheck, textBefore); // Использует utilsParentSyncDisabled - const expected = [ // Родитель НЕ должен обновиться - '- [ ] Parent', - ' - [x] Child 1', - ' - [x] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should update parent when parent sync is ENABLED and user unchecks child', () => { - const textBefore = [ - '- [x] Parent', - ' - [x] Child 1', // User will uncheck this - ' - [x] Child 2', - ].join('\n'); - const textAfterUserUncheck = [ - '- [x] Parent', - ' - [ ] Child 1', // User unchecked this - ' - [x] Child 2', - ].join('\n'); - - const result = utilsDefault.syncText(textAfterUserUncheck, textBefore); - // SingleDiff won't trigger down prop. FromChildren runs. - const expected = [ - '- [ ] Parent', // Updated by FromChildren - ' - [ ] Child 1', - ' - [x] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should NOT update parent when parent sync is DISABLED and user unchecks child', () => { - const textBefore = [ - '- [x] Parent', - ' - [x] Child 1', // User will uncheck this - ' - [x] Child 2', - ].join('\n'); - const textAfterChildUncheck = [ - '- [x] Parent', // State before syncText runs propagateFromChildren - ' - [ ] Child 1', // User unchecked this - ' - [x] Child 2', - ].join('\n'); - const result = utilsParentSyncDisabled.syncText(textAfterChildUncheck, textBefore); // utilsParentSyncDisabled - const expected = [ // Родитель НЕ должен обновиться - '- [x] Parent', - ' - [ ] Child 1', - ' - [x] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should update parent based on children when multiple lines change and parent sync is ENABLED', () => { - const textBefore = [ - '- [ ] Parent', - ' - [ ] Child 1', - ' - [ ] Child 2', - ' - [ ] Child 3', - ].join('\n'); - // User checks Parent AND Child 1, but leaves Child 2 unchecked - const textAfterMultipleChanges = [ - '- [x] Parent', // Changed by user - ' - [x] Child 1', // Changed by user - ' - [ ] Child 2', // Left unchecked - ' - [ ] Child 3', // Left unchecked - ].join('\n'); - - const result = utilsDefault.syncText(textAfterMultipleChanges, textBefore); - // SingleDiff won't run (2 changes). FromChildren runs. - // Since Child 2/3 are unchecked, Parent should become unchecked. - const expected = [ - '- [ ] Parent', // Corrected by FromChildren - ' - [x] Child 1', - ' - [ ] Child 2', - ' - [ ] Child 3', - ].join('\n'); - expect(result).toBe(expected); - }); - - it('should NOT update parent when multiple lines change and parent sync is DISABLED', () => { - const textBefore = [ - '- [ ] Parent', - ' - [ ] Child 1', - ' - [ ] Child 2', - ].join('\n'); - const textAfterMultipleChanges = [ // User checks Parent and Child 1 - '- [x] Parent', - ' - [x] Child 1', - ' - [ ] Child 2', - ].join('\n'); - const result = utilsParentSyncDisabled.syncText(textAfterMultipleChanges, textBefore); // utilsParentSyncDisabled - const expected = [ // Родитель НЕ корректируется, остается как есть - '- [x] Parent', - ' - [x] Child 1', - ' - [ ] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); - - }); -}); \ No newline at end of file diff --git a/__tests__/findCheckboxesLine.test.ts b/__tests__/findCheckboxesLine.test.ts deleted file mode 100644 index 83bcc1f..0000000 --- a/__tests__/findCheckboxesLine.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { CheckboxUtils } from "../src/checkboxUtils"; - -describe("CheckboxUtils old", () => { - let checkboxUtilsXOnly: CheckboxUtils; - let checkboxUtilsSpaceOnly: CheckboxUtils; - - beforeEach(() => { - checkboxUtilsXOnly = new CheckboxUtils({ xOnlyMode: true, enableAutomaticParentState: true, enableAutomaticChildState: true, }); - checkboxUtilsSpaceOnly = new CheckboxUtils({ xOnlyMode: false, enableAutomaticParentState: true, enableAutomaticChildState: true,}); - }); - - describe("findCheckboxesLine", () => { - test("recognizes valid checkbox lines", () => { - expect(checkboxUtilsXOnly.matchCheckboxLine("- [ ] Task")).not.toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine(" - [ ] Task")).not.toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("- [x] Task")).not.toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("* [ ] Another task")).not.toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("+ [x] Task")).not.toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("1. [ ] Numbered task")).not.toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("1. [x] Numbered task")).not.toBeNull(); - }); - - test("correctly handles different checkbox states", () => { - const states = [" ", "x", "-", "?", "!", "*", "#", "@", "[", "]", "y", "X", "Y"]; - - for (const state of states) { - const lineInfo = checkboxUtilsXOnly.matchCheckboxLine(`- [${state}] Task`); - expect(lineInfo).not.toBeNull(); - expect(lineInfo?.checkChar).toBe(state); - } - }); - - test("correctly handles indentation", () => { - const noIndentMatch = checkboxUtilsXOnly.matchCheckboxLine("- [ ] Task"); - expect(noIndentMatch).not.toBeNull(); - expect(noIndentMatch!.indent).toBe("".length); - - const singleSpaceMatch = checkboxUtilsXOnly.matchCheckboxLine(" - [ ] Task"); - expect(singleSpaceMatch).not.toBeNull(); - expect(singleSpaceMatch!.indent).toBe(" ".length); - - const multiSpaceMatch = checkboxUtilsXOnly.matchCheckboxLine(" - [ ] Task"); - expect(multiSpaceMatch).not.toBeNull(); - expect(multiSpaceMatch!.indent).toBe(" ".length); - - const tabMatch = checkboxUtilsXOnly.matchCheckboxLine("\t- [ ] Task"); - expect(tabMatch).not.toBeNull(); - expect(tabMatch!.indent).toBe("\t".length); - }); - - test("ignores invalid lines", () => { - expect(checkboxUtilsXOnly.matchCheckboxLine("-[ ] Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("*[x] Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("1.[ ] Numbered task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("- [] Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine(" - Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("Just some text")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("1. Some numbered task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("")).toBeNull(); - }); - - test("ignores lines with missing opening or closing brackets", () => { - // Missing opening bracket [ - expect(checkboxUtilsXOnly.matchCheckboxLine("- ] Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("* ] Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("+ ] Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("1. ] Task")).toBeNull(); - - // Missing closing bracket ] - expect(checkboxUtilsXOnly.matchCheckboxLine("- [x Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("* [ Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("+ [- Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("1. [? Task")).toBeNull(); - - // Malformed with spaces - expect(checkboxUtilsXOnly.matchCheckboxLine("- [ x Task")).toBeNull(); - expect(checkboxUtilsXOnly.matchCheckboxLine("- x] Task")).toBeNull(); - }); - - test("extracts list marker correctly", () => { - const dashMatch = checkboxUtilsXOnly.matchCheckboxLine("- [ ] Task"); - expect(dashMatch).not.toBeNull(); - expect(dashMatch!.marker).toBe("-"); - - const asteriskMatch = checkboxUtilsXOnly.matchCheckboxLine("* [ ] Task"); - expect(asteriskMatch).not.toBeNull(); - expect(asteriskMatch!.marker).toBe("*"); - - const plusMatch = checkboxUtilsXOnly.matchCheckboxLine("+ [ ] Task"); - expect(plusMatch).not.toBeNull(); - expect(plusMatch!.marker).toBe("+"); - - const numberedMatch = checkboxUtilsXOnly.matchCheckboxLine("1. [ ] Task"); - expect(numberedMatch).not.toBeNull(); - expect(numberedMatch!.marker).toBe("1."); - }); - }); - - describe("isCheckedSymbol", () => { - test("in xOnlyMode only 'x' is considered checked", () => { - expect(checkboxUtilsXOnly.isCheckedSymbol("x")).toBe(true); - expect(checkboxUtilsXOnly.isCheckedSymbol(" ")).toBe(false); - expect(checkboxUtilsXOnly.isCheckedSymbol("-")).toBe(false); - expect(checkboxUtilsXOnly.isCheckedSymbol("?")).toBe(false); - }); - - test("in normal mode all symbols except space are considered checked", () => { - expect(checkboxUtilsSpaceOnly.isCheckedSymbol("x")).toBe(true); - expect(checkboxUtilsSpaceOnly.isCheckedSymbol(" ")).toBe(false); - expect(checkboxUtilsSpaceOnly.isCheckedSymbol("-")).toBe(true); - expect(checkboxUtilsSpaceOnly.isCheckedSymbol("?")).toBe(true); - }); - - test("handles uppercase 'X' correctly", () => { - expect(checkboxUtilsXOnly.isCheckedSymbol("X")).toBe(false); - expect(checkboxUtilsSpaceOnly.isCheckedSymbol("X")).toBe(true); - }); - }); - - describe("updateSettings", () => { - test("updates xOnlyMode setting correctly", () => { - - const settings = { xOnlyMode: true, enableAutomaticParentState: true, enableAutomaticChildState: true,}; - const utils = new CheckboxUtils(settings); - expect(utils.isCheckedSymbol("-")).toBe(false); - - settings.xOnlyMode = false; - expect(utils.isCheckedSymbol("-")).toBe(true); - - settings.xOnlyMode = true; - expect(utils.isCheckedSymbol("-")).toBe(false); - }); - }); - - describe("syncCheckboxes", () => { - test("updates parent checkbox if all children are checked (xOnlyMode)", () => { - const text = "- [ ] Parent\n - [x] Child 1\n - [x] Child 2"; - const expected = "- [x] Parent\n - [x] Child 1\n - [x] Child 2"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("updates parent checkbox if all children are checked (spaceOnly)", () => { - const text = "- [ ] Parent\n - [?] Child 1\n - [-] Child 2"; - const expected = "- [x] Parent\n - [?] Child 1\n - [-] Child 2"; - expect(checkboxUtilsSpaceOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("unchecks parent if not all children are checked", () => { - const text = "- [x] Parent\n - [x] Child 1\n - [ ] Child 2"; - const expected = "- [ ] Parent\n - [x] Child 1\n - [ ] Child 2"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("doesn't change parent if it has no children", () => { - const text = "- [x] Single"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(text); - }); - - test("handles multiple levels of nesting correctly", () => { - const text = "- [ ] Parent\n - [ ] Child\n - [x] Grandchild"; - const expected = "- [x] Parent\n - [x] Child\n - [x] Grandchild"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("handles mixed indentation levels correctly", () => { - const text = "- [ ] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [x] Child 2"; - const expected = "- [x] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [x] Child 2"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("handles siblings with different indentation correctly", () => { - const text = "- [ ] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [ ] Child 2"; - const expected = "- [ ] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [ ] Child 2"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("handles multiple parents correctly", () => { - const text = "- [ ] Parent 1\n - [x] Child 1\n- [ ] Parent 2\n - [x] Child 2"; - const expected = "- [x] Parent 1\n - [x] Child 1\n- [x] Parent 2\n - [x] Child 2"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - - test("handles mixed content with non-checkbox lines correctly", () => { - const text = "- [ ] Parent\nSome regular text\n - [x] Child 1\nMore text\n - [x] Child 2"; - const expected = "- [ ] Parent\nSome regular text\n - [x] Child 1\nMore text\n - [x] Child 2"; - expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected); - }); - }); -});