groldsf_obsidian_check_plugin/__tests__/TextSyncPipeline.test.ts

235 lines
11 KiB
TypeScript
Raw Normal View History

2025-07-05 11:27:48 +00:00
import { CheckboxUtils } from "src/core/checkboxUtils";
2025-05-21 12:28:04 +00:00
import { FileFilter } from "src/FileFilter";
import TextSyncPipeline from "src/TextSyncPipeline";
import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "src/types";
import { InMemoryFilePathStateHolder } from "./fakes/InMemoryFilePathStateHolder";
describe('TextSyncPipeline E2E-like Tests', () => {
let fakeFileStateHolder: InMemoryFilePathStateHolder;
let checkboxUtils: CheckboxUtils;
let fileFilter: FileFilter;
let pipeline: TextSyncPipeline;
let settings: CheckboxSyncPluginSettings;
beforeEach(() => {
// Используем настройки по умолчанию для большинства тестов,
// их можно переопределять для специфических сценариев
settings = { ...DEFAULT_SETTINGS };
fakeFileStateHolder = new InMemoryFilePathStateHolder();
checkboxUtils = new CheckboxUtils(settings);
fileFilter = new FileFilter(settings); // Инициализируем с настройками
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
});
// --- Тесты на разрешение путей и кеширование ---
describe('Path Filtering and Caching Logic', () => {
it('process: when path is NOT allowed, should return original text and cache original text', () => {
// Настраиваем FileFilter, чтобы он запрещал путь
settings.pathGlobs = ["restricted/path.md"]; // Этот путь будет игнорироваться
fileFilter.updateSettings(settings); // Обновляем фильтр с новыми глобами
const filePath = "restricted/path.md";
const currentText = "- [ ] task 1\n- [x] task 2";
// Act
const resultText = pipeline.applySyncLogic(currentText, filePath);
// Assert
// 1. Текст не должен измениться
expect(resultText).toBe(currentText);
// 2. Оригинальный текст должен быть закеширован
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(currentText);
});
it('process: when path IS allowed, but no sync changes occur, should return original text and cache original (synced) text', () => {
// Путь разрешен по умолчанию (pathGlobs пустой)
settings.pathGlobs = [];
fileFilter.updateSettings(settings);
const filePath = "allowed/path.md";
// Текст, который не будет изменен CheckboxUtils при текущих настройках
// (например, нет родительских/дочерних для пропагации, или они уже синхронизованы)
const currentText = "- [ ] task 1\n- [ ] task 2";
const previousTextInHolder = "- [ ] task 1\n- [ ] task 2"; // Предположим, предыдущее состояние было таким же
fakeFileStateHolder.setInitialStates({ [filePath]: previousTextInHolder });
// Act
const resultText = pipeline.applySyncLogic(currentText, filePath);
// Assert
// 1. Текст не должен измениться (т.к. syncText вернул то же самое)
expect(resultText).toBe(currentText);
// 2. В кеше должен быть этот же (оригинальный/синхронизированный) текст
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(currentText);
});
it('process: when path IS allowed and sync changes occur, should return modified text and cache modified text', () => {
// Путь разрешен
settings.pathGlobs = [];
fileFilter.updateSettings(settings);
// Включаем автоматическую пропагацию, чтобы изменения точно были
settings.enableAutomaticChildState = true;
checkboxUtils = new CheckboxUtils(settings); // Пересоздаем с новыми настройками
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
const filePath = "allowed/changes.md";
const currentText = "- [x] parent\n - [ ] child"; // Родитель изменен, дочерний должен измениться
const previousTextInHolder = "- [ ] parent\n - [ ] child"; // Предыдущее состояние
fakeFileStateHolder.setInitialStates({ [filePath]: previousTextInHolder });
// Act
const resultText = pipeline.applySyncLogic(currentText, filePath);
const expectedTextAfterSync = "- [x] parent\n - [x] child"; // Ожидаемый результат от CheckboxUtils
// Assert
// 1. Текст должен измениться
expect(resultText).toBe(expectedTextAfterSync);
// 2. В кеше должен быть измененный текст
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(expectedTextAfterSync);
});
it('process: when path IS allowed and file was not in state holder, should process and cache result', () => {
settings.pathGlobs = [];
fileFilter.updateSettings(settings);
settings.enableAutomaticChildState = true;
checkboxUtils = new CheckboxUtils(settings);
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
const filePath = "allowed/new_file.md";
const currentText = "- [x] parent\n - [ ] child";
// Предыдущего состояния нет в fakeFileStateHolder
// Act
const resultText = pipeline.applySyncLogic(currentText, filePath);
const expectedTextAfterSync = "- [ ] parent\n - [ ] child";
// Assert
expect(resultText).toBe(expectedTextAfterSync);
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(expectedTextAfterSync);
});
it('process: subsequent calls for the same allowed path should use updated cached state', () => {
settings.pathGlobs = [];
fileFilter.updateSettings(settings);
settings.enableAutomaticChildState = true;
checkboxUtils = new CheckboxUtils(settings);
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
const filePath = "allowed/sequential.md";
const initialText = "- [ ] parent\n - [ ] child 1";
const textAfterFirstEdit = "- [x] parent\n - [ ] child 1"; // Пользователь изменил родителя
const expectedAfterFirstPipeline = "- [x] parent\n - [x] child 1";
// Первый вызов (например, после первого изменения в редакторе)
fakeFileStateHolder.setByPath(filePath, initialText);
let resultText = pipeline.applySyncLogic(textAfterFirstEdit, filePath); // previousText будет undefined
expect(resultText).toBe(expectedAfterFirstPipeline);
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(expectedAfterFirstPipeline);
// Второе изменение (пользователь снимает галочку с родителя)
const textAfterSecondEdit = "- [ ] parent\n - [x] child 1"; // previousText теперь expectedAfterFirstPipeline
const expectedAfterSecondPipeline = "- [ ] parent\n - [ ] child 1";
resultText = pipeline.applySyncLogic(textAfterSecondEdit, filePath);
expect(resultText).toBe(expectedAfterSecondPipeline);
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(expectedAfterSecondPipeline);
});
});
// --- Можно добавить тесты на специфическое поведение CheckboxUtils через pipeline ---
// Например, если enableAutomaticParentState = true и т.д.
describe('CheckboxUtils Behavior through Pipeline (Path Allowed)', () => {
beforeEach(() => {
// Убедимся, что путь всегда разрешен для этих тестов
settings.pathGlobs = [];
fileFilter.updateSettings(settings);
});
it('process: should propagate state to children if enabled', () => {
settings.enableAutomaticChildState = true;
checkboxUtils = new CheckboxUtils(settings); // Обновляем CheckboxUtils
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
const filePath = "test.md";
const previousText = "- [ ] Parent\n - [ ] Child";
const currentText = "- [x] Parent\n - [ ] Child"; // Пользователь отметил родителя
const expectedText = "- [x] Parent\n - [x] Child";
fakeFileStateHolder.setInitialStates({ [filePath]: previousText });
const result = pipeline.applySyncLogic(currentText, filePath);
expect(result).toBe(expectedText);
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(expectedText);
});
it('process: should propagate state from children if enabled', () => {
settings.enableAutomaticParentState = true;
// Убедимся, что child state не влияет на этот тест, если он не нужен
settings.enableAutomaticChildState = false;
checkboxUtils = new CheckboxUtils(settings);
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
const filePath = "test.md";
// Предыдущее состояние не так важно, если мы не проверяем diff-логику
const currentText = "- [ ] Parent\n - [x] Child 1\n - [x] Child 2"; // Дети отмечены
const expectedText = "- [x] Parent\n - [x] Child 1\n - [x] Child 2"; // Родитель должен стать отмеченным
const result = pipeline.applySyncLogic(currentText, filePath);
expect(result).toBe(expectedText);
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(expectedText);
});
});
describe('Text content variations (Path Allowed)', () => {
beforeEach(() => {
// Убедимся, что путь всегда разрешен для этих тестов
settings.pathGlobs = [];
fileFilter.updateSettings(settings);
// Оставляем настройки CheckboxUtils по умолчанию, если не указано иное
checkboxUtils = new CheckboxUtils(settings);
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
});
it('process: text with no checkboxes should return original text and cache it', () => {
const filePath = "text_files/no_checkboxes.md";
const currentText = "This is a line.\nAnd another line without any checkboxes.";
const previousTextInHolder = "Some old text";
fakeFileStateHolder.setInitialStates({ [filePath]: previousTextInHolder });
// Act
const resultText = pipeline.applySyncLogic(currentText, filePath);
// Assert
expect(resultText).toBe(currentText);
// CheckboxUtils.syncText не должен был ничего изменить
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(currentText);
});
it('process: text with only ignored checkboxes should return original text and cache it', () => {
settings.ignoreSymbols = ["~"];
checkboxUtils = new CheckboxUtils(settings); // Обновляем с новыми ignoreSymbols
pipeline = new TextSyncPipeline(checkboxUtils, fakeFileStateHolder, fileFilter);
const filePath = "text_files/ignored_checkboxes.md";
const currentText = "- [~] An ignored task\n - [~] Another sub-task also ignored";
const previousTextInHolder = "- [~] An old ignored task";
fakeFileStateHolder.setInitialStates({ [filePath]: previousTextInHolder });
// Act
const resultText = pipeline.applySyncLogic(currentText, filePath);
// Assert
expect(resultText).toBe(currentText);
expect(fakeFileStateHolder.getInternalState(filePath)).toBe(currentText);
});
});
});