mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:45:18 +00:00
O painel `pnpm issues:tui` volta a enxergar o design UI depois do rename do
OPS114: `siblingPlanPaths` deriva `-ui-design.html` (vigente) e
`-ui-draft.html` (legado) e o novo `resolveUiDraft(planPath, exists)` — puro,
com a existência injetada pelo CLI — é dono da precedência (design vence,
draft é fallback, nenhum → design com `exists:false`). O `readPlans` sonda
design→draft no disco e o caminho REAL flui por `plansByNumber`/`buildIssueRow`
até o view model; o contrato `--json` segue `version: 1` com o shape
`{ path, exists }` e a chave histórica `uiDraft`. As superfícies do painel
trocam "rascunho UI" por "design UI".
Retrocompat é só de leitura — o acervo legado (30 `*-ui-draft.html`) não é
tocado. Unit dos dois specs cobre design-only, draft-only, ambos, nenhum, o
caminho real, body linkando o impl e o shape do JSON. Sem migration.
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
buildPanelViewModel,
|
|
issueAction,
|
|
issueState,
|
|
planLabel,
|
|
toJsonPayload,
|
|
visibleWindow,
|
|
} from '../../scripts/lib/issues-panel.mjs'
|
|
|
|
const issue = (over: Record<string, unknown> = {}) => ({
|
|
number: 999,
|
|
title: 'Test',
|
|
body: '---\nid: TEST999\n---\nPlano: [`docs/plans/test999.md`](docs/plans/test999.md)\n',
|
|
state: 'OPEN',
|
|
createdAt: '2026-09-15T10:00:00.000Z',
|
|
labels: [{ name: 'ready', color: '' }],
|
|
...over,
|
|
})
|
|
|
|
describe('planLabel (OPS109)', () => {
|
|
it('says sem plano when both plans are null', () => {
|
|
expect(planLabel({ plan: { intention: null, impl: null } })).toBe('sem plano')
|
|
})
|
|
|
|
it('says sem plano when intention path is null and impl path is null', () => {
|
|
expect(
|
|
planLabel({
|
|
plan: {
|
|
intention: { path: null, exists: false, classification: 'none' },
|
|
impl: { path: null, exists: false, classification: 'none' },
|
|
},
|
|
}),
|
|
).toBe('sem plano')
|
|
})
|
|
|
|
it('shows the intention status when the intention exists but impl is missing', () => {
|
|
expect(
|
|
planLabel({
|
|
plan: {
|
|
intention: {
|
|
path: 'docs/plans/test.md',
|
|
exists: true,
|
|
classification: 'aprovado',
|
|
status: { raw: 'aprovado', classification: 'aprovado' },
|
|
},
|
|
impl: {
|
|
path: 'docs/plans/test-impl.md',
|
|
exists: false,
|
|
classification: 'não criado',
|
|
status: null,
|
|
},
|
|
},
|
|
}),
|
|
).toBe('intenção: aprovado')
|
|
})
|
|
|
|
it('prefers the impl when it exists', () => {
|
|
expect(
|
|
planLabel({
|
|
plan: {
|
|
intention: {
|
|
path: 'docs/plans/test.md',
|
|
exists: true,
|
|
classification: 'aprovado',
|
|
status: { raw: 'aprovado', classification: 'aprovado' },
|
|
},
|
|
impl: {
|
|
path: 'docs/plans/test-impl.md',
|
|
exists: true,
|
|
classification: 'andou',
|
|
status: { raw: 'em execução', classification: 'andou' },
|
|
},
|
|
},
|
|
}),
|
|
).toBe('impl em execução')
|
|
})
|
|
})
|
|
|
|
describe('issueAction (OPS109 — blocked wins)', () => {
|
|
it('says travada when blocked label is present even if plan is aguardando', () => {
|
|
expect(
|
|
issueAction({
|
|
state: 'blocked',
|
|
plan: { intention: { classification: 'aguardando' }, impl: null },
|
|
blockedBy: [],
|
|
}),
|
|
).toBe('travada')
|
|
})
|
|
})
|
|
|
|
describe('issueState (OPS109 — closed issues)', () => {
|
|
it('returns done for closed issues even when the label still says in-progress', () => {
|
|
expect(
|
|
issueState(issue({ state: 'CLOSED', labels: [{ name: 'in-progress', color: '' }] })),
|
|
).toBe('done')
|
|
})
|
|
|
|
it('returns in-prod for closed in-prod issues', () => {
|
|
expect(issueState(issue({ state: 'CLOSED', labels: [{ name: 'in-prod', color: '' }] }))).toBe(
|
|
'in-prod',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('visibleWindow (OPS109 — list scroll math)', () => {
|
|
it('returns the whole list when it fits', () => {
|
|
expect(visibleWindow({ count: 7, cursor: 3, capacity: 10 })).toEqual({ start: 0, end: 7 })
|
|
})
|
|
|
|
it('keeps the cursor centered in a long list', () => {
|
|
expect(visibleWindow({ count: 100, cursor: 50, capacity: 10 })).toEqual({
|
|
start: 45,
|
|
end: 55,
|
|
})
|
|
})
|
|
|
|
it('clamps at the top and the bottom', () => {
|
|
expect(visibleWindow({ count: 100, cursor: 0, capacity: 10 })).toEqual({ start: 0, end: 10 })
|
|
expect(visibleWindow({ count: 100, cursor: 99, capacity: 10 })).toEqual({
|
|
start: 90,
|
|
end: 100,
|
|
})
|
|
})
|
|
|
|
it('never slices the cursor out at the window edges', () => {
|
|
const { start, end } = visibleWindow({ count: 23, cursor: 22, capacity: 4 })
|
|
expect(start).toBeLessThanOrEqual(22)
|
|
expect(end).toBeGreaterThan(22)
|
|
expect(end - start).toBe(4)
|
|
})
|
|
|
|
it('degrades to a single row for an empty or zero capacity', () => {
|
|
expect(visibleWindow({ count: 0, cursor: 0, capacity: 0 })).toEqual({ start: 0, end: 0 })
|
|
expect(visibleWindow({ count: 5, cursor: 2, capacity: 0 })).toEqual({ start: 2, end: 3 })
|
|
})
|
|
})
|
|
|
|
describe('toJsonPayload / buildPanelViewModel (OPS109 — no regressions)', () => {
|
|
it('marks a missing impl as não criado in the JSON contract', () => {
|
|
const model = buildPanelViewModel({
|
|
issues: [
|
|
issue({
|
|
number: 999,
|
|
body: '---\nid: TEST999\n---\nPlano: [`docs/plans/test999.md`](docs/plans/test999.md)',
|
|
}),
|
|
],
|
|
plansByNumber: new Map([
|
|
[
|
|
999,
|
|
{
|
|
intention: 'Status: aprovado\n',
|
|
impl: null,
|
|
uiDraft: { path: 'docs/plans/test999-ui-draft.html', exists: true },
|
|
},
|
|
],
|
|
]),
|
|
limit: 200,
|
|
})
|
|
const payload = toJsonPayload(model)
|
|
const row = payload.rows[0]
|
|
expect(row.plan.intention).toMatchObject({ classification: 'aprovado', status: 'aprovado' })
|
|
expect(row.plan.impl).toMatchObject({ exists: false, classification: 'não criado' })
|
|
expect(row.uiDraft).toEqual({ path: 'docs/plans/test999-ui-draft.html', exists: true })
|
|
})
|
|
})
|