/// Tests for the native Claude input composer (T-138): Enter submits, /// Shift+Enter does not, blank input is ignored, and submitted text is /// PTY-encoded for `pane.write`. library; import 'package:clide/builtin/claude/src/claude_composer.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../../helpers/kernel_fixture.dart'; import '../../helpers/widget_harness.dart'; void main() { group('encodeClaudeInput', () { test('single-line input gets a trailing carriage return', () { expect(encodeClaudeInput('hello claude'), 'hello claude\r'); }); test('multi-line input is wrapped in bracketed-paste markers', () { expect( encodeClaudeInput('line one\nline two'), '\x1b[200~line one\nline two\x1b[201~\r', ); }); }); group('ClaudeComposer', () { late KernelFixture f; setUp(() async => f = await KernelFixture.create()); tearDown(() => f.dispose()); Future> pump(WidgetTester tester, {bool enabled = true}) async { final submitted = []; await tester.pumpWidget(harness( f, ClaudeComposer(enabled: enabled, onSubmit: submitted.add), )); return submitted; } testWidgets('Enter submits the text and clears the field', (tester) async { final submitted = await pump(tester); await tester.enterText(find.byType(EditableText), 'hey there'); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.enter); await tester.pump(); expect(submitted, ['hey there']); expect(tester.widget(find.byType(EditableText)).controller.text, isEmpty); }); testWidgets('Shift+Enter does not submit', (tester) async { final submitted = await pump(tester); await tester.enterText(find.byType(EditableText), 'draft'); await tester.pump(); await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); await tester.sendKeyEvent(LogicalKeyboardKey.enter); await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pump(); expect(submitted, isEmpty); }); testWidgets('blank input is ignored', (tester) async { final submitted = await pump(tester); await tester.enterText(find.byType(EditableText), ' '); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.enter); await tester.pump(); expect(submitted, isEmpty); }); testWidgets('disabled composer is read-only', (tester) async { await pump(tester, enabled: false); expect(tester.widget(find.byType(EditableText)).readOnly, isTrue); }); testWidgets('Ctrl+V routes through the paste resolver and inserts its @path', (tester) async { // WidgetsApp provides DefaultTextEditingShortcuts in the real app; // the bare harness doesn't, so wrap explicitly to map Ctrl+V -> // PasteTextIntent, which the composer's Actions override intercepts. await tester.pumpWidget(harness( f, DefaultTextEditingShortcuts( child: ClaudeComposer( onSubmit: (_) {}, pasteResolver: () async => '@/tmp/shot.png', ), ), )); tester.widget(find.byType(EditableText)).focusNode.requestFocus(); await tester.pump(); await tester.sendKeyDownEvent(LogicalKeyboardKey.control); await tester.sendKeyEvent(LogicalKeyboardKey.keyV); await tester.sendKeyUpEvent(LogicalKeyboardKey.control); await tester.pumpAndSettle(); expect( tester.widget(find.byType(EditableText)).controller.text, '@/tmp/shot.png', ); }); }); }