kin: KIN-UI-015-frontend_dev

This commit is contained in:
Gros Frumos 2026-03-18 15:50:08 +02:00
parent abd58e00f0
commit 62a483b62a
3 changed files with 12 additions and 71 deletions

View file

@ -19,8 +19,7 @@ vi.mock('vue-router', () => ({
vi.mock('../api', () => ({ vi.mock('../api', () => ({
api: { api: {
notifications: vi.fn(), notifications: vi.fn(),
projects: vi.fn(), listTasks: vi.fn(),
project: vi.fn(),
reviseTask: vi.fn(), reviseTask: vi.fn(),
}, },
})) }))
@ -38,40 +37,6 @@ const localStorageMock = (() => {
})() })()
Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true }) Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true })
function makeProject(id = 'proj-1', name = 'MyProject', doneCount = 1) {
return {
id,
name,
path: '/projects/test',
status: 'active',
priority: 1,
tech_stack: ['python'],
execution_mode: null,
autocommit_enabled: null,
auto_test_enabled: null,
worktrees_enabled: null,
obsidian_vault_path: null,
deploy_command: null,
test_command: null,
deploy_host: null,
deploy_path: null,
deploy_runtime: null,
deploy_restart_cmd: null,
created_at: '2024-01-01',
total_tasks: doneCount,
done_tasks: doneCount,
active_tasks: 0,
blocked_tasks: 0,
review_tasks: 0,
project_type: null,
ssh_host: null,
ssh_user: null,
ssh_key_path: null,
ssh_proxy_jump: null,
description: null,
}
}
function makeCompletedTask(id = 'TSK-1', title = 'Test task') { function makeCompletedTask(id = 'TSK-1', title = 'Test task') {
return { return {
id, id,
@ -93,21 +58,13 @@ function makeCompletedTask(id = 'TSK-1', title = 'Test task') {
} }
} }
function makeProjectDetail(
project: ReturnType<typeof makeProject>,
tasks: ReturnType<typeof makeCompletedTask>[],
) {
return { ...project, tasks, modules: [], decisions: [] }
}
beforeEach(() => { beforeEach(() => {
localStorageMock.clear() localStorageMock.clear()
vi.clearAllMocks() vi.clearAllMocks()
mockPush.mockClear() mockPush.mockClear()
vi.useFakeTimers() vi.useFakeTimers()
vi.mocked(api.notifications).mockResolvedValue([]) vi.mocked(api.notifications).mockResolvedValue([])
vi.mocked(api.projects).mockResolvedValue([]) vi.mocked(api.listTasks).mockResolvedValue([])
vi.mocked(api.project).mockResolvedValue(makeProjectDetail(makeProject(), []))
vi.mocked(api.reviseTask).mockResolvedValue({ status: 'ok', comment: '' }) vi.mocked(api.reviseTask).mockResolvedValue({ status: 'ok', comment: '' })
}) })
@ -116,13 +73,8 @@ afterEach(() => {
vi.restoreAllMocks() vi.restoreAllMocks()
}) })
async function mountWithCompleted( async function mountWithCompleted(tasks = [makeCompletedTask()]) {
tasks = [makeCompletedTask()], vi.mocked(api.listTasks).mockResolvedValue(tasks)
projectName = 'MyProject',
) {
const project = makeProject('proj-1', projectName)
vi.mocked(api.projects).mockResolvedValue([project])
vi.mocked(api.project).mockResolvedValue(makeProjectDetail(project, tasks))
const wrapper = mount(EscalationBanner) const wrapper = mount(EscalationBanner)
await flushPromises() await flushPromises()
return wrapper return wrapper
@ -158,7 +110,7 @@ describe('KIN-125 AC1: завершённые задачи отображают
}) })
it('Бейдж не отображается когда нет завершённых задач', async () => { it('Бейдж не отображается когда нет завершённых задач', async () => {
vi.mocked(api.projects).mockResolvedValue([makeProject('proj-1', 'P', 0)]) vi.mocked(api.listTasks).mockResolvedValue([])
const wrapper = mount(EscalationBanner) const wrapper = mount(EscalationBanner)
await flushPromises() await flushPromises()
const badge = wrapper.findAll('button').find(b => b.text().includes('Completed')) const badge = wrapper.findAll('button').find(b => b.text().includes('Completed'))

View file

@ -410,6 +410,11 @@ export const api = {
deleteAttachment: (taskId: string, id: number) => deleteAttachment: (taskId: string, id: number) =>
del<void>(`/tasks/${taskId}/attachments/${id}`), del<void>(`/tasks/${taskId}/attachments/${id}`),
attachmentUrl: (id: number) => `${BASE}/attachments/${id}/file`, attachmentUrl: (id: number) => `${BASE}/attachments/${id}/file`,
listTasks: (status?: string, limit = 20, sort = 'updated_at') => {
const q = new URLSearchParams({ limit: String(limit), sort })
if (status) q.set('status', status)
return get<Task[]>(`/tasks?${q.toString()}`)
},
getPipelineLogs: (pipelineId: string, sinceId: number) => getPipelineLogs: (pipelineId: string, sinceId: number) =>
get<PipelineLog[]>(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`), get<PipelineLog[]>(`/pipelines/${pipelineId}/logs?since_id=${sinceId}`),
projectLinks: (projectId: string) => projectLinks: (projectId: string) =>

View file

@ -148,24 +148,8 @@ const visibleCompleted = computed(() =>
async function loadCompletedTasks() { async function loadCompletedTasks() {
try { try {
const projects = await api.projects() const tasks = await api.listTasks('completed', 20, 'updated_at')
const withCompleted = projects.filter(p => p.done_tasks > 0) completedTasks.value = tasks.map(t => ({ ...t, project_name: t.project_id }))
if (withCompleted.length === 0) {
completedTasks.value = []
return
}
const details = await Promise.all(withCompleted.map(p => api.project(p.id)))
const results: CompletedTaskItem[] = []
for (let i = 0; i < details.length; i++) {
const projectName = withCompleted[i].name
for (const task of details[i].tasks) {
if (task.status === 'completed') {
results.push({ ...task, project_name: projectName })
}
}
}
results.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
completedTasks.value = results.slice(0, 20)
} catch { } catch {
// silent // silent
} }