feat: read the real gitea backlog into the app (P1 thin slice)
The app now displays its own live backlog instead of fixtures. First end of the sync loop — the tap-root (#1) grows list reads and a read path through the Electron main process. - @commitea/core: client gains `listIssues` (paginated, PRs excluded) and `listMilestones`; a `lifecycle-v0` mapper (closed→done, labelled/milestoned→triage, bare→diagnosis — steeping/review await event inference in P1-5). +10 unit tests. - main: gitea bridge over IPC (token stays in main, never the renderer); config from env / .env.local; gated off under COMMITEA_E2E so the committed e2e stays on fixtures. Preload exposes the typed bridge. - renderer: useBacklog() reconciles once on mount; issuesToBoardColumns shapes real issues into The pot. Board takes optional real columns + a loading state, falling back to demo fixtures when unconfigured. Verified: 14 e2e green (fixture mode) + a gated live spec that launches against the real repo — the board renders the actual 25 open + 9 closed issues (screenshot). SQLite mirror + reconcile-on-a-timer + lifecycle event inference are the next slices (#2/#3/#5). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -107,6 +107,50 @@ describe('createGiteaClient.getIssue', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('createGiteaClient.listIssues', () => {
|
||||
/** Stub fetch that pages 50-at-a-time and records the paths it was asked for. */
|
||||
function pagedFetch(pages: unknown[][]): { fetch: FetchLike; paths: string[] } {
|
||||
const paths: string[] = []
|
||||
const fetch: FetchLike = (url) => {
|
||||
paths.push(url)
|
||||
const page = Number(/[?&]page=(\d+)/.exec(url)?.[1] ?? '1')
|
||||
const body = pages[page - 1] ?? []
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => Promise.resolve(body),
|
||||
text: () => Promise.resolve(''),
|
||||
})
|
||||
}
|
||||
return { fetch, paths }
|
||||
}
|
||||
|
||||
it('walks every page until a short page ends it', async () => {
|
||||
const full = Array.from({ length: 50 }, (_, i) => ({ ...RAW_ISSUE, number: i + 1 }))
|
||||
const tail = [{ ...RAW_ISSUE, number: 51 }]
|
||||
const { fetch, paths } = pagedFetch([full, tail])
|
||||
|
||||
const issues = await createGiteaClient(CONFIG, fetch).listIssues()
|
||||
expect(issues).toHaveLength(51)
|
||||
expect(paths).toHaveLength(2) // stopped after the short second page
|
||||
expect(paths[0]).toContain('/issues?type=issues&state=all&page=1&limit=50')
|
||||
})
|
||||
|
||||
it('excludes pull requests even if the API returns them', async () => {
|
||||
const { fetch } = pagedFetch([
|
||||
[{ ...RAW_ISSUE, number: 1 }, { ...RAW_ISSUE, number: 2, pull_request: { url: 'x' } }],
|
||||
])
|
||||
const issues = await createGiteaClient(CONFIG, fetch).listIssues()
|
||||
expect(issues.map((i) => i.number)).toEqual([1])
|
||||
})
|
||||
|
||||
it('passes the state filter through', async () => {
|
||||
const { fetch, paths } = pagedFetch([[]])
|
||||
await createGiteaClient(CONFIG, fetch).listIssues({ state: 'open' })
|
||||
expect(paths[0]).toContain('state=open')
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeIssue', () => {
|
||||
it('defaults missing labels/assignees/body to empty and maps a closed state', () => {
|
||||
const issue = normalizeIssue({
|
||||
|
||||
Reference in New Issue
Block a user