/** * KIN-UI-009: Предупреждение при частичной загрузке вложений * * Проверяет: * 1. Если все файлы загружены успешно — баннер uploadWarning не показывается * 2. Если один файл упал — показывается предупреждение с его именем * 3. Если несколько файлов упали — все имена перечислены в предупреждении * 4. Если часть файлов прошла успешно — в предупреждении только упавшие * 5. Кнопка ✕ скрывает баннер */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { createRouter, createMemoryHistory } from 'vue-router' import ProjectView from '../views/ProjectView.vue' vi.mock('../api', () => ({ api: { project: vi.fn(), taskFull: vi.fn(), runTask: vi.fn(), auditProject: vi.fn(), createTask: vi.fn(), patchTask: vi.fn(), patchProject: vi.fn(), deployProject: vi.fn(), getPhases: vi.fn(), uploadAttachment: vi.fn(), environments: vi.fn(), }, })) import { api } from '../api' const Stub = { template: '
' } function makeTask(id: string, status: string = 'pending') { return { id, project_id: 'KIN', title: `Task ${id}`, status, priority: 5, assigned_role: null, parent_task_id: null, brief: null, spec: null, execution_mode: null, blocked_reason: null, dangerously_skipped: null, category: null, acceptance_criteria: null, created_at: '2024-01-01', updated_at: '2024-01-01', } } const MOCK_PROJECT = { id: 'KIN', name: 'Kin', path: '/projects/kin', status: 'active', priority: 5, tech_stack: ['python', 'vue'], execution_mode: 'review', autocommit_enabled: 0, obsidian_vault_path: null, deploy_command: null, created_at: '2024-01-01', total_tasks: 0, done_tasks: 0, active_tasks: 0, blocked_tasks: 0, review_tasks: 0, project_type: 'development', ssh_host: null, ssh_user: null, ssh_key_path: null, ssh_proxy_jump: null, description: null, 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 }, ], }) } async function mountAndOpenAddTaskModal() { const router = makeRouter() await router.push('/project/KIN') const wrapper = mount(ProjectView, { props: { id: 'KIN' }, global: { plugins: [router] }, }) await flushPromises() // Открываем модал добавления задачи const tasBtn = wrapper.findAll('button').find(b => b.text() === '+ Тас')! await tasBtn.trigger('click') await flushPromises() return wrapper } /** Добавляет файлы в pendingFiles через внутреннее состояние