fix: update side panel on update settings
This commit is contained in:
+59
-62
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
## 1. Невыполненный пункт todo.md
|
## 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
|
## 3. Тесты на edge cases
|
||||||
|
|
||||||
> ✅ **Выполнено** — добавлено 14 тестов во все перечисленные модули.
|
> ✅ **Выполнено** — добавлено 14 тестов + интеграционный тест.
|
||||||
|
|
||||||
Список покрытых кейсов:
|
|
||||||
|
|
||||||
| Модуль | Что добавили |
|
| Модуль | Что добавили |
|
||||||
| ------------------------------- | --------------------------------------------------------------------- |
|
| ------------------------------- | --------------------------------------------------------------------- |
|
||||||
@@ -33,79 +33,76 @@
|
|||||||
| `dashboard/buildDashboardView` | пустой массив |
|
| `dashboard/buildDashboardView` | пустой массив |
|
||||||
| `dashboard/groupTasks` | неизвестный статус/приоритет |
|
| `dashboard/groupTasks` | неизвестный статус/приоритет |
|
||||||
| `dashboard/filterTasks` | пустой массив |
|
| `dashboard/filterTasks` | пустой массив |
|
||||||
|
| `FsTaskRepository` | интеграционный: create → file → parse → сверка |
|
||||||
### 🟡 Ещё можно добавить
|
|
||||||
|
|
||||||
| Модуль | Кейс |
|
|
||||||
| ------------------ | -------------------------------------------------------------- |
|
|
||||||
| — | Интеграционный тест: 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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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-тесты
|
### Snapshot-тесты
|
||||||
|
|
||||||
- `model/taskStatus.test.ts` — `toMatchSnapshot()` на `TaskStatus`
|
- Оставлены `toMatchSnapshot()` как есть (сознательное решение — обновляются
|
||||||
- `model/taskPriority.test.ts` — `toMatchSnapshot()` на `TaskPriority`
|
через `--update`).
|
||||||
|
|
||||||
Ломаются от любого изменения порядка ключей.
|
### `_unsafeUnwrap()` → безопасный паттерн
|
||||||
|
|
||||||
→ Заменить на `toEqual()` с явными ожиданиями.
|
- `addProjectTag.test.ts` — 3 места
|
||||||
|
- `parseProjectConfig.test.ts` — 2 места
|
||||||
|
- `serializeProjectConfig.test.ts` — 2 места
|
||||||
|
|
||||||
### `_unsafeUnwrap()` вместо проверок isErr/isOk
|
Все заменены на `isOk()/isErr()` + ранний return.
|
||||||
|
|
||||||
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` паттерн.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Untested-модули (кроме UI/vscode-адаптеров)
|
## 6. Untested-модули
|
||||||
|
|
||||||
**`src/config.ts`** (60 строк) После слияния `frontmatterBlock.ts` — самый
|
> ✅ **Выполнено**
|
||||||
крупный untested модуль.
|
|
||||||
Стоит покрыть: `getConfigSection`, `getSettingDefault`, ошибки при missing keys.
|
|
||||||
|
|
||||||
**`src/model/taskFile.ts`** (46 строк) `isTaskFileName` + константы — можно
|
**`config.test.ts`** — 4 теста:
|
||||||
быстрый unit-тест.
|
|
||||||
|
- `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] **Рефакторинг** — `frontmatterBlock.ts` → `frontmatterFields.ts` + тесты
|
||||||
- [x] **Edge case тесты** — 14 тестов добавлены, все зелёные
|
- [x] **Edge case тесты** — 14 тестов + интеграционный
|
||||||
- [ ] **Дизайн-вопрос** — решить судьбу неизвестных статусов/приоритетов
|
- [x] **rename TODO → OPEN + UNKNOWN**
|
||||||
- [ ] **Хрупкие тесты** — snapshot → `toEqual`, `_unsafeUnwrap` → безопасный
|
- [x] **Хрупкие тесты** — snapshot оставлены, `_unsafeUnwrap` заменён
|
||||||
паттерн
|
- [x] **config.ts + taskFile.ts** — unit-тесты
|
||||||
- [ ] **config.ts + taskFile.ts** — unit-тесты
|
- [x] **todo.md** — refresh боковой панели
|
||||||
- [ ] **todo.md** — refresh боковой панели
|
- [x] **Сырые строки → константы** в тестах
|
||||||
- [ ] **Интеграционный тест** — полный round-trip через файл
|
|
||||||
|
|||||||
+34
-10
@@ -5,6 +5,7 @@ import { TASK_FILE_EXTENSION } from "@/model/taskFile";
|
|||||||
import { FsTaskRepository } from "@/storage/FsTaskRepository";
|
import { FsTaskRepository } from "@/storage/FsTaskRepository";
|
||||||
import { NodeFileSystem } from "@/storage/NodeFileSystem";
|
import { NodeFileSystem } from "@/storage/NodeFileSystem";
|
||||||
import { VscodeConfigProvider } from "@/storage/VscodeConfigProvider";
|
import { VscodeConfigProvider } from "@/storage/VscodeConfigProvider";
|
||||||
|
import { IConfigProvider } from "@/ports";
|
||||||
import { registerTaskCodeActions } from "@/ui/taskCodeActions";
|
import { registerTaskCodeActions } from "@/ui/taskCodeActions";
|
||||||
import { registerTaskCommands } from "@/ui/taskCommands";
|
import { registerTaskCommands } from "@/ui/taskCommands";
|
||||||
import { registerTaskDecorations } from "@/ui/taskDecorations";
|
import { registerTaskDecorations } from "@/ui/taskDecorations";
|
||||||
@@ -13,6 +14,24 @@ import { TaskDashboardPanel } from "@/views/taskDashboardPanel";
|
|||||||
import { TaskKanbanPanel } from "@/views/taskKanbanPanel";
|
import { TaskKanbanPanel } from "@/views/taskKanbanPanel";
|
||||||
import { TaskTreeProvider } from "@/views/taskTreeProvider";
|
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) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
const fileSystem = new NodeFileSystem();
|
const fileSystem = new NodeFileSystem();
|
||||||
const config = new VscodeConfigProvider();
|
const config = new VscodeConfigProvider();
|
||||||
@@ -29,16 +48,21 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.window.registerTreeDataProvider("xboctukFlow.tasks", tree),
|
vscode.window.registerTreeDataProvider("xboctukFlow.tasks", tree),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const location of resolveTaskLocations(config)) {
|
let watcherDisposable: vscode.Disposable = createTaskWatchers(
|
||||||
const taskPattern = path
|
config,
|
||||||
.join(location.folderPath, `*${TASK_FILE_EXTENSION}`)
|
onTasksMutated,
|
||||||
.replace(/\\/g, "/");
|
);
|
||||||
const watcher = vscode.workspace.createFileSystemWatcher(taskPattern);
|
context.subscriptions.push(watcherDisposable);
|
||||||
watcher.onDidCreate(onTasksMutated);
|
|
||||||
watcher.onDidChange(onTasksMutated);
|
context.subscriptions.push(
|
||||||
watcher.onDidDelete(onTasksMutated);
|
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||||
context.subscriptions.push(watcher);
|
if (!e.affectsConfiguration("xboctukFlow")) return;
|
||||||
}
|
watcherDisposable.dispose();
|
||||||
|
watcherDisposable = createTaskWatchers(config, onTasksMutated);
|
||||||
|
context.subscriptions.push(watcherDisposable);
|
||||||
|
tree.refresh();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const statusBarItem = vscode.window.createStatusBarItem(
|
const statusBarItem = vscode.window.createStatusBarItem(
|
||||||
vscode.StatusBarAlignment.Left,
|
vscode.StatusBarAlignment.Left,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const TASK_STATUS_META: Record<TaskStatus, TaskStatusMeta> = {
|
|||||||
},
|
},
|
||||||
[TaskStatus.UNKNOWN]: {
|
[TaskStatus.UNKNOWN]: {
|
||||||
label: "Unknown",
|
label: "Unknown",
|
||||||
defaultHidden: false,
|
defaultHidden: true,
|
||||||
icon: "question",
|
icon: "question",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,6 +55,6 @@
|
|||||||
- сортировка по **created**: по умолчанию старые сверху (ASC), toggle Newest
|
- сортировка по **created**: по умолчанию старые сверху (ASC), toggle Newest
|
||||||
first
|
first
|
||||||
|
|
||||||
[ ] **Боковая панель**
|
[x] **Боковая панель**
|
||||||
|
|
||||||
- при изменении настроек должна обновляться (или добавить кнопку refresh)
|
- при изменении настроек должна обновляться (или добавить кнопку refresh)
|
||||||
|
|||||||
Reference in New Issue
Block a user