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) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 01:13:03 +02:00
co-authored by Claude Opus 4.8
parent 53b93f092b
commit da82b48f9b
4 changed files with 85 additions and 0 deletions
@@ -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);
});
}
@@ -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-');
@@ -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 = <Map<String, Object?>>[];
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);
});
}
@@ -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']);