From 3d1ea000098fe5fcbaa5f82b765d1aeb386a5672 Mon Sep 17 00:00:00 2001 From: Grol Grol Date: Fri, 11 Apr 2025 21:09:29 +0300 Subject: [PATCH] add settings --- __tests__/checkboxUtils.test.ts | 179 +++++++++++++++++++++------ __tests__/findCheckboxesLine.test.ts | 6 +- src/CheckboxSyncPluginSettingTab.ts | 24 +++- src/checkboxUtils.ts | 10 +- src/main.ts | 2 + src/types.ts | 2 + 6 files changed, 178 insertions(+), 45 deletions(-) diff --git a/__tests__/checkboxUtils.test.ts b/__tests__/checkboxUtils.test.ts index ae05310..c1c1c16 100644 --- a/__tests__/checkboxUtils.test.ts +++ b/__tests__/checkboxUtils.test.ts @@ -1,25 +1,45 @@ // checkboxUtils.test.ts -import { CheckboxUtils, CheckboxLineInfo } from '../src/checkboxUtils'; +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 --- @@ -453,49 +473,72 @@ describe('CheckboxUtils', () => { // --- syncText (Integration) --- describe('syncText', () => { - it('should handle user checking parent (down -> up)', () => { + 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'); + '- [ ] Parent', + ' - [ ] Child 1', + ' - [ ] Child 2', + ].join('\n'); + const textAfterUserCheck = [ + '- [x] Parent', // User checked this + ' - [ ] Child 1', + ' - [ ] Child 2', + ].join('\n'); - const result = utilsDefault.syncText(textAfterUserCheck, textBefore); - const expected = [ // Down propagation first, then up (which changes nothing here) - '- [x] Parent', - ' - [x] Child 1', - ' - [x] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); + 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 handle user unchecking parent (down -> up)', () => { - const textBefore = [ - '- [x] Parent', - ' - [x] Child 1', - ' - [x] Child 2', + 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', + '- [ ] Parent', // User unchecked this + ' - [x] Child 1', + ' - [x] Child 2', ].join('\n'); - const result = utilsDefault.syncText(textAfterUserUncheck, textBefore); - const expected = [ // Down propagation first, then up - '- [ ] Parent', - ' - [ ] Child 1', - ' - [ ] Child 2', - ].join('\n'); - expect(result).toBe(expected); - }); + 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 handle user checking the last child, making parent checked (no down -> up)', () => { + 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', @@ -517,7 +560,27 @@ describe('CheckboxUtils', () => { expect(result).toBe(expected); }); - it('should handle user unchecking one child, making parent unchecked (no down -> up)', () => { + 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 @@ -539,7 +602,27 @@ describe('CheckboxUtils', () => { expect(result).toBe(expected); }); - it('should handle multiple changes correctly (no down -> up)', () => { + 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', @@ -566,5 +649,25 @@ describe('CheckboxUtils', () => { 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 index cd42edb..83bcc1f 100644 --- a/__tests__/findCheckboxesLine.test.ts +++ b/__tests__/findCheckboxesLine.test.ts @@ -5,8 +5,8 @@ describe("CheckboxUtils old", () => { let checkboxUtilsSpaceOnly: CheckboxUtils; beforeEach(() => { - checkboxUtilsXOnly = new CheckboxUtils({ xOnlyMode: true }); - checkboxUtilsSpaceOnly = new CheckboxUtils({ xOnlyMode: false }); + checkboxUtilsXOnly = new CheckboxUtils({ xOnlyMode: true, enableAutomaticParentState: true, enableAutomaticChildState: true, }); + checkboxUtilsSpaceOnly = new CheckboxUtils({ xOnlyMode: false, enableAutomaticParentState: true, enableAutomaticChildState: true,}); }); describe("findCheckboxesLine", () => { @@ -120,7 +120,7 @@ describe("CheckboxUtils old", () => { describe("updateSettings", () => { test("updates xOnlyMode setting correctly", () => { - const settings = { xOnlyMode: true }; + const settings = { xOnlyMode: true, enableAutomaticParentState: true, enableAutomaticChildState: true,}; const utils = new CheckboxUtils(settings); expect(utils.isCheckedSymbol("-")).toBe(false); diff --git a/src/CheckboxSyncPluginSettingTab.ts b/src/CheckboxSyncPluginSettingTab.ts index a555069..f6389b0 100644 --- a/src/CheckboxSyncPluginSettingTab.ts +++ b/src/CheckboxSyncPluginSettingTab.ts @@ -20,10 +20,32 @@ export class CheckboxSyncPluginSettingTab extends PluginSettingTab { toggle .setValue(this.plugin.settings.xOnlyMode) .onChange(async (value) => { - await this.plugin.updateSettings((settings) => { + await this.plugin.updateSettings(settings => { settings.xOnlyMode = value; }); }) ); + + new Setting(containerEl) + .setName('Update parent checkbox state automatically') + .setDesc('If enabled, the state of a parent checkbox will be automatically updated based on the state of its children (all children checked = parent checked). If disabled, only manually changing a parent will affect its children.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.enableAutomaticParentState) + .onChange(async (value) => { + await this.plugin.updateSettings(settings => { + settings.enableAutomaticParentState = value; + }); + })); + + new Setting(containerEl) + .setName('Update child checkbox state automatically') + .setDesc('If enabled, changing the state of a parent checkbox will automatically update the state of all its direct and nested children. If disabled, changing a parent checkbox will not affect its children.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.enableAutomaticChildState) + .onChange(async (value) => { + await this.plugin.updateSettings(settings => { + settings.enableAutomaticChildState = value; + }); + })); } } \ No newline at end of file diff --git a/src/checkboxUtils.ts b/src/checkboxUtils.ts index 159435a..0dc29a9 100644 --- a/src/checkboxUtils.ts +++ b/src/checkboxUtils.ts @@ -100,9 +100,13 @@ export class CheckboxUtils { } syncText(text: string, textBefore: string | undefined): string { - let newText; - newText = this.propagateStateToChildrenFromSingleDiff(text, textBefore); - newText = this.propagateStateFromChildren(newText); + let newText = text; + if (this.settings.enableAutomaticChildState) { + newText = this.propagateStateToChildrenFromSingleDiff(text, textBefore); + } + if (this.settings.enableAutomaticParentState) { + newText = this.propagateStateFromChildren(newText); + } return newText; } diff --git a/src/main.ts b/src/main.ts index 7a0afc5..8a17fd8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,8 @@ import { FileChangeEventHandler } from "./events/FileChangeEventHandler"; const DEFAULT_SETTINGS: CheckboxSyncPluginSettings = { xOnlyMode: true, + enableAutomaticParentState: true, + enableAutomaticChildState: true, }; export default class CheckboxSyncPlugin extends Plugin { diff --git a/src/types.ts b/src/types.ts index 641804d..ed447ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ export interface CheckboxSyncPluginSettings { xOnlyMode: boolean; + enableAutomaticParentState: boolean; + enableAutomaticChildState: boolean; } \ No newline at end of file