The model-independent half of the ssh:// open scheme. WorkspaceRef is the value type for "where a workspace lives" — a local path or ssh://[user@]host[:port]/abs/path, with parse/uri round-tripping and a host:path display form. RecentProject carries host/port/user (back-compatible JSON: absent keys deserialize as local) so remote recents survive restarts and render with their host badge. The remaining T-332 scope — ProjectManager.current off bare Directory, open() branching, remote resolveProject — is gated on the execution layer (T-336), which is itself blocked on the T-330 footprint pick; the epic's blocker graph now encodes that gating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
334 lines
14 KiB
Dart
334 lines
14 KiB
Dart
/// Unit tests for `ProjectManager` and `RecentProject` in
|
|
/// `lib/kernel/src/project.dart`. Uses an injected `onValidateProject`
|
|
/// hook so the manager doesn't shell out to git in tests.
|
|
library;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:clide/kernel/kernel.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
Future<ProjectManager> _build({
|
|
Future<String?> Function(String path)? onValidateProject,
|
|
Future<void> Function(String path)? onProjectOpen,
|
|
Map<String, Object?> initialSettings = const {},
|
|
}) async {
|
|
final tmp = await Directory.systemTemp.createTemp('clide-project-test-');
|
|
final settings = SettingsStore(appDir: tmp);
|
|
await settings.load();
|
|
for (final entry in initialSettings.entries) {
|
|
await settings.set<Object>(entry.key, entry.value!);
|
|
}
|
|
final toolchain = Toolchain();
|
|
toolchain.applyResolved(const ResolvedPaths(git: '/usr/bin/git'));
|
|
final pm = ProjectManager(
|
|
log: Logger(minLevel: LogLevel.error),
|
|
events: DaemonBus(),
|
|
settings: settings,
|
|
toolchain: toolchain,
|
|
onValidateProject: onValidateProject,
|
|
onProjectOpen: onProjectOpen,
|
|
);
|
|
addTearDown(() async {
|
|
if (tmp.existsSync()) tmp.deleteSync(recursive: true);
|
|
});
|
|
return pm;
|
|
}
|
|
|
|
void main() {
|
|
group('RecentProject', () {
|
|
test(r'relativePath collapses $HOME prefix to ~', () {
|
|
final home = Platform.environment['HOME'] ?? '';
|
|
if (home.isEmpty) return;
|
|
final p = RecentProject(path: '$home/projects/clide', name: 'clide', lastOpened: DateTime.now());
|
|
expect(p.relativePath, startsWith('~/'));
|
|
});
|
|
|
|
test(r'relativePath returns the absolute path when not under $HOME', () {
|
|
final p = RecentProject(path: '/elsewhere/repo', name: 'repo', lastOpened: DateTime.now());
|
|
// If $HOME is /elsewhere... we'd collapse. Otherwise absolute is preserved.
|
|
if (!'/elsewhere/repo'.startsWith(Platform.environment['HOME'] ?? ' |