From 0d61e05e98f6f020722ee4d43f024c1da348d2a9 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Thu, 14 May 2026 12:41:55 -0500 Subject: feat: add Safari extension for sourcehut deep links --- HutchTests/AppStateTests.swift | 8 +++++ HutchTests/DeepLinkTests.swift | 32 ++++++++++++++++- HutchTests/SourceHutWebDeepLinkMapperTests.swift | 44 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 HutchTests/SourceHutWebDeepLinkMapperTests.swift (limited to 'HutchTests') diff --git a/HutchTests/AppStateTests.swift b/HutchTests/AppStateTests.swift index 88a8272..b7e1755 100644 --- a/HutchTests/AppStateTests.swift +++ b/HutchTests/AppStateTests.swift @@ -4,6 +4,14 @@ import Testing struct AppStateTests { + @Test + @MainActor + func srhtUsernameNormalizesWebPathOwner() { + #expect(AppState.srhtUsername(from: "~vicho") == "vicho") + #expect(AppState.srhtUsername(from: " vicho ") == "vicho") + #expect(AppState.srhtUsername(from: " ~vicho ") == "vicho") + } + @Test @MainActor func presentRepositoryDeepLinkErrorSetsUserFacingMessage() { diff --git a/HutchTests/DeepLinkTests.swift b/HutchTests/DeepLinkTests.swift index c57cfce..500c4cd 100644 --- a/HutchTests/DeepLinkTests.swift +++ b/HutchTests/DeepLinkTests.swift @@ -19,7 +19,19 @@ struct DeepLinkTests { @Test func parsesRepositoryLink() { let link = DeepLink(url: HutchDeepLinkURL.repositoryGit) - #expect(link == .repository(owner: "~user", repo: "repo")) + #expect(link == .repository(service: .git, owner: "~user", repo: "repo")) + } + + @Test + func parsesHgRepositoryLink() { + let link = DeepLink(url: HutchDeepLinkURL.repositoryHg) + #expect(link == .repository(service: .hg, owner: "~user", repo: "repo")) + } + + @Test + func parsesServiceOwnerRootAsUserProfile() { + let link = DeepLink(url: HutchDeepLinkURL.gitOwnerRoot) + #expect(link == .userProfile(owner: "~owner")) } @Test @@ -34,6 +46,18 @@ struct DeepLinkTests { #expect(link == .build(jobId: 12345)) } + @Test + func parsesBuildJobLinkWithOwnerPath() { + let link = DeepLink(url: HutchDeepLinkURL.buildJobWithOwner) + #expect(link == .build(jobId: 12345)) + } + + @Test + func parsesMailingListLink() { + let link = DeepLink(url: HutchDeepLinkURL.mailingList) + #expect(link == .mailingList(owner: "~owner", list: "list")) + } + @Test func parsesBuildsTabLink() { let link = DeepLink(url: HutchDeepLinkURL.builds) @@ -64,6 +88,12 @@ struct DeepLinkTests { #expect(link == .lookup) } + @Test + func parsesUserProfileLink() { + let link = DeepLink(url: HutchDeepLinkURL.userProfile) + #expect(link == .userProfile(owner: "~owner")) + } + @Test func rejectsNonHutchScheme() { let link = DeepLink(url: URL(string: "https://example.com/home")!) diff --git a/HutchTests/SourceHutWebDeepLinkMapperTests.swift b/HutchTests/SourceHutWebDeepLinkMapperTests.swift new file mode 100644 index 0000000..0fa3aaa --- /dev/null +++ b/HutchTests/SourceHutWebDeepLinkMapperTests.swift @@ -0,0 +1,44 @@ +import Foundation +import Testing +@testable import Hutch + +struct SourceHutWebDeepLinkMapperTests { + @Test + func mapsRepositoryURLs() { + #expect(mapped("https://git.sr.ht/~ccleberg/Hutch") == "hutch://git/~ccleberg/Hutch") + #expect(mapped("https://hg.sr.ht/~user/repo") == "hutch://hg/~user/repo") + } + + @Test + func mapsTrackerBuildAndListURLs() { + #expect(mapped("https://todo.sr.ht/~ccleberg/hutch/123") == "hutch://todo/~ccleberg/hutch/123") + #expect(mapped("https://builds.sr.ht/~ccleberg/job/12345") == "hutch://builds/~ccleberg/job/12345") + #expect(mapped("https://lists.sr.ht/~ccleberg/hutch-announce") == "hutch://lists/~ccleberg/hutch-announce") + } + + @Test + func mapsProfileHostsToLookup() { + #expect(mapped("https://sr.ht/~ccleberg") == "hutch://lookup/~ccleberg") + #expect(mapped("https://meta.sr.ht/~ccleberg") == "hutch://lookup/~ccleberg") + #expect(mapped("https://git.sr.ht/~ccleberg/") == "hutch://lookup/~ccleberg") + #expect(mapped("https://hg.sr.ht/~user") == "hutch://lookup/~user") + #expect(mapped("https://todo.sr.ht/~user") == "hutch://lookup/~user") + #expect(mapped("https://lists.sr.ht/~user") == "hutch://lookup/~user") + #expect(mapped("https://sr.ht/projects/~user") == "hutch://lookup/~user") + } + + @Test + func preservesQueryAndFragment() { + #expect(mapped("https://git.sr.ht/~ccleberg/Hutch/tree/main/item/README.md?plain=1#L10") == "hutch://git/~ccleberg/Hutch/tree/main/item/README.md?plain=1#L10") + } + + @Test + func rejectsUnsupportedHostsAndSchemes() { + #expect(SourceHutWebDeepLinkMapper.deepLink(for: URL(string: "https://chat.sr.ht")!) == nil) + #expect(SourceHutWebDeepLinkMapper.deepLink(for: URL(string: "http://git.sr.ht/~ccleberg/Hutch")!) == nil) + } + + private func mapped(_ string: String) -> String? { + SourceHutWebDeepLinkMapper.deepLink(for: URL(string: string)!)?.absoluteString + } +} -- cgit v1.2.3