mirror of
https://github.com/groldsf/obsidian_check_plugin.git
synced 2026-07-22 05:37:48 +00:00
refactor(settings): Add EnableParentSync component and testable validator
This commit is contained in:
parent
0f4b53f8dd
commit
840a50ae41
6 changed files with 212 additions and 4 deletions
70
__tests__/ui/components/validation/validators.test.ts
Normal file
70
__tests__/ui/components/validation/validators.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { validateValueIsBoolean } from "src/ui/validation/validators";
|
||||
|
||||
|
||||
// Группа тестов для всего файла валидаторов
|
||||
describe('Validation Utility Functions', () => {
|
||||
|
||||
// Подгруппа тестов для конкретной функции validateValueIsBoolean
|
||||
describe('validateValueIsBoolean', () => {
|
||||
|
||||
// Тест кейсы для валидных значений (должны возвращать null)
|
||||
test('should return null for true', () => {
|
||||
expect(validateValueIsBoolean(true)).toBeNull();
|
||||
});
|
||||
|
||||
test('should return null for false', () => {
|
||||
expect(validateValueIsBoolean(false)).toBeNull();
|
||||
});
|
||||
|
||||
// Тест кейсы для невалидных значений (должны возвращать объект ошибки)
|
||||
test('should return error object for null', () => {
|
||||
const expectedError = { message: 'Value must be a boolean.' };
|
||||
expect(validateValueIsBoolean(null)).toEqual(expectedError);
|
||||
});
|
||||
|
||||
test('should return error object for undefined', () => {
|
||||
const expectedError = { message: 'Value must be a boolean.' };
|
||||
expect(validateValueIsBoolean(undefined)).toEqual(expectedError);
|
||||
});
|
||||
|
||||
test('should return error object for numbers', () => {
|
||||
const expectedError = { message: 'Value must be a boolean.' };
|
||||
expect(validateValueIsBoolean(0)).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean(1)).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean(123)).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean(-10)).toEqual(expectedError);
|
||||
});
|
||||
|
||||
test('should return error object for strings', () => {
|
||||
const expectedError = { message: 'Value must be a boolean.' };
|
||||
expect(validateValueIsBoolean('')).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean('true')).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean('false')).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean('any string')).toEqual(expectedError);
|
||||
});
|
||||
|
||||
test('should return error object for objects', () => {
|
||||
const expectedError = { message: 'Value must be a boolean.' };
|
||||
expect(validateValueIsBoolean({})).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean({ a: 1 })).toEqual(expectedError);
|
||||
});
|
||||
|
||||
test('should return error object for arrays', () => {
|
||||
const expectedError = { message: 'Value must be a boolean.' };
|
||||
expect(validateValueIsBoolean([])).toEqual(expectedError);
|
||||
expect(validateValueIsBoolean([true])).toEqual(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
// --- Сюда можно будет добавлять describe-блоки для других функций валидации ---
|
||||
/*
|
||||
describe('validateJsonStringArray', () => {
|
||||
// ... тесты для валидации JSON ...
|
||||
});
|
||||
|
||||
describe('validateNotEmptyArray', () => {
|
||||
// ... тесты для проверки на пустой массив ...
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
||||
|
|
@ -36,6 +36,12 @@ const config: Config = {
|
|||
// Limit the number of workers that run tests in parallel
|
||||
maxWorkers: 1, // <--- Задает последовательное выполнение
|
||||
|
||||
moduleNameMapper: {
|
||||
// Этот паттерн говорит: если импорт начинается с 'src/',
|
||||
// замени 'src/' на '<rootDir>/src/' при поиске файла.
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Setting } from 'obsidian';
|
||||
import { CheckboxSyncPluginSettings } from '../../types';
|
||||
import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from '../../types';
|
||||
import { ISettingComponent } from '../interfaces/ISettingComponent';
|
||||
import { ValidationError } from '../validation/types';
|
||||
// Убрали App и CheckboxSyncPlugin из импортов, если они не нужны ВСЕМ наследникам
|
||||
|
|
@ -13,13 +13,26 @@ export abstract class BaseSettingComponent implements ISettingComponent {
|
|||
|
||||
// --- Методы для реализации наследниками ---
|
||||
abstract getSettingKey(): keyof CheckboxSyncPluginSettings;
|
||||
abstract getDefaultValue(): any;
|
||||
// render теперь должен сам создавать Setting и сохранять ссылку в this.setting
|
||||
abstract render(container: HTMLElement, currentValue: any): void;
|
||||
abstract getValueFromUi(): any;
|
||||
abstract setValueInUi(value: any): void;
|
||||
abstract validate(value: any): ValidationError | null;
|
||||
abstract validate(): ValidationError | null;
|
||||
|
||||
|
||||
public getDefaultValue(): any {
|
||||
const key = this.getSettingKey(); // Получаем ключ от наследника
|
||||
if (key in DEFAULT_SETTINGS) {
|
||||
// Возвращаем значение из импортированного объекта
|
||||
return DEFAULT_SETTINGS[key];
|
||||
} else {
|
||||
// Логируем предупреждение, если ключ не найден (ошибка конфигурации)
|
||||
console.warn(
|
||||
`[${this.constructor.name}] Default value for key "${key}" not found in DEFAULT_SETTINGS. Returning undefined.`
|
||||
);
|
||||
return undefined; // Или null, или выбросить ошибку, но undefined безопаснее
|
||||
}
|
||||
}
|
||||
// --- Общая реализация ---
|
||||
public setChangeListener(listener: () => void): void {
|
||||
this.onChangeCallback = listener;
|
||||
|
|
|
|||
70
src/ui/components/EnableParentSyncSettingComponent.ts
Normal file
70
src/ui/components/EnableParentSyncSettingComponent.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { Setting, ToggleComponent } from 'obsidian';
|
||||
import { CheckboxSyncPluginSettings } from '../../types';
|
||||
import { BaseSettingComponent } from './BaseSettingComponent';
|
||||
import { ValidationError } from '../validation/types';
|
||||
import { validateValueIsBoolean } from '../validation/validators';
|
||||
|
||||
export class EnableParentSyncSettingComponent extends BaseSettingComponent {
|
||||
private toggleComponent: ToggleComponent;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getSettingKey(): keyof CheckboxSyncPluginSettings {
|
||||
return 'enableAutomaticParentState'; // Ключ этой настройки
|
||||
}
|
||||
|
||||
render(container: HTMLElement, currentValue: any): void {
|
||||
|
||||
this.setting = new Setting(container)
|
||||
.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 => {
|
||||
this.toggleComponent = toggle;
|
||||
// Используем as boolean, т.к. знаем тип для этой настройки
|
||||
toggle
|
||||
.setValue(currentValue as boolean)
|
||||
.onChange(this.onChangeCallback); // Просто передаем колбэк
|
||||
});
|
||||
}
|
||||
|
||||
getValueFromUi(): boolean {
|
||||
if (this.toggleComponent) {
|
||||
return this.toggleComponent.getValue();
|
||||
}
|
||||
console.warn(`getValueFromUi called before render for ${this.getSettingKey()}`);
|
||||
// Используем унаследованный getDefaultValue() для консистентности
|
||||
return this.getDefaultValue();
|
||||
}
|
||||
|
||||
setValueInUi(value: any): void {
|
||||
if (this.toggleComponent) {
|
||||
this.toggleComponent.setValue(value as boolean);
|
||||
}
|
||||
}
|
||||
|
||||
validate(): ValidationError | null {
|
||||
let valueFromUi: boolean;
|
||||
try {
|
||||
valueFromUi = this.getValueFromUi();
|
||||
} catch (error) {
|
||||
console.error(`Error getting value from UI for ${this.getSettingKey()}:`, error);
|
||||
return {
|
||||
field: this.getSettingKey(),
|
||||
message: `Internal error getting value from UI: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
|
||||
// Вызываем внешнюю чистую функцию валидации
|
||||
const validationResult = validateValueIsBoolean(valueFromUi);
|
||||
|
||||
if (validationResult) {
|
||||
return {
|
||||
field: this.getSettingKey(),
|
||||
message: validationResult.message
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ export interface ISettingComponent {
|
|||
* @param value - Чистое значение для валидации (обычно результат getValueFromUi).
|
||||
* @returns Объект ValidationError, если значение невалидно, иначе null.
|
||||
*/
|
||||
validate(value: any): ValidationError | null;
|
||||
validate(): ValidationError | null;
|
||||
|
||||
/**
|
||||
* Устанавливает колбэк, который будет вызван при изменении значения в UI.
|
||||
|
|
|
|||
49
src/ui/validation/validators.ts
Normal file
49
src/ui/validation/validators.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// src/settings/validation/validators.ts
|
||||
|
||||
/**
|
||||
* Проверяет, является ли предоставленное значение булевым типом (boolean).
|
||||
* Эта функция не зависит от контекста Obsidian или конкретного компонента.
|
||||
*
|
||||
* @param value - Значение любого типа для проверки.
|
||||
* @returns Объект с сообщением об ошибке, если значение не является boolean, иначе null.
|
||||
*/
|
||||
export function validateValueIsBoolean(value: any): { message: string } | null {
|
||||
if (typeof value !== 'boolean') {
|
||||
return { message: 'Value must be a boolean.' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Сюда в будущем можно будет добавлять другие чистые функции валидации ---
|
||||
|
||||
/*
|
||||
// Пример для будущей валидации JSON-массива строк
|
||||
export function validateJsonStringArray(rawJson: string): { message: string } | null {
|
||||
try {
|
||||
const parsed = JSON.parse(rawJson);
|
||||
if (!Array.isArray(parsed)) {
|
||||
return { message: 'Input must be a valid JSON array (e.g., ["x", " "]).' };
|
||||
}
|
||||
for (let i = 0; i < parsed.length; i++) {
|
||||
const element = parsed[i];
|
||||
if (typeof element !== 'string') {
|
||||
return { message: `Array element at index ${i} is not a string.` };
|
||||
}
|
||||
if (element.length !== 1) {
|
||||
return { message: `Element "${element}" at index ${i} must be a single character.` };
|
||||
}
|
||||
}
|
||||
return null; // Все проверки пройдены
|
||||
} catch (e: any) {
|
||||
return { message: `Invalid JSON format: ${e.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
// Пример для проверки, что массив не пустой
|
||||
export function validateNotEmptyArray(arr: any[]): { message: string } | null {
|
||||
if (!Array.isArray(arr) || arr.length === 0) {
|
||||
return { message: 'The list cannot be empty.' };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
Loading…
Reference in a new issue