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:
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user