add WorkspaceRef + remote identity on RecentProject (T-332)
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>
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,78 @@
|
||||
/// Unit tests for `WorkspaceRef` (T-332) — local/remote workspace
|
||||
/// identity and the `ssh://[user@]host[:port]/abs/path` open scheme.
|
||||
library;
|
||||
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('WorkspaceRef.parse — local', () {
|
||||
test('a bare path is a local ref', () {
|
||||
final ref = WorkspaceRef.parse('/var/repo');
|
||||
expect(ref, const WorkspaceRef.local('/var/repo'));
|
||||
expect(ref!.isRemote, isFalse);
|
||||
expect(ref.uri, '/var/repo');
|
||||
expect(ref.display, '/var/repo');
|
||||
});
|
||||
|
||||
test('a relative path stays a local ref verbatim', () {
|
||||
expect(WorkspaceRef.parse('repo'), const WorkspaceRef.local('repo'));
|
||||
});
|
||||
});
|
||||
|
||||
group('WorkspaceRef.parse — ssh://', () {
|
||||
test('host + path', () {
|
||||
final ref = WorkspaceRef.parse('ssh://buildbox/srv/repo');
|
||||
expect(ref, WorkspaceRef.remote(host: 'buildbox', path: '/srv/repo'));
|
||||
expect(ref!.isRemote, isTrue);
|
||||
expect(ref.port, isNull);
|
||||
expect(ref.user, isNull);
|
||||
});
|
||||
|
||||
test('user@host:port + path', () {
|
||||
final ref = WorkspaceRef.parse('ssh://jeroen@buildbox:2222/srv/repo');
|
||||
expect(ref!.user, 'jeroen');
|
||||
expect(ref.host, 'buildbox');
|
||||
expect(ref.port, 2222);
|
||||
expect(ref.path, '/srv/repo');
|
||||
});
|
||||
|
||||
test('uri round-trips through parse', () {
|
||||
const refs = [
|
||||
WorkspaceRef.local('/var/repo'),
|
||||
WorkspaceRef.remote(host: 'buildbox', path: '/srv/repo'),
|
||||
WorkspaceRef.remote(host: 'buildbox', path: '/srv/repo', port: 2222, user: 'jeroen'),
|
||||
];
|
||||
for (final ref in refs) {
|
||||
expect(WorkspaceRef.parse(ref.uri), ref, reason: ref.uri);
|
||||
}
|
||||
});
|
||||
|
||||
test('display is host:path', () {
|
||||
expect(WorkspaceRef.remote(host: 'buildbox', path: '/srv/repo').display, 'buildbox:/srv/repo');
|
||||
});
|
||||
|
||||
test('missing host or missing path is rejected', () {
|
||||
expect(WorkspaceRef.parse('ssh:///srv/repo'), isNull);
|
||||
expect(WorkspaceRef.parse('ssh://buildbox'), isNull);
|
||||
expect(WorkspaceRef.parse('ssh://buildbox/'), isNull);
|
||||
});
|
||||
|
||||
test('garbage after the scheme is rejected, not crashed on', () {
|
||||
expect(WorkspaceRef.parse('ssh://[::bad'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('WorkspaceRef equality', () {
|
||||
test('value equality + hashCode', () {
|
||||
expect(WorkspaceRef.remote(host: 'h', path: '/p'), WorkspaceRef.remote(host: 'h', path: '/p'));
|
||||
expect(WorkspaceRef.remote(host: 'h', path: '/p').hashCode, WorkspaceRef.remote(host: 'h', path: '/p').hashCode);
|
||||
expect(WorkspaceRef.remote(host: 'h', path: '/p'), isNot(const WorkspaceRef.local('/p')));
|
||||
expect(WorkspaceRef.remote(host: 'h', path: '/p', port: 22), isNot(WorkspaceRef.remote(host: 'h', path: '/p')));
|
||||
});
|
||||
|
||||
test('toString carries the uri form', () {
|
||||
expect(WorkspaceRef.remote(host: 'h', path: '/p').toString(), contains('ssh://h/p'));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user