From da82b48f9b2dcbdaaa45c006c894474935e0d38b Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 28 Jun 2026 01:13:03 +0200 Subject: [PATCH] test(claude): cover the per-repo account branches to clear the 95% gate Adds the missing-line coverage the epic's new code left uncovered: Account / DetectedAccount value equality, the set/unset no-workspace error branches, the login dialog's escape-to-close, and the settings controls' live-update / no-workspace / duplicate-add / re-login paths. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../claude/account_login_dialog_test.dart | 14 ++++++ .../builtin/claude/account_registry_test.dart | 16 +++++++ .../claude/account_settings_control_test.dart | 45 +++++++++++++++++++ test/daemon/claude_account_commands_test.dart | 10 +++++ 4 files changed, 85 insertions(+) diff --git a/test/builtin/claude/account_login_dialog_test.dart b/test/builtin/claude/account_login_dialog_test.dart index b8ad77a3..2b90dc50 100644 --- a/test/builtin/claude/account_login_dialog_test.dart +++ b/test/builtin/claude/account_login_dialog_test.dart @@ -6,6 +6,7 @@ library; import 'package:clide/builtin/claude/src/account_login_dialog.dart'; import 'package:clide/builtin/terminal/src/terminal_pane.dart'; import 'package:clide/clide.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -40,4 +41,17 @@ void main() { await tester.pump(); expect(closed, isTrue); }); + + testWidgets('escape closes the dialog', (tester) async { + var closed = false; + fixture.ipc.setConnected(true); + fixture.ipc.stub('pane.spawn', (args) async => IpcResponse.ok(id: 'r1', data: {'id': 'p1', 'pid': 1})); + fixture.ipc.stub('pane.close', (args) async => IpcResponse.ok(id: 'r2')); + + await tester.pumpWidget(harness(fixture, ClaudeLoginDialog(name: 'work', dir: '/home/u/.claude-work', onClose: () => closed = true))); + await pumpAsync(tester); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pump(); + expect(closed, isTrue); + }); } diff --git a/test/builtin/claude/account_registry_test.dart b/test/builtin/claude/account_registry_test.dart index 752ccbb5..65d80baa 100644 --- a/test/builtin/claude/account_registry_test.dart +++ b/test/builtin/claude/account_registry_test.dart @@ -138,6 +138,22 @@ void main() { }); }); + group('value types', () { + test('Account + DetectedAccount value equality, hashCode, toString', () { + expect(const Account(name: 'a', dir: '/d'), const Account(name: 'a', dir: '/d')); + expect(const Account(name: 'a', dir: '/d'), isNot(const Account(name: 'a', dir: '/e'))); + expect(const Account(name: 'a', dir: '/d'), isNot('a')); + expect(const Account(name: 'a', dir: '/d').hashCode, const Account(name: 'a', dir: '/d').hashCode); + expect(const Account(name: 'a', dir: '/d').toString(), contains('a')); + expect(const Account(name: 'a', dir: '/d').toJson(), {'name': 'a', 'dir': '/d'}); + + expect(const DetectedAccount(name: 'a', dir: '/d'), const DetectedAccount(name: 'a', dir: '/d')); + expect(const DetectedAccount(name: 'a', dir: '/d'), isNot(const DetectedAccount(name: 'b', dir: '/d'))); + expect(const DetectedAccount(name: 'a', dir: '/d'), isNot('a')); + expect(const DetectedAccount(name: 'a', dir: '/d').hashCode, const DetectedAccount(name: 'a', dir: '/d').hashCode); + }); + }); + group('sign-in probe (T-482)', () { test('true with a .credentials.json or an oauthAccount marker; false otherwise', () async { final home = await Directory.systemTemp.createTemp('clide-signin-'); diff --git a/test/builtin/claude/account_settings_control_test.dart b/test/builtin/claude/account_settings_control_test.dart index 0de00f45..d6df4ce8 100644 --- a/test/builtin/claude/account_settings_control_test.dart +++ b/test/builtin/claude/account_settings_control_test.dart @@ -216,4 +216,49 @@ void main() { tokens.buttonBackground, ], contains(accountAccent('work', tokens))); }); + + testWidgets('badge: renders nothing without a workspace root', (tester) async { + final reg = AccountRegistry(f.services.settings); + await tester.runAsync(() => reg.registerAccount('work', '/home/u/.claude-work')); + await pumpBadge(tester, null); + await tester.pump(); + expect(find.text('default'), findsNothing); + expect(find.text('work'), findsNothing); + }); + + testWidgets('registry list: re-login publishes login; adding a duplicate name is a no-op', (tester) async { + final reg = AccountRegistry(f.services.settings); + await tester.runAsync(() => reg.registerAccount('work', '/home/u/.claude-work')); + final published = >[]; + final sub = f.services.messages.subscribe(channel: accountActionChannel).listen((m) => published.add(m.data)); + addTearDown(sub.cancel); + + await pumpList(tester); + await tester.pump(); + await tester.tap(find.bySemanticsLabel('Sign in to work')); + await tester.pump(); + expect(published.single['action'], 'login'); + + // Add the same name again → no-op (still one account, no error). + await tester.enterText(find.byType(EditableText), 'work'); + await tester.tap(find.text('Add account')); + await tester.pump(); + expect(reg.accounts.where((a) => a.name == 'work'), hasLength(1)); + }); + + testWidgets('picker: live-updates when a binding changes from outside', (tester) async { + final reg = AccountRegistry(f.services.settings); + await tester.runAsync(() async { + await f.services.settings.setProjectDir(f.tempDir); + await reg.registerAccount('work', '/home/u/.claude-work'); + }); + await pump(tester); + await tester.pump(); + expect(find.textContaining('Default'), findsOneWidget); + + // A CLI-side bind notifies the shared store → the picker rebuilds. + await tester.runAsync(() => reg.bindWorkspace(f.tempDir.path, 'work')); + await tester.pump(); + expect(find.text('work'), findsOneWidget); + }); } diff --git a/test/daemon/claude_account_commands_test.dart b/test/daemon/claude_account_commands_test.dart index 269062e3..a23e4248 100644 --- a/test/daemon/claude_account_commands_test.dart +++ b/test/daemon/claude_account_commands_test.dart @@ -140,6 +140,16 @@ void main() { expect(published.single.data, {'action': 'login', 'name': 'work', 'dir': '/home/u/.claude-work'}); }); + test('set/unset with no workspace open error clearly', () async { + wire(cwd: null); + final set = await run(['set', 'work']); + expect(set.ok, isFalse); + expect(set.error?.message, contains('no workspace')); + final unset = await run(['unset']); + expect(unset.ok, isFalse); + expect(unset.error?.message, contains('no workspace')); + }); + test('unknown action → userError listing the verbs', () async { wire(); final r = await run(['frobnicate']);