Files
clide/test/kernel/src/toolchain_test.dart
T
jpmschweitzerandClaude Opus 4.7 7ba0500f75
test / unit + widget + golden + a11y (push) Failing after 30s
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / integration_test (xvfb) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
test sweep: cover kernel toolchain + medium services (T-91)
Two test files chasing the bigger kernel residuals:

- test/kernel/src/toolchain_test.dart (10 tests): Toolchain defaults
  + missing list, applyResolved with full / partial paths,
  waitForResolution sync + async, Toolchain.resolvePaths against the
  current workspace + dugite detection + PATH fallback,
  resolveToolchainPaths top-level matches the static.
- test/kernel/src/services_bigger_test.dart (15 tests): DialogRouter
  show/dismiss/queue/notify, FileServices.pick* UnimplementedError
  trio + notifyDropped event, OsBridge openURL / reveal / fire,
  WindowControls setStyle idempotency + MissingPlugin-safe
  platform-channel methods + isMaximized success path,
  SchedulerTier intervals + SchedulerTick payload + start/dispose.

Coverage: kernel/src/toolchain.dart 37/95 -> 67/95 (71%);
dialog.dart 20/47 -> 27/47 (remaining is the DialogHost widget,
needs a real overlay tree); files.dart 1/16 -> 14/16 (88%);
os.dart 1/26 -> 19/26 (73%); window_controls.dart 2/25 -> 25/25
(100%); scheduler.dart 14/41 -> 18/41 (remaining is the isolate
ticker entry point, only fires after a real project-open event).

Total coverage 82.43% -> 83.57%; floor bumped to 83.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:00:23 +02:00

108 lines
3.8 KiB
Dart

/// Unit tests for `Toolchain` + `resolveToolchainPaths` in
/// `lib/kernel/src/toolchain.dart`.
library;
import 'dart:io';
import 'package:clide/kernel/src/toolchain.dart';
import 'package:test/test.dart';
void main() {
group('Toolchain', () {
test('default getters return the bare command name when unresolved', () {
final t = Toolchain();
expect(t.git, 'git');
expect(t.pql, 'pql');
expect(t.tmux, 'tmux');
expect(t.shell, '/bin/bash');
expect(t.resolved, isFalse);
expect(t.allOk, isFalse);
expect(t.missing, ['git', 'pql', 'tmux']);
});
test('applyResolved with full paths flips resolved + allOk + clears missing', () {
final t = Toolchain();
var calls = 0;
t.addListener(() => calls++);
t.applyResolved(const ResolvedPaths(
git: '/usr/bin/git',
pql: '/usr/bin/pql',
tmux: '/usr/bin/tmux',
shell: '/bin/bash',
gitEnv: {'GIT_EXEC_PATH': '/usr/lib/git-core'},
));
expect(t.resolved, isTrue);
expect(t.allOk, isTrue);
expect(t.missing, isEmpty);
expect(t.gitEnv?['GIT_EXEC_PATH'], '/usr/lib/git-core');
expect(t.git, '/usr/bin/git');
expect(calls, 1);
});
test('applyResolved with only some tools resolved reports the rest as missing', () {
final t = Toolchain();
t.applyResolved(const ResolvedPaths(pql: '/usr/bin/pql'));
expect(t.resolved, isTrue);
expect(t.allOk, isFalse);
expect(t.missing, ['git', 'tmux']);
});
test('waitForResolution completes immediately when already resolved', () async {
final t = Toolchain();
t.applyResolved(const ResolvedPaths());
await t.waitForResolution(); // shouldn't hang
});
test('waitForResolution awaits applyResolved when not yet resolved', () async {
final t = Toolchain();
final f = t.waitForResolution();
// Resolve on the next microtask.
Future.microtask(() => t.applyResolved(const ResolvedPaths()));
await f.timeout(const Duration(seconds: 1));
});
});
group('Toolchain.resolvePaths (static)', () {
test('returns a ResolvedPaths against the current workspace', () {
final paths = Toolchain.resolvePaths(workspaceRoot: Directory.current.path);
// Whatever was found, the result must be a ResolvedPaths.
expect(paths, isA<ResolvedPaths>());
// On this CI host pql is installed (per repo memory).
expect(paths.pql, isNotNull);
});
test('uses the dugite git when present in the workspace', () {
// The clide repo bundles dugite under native/dugite/bin/git.
final paths = Toolchain.resolvePaths(workspaceRoot: Directory.current.path);
final dugitePath = '${Directory.current.path}/native/dugite/bin/git';
if (File(dugitePath).existsSync()) {
expect(paths.git, dugitePath);
expect(paths.gitEnv?['GIT_EXEC_PATH'], isNotNull);
}
});
test('falls back to PATH git when no dugite is present', () {
final paths = Toolchain.resolvePaths(workspaceRoot: '/tmp/clide-no-dugite-${DateTime.now().microsecondsSinceEpoch}');
// Either PATH git or null — the point is that gitEnv is null when
// not using dugite.
if (paths.git != null) {
expect(paths.gitEnv, isNull);
}
});
});
group('resolveToolchainPaths (top-level, for isolates)', () {
test('matches Toolchain.resolvePaths shape', () {
final viaStatic = Toolchain.resolvePaths(workspaceRoot: Directory.current.path);
final viaTopLevel = resolveToolchainPaths(Directory.current.path);
// Both must agree on the pql binary (or both null if missing).
expect(viaTopLevel.pql, viaStatic.pql);
});
test('returns a ResolvedPaths for an arbitrary path', () {
final paths = resolveToolchainPaths('/tmp/clide-arbitrary');
expect(paths, isA<ResolvedPaths>());
});
});
}