Remove useless tests
This commit is contained in:
1 parent
e54f5c81c9
commit
1d6792d66a
24 files changed
+98
-1316
No files matched your search
@@ -42,7 +42,10 @@ and bundle-derived Keychain access group, with target-specific signing.
|
||||
Shared localized resources are copied into each app bundle. Platform icons and
|
||||
metadata remain app-owned. Framework, macOS, and mobile tests stay separate;
|
||||
architecture tests fail closed when an expected production directory is absent or
|
||||
empty.
|
||||
empty. Source-based checks are limited to module dependencies, native-list import
|
||||
allowlists, AgentKit type ownership, and SwiftData model ownership. They do not pin
|
||||
UI copy, source layout, call-site spellings, retired paths, or shared-component wiring;
|
||||
those concerns rely on behavioral tests or explicit visual review.
|
||||
|
||||
## State, navigation, and ownership
|
||||
|
||||
@@ -623,7 +626,6 @@ Presentation owns the shared command-history subtitle, Vault sync status label,
|
||||
and the exported SFTP drag type. App targets retain native card/list composition,
|
||||
selection, menus, and clipboard behavior.
|
||||
|
||||
|
||||
### Assistant transcript presentation parity (2026-09-12)
|
||||
|
||||
TermindPresentation owns tool summaries, batch status semantics, bounded result previews, timestamp formatting,
|
||||
|
||||
@@ -118,3 +118,13 @@ Verification and remaining limitations (2026-09-16):
|
||||
- The full TFI action did not build or execute tests: the supplied simulator destination UUID was unavailable. This is not a passing iOS verification. A fresh device listing found an available booted iPhone 17 Pro, but the owner requested an immediate commit before rerunning either platform.
|
||||
- Strict Swift formatting across all production/test targets, `Scripts/check-zh-localization.sh`, the removed desktop-helper reference scan, and `git diff --check` passed. Xcode successfully resolved modules that standalone SourceKit could not find. Existing generic SwiftLint file/type/function-size and complexity findings remain outside this independent refactor; no inline suppressions were added. Dependencies and localization catalogs are unchanged.
|
||||
- Submitted at the owner's explicit request with these verification limitations retained rather than deleting unrelated behavioral tests to obtain a green result.
|
||||
|
||||
## Remove Low-Value UI and Source Assertions (2026-09-16)
|
||||
|
||||
- **Implemented; full verification in progress.** Evidence: `rg -n '#filePath|sourceRoot|String\(contentsOf|source\.contains' FrameworkTests TFMTests TFITests -g '*.swift'` found 35 architecture tests plus eight source snapshots in Color System, Menu Symbol, SSH Import, Keychain Order, Commerce Configuration, and Mobile Navigation Parity. At the owner's request, removed implementation-spelling/layout snapshots, enum and palette echoes, fixed-canvas render checks, disconnected standard-library sorting tests, and orphaned helpers; trimmed literal copy from mixed behavioral tests. Removed 52 test declarations and a net 1,230 Swift lines against an estimated 1,000–1,200-line reduction. Source assertions dropped from 43 tests to nine narrow module/dependency and ownership guards. The ListViewKit import allowlist now permits removing an importer without requiring a snapshot update, while still rejecting new unapproved importers. Retained language/catalog validation, protocol/data contracts, behavioral lifecycle/rendering coverage, and opt-in screenshot capture. Grouped host-list coverage now checks row placement using fixture data rather than localized headings. No production, dependency, or localization catalog changed; existing architecture-document edits were preserved.
|
||||
|
||||
Verification in progress:
|
||||
|
||||
- Initial pinned Xcode checks passed all 18 selected macOS tests and five selected iOS tests, compiling both test targets. Results: `/tmp/termind-test-cleanup-initial-macos.xcresult` and `/tmp/termind-test-cleanup-initial-ios.xcresult`.
|
||||
- Strict Swift formatting across production/test targets, `Scripts/check-zh-localization.sh`, the remaining-source-assertion scan, and `git diff --check` passed. Active standalone SourceKit checks could not resolve the Xcode test module graph and were partly inconclusive; Xcode compilation is the authoritative check. Pre-existing generic SwiftLint size, tuple, pixel-coordinate naming, and force-try findings were left outside this deletion; no inline suppressions were added.
|
||||
- Full pinned macOS and iOS test actions are running sequentially. No unrelated behavioral test is being removed to hide a failing result.
|
||||
@@ -1,4 +1,3 @@
|
||||
import AgentKit
|
||||
import SwiftUI
|
||||
import TermindDomain
|
||||
import Testing
|
||||
@@ -8,59 +7,6 @@ import UIKit
|
||||
|
||||
@Suite(.serialized) @MainActor
|
||||
struct MobileAISettingsToolbarTests {
|
||||
@Test func managementPagesExposeTheirNativeToolbarActions() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
|
||||
try await withNavigation(MobileModelsSettingsView(ai: app.ai)) { names in
|
||||
#expect(names.contains("Add Provider"))
|
||||
}
|
||||
try await withNavigation(MobileSkillSettings(skills: app.skills)) { names in
|
||||
#expect(names.contains("Add Skill"))
|
||||
#expect(names.contains("More"))
|
||||
#expect(!names.contains("Import Skill…"))
|
||||
}
|
||||
try await withNavigation(MobileMCPSettings(ai: app.ai, pro: app.pro)) { names in
|
||||
#expect(names.contains("Add Server"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func mcpServerUsesPushedEditorToolbar() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let server = MCPServer(name: "Test", url: "https://example.invalid/mcp")
|
||||
|
||||
try await withNavigation(MobileMCPEditor(ai: app.ai, pro: app.pro, server: server)) { names in
|
||||
#expect(names.contains("More"))
|
||||
#expect(names.contains("Save"))
|
||||
#expect(!names.contains("Cancel"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func workspacePagesExposeTheirExpectedToolbarActions() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let host = TermindDomain.Host(name: "Test", host: "example.invalid")
|
||||
app.library.upsert(host)
|
||||
|
||||
for kind in [ConnectionHistoryEntry.Kind.ssh, .sftp] {
|
||||
let workspace = MobileWorkspace(
|
||||
descriptor: MobileWorkspaceDescriptor(hostID: host.id, title: host.name, kind: kind),
|
||||
hosts: app.library)
|
||||
try await withNavigation(
|
||||
MobileWorkspaceView(
|
||||
library: app.library, pro: app.pro, commandGenerator: app.commandGenerator,
|
||||
workspace: workspace, workspaces: [workspace],
|
||||
selectWorkspace: { _ in }, renameWorkspace: { _ in },
|
||||
openSameDirectory: { _ in }, closeWorkspace: { _ in })
|
||||
) { names in
|
||||
#expect(names.contains("More"))
|
||||
#expect(!names.contains("History"))
|
||||
#expect(!names.contains("Transfers"))
|
||||
#expect(!names.contains("Close"))
|
||||
#expect(!names.contains("Previous Location"))
|
||||
#expect(!names.contains("Next Location"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test func workspaceDetailHidesTheRootTabBar() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let host = TermindDomain.Host(name: "Test", host: "example.invalid")
|
||||
@@ -82,16 +28,6 @@ struct MobileAISettingsToolbarTests {
|
||||
}
|
||||
}
|
||||
|
||||
private func withNavigation<Content: View>(
|
||||
_ content: Content,
|
||||
check: (Set<String>) throws -> Void
|
||||
) async throws {
|
||||
try await withHostedView(NavigationStack { content }) { controller in
|
||||
let navigation = try #require(descendant(of: UINavigationController.self, in: controller))
|
||||
try check(Set(toolbarNames(in: navigation.topViewController?.navigationItem)))
|
||||
}
|
||||
}
|
||||
|
||||
private func withHostedView<Content: View>(
|
||||
_ content: Content,
|
||||
check: (UIViewController) async throws -> Void
|
||||
@@ -112,18 +48,6 @@ struct MobileAISettingsToolbarTests {
|
||||
try await check(controller)
|
||||
}
|
||||
|
||||
private func toolbarNames(in item: UINavigationItem?) -> [String] {
|
||||
guard let item else { return [] }
|
||||
let buttons =
|
||||
item.leadingItemGroups.flatMap(\.barButtonItems)
|
||||
+ item.trailingItemGroups.flatMap(\.barButtonItems)
|
||||
+ (item.leftBarButtonItems ?? [])
|
||||
+ (item.rightBarButtonItems ?? [])
|
||||
return buttons.compactMap { button in
|
||||
button.accessibilityLabel ?? button.title ?? button.customView?.accessibilityLabel
|
||||
}
|
||||
}
|
||||
|
||||
private func descendant<T: UIViewController>(of type: T.Type, in controller: UIViewController) -> T? {
|
||||
var pending = [controller]
|
||||
var visited = Set<ObjectIdentifier>()
|
||||
|
||||
@@ -23,13 +23,6 @@ struct MobileAssistantTranscriptTests {
|
||||
#expect(long <= 320)
|
||||
}
|
||||
|
||||
@Test func activityUsesTheAvailableWidthToKeepItsTitleLeading() {
|
||||
let controller = UIHostingController(rootView: MobileAssistantActivity())
|
||||
let size = controller.sizeThatFits(in: CGSize(width: 320, height: 1_000))
|
||||
|
||||
#expect(size.width == 320)
|
||||
}
|
||||
|
||||
@Test func imageFlowKeepsEveryUserRowTrailingAndAssistantRowsLeading() {
|
||||
let tile = CGSize(width: 104, height: 104)
|
||||
let sizes = Array(repeating: tile, count: 5)
|
||||
|
||||
@@ -9,23 +9,6 @@ import UIKit
|
||||
/// Opt-in native screenshots cover the connection states without opening a transport.
|
||||
@Suite(.serialized) @MainActor
|
||||
struct MobileConnectionJourneyRenderingTests {
|
||||
@Test func failedJourneyWithoutEndpointsRenders() throws {
|
||||
let journey = ConnectionJourney()
|
||||
journey.fail("Network unavailable")
|
||||
let view = MobileConnectionJourneyView(
|
||||
journey: journey, library: HostLibraryModel(), authenticationPrompt: nil, hostKey: nil,
|
||||
reconnectState: nil, submit: { _, _, _, _, _, _ in }, accept: { _ in }, retry: {},
|
||||
retryReconnectNow: {}, stopReconnecting: {}, cancel: {}
|
||||
)
|
||||
.frame(width: 390, height: 844)
|
||||
let renderer = ImageRenderer(content: view)
|
||||
renderer.scale = 1
|
||||
|
||||
let image = try #require(renderer.uiImage)
|
||||
|
||||
#expect(image.size == CGSize(width: 390, height: 844))
|
||||
}
|
||||
|
||||
@Test func renderConnectionJourneyStates() async throws {
|
||||
guard ProcessInfo.processInfo.environment["TERMIND_JOURNEY_SCREENSHOTS"] == "1" else { return }
|
||||
let host = TermindDomain.Host(name: "Production Gateway", host: "gateway.example.invalid")
|
||||
|
||||
@@ -82,34 +82,7 @@ struct MobileConnectionPickerTests {
|
||||
#expect(Set(rows.map { "\($0.entry.hostID.uuidString):\($0.entry.kind.rawValue)" }).count == rows.count)
|
||||
}
|
||||
|
||||
@Test func sharedHostPickerUsesConfiguredTitleWithoutHostsHeaderOrProtocolControl() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
app.library.upsert(Host(name: "Production", host: "production.example.invalid"))
|
||||
|
||||
try await withNavigation(
|
||||
MobileHostPicker(
|
||||
library: app.library,
|
||||
title: "Add Server",
|
||||
emptyDescription: "Add a saved Linux server to start monitoring.",
|
||||
cancel: {},
|
||||
select: { _ in }),
|
||||
language: "en"
|
||||
) { navigation in
|
||||
#expect(navigation.topViewController?.navigationItem.title == "Add Server")
|
||||
#expect(descendant(of: UISegmentedControl.self, in: navigation) == nil)
|
||||
#expect(!labels(in: navigation.view).contains("Hosts"))
|
||||
}
|
||||
|
||||
try await withNavigation(
|
||||
MobileConnectionPicker(library: app.library, connect: { _, _ in }, cancel: {}),
|
||||
language: "en"
|
||||
) { navigation in
|
||||
#expect(descendant(of: UISegmentedControl.self, in: navigation) != nil)
|
||||
#expect(!labels(in: navigation.view).contains("Hosts"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func snippetHostPickerUsesContextualTitlesAndCheckmarkConfirmation() async throws {
|
||||
@Test func snippetHostPickerGatesConfirmationOnRequiredSelection() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let host = Host(name: "Production", host: "production.example.invalid")
|
||||
app.library.upsert(host)
|
||||
@@ -120,7 +93,6 @@ struct MobileConnectionPickerTests {
|
||||
requiresSelection: true, save: { _ in }),
|
||||
language: "en"
|
||||
) { navigation in
|
||||
#expect(navigation.topViewController?.navigationItem.title == "Execute on Hosts")
|
||||
let execute = try #require(toolbarButton(named: "Execute", in: navigation))
|
||||
#expect(!execute.isEnabled)
|
||||
}
|
||||
@@ -131,7 +103,6 @@ struct MobileConnectionPickerTests {
|
||||
requiresSelection: false, save: { _ in }),
|
||||
language: "en"
|
||||
) { navigation in
|
||||
#expect(navigation.topViewController?.navigationItem.title == "Add Hosts")
|
||||
let done = try #require(toolbarButton(named: "Done", in: navigation))
|
||||
#expect(done.isEnabled)
|
||||
}
|
||||
@@ -164,18 +135,6 @@ struct MobileConnectionPickerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func workspaceToolbarKeepsMoreAndConnectAvailableWithoutWorkspaces() async throws {
|
||||
try await withNavigation(
|
||||
ToolbarHost(hasWorkspaces: false, closeAll: {}, connect: {}),
|
||||
language: "en"
|
||||
) { navigation in
|
||||
let more = try #require(toolbarButton(named: "More", in: navigation))
|
||||
#expect(more.isEnabled)
|
||||
let connect = try #require(toolbarButton(named: "Connect", in: navigation))
|
||||
#expect(connect.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private func withNavigation<Content: View>(
|
||||
_ content: Content,
|
||||
language: String,
|
||||
@@ -245,24 +204,6 @@ struct MobileConnectionPickerTests {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func descendant<T: UIView>(of type: T.Type, in viewController: UIViewController) -> T? {
|
||||
descendant(of: type, in: viewController.view)
|
||||
}
|
||||
|
||||
private func descendant<T: UIView>(of type: T.Type, in view: UIView) -> T? {
|
||||
if let result = view as? T { return result }
|
||||
for child in view.subviews {
|
||||
if let result = descendant(of: type, in: child) { return result }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func labels(in view: UIView) -> [String] {
|
||||
var result = (view as? UILabel).flatMap(\.text).map { [$0] } ?? []
|
||||
for child in view.subviews { result.append(contentsOf: labels(in: child)) }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private struct ToolbarHost: View {
|
||||
|
||||
@@ -8,19 +8,11 @@ import UIKit
|
||||
@Suite(.serialized) @MainActor
|
||||
struct MobileEditorToolbarTests {
|
||||
@Test(arguments: ["en", "zh-Hans", "zh-Hant"])
|
||||
func iconButtonsKeepTheirNamesAndSaveLifecycle(language: String) async throws {
|
||||
func saveDisablesActionsUntilCompletionAndThenDismisses(language: String) async throws {
|
||||
let state = EditorState()
|
||||
try await withEditor(state, language: language) { _, navigation in
|
||||
let cancel = try #require(cancelButton(in: navigation))
|
||||
let save = try #require(saveButton(in: navigation))
|
||||
#expect(accessibleName(cancel) == (language == "en" ? "Cancel" : "取消"))
|
||||
let expectedSave =
|
||||
switch language {
|
||||
case "zh-Hans": "保存"
|
||||
case "zh-Hant": "儲存"
|
||||
default: "Save"
|
||||
}
|
||||
#expect(accessibleName(save) == expectedSave)
|
||||
#expect(cancel.isEnabled)
|
||||
#expect(!save.isEnabled)
|
||||
state.canSave = true
|
||||
@@ -51,8 +43,7 @@ struct MobileEditorToolbarTests {
|
||||
#expect(state.isPresented)
|
||||
#expect(state.title == "Example")
|
||||
#expect(saveButton(in: navigation)?.isEnabled == true)
|
||||
let alert = try #require(descendant(of: UIAlertController.self, in: controller))
|
||||
#expect(alert.title == "Unable to Save")
|
||||
_ = try #require(descendant(of: UIAlertController.self, in: controller))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,6 @@ import Testing
|
||||
|
||||
@Suite @MainActor
|
||||
struct MobileNavigationParityTests {
|
||||
@Test func speechAudioTapDoesNotInheritMainActorIsolation() throws {
|
||||
let root = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
let source = try String(
|
||||
contentsOf: root.appending(path: "TFI/Platform/IOSSpeechAudioInput.swift"), encoding: .utf8)
|
||||
|
||||
#expect(source.contains("nonisolated static func makeTapHandler"))
|
||||
#expect(source.contains("block: Self.makeTapHandler"))
|
||||
}
|
||||
|
||||
@Test func vaultReplacesWelcomeAndMetricsRemainsBelowItOnIPad() {
|
||||
let features = MobileFeatureRegistry.standard()
|
||||
#expect(features.registrations(on: .phoneTab).map(\.id) == [.vault, .workspaces, .assistant, .settings])
|
||||
@@ -27,10 +16,6 @@ struct MobileNavigationParityTests {
|
||||
)
|
||||
}
|
||||
|
||||
@Test func aiSettingsUseThreeNativeManagementPages() {
|
||||
#expect(MobileAISettingsDestination.allCases == [.models, .skills, .mcp])
|
||||
}
|
||||
|
||||
@Test func metricsUsesAvailableWidthRatherThanDeviceOrientation() {
|
||||
#expect(!MobileMetricsDetailPage.showsBothColumns(width: 430))
|
||||
#expect(!MobileMetricsDetailPage.showsBothColumns(width: 680))
|
||||
|
||||
@@ -53,34 +53,6 @@ struct MobileTerminalListTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func nativeHistoryRemeasuresForAccessibilityDynamicType() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let host = TermindDomain.Host(name: "Test", host: "example.invalid")
|
||||
app.library.upsert(host)
|
||||
app.commandHistory.record("echo a command that needs two lines in the compact sheet", for: host.id)
|
||||
let workspace = MobileWorkspace(
|
||||
descriptor: MobileWorkspaceDescriptor(hostID: host.id, title: host.name, kind: .ssh),
|
||||
hosts: app.library,
|
||||
commandHistory: app.commandHistory)
|
||||
|
||||
try await withHostedView(
|
||||
MobileCommandHistoryView(workspace: workspace)
|
||||
.environment(\.dynamicTypeSize, .accessibility3)
|
||||
) { root in
|
||||
let cell = try #require(nativeVisibleCells(in: root.view).first)
|
||||
#expect(cell.bounds.height > 100)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func terminalRiskIndicatorsShareTheCompactPalette() {
|
||||
#expect(MobileTerminalRiskIndicator.color(for: .safe) == .systemGreen)
|
||||
#expect(MobileTerminalRiskIndicator.color(for: .caution) == .systemOrange)
|
||||
#expect(MobileTerminalRiskIndicator.color(for: .destructive) == .systemRed)
|
||||
#expect(MobileTerminalRiskIndicator.accessibilityIdentifier(for: .safe) == "TerminalRisk.safe")
|
||||
#expect(MobileTerminalRiskIndicator.accessibilityIdentifier(for: .caution) == "TerminalRisk.caution")
|
||||
#expect(MobileTerminalRiskIndicator.accessibilityIdentifier(for: .destructive) == "TerminalRisk.destructive")
|
||||
}
|
||||
|
||||
private func withHostedView<Content: View>(
|
||||
_ content: Content, check: (UIViewController) async throws -> Void
|
||||
) async throws {
|
||||
|
||||
@@ -761,11 +761,10 @@ struct AgentRuntimeTests {
|
||||
event: { event in Task { await events.append(event) } }
|
||||
)
|
||||
|
||||
guard case .deny(let reason) = await broker.authorize(request, mode: .approveForMe) else {
|
||||
guard case .deny = await broker.authorize(request, mode: .approveForMe) else {
|
||||
Issue.record("Expected timeout denial")
|
||||
return
|
||||
}
|
||||
#expect(reason.contains("timed out"))
|
||||
try? await Task.sleep(for: .milliseconds(10))
|
||||
#expect(
|
||||
await events.values.contains {
|
||||
|
||||
@@ -191,17 +191,11 @@ struct AgentToolPresentationTests {
|
||||
Issue.record("Expected a grouped host listing")
|
||||
return
|
||||
}
|
||||
#expect(listing.summary == "3 hosts · 1 connected · 1 temporary")
|
||||
#expect(
|
||||
listing.sections.map(\.title) == [
|
||||
"Production · 1 host", "Production / Edge · 0 hosts",
|
||||
"Ungrouped · 1 host", "Temporary · 1 host",
|
||||
listing.sections.map { $0.rows.map(\.title) } == [
|
||||
["API Server"], [], ["Loose Box"], ["Current Terminal"],
|
||||
])
|
||||
#expect(listing.sections[0].rows[0].title == "API Server")
|
||||
#expect(listing.sections[0].rows[0].subtitle == "deploy@api.example.com · Connected")
|
||||
#expect(listing.sections[0].rows[0].badges == ["prod", "nginx"])
|
||||
#expect(listing.sections[2].rows[0].subtitle == "root@10.0.0.2 · Failed")
|
||||
#expect(listing.sections[3].rows[0].subtitle == "root@10.0.0.8 · Reconnecting")
|
||||
let rendered = String(describing: detail)
|
||||
#expect(!rendered.contains(productionID))
|
||||
#expect(!rendered.contains("aaaaaaaa-aaaa"))
|
||||
@@ -594,88 +588,6 @@ struct AgentToolPresentationTests {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
func sshExecBatchCountsCommandsAcrossLifecycleStatesAndLocales() {
|
||||
let presentation = AgentToolDescriptor.Presentation(
|
||||
symbol: "terminal",
|
||||
activity: .semanticArgument(key: "command", fallback: .sshCommand),
|
||||
actionKind: .run
|
||||
)
|
||||
|
||||
func cards(_ state: AgentToolCard.State) -> [AgentToolCard] {
|
||||
["uname -a", "uptime"].map { command in
|
||||
var card = makeCard(
|
||||
name: "ssh_exec", target: .host,
|
||||
arguments: ["command": .string(command)], presentation: presentation
|
||||
)
|
||||
card.state = state
|
||||
return card
|
||||
}
|
||||
}
|
||||
|
||||
let english = Locale(identifier: "en")
|
||||
#expect(AgentToolPresentation.batchSummary(for: cards(.running), locale: english) == "Running 2 commands")
|
||||
#expect(AgentToolPresentation.batchSummary(for: cards(.completed), locale: english) == "Ran 2 commands")
|
||||
#expect(AgentToolPresentation.batchSummary(for: cards(.failed), locale: english) == "Failed to run 2 commands")
|
||||
#expect(
|
||||
AgentToolPresentation.batchSummary(
|
||||
for: cards(.completed), locale: Locale(identifier: "zh-Hans")
|
||||
) == "已运行2条命令"
|
||||
)
|
||||
#expect(
|
||||
AgentToolPresentation.batchSummary(
|
||||
for: cards(.completed), locale: Locale(identifier: "zh-Hant")
|
||||
) == "已執行2條指令"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
func manageTasksBatchCountsWholeListUpdates() {
|
||||
let presentation = AgentToolDescriptor.Presentation(
|
||||
symbol: "checklist", activity: .semanticLabel(.taskList),
|
||||
actionKind: .update
|
||||
)
|
||||
let cards = (0..<2).map { _ in
|
||||
var card = makeCard(name: "manage_tasks", target: .user, presentation: presentation)
|
||||
card.state = .completed
|
||||
return card
|
||||
}
|
||||
|
||||
#expect(
|
||||
AgentToolPresentation.batchSummary(for: cards, locale: Locale(identifier: "en"))
|
||||
== "Updated the task list 2 times"
|
||||
)
|
||||
#expect(
|
||||
AgentToolPresentation.batchSummary(
|
||||
for: cards, locale: Locale(identifier: "zh-Hans")
|
||||
) == "已更新任务列表2次"
|
||||
)
|
||||
#expect(
|
||||
AgentToolPresentation.batchSummary(
|
||||
for: cards, locale: Locale(identifier: "zh-Hant")
|
||||
) == "已更新任務清單2次"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
func unknownSemanticBatchKeepsGenericToolCallFallback() {
|
||||
let presentation = AgentToolDescriptor.Presentation(
|
||||
symbol: "wrench.and.screwdriver",
|
||||
activity: .semanticLabel(.init("database")),
|
||||
actionKind: .inspect
|
||||
)
|
||||
let cards = (0..<2).map { _ in
|
||||
var card = makeCard(name: "custom_inspect", target: .mcp, presentation: presentation)
|
||||
card.state = .completed
|
||||
return card
|
||||
}
|
||||
|
||||
#expect(
|
||||
AgentToolPresentation.batchSummary(for: cards, locale: Locale(identifier: "en"))
|
||||
== "Inspected 2 tool calls"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
func reasoningBatchTitleIsPlainSingleLineText() {
|
||||
let title = AgentToolPresentation.reasoningTitle(
|
||||
|
||||
@@ -1,318 +1,10 @@
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
import TermindPersistence
|
||||
import TermindPresentation
|
||||
import TermindRemoteInfrastructure
|
||||
import Testing
|
||||
|
||||
@Suite
|
||||
struct ArchitectureBoundaryTests {
|
||||
@Test
|
||||
func resumableTransferStateRemainsProcessLocal() throws {
|
||||
let root = sourceRoot.deletingLastPathComponent()
|
||||
let checkpoint = try String(
|
||||
contentsOf: root.appending(
|
||||
path: "Frameworks/TermindRemoteInfrastructure/Transfers/SFTPResumeState.swift"), encoding: .utf8)
|
||||
for forbidden in ["Codable", "SwiftData", "UserDefaults", "bookmark", "FileHandle", "FileManager"] {
|
||||
#expect(!checkpoint.contains(forbidden))
|
||||
}
|
||||
let upload = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "Application/MCPServer/MCPUploadCoordinator.swift"), encoding: .utf8)
|
||||
#expect(upload.contains("upload-offset"))
|
||||
#expect(upload.contains("ticket.runID"))
|
||||
let download = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "Application/MCPServer/MCPDownloadCoordinator.swift"), encoding: .utf8)
|
||||
#expect(download.contains("Content-Range"))
|
||||
#expect(download.contains("ticket.expiresAt > now()"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func httpDownloadsUseCheckedRemoteStreamsWithoutLocalFilesOrScratch() throws {
|
||||
let root = sourceRoot.deletingLastPathComponent()
|
||||
let coordinator = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Application/MCPServer/MCPDownloadCoordinator.swift"),
|
||||
encoding: .utf8)
|
||||
for forbidden in [
|
||||
"AgentScratchWorkspace", "FileHandle", "FileManager", "localURL", "startAccessingSecurityScopedResource",
|
||||
] {
|
||||
#expect(!coordinator.contains(forbidden))
|
||||
}
|
||||
let stream = try String(
|
||||
contentsOf: root.appending(
|
||||
path: "Frameworks/TermindRemoteInfrastructure/Transfers/SFTPDownloadStream.swift"), encoding: .utf8)
|
||||
for forbidden in ["HTTPRequest", "HTTPResponse", "import Network", "AgentScratchWorkspace", "FileHandle"] {
|
||||
#expect(!stream.contains(forbidden))
|
||||
}
|
||||
let tools = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Application/MCPServer/MCPServerTools.swift"), encoding: .utf8)
|
||||
#expect(tools.contains("openDownloadStream("))
|
||||
#expect(tools.contains("withDedicatedSFTP("))
|
||||
let native = try String(
|
||||
contentsOf: root.appending(path: "Frameworks/TermindRemoteInfrastructure/SSH/SFTPClient.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(native.contains("readDownloadChunk(id:"))
|
||||
#expect(native.contains("libssh2_sftp_fstat_ex(entry.handle"))
|
||||
#expect(native.contains("entry.modificationTime"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func httpUploadsStayOutsideScratchAndDoNotReadClientFilePaths() throws {
|
||||
let root = sourceRoot.deletingLastPathComponent()
|
||||
let coordinator = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Application/MCPServer/MCPUploadCoordinator.swift"), encoding: .utf8)
|
||||
for forbidden in [
|
||||
"AgentScratchWorkspace", "FileHandle", "FileManager", "localURL", "startAccessingSecurityScopedResource",
|
||||
] {
|
||||
#expect(!coordinator.contains(forbidden))
|
||||
}
|
||||
let stream = try String(
|
||||
contentsOf: root.appending(
|
||||
path: "Frameworks/TermindRemoteInfrastructure/Transfers/SFTPStreamUploadOperation.swift"),
|
||||
encoding: .utf8)
|
||||
for forbidden in ["HTTPRequest", "HTTPResponse", "import Network", "AgentScratchWorkspace", "FileHandle"] {
|
||||
#expect(!stream.contains(forbidden))
|
||||
}
|
||||
#expect(stream.contains("digestMismatch"))
|
||||
#expect(stream.contains("relay.finishWriting()"))
|
||||
let bridge = try String(contentsOf: root.appending(path: "TermindMCPBridge/main.swift"), encoding: .utf8)
|
||||
#expect(!bridge.contains("/uploads/"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func singleLineEditingIsSharedButNativeFieldEditorsRemainAppOwned() throws {
|
||||
let root = sourceRoot.deletingLastPathComponent()
|
||||
let policy = try String(
|
||||
contentsOf: root.appending(path: "Frameworks/TermindPresentation/SingleLineTextInput.swift"),
|
||||
encoding: .utf8)
|
||||
for forbidden in ["import AppKit", "import UIKit", "import TermindPersistence", "import AgentKit"] {
|
||||
#expect(!policy.contains(forbidden))
|
||||
}
|
||||
let native = try String(
|
||||
contentsOf: sourceRoot.appending(path: "UI/Components/SingleLineFieldEditing.swift"), encoding: .utf8)
|
||||
#expect(native.contains("SingleLineTextInput.normalized"))
|
||||
#expect(native.contains("hasMarkedText()"))
|
||||
for path in [
|
||||
"TFM/Features/AI/ConversationTranscriptView.swift",
|
||||
"TFI/Features/AI/MobileAssistantComposer.swift",
|
||||
"TFI/Features/AI/MobileAssistantInteraction.swift",
|
||||
] {
|
||||
let content = try String(contentsOf: root.appending(path: path), encoding: .utf8)
|
||||
#expect(!content.contains(".singleLineText"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func aiSettingsUsePortableModelsAndSyncedSkills() throws {
|
||||
let root = sourceRoot.deletingLastPathComponent()
|
||||
let library = try String(
|
||||
contentsOf: root.appending(path: "Frameworks/TermindApplication/FeatureModels/SkillLibraryModel.swift"),
|
||||
encoding: .utf8)
|
||||
for forbidden in ["import SwiftData", "import AppKit", "import UIKit", "import TermindPersistence"] {
|
||||
#expect(!library.contains(forbidden))
|
||||
}
|
||||
let role = try String(
|
||||
contentsOf: root.appending(path: "Frameworks/TermindPresentation/ModelRoleSettings.swift"), encoding: .utf8)
|
||||
#expect(role.contains("role.unsetLabel"))
|
||||
#expect(!role.contains("AppModel"))
|
||||
let mobile = try String(contentsOf: root.appending(path: "TFI/App/MobileAppModel.swift"), encoding: .utf8)
|
||||
#expect(mobile.contains("SyncedSkillLibraryPersistence"))
|
||||
#expect(mobile.contains("skills.applyRemoteSnapshot"))
|
||||
let settings = try String(
|
||||
contentsOf: root.appending(path: "TFI/Features/Settings/MobileAISettingsView.swift"), encoding: .utf8)
|
||||
#expect(!settings.contains("AgentRuntime("))
|
||||
}
|
||||
|
||||
@Test
|
||||
func screenshotScenesShareTheProductionWindowHost() throws {
|
||||
let source = try String(contentsOf: sourceRoot.appending(path: "App/TermindApp.swift"), encoding: .utf8)
|
||||
#expect(source.components(separatedBy: "RootView(").count == 2)
|
||||
#expect(!source.contains("ScreenshotDemoWindow"))
|
||||
#expect(!source.contains(".suppressed"))
|
||||
#expect(source.contains(".restorationBehavior("))
|
||||
}
|
||||
|
||||
@Test
|
||||
func reviewInvitationsStayLocalAndOutsideTheAgentRuntime() throws {
|
||||
let sync = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../Frameworks/TermindDomain/Settings/SettingsSyncTypes.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(!sync.contains("reviewFirstUse"))
|
||||
#expect(!sync.contains("reviewLastAttempt"))
|
||||
let controller = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindApplication/FeatureModels/AppReviewController.swift"), encoding: .utf8
|
||||
)
|
||||
for forbidden in ["import StoreKit", "import AppKit", "import SwiftUI", "import AgentKit"] {
|
||||
#expect(!controller.contains(forbidden))
|
||||
}
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Platform/MobileAppReviewWindowReader.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(mobile.contains("ReviewInteractionGestureRecognizer"))
|
||||
#expect(mobile.contains("controller.removeSurface(surfaceID)"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func assistantCompositionIsSharedAndMobileInputRemainsSwiftUI() throws {
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Application/Agent/MobileAssistantModel.swift"),
|
||||
encoding: .utf8)
|
||||
let desktop = try String(contentsOf: sourceRoot.appending(path: "App/AppModel.swift"), encoding: .utf8)
|
||||
for source in [mobile, desktop] {
|
||||
#expect(source.contains("AssistantExecutionController("))
|
||||
#expect(!source.contains("AgentRuntime("))
|
||||
}
|
||||
let composer = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/AI/MobileAssistantComposer.swift"), encoding: .utf8)
|
||||
#expect(composer.contains("axis: .vertical"))
|
||||
#expect(!composer.contains("UITextView"))
|
||||
#expect(!composer.contains("UIViewRepresentable"))
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindApplication/Agent",
|
||||
forbidden: ["TermindInfrastructure", "TermindRemoteInfrastructure", "UIKit", "AppKit", "PDFKit", "Speech"])
|
||||
}
|
||||
|
||||
private var sourceRoot: URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "TFM", directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
@Test
|
||||
func settingsSyncKeepsExecutionHistoryAndLocalPermissionsOutsideItsPayloads() throws {
|
||||
let records = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../Frameworks/TermindDomain/Settings/SettingsSyncTypes.swift"),
|
||||
encoding: .utf8)
|
||||
for excluded in [
|
||||
"AgentPermissionMode", "AgentRun", "AgentConversation", "mcpServerPort", "mcpServerEnabled",
|
||||
"mcpServerDisabledTools", "mcpserver.token",
|
||||
] {
|
||||
#expect(!records.contains(excluded))
|
||||
}
|
||||
let localServer = try String(
|
||||
contentsOf: sourceRoot.appending(path: "App/MCPServerController.swift"), encoding: .utf8)
|
||||
#expect(localServer.contains("MCPServerTokenStore"))
|
||||
#expect(!localServer.contains("AIConfigurationModel"))
|
||||
let terminal = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindPersistence/TerminalPreferences+Defaults.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(!terminal.contains("dictionaryRepresentation"))
|
||||
#expect(!terminal.contains("agentPermissionMode"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func metricsViewsCannotOwnTransportsAndSecretsAreResolvedOffMainActor() throws {
|
||||
try assertNoConcreteImplementations(in: "Features/Metrics")
|
||||
let model = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../Frameworks/TermindApplication/Metrics/ServerMetricsModel.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(!model.contains("hosts.sshConnectionConfiguration("))
|
||||
#expect(model.contains("workers[server.id] = Task.detached"))
|
||||
#expect(model.contains("private nonisolated func run("))
|
||||
#expect(model.contains("try await beginRunAttempt("))
|
||||
let preparation = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindApplication/Connections/SSHConnectionPreparation.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(preparation.contains("Task.detached"))
|
||||
let view = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Features/Metrics/MetricsView.swift"), encoding: .utf8)
|
||||
#expect(!view.contains("SSHExecSession"))
|
||||
#expect(!view.contains("KeychainSecretStore"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func metricsToolDependsOnSnapshotsAndMonitoringDoesNotImportAgentKit() throws {
|
||||
let tool = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindRemoteInfrastructure/Agent/GetMetricsTool.swift"), encoding: .utf8)
|
||||
#expect(tool.contains("any MetricsSnapshotReading"))
|
||||
for forbidden in [
|
||||
"SSHMetricsSampler", "AgentConnectionPool", "ServerMetricsModel", "observeDetails(",
|
||||
"requestAuthentication(",
|
||||
] {
|
||||
#expect(!tool.contains(forbidden))
|
||||
}
|
||||
for path in [
|
||||
"../Frameworks/TermindApplication/Metrics/MetricsSnapshot.swift",
|
||||
"../Frameworks/TermindApplication/Metrics/ServerMetricsModel.swift",
|
||||
] {
|
||||
let source = try String(contentsOf: sourceRoot.appending(path: path), encoding: .utf8)
|
||||
#expect(!source.contains("import AgentKit"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func coreDirectoryIsGone() {
|
||||
let core = sourceRoot.appending(path: "Core", directoryHint: .isDirectory)
|
||||
#expect(!FileManager.default.fileExists(atPath: core.path))
|
||||
}
|
||||
|
||||
@Test
|
||||
func retiredFeatureDirectoriesAreGone() {
|
||||
for directory in ["Tabs", "ContextPanel"] {
|
||||
let url = sourceRoot.appending(path: "Features/\(directory)", directoryHint: .isDirectory)
|
||||
#expect(!FileManager.default.fileExists(atPath: url.path))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func appTargetsDoNotReintroduceFrameworkForwardingAliases() throws {
|
||||
let forwarding = try Regex(
|
||||
#"(?m)^typealias\s+[A-Za-z_][A-Za-z0-9_]*\s*=\s*(?:Termind|Shared)[A-Za-z0-9_.]*"#
|
||||
)
|
||||
for directory in ["", "../TFI"] {
|
||||
for file in try swiftFiles(in: directory) {
|
||||
let source = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(
|
||||
source.firstMatch(of: forwarding) == nil,
|
||||
"\(file.path) forwards a framework type instead of importing its owner"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for file in try swiftFiles(in: "../Frameworks/TermindPersistence") {
|
||||
#expect(!file.lastPathComponent.hasPrefix("Shared"))
|
||||
}
|
||||
|
||||
let sharedDeclaration = try Regex(
|
||||
#"(?m)^public\s+(?:nonisolated\s+)?(?:final\s+)?(?:struct|class|enum|actor|protocol|typealias)\s+Shared[A-Z]"#
|
||||
)
|
||||
for module in [
|
||||
"TermindDomain", "TermindApplication", "TermindInfrastructure",
|
||||
"TermindRemoteInfrastructure", "TermindPresentation", "TermindPersistence",
|
||||
] {
|
||||
for file in try swiftFiles(in: "../Frameworks/\(module)") {
|
||||
let source = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(
|
||||
source.firstMatch(of: sharedDeclaration) == nil,
|
||||
"\(file.path) uses Shared as a framework namespace"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func mobileTargetUsesTheDesktopLayerVocabulary() throws {
|
||||
let mobile = sourceRoot.appending(path: "../TFI", directoryHint: .isDirectory)
|
||||
for name in [
|
||||
"App", "Application", "Design", "DesignSystem", "Features", "Infrastructure", "Platform", "UI",
|
||||
] {
|
||||
let directory = mobile.appending(path: name, directoryHint: .isDirectory)
|
||||
var isDirectory: ObjCBool = false
|
||||
#expect(FileManager.default.fileExists(atPath: directory.path, isDirectory: &isDirectory))
|
||||
#expect(isDirectory.boolValue)
|
||||
#expect(!(try swiftFiles(in: "../TFI/\(name)")).isEmpty)
|
||||
}
|
||||
|
||||
func mobileLayersKeepUIAndInfrastructureSeparate() throws {
|
||||
try assertNoImports(
|
||||
in: "../TFI/Application",
|
||||
forbidden: ["SwiftUI", "UIKit", "SwiftData", "TermindPersistence"])
|
||||
@@ -322,199 +14,6 @@ struct ArchitectureBoundaryTests {
|
||||
try assertNoConcreteImplementations(in: "../TFI/Features")
|
||||
}
|
||||
|
||||
@Test
|
||||
func mobileFeaturesAndLiveServicesEnterThroughRegistries() throws {
|
||||
let root = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileRootView.swift"), encoding: .utf8)
|
||||
let features = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileFeatureRegistry.swift"), encoding: .utf8)
|
||||
#expect(root.contains("features.registrations(on: .phoneTab)"))
|
||||
#expect(root.contains("features.registrations(on: .padSidebar)"))
|
||||
#expect(!root.contains("switch scene.selectedDestination"))
|
||||
#expect(features.contains("mutating func register("))
|
||||
#expect(features.contains("Duplicate mobile feature registration"))
|
||||
#expect(features.contains("model: scene.assistant"))
|
||||
#expect(!features.contains("model: app.assistant"))
|
||||
|
||||
let scene = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileSceneModel.swift"), encoding: .utf8)
|
||||
#expect(scene.contains("let assistant: MobileAssistantSceneModel"))
|
||||
|
||||
let descriptor = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../TFI/Application/Workspaces/MobileWorkspaceDescriptor.swift"), encoding: .utf8)
|
||||
#expect(descriptor.contains("var kind: ConnectionHistoryEntry.Kind"))
|
||||
#expect(!descriptor.contains("enum Kind"))
|
||||
|
||||
let assistant = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Application/Agent/MobileAssistantModel.swift"),
|
||||
encoding: .utf8)
|
||||
let metrics = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Application/Metrics/MobileMetricsModel.swift"),
|
||||
encoding: .utf8)
|
||||
for concrete in ["SSHExecSession()", "SFTPClient()"] {
|
||||
#expect(!assistant.contains(concrete))
|
||||
}
|
||||
for concrete in ["SSHMetricsSampler", "SSHExecSession"] {
|
||||
#expect(!metrics.contains(concrete))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func sessionFeaturesDependOnTheConnectionPort() throws {
|
||||
for path in ["Features/Terminal/SSHSessionModel.swift", "Features/SFTP/SFTPSessionModel.swift"] {
|
||||
let text = try String(
|
||||
contentsOf: sourceRoot.appending(path: path), encoding: .utf8
|
||||
)
|
||||
#expect(!text.contains("HostLibraryModel"), "\(path) bypasses RemoteSessionHostStore")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func sshHostKeyTrustStaysInsideTheLiveConnectionAttempt() throws {
|
||||
let request = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindApplication/SSHConnectionTypes.swift"
|
||||
),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(request.contains("struct SSHConnectionRequest"))
|
||||
#expect(request.contains("SSHHostKeyAuthorizer"))
|
||||
|
||||
for root in [
|
||||
"../Frameworks/TermindRemoteInfrastructure", "Features/Terminal",
|
||||
"Features/SFTP", "../TFI",
|
||||
] {
|
||||
for file in try swiftFiles(in: root) {
|
||||
let source = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(!source.contains("discoverHostKey"), "\(file.path) opens a separate host-key probe")
|
||||
#expect(!source.contains("probe complete"), "\(file.path) closes an unauthenticated probe")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func remoteDirectoryStateDependsOnTheApplicationPort() throws {
|
||||
let value = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../Frameworks/TermindDomain/Transfers/RemoteFile.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(value.contains("struct RemoteFile"))
|
||||
#expect(!value.contains("symbol:"))
|
||||
#expect(!value.contains("sftpKindDescription"))
|
||||
|
||||
let transport = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindRemoteInfrastructure/SSH/SFTPClient.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(transport.contains("SFTPClientProtocol: RemoteFileSystem"))
|
||||
#expect(!transport.contains("struct RemoteFile"))
|
||||
#expect(transport.contains("sourceSucceeded()"))
|
||||
#expect(transport.contains("observeCancellation { relay.stop() }"))
|
||||
|
||||
let copy = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindRemoteInfrastructure/Agent/SFTPCopyTool.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(copy.contains("withDedicatedSFTP"))
|
||||
#expect(!copy.contains("AgentScratchWorkspace"))
|
||||
#expect(!copy.contains("SFTPClient()"))
|
||||
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Application/Transfers/MobileSFTPSessionModel.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(mobile.contains("any RemoteFileSystem"))
|
||||
#expect(!mobile.contains("TermindRemoteInfrastructure"))
|
||||
|
||||
let workspace = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Workspaces/MobileWorkspace.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(!workspace.contains("private(set) var files: [RemoteFile]"))
|
||||
#expect(!workspace.contains("var path = \"/\""))
|
||||
}
|
||||
|
||||
@Test
|
||||
func terminalPresentationPolicyIsSharedAcrossApps() throws {
|
||||
let presentation = try String(
|
||||
contentsOf: sourceRoot.appending(
|
||||
path: "../Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
#expect(presentation.contains("enum TermindTerminalThemeCatalog"))
|
||||
#expect(presentation.contains("func applyPreferences"))
|
||||
#expect(
|
||||
!FileManager.default.fileExists(
|
||||
atPath: sourceRoot.appending(path: "../TFI/Platform/MobileTerminalAppearance.swift").path
|
||||
)
|
||||
)
|
||||
|
||||
let desktopTheme = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Design/TerminalTheme.swift"), encoding: .utf8)
|
||||
#expect(!desktopTheme.contains("enum TermindTerminalThemeCatalog"))
|
||||
#expect(!desktopTheme.contains("func applyAppearance"))
|
||||
|
||||
for path in [
|
||||
"Features/Terminal/SSHSessionModel.swift",
|
||||
"Features/Terminal/SerialSessionModel.swift",
|
||||
"../TFI/Features/Workspaces/MobileWorkspace.swift",
|
||||
] {
|
||||
let source = try String(contentsOf: sourceRoot.appending(path: path), encoding: .utf8)
|
||||
#expect(source.contains("TerminalFindController("), "\(path) does not use shared Find state")
|
||||
#expect(!source.contains("var findQuery ="), "\(path) owns a second Find query")
|
||||
}
|
||||
|
||||
for path in [
|
||||
"UI/Components/ConnectionJourneyView.swift",
|
||||
"../TFI/UI/Components/MobileConnectionJourneyView.swift",
|
||||
] {
|
||||
let source = try String(contentsOf: sourceRoot.appending(path: path), encoding: .utf8)
|
||||
#expect(source.contains("ConnectionJourneyHeader("))
|
||||
#expect(source.contains("ConnectionJourneyReconnectStatus("))
|
||||
#expect(!source.contains("private var headerSymbol"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func retiredContextPanelTypesAreGone() throws {
|
||||
for file in try swiftFiles(in: "Features") {
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(!text.contains("ContextPanel"), "\(file.path) retains retired ContextPanel naming")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func customKeyboardShortcutsUseTheApplicationRegistry() throws {
|
||||
for file in try swiftFiles(in: "")
|
||||
where file.lastPathComponent != "AppShortcut.swift" {
|
||||
let lines = try String(contentsOf: file, encoding: .utf8)
|
||||
.split(separator: "\n", omittingEmptySubsequences: false)
|
||||
for (offset, line) in lines.enumerated() where line.contains(".keyboardShortcut(") {
|
||||
let isStandardDialogAction =
|
||||
line.contains(".defaultAction")
|
||||
|| line.contains(".cancelAction")
|
||||
#expect(
|
||||
isStandardDialogAction,
|
||||
"\(file.path):\(offset + 1) bypasses AppShortcut"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func featureFacadesDoNotExposeTheirBackingLibrary() throws {
|
||||
for file in try swiftFiles(in: "../Frameworks/TermindApplication/FeatureModels") {
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(
|
||||
!text.contains("\n let library: HostLibraryModel"),
|
||||
"\(file.path) exposes the backing aggregate"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func domainImportsOnlyPlatformIndependentFrameworks() throws {
|
||||
try assertNoImports(
|
||||
@@ -574,23 +73,87 @@ struct ArchitectureBoundaryTests {
|
||||
}
|
||||
)
|
||||
|
||||
#expect(importers == allowed)
|
||||
#expect(importers.isSubset(of: allowed))
|
||||
}
|
||||
|
||||
let mobileConsumers = try Set(
|
||||
swiftFiles(in: "../TFI").compactMap { file -> String? in
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
guard text.contains("MobileNativeList(") else { return nil }
|
||||
return file.standardizedFileURL.path.replacingOccurrences(
|
||||
of: repositoryRoot.path + "/", with: "")
|
||||
}
|
||||
/// App-local types shadow imports, so redefining these would silently fork AgentKit.
|
||||
@Test
|
||||
func typesOwnedByTheAgentPackageAreNotRedefinedHere() throws {
|
||||
let owned = [
|
||||
"AgentRuntime", "AgentToolExecutor", "AgentToolScheduler", "AgentTurnDriver",
|
||||
"AgentToolCatalog", "AgentToolRegistry", "AgentEventChannel", "AgentContextPipeline",
|
||||
"AgentSecretLifecycle", "AgentApprovalBroker", "SecretBroker", "AgentProviderClient",
|
||||
"AgentModelRetryPolicy", "AgentModelRetryProgress",
|
||||
"MCPClient", "MCPServer", "ModelProvider", "AIModel", "AgentSkill",
|
||||
]
|
||||
let declaration = try Regex(
|
||||
#"(?m)^\s*(?:public |internal |private |fileprivate |nonisolated |final |indirect )*"#
|
||||
+ #"(?:struct|class|enum|actor|protocol|typealias)\s+([A-Za-z_][A-Za-z0-9_]*)"#
|
||||
)
|
||||
#expect(
|
||||
mobileConsumers == [
|
||||
"TFI/Features/AI/MobileAssistantConversation.swift",
|
||||
"TFI/Features/SFTP/MobileSFTPBrowser.swift",
|
||||
for file in try swiftFiles(in: "") {
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
for match in text.matches(of: declaration) {
|
||||
let name = String(match.output[1].substring ?? "")
|
||||
#expect(
|
||||
!owned.contains(name),
|
||||
"\(file.path) redefines \(name), which AgentKit owns"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func sharedFrameworksCannotImportPlatformAppsOrUI() throws {
|
||||
for module in [
|
||||
"TermindDomain", "TermindApplication", "TermindInfrastructure", "TermindRemoteInfrastructure",
|
||||
"TermindPersistence",
|
||||
] {
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/\(module)",
|
||||
forbidden: ["Termind", "TermindIOS", "AppKit", "UIKit", "IOKit", "ServiceManagement"])
|
||||
}
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindApplication",
|
||||
forbidden: ["TermindInfrastructure", "TermindPersistence", "SwiftData"])
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindPersistence",
|
||||
forbidden: ["TermindInfrastructure", "TermindRemoteInfrastructure"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func sharedPresentationHasNoPlatformOrStorageDependency() throws {
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindPresentation",
|
||||
forbidden: [
|
||||
"Termind", "TermindIOS", "UIKit", "AppKit", "SwiftData", "TermindPersistence",
|
||||
"TermindRemoteInfrastructure",
|
||||
])
|
||||
}
|
||||
|
||||
@Test
|
||||
func swiftDataModelsHaveOneSharedOwner() throws {
|
||||
for directory in [
|
||||
"", "../TFI", "../Frameworks/TermindDomain", "../Frameworks/TermindApplication",
|
||||
"../Frameworks/TermindInfrastructure",
|
||||
] {
|
||||
for file in try swiftFiles(in: directory) {
|
||||
let source = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(
|
||||
source.range(of: #"(?m)^\s*@Model\b"#, options: .regularExpression) == nil,
|
||||
"\(file.path) declares a model outside TermindPersistence")
|
||||
}
|
||||
}
|
||||
let models = try swiftFiles(in: "../Frameworks/TermindPersistence")
|
||||
#expect(!models.isEmpty)
|
||||
}
|
||||
|
||||
private var sourceRoot: URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "TFM", directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
private func assertNoImports(in directory: String, forbidden: Set<String>) throws {
|
||||
for file in try swiftFiles(in: directory) {
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
@@ -632,188 +195,6 @@ struct ArchitectureBoundaryTests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The agent runtime lives in AgentKit and must keep living there.
|
||||
///
|
||||
/// A redefinition here would compile: Swift resolves the app's own type
|
||||
/// ahead of an imported one, so the app would silently fork the runtime and
|
||||
/// nothing would fail until a card drawn by one was replayed by the other.
|
||||
/// Checked by definition rather than by import, because the app is expected
|
||||
/// to import AgentKit everywhere.
|
||||
@Test
|
||||
func typesOwnedByTheAgentPackageAreNotRedefinedHere() throws {
|
||||
let owned = [
|
||||
"AgentRuntime", "AgentToolExecutor", "AgentToolScheduler", "AgentTurnDriver",
|
||||
"AgentToolCatalog", "AgentToolRegistry", "AgentEventChannel", "AgentContextPipeline",
|
||||
"AgentSecretLifecycle", "AgentApprovalBroker", "SecretBroker", "AgentProviderClient",
|
||||
"AgentModelRetryPolicy", "AgentModelRetryProgress",
|
||||
"MCPClient", "MCPServer", "ModelProvider", "AIModel", "AgentSkill",
|
||||
]
|
||||
let declaration = try Regex(
|
||||
#"(?m)^\s*(?:public |internal |private |fileprivate |nonisolated |final |indirect )*"#
|
||||
+ #"(?:struct|class|enum|actor|protocol|typealias)\s+([A-Za-z_][A-Za-z0-9_]*)"#
|
||||
)
|
||||
for file in try swiftFiles(in: "") {
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
for match in text.matches(of: declaration) {
|
||||
let name = String(match.output[1].substring ?? "")
|
||||
#expect(
|
||||
!owned.contains(name),
|
||||
"\(file.path) redefines \(name), which AgentKit owns"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func mobilePortUsesSharedStoresAndSeparateSigningFiles() throws {
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileAppModel.swift"), encoding: .utf8)
|
||||
#expect(mobile.contains("SyncedAIConfigurationPersistence(settingsPersistence)"))
|
||||
#expect(mobile.contains("settingsSync?.didApplyRemote"))
|
||||
#expect(mobile.contains("vaultSync?.didApplyRemote"))
|
||||
let project = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../Termind.xcodeproj/project.pbxproj"), encoding: .utf8)
|
||||
#expect(project.contains("TFI/TFI.entitlements"))
|
||||
#expect(project.contains("TFM/TFM.entitlements"))
|
||||
#expect(!project.contains("Termind.entitlements"))
|
||||
let terminal = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Settings/MobileTerminalSettingsView.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(!terminal.contains("remoteWindowTitleInMenuBar"))
|
||||
let cloud = try swiftFiles(in: "../Frameworks/TermindInfrastructure/Cloud")
|
||||
#expect(cloud.count == 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
func forwardingEditorsShareTheFlatFormAndContextualLabels() throws {
|
||||
let desktop = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Features/Ports/PortForwardingView.swift"), encoding: .utf8)
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Ports/MobileForwardingEditor.swift"),
|
||||
encoding: .utf8)
|
||||
let mobileHost = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/HostEditor/MobileHostEditor.swift"),
|
||||
encoding: .utf8)
|
||||
let shared = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../Frameworks/TermindPresentation/PortForwardingFields.swift"),
|
||||
encoding: .utf8)
|
||||
|
||||
#expect(shared.contains("Section(\"Port Forwarding\")"))
|
||||
for editor in [desktop, mobile] {
|
||||
#expect(editor.contains("PortForwardingFields(rule: $draft)"))
|
||||
#expect(editor.contains("New Forwarding"))
|
||||
#expect(editor.contains("Edit Forwarding"))
|
||||
}
|
||||
#expect(desktop.contains("FormNavigationAction(title: \"Add Forwarding\")"))
|
||||
#expect(desktop.contains("Button(\"Enable All\""))
|
||||
#expect(desktop.contains("Button(\"Disable All\""))
|
||||
#expect(desktop.contains("Waiting for Save"))
|
||||
#expect(mobileHost.contains("Button(\"Add Forwarding\", systemImage: \"plus\")"))
|
||||
#expect(mobileHost.contains("Waiting for Save"))
|
||||
#expect(mobileHost.contains("isNew: !draftForwardings.contains"))
|
||||
for removed in ["Add Port Forward", "New Port Forward", "Edit Port Forward"] {
|
||||
#expect(!desktop.contains(removed))
|
||||
#expect(!mobile.contains(removed))
|
||||
#expect(!mobileHost.contains(removed))
|
||||
}
|
||||
for removed in ["DisclosureGroup(\"Advanced\"", "Section(\"SSH Host\")", "Section(\"Route Preview\")"] {
|
||||
#expect(!desktop.contains(removed))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func sharedFrameworksCannotImportPlatformAppsOrUI() throws {
|
||||
for module in [
|
||||
"TermindDomain", "TermindApplication", "TermindInfrastructure", "TermindRemoteInfrastructure",
|
||||
"TermindPersistence",
|
||||
] {
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/\(module)",
|
||||
forbidden: ["Termind", "TermindIOS", "AppKit", "UIKit", "IOKit", "ServiceManagement"])
|
||||
}
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindApplication",
|
||||
forbidden: ["TermindInfrastructure", "TermindPersistence", "SwiftData"])
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindPersistence",
|
||||
forbidden: ["TermindInfrastructure", "TermindRemoteInfrastructure"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func sharedPresentationHasNoPlatformOrStorageDependency() throws {
|
||||
try assertNoImports(
|
||||
in: "../Frameworks/TermindPresentation",
|
||||
forbidden: [
|
||||
"Termind", "TermindIOS", "UIKit", "AppKit", "SwiftData", "TermindPersistence",
|
||||
"TermindRemoteInfrastructure",
|
||||
])
|
||||
}
|
||||
|
||||
@Test
|
||||
func fullSurfaceDropOverlayHasOneSharedPresentationOwner() throws {
|
||||
let sharedPath = sourceRoot.appending(
|
||||
path: "../Frameworks/TermindPresentation/DropTargetOverlay.swift")
|
||||
let shared = try String(contentsOf: sharedPath, encoding: .utf8)
|
||||
|
||||
#expect(shared.contains("public struct DropTargetOverlay"))
|
||||
#expect(shared.contains("public nonisolated struct DropTargetPresentation"))
|
||||
#expect(
|
||||
!FileManager.default.fileExists(
|
||||
atPath: sourceRoot.appending(path: "UI/Components/DropTargetOverlay.swift").path
|
||||
)
|
||||
)
|
||||
|
||||
for path in [
|
||||
"Features/AI/ConversationTranscriptView.swift",
|
||||
"../TFI/Features/AI/MobileAssistantConversation.swift",
|
||||
"../TFI/Features/Workspaces/MobileWorkspaceView.swift",
|
||||
] {
|
||||
let consumer = try String(
|
||||
contentsOf: sourceRoot.appending(path: path), encoding: .utf8)
|
||||
#expect(consumer.contains("DropTargetOverlay("), "\(path) does not reuse the shared overlay")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func sftpUploadTargetsTheCurrentOpenDirectoryAcrossPlatforms() throws {
|
||||
let desktop = try String(
|
||||
contentsOf: sourceRoot.appending(path: "Features/SFTP/SFTPSessionModel.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Workspaces/MobileWorkspace.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
let desktopUpload = try #require(
|
||||
desktop.components(separatedBy: "func prepareUpload").last?
|
||||
.components(separatedBy: "func prepareDownload").first
|
||||
)
|
||||
let mobileDestination = try #require(
|
||||
mobile.components(separatedBy: "var uploadDestination").last?
|
||||
.components(separatedBy: "var uploadTargetProblem").first
|
||||
)
|
||||
|
||||
#expect(desktopUpload.contains("destination: path"))
|
||||
#expect(mobileDestination.contains("return sftpSession.path"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func swiftDataModelsHaveOneSharedOwner() throws {
|
||||
for directory in [
|
||||
"", "../TFI", "../Frameworks/TermindDomain", "../Frameworks/TermindApplication",
|
||||
"../Frameworks/TermindInfrastructure",
|
||||
] {
|
||||
for file in try swiftFiles(in: directory) {
|
||||
let source = try String(contentsOf: file, encoding: .utf8)
|
||||
#expect(
|
||||
source.range(of: #"(?m)^\s*@Model\b"#, options: .regularExpression) == nil,
|
||||
"\(file.path) declares a model outside TermindPersistence")
|
||||
}
|
||||
}
|
||||
let models = try swiftFiles(in: "../Frameworks/TermindPersistence")
|
||||
#expect(!models.isEmpty)
|
||||
}
|
||||
|
||||
private func swiftFiles(in directory: String) throws -> [URL] {
|
||||
let root = sourceRoot.appending(path: directory, directoryHint: .isDirectory)
|
||||
let keys: [URLResourceKey] = [.isRegularFileKey]
|
||||
|
||||
@@ -68,74 +68,6 @@ struct ColorSystemTests {
|
||||
#expect(colorDistance(light, dark) > 0.5)
|
||||
}
|
||||
|
||||
@Test
|
||||
func featureUIHasNoLiteralDisplayColorsOrDecorativeBlue() throws {
|
||||
let sourceRoot = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "TFM", directoryHint: .isDirectory)
|
||||
let exemptPaths = ["DesignSystem/TagColor+Presentation.swift"]
|
||||
let forbidden = [
|
||||
"Color.white", "Color.black", "NSColor.white", "NSColor.gray",
|
||||
"foregroundStyle(.white)", "AnyShapeStyle(.white)",
|
||||
".shadow(color: .black", "Color(white:", "Color(red:", "NSColor(red:",
|
||||
".blue", "systemBlue",
|
||||
]
|
||||
|
||||
for file in try swiftFiles(in: sourceRoot) {
|
||||
let relative = file.path.replacingOccurrences(
|
||||
of: sourceRoot.path + "/",
|
||||
with: ""
|
||||
)
|
||||
if exemptPaths.contains(relative) { continue }
|
||||
let lines = try String(contentsOf: file, encoding: .utf8)
|
||||
.split(separator: "\n", omittingEmptySubsequences: false)
|
||||
for (offset, line) in lines.enumerated() {
|
||||
// Black and white are alpha values here, not visible UI hues.
|
||||
let isMask =
|
||||
relative == "Features/AI/TranscriptNativeRows.swift"
|
||||
&& line.contains("mask.colors")
|
||||
if isMask { continue }
|
||||
// A `TagColor` case a user picked for a host or key, not a hue
|
||||
// this code chose to draw. Its presentation is resolved through
|
||||
// the exempt `TagColor+Presentation.swift`, which is the file
|
||||
// this scan exists to funnel such choices into.
|
||||
if line.contains("colorTag:") { continue }
|
||||
for token in forbidden {
|
||||
#expect(
|
||||
!line.contains(token),
|
||||
"\(relative):\(offset + 1) bypasses TermindColor with \(token)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func nativeHostingListsReloadRowsWhenTheirEnvironmentChanges() throws {
|
||||
let sourceRoot = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "TFM", directoryHint: .isDirectory)
|
||||
|
||||
for file in try swiftFiles(in: sourceRoot) {
|
||||
let text = try String(contentsOf: file, encoding: .utf8)
|
||||
let environmentChangeCount =
|
||||
text.components(
|
||||
separatedBy: "let environmentChanged"
|
||||
).count - 1
|
||||
guard environmentChangeCount > 0 else { continue }
|
||||
let forcedReloadCount =
|
||||
text.components(
|
||||
separatedBy: "if environmentChanged { listView.reloadData() }"
|
||||
).count - 1
|
||||
#expect(
|
||||
forcedReloadCount == environmentChangeCount,
|
||||
"\(file.path) can leave recycled NSHostingView rows in the old appearance"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func resolved(
|
||||
_ color: Color,
|
||||
appearance name: NSAppearance.Name
|
||||
@@ -171,19 +103,4 @@ struct ColorSystemTests {
|
||||
+ 0.7152 * linear(color.greenComponent)
|
||||
+ 0.0722 * linear(color.blueComponent)
|
||||
}
|
||||
|
||||
private func swiftFiles(in root: URL) throws -> [URL] {
|
||||
let keys: Set<URLResourceKey> = [.isRegularFileKey]
|
||||
guard
|
||||
let enumerator = FileManager.default.enumerator(
|
||||
at: root,
|
||||
includingPropertiesForKeys: Array(keys)
|
||||
)
|
||||
else { return [] }
|
||||
return try enumerator.compactMap { value in
|
||||
guard let url = value as? URL, url.pathExtension == "swift" else { return nil }
|
||||
let values = try url.resourceValues(forKeys: keys)
|
||||
return values.isRegularFile == true ? url : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,29 +10,6 @@ import Testing
|
||||
@testable import Termind
|
||||
|
||||
struct CommerceConfigurationTests {
|
||||
@Test
|
||||
func normalDebugLaunchUsesLiveCommerce() throws {
|
||||
let appFile = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "TFM/App/TermindApp.swift")
|
||||
let app = try String(contentsOf: appFile, encoding: .utf8)
|
||||
|
||||
#expect(!app.contains(".developmentDefault()"))
|
||||
#expect(app.contains(": .live()"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func appTargetDeclaresInAppPurchaseCapability() throws {
|
||||
let projectFile = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "Termind.xcodeproj/project.pbxproj")
|
||||
let project = try String(contentsOf: projectFile, encoding: .utf8)
|
||||
|
||||
#expect(project.contains("com.apple.InAppPurchase = {"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func localStoreKitProductMatchesPurchaseRequest() throws {
|
||||
let configurationFile = URL(fileURLWithPath: #filePath)
|
||||
|
||||
@@ -48,7 +48,6 @@ struct ConnectionJourneyTests {
|
||||
#expect(journey.mode == .reconnection)
|
||||
#expect(journey.phase == .connecting)
|
||||
#expect(journey.isPresented)
|
||||
#expect(journey.plainTextLog.contains("Reconnecting SSH session"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,7 +113,6 @@ struct ConnectionJourneyTests {
|
||||
journey.waitForLocalPassphrase(at: destination.id)
|
||||
#expect(journey.waitsForLocalPassphrase)
|
||||
#expect(!journey.waitsForLocalIdentity)
|
||||
#expect(journey.plainTextLog.contains("Waiting for a Passphrase"))
|
||||
|
||||
journey.waitForLocalIdentity(at: destination.id)
|
||||
#expect(journey.waitsForLocalIdentity)
|
||||
|
||||
@@ -13,33 +13,6 @@ import Testing
|
||||
@Suite
|
||||
@MainActor
|
||||
struct KeychainOrderTests {
|
||||
private func storagePrecedesColor(in source: String, occurrences: Int) -> Bool {
|
||||
var searchRange = source.startIndex..<source.endIndex
|
||||
for _ in 0..<occurrences {
|
||||
guard let storage = source.range(of: "Picker(\"Store in\"", range: searchRange),
|
||||
let color = source.range(of: "ColorTagPicker", range: storage.upperBound..<source.endIndex)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
searchRange = color.upperBound..<source.endIndex
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Test
|
||||
func keyFormsPlaceStorageBeforeColor() throws {
|
||||
let sourceRoot = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent()
|
||||
let desktop = try String(
|
||||
contentsOf: sourceRoot.appendingPathComponent("TFM/Features/Keychain/KeychainPageView.swift"),
|
||||
encoding: .utf8)
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appendingPathComponent("TFI/Features/Keychain/MobileKeyEditor.swift"),
|
||||
encoding: .utf8)
|
||||
|
||||
#expect(storagePrecedesColor(in: desktop, occurrences: 2))
|
||||
#expect(storagePrecedesColor(in: mobile, occurrences: 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
func unsortedInputsStartAlphabeticallyAndCanBeReordered() throws {
|
||||
let zebra = SSHKeyRef(label: "Zebra")
|
||||
|
||||
@@ -156,7 +156,6 @@ struct ListViewMigrationTests {
|
||||
#expect(normal.id == naming.id)
|
||||
#expect(normal != naming)
|
||||
#expect(normal.height == Metrics.Sidebar.historyCardHeight)
|
||||
#expect(normal.height == 52)
|
||||
#expect(naming.height == Metrics.Sidebar.historyCardEditingHeight)
|
||||
#expect(naming.height > normal.height)
|
||||
}
|
||||
|
||||
@@ -12,13 +12,6 @@ import Testing
|
||||
|
||||
@Suite
|
||||
struct MenuSymbolTests {
|
||||
private var vocabularySource: URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "TFM/DesignSystem/MenuSymbol.swift")
|
||||
}
|
||||
|
||||
/// A misspelled SF Symbol draws nothing and reports nothing, so the menu
|
||||
/// simply loses one glyph and no one finds out. This is the only place that
|
||||
/// notices.
|
||||
@@ -32,26 +25,6 @@ struct MenuSymbolTests {
|
||||
}
|
||||
}
|
||||
|
||||
/// `all` is hand-written, so a new verb can be added and left out of it —
|
||||
/// which would quietly exempt the new glyph from the check above.
|
||||
@Test
|
||||
func theCheckedListCoversEveryDeclaredGlyph() throws {
|
||||
let text = try String(contentsOf: vocabularySource, encoding: .utf8)
|
||||
let declarations = text.split(separator: "\n").compactMap { line -> String? in
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
guard trimmed.hasPrefix("static let "), trimmed.contains("= \"") else { return nil }
|
||||
let name = trimmed.dropFirst("static let ".count).prefix { $0 != " " }
|
||||
return String(name)
|
||||
}
|
||||
#expect(!declarations.isEmpty)
|
||||
|
||||
let listMarker = "static let all: [String] = ["
|
||||
let listed = text.components(separatedBy: listMarker).last ?? ""
|
||||
for name in declarations {
|
||||
#expect(listed.contains(name), "MenuSymbol.\(name) is missing from `all`")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MainActor
|
||||
func menuBarBrandAssetLoadsAsATransparentTemplate() throws {
|
||||
|
||||
@@ -34,18 +34,6 @@ struct MetricsInteractionTests {
|
||||
#expect(window.isToolbarFindFocused)
|
||||
}
|
||||
|
||||
@Test
|
||||
func processColumnsSupportAscendingAndDescendingSort() {
|
||||
let rows = [
|
||||
MetricsProcess(pid: 20, started: 1, name: "zeta", cpu: 30, memory: 200),
|
||||
MetricsProcess(pid: 2, started: 1, name: "alpha", cpu: 70, memory: 100),
|
||||
]
|
||||
#expect(rows.sorted(using: KeyPathComparator(\.pid)).map(\.pid) == [2, 20])
|
||||
#expect(rows.sorted(using: KeyPathComparator(\.name)).map(\.pid) == [2, 20])
|
||||
#expect(rows.sorted(using: KeyPathComparator(\.cpuSortValue, order: .reverse)).map(\.pid) == [2, 20])
|
||||
#expect(rows.sorted(using: KeyPathComparator(\.memorySortValue, order: .reverse)).map(\.pid) == [20, 2])
|
||||
}
|
||||
|
||||
@Test(arguments: [HostChooserPurpose.connect, .batchStartup, .metrics])
|
||||
func hostChoosersRenderWithTheirRequiredEnvironment(
|
||||
purpose: HostChooserPurpose
|
||||
|
||||
@@ -39,69 +39,6 @@ struct MetricsPresentationTests {
|
||||
#expect(try height() == connecting)
|
||||
}
|
||||
|
||||
@Test
|
||||
func diskColumnsRenderAcrossTheFullAvailableWidth() throws {
|
||||
let io = MetricsDevicePoint(
|
||||
id: "8:1", read: 0, write: 0, readTotal: 438.4 * 1_073_741_824,
|
||||
writeTotal: 625.2 * 1_073_741_824, readIOPS: 0, writeIOPS: 0
|
||||
)
|
||||
for width in [246.0, 300.0] {
|
||||
let renderer = ImageRenderer(
|
||||
content: MetricsDiskTable(io: io).frame(width: width)
|
||||
.environment(\.locale, Locale(identifier: "zh-Hans")))
|
||||
renderer.scale = 2
|
||||
let image = try #require(renderer.nsImage)
|
||||
#expect(image.size.width == CGFloat(width))
|
||||
#expect(image.size.height == 52)
|
||||
let bitmap = try #require(image.tiffRepresentation.flatMap { NSBitmapImageRep(data: $0) })
|
||||
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
||||
try png.write(
|
||||
to: FileManager.default.temporaryDirectory.appending(path: "termind-disk-table-\(Int(width)).png"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func nvidiaDetailsRenderWithTwoSimulatedDevicesWithoutConnecting() throws {
|
||||
let gib = 1_073_741_824.0
|
||||
let server = ServerMetric(selection: MetricsSelection(id: UUID()), name: "GPU Preview")
|
||||
server.isConnecting = false
|
||||
server.gpuState = .available
|
||||
server.gpuSnapshot = NvidiaSnapshot(
|
||||
cudaVersion: "12.4",
|
||||
devices: [
|
||||
NvidiaGPU(
|
||||
id: "GPU-00000000-0000-0000-0000-000000000001", name: "NVIDIA GPU 0",
|
||||
utilization: 74, memoryUsed: 17.3 * gib, memoryTotal: 24 * gib, memoryFree: 6.7 * gib,
|
||||
memoryUtilization: 51, encoder: 12, decoder: 0, fan: 45, vbios: "95.02.3C.00.01"
|
||||
),
|
||||
NvidiaGPU(
|
||||
id: "GPU-00000000-0000-0000-0000-000000000002", name: "NVIDIA GPU 1",
|
||||
utilization: 18, memoryUsed: 4.1 * gib, memoryTotal: 24 * gib, memoryFree: 19.9 * gib,
|
||||
memoryUtilization: 9, encoder: 0, decoder: 0, fan: nil, vbios: "95.02.3C.00.01"
|
||||
),
|
||||
])
|
||||
for language in ["en", "zh-Hans", "zh-Hant"] {
|
||||
for expanded in [false, true] {
|
||||
let content = MetricsGPUSection(server: server, initiallyExpanded: expanded)
|
||||
.frame(width: 324, alignment: .leading)
|
||||
.padding(12).background(TermindColor.windowSurface)
|
||||
.environment(\.locale, Locale(identifier: language))
|
||||
.environment(\.colorScheme, .light)
|
||||
let renderer = ImageRenderer(content: content)
|
||||
renderer.scale = 2
|
||||
var image: NSImage?
|
||||
NSAppearance(named: .aqua)?.performAsCurrentDrawingAppearance { image = renderer.nsImage }
|
||||
let bitmap = try #require(image?.tiffRepresentation.flatMap { NSBitmapImageRep(data: $0) })
|
||||
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
||||
#expect(bitmap.pixelsWide == 696)
|
||||
#expect(bitmap.pixelsHigh > 600)
|
||||
try png.write(
|
||||
to: FileManager.default.temporaryDirectory.appending(
|
||||
path: "termind-gpu-preview-\(language)\(expanded ? "-expanded" : "").png"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func swapRendersAsASeparatePanelBelowMemoryAndHidesAtZeroCapacity() throws {
|
||||
let withSwap = ServerMetric(selection: MetricsSelection(id: UUID()), name: "Swap Preview")
|
||||
@@ -138,46 +75,6 @@ struct MetricsPresentationTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func compactCardsRenderInEveryLanguageAndAppearance() throws {
|
||||
let hosts = HostLibraryModel()
|
||||
let model = ServerMetricsModel(
|
||||
hosts: hosts, preferences: PreviewMetricsPreferences(),
|
||||
makeSampler: { SSHMetricsSampler(session: RemoteConnectionCoordinator().makeExecSession()) }
|
||||
)
|
||||
let server = ServerMetric(selection: MetricsSelection(id: UUID()), name: "ElasticSearch")
|
||||
server.isConnecting = false
|
||||
server.status = ""
|
||||
server.point = MetricsPoint(
|
||||
date: Date(), cpu: 94, memory: 65, memoryUsed: 2_400_000_000, memoryTotal: 4_000_000_000,
|
||||
diskRead: 89_000_000, diskWrite: 161_000, networkReceive: 32_000, networkSend: 18_000
|
||||
)
|
||||
server.gpuState = .unavailable
|
||||
for language in ["en", "zh-Hans", "zh-Hant"] {
|
||||
for dark in [false, true] {
|
||||
let content = ServerMetricsCard(
|
||||
server: server, model: model, compact: true, inspect: {}, openTerminal: {}
|
||||
)
|
||||
.frame(width: 350).padding(14)
|
||||
.background(TermindColor.windowSurface)
|
||||
.environment(\.locale, Locale(identifier: language))
|
||||
.environment(\.colorScheme, dark ? .dark : .light)
|
||||
let renderer = ImageRenderer(content: content)
|
||||
renderer.scale = 2
|
||||
var image: NSImage?
|
||||
NSAppearance(named: dark ? .darkAqua : .aqua)?.performAsCurrentDrawingAppearance {
|
||||
image = renderer.nsImage
|
||||
}
|
||||
let bitmap = try #require(image?.tiffRepresentation.flatMap { NSBitmapImageRep(data: $0) })
|
||||
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
||||
#expect(bitmap.pixelsWide >= 700)
|
||||
try png.write(
|
||||
to: FileManager.default.temporaryDirectory.appending(
|
||||
path: "termind-metrics-\(language)-\(dark ? "dark" : "light").png"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class PreviewMetricsPreferences: MetricsConfigurationStoring {
|
||||
|
||||
@@ -98,15 +98,13 @@ struct OnboardingControllerTests {
|
||||
controller.preview(.commandGenerator)
|
||||
controller.consider(window: window, context: OnboardingContext())
|
||||
guard
|
||||
case .waitingForContext(let reason) = controller.developerStatus(
|
||||
case .waitingForContext = controller.developerStatus(
|
||||
for: .commandGenerator
|
||||
)
|
||||
else {
|
||||
Issue.record("Expected Preview to wait for a shell context")
|
||||
return
|
||||
}
|
||||
#expect(reason.contains("supported SSH shell"))
|
||||
|
||||
controller.consider(
|
||||
window: window,
|
||||
context: OnboardingContext(canGenerateCommand: true)
|
||||
|
||||
@@ -171,30 +171,6 @@ struct SSHImportTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func importPreviewKeepsItemsAsDirectFormRows() throws {
|
||||
let sourceRoot = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent()
|
||||
let source = try String(
|
||||
contentsOf: sourceRoot.appendingPathComponent("TFM/Features/SSHImport/SSHImportView.swift"),
|
||||
encoding: .utf8)
|
||||
|
||||
#expect(source.contains("Form {"))
|
||||
#expect(source.contains(".formStyle(.grouped)"))
|
||||
#expect(source.contains("ForEach(model.items.filter"))
|
||||
let tags = try #require(source.range(of: "TagField(tags: $model.hostTags"))
|
||||
let storage = try #require(source.range(of: "Picker(\"Store in\", selection: $model.storage)"))
|
||||
let color = try #require(source.range(of: "ColorTagPicker(selection: $model.importColor"))
|
||||
let preview = try #require(source.range(of: "if !model.items.isEmpty"))
|
||||
#expect(storage.lowerBound < color.lowerBound)
|
||||
#expect(color.lowerBound < tags.lowerBound)
|
||||
#expect(tags.lowerBound < preview.lowerBound)
|
||||
#expect(!source.contains("hostColor"))
|
||||
#expect(!source.contains("keyColor"))
|
||||
#expect(!source.contains("identityColor"))
|
||||
#expect(!source.contains("Store password in"))
|
||||
#expect(!source.contains("private func categorySection"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func droppedSourcesUseTheSameTypeValidationAsTheFilePicker() async throws {
|
||||
let url = try root()
|
||||
|
||||
@@ -119,7 +119,6 @@ struct SerialFeatureTests {
|
||||
window.select(workspace.id)
|
||||
|
||||
#expect(pane.kind == .serialSession)
|
||||
#expect(pane.symbol == "cable.connector")
|
||||
#expect(pane.serialSession?.title == "USB UART")
|
||||
#expect(window.sessionPanes.map(\.pane.id) == [pane.id])
|
||||
#expect(window.serialSessionPanes.map(\.pane.id) == [pane.id])
|
||||
|
||||
@@ -9,12 +9,6 @@ import Testing
|
||||
@testable import Termind
|
||||
|
||||
struct TerminalThemeTests {
|
||||
@Test
|
||||
func defaultThemeBackgroundsMatchMacOSWindowBackgrounds() {
|
||||
#expect(TermindTerminalPalette.termindLight.background.hex == "FFFFFF")
|
||||
#expect(TermindTerminalPalette.termindDark.background.hex == "1E1E1E")
|
||||
}
|
||||
|
||||
@Test
|
||||
func unavailableThemesFallBackForBothAppearances() {
|
||||
let light = TermindTerminalThemeCatalog.background(
|
||||
|
||||
Reference in new issue
Block a user