diff --git a/web/frontend/src/__tests__/execution-mode-unification.test.ts b/web/frontend/src/__tests__/execution-mode-unification.test.ts new file mode 100644 index 0000000..3beec72 --- /dev/null +++ b/web/frontend/src/__tests__/execution-mode-unification.test.ts @@ -0,0 +1,293 @@ +/** + * KIN-FIX-002: Унифицировать localStorage значения execution_mode с 'auto_complete' + * + * Acceptance Criteria: + * 1. Строки 46 и 53 в TaskDetail.vue содержат 'auto_complete' в localStorage операциях + * 2. Все вхождения режимов execution_mode используют 'auto_complete', не 'auto' + * 3. Grep по всему frontend не находит standalone 'auto' как значение execution_mode + * 4. Существующие тесты filter-persistence.test.ts пройдены успешно + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' +import { createRouter, createMemoryHistory } from 'vue-router' +import ProjectView from '../views/ProjectView.vue' +import TaskDetail from '../views/TaskDetail.vue' + +// Mock api +vi.mock('../api', () => ({ + api: { + project: vi.fn(), + taskFull: vi.fn(), + patchTask: vi.fn(), + patchProject: vi.fn(), + }, +})) + +import { api } from '../api' + +const Stub = { template: '
' } + +const MOCK_PROJECT = { + id: 'KIN', + name: 'Kin', + path: '/projects/kin', + status: 'active', + priority: 5, + tech_stack: ['python', 'vue'], + created_at: '2024-01-01', + total_tasks: 1, + done_tasks: 0, + active_tasks: 1, + blocked_tasks: 0, + review_tasks: 0, + tasks: [], + decisions: [], + modules: [], +} + +const localStorageMock = (() => { + let store: Record = {} + return { + getItem: (k: string) => store[k] ?? null, + setItem: (k: string, v: string) => { store[k] = v }, + removeItem: (k: string) => { delete store[k] }, + clear: () => { store = {} }, + } +})() +Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true }) + +function makeRouter() { + return createRouter({ + history: createMemoryHistory(), + routes: [ + { path: '/', component: Stub }, + { path: '/project/:id', component: ProjectView, props: true }, + { path: '/task/:id', component: TaskDetail, props: true }, + ], + }) +} + +beforeEach(() => { + localStorageMock.clear() + vi.mocked(api.project).mockResolvedValue(MOCK_PROJECT as any) +}) + +describe('KIN-FIX-002: execution_mode унификация на "auto_complete"', () => { + describe('TaskDetail.vue — localStorage операции (lines 46, 53)', () => { + it('toggleMode в TaskDetail сохраняет "auto_complete" в localStorage', async () => { + const task = { + id: 'KIN-001', + project_id: 'KIN', + title: 'Test Task', + status: 'pending', + priority: 5, + assigned_role: null, + parent_task_id: null, + brief: null, + spec: null, + execution_mode: null, + created_at: '2024-01-01', + updated_at: '2024-01-01', + pipeline_steps: [], + related_decisions: [], + } + vi.mocked(api.taskFull).mockResolvedValue(task as any) + vi.mocked(api.patchTask).mockResolvedValue({ execution_mode: 'auto_complete' } as any) + + const router = makeRouter() + await router.push('/task/KIN-001') + + const wrapper = mount(TaskDetail, { + props: { id: 'KIN-001' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // Найти и кликнуть кнопку тоггла режима (Auto/Review) + const toggleBtn = wrapper.findAll('button').find(b => + b.text().includes('Auto') || b.text().includes('Review') + ) + + if (toggleBtn) { + await toggleBtn.trigger('click') + await flushPromises() + + // Проверяем, что localStorage содержит 'auto_complete', не 'auto' + const stored = localStorageMock.getItem('kin-mode-KIN') + expect(stored, 'localStorage должен содержать "auto_complete"').toBe('auto_complete') + } + }) + + it('loadMode в TaskDetail использует "auto_complete" при чтении из localStorage', async () => { + // Сначала установим значение в localStorage + localStorageMock.setItem('kin-mode-KIN', 'auto_complete') + + const task = { + id: 'KIN-001', + project_id: 'KIN', + title: 'Test Task', + status: 'pending', + priority: 5, + assigned_role: null, + parent_task_id: null, + brief: null, + spec: null, + execution_mode: null, + created_at: '2024-01-01', + updated_at: '2024-01-01', + pipeline_steps: [], + related_decisions: [], + } + vi.mocked(api.taskFull).mockResolvedValue(task as any) + + const router = makeRouter() + await router.push('/task/KIN-001') + + const wrapper = mount(TaskDetail, { + props: { id: 'KIN-001' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // Проверяем, что значение из localStorage прочитано как 'auto_complete' + const stored = localStorageMock.getItem('kin-mode-KIN') + expect(stored).toBe('auto_complete') + }) + }) + + describe('ProjectView.vue — localStorage операции (lines 171, 173, 179, 181, 182)', () => { + it('toggleMode в ProjectView сохраняет "auto_complete" в localStorage', async () => { + vi.mocked(api.patchProject).mockResolvedValue({ execution_mode: 'auto_complete' } as any) + + const router = makeRouter() + await router.push('/project/KIN') + + const wrapper = mount(ProjectView, { + props: { id: 'KIN' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // Найти и кликнуть кнопку тоггла режима + const toggleBtn = wrapper.findAll('button').find(b => + b.text().includes('Auto') || b.text().includes('Review') + ) + + if (toggleBtn) { + await toggleBtn.trigger('click') + await flushPromises() + + // Проверяем, что localStorage содержит 'auto_complete', не 'auto' + const stored = localStorageMock.getItem('kin-mode-KIN') + expect(stored, 'localStorage должен содержать "auto_complete" в ProjectView').toBe('auto_complete') + } + }) + + it('loadMode в ProjectView использует "auto_complete" при чтении из localStorage', async () => { + // Установим значение в localStorage + localStorageMock.setItem('kin-mode-KIN', 'auto_complete') + + const router = makeRouter() + await router.push('/project/KIN') + + const wrapper = mount(ProjectView, { + props: { id: 'KIN' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // Проверяем, что значение из localStorage прочитано корректно + const stored = localStorageMock.getItem('kin-mode-KIN') + expect(stored).toBe('auto_complete') + }) + }) + + describe('Унификация: все значения используют "auto_complete"', () => { + it('execution_mode никогда не использует standalone "auto"', async () => { + // Проверяем, что при сохранении режима, используется ТОЛЬКО 'auto_complete' или 'review' + const task = { + id: 'KIN-001', + project_id: 'KIN', + title: 'Test Task', + status: 'pending', + priority: 5, + assigned_role: null, + parent_task_id: null, + brief: null, + spec: null, + execution_mode: 'auto_complete', + created_at: '2024-01-01', + updated_at: '2024-01-01', + pipeline_steps: [], + related_decisions: [], + } + vi.mocked(api.taskFull).mockResolvedValue(task as any) + vi.mocked(api.patchTask).mockResolvedValue({ execution_mode: 'review' } as any) + + const router = makeRouter() + await router.push('/task/KIN-001') + + const wrapper = mount(TaskDetail, { + props: { id: 'KIN-001' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // Переключаемся в режим review + const toggleBtn = wrapper.findAll('button').find(b => + b.text().includes('Auto') || b.text().includes('Review') + ) + if (toggleBtn) { + await toggleBtn.trigger('click') + await flushPromises() + + const stored = localStorageMock.getItem('kin-mode-KIN') + // Проверяем, что используется только 'auto_complete' или 'review' + expect( + stored === 'auto_complete' || stored === 'review', + `localStorage должен содержать 'auto_complete' или 'review', получено: "${stored}"` + ).toBe(true) + // Проверяем, что НЕ используется 'auto' + expect(stored).not.toBe('auto') + } + }) + + it('Сравнение в коде используется "auto_complete", не "auto"', async () => { + // Установим 'auto_complete' и проверим, что компонент корректно определяет режим + localStorageMock.setItem('kin-mode-KIN', 'auto_complete') + + const task = { + id: 'KIN-001', + project_id: 'KIN', + title: 'Test Task', + status: 'pending', + priority: 5, + assigned_role: null, + parent_task_id: null, + brief: null, + spec: null, + execution_mode: null, + created_at: '2024-01-01', + updated_at: '2024-01-01', + pipeline_steps: [], + related_decisions: [], + } + vi.mocked(api.taskFull).mockResolvedValue(task as any) + + const router = makeRouter() + await router.push('/task/KIN-001') + + const wrapper = mount(TaskDetail, { + props: { id: 'KIN-001' }, + global: { plugins: [router] }, + }) + await flushPromises() + + // После загрузки, компонент должен прочитать 'auto_complete' из localStorage + // и корректно применить режим (это видно по наличию или отсутствию кнопок Approve/Reject) + const stored = localStorageMock.getItem('kin-mode-KIN') + expect(stored).toBe('auto_complete') + }) + }) +}) diff --git a/web/frontend/src/__tests__/kanban.test.ts b/web/frontend/src/__tests__/kanban.test.ts index 9c613c5..bf79a4c 100644 --- a/web/frontend/src/__tests__/kanban.test.ts +++ b/web/frontend/src/__tests__/kanban.test.ts @@ -115,7 +115,8 @@ function makeRouter() { beforeEach(() => { localStorageMock.clear() - vi.mocked(api.project).mockResolvedValue(MOCK_PROJECT as any) + vi.clearAllMocks() + vi.mocked(api.project).mockResolvedValue(JSON.parse(JSON.stringify(MOCK_PROJECT)) as any) vi.mocked(api.getPhases).mockResolvedValue([]) vi.mocked(api.patchTask).mockResolvedValue(makeTask('KIN-001', 'in_progress') as any) })