Initial Release: OpenQode Public Alpha v1.3
This commit is contained in:
127
tests/QA-CHECKLIST.md
Normal file
127
tests/QA-CHECKLIST.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# TUI Manual QA Checklist
|
||||
|
||||
## Pre-Test Setup
|
||||
- [ ] Windows Terminal installed
|
||||
- [ ] PowerShell 7+ available
|
||||
- [ ] CMD available for fallback testing
|
||||
- [ ] Linux terminal available (WSL or actual Linux)
|
||||
|
||||
## Responsive Layout Tests
|
||||
|
||||
### Wide Mode (≥120 columns)
|
||||
- [ ] Sidebar visible with full width
|
||||
- [ ] All commands visible in sidebar
|
||||
- [ ] Multi-Agent status shows correctly
|
||||
- [ ] Main panel has comfortable width
|
||||
|
||||
### Medium Mode (90-119 columns)
|
||||
- [ ] Sidebar narrower but visible
|
||||
- [ ] Text truncates without breaking
|
||||
- [ ] Layout doesn't overflow
|
||||
|
||||
### Narrow Mode (<90 columns)
|
||||
- [ ] Sidebar hidden by default
|
||||
- [ ] Press Tab → sidebar appears
|
||||
- [ ] "[Tab] Hide" hint shows in sidebar
|
||||
- [ ] Press Tab again → sidebar hides
|
||||
- [ ] Ctrl+P → Command palette opens
|
||||
- [ ] All commands accessible via palette
|
||||
|
||||
### Tiny Mode (<60 cols or <20 rows)
|
||||
- [ ] Minimal chrome
|
||||
- [ ] Still functional
|
||||
- [ ] Input still visible and usable
|
||||
|
||||
## Resize Testing
|
||||
- [ ] Resize from wide to narrow while streaming → no crash
|
||||
- [ ] Resize to tiny height and back → layout recovers
|
||||
- [ ] Sidebar visibility persists across mode changes
|
||||
|
||||
## Markdown Rendering Tests
|
||||
|
||||
### Headings
|
||||
- [ ] `## Heading` appears on own line
|
||||
- [ ] Heading has spacing before/after
|
||||
- [ ] Heading does NOT merge with paragraph text
|
||||
|
||||
### Paragraphs
|
||||
- [ ] Paragraphs separated by blank line
|
||||
- [ ] Long text wraps cleanly
|
||||
- [ ] No text bleeding into borders
|
||||
|
||||
### Lists
|
||||
- [ ] Bullets aligned properly
|
||||
- [ ] Nested lists indented
|
||||
- [ ] List items don't merge
|
||||
|
||||
### Code Blocks
|
||||
- [ ] Bordered with language label
|
||||
- [ ] Syntax highlighting works
|
||||
- [ ] Unknown language falls back gracefully
|
||||
- [ ] Indentation preserved
|
||||
|
||||
## Smart Agent Flow Tests
|
||||
|
||||
### Commands
|
||||
- [ ] `/agents` → Opens menu
|
||||
- [ ] `/agents on` → "Multi: ON" in sidebar
|
||||
- [ ] `/agents off` → "Multi: OFF" in sidebar
|
||||
- [ ] `/agents list` → Shows all 6 agents
|
||||
- [ ] `/plan` → Switches to planner agent
|
||||
|
||||
### Agent Registry
|
||||
- [ ] Build agent present
|
||||
- [ ] Plan agent present
|
||||
- [ ] Test agent present
|
||||
- [ ] Docs agent present
|
||||
- [ ] Security agent present
|
||||
- [ ] Refactor agent present
|
||||
|
||||
## Command Palette Tests
|
||||
- [ ] Ctrl+P opens palette
|
||||
- [ ] Shows all 12 commands
|
||||
- [ ] Arrow keys navigate
|
||||
- [ ] Enter selects command
|
||||
- [ ] ESC closes palette
|
||||
- [ ] Selected command appears in input
|
||||
|
||||
## Terminal Compatibility
|
||||
|
||||
### Windows Terminal
|
||||
- [ ] Unicode borders render (╭╮╯╰)
|
||||
- [ ] Emojis display correctly
|
||||
- [ ] Colors correct
|
||||
|
||||
### PowerShell
|
||||
- [ ] Functional
|
||||
- [ ] Fallback borders if needed
|
||||
|
||||
### CMD
|
||||
- [ ] Falls back to ASCII borders
|
||||
- [ ] Still readable and functional
|
||||
|
||||
### Linux Terminal (bash/zsh)
|
||||
- [ ] Unicode renders correctly
|
||||
- [ ] Colors correct
|
||||
- [ ] All features work
|
||||
|
||||
## Streaming Tests
|
||||
- [ ] Long response streams smoothly
|
||||
- [ ] No excessive re-rendering
|
||||
- [ ] Headings appear properly during stream
|
||||
- [ ] Code blocks form correctly as they stream
|
||||
|
||||
## Existing Feature Verification
|
||||
- [ ] `/help` works
|
||||
- [ ] `/context` toggles context
|
||||
- [ ] `/clear` clears session
|
||||
- [ ] `/paste` pastes from clipboard
|
||||
- [ ] `/write` writes pending files
|
||||
- [ ] `/exit` exits TUI
|
||||
- [ ] Project selection works
|
||||
- [ ] Agent selection works
|
||||
- [ ] Git branch shows correctly
|
||||
|
||||
## Pass Criteria
|
||||
All items must be checked ✓ for QA pass.
|
||||
Document any failures with screenshots and steps to reproduce.
|
||||
221
tests/tui-components.test.mjs
Normal file
221
tests/tui-components.test.mjs
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* TUI Component Tests
|
||||
* Tests for markdown rendering, layout utilities, and theme
|
||||
*
|
||||
* Run with: node --experimental-vm-modules node_modules/jest/bin/jest.js tests/
|
||||
* Or: npm test (if configured in package.json)
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from '@jest/globals';
|
||||
import { computeLayoutMode, truncateText, calculateViewport, getTextWidth } from '../bin/tui-layout.mjs';
|
||||
import { theme } from '../bin/tui-theme.mjs';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// LAYOUT UTILITY TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
describe('Layout Utilities', () => {
|
||||
describe('computeLayoutMode', () => {
|
||||
test('returns wide mode for columns >= 120', () => {
|
||||
const result = computeLayoutMode(120, 40);
|
||||
expect(result.mode).toBe('wide');
|
||||
expect(result.sidebarWidth).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('returns medium mode for columns 90-119', () => {
|
||||
const result = computeLayoutMode(100, 40);
|
||||
expect(result.mode).toBe('medium');
|
||||
});
|
||||
|
||||
test('returns narrow mode for columns 60-89', () => {
|
||||
const result = computeLayoutMode(80, 40);
|
||||
expect(result.mode).toBe('narrow');
|
||||
expect(result.sidebarWidth).toBe(0); // collapsed by default
|
||||
});
|
||||
|
||||
test('returns tiny mode for columns < 60', () => {
|
||||
const result = computeLayoutMode(50, 40);
|
||||
expect(result.mode).toBe('tiny');
|
||||
});
|
||||
|
||||
test('returns tiny mode for rows < 20', () => {
|
||||
const result = computeLayoutMode(100, 15);
|
||||
expect(result.mode).toBe('tiny');
|
||||
});
|
||||
|
||||
test('handles null dimensions with defaults', () => {
|
||||
const result = computeLayoutMode(null, null);
|
||||
expect(result.cols).toBe(80);
|
||||
expect(result.rows).toBe(24);
|
||||
});
|
||||
});
|
||||
|
||||
describe('truncateText', () => {
|
||||
test('returns empty string for empty input', () => {
|
||||
expect(truncateText('', 10)).toBe('');
|
||||
expect(truncateText(null, 10)).toBe('');
|
||||
});
|
||||
|
||||
test('returns original text if shorter than width', () => {
|
||||
expect(truncateText('hello', 10)).toBe('hello');
|
||||
});
|
||||
|
||||
test('truncates text longer than width', () => {
|
||||
const result = truncateText('hello world', 8);
|
||||
expect(result.length).toBeLessThanOrEqual(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateViewport', () => {
|
||||
test('calculates viewport height correctly', () => {
|
||||
const layout = { rows: 40, cols: 100, mode: 'wide' };
|
||||
const viewport = calculateViewport(layout, {
|
||||
headerRows: 2,
|
||||
inputRows: 4,
|
||||
thinkingRows: 0,
|
||||
marginsRows: 2
|
||||
});
|
||||
expect(viewport.viewHeight).toBe(32); // 40 - 2 - 4 - 0 - 2
|
||||
expect(viewport.maxMessages).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('ensures minimum viewport height', () => {
|
||||
const layout = { rows: 10, cols: 100, mode: 'wide' };
|
||||
const viewport = calculateViewport(layout, {
|
||||
headerRows: 5,
|
||||
inputRows: 5,
|
||||
thinkingRows: 5,
|
||||
marginsRows: 5
|
||||
});
|
||||
expect(viewport.viewHeight).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// THEME TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
describe('Theme System', () => {
|
||||
test('has required color properties', () => {
|
||||
expect(theme.colors).toBeDefined();
|
||||
expect(theme.colors.fg).toBeDefined();
|
||||
expect(theme.colors.muted).toBeDefined();
|
||||
expect(theme.colors.success).toBeDefined();
|
||||
expect(theme.colors.error).toBeDefined();
|
||||
expect(theme.colors.warning).toBeDefined();
|
||||
expect(theme.colors.info).toBeDefined();
|
||||
});
|
||||
|
||||
test('has spacing tokens', () => {
|
||||
expect(theme.spacing).toBeDefined();
|
||||
expect(theme.spacing.xs).toBeDefined();
|
||||
expect(theme.spacing.sm).toBeDefined();
|
||||
expect(theme.spacing.md).toBeDefined();
|
||||
});
|
||||
|
||||
test('has border styles', () => {
|
||||
expect(theme.borders).toBeDefined();
|
||||
expect(theme.borders.single).toBeDefined();
|
||||
expect(theme.borders.round).toBeDefined();
|
||||
});
|
||||
|
||||
test('has icon definitions', () => {
|
||||
expect(theme.icons).toBeDefined();
|
||||
expect(theme.icons.prompt).toBeDefined();
|
||||
expect(theme.icons.bullet).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// STREAMING BUFFER TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
describe('Streaming Buffer', () => {
|
||||
// Note: These are conceptual tests - actual hook testing requires React testing library
|
||||
|
||||
test('buffer module exports correctly', async () => {
|
||||
const { useStreamBuffer, useResizeDebounce } = await import('../bin/tui-stream-buffer.mjs');
|
||||
expect(typeof useStreamBuffer).toBe('function');
|
||||
expect(typeof useResizeDebounce).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MESSAGE PARSING TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Logic mirrored from flattenMessagesToBlocks in opencode-ink.mjs
|
||||
|
||||
const splitMessageContent = (content) => {
|
||||
// Regex captures: Code blocks OR [AGENT: Name] tags
|
||||
// Match code blocks (```...```) OR Agent tags ([AGENT: ...])
|
||||
return content.split(/(```[\s\S]*?```|\[AGENT:[^\]]+\])/g);
|
||||
};
|
||||
|
||||
describe('Message Parsing', () => {
|
||||
test('splits agent tags correctly', () => {
|
||||
const input = "Thinking...\n[AGENT: Security]\nReviewing code...";
|
||||
const parts = splitMessageContent(input).filter(p => p.trim());
|
||||
|
||||
expect(parts.length).toBeGreaterThan(1);
|
||||
expect(parts.some(p => p.includes('[AGENT: Security]'))).toBe(true);
|
||||
});
|
||||
|
||||
test('interleaves agent tags and code blocks', () => {
|
||||
const input = "Start [AGENT: Planner] Plan:\n```text\nstep 1\n```\n[AGENT: Builder] Go.";
|
||||
const parts = splitMessageContent(input).filter(p => p.trim());
|
||||
|
||||
// Expected: "Start", "[AGENT: Planner]", "Plan:", "```...```", "[AGENT: Builder]", "Go."
|
||||
expect(parts).toContain('[AGENT: Planner]');
|
||||
expect(parts).toContain('[AGENT: Builder]');
|
||||
const codeBlock = parts.find(p => p.startsWith('```'));
|
||||
expect(codeBlock).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SMART AGENT FLOW TESTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
describe('Smart Agent Flow', () => {
|
||||
test('exports SmartAgentFlow class', async () => {
|
||||
const { SmartAgentFlow, getSmartAgentFlow } = await import('../bin/smart-agent-flow.mjs');
|
||||
expect(SmartAgentFlow).toBeDefined();
|
||||
expect(typeof getSmartAgentFlow).toBe('function');
|
||||
});
|
||||
|
||||
test('getSmartAgentFlow returns singleton instance', async () => {
|
||||
const { getSmartAgentFlow } = await import('../bin/smart-agent-flow.mjs');
|
||||
const flow1 = getSmartAgentFlow();
|
||||
const flow2 = getSmartAgentFlow();
|
||||
expect(flow1).toBe(flow2);
|
||||
});
|
||||
|
||||
test('has built-in agents', async () => {
|
||||
const { getSmartAgentFlow } = await import('../bin/smart-agent-flow.mjs');
|
||||
const flow = getSmartAgentFlow();
|
||||
const agents = flow.getAgents();
|
||||
expect(agents.length).toBeGreaterThanOrEqual(6);
|
||||
expect(agents.some(a => a.id === 'build')).toBe(true);
|
||||
expect(agents.some(a => a.id === 'plan')).toBe(true);
|
||||
expect(agents.some(a => a.id === 'test')).toBe(true);
|
||||
});
|
||||
|
||||
test('analyzeRequest detects security patterns', async () => {
|
||||
const { getSmartAgentFlow } = await import('../bin/smart-agent-flow.mjs');
|
||||
const flow = getSmartAgentFlow();
|
||||
const result = flow.analyzeRequest('add authentication and password handling');
|
||||
expect(result.patterns).toContain('security-sensitive');
|
||||
});
|
||||
|
||||
test('toggle enables/disables multi-agent mode', async () => {
|
||||
const { getSmartAgentFlow } = await import('../bin/smart-agent-flow.mjs');
|
||||
const flow = getSmartAgentFlow();
|
||||
flow.toggle(true);
|
||||
expect(flow.config.enabled).toBe(true);
|
||||
flow.toggle(false);
|
||||
expect(flow.config.enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('✅ All test suites loaded successfully');
|
||||
Reference in New Issue
Block a user