diff --git a/docs/AUDIT-PLAN.md b/docs/AUDIT-PLAN.md index 8af084c..4126b24 100644 --- a/docs/AUDIT-PLAN.md +++ b/docs/AUDIT-PLAN.md @@ -2,9 +2,11 @@ ## 1. Невыполненный пункт todo.md -- [ ] **Боковая панель** — при изменении настроек должна обновляться (или кнопка - refresh) - - `todo.md:58-60` +> ✅ **Выполнено** + +Добавлен `vscode.workspace.onDidChangeConfiguration` в `extension.ts` — при +изменении `xboctukFlow.*` настроек пересоздаются file watchers и вызывается +`tree.refresh()`. --- @@ -20,9 +22,7 @@ ## 3. Тесты на edge cases -> ✅ **Выполнено** — добавлено 14 тестов во все перечисленные модули. - -Список покрытых кейсов: +> ✅ **Выполнено** — добавлено 14 тестов + интеграционный тест. | Модуль | Что добавили | | ------------------------------- | --------------------------------------------------------------------- | @@ -33,79 +33,76 @@ | `dashboard/buildDashboardView` | пустой массив | | `dashboard/groupTasks` | неизвестный статус/приоритет | | `dashboard/filterTasks` | пустой массив | - -### 🟡 Ещё можно добавить - -| Модуль | Кейс | -| ------------------ | -------------------------------------------------------------- | -| — | Интеграционный тест: create → serialize → write → read → parse | -| `FsTaskRepository` | конфликт ID при merge задач из project + global | - -### ❓ Дизайн-вопрос: неизвестные статусы/приоритеты - -`groupTasks` молча дропает задачи, у которых `status` или `priority` не входит в -известный список (`TASK_STATUS_ORDER` / `TASK_PRIORITY_ORDER`). Такая задача -будет в `visibleTasks`, но не попадёт ни в одну группу → фактически невидима в -grouped-view. - -**Возможное решение:** добавить `TaskStatus.UNKNOWN` / `TaskPriority.UNKNOWN` и -маппить любые невалидные значения на них при парсинге в `parseTaskFile` -(`utils/markdown.ts`). Тогда groupTasks всегда найдёт такую задачу в группе -"Unknown". - -Решение требует изменений: - -- `model/taskStatus.ts` — `TaskStatus.UNKNOWN`, `TASK_STATUS_ORDER`, - `TASK_STATUS_META` -- `model/taskPriority.ts` — `TaskPriority.UNKNOWN`, `TASK_PRIORITY_ORDER`, - `TASK_PRIORITY_META` -- `utils/markdown.ts` — маппинг в `parseTaskFile` -- `validateFrontmatter.ts` — возможно, смягчить валидацию для unknown +| `FsTaskRepository` | интеграционный: create → file → parse → сверка | --- -## 4. Хрупкие тесты +## 4. rename: TODO → OPEN + UNKNOWN + +> ✅ **Выполнено** + +- `TaskStatus.TODO` → `TaskStatus.OPEN` (wire: `"open"`) +- Добавлены `TaskStatus.UNKNOWN` и `TaskPriority.UNKNOWN` +- Невалидные статусы/приоритеты при парсинге → `UNKNOWN` (решает проблему + невидимых задач в groupTasks) +- `normalizeTaskStatus` / `normalizeTaskPriority` в `utils/markdown.ts` +- `defaultHidden: true` для UNKNOWN (как backlog/cancelled) + +--- + +## 5. Хрупкие тесты + +> ✅ **Выполнено** ### Snapshot-тесты -- `model/taskStatus.test.ts` — `toMatchSnapshot()` на `TaskStatus` -- `model/taskPriority.test.ts` — `toMatchSnapshot()` на `TaskPriority` +- Оставлены `toMatchSnapshot()` как есть (сознательное решение — обновляются + через `--update`). -Ломаются от любого изменения порядка ключей. +### `_unsafeUnwrap()` → безопасный паттерн -→ Заменить на `toEqual()` с явными ожиданиями. +- `addProjectTag.test.ts` — 3 места +- `parseProjectConfig.test.ts` — 2 места +- `serializeProjectConfig.test.ts` — 2 места -### `_unsafeUnwrap()` вместо проверок isErr/isOk - -7 мест, где используется `_unsafeUnwrap()` — при падении теста вместо понятной -assertion ошибки будет `RuntimeError: called _unsafeUnwrap on an Err`: - -- `src/commands/addProjectTag.test.ts:27,28,50,63,66` -- `src/projectConfig/parseProjectConfig.test.ts:14,15,24` -- `src/projectConfig/serializeProjectConfig.test.ts:11,17` - -→ Заменить на `isErr()`/`isOk()` + `if (result.isErr()) return` паттерн. +Все заменены на `isOk()/isErr()` + ранний return. --- -## 5. Untested-модули (кроме UI/vscode-адаптеров) +## 6. Untested-модули -**`src/config.ts`** (60 строк) После слияния `frontmatterBlock.ts` — самый -крупный untested модуль. -Стоит покрыть: `getConfigSection`, `getSettingDefault`, ошибки при missing keys. +> ✅ **Выполнено** -**`src/model/taskFile.ts`** (46 строк) `isTaskFileName` + константы — можно -быстрый unit-тест. +**`config.test.ts`** — 4 теста: + +- `getConfigSection` возвращает непустую строку +- `getSettingDefault` для обоих ключей +- ошибка при missing key + +**`taskFile.test.ts`** — 6 тестов: + +- extension и language id — непустые строки +- `MAX_TASK_TITLE_LENGTH` — положительное число +- `isTaskFileName` — корректно проверяет расширение + +--- + +## 7. Сырые строки → константы в тестах + +> ✅ **Выполнено** + +Во всех тестовых файлах `"open"`/`"done"`/`"in-progress"`/`"unknown"` и +приоритеты заменены на `TaskStatus.*` / `TaskPriority.*`. Serialization-ассерты +(`toContain("status: open")`) и YAML-фикстуры оставлены как есть. --- ## Текущий статус - [x] **Рефакторинг** — `frontmatterBlock.ts` → `frontmatterFields.ts` + тесты -- [x] **Edge case тесты** — 14 тестов добавлены, все зелёные -- [ ] **Дизайн-вопрос** — решить судьбу неизвестных статусов/приоритетов -- [ ] **Хрупкие тесты** — snapshot → `toEqual`, `_unsafeUnwrap` → безопасный - паттерн -- [ ] **config.ts + taskFile.ts** — unit-тесты -- [ ] **todo.md** — refresh боковой панели -- [ ] **Интеграционный тест** — полный round-trip через файл +- [x] **Edge case тесты** — 14 тестов + интеграционный +- [x] **rename TODO → OPEN + UNKNOWN** +- [x] **Хрупкие тесты** — snapshot оставлены, `_unsafeUnwrap` заменён +- [x] **config.ts + taskFile.ts** — unit-тесты +- [x] **todo.md** — refresh боковой панели +- [x] **Сырые строки → константы** в тестах diff --git a/src/extension.ts b/src/extension.ts index 029d60e..f425a8b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,7 @@ import { TASK_FILE_EXTENSION } from "@/model/taskFile"; import { FsTaskRepository } from "@/storage/FsTaskRepository"; import { NodeFileSystem } from "@/storage/NodeFileSystem"; import { VscodeConfigProvider } from "@/storage/VscodeConfigProvider"; +import { IConfigProvider } from "@/ports"; import { registerTaskCodeActions } from "@/ui/taskCodeActions"; import { registerTaskCommands } from "@/ui/taskCommands"; import { registerTaskDecorations } from "@/ui/taskDecorations"; @@ -13,6 +14,24 @@ import { TaskDashboardPanel } from "@/views/taskDashboardPanel"; import { TaskKanbanPanel } from "@/views/taskKanbanPanel"; import { TaskTreeProvider } from "@/views/taskTreeProvider"; +function createTaskWatchers( + config: IConfigProvider, + onMutated: () => void, +): vscode.Disposable { + return vscode.Disposable.from( + ...resolveTaskLocations(config).map((location) => { + const pattern = path + .join(location.folderPath, `*${TASK_FILE_EXTENSION}`) + .replace(/\\/g, "/"); + const watcher = vscode.workspace.createFileSystemWatcher(pattern); + watcher.onDidCreate(onMutated); + watcher.onDidChange(onMutated); + watcher.onDidDelete(onMutated); + return watcher; + }), + ); +} + export function activate(context: vscode.ExtensionContext) { const fileSystem = new NodeFileSystem(); const config = new VscodeConfigProvider(); @@ -29,16 +48,21 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.registerTreeDataProvider("xboctukFlow.tasks", tree), ); - for (const location of resolveTaskLocations(config)) { - const taskPattern = path - .join(location.folderPath, `*${TASK_FILE_EXTENSION}`) - .replace(/\\/g, "/"); - const watcher = vscode.workspace.createFileSystemWatcher(taskPattern); - watcher.onDidCreate(onTasksMutated); - watcher.onDidChange(onTasksMutated); - watcher.onDidDelete(onTasksMutated); - context.subscriptions.push(watcher); - } + let watcherDisposable: vscode.Disposable = createTaskWatchers( + config, + onTasksMutated, + ); + context.subscriptions.push(watcherDisposable); + + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e) => { + if (!e.affectsConfiguration("xboctukFlow")) return; + watcherDisposable.dispose(); + watcherDisposable = createTaskWatchers(config, onTasksMutated); + context.subscriptions.push(watcherDisposable); + tree.refresh(); + }), + ); const statusBarItem = vscode.window.createStatusBarItem( vscode.StatusBarAlignment.Left, diff --git a/src/model/taskStatus.ts b/src/model/taskStatus.ts index 07f8a16..926d9ec 100644 --- a/src/model/taskStatus.ts +++ b/src/model/taskStatus.ts @@ -55,7 +55,7 @@ export const TASK_STATUS_META: Record = { }, [TaskStatus.UNKNOWN]: { label: "Unknown", - defaultHidden: false, + defaultHidden: true, icon: "question", }, }; diff --git a/todo.md b/todo.md index af0a7f8..d19df58 100644 --- a/todo.md +++ b/todo.md @@ -55,6 +55,6 @@ - сортировка по **created**: по умолчанию старые сверху (ASC), toggle Newest first -[ ] **Боковая панель** +[x] **Боковая панель** - при изменении настроек должна обновляться (или добавить кнопку refresh)