kin: KIN-078 Канбан доска не отображается в в полную ширину экрана. Проверить был ли вызван хук перезагрузки после выполнения задачи.
This commit is contained in:
parent
c14c0b7832
commit
cc592bfbbc
2 changed files with 153 additions and 2 deletions
|
|
@ -646,3 +646,153 @@ describe('KIN-075: канбан — кнопки управления', () => {
|
||||||
expect(wrapper.text()).toContain('Add Task')
|
expect(wrapper.text()).toContain('Add Task')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// KIN-078: полная ширина канбан-доски (нет max-w ограничений)
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('KIN-078: канбан — flex layout без ограничений ширины', () => {
|
||||||
|
it('Flex-контейнер колонок имеет класс w-full', async () => {
|
||||||
|
const wrapper = await mountOnKanban()
|
||||||
|
|
||||||
|
const flexContainer = wrapper.find('.flex.gap-3.w-full')
|
||||||
|
expect(flexContainer.exists(), 'flex gap-3 w-full контейнер должен существовать').toBe(true)
|
||||||
|
expect(flexContainer.classes()).toContain('w-full')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Flex-контейнер колонок не содержит inline style min-width: max-content', async () => {
|
||||||
|
const wrapper = await mountOnKanban()
|
||||||
|
|
||||||
|
const flexContainer = wrapper.find('.flex.gap-3.w-full')
|
||||||
|
const style = flexContainer.element.getAttribute('style')
|
||||||
|
expect(style ?? '').not.toContain('min-width')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Каждая из 5 колонок имеет flex-1 (растягивается), а не фиксированный w-64', async () => {
|
||||||
|
const wrapper = await mountOnKanban()
|
||||||
|
|
||||||
|
// KANBAN_COLUMNS — 5 колонок, все должны иметь flex-1
|
||||||
|
const allFlex1 = wrapper.findAll('div').filter(d => d.classes().includes('flex-1') && d.classes().includes('flex-col'))
|
||||||
|
expect(allFlex1.length, '5 колонок с flex-1 flex-col должны быть').toBe(5)
|
||||||
|
|
||||||
|
for (const col of allFlex1) {
|
||||||
|
expect(col.classes(), 'Колонка не должна иметь фиксированный w-64').not.toContain('w-64')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Каждая из 5 колонок имеет min-w-[12rem] (минимальная ширина)', async () => {
|
||||||
|
const wrapper = await mountOnKanban()
|
||||||
|
|
||||||
|
const columns = wrapper.findAll('div').filter(d =>
|
||||||
|
d.classes().includes('flex-1') && d.classes().includes('flex-col')
|
||||||
|
)
|
||||||
|
expect(columns.length).toBe(5)
|
||||||
|
|
||||||
|
for (const col of columns) {
|
||||||
|
expect(col.classes(), 'Колонка должна иметь min-w-[12rem]').toContain('min-w-[12rem]')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// KIN-078: runTask → запуск polling на kanban-вкладке
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('KIN-078: runTask → polling на kanban-вкладке', () => {
|
||||||
|
it('runTask запускает polling если пользователь переключился на kanban пока задача выполнялась', async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
|
||||||
|
// Изначально нет in_progress задач → переключение на kanban не запускает polling
|
||||||
|
const projectNoPending = {
|
||||||
|
...MOCK_PROJECT,
|
||||||
|
tasks: MOCK_PROJECT.tasks.filter(t => t.status !== 'in_progress'),
|
||||||
|
}
|
||||||
|
vi.mocked(api.project).mockResolvedValue(projectNoPending as any)
|
||||||
|
|
||||||
|
// Откладываем api.runTask — имитируем задержку
|
||||||
|
let resolveRun!: () => void
|
||||||
|
vi.mocked(api.runTask).mockReturnValue(new Promise<void>(res => { resolveRun = () => res() }) as any)
|
||||||
|
vi.spyOn(window, 'confirm').mockReturnValue(true)
|
||||||
|
|
||||||
|
const router = makeRouter()
|
||||||
|
await router.push('/project/KIN')
|
||||||
|
const wrapper = mount(ProjectView, {
|
||||||
|
props: { id: 'KIN' },
|
||||||
|
global: { plugins: [router] },
|
||||||
|
})
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// Кликаем ▶ для KIN-001 (pending) на вкладке Tasks — runTask подвисает на await api.runTask
|
||||||
|
const runBtn = wrapper.find('button[title="Run pipeline"]')
|
||||||
|
expect(runBtn.exists(), '▶ кнопка должна быть на Tasks вкладке').toBe(true)
|
||||||
|
await runBtn.trigger('click')
|
||||||
|
|
||||||
|
// Пока runTask ждёт — переключаемся на kanban (нет in_progress → polling не стартует)
|
||||||
|
const kanbanTab = wrapper.findAll('button').find(b =>
|
||||||
|
b.classes().includes('border-b-2') && b.text().includes('Kanban')
|
||||||
|
)!
|
||||||
|
await kanbanTab.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// Убеждаемся что polling не запустился (нет in_progress задач)
|
||||||
|
await vi.advanceTimersByTimeAsync(5000)
|
||||||
|
await flushPromises()
|
||||||
|
const callsBefore = vi.mocked(api.project).mock.calls.length
|
||||||
|
|
||||||
|
// Настраиваем следующий load() чтобы вернуть проект с in_progress задачами
|
||||||
|
vi.mocked(api.project).mockResolvedValue(MOCK_PROJECT as any)
|
||||||
|
|
||||||
|
// Завершаем api.runTask → runTask продолжает: load() → checkAndPollKanban()
|
||||||
|
resolveRun()
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const callsAfterLoad = vi.mocked(api.project).mock.calls.length
|
||||||
|
expect(callsAfterLoad, 'load() должен вызвать api.project').toBeGreaterThan(callsBefore)
|
||||||
|
|
||||||
|
// Продвигаем время на 5с → polling-тик должен сработать
|
||||||
|
await vi.advanceTimersByTimeAsync(5000)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(
|
||||||
|
vi.mocked(api.project).mock.calls.length,
|
||||||
|
'Polling должен запуститься после runTask когда activeTab === kanban',
|
||||||
|
).toBeGreaterThan(callsAfterLoad)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('runTask не запускает polling если activeTab !== kanban в момент завершения', async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
vi.mocked(api.runTask).mockResolvedValue(undefined as any)
|
||||||
|
vi.spyOn(window, 'confirm').mockReturnValue(true)
|
||||||
|
|
||||||
|
// Нет in_progress задач → ни через watcher, ни через runTask polling не стартует
|
||||||
|
const projectNoPending = {
|
||||||
|
...MOCK_PROJECT,
|
||||||
|
tasks: MOCK_PROJECT.tasks.filter(t => t.status !== 'in_progress'),
|
||||||
|
}
|
||||||
|
vi.mocked(api.project).mockResolvedValue(projectNoPending as any)
|
||||||
|
|
||||||
|
const router = makeRouter()
|
||||||
|
await router.push('/project/KIN')
|
||||||
|
const wrapper = mount(ProjectView, {
|
||||||
|
props: { id: 'KIN' },
|
||||||
|
global: { plugins: [router] },
|
||||||
|
})
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
// Остаёмся на Tasks вкладке (activeTab === 'tasks') и кликаем ▶
|
||||||
|
const runBtn = wrapper.find('button[title="Run pipeline"]')
|
||||||
|
await runBtn.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const callsAfterRun = vi.mocked(api.project).mock.calls.length
|
||||||
|
|
||||||
|
// Продвигаем время — polling не должен запуститься (мы на tasks, нет in_progress)
|
||||||
|
await vi.advanceTimersByTimeAsync(5000)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(
|
||||||
|
vi.mocked(api.project).mock.calls.length,
|
||||||
|
'Polling не должен запуститься когда activeTab !== kanban',
|
||||||
|
).toBe(callsAfterRun)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,7 @@ async function runTask(taskId: string, event: Event) {
|
||||||
try {
|
try {
|
||||||
await api.runTask(taskId)
|
await api.runTask(taskId)
|
||||||
await load()
|
await load()
|
||||||
|
if (activeTab.value === 'kanban') checkAndPollKanban()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.value = e.message
|
error.value = e.message
|
||||||
}
|
}
|
||||||
|
|
@ -883,8 +884,8 @@ async function addDecision() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex gap-3" style="min-width: max-content">
|
<div class="flex gap-3 w-full">
|
||||||
<div v-for="col in KANBAN_COLUMNS" :key="col.status" class="w-64 flex flex-col gap-2">
|
<div v-for="col in KANBAN_COLUMNS" :key="col.status" class="flex-1 min-w-[12rem] flex flex-col gap-2">
|
||||||
<!-- Column header -->
|
<!-- Column header -->
|
||||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||||
<span class="text-xs font-semibold uppercase tracking-wide" :class="col.headerClass">{{ col.label }}</span>
|
<span class="text-xs font-semibold uppercase tracking-wide" :class="col.headerClass">{{ col.label }}</span>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue