diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-06-03 23:01:39 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-06-03 23:01:39 +0000 |
| commit | 68cf1e24dd2ca2612b72babc4b35280cd3512f92 (patch) | |
| tree | 14b73f21570a2f42ae729ca7e9676de6628d6819 /web/test/stories.test.mjs | |
| parent | b28cfc6ff288d083f6c8e9c055b69bfcadbceccc (diff) | |
| parent | 7388337d3be5cc7f65ef547e30b2e39884dd165b (diff) | |
merge: integrate oss branch — budget gating, MCP back-channel, events, loopback-only bind
Key features from OSS branch:
- Budget gating: rolling per-provider spend caps with BUDGET_EXCEEDED task state
- Agent MCP back-channel: runners mint tokens; /mcp endpoint resolves them
- Chatbot MCP server at /chatbot/mcp (requires api_token in config)
- Event timeline: unified event stream replacing ad-hoc question/summary flows
- Loopback-only default bind (127.0.0.1:8484); external_bind_allowed=true to expose
- repository_url required on task creation (enforces traceability)
- Auto-checker: spawns verification task after each execution
Conflict resolutions:
- serve.go: keep cfg.Runners.XEnabled() conditional registration from main + agentRegistry from oss
- config.go: keep RunnersConfig from main + BudgetConfig/ExternalBindAllowed from oss
- server.go: handleStaticFiles (base-path rewrite) kept; deduplicated duplicate from both sides
- executor/claude.go: accepted oss deletion (ClaudeRunner replaced by ContainerRunner)
- container_test.go: added noopChannel + AgentChannel parameter to Run call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web/test/stories.test.mjs')
| -rw-r--r-- | web/test/stories.test.mjs | 164 |
1 files changed, 0 insertions, 164 deletions
diff --git a/web/test/stories.test.mjs b/web/test/stories.test.mjs deleted file mode 100644 index 263f202..0000000 --- a/web/test/stories.test.mjs +++ /dev/null @@ -1,164 +0,0 @@ -// stories.test.mjs — TDD tests for stories UI functions. -// -// Run with: node --test web/test/stories.test.mjs - -import { describe, it, beforeEach } from 'node:test'; -import assert from 'node:assert/strict'; -import { renderStoryCard, storyStatusLabel } from '../app.js'; - -// ── Minimal DOM mock ────────────────────────────────────────────────────────── - -function makeMockDoc() { - function createElement(tag) { - return { - tag, - className: '', - textContent: '', - innerHTML: '', - hidden: false, - dataset: {}, - children: [], - _listeners: {}, - style: {}, - appendChild(child) { this.children.push(child); return child; }, - prepend(...nodes) { this.children.unshift(...nodes); }, - append(...nodes) { nodes.forEach(n => this.children.push(n)); }, - addEventListener(ev, fn) { this._listeners[ev] = fn; }, - querySelector(sel) { - const cls = sel.startsWith('.') ? sel.slice(1) : null; - function search(el) { - if (cls && el.className && el.className.split(' ').includes(cls)) return el; - for (const c of el.children || []) { - const found = search(c); - if (found) return found; - } - return null; - } - return search(this); - }, - querySelectorAll(sel) { - const cls = sel.startsWith('.') ? sel.slice(1) : null; - const results = []; - function search(el) { - if (cls && el.className && el.className.split(' ').includes(cls)) results.push(el); - for (const c of el.children || []) search(c); - } - search(this); - return results; - }, - }; - } - return { createElement }; -} - -function makeStory(overrides = {}) { - return { - id: 'story-1', - name: 'Add login page', - project_id: 'claudomator', - branch_name: 'story/add-login-page', - status: 'PENDING', - created_at: '2026-03-25T10:00:00Z', - updated_at: '2026-03-25T10:00:00Z', - ...overrides, - }; -} - -// ── storyStatusLabel ────────────────────────────────────────────────────────── - -describe('storyStatusLabel', () => { - it('returns human-readable label for PENDING', () => { - assert.equal(storyStatusLabel('PENDING'), 'Pending'); - }); - - it('returns human-readable label for IN_PROGRESS', () => { - assert.equal(storyStatusLabel('IN_PROGRESS'), 'In Progress'); - }); - - it('returns human-readable label for SHIPPABLE', () => { - assert.equal(storyStatusLabel('SHIPPABLE'), 'Shippable'); - }); - - it('returns human-readable label for DEPLOYED', () => { - assert.equal(storyStatusLabel('DEPLOYED'), 'Deployed'); - }); - - it('returns human-readable label for VALIDATING', () => { - assert.equal(storyStatusLabel('VALIDATING'), 'Validating'); - }); - - it('returns human-readable label for REVIEW_READY', () => { - assert.equal(storyStatusLabel('REVIEW_READY'), 'Review Ready'); - }); - - it('returns human-readable label for NEEDS_FIX', () => { - assert.equal(storyStatusLabel('NEEDS_FIX'), 'Needs Fix'); - }); - - it('falls back to the raw status for unknown values', () => { - assert.equal(storyStatusLabel('UNKNOWN_STATE'), 'UNKNOWN_STATE'); - }); -}); - -// ── renderStoryCard ─────────────────────────────────────────────────────────── - -describe('renderStoryCard', () => { - let doc; - beforeEach(() => { doc = makeMockDoc(); }); - - it('renders the story name', () => { - const card = renderStoryCard(makeStory(), doc); - function findText(el, text) { - if (el.textContent === text) return true; - return (el.children || []).some(c => findText(c, text)); - } - assert.ok(findText(card, 'Add login page'), 'card should contain story name'); - }); - - it('has story-card class', () => { - const card = renderStoryCard(makeStory(), doc); - assert.ok(card.className.split(' ').includes('story-card'), 'root element should have story-card class'); - }); - - it('status badge has data-status matching story status', () => { - const card = renderStoryCard(makeStory({ status: 'IN_PROGRESS' }), doc); - const badge = card.querySelector('.story-status-badge'); - assert.ok(badge, 'badge element should exist'); - assert.equal(badge.dataset.status, 'IN_PROGRESS'); - }); - - it('status badge shows human-readable label', () => { - const card = renderStoryCard(makeStory({ status: 'REVIEW_READY' }), doc); - const badge = card.querySelector('.story-status-badge'); - assert.equal(badge.textContent, 'Review Ready'); - }); - - it('shows project_id', () => { - const card = renderStoryCard(makeStory({ project_id: 'nav' }), doc); - function findText(el, text) { - if (el.textContent === text) return true; - return (el.children || []).some(c => findText(c, text)); - } - assert.ok(findText(card, 'nav'), 'card should show project_id'); - }); - - it('shows branch_name when present', () => { - const card = renderStoryCard(makeStory({ branch_name: 'story/my-feature' }), doc); - function findText(el, text) { - if (el.textContent && el.textContent.includes(text)) return true; - return (el.children || []).some(c => findText(c, text)); - } - assert.ok(findText(card, 'story/my-feature'), 'card should show branch_name'); - }); - - it('does not show branch section when branch_name is empty', () => { - const card = renderStoryCard(makeStory({ branch_name: '' }), doc); - const branchEl = card.querySelector('.story-branch'); - assert.ok(!branchEl, 'no .story-branch element when branch is empty'); - }); - - it('card dataset.storyId is set to story id', () => { - const card = renderStoryCard(makeStory({ id: 'abc-123' }), doc); - assert.equal(card.dataset.storyId, 'abc-123'); - }); -}); |
