kin: auto-commit after pipeline
This commit is contained in:
parent
1487e84eb6
commit
8623323161
2 changed files with 391 additions and 0 deletions
283
web/frontend/src/__tests__/date-filter.test.ts
Normal file
283
web/frontend/src/__tests__/date-filter.test.ts
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/**
|
||||
* KIN-126: Тесты фильтра по дате выполнения задач (done tasks date range filter)
|
||||
*
|
||||
* AC: фильтр сделан и работает
|
||||
*
|
||||
* Проверяет:
|
||||
* 1. Date filter inputs отображаются только когда выбран статус 'done'
|
||||
* 2. Date filter inputs скрыты на других статусах
|
||||
* 3. Date filter inputs скрыты без фильтра статуса
|
||||
* 4. Фильтрация по dateFrom — задачи до этой даты исключаются
|
||||
* 5. Фильтрация по dateTo — задачи после этой даты исключаются
|
||||
* 6. Пустой результат при несовпадении дат
|
||||
* 7. Сброс фильтра (кнопка ✕) показывает все done-задачи снова
|
||||
* 8. Fallback на updated_at когда completed_at=null
|
||||
*/
|
||||
|
||||
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'
|
||||
|
||||
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(),
|
||||
},
|
||||
}))
|
||||
|
||||
import { api } from '../api'
|
||||
|
||||
const Stub = { template: '<div />' }
|
||||
|
||||
function makeTask(
|
||||
id: string,
|
||||
status: string,
|
||||
completedAt: string | null,
|
||||
updatedAt = '2024-01-10T00:00:00',
|
||||
) {
|
||||
return {
|
||||
id,
|
||||
project_id: 'KIN',
|
||||
title: `Task ${id}`,
|
||||
status,
|
||||
priority: 5,
|
||||
assigned_role: null,
|
||||
parent_task_id: null,
|
||||
brief: null,
|
||||
spec: null,
|
||||
created_at: '2024-01-01T00:00:00',
|
||||
updated_at: updatedAt,
|
||||
completed_at: completedAt,
|
||||
}
|
||||
}
|
||||
|
||||
const MOCK_PROJECT_DATE_FILTER = {
|
||||
id: 'KIN',
|
||||
name: 'Kin',
|
||||
path: '/projects/kin',
|
||||
status: 'active',
|
||||
priority: 5,
|
||||
tech_stack: ['python', 'vue'],
|
||||
created_at: '2024-01-01',
|
||||
total_tasks: 4,
|
||||
done_tasks: 3,
|
||||
active_tasks: 1,
|
||||
blocked_tasks: 0,
|
||||
review_tasks: 0,
|
||||
tasks: [
|
||||
makeTask('KIN-001', 'done', '2024-01-05T12:00:00'), // before range
|
||||
makeTask('KIN-002', 'done', '2024-01-15T12:00:00'), // in middle
|
||||
makeTask('KIN-003', 'done', '2024-01-25T12:00:00'), // after range
|
||||
makeTask('KIN-004', 'pending', null),
|
||||
],
|
||||
decisions: [],
|
||||
modules: [],
|
||||
}
|
||||
|
||||
function makeRouter() {
|
||||
return createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: Stub },
|
||||
{ path: '/project/:id', component: ProjectView, props: true },
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const localStorageMock = (() => {
|
||||
let store: Record<string, string> = {}
|
||||
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 })
|
||||
|
||||
beforeEach(() => {
|
||||
localStorageMock.clear()
|
||||
vi.mocked(api.project).mockResolvedValue(MOCK_PROJECT_DATE_FILTER as any)
|
||||
})
|
||||
|
||||
describe('KIN-126: фильтр по дате выполнения задач', () => {
|
||||
it('1. Date filter inputs отображаются когда выбран статус done', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=done')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-testid="date-from"]').exists(),
|
||||
'date-from должен быть виден при status=done',
|
||||
).toBe(true)
|
||||
expect(
|
||||
wrapper.find('[data-testid="date-to"]').exists(),
|
||||
'date-to должен быть виден при status=done',
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('2. Date filter inputs скрыты когда done не выбран', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=pending')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-testid="date-from"]').exists(),
|
||||
'date-from не должен быть виден при status=pending',
|
||||
).toBe(false)
|
||||
expect(
|
||||
wrapper.find('[data-testid="date-to"]').exists(),
|
||||
'date-to не должен быть виден при status=pending',
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('3. Date filter inputs скрыты без фильтра статуса', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-testid="date-from"]').exists(),
|
||||
'date-from не должен быть виден без фильтра статуса',
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('4. Фильтрация по dateFrom исключает задачи выполненные до этой даты', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=done')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('[data-testid="date-from"]').setValue('2024-01-10')
|
||||
await flushPromises()
|
||||
|
||||
const links = wrapper.findAll('a[href^="/task/"]')
|
||||
const ids = links.map(l => l.attributes('href')?.replace('/task/', '').split('?')[0])
|
||||
|
||||
expect(ids, 'KIN-001 (2024-01-05) должен быть исключён').not.toContain('KIN-001')
|
||||
expect(ids, 'KIN-002 (2024-01-15) должен быть виден').toContain('KIN-002')
|
||||
expect(ids, 'KIN-003 (2024-01-25) должен быть виден').toContain('KIN-003')
|
||||
})
|
||||
|
||||
it('5. Фильтрация по dateTo исключает задачи выполненные после этой даты', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=done')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('[data-testid="date-to"]').setValue('2024-01-20')
|
||||
await flushPromises()
|
||||
|
||||
const links = wrapper.findAll('a[href^="/task/"]')
|
||||
const ids = links.map(l => l.attributes('href')?.replace('/task/', '').split('?')[0])
|
||||
|
||||
expect(ids, 'KIN-003 (2024-01-25) должен быть исключён').not.toContain('KIN-003')
|
||||
expect(ids, 'KIN-001 (2024-01-05) должен быть виден').toContain('KIN-001')
|
||||
expect(ids, 'KIN-002 (2024-01-15) должен быть виден').toContain('KIN-002')
|
||||
})
|
||||
|
||||
it('6. Пустой результат когда ни одна задача не попадает в диапазон дат', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=done')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('[data-testid="date-from"]').setValue('2024-02-01')
|
||||
await wrapper.find('[data-testid="date-to"]').setValue('2024-02-28')
|
||||
await flushPromises()
|
||||
|
||||
const links = wrapper.findAll('a[href^="/task/"]')
|
||||
expect(links, 'Ни одна done-задача не должна отображаться при несовпадении дат').toHaveLength(0)
|
||||
})
|
||||
|
||||
it('7. Клик по кнопке сброса очищает фильтр и показывает все done-задачи', async () => {
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=done')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
// Устанавливаем dateFrom — остаётся только одна done-задача
|
||||
await wrapper.find('[data-testid="date-from"]').setValue('2024-01-20')
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('a[href^="/task/"]')).toHaveLength(1)
|
||||
|
||||
// Кнопка сброса — ✕ без data-action (не clear-status)
|
||||
const resetBtn = wrapper.findAll('button').find(
|
||||
b => b.text() === '✕' && !b.attributes('data-action'),
|
||||
)
|
||||
expect(resetBtn?.exists(), 'Кнопка сброса дат должна появиться').toBe(true)
|
||||
await resetBtn!.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
// После сброса — все 3 done-задачи видны
|
||||
expect(wrapper.findAll('a[href^="/task/"]')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('8. Fallback на updated_at когда completed_at=null', async () => {
|
||||
const projectWithNullCompletedAt = {
|
||||
...MOCK_PROJECT_DATE_FILTER,
|
||||
tasks: [
|
||||
makeTask('KIN-010', 'done', null, '2024-01-05T00:00:00'), // updated_at early
|
||||
makeTask('KIN-011', 'done', null, '2024-01-25T00:00:00'), // updated_at late
|
||||
],
|
||||
}
|
||||
vi.mocked(api.project).mockResolvedValue(projectWithNullCompletedAt as any)
|
||||
|
||||
const router = makeRouter()
|
||||
await router.push('/project/KIN?status=done')
|
||||
|
||||
const wrapper = mount(ProjectView, {
|
||||
props: { id: 'KIN' },
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
// dateFrom='2024-01-20' → KIN-010 (updated_at 2024-01-05) должен быть исключён
|
||||
await wrapper.find('[data-testid="date-from"]').setValue('2024-01-20')
|
||||
await flushPromises()
|
||||
|
||||
const links = wrapper.findAll('a[href^="/task/"]')
|
||||
const ids = links.map(l => l.attributes('href')?.replace('/task/', '').split('?')[0])
|
||||
|
||||
expect(ids, 'KIN-010 должен быть исключён (updated_at до dateFrom)').not.toContain('KIN-010')
|
||||
expect(ids, 'KIN-011 должен быть виден (updated_at после dateFrom)').toContain('KIN-011')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue