diff --git a/AGENTS.md b/AGENTS.md index 376044c0..b87dcda5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,11 +52,13 @@ Follow `.swift-format`: four spaces and 120 columns. Use PascalCase types, camel Use Swift Testing (`@Suite`, `@Test`, `#expect`) in the three test directories. Add regression coverage and run the default test suite before submission. Web changes must pass `npm run build`; verify English and `/zh/` routes visually. +Assert observable behavior and regressions. Avoid assertions that only repeat fixture values, compare identical constructions, or impose arbitrary PNG byte-count thresholds. Keep screenshot export helpers opt-in for visual review. + Test SwiftData migrations by opening historical stores and verifying retained data after reopening. Do not assert that the latest schema equals a hard-coded version number; adding a migration must not invalidate unrelated tests. macOS tests that present windows or activate the app must use `.requiresVisibleWindow` on their suite or individual test. They are skipped by default; keep this opt-in off for routine verification. To opt in from the command line, prefix the test command with `TEST_RUNNER_TERMIND_WINDOW_TESTS=1` (Xcode forwards it as `TERMIND_WINDOW_TESTS=1` to the test host), preferably with `-only-testing:TFMTests/` to limit the run. In Xcode, set `TERMIND_WINDOW_TESTS=1` in the test action's environment. Screenshot tests with existing dedicated opt-in variables keep those gates. Offscreen layout and logic tests remain enabled by default. -StoreKit tests require manual authorization and are also skipped by default. Run them explicitly with `TERMIND_STOREKIT_TESTS=1`, preferably with `-only-testing:TFMTests/StoreKitTrialTests`. +Commerce access and paywall-state behavior are covered by `RevenueCatCommerceTests`, `ProPaywallStateTests`, and feature-access tests. `StoreKit/Termind.storekit` is a manual development fixture; live products, trial eligibility, and purchase/restore behavior require RevenueCat and App Store Sandbox validation. Mobile key-window integration suites are skipped by default because they share UIKit's process-wide presentation state. Run them explicitly with `TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1` (Xcode forwards it as `TERMIND_MOBILE_UI_TESTS=1`), `-parallel-testing-enabled NO`, and narrow `-only-testing:TFITests/` filters. diff --git a/FrameworkTests/RevenueCatCommerceTests.swift b/FrameworkTests/RevenueCatCommerceTests.swift index 071ce6b7..2ea9ec77 100644 --- a/FrameworkTests/RevenueCatCommerceTests.swift +++ b/FrameworkTests/RevenueCatCommerceTests.swift @@ -32,7 +32,7 @@ struct RevenueCatCommerceTests { @Test func onlyTheConfiguredEntitlementUnlocksPro() throws { let info = try customerInfo(entitlementID: "another_entitlement", expiration: now.addingTimeInterval(60)) - #expect(resolve(info) == .free(trialAvailable: false)) + #expect(resolve(info) == .free) } @Test @@ -85,7 +85,7 @@ struct RevenueCatCommerceTests { #expect(resolve(try customerInfo(expiration: nil, verification: 2)) == .verificationFailed) } - @Test(arguments: [EntitlementStatus.purchased(.purchased), .free(trialAvailable: false)]) + @Test(arguments: [EntitlementStatus.purchased(.purchased), .free]) func remotePaywallCompletionUpdatesAccessWithoutClosingItsAccountVariant(status: EntitlementStatus) async { let service = FakeService() let controller = ProAccessController(purchaseService: service, observeUpdates: false) @@ -97,7 +97,7 @@ struct RevenueCatCommerceTests { #expect(controller.isPaywallPresented) #expect(!controller.isProcessing) - service.status = .free(trialAvailable: false) + service.status = .free await controller.refresh() #expect(!controller.hasProAccess) #expect(controller.isPaywallPresented) @@ -111,7 +111,7 @@ struct RevenueCatCommerceTests { service.continuation.yield(.purchased(.purchased)) for _ in 0..<100 where !controller.hasProAccess { await Task.yield() } #expect(controller.hasProAccess) - service.continuation.yield(.free(trialAvailable: false)) + service.continuation.yield(.free) for _ in 0..<100 where controller.hasProAccess { await Task.yield() } #expect(!controller.hasProAccess) } @@ -127,7 +127,7 @@ struct RevenueCatCommerceTests { await controller.refresh() service.status = .purchased(.familyShared) await controller.refresh() - service.status = .free(trialAvailable: false) + service.status = .free await controller.refresh() #expect(observed == [true, false]) #expect(!controller.isPaywallPresented) @@ -145,7 +145,7 @@ struct RevenueCatCommerceTests { service.continuation.yield(.purchased(.purchased)) for _ in 0..<100 where !controller.hasProAccess { await Task.yield() } #expect(controller.hasProAccess) - service.refreshContinuation?.resume(returning: .free(trialAvailable: false)) + service.refreshContinuation?.resume(returning: .free) service.refreshContinuation = nil await refresh.value #expect(controller.hasProAccess) @@ -170,7 +170,7 @@ struct RevenueCatCommerceTests { let newerResponse = try #require(service.refreshContinuation) service.refreshContinuation = nil - olderResponse.resume(returning: .free(trialAvailable: false)) + olderResponse.resume(returning: .free) await olderRefresh.value #expect(controller.hasProAccess) newerResponse.resume(returning: .purchased(.familyShared)) @@ -267,7 +267,7 @@ struct RevenueCatCommerceTests { private final class FakeService: ProPurchaseService { let updates: AsyncStream let continuation: AsyncStream.Continuation - var status: EntitlementStatus = .free(trialAvailable: false) + var status: EntitlementStatus = .free var refreshes = 0 var refreshFails = false var suspendsRefresh = false diff --git a/Frameworks/Resources/Localizable.xcstrings b/Frameworks/Resources/Localizable.xcstrings index 8ae7bc0d..171f047a 100644 --- a/Frameworks/Resources/Localizable.xcstrings +++ b/Frameworks/Resources/Localizable.xcstrings @@ -211,57 +211,6 @@ } } }, - "%@ every %@. Auto-renews until canceled." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@每%2$@。除非取消,否则会自动续订。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$@每%2$@。除非取消,否則會自動續訂。" - } - } - } - }, - "%@ free" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "免费%@" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "免費%@" - } - } - } - }, - "%@ free, then %@ per %@. Auto-renews until canceled." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "免费%1$@,之后每%3$@收费%2$@。除非取消,否则会自动续订。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "免費%1$@,之後每%3$@收費%2$@。除非取消,否則會自動續訂。" - } - } - } - }, "%@ on %@" : { "extractionState" : "manual", "localizations" : { @@ -285,23 +234,6 @@ } } }, - "%@ per %@. Auto-renews until canceled." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "每%2$@收费%1$@。除非取消,否则会自动续订。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "每%2$@收費%1$@。除非取消,否則會自動續訂。" - } - } - } - }, "%@ provider value" : { "extractionState" : "manual", "localizations" : { @@ -2042,25 +1974,6 @@ } } }, - "AI provider usage and charges are separate from Termind Pro." : { - "comment" : "A description of the difference between Termind Pro and the AI provider.", - "extractionState" : "manual", - "isCommentAutoGenerated" : true, - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "AI提供方的用量与费用不包含在Termind Pro中。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "AI供應商的用量與費用不包含在Termind Pro中。" - } - } - } - }, "API Key" : { "extractionState" : "manual", "localizations" : { @@ -2131,23 +2044,6 @@ } } }, - "Access Ends" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "权益结束时间" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "權益結束時間" - } - } - } - }, "Action" : { "extractionState" : "manual", "localizations" : { @@ -3359,23 +3255,6 @@ } } }, - "Annual · Family Shared" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "年度订阅·家庭共享" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "年度訂閱·家庭共享" - } - } - } - }, "Another MCP server already uses this ID." : { "extractionState" : "manual", "localizations" : { @@ -4574,40 +4453,6 @@ } } }, - "Buy Lifetime Access" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "购买永久版" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "購買永久版" - } - } - } - }, - "Buy Lifetime Access — %@" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "购买永久版—%@" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "購買永久版—%@" - } - } - } - }, "Buy Termind Pro" : { "comment" : "A button that purchases Termind Pro.", "extractionState" : "manual", @@ -9455,25 +9300,6 @@ } } }, - "Development Access" : { - "comment" : "A label for a user with access to all features.", - "extractionState" : "manual", - "isCommentAutoGenerated" : true, - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "开发环境权限" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "開發環境權限" - } - } - } - }, "Device" : { "extractionState" : "manual", "localizations" : { @@ -11505,23 +11331,6 @@ } } }, - "Expired" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "过期时间" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "過期時間" - } - } - } - }, "Export" : { "extractionState" : "manual", "localizations" : { @@ -12593,23 +12402,6 @@ } } }, - "Free for %@, then %@ every %@. Auto-renews until canceled." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "免费%1$@,之后每%3$@收费%2$@。除非取消,否则会自动续订。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "免費%1$@,之後每%3$@收費%2$@。除非取消,否則會自動續訂。" - } - } - } - }, "Free includes up to 3 monitoring cards. Upgrade to Pro for more." : { "extractionState" : "manual", "localizations" : { @@ -12984,23 +12776,6 @@ } } }, - "Grace Period Ends" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "宽限期结束时间" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "寬限期結束時間" - } - } - } - }, "Green" : { "extractionState" : "manual", "localizations" : { @@ -18068,25 +17843,6 @@ } } }, - "No subscription or automatic renewal" : { - "comment" : "A description of a user's subscription status.", - "extractionState" : "manual", - "isCommentAutoGenerated" : true, - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "无订阅或自动续费" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "無訂閱或自動續費" - } - } - } - }, "No supported private key in this file." : { "extractionState" : "manual", "localizations" : { @@ -18663,23 +18419,6 @@ } } }, - "One payment. No subscription or automatic renewal." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "一次付费,无订阅或自动续订。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "一次付費,無訂閱或自動續訂。" - } - } - } - }, "One purchase for AI, more monitored servers, remote MCP tools, and iCloud sync." : { "comment" : "A summary of Termind Pro features available on iOS.", "extractionState" : "manual", @@ -21899,23 +21638,6 @@ } } }, - "Received text is not valid %@." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "收到的文本不是有效的%@。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "收到的文字不是有效的%@。" - } - } - } - }, "Recent Workspaces" : { "extractionState" : "manual", "localizations" : { @@ -22667,23 +22389,6 @@ } } }, - "Renews" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "续订时间" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "續訂時間" - } - } - } - }, "Reopen Closed Workspace" : { "extractionState" : "manual", "localizations" : { @@ -26447,23 +26152,6 @@ } } }, - "Start Free Trial" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "开始免费试用" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "開始免費試用" - } - } - } - }, "Start a Conversation" : { "extractionState" : "manual", "localizations" : { @@ -26957,40 +26645,6 @@ } } }, - "Subscribe Annually" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "订阅年度方案" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "訂閱年度方案" - } - } - } - }, - "Subscribe Annually — %@" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "订阅年度方案—%@" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "訂閱年度方案—%@" - } - } - } - }, "Succeeded · %@" : { "extractionState" : "manual", "localizations" : { @@ -27683,42 +27337,6 @@ } } }, - "Text cannot be represented using %@." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "文本无法使用%@表示。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "文字無法使用%@表示。" - } - } - } - }, - "The App Store is temporarily unavailable. You can keep using already unlocked features." : { - "comment" : "Error message displayed when the App Store is temporarily unavailable.", - "extractionState" : "manual", - "isCommentAutoGenerated" : true, - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "App Store暂时不可用,已经解锁的功能仍可继续使用。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "App Store暫時無法使用,已經解鎖的功能仍可繼續使用。" - } - } - } - }, "The Chat model has no available credential." : { "extractionState" : "manual", "localizations" : { @@ -29274,25 +28892,6 @@ } } }, - "The purchase is waiting for App Store approval." : { - "comment" : "Message displayed when a purchase is pending approval.", - "extractionState" : "manual", - "isCommentAutoGenerated" : true, - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "购买正在等待App Store批准。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "購買正在等待App Store核准。" - } - } - } - }, "The remote file changed or could not be verified. Replace it with your saved local changes?" : { "extractionState" : "manual", "localizations" : { @@ -31610,25 +31209,6 @@ } } }, - "Trial Ends" : { - "comment" : "A label that displays the date when a user's trial of Termind Pro will end.", - "extractionState" : "manual", - "isCommentAutoGenerated" : true, - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "试用结束时间" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "試用結束時間" - } - } - } - }, "Trust and Continue" : { "extractionState" : "manual", "localizations" : { @@ -34745,23 +34325,6 @@ } } }, - "day" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "天" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "天" - } - } - } - }, "delete" : { "extractionState" : "manual", "localizations" : { @@ -35153,23 +34716,6 @@ } } }, - "month" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "月" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "月" - } - } - } - }, "move" : { "extractionState" : "manual", "localizations" : { @@ -35696,23 +35242,6 @@ } } }, - "week" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "周" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "週" - } - } - } - }, "what to emphasize" : { "extractionState" : "manual", "localizations" : { @@ -35815,23 +35344,6 @@ } } }, - "year" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "年" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "年" - } - } - } - }, "~" : { "extractionState" : "manual", "localizations" : { diff --git a/Frameworks/TermindDomain/Commerce/ProAccessTypes.swift b/Frameworks/TermindDomain/Commerce/ProAccessTypes.swift index 98ff71a5..eb449216 100644 --- a/Frameworks/TermindDomain/Commerce/ProAccessTypes.swift +++ b/Frameworks/TermindDomain/Commerce/ProAccessTypes.swift @@ -15,11 +15,6 @@ public nonisolated enum ProOwnership: Equatable, Sendable { case familyShared } -public nonisolated enum ProPurchasePlan: Hashable, Sendable { - case annual - case lifetime -} - public nonisolated enum ProSubscriptionPhase: Equatable, Sendable { case introductoryTrial case subscribed @@ -57,7 +52,7 @@ public nonisolated struct ProSubscriptionStatus: Equatable, Sendable { public nonisolated enum EntitlementStatus: Equatable, Sendable { case loading - case free(trialAvailable: Bool) + case free case subscription(ProSubscriptionStatus) case purchased(ProOwnership) case verificationFailed @@ -70,12 +65,4 @@ public nonisolated enum EntitlementStatus: Equatable, Sendable { default: false } } - - public var canPurchase: Bool { - switch self { - case .purchased, .unrestrictedForTesting: false - case .subscription(let subscription): !subscription.phase.hasProAccess - default: true - } - } } diff --git a/Frameworks/TermindInfrastructure/Commerce/RevenueCatEntitlementResolver.swift b/Frameworks/TermindInfrastructure/Commerce/RevenueCatEntitlementResolver.swift index d4ed11cc..1a3844a6 100644 --- a/Frameworks/TermindInfrastructure/Commerce/RevenueCatEntitlementResolver.swift +++ b/Frameworks/TermindInfrastructure/Commerce/RevenueCatEntitlementResolver.swift @@ -5,11 +5,11 @@ import TermindDomain nonisolated enum RevenueCatEntitlementResolver { static func resolve(_ info: CustomerInfo, entitlementID: String, now: Date) -> EntitlementStatus { guard info.entitlements.verification != .failed else { return .verificationFailed } - guard let entitlement = info.entitlements.all[entitlementID] else { return .free(trialAvailable: false) } + guard let entitlement = info.entitlements.all[entitlementID] else { return .free } guard entitlement.verification != .failed else { return .verificationFailed } let ownership: ProOwnership = entitlement.ownershipType == .familyShared ? .familyShared : .purchased if entitlement.expirationDate == nil { - return entitlement.isActive ? .purchased(ownership) : .free(trialAvailable: false) + return entitlement.isActive ? .purchased(ownership) : .free } let subscription = info.subscriptionsByProductIdentifier[entitlement.productIdentifier] let expiration = entitlement.expirationDate diff --git a/Frameworks/TermindPresentation/ConnectionDecisionForms.swift b/Frameworks/TermindPresentation/ConnectionDecisionForms.swift index b46526e1..f01773f4 100644 --- a/Frameworks/TermindPresentation/ConnectionDecisionForms.swift +++ b/Frameworks/TermindPresentation/ConnectionDecisionForms.swift @@ -84,11 +84,6 @@ public struct SSHAuthenticationForm: View { } } - private var needsPasswordEntry: Bool { - guard let selectedIdentity else { return true } - return library.passwordAvailability(for: selectedIdentity) != .available - } - private var title: LocalizedStringResource { if needsPassphrase { return "Passphrase required" } if prompt.isProxy { return "Proxy authentication required" } diff --git a/Frameworks/TermindPresentation/Strings/Settings.swift b/Frameworks/TermindPresentation/Strings/Settings.swift index ab91663e..2d78697c 100644 --- a/Frameworks/TermindPresentation/Strings/Settings.swift +++ b/Frameworks/TermindPresentation/Strings/Settings.swift @@ -26,10 +26,6 @@ extension AppStrings { "Abilities" } - public static var accessEnds: LocalizedStringResource { - "Access Ends" - } - public static func add(_ count: Int) -> LocalizedStringResource { "Add \(count)" } @@ -78,14 +74,6 @@ extension AppStrings { "AI" } - public static var aiProviderUsageAndChargesAreSeparateFromTermindPro: LocalizedStringResource { - "AI provider usage and charges are separate from Termind Pro." - } - - public static var annualFamilyShared: LocalizedStringResource { - "Annual · Family Shared" - } - public static var anotherMcpServerAlreadyUsesThisId: LocalizedStringResource { "Another MCP server already uses this ID." } @@ -250,10 +238,6 @@ extension AppStrings { "Description" } - public static var developmentAccess: LocalizedStringResource { - "Development Access" - } - public static var disabled: LocalizedStringResource { "Disabled" } @@ -306,10 +290,6 @@ extension AppStrings { "Exact model ID, such as gpt-5.4" } - public static var expired: LocalizedStringResource { - "Expired" - } - public static var exposedTools: LocalizedStringResource { "Exposed Tools" } @@ -354,10 +334,6 @@ extension AppStrings { "Ghostty" } - public static var gracePeriodEnds: LocalizedStringResource { - "Grace Period Ends" - } - public static var headerName: LocalizedStringResource { "Header name" } @@ -658,10 +634,6 @@ extension AppStrings { "Removes the selected models. You can fetch them again." } - public static var renews: LocalizedStringResource { - "Renews" - } - public static var replaceKey: LocalizedStringResource { "Replace Key" } @@ -814,10 +786,6 @@ extension AppStrings { "Transport" } - public static var trialEnds: LocalizedStringResource { - "Trial Ends" - } - public static var url: LocalizedStringResource { "URL" } diff --git a/Frameworks/TermindRemoteInfrastructure/SSH/RemoteCharsetConverter.swift b/Frameworks/TermindRemoteInfrastructure/SSH/RemoteCharsetConverter.swift index c32a6f16..fd614623 100644 --- a/Frameworks/TermindRemoteInfrastructure/SSH/RemoteCharsetConverter.swift +++ b/Frameworks/TermindRemoteInfrastructure/SSH/RemoteCharsetConverter.swift @@ -17,27 +17,6 @@ nonisolated extension RemoteCharset { } } -public nonisolated struct RemoteCharsetConversionError: LocalizedError, Sendable { - public enum Direction: Sendable { case encoding, decoding } - - public var charset: RemoteCharset - public var direction: Direction - - public init(charset: RemoteCharset, direction: Direction) { - self.charset = charset - self.direction = direction - } - - public var errorDescription: String? { - switch direction { - case .encoding: - String(localized: "Text cannot be represented using \(charset.displayName).") - case .decoding: - String(localized: "Received text is not valid \(charset.displayName).") - } - } -} - /// A stateful iconv adapter that preserves unconvertible source bytes verbatim. /// Valid input is converted while malformed, incomplete, or unrepresentable bytes /// remain in place so Charset conversion never interrupts a byte stream. diff --git a/Frameworks/TermindRemoteInfrastructure/SSH/SFTPClient.swift b/Frameworks/TermindRemoteInfrastructure/SSH/SFTPClient.swift index c66682ab..826873f6 100644 --- a/Frameworks/TermindRemoteInfrastructure/SSH/SFTPClient.swift +++ b/Frameworks/TermindRemoteInfrastructure/SSH/SFTPClient.swift @@ -48,48 +48,6 @@ public nonisolated struct SFTPProgress: Sendable { } -nonisolated private struct SFTPProgressMeter { - public let total: Int64 - public let report: @Sendable (SFTPProgress) -> Void - public private(set) var completed: Int64 = 0 - private var sampledBytes: Int64 = 0 - private var sampledAt = ContinuousClock.now - - public nonisolated init( - total: Int64, offset: Int64 = 0, report: @escaping @Sendable (SFTPProgress) -> Void - ) { - self.total = total - self.completed = offset - self.report = report - } - - public nonisolated mutating func add(_ bytes: Int) { - completed += Int64(bytes) - sampledBytes += Int64(bytes) - let elapsed = sampledAt.duration(to: .now) - guard elapsed >= .milliseconds(250) else { return } - let components = elapsed.components - let seconds = max( - 0.001, - Double(components.seconds) + Double(components.attoseconds) / 1e18 - ) - report( - SFTPProgress( - completedBytes: completed, totalBytes: total, - bytesPerSecond: Int64(Double(sampledBytes) / seconds) - )) - sampledBytes = 0 - sampledAt = .now - } - - public nonisolated func finish() { - report( - SFTPProgress( - completedBytes: completed, totalBytes: total, bytesPerSecond: 0 - )) - } -} - /// One initialized SFTP subsystem running over an SSH channel. public nonisolated protocol SFTPChannelProtocol: RemoteFileSystem, Sendable { /// Opens another SFTP subsystem channel on the same authenticated SSH diff --git a/Frameworks/TermindRemoteInfrastructure/SSH/SSHAgentChannels.swift b/Frameworks/TermindRemoteInfrastructure/SSH/SSHAgentChannels.swift index f8dfa970..3687617e 100644 --- a/Frameworks/TermindRemoteInfrastructure/SSH/SSHAgentChannels.swift +++ b/Frameworks/TermindRemoteInfrastructure/SSH/SSHAgentChannels.swift @@ -18,7 +18,6 @@ nonisolated final class SSHAgentChannels: @unchecked Sendable { private let wake: @Sendable () -> Void private var channels: [Channel] = [] private var stopped = false - var hasChannels: Bool { !channels.isEmpty } init(provider: any SSHAgentForwarding, queue: DispatchQueue, wake: @escaping @Sendable () -> Void) { self.provider = provider diff --git a/Frameworks/TermindRemoteInfrastructure/Transfers/SFTPTransferContext.swift b/Frameworks/TermindRemoteInfrastructure/Transfers/SFTPTransferContext.swift index 10c685ba..ed289590 100644 --- a/Frameworks/TermindRemoteInfrastructure/Transfers/SFTPTransferContext.swift +++ b/Frameworks/TermindRemoteInfrastructure/Transfers/SFTPTransferContext.swift @@ -49,10 +49,6 @@ public final class SFTPTransferContext { if let primary { Task { await primary.disconnect() } } } - /// Admission is owned by `SFTPTransferCoordinator`; a channel consumes the - /// same slot as a connection, so fallback never changes capacity. - var canStartTransfer: Bool { true } - func configureClientFactory(_ factory: @escaping SFTPTransferCoordinator.ClientFactory) { clientFactory = factory } diff --git a/TFI/Features/Workspaces/MobileWorkspace.swift b/TFI/Features/Workspaces/MobileWorkspace.swift index 50b96663..80e103b4 100644 --- a/TFI/Features/Workspaces/MobileWorkspace.swift +++ b/TFI/Features/Workspaces/MobileWorkspace.swift @@ -350,16 +350,6 @@ final class MobileWorkspace: Identifiable { var hostKeyPrompt: SSHHostKeyPrompt? { connection.pendingHostKey } - private func preparation( - for host: Host, submissions: [UUID: SSHAuthenticationSubmission] - ) throws -> SSHConnectionPreparation { - try hosts.prepareSSHConnection( - for: host, - submissions: submissions, - temporaryHostKeys: temporaryKeys - ) - } - func submitAuthentication( identityID: UUID?, username: String, password: String?, passphrase: String?, keyID: UUID?, save: Bool ) { diff --git a/TFITests/MobileAISettingsRenderingTests.swift b/TFITests/MobileAISettingsRenderingTests.swift index 005e4868..458f7037 100644 --- a/TFITests/MobileAISettingsRenderingTests.swift +++ b/TFITests/MobileAISettingsRenderingTests.swift @@ -241,7 +241,6 @@ struct MobileAISettingsRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10000) let directory = FileManager.default.temporaryDirectory.appending(path: "ai-settings-screenshots") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" diff --git a/TFITests/MobileAssistantRenderingTests.swift b/TFITests/MobileAssistantRenderingTests.swift index 30113576..efc5ff3b 100644 --- a/TFITests/MobileAssistantRenderingTests.swift +++ b/TFITests/MobileAssistantRenderingTests.swift @@ -476,7 +476,6 @@ struct MobileAssistantRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10000) let directory = FileManager.default.temporaryDirectory.appending(path: "assistant-screenshots") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" diff --git a/TFITests/MobileCardRenderingTests.swift b/TFITests/MobileCardRenderingTests.swift index 9efa1f99..28b89318 100644 --- a/TFITests/MobileCardRenderingTests.swift +++ b/TFITests/MobileCardRenderingTests.swift @@ -218,7 +218,6 @@ struct MobileCardRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10000) let directory = FileManager.default.temporaryDirectory.appending(path: "card-screenshots") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" diff --git a/TFITests/MobileConnectionJourneyRenderingTests.swift b/TFITests/MobileConnectionJourneyRenderingTests.swift index d62bc6f4..640cb43a 100644 --- a/TFITests/MobileConnectionJourneyRenderingTests.swift +++ b/TFITests/MobileConnectionJourneyRenderingTests.swift @@ -110,7 +110,6 @@ struct MobileConnectionJourneyRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10_000) Attachment.record(data, named: "\(name)-\(language)-\(scheme == .dark ? "dark" : "light").png") let directory = FileManager.default.temporaryDirectory.appending(path: "journey-screenshots") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) diff --git a/TFITests/MobileHostIntegrationRenderingTests.swift b/TFITests/MobileHostIntegrationRenderingTests.swift index 5bba3c32..7070e468 100644 --- a/TFITests/MobileHostIntegrationRenderingTests.swift +++ b/TFITests/MobileHostIntegrationRenderingTests.swift @@ -68,7 +68,6 @@ struct MobileHostIntegrationRenderingTests { #expect(window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)) } let data = try #require(image.pngData()) - #expect(data.count > 5_000) Attachment.record(data, named: "host-integration-mobile-\(language).png") try data.write( to: FileManager.default.temporaryDirectory.appending(path: "host-integration-mobile-\(language).png")) diff --git a/TFITests/MobileIconRenderingTests.swift b/TFITests/MobileIconRenderingTests.swift index 94e02cd2..945d7642 100644 --- a/TFITests/MobileIconRenderingTests.swift +++ b/TFITests/MobileIconRenderingTests.swift @@ -268,7 +268,6 @@ struct MobileIconRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10000) let directory = FileManager.default.temporaryDirectory.appending(path: "icon-screenshots") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" diff --git a/TFITests/MobileKeyExportRenderingTests.swift b/TFITests/MobileKeyExportRenderingTests.swift index d12b6ca7..14e93fb2 100644 --- a/TFITests/MobileKeyExportRenderingTests.swift +++ b/TFITests/MobileKeyExportRenderingTests.swift @@ -77,7 +77,6 @@ struct MobileKeyExportRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10000) let directory = FileManager.default.temporaryDirectory.appending(path: "key-export-screenshots") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone" diff --git a/TFITests/MobileMarketingScreenshotTests.swift b/TFITests/MobileMarketingScreenshotTests.swift index fc1c9bce..57c70ead 100644 --- a/TFITests/MobileMarketingScreenshotTests.swift +++ b/TFITests/MobileMarketingScreenshotTests.swift @@ -266,7 +266,6 @@ struct MobileMarketingScreenshotTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 100_000) let outputDirectory = try #require(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first) .appending(path: "marketing-screenshots", directoryHint: .isDirectory) diff --git a/TFITests/MobileMemorySettingsRenderingTests.swift b/TFITests/MobileMemorySettingsRenderingTests.swift index ad3d818f..f7f58eda 100644 --- a/TFITests/MobileMemorySettingsRenderingTests.swift +++ b/TFITests/MobileMemorySettingsRenderingTests.swift @@ -46,7 +46,6 @@ struct MobileMemorySettingsRenderingTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10_000) Attachment.record(data, named: "mobile-memory-\(language)-\(enabled ? "enabled" : "disabled").png") await model.destroy() } diff --git a/TFITests/MobileMultiConnectionAccessTests.swift b/TFITests/MobileMultiConnectionAccessTests.swift index 11c826c5..129a76f4 100644 --- a/TFITests/MobileMultiConnectionAccessTests.swift +++ b/TFITests/MobileMultiConnectionAccessTests.swift @@ -18,7 +18,7 @@ struct MobileMultiConnectionAccessTests { service.status = .purchased(.purchased) await pro.refresh() #expect(app.transfers.strategy == strategy) - service.status = .free(trialAvailable: false) + service.status = .free await pro.refresh() #expect(app.transfers.strategy == .channelsOnly) #expect(app.sftpSettings.preferences.strategy == strategy) @@ -32,6 +32,6 @@ struct MobileMultiConnectionAccessTests { @MainActor private final class MobileConnectionPurchaseService: ProPurchaseService { let updates = AsyncStream { $0.finish() } - var status = EntitlementStatus.free(trialAvailable: false) + var status = EntitlementStatus.free func currentStatus() async throws -> EntitlementStatus { status } } diff --git a/TFITests/MobilePassphraseFormLayoutTests.swift b/TFITests/MobilePassphraseFormLayoutTests.swift index 11704cb0..0d69b916 100644 --- a/TFITests/MobilePassphraseFormLayoutTests.swift +++ b/TFITests/MobilePassphraseFormLayoutTests.swift @@ -64,7 +64,6 @@ struct MobilePassphraseFormLayoutTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10000) let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent( "termind-passphrase-ios-\(language)-\(showKeys ? "keys" : "form").png") try data.write(to: url) diff --git a/TFITests/MobileSFTPSettingsRenderingTests.swift b/TFITests/MobileSFTPSettingsRenderingTests.swift index 2304ee3d..779af798 100644 --- a/TFITests/MobileSFTPSettingsRenderingTests.swift +++ b/TFITests/MobileSFTPSettingsRenderingTests.swift @@ -36,7 +36,6 @@ struct MobileSFTPSettingsRenderingTests { } let data = try #require(image.pngData()) Attachment.record(data, named: "mobile-connection-settings-\(language)-\(value.globalLimit).png") - #expect(data.count > 10_000) } } } diff --git a/TFITests/MobileTerminalFontTests.swift b/TFITests/MobileTerminalFontTests.swift index c5faf9f1..33ba8a16 100644 --- a/TFITests/MobileTerminalFontTests.swift +++ b/TFITests/MobileTerminalFontTests.swift @@ -93,7 +93,6 @@ struct MobileTerminalFontTests { window.drawHierarchy(in: window.bounds, afterScreenUpdates: true) } let data = try #require(image.pngData()) - #expect(data.count > 10_000) Attachment.record(data, named: "\(name).png") } } diff --git a/TFITests/MobileTerminalHighlightingTests.swift b/TFITests/MobileTerminalHighlightingTests.swift index f0ded681..26f78042 100644 --- a/TFITests/MobileTerminalHighlightingTests.swift +++ b/TFITests/MobileTerminalHighlightingTests.swift @@ -45,7 +45,6 @@ struct MobileTerminalHighlightingTests { } let data = try #require(image.pngData()) Attachment.record(data, named: "mobile-keyword-\(name)-\(language)-\(Int(window.bounds.width)).png") - #expect(data.count > 20000) } } } diff --git a/TFITests/MobileTerminalThemeTests.swift b/TFITests/MobileTerminalThemeTests.swift index 3d147e95..3b11995f 100644 --- a/TFITests/MobileTerminalThemeTests.swift +++ b/TFITests/MobileTerminalThemeTests.swift @@ -38,7 +38,6 @@ struct MobileTerminalThemeTests { } let data = try #require(image.pngData()) Attachment.record(data, named: "mobile-themes-\(language)-\(isDark ? "dark" : "light").png") - #expect(data.count > 10_000) #expect(model.preferences == initial) let directory = FileManager.default.temporaryDirectory.appendingPathComponent("termind-theme-previews") try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) diff --git a/TFM/Application/Serial/SerialPortService.swift b/TFM/Application/Serial/SerialPortService.swift index 7ba8538e..84cb56f9 100644 --- a/TFM/Application/Serial/SerialPortService.swift +++ b/TFM/Application/Serial/SerialPortService.swift @@ -39,7 +39,6 @@ protocol SerialPortConnection: Sendable { } protocol SerialPortServing: Sendable { - func availablePorts() async -> [SerialPortDescriptor] func portUpdates() -> AsyncStream<[SerialPortDescriptor]> func open( _ configuration: SerialConnectionConfiguration, diff --git a/TFM/Features/Vault/VaultPageStates.swift b/TFM/Features/Vault/VaultPageStates.swift index 965b8831..15adf4ed 100644 --- a/TFM/Features/Vault/VaultPageStates.swift +++ b/TFM/Features/Vault/VaultPageStates.swift @@ -78,8 +78,6 @@ final class VaultSelectionState { set(items, for: page) } - func clear(_ page: VaultPage) { selections[page] = [] } - func prune(available: Set, pages: Set = Set(VaultPage.allCases)) { for page in pages { let valid = selection(for: page).filter(available.contains) diff --git a/TFM/Features/Workspaces/WindowModel.swift b/TFM/Features/Workspaces/WindowModel.swift index 02d24ad6..7d4a0757 100644 --- a/TFM/Features/Workspaces/WindowModel.swift +++ b/TFM/Features/Workspaces/WindowModel.swift @@ -174,10 +174,6 @@ final class WindowModel { vaultPages.selection.toggle(detail, on: page) } - func clearVaultSelection(for page: VaultPage) { - vaultPages.selection.clear(page) - } - /// Removes records deleted out from under a remembered selection. func pruneVaultSelections(in store: HostLibraryModel) { vaultPages.selection.prune(available: Self.availableVaultSelections(in: store)) @@ -1615,13 +1611,6 @@ nonisolated enum DragPayload: Hashable, Sendable { case workspace(UUID) case pane(UUID) case assistantConversation(UUID) - - nonisolated var isLayoutPayload: Bool { - switch self { - case .workspace, .pane: true - case .assistantConversation: false - } - } } // MARK: - Sidebar rows diff --git a/TFM/Infrastructure/Serial/SystemSerialPortService.swift b/TFM/Infrastructure/Serial/SystemSerialPortService.swift index 0a135b78..51667d3a 100644 --- a/TFM/Infrastructure/Serial/SystemSerialPortService.swift +++ b/TFM/Infrastructure/Serial/SystemSerialPortService.swift @@ -9,7 +9,6 @@ nonisolated final class SystemSerialPortService: SerialPortServing, @unchecked S private let openQueue = DispatchQueue( label: "com.akinokaede.termind.serial.open", attributes: .concurrent ) - func availablePorts() async -> [SerialPortDescriptor] { Self.scanPorts() } func portUpdates() -> AsyncStream<[SerialPortDescriptor]> { AsyncStream { continuation in diff --git a/TFMTests/CommerceConfigurationTests.swift b/TFMTests/CommerceConfigurationTests.swift deleted file mode 100644 index 60404e0f..00000000 --- a/TFMTests/CommerceConfigurationTests.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation -import TermindApplication -import TermindDomain -import TermindInfrastructure -import TermindPersistence -import TermindPresentation -import TermindRemoteInfrastructure -import Testing - -@testable import Termind - -struct CommerceConfigurationTests { - @Test - func localStoreKitFixtureContainsAnnualAndLifetimeProducts() throws { - let configurationFile = URL(fileURLWithPath: #filePath) - .deletingLastPathComponent() - .deletingLastPathComponent() - .appending(path: "StoreKit/Termind.storekit") - let data = try Data(contentsOf: configurationFile) - let configuration = try #require( - JSONSerialization.jsonObject(with: data) as? [String: Any] - ) - let products = try #require(configuration["products"] as? [[String: Any]]) - let productIDs = products.compactMap { $0["productID"] as? String } - - #expect(Set(productIDs) == ["com.akinokaede.termind.pro"]) - let pro = try #require( - products.first { $0["productID"] as? String == "com.akinokaede.termind.pro" }) - #expect(pro["displayPrice"] as? String == "9.99") - #expect(pro["familyShareable"] as? Bool == true) - - let groups = try #require(configuration["subscriptionGroups"] as? [[String: Any]]) - let group = try #require(groups.first) - let subscriptions = try #require(group["subscriptions"] as? [[String: Any]]) - let annual = try #require( - subscriptions.first { $0["productID"] as? String == "com.akinokaede.termind.pro.annual" }) - #expect(annual["displayPrice"] as? String == "3.99") - #expect(annual["familyShareable"] as? Bool == true) - #expect(annual["recurringSubscriptionPeriod"] as? String == "P1Y") - #expect(annual["type"] as? String == "RecurringSubscription") - let offer = try #require(annual["introductoryOffer"] as? [String: Any]) - #expect(offer["paymentMode"] as? String == "free") - #expect(offer["subscriptionPeriod"] as? String == "P2W") - } -} diff --git a/TFMTests/DiagnosisConsentRenderingTests.swift b/TFMTests/DiagnosisConsentRenderingTests.swift index a1e5924d..5b5126d5 100644 --- a/TFMTests/DiagnosisConsentRenderingTests.swift +++ b/TFMTests/DiagnosisConsentRenderingTests.swift @@ -33,7 +33,6 @@ struct DiagnosisConsentRenderingTests { let bitmap = try #require(host.view.bitmapImageRepForCachingDisplay(in: host.view.bounds)) host.view.cacheDisplay(in: host.view.bounds, to: bitmap) let png = try #require(bitmap.representation(using: .png, properties: [:])) - #expect(png.count > 5_000) #expect(controller.needsConsent) #expect(defaults.object(forKey: SettingsKey.diagnosisConsent) == nil) Attachment.record(png, named: "diagnosis-\(language)-\(Int(width)).png") diff --git a/TFMTests/ListViewMigrationTests.swift b/TFMTests/ListViewMigrationTests.swift index 00fdfeea..31462ed1 100644 --- a/TFMTests/ListViewMigrationTests.swift +++ b/TFMTests/ListViewMigrationTests.swift @@ -41,22 +41,6 @@ struct ListViewMigrationTests { #expect(normal != selected) } - /// Folding a section must not change the item's *value*, or ListViewKit - /// reconfigures the header's hosting view from inside the click that asked - /// for the fold — which crashed. Only the count is data; expansion is read - /// by the row itself. See `AssistantSectionHeaderRow`. - @Test - func foldingASectionDoesNotChangeItsListItem() { - let expanded = AssistantConversationListItem.section(.pinned, count: 3) - let collapsed = AssistantConversationListItem.section(.pinned, count: 3) - - #expect(expanded == collapsed) - #expect( - AssistantConversationListItem.Section.pinned.expansionKey - != AssistantConversationListItem.Section.chats.expansionKey - ) - } - @Test func assistantSectionIdentityIsSeparateFromConversationIdentity() { let pinned = AssistantConversationListItem.section(.pinned) diff --git a/TFMTests/MetricsLimitTests.swift b/TFMTests/MetricsLimitTests.swift index 62b011b2..88eafb87 100644 --- a/TFMTests/MetricsLimitTests.swift +++ b/TFMTests/MetricsLimitTests.swift @@ -161,7 +161,7 @@ struct MetricsLimitTests { @Test func productionWiringUsesTheExistingProPaywallAndIncludesSubscriptionAccess() { - let free = ProAccessController.testing(status: .free(trialAvailable: true)) + let free = ProAccessController.testing(status: .free) let app = AppModel(hosts: HostLibraryModel(), proAccess: free) defer { app.shutdownForTermination() } #expect(app.serverMetrics.remainingCardSlots == 3) diff --git a/TFMTests/ModelProviderRenderingTests.swift b/TFMTests/ModelProviderRenderingTests.swift index cbef051f..0219afa4 100644 --- a/TFMTests/ModelProviderRenderingTests.swift +++ b/TFMTests/ModelProviderRenderingTests.swift @@ -137,6 +137,5 @@ struct ModelProviderRenderingTests { controller.view.cacheDisplay(in: controller.view.bounds, to: bitmap) let data = try #require(bitmap.representation(using: .png, properties: [:])) Attachment.record(data, named: "keyless-\(name)-\(language).png") - #expect(data.count > 10_000) } } diff --git a/TFMTests/PresentationCopyTests.swift b/TFMTests/PresentationCopyTests.swift index 28d930da..cc761419 100644 --- a/TFMTests/PresentationCopyTests.swift +++ b/TFMTests/PresentationCopyTests.swift @@ -54,15 +54,6 @@ struct PresentationCopyTests { #expect(!message.contains("NSLanguage")) } - @Test(arguments: ["en", "zh-Hans", "zh-Hant"]) - func singleFileDeletionWarningDoesNotRepeatTheTitlePath(language: String) { - let locale = Locale(identifier: language) - let warning = AppLocalization.resolve(AppStrings.SFTP.recursiveDeletionWarning, locale: locale) - #expect(!warning.contains("/home/example/file.txt")) - #expect(!warning.contains("NSLanguage")) - #expect(!warning.isEmpty) - } - @Test(arguments: ["en", "zh-Hans", "zh-Hant"], [0, 1, 2]) func groupDeletionUsesTheRequestedLanguageAndPreservesPluralRules(language: String, count: Int) { let locale = Locale(identifier: language) diff --git a/TFMTests/ProAccessTests.swift b/TFMTests/ProAccessTests.swift index 247b6c8a..48c8e89d 100644 --- a/TFMTests/ProAccessTests.swift +++ b/TFMTests/ProAccessTests.swift @@ -13,7 +13,7 @@ struct ProAccessTests { @Test func entitlementStatesExposeOnlyRealAccess() { #expect(!EntitlementStatus.loading.hasProAccess) - #expect(!EntitlementStatus.free(trialAvailable: true).hasProAccess) + #expect(!EntitlementStatus.free.hasProAccess) #expect(!EntitlementStatus.verificationFailed.hasProAccess) #expect(EntitlementStatus.purchased(.purchased).hasProAccess) #expect(EntitlementStatus.purchased(.familyShared).hasProAccess) @@ -40,7 +40,7 @@ struct ProAccessTests { @Test(arguments: ProFeature.allCases) @MainActor func freeAccessRefusesEveryPaidFeatureBeforeAnyCallerCanProceed(feature: ProFeature) { - let access = ProAccessController.testing(status: .free(trialAvailable: true)) + let access = ProAccessController.testing(status: .free) #expect(!access.canUse(feature)) #expect(!access.require(feature)) #expect(access.isPaywallPresented) diff --git a/TFMTests/SFTPSettingsIntegrationTests.swift b/TFMTests/SFTPSettingsIntegrationTests.swift index 8470babf..886513a8 100644 --- a/TFMTests/SFTPSettingsIntegrationTests.swift +++ b/TFMTests/SFTPSettingsIntegrationTests.swift @@ -48,7 +48,7 @@ struct SFTPSettingsIntegrationTests { @Test func freeStartupAndSyncedPreferencesUseOnlyTheEffectiveStrategy() { let settings = SFTPSettingsModel() - let pro = ProAccessController.testing(status: .free(trialAvailable: true)) + let pro = ProAccessController.testing(status: .free) let app = AppModel(hosts: HostLibraryModel(), sftpSettings: settings, proAccess: pro) defer { app.shutdownForTermination() } #expect(settings.preferences.strategy == .smart) @@ -107,7 +107,6 @@ struct SFTPSettingsIntegrationTests { controller.view.cacheDisplay(in: controller.view.bounds, to: bitmap) let data = try #require(bitmap.representation(using: .png, properties: [:])) Attachment.record(data, named: "sftp-settings-\(language)-\(value.globalLimit).png") - #expect(data.count > 5_000) } } } @@ -115,6 +114,6 @@ struct SFTPSettingsIntegrationTests { @MainActor private final class ConnectionPurchaseService: ProPurchaseService { let updates = AsyncStream { $0.finish() } - var status = EntitlementStatus.free(trialAvailable: false) + var status = EntitlementStatus.free func currentStatus() async throws -> EntitlementStatus { status } } diff --git a/TFMTests/SSHAgentForwardingRenderingTests.swift b/TFMTests/SSHAgentForwardingRenderingTests.swift index 9de68098..5f1401cc 100644 --- a/TFMTests/SSHAgentForwardingRenderingTests.swift +++ b/TFMTests/SSHAgentForwardingRenderingTests.swift @@ -32,7 +32,6 @@ struct SSHAgentForwardingRenderingTests { let bitmap = try #require(controller.view.bitmapImageRepForCachingDisplay(in: controller.view.bounds)) controller.view.cacheDisplay(in: controller.view.bounds, to: bitmap) let data = try #require(bitmap.representation(using: .png, properties: [:])) - #expect(data.count > 5000) Attachment.record(data, named: "agent-forwarding-mac-\(language).png") try data.write( to: FileManager.default.temporaryDirectory.appending(path: "agent-forwarding-mac-\(language).png")) diff --git a/TFMTests/SerialFeatureTests.swift b/TFMTests/SerialFeatureTests.swift index e9bdaee8..c28568d8 100644 --- a/TFMTests/SerialFeatureTests.swift +++ b/TFMTests/SerialFeatureTests.swift @@ -15,7 +15,6 @@ nonisolated private struct InertSerialConnection: SerialPortConnection { } nonisolated private struct InertSerialService: SerialPortServing { - func availablePorts() async -> [SerialPortDescriptor] { [] } func portUpdates() -> AsyncStream<[SerialPortDescriptor]> { AsyncStream { $0.finish() } } @@ -32,7 +31,6 @@ nonisolated private struct InertSerialService: SerialPortServing { nonisolated private struct EmittingSerialService: SerialPortServing { let data: Data - func availablePorts() async -> [SerialPortDescriptor] { [] } func portUpdates() -> AsyncStream<[SerialPortDescriptor]> { AsyncStream { $0.finish() } } @@ -60,7 +58,6 @@ nonisolated private final class RecordingSerialConnection: SerialPortConnection, nonisolated private struct RecordingSerialService: SerialPortServing { let connection: RecordingSerialConnection - func availablePorts() async -> [SerialPortDescriptor] { [] } func portUpdates() -> AsyncStream<[SerialPortDescriptor]> { AsyncStream { $0.finish() } } diff --git a/TFMTests/SettingsNavigationTests.swift b/TFMTests/SettingsNavigationTests.swift index f8ca66ec..2bca89a3 100644 --- a/TFMTests/SettingsNavigationTests.swift +++ b/TFMTests/SettingsNavigationTests.swift @@ -13,7 +13,7 @@ import Testing struct SettingsNavigationTests { @Test( arguments: [ - EntitlementStatus.free(trialAvailable: true), .purchased(.purchased), .purchased(.familyShared), + EntitlementStatus.free, .purchased(.purchased), .purchased(.familyShared), .subscription( ProSubscriptionStatus( phase: .subscribed, expiresAt: .distantFuture, willAutoRenew: true, ownership: .purchased)), diff --git a/TFMTests/SheetQuitInteractionTests.swift b/TFMTests/SheetQuitInteractionTests.swift index e76f25da..65f86e9d 100644 --- a/TFMTests/SheetQuitInteractionTests.swift +++ b/TFMTests/SheetQuitInteractionTests.swift @@ -11,7 +11,7 @@ import Testing struct SheetQuitInteractionTests { @Test(arguments: ["connect", "batch", "pro"]) func nativeQuitWorksWithSheetFocusAndAfterCancellingAndReopening(_ surface: String) async throws { - let app = AppModel(hosts: HostLibraryModel(), proAccess: .testing(status: .free(trialAvailable: true))) + let app = AppModel(hosts: HostLibraryModel(), proAccess: .testing(status: .free)) let model = WindowModel() let root = RootView(window: model) .environment(app) diff --git a/TFMTests/SkillSettingsRenderingTests.swift b/TFMTests/SkillSettingsRenderingTests.swift index 7de58e67..1b39e695 100644 --- a/TFMTests/SkillSettingsRenderingTests.swift +++ b/TFMTests/SkillSettingsRenderingTests.swift @@ -33,6 +33,5 @@ import Testing controller.view.cacheDisplay(in: controller.view.bounds, to: bitmap) let data = try #require(bitmap.representation(using: .png, properties: [:])) Attachment.record(data, named: "ai-skills-memory-\(language).png") - #expect(data.count > 20_000) } } diff --git a/TFMTests/StoreKitTrialTests.swift b/TFMTests/StoreKitTrialTests.swift deleted file mode 100644 index aaf5f7fa..00000000 --- a/TFMTests/StoreKitTrialTests.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation -import StoreKit -import StoreKitTest -import Testing - -@Suite( - .enabled( - if: ProcessInfo.processInfo.environment["TERMIND_STOREKIT_TESTS"] == "1", - "Set TERMIND_STOREKIT_TESTS=1 to run StoreKit tests that require manual authorization"), - .serialized) -@MainActor -struct StoreKitTrialTests { - // This local fixture validates catalog metadata only. RevenueCatCommerceTests covers app access. - @Test - func fixtureHasTheUniversalAnnualAndLifetimeProductsWithAFreeIntroOffer() async throws { - let configuration = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent() - .appending(path: "StoreKit/Termind.storekit") - let session = try SKTestSession(contentsOf: configuration) - session.resetToDefaultState() - session.disableDialogs = true - defer { session.resetToDefaultState() } - - let products = try await Product.products( - for: ["com.akinokaede.termind.pro.annual", "com.akinokaede.termind.pro"]) - let annual = try #require(products.first { $0.id == "com.akinokaede.termind.pro.annual" }) - let lifetime = try #require(products.first { $0.id == "com.akinokaede.termind.pro" }) - #expect(annual.type == .autoRenewable) - #expect(annual.isFamilyShareable) - #expect(lifetime.type == .nonConsumable) - #expect(lifetime.isFamilyShareable) - let subscription = try #require(annual.subscription) - #expect(subscription.subscriptionPeriod.unit == .year) - #expect(subscription.subscriptionPeriod.value == 1) - let offer = try #require(subscription.introductoryOffer) - #expect(offer.paymentMode == .freeTrial) - #expect(offer.price == .zero) - #expect(offer.period.unit == .week) - #expect(offer.period.value * offer.periodCount == 2) - } -} diff --git a/TFMTests/TerminalSettingsRenderingTests.swift b/TFMTests/TerminalSettingsRenderingTests.swift index 7d33535d..4408a465 100644 --- a/TFMTests/TerminalSettingsRenderingTests.swift +++ b/TFMTests/TerminalSettingsRenderingTests.swift @@ -71,6 +71,5 @@ struct TerminalSettingsRenderingTests { view.cacheDisplay(in: view.bounds, to: bitmap) let data = try #require(bitmap.representation(using: .png, properties: [:])) Attachment.record(data, named: "\(name).png") - #expect(data.count > 5_000) } } diff --git a/TFMTests/WindowSidebarTests.swift b/TFMTests/WindowSidebarTests.swift index 639bf6de..02b806e4 100644 --- a/TFMTests/WindowSidebarTests.swift +++ b/TFMTests/WindowSidebarTests.swift @@ -15,7 +15,6 @@ nonisolated private struct SidebarTestSerialConnection: SerialPortConnection { } nonisolated private struct SidebarTestSerialService: SerialPortServing { - func availablePorts() async -> [SerialPortDescriptor] { [] } func portUpdates() -> AsyncStream<[SerialPortDescriptor]> { AsyncStream { $0.finish() } } diff --git a/TFMTests/WorkspaceAccessTests.swift b/TFMTests/WorkspaceAccessTests.swift index 4c9cfa93..b35aef6c 100644 --- a/TFMTests/WorkspaceAccessTests.swift +++ b/TFMTests/WorkspaceAccessTests.swift @@ -13,7 +13,7 @@ import struct TermindDomain.Host struct WorkspaceAccessTests { nonisolated static let nonProStatuses: [EntitlementStatus] = [ - .loading, .free(trialAvailable: true), .free(trialAvailable: false), .verificationFailed, + .loading, .free, .verificationFailed, ] + [ProSubscriptionPhase.billingRetry, .expired, .revoked].map { .subscription(