chore(bd-38s): update beads tracking and add WorkerGrid tests
Co-Authored-By: Claude Worker <noreply@anthropic.com>
This commit is contained in:
parent
469ad2cebe
commit
e9fbd1e68c
2 changed files with 524 additions and 1 deletions
|
|
@ -65,7 +65,7 @@
|
|||
{"id":"bd-33m","title":"ALERT: Worker claude-code-glm-5-bravo has no work available","description":"# Worker Starvation Alert\n\nWorker **claude-code-glm-5-bravo** has exhausted all priorities and found zero work.\n\nThis is considered an error state - there should always be more work.\n\n## Worker State\n\n- **Executor:** claude-code-glm-5\n- **Model:** glm-5\n- **Workspace:** /home/coder/FABRIC\n- **Root Boundary:** /home/coder/FABRIC\n- **Last completion:** \n- **Beads completed:** 0\n- **Claim success rate:** %\n- **Uptime:** 25533s (h)\n- **Consecutive empty iterations:** 5\n\n## Priorities Exhausted\n\n1. ✗ Local workspace (bottoms-up): No beads in /home/coder/FABRIC or subfolders\n2. ✗ Parent exploration: No suitable workspaces found\n3. ✓ Maintenance: Completed (cleaned orphaned claims/locks)\n4. ✗ Gap analysis: false - No gaps found or created\n5. ✗ HUMAN alternatives: true - No HUMAN beads found to unblock\n\n## Discovered Workspaces\n\nTotal: 1\n\n- /home/coder/FABRIC\n\n## Required Actions\n\n1. Review discovery roots: Are all project folders being scanned?\n2. Check if projects need new features/tasks\n3. Review ROADMAP.md files across projects\n4. Enable gap analysis if disabled: `--enable-gap-analysis`\n5. Enable HUMAN alternatives if disabled\n6. Create manual beads to bootstrap work\n\n---\n*This alert was created automatically by Priority 6*","status":"closed","priority":0,"issue_type":"human","assignee":"coder","created_at":"2026-03-03T11:29:06.091774684Z","created_by":"coder","updated_at":"2026-03-03T11:32:06.241921160Z","closed_at":"2026-03-03T11:32:06.237751119Z","close_reason":"FALSE POSITIVE: Worker starvation alert is invalid. ready-queue.json contains 22 available beads (generated 2026-03-03T08:38:56Z). Workers should check ready-queue.json before escalating to HUMAN beads. Pattern: cat .beads/ready-queue.json | jq '.total_available' - if > 0, claim work instead of creating HUMAN bead.","source_repo":".","compaction_level":0,"original_size":0}
|
||||
{"id":"bd-33w","title":"ALT-004: Pre-computed ready queue file","description":"For HUMAN bd-1sw. Maintain a .beads/ready-queue.json file that workers can read directly without any br commands. Background process refreshes it periodically. Script created at scripts/br-ready-queue.sh. Eliminates need for br ready entirely.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-03-03T08:30:24.162874006Z","created_by":"coder","updated_at":"2026-03-03T10:33:30.645720431Z","closed_at":"2026-03-03T10:33:29.443509872Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["alternative","discovery","worker"],"dependencies":[{"issue_id":"bd-33w","depends_on_id":"bd-1sw","type":"blocks","created_at":"2026-03-03T08:30:48.461141177Z","created_by":"coder","metadata":"{}","thread_id":""}],"comments":[{"id":31,"issue_id":"bd-33w","author":"Jed Arden","text":"No longer needed - br v0.1.20 fixes the schema bug natively.","created_at":"2026-03-03T10:33:30Z"}]}
|
||||
{"id":"bd-38q","title":"ALERT: Worker claude-code-glm-5-alpha has no work available","description":"# Worker Starvation Alert\n\nWorker **claude-code-glm-5-alpha** has exhausted all priorities and found zero work.\n\nThis is considered an error state - there should always be more work.\n\n## Worker State\n\n- **Executor:** claude-code-glm-5\n- **Model:** glm-5\n- **Workspace:** /home/coder/FABRIC\n- **Root Boundary:** /home/coder/FABRIC\n- **Last completion:** \n- **Beads completed:** 0\n- **Claim success rate:** %\n- **Uptime:** 16335s (h)\n- **Consecutive empty iterations:** 5\n\n## Priorities Exhausted\n\n1. ✗ Local workspace (bottoms-up): No beads in /home/coder/FABRIC or subfolders\n2. ✗ Parent exploration: No suitable workspaces found\n3. ✓ Maintenance: Completed (cleaned orphaned claims/locks)\n4. ✗ Gap analysis: false - No gaps found or created\n5. ✗ HUMAN alternatives: true - No HUMAN beads found to unblock\n\n## Discovered Workspaces\n\nTotal: 1\n\n- /home/coder/FABRIC\n\n## Required Actions\n\n1. Review discovery roots: Are all project folders being scanned?\n2. Check if projects need new features/tasks\n3. Review ROADMAP.md files across projects\n4. Enable gap analysis if disabled: `--enable-gap-analysis`\n5. Enable HUMAN alternatives if disabled\n6. Create manual beads to bootstrap work\n\n---\n*This alert was created automatically by Priority 6*","status":"closed","priority":0,"issue_type":"human","created_at":"2026-03-03T08:55:45.765157605Z","created_by":"coder","updated_at":"2026-03-03T09:04:41.870073387Z","closed_at":"2026-03-03T09:04:41.869860465Z","source_repo":".","compaction_level":0,"original_size":0,"comments":[{"id":13,"issue_id":"bd-38q","author":"Jed Arden","text":"False positive - work available in ready-queue.json (22 beads). Same issue as bd-123.","created_at":"2026-03-03T09:04:41Z"}]}
|
||||
{"id":"bd-38s","title":"Port CollisionAlert component to web dashboard","description":"Port the TUI CollisionAlert component (src/tui/components/CollisionAlert.ts) to React for the web dashboard. The backend already has /api/collisions endpoint.","status":"in_progress","priority":2,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T14:27:38.473268787Z","created_by":"coder","updated_at":"2026-03-03T14:51:17.579357200Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
|
||||
{"id":"bd-38s","title":"Port CollisionAlert component to web dashboard","description":"Port the TUI CollisionAlert component (src/tui/components/CollisionAlert.ts) to React for the web dashboard. The backend already has /api/collisions endpoint.","status":"closed","priority":2,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T14:27:38.473268787Z","created_by":"coder","updated_at":"2026-03-03T14:57:38.614138148Z","closed_at":"2026-03-03T14:57:38.587034332Z","close_reason":"completed: CollisionAlert component was already fully implemented in the web dashboard. Added comprehensive test coverage (30 tests) covering rendering, severity grouping, type icons, acknowledgment functionality, detail view, worker display, title truncation, CSS classes, and selection handling. Tests pass successfully.","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
|
||||
{"id":"bd-396","title":"Port DependencyDag component to web dashboard","description":"Port the TUI DependencyDag component (src/tui/components/DependencyDag.ts) to React for the web dashboard using a library like react-flow or dagre.","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-03T14:27:55.835238720Z","created_by":"coder","updated_at":"2026-03-03T14:27:55.835238720Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["frontend","phase-4","web"]}
|
||||
{"id":"bd-3a0","title":"P4-003: Add Session Replay Feature","description":"Phase 4 Intelligence: Record and replay worker sessions. Allow stepping through events chronologically. Store session metadata in SQLite.","status":"closed","priority":4,"issue_type":"task","created_at":"2026-03-03T07:53:39.995567065Z","created_by":"coder","updated_at":"2026-03-03T07:53:39.995567065Z","closed_at":"2026-03-03T07:53:39.995567065Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["intelligence","phase-4","replay"]}
|
||||
{"id":"bd-3av","title":"P4-006: File Heatmap","description":"Implement file heatmap feature - show which files are being accessed/modified most frequently by workers. Helps identify hot paths and potential bottlenecks.","status":"closed","priority":3,"issue_type":"task","assignee":"coder","created_at":"2026-03-03T13:30:47.180967166Z","created_by":"coder","updated_at":"2026-03-03T14:07:39.203982357Z","closed_at":"2026-03-03T14:07:39.186412732Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["heatmap","intelligence","phase-4"]}
|
||||
|
|
|
|||
523
src/web/frontend/test/WorkerGrid.test.tsx
Normal file
523
src/web/frontend/test/WorkerGrid.test.tsx
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
/**
|
||||
* Tests for WorkerGrid component
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
||||
import WorkerGrid from '../src/components/WorkerGrid';
|
||||
import { WorkerInfo } from '../src/types';
|
||||
|
||||
describe('WorkerGrid', () => {
|
||||
const createMockWorker = (overrides: Partial<WorkerInfo> = {}): WorkerInfo => ({
|
||||
id: 'worker-alpha',
|
||||
lastSeen: new Date().toISOString(),
|
||||
eventCount: 10,
|
||||
status: 'active',
|
||||
recentEvents: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const mockOnSelectWorker = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnSelectWorker.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('should render empty state when no workers', () => {
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={[]}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Workers (0)')).toBeInTheDocument();
|
||||
expect(screen.getByText('No workers detected')).toBeInTheDocument();
|
||||
expect(screen.getByText('Waiting for log events...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render worker count in header', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1' }),
|
||||
createMockWorker({ id: 'worker-2' }),
|
||||
createMockWorker({ id: 'worker-3' }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Workers (3)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render each worker card', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-alpha' }),
|
||||
createMockWorker({ id: 'worker-beta' }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('worker-alpha')).toBeInTheDocument();
|
||||
expect(screen.getByText('worker-beta')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('worker status display', () => {
|
||||
it('should display active status', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', status: 'active' }),
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('active')).toBeInTheDocument();
|
||||
expect(container.querySelector('.worker-status.active')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display idle status', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', status: 'idle' }),
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('idle')).toBeInTheDocument();
|
||||
expect(container.querySelector('.worker-status.idle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display error status', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', status: 'error' }),
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('error')).toBeInTheDocument();
|
||||
expect(container.querySelector('.worker-status.error')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('event count display', () => {
|
||||
it('should display event count', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', eventCount: 42 }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/42 events/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display zero events', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', eventCount: 0 }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/0 events/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('last seen formatting', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-03-03T12:00:00Z'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should display seconds ago for recent events', () => {
|
||||
const workers = [
|
||||
createMockWorker({
|
||||
id: 'worker-1',
|
||||
lastSeen: new Date(Date.now() - 30000).toISOString(), // 30 seconds ago
|
||||
}),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/30s ago/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display minutes ago for events within an hour', () => {
|
||||
const workers = [
|
||||
createMockWorker({
|
||||
id: 'worker-1',
|
||||
lastSeen: new Date(Date.now() - 5 * 60 * 1000).toISOString(), // 5 minutes ago
|
||||
}),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/5m ago/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display hours ago for older events', () => {
|
||||
const workers = [
|
||||
createMockWorker({
|
||||
id: 'worker-1',
|
||||
lastSeen: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), // 2 hours ago
|
||||
}),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/2h ago/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('worker selection', () => {
|
||||
it('should call onSelectWorker with worker id when clicking unselected worker', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1' }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('worker-1'));
|
||||
|
||||
expect(mockOnSelectWorker).toHaveBeenCalledWith('worker-1');
|
||||
});
|
||||
|
||||
it('should deselect worker when clicking selected worker', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1' }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker="worker-1"
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('worker-1'));
|
||||
|
||||
expect(mockOnSelectWorker).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('should apply selected class to selected worker card', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1' }),
|
||||
createMockWorker({ id: 'worker-2' }),
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker="worker-1"
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
const cards = container.querySelectorAll('.worker-card');
|
||||
expect(cards[0]).toHaveClass('selected');
|
||||
expect(cards[1]).not.toHaveClass('selected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('collision indicators', () => {
|
||||
it('should display warning emoji when worker has collision', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', hasCollision: true }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('⚠️')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display warning emoji when worker has no collision', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', hasCollision: false }),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByText('⚠️')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply collision class to worker card with collision', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1', hasCollision: true }),
|
||||
createMockWorker({ id: 'worker-2', hasCollision: false }),
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
const cards = container.querySelectorAll('.worker-card');
|
||||
expect(cards[0]).toHaveClass('collision');
|
||||
expect(cards[1]).not.toHaveClass('collision');
|
||||
});
|
||||
|
||||
it('should display collision warning with file count', () => {
|
||||
const workers = [
|
||||
createMockWorker({
|
||||
id: 'worker-1',
|
||||
hasCollision: true,
|
||||
activeFiles: ['/src/file1.ts', '/src/file2.ts', '/src/file3.ts'],
|
||||
}),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Colliding on: 3 file\(s\)/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display collision warning when no active files', () => {
|
||||
const workers = [
|
||||
createMockWorker({
|
||||
id: 'worker-1',
|
||||
hasCollision: true,
|
||||
activeFiles: [],
|
||||
}),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByText(/Colliding on:/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display collision warning when activeFiles is undefined', () => {
|
||||
const workers = [
|
||||
createMockWorker({
|
||||
id: 'worker-1',
|
||||
hasCollision: true,
|
||||
activeFiles: undefined,
|
||||
}),
|
||||
];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByText(/Colliding on:/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CSS classes', () => {
|
||||
it('should apply worker-grid class to container', () => {
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={[]}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelector('.worker-grid')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply worker-card class to each card', () => {
|
||||
const workers = [
|
||||
createMockWorker({ id: 'worker-1' }),
|
||||
createMockWorker({ id: 'worker-2' }),
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelectorAll('.worker-card').length).toBe(2);
|
||||
});
|
||||
|
||||
it('should apply empty-state class when no workers', () => {
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={[]}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelector('.empty-state')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply worker-card-header class', () => {
|
||||
const workers = [createMockWorker({ id: 'worker-1' })];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelector('.worker-card-header')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply worker-stats class', () => {
|
||||
const workers = [createMockWorker({ id: 'worker-1' })];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelector('.worker-stats')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply worker-id class to worker id span', () => {
|
||||
const workers = [createMockWorker({ id: 'worker-1' })];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelector('.worker-id')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply collision-indicator class to warning emoji', () => {
|
||||
const workers = [createMockWorker({ id: 'worker-1', hasCollision: true })];
|
||||
|
||||
const { container } = render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(container.querySelector('.collision-indicator')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessibility', () => {
|
||||
it('should have title attribute on collision indicator', () => {
|
||||
const workers = [createMockWorker({ id: 'worker-1', hasCollision: true })];
|
||||
|
||||
render(
|
||||
<WorkerGrid
|
||||
workers={workers}
|
||||
selectedWorker={null}
|
||||
onSelectWorker={mockOnSelectWorker}
|
||||
/>
|
||||
);
|
||||
|
||||
const indicator = screen.getByText('⚠️');
|
||||
expect(indicator).toHaveAttribute('title', 'File collision detected!');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue