Files
clide/test/builtin/claude/activity_tab_test.dart
T
jpmschweitzerandClaude Opus 4.8 b399acedbf surface Claude Code Workflow runs in convo + sidebar (T-416)
A `Workflow` tool-use launches its multi-agent run in the background and
returns immediately; the real fan-out arrives out-of-band on stream-json
`type:"system"` task_* events (task_started / task_progress / task_updated /
task_notification) keyed by the launching tool-use id — which clide was
dropping. (Wire shape captured by two live stream-json probes; recorded on
the ticket.)

- workflow_run.dart: a pure, Flutter-free WorkflowRun/WorkflowAgent model
  that folds those events (phases, per-agent start→progress→done deltas,
  usage) into a snapshot.
- StreamJsonSession recognises the events, accumulates a
  Map<toolUseId, WorkflowRun>, and exposes `workflows` + `workflowsStream`.
- A `Workflow` tool-use with a live run renders a dedicated run card —
  phase groups, per-agent rows with spinner/check status, usage, and the
  script — falling back to the generic tool card pre-progress or on reload.
  The run breaks the activity cluster so it's always first-class (like T-342).
- The sidebar Activity tab adds a WORKFLOWS section: one row per run with its
  done/total agent count, tinted by running/done state.

Closes T-416 and the T-410 epic (all children done).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 21:44:41 +02:00

63 lines
2.8 KiB
Dart

/// Direct tests for ActivityTabView (T-415): the USAGE block renders parsed
/// /usage values; the empty state still shows under the control strip.
library;
import 'package:clide/builtin/claude/src/claude_stats.dart';
import 'package:clide/builtin/claude/src/claude_status.dart' show ClaudeUsage;
import 'package:clide/builtin/claude/src/meta_sidebar/activity_tab.dart';
import 'package:clide/builtin/claude/src/workflow_run.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
import '../../helpers/widget_harness.dart';
void main() {
late KernelFixture f;
setUp(() async => f = await KernelFixture.create());
tearDown(() => f.dispose());
testWidgets('renders the USAGE block from parsed /usage values', (tester) async {
const usage = ClaudeUsage(session: '15% used · resets Jun 12, 3:39pm', week: '53% used · resets Jun 15, 6:59pm', weekSonnet: '0% used');
await tester.pumpWidget(harness(f, const ActivityTabView(stats: ClaudeStats(), primaryStatus: null, config: null, usage: usage)));
await tester.pump();
expect(find.text('USAGE'), findsOneWidget);
expect(find.text('15% used · resets Jun 12, 3:39pm'), findsOneWidget);
expect(find.text('53% used · resets Jun 15, 6:59pm'), findsOneWidget);
expect(find.text('0% used'), findsOneWidget);
});
testWidgets('no stats and no usage → the control strip plus the placeholder', (tester) async {
await tester.pumpWidget(harness(f, const ActivityTabView(stats: ClaudeStats(), primaryStatus: null, config: null)));
await tester.pump();
expect(find.text('SESSION'), findsOneWidget); // controls always present
expect(find.text('No activity recorded yet.'), findsOneWidget);
expect(find.text('USAGE'), findsNothing);
});
testWidgets('renders a WORKFLOWS row per live run with its done/total count (T-416)', (tester) async {
var run = const WorkflowRun(toolUseId: 'x1', name: 'parallel-words');
run = run.foldEvent({
'subtype': 'task_progress',
'tool_use_id': 'x1',
'workflow_progress': [
{'type': 'workflow_agent', 'index': 1, 'label': 'a', 'state': 'done'},
{'type': 'workflow_agent', 'index': 2, 'label': 'b', 'state': 'start'},
],
});
await tester.pumpWidget(harness(f, ActivityTabView(stats: const ClaudeStats(), primaryStatus: null, config: null, workflows: {'x1': run})));
await tester.pump();
expect(find.text('WORKFLOWS'), findsOneWidget);
expect(find.text('parallel-words'), findsOneWidget);
expect(find.text('1/2 agents'), findsOneWidget);
});
testWidgets('no workflows → no WORKFLOWS section', (tester) async {
await tester.pumpWidget(harness(f, const ActivityTabView(stats: ClaudeStats(), primaryStatus: null, config: null)));
await tester.pump();
expect(find.text('WORKFLOWS'), findsNothing);
});
}