MEGAINFRA-WEBAPP/foreman/mod/tests/foreman.test.tsx
DEls e97bf93c7d chore: add foreman scheduler mod; keep it out of the app type-check
foreman/ is a Claude Code mod (job runner + hooks) that imports the
claude-code runtime types, so tsc over the app failed on it. Excluding
it from tsconfig keeps the Next build green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:05:50 +02:00

155 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, mock, test } from 'claude-code/testing'
import type { On } from 'claude-code'
const ROOT = '/Volumes/EXT/DEVELOPMENT/MEGAIFRASTRUCTURE/foreman'
const SURFACES = ['terminal', 'desktop'] as const
const PANE = {
component: 'Pane',
requestId: 'foreman',
// scroll and view are engine-filled; the pane never reads them.
props: { title: '👷 Foreman', isFocused: false, bodyColumns: 120, placement: 'dock' } as never,
} as const
// An in-memory copy of Foreman's folder, answering the plugin's $.fs calls.
function fakeDisk(on: On, files: Record<string, string>) {
const disk = new Map(Object.entries(files))
const runs: string[][] = []
on('fs.read', (_$, e) => (disk.has(e.path) ? { value: disk.get(e.path)! } : { deny: 'ENOENT' }))
on('fs.write', (_$, e) => {
disk.set(e.path, e.text)
return { value: undefined }
})
on('fs.list', (_$, e) => {
const prefix = `${e.path}/`
const names = new Set<string>()
for (const p of disk.keys()) if (p.startsWith(prefix)) names.add(p.slice(prefix.length).split('/')[0]!)
if (names.size === 0) return { deny: 'ENOENT' }
return { value: [...names].map(name => ({ name, kind: disk.has(prefix + name) ? ('file' as const) : ('dir' as const), size: 0, mtimeMs: 0, isLink: false })) }
})
on('process.run', (_$, e) => {
runs.push([...e.argv])
return { value: { exitCode: 0, stdout: '', stderr: '', isStdoutTruncated: false, isStderrTruncated: false } }
})
on('ui.open', () => ({ value: { isPlaced: true as const } }))
return { disk, runs }
}
const jobsJson = (jobs: unknown[]) => JSON.stringify({ paused: false, node: '/usr/local/bin/node', jobs })
const NOW = new Date(2026, 9, 9, 10, 30).getTime() // Fri 9 Oct 2026 10:30
test('an empty Foreman draws his guide on every surface', async ($, on) => {
mock.clock(on, { now: NOW })
mock.store(on)
fakeDisk(on, { [`${ROOT}/jobs.json`]: jobsJson([]) })
await $.command.run({ command: 'foreman', args: '' } as never)
for (const surface of SURFACES) {
const ui = await $.ui.mount({ plugin: 'foreman', surface, ...PANE })
expect(await ui.find({ type: 'Text', text: /^Foreman$/ })).toBeDefined()
expect(await ui.find({ type: 'Text', text: /No jobs yet/ })).toBeDefined()
expect(await ui.find({ type: 'Text', text: /Scheduler not running/ })).toBeDefined()
await ui.unmount()
}
})
test('the board shows what is next, what needs attention, and Reviewed clears it', async ($, on) => {
mock.clock(on, { now: NOW })
mock.store(on)
const run = {
runId: 'notes-2026-10-09-08-00', trigger: 'schedule', startedAt: new Date(2026, 9, 9, 8, 0).toISOString(),
status: 'attention', attentionCount: 2, summary: 'Two delivery notes have no purchase order.', report: 'runs/notes/x.md',
}
const { disk } = fakeDisk(on, {
[`${ROOT}/jobs.json`]: jobsJson([{ id: 'notes', name: 'Delivery notes check', skill: 'skills/notes/SKILL.md', schedule: 'mon-fri 08:00 11:00 14:00', enabled: true }]),
[`${ROOT}/state/notes.json`]: JSON.stringify({ id: 'notes', status: 'attention', lastRun: run, history: [run] }),
[`${ROOT}/state/scheduler.json`]: JSON.stringify({ lastTick: new Date(NOW - 30_000).toISOString() }),
[`${ROOT}/runs/notes/x.md`]: '# Delivery notes check\n\nVERDICT: ATTENTION (2 items)\n\n## Summary\nTwo notes lack a PO.',
[`${ROOT}/skills/notes/SKILL.md`]: '---\nname: notes\ndescription: Checks delivery notes\n---\nbody',
})
await $.command.run({ command: 'foreman', args: '' } as never)
for (const surface of SURFACES) {
const ui = await $.ui.mount({ plugin: 'foreman', surface, ...PANE })
expect(await ui.find({ type: 'Text', text: /^UP NEXT · 1$/ })).toBeDefined()
expect(await ui.find({ type: 'Text', text: /today 11:00/ })).toBeDefined()
expect(await ui.find({ type: 'Text', text: /^NEEDS ATTENTION · 1$/ })).toBeDefined()
expect(await ui.find({ type: 'Text', text: /Scheduler on/ })).toBeDefined()
await ui.unmount()
}
const ui = await $.ui.mount({ plugin: 'foreman', surface: 'desktop', ...PANE })
await ui.press({ key: `rep-${run.runId}` })
expect(await ui.find({ type: 'Text', text: /VERDICT: ATTENTION/ })).toBeDefined()
await ui.press({ key: 'rack' })
expect(JSON.parse(disk.get(`${ROOT}/state/acks.json`) ?? '[]')).toContain(run.runId)
await ui.press({ key: 'rback' })
expect(await ui.find({ type: 'Text', text: /^NEEDS ATTENTION · 0$/ })).toBeDefined()
await ui.unmount()
})
test('a skill goes on the board, its schedule is edited, and Run now starts the runner', async ($, on) => {
mock.clock(on, { now: NOW })
mock.store(on)
const { disk, runs } = fakeDisk(on, {
[`${ROOT}/jobs.json`]: jobsJson([]),
[`${ROOT}/skills/notes/SKILL.md`]: '---\nname: Delivery notes check\ndescription: Checks delivery notes\n---\nbody',
})
await $.command.run({ command: 'foreman', args: '' } as never)
const ui = await $.ui.mount({ plugin: 'foreman', surface: 'desktop', ...PANE })
await ui.press({ key: 'nav-skills' })
await ui.press({ key: `addskill-${ROOT}/skills/notes/SKILL.md` })
const jobs = () => JSON.parse(disk.get(`${ROOT}/jobs.json`)!).jobs as Array<Record<string, unknown>>
expect(jobs()[0]).toMatchObject({ id: 'delivery-notes-check', skill: 'skills/notes/SKILL.md', enabled: false })
await ui.input({ key: 'sched-delivery-notes-check', text: 'banana 8:00' })
expect(await ui.find({ type: 'Text', text: /Didn't understand "banana"/ })).toBeDefined()
expect(jobs()[0]?.schedule).toBe('mon-fri 08:00')
await ui.input({ key: 'sched-delivery-notes-check', text: 'every 2h mon-fri 07:00-17:00' })
expect(jobs()[0]?.schedule).toBe('every 2h mon-fri 07:00-17:00')
expect(await ui.find({ type: 'Text', text: /Every 2h, Mon–Fri, 07:00–17:00/ })).toBeDefined()
await ui.press({ key: 'etoggle-delivery-notes-check' })
expect(jobs()[0]?.enabled).toBe(true)
await ui.press({ key: 'erun-delivery-notes-check' })
expect(runs.at(-1)?.join(' ')).toContain('runner.mjs')
expect(runs.at(-1)?.join(' ')).toContain('run delivery-notes-check')
await ui.unmount()
})
test('the theme button cycles through the bright themes', async ($, on) => {
mock.clock(on, { now: NOW })
mock.store(on)
fakeDisk(on, { [`${ROOT}/jobs.json`]: jobsJson([]) })
for (const surface of SURFACES) {
const ui = await $.ui.mount({ plugin: 'foreman', surface, ...PANE })
const before = (await ui.find({ key: 'theme' }))?.text ?? ''
await ui.press({ key: 'theme' })
expect((await ui.find({ key: 'theme' }))?.text).not.toBe(before)
await ui.unmount()
}
})
test('dark themes (Dracula, Monokai) still draw every control', async ($, on) => {
mock.clock(on, { now: NOW })
mock.store(on)
fakeDisk(on, {
[`${ROOT}/jobs.json`]: jobsJson([{ id: 'notes', name: 'Delivery notes check', skill: 'skills/notes/SKILL.md', schedule: 'mon-fri 08:00', enabled: true }]),
})
await $.command.run({ command: 'foreman', args: '' } as never)
for (const surface of SURFACES) {
const ui = await $.ui.mount({ plugin: 'foreman', surface, ...PANE })
const seen: string[] = []
for (let i = 0; i < 8; i++) {
const label = (await ui.find({ key: 'theme' }))?.text ?? ''
seen.push(label)
if (/Dracula|Monokai/.test(label)) {
expect(await ui.find({ key: 'run-notes' })).toBeDefined()
expect(await ui.find({ key: 'toggle-notes' })).toBeDefined()
expect(await ui.find({ key: 'nav-skills' })).toBeDefined()
}
await ui.press({ key: 'theme' })
}
expect(seen.some(l => l.includes('Dracula'))).toBe(true)
expect(seen.some(l => l.includes('Monokai'))).toBe(true)
await ui.unmount()
}
})