diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 529ad3b..22ba86c 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; }; CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; }; CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */; }; + CA452BC0259FDDFE0072DFA4 /* Stub-0.0.0.plist in Resources */ = {isa = PBXBuildFile; fileRef = CA452BBE259FDDFE0072DFA4 /* Stub-0.0.0.plist */; }; + CA452BC1259FDDFE0072DFA4 /* Stub-version.plist in Resources */ = {isa = PBXBuildFile; fileRef = CA452BBF259FDDFE0072DFA4 /* Stub-version.plist */; }; CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */; }; CA61A6E0259835580008926E /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA61A6DF259835580008926E /* Xcode.swift */; }; CA735109257BF96D00EA9CF8 /* AttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA735108257BF96D00EA9CF8 /* AttributedText.swift */; }; @@ -65,6 +67,8 @@ CAC281C8259F97E100B8AB0B /* InstallationStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */; }; CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */; }; CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281D9259F985100B8AB0B /* InstallationStep.swift */; }; + CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */; }; + CAC281E7259FA45A00B8AB0B /* Environment+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */; }; CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD2E7A12449574E00113D76 /* XcodesApp.swift */; }; CAD2E7A42449574E00113D76 /* XcodeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD2E7A32449574E00113D76 /* XcodeListView.swift */; }; CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CAD2E7A52449575000113D76 /* Assets.xcassets */; }; @@ -128,6 +132,8 @@ CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = ""; }; CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = ""; }; + CA452BBE259FDDFE0072DFA4 /* Stub-0.0.0.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Stub-0.0.0.plist"; sourceTree = ""; }; + CA452BBF259FDDFE0072DFA4 /* Stub-version.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Stub-version.plist"; sourceTree = ""; }; CA538A0C255A4F1A00E64DD7 /* AppleAPI */ = {isa = PBXFileReference; lastKnownFileType = folder; name = AppleAPI; path = Xcodes/AppleAPI; sourceTree = ""; }; CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinCodeTextView.swift; sourceTree = ""; }; CA61A6DF259835580008926E /* Xcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xcode.swift; sourceTree = ""; }; @@ -187,6 +193,8 @@ CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepView.swift; sourceTree = ""; }; CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressIndicator.swift; sourceTree = ""; }; CAC281D9259F985100B8AB0B /* InstallationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStep.swift; sourceTree = ""; }; + CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+XcodesTests.swift"; sourceTree = ""; }; + CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+Mock.swift"; sourceTree = ""; }; CAD2E79E2449574E00113D76 /* Xcodes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Xcodes.app; sourceTree = BUILT_PRODUCTS_DIR; }; CAD2E7A12449574E00113D76 /* XcodesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodesApp.swift; sourceTree = ""; }; CAD2E7A32449574E00113D76 /* XcodeListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListView.swift; sourceTree = ""; }; @@ -251,6 +259,15 @@ path = Common; sourceTree = ""; }; + CA452BBD259FDDBF0072DFA4 /* Fixtures */ = { + isa = PBXGroup; + children = ( + CA452BBE259FDDFE0072DFA4 /* Stub-0.0.0.plist */, + CA452BBF259FDDFE0072DFA4 /* Stub-version.plist */, + ); + path = Fixtures; + sourceTree = ""; + }; CA538A12255A4F7C00E64DD7 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -438,6 +455,9 @@ CAD2E7B62449575100113D76 /* XcodesTests */ = { isa = PBXGroup; children = ( + CA452BBD259FDDBF0072DFA4 /* Fixtures */, + CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */, + CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */, CAD2E7B72449575100113D76 /* XcodesTests.swift */, CAD2E7B92449575100113D76 /* Info.plist */, ); @@ -576,6 +596,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CA452BC0259FDDFE0072DFA4 /* Stub-0.0.0.plist in Resources */, + CA452BC1259FDDFE0072DFA4 /* Stub-version.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -685,6 +707,8 @@ buildActionMask = 2147483647; files = ( CAD2E7B82449575100113D76 /* XcodesTests.swift in Sources */, + CAC281E7259FA45A00B8AB0B /* Environment+Mock.swift in Sources */, + CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 4e4c1fc..4dfa58d 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -9,7 +9,6 @@ import Version class AppState: ObservableObject { private let client = AppleAPI.Client() - private let helperClient = HelperClient() private var cancellables = Set() private var selectPublisher: AnyCancellable? private var uninstallPublisher: AnyCancellable? @@ -184,14 +183,14 @@ class AppState: ObservableObject { // MARK: - Helper func installHelper() { - HelperInstaller.install() + Current.helper.install() checkIfHelperIsInstalled() } private func checkIfHelperIsInstalled() { helperInstallState = .unknown - helperClient.checkIfLatestHelperIsInstalled() + Current.helper.checkIfLatestHelperIsInstalled() .receive(on: DispatchQueue.main) .sink( receiveValue: { installed in diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index 203c5f4..a5b2a1d 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -19,6 +19,7 @@ public struct Environment { public var keychain = Keychain() public var defaults = Defaults() public var date: () -> Date = Date.init + public var helper = Helper() } public var Current = Environment() @@ -91,8 +92,12 @@ private func _installedXcodes(destination: Path) -> [InstalledXcode] { public struct Network { private static let client = AppleAPI.Client() - public var dataTask: (URLRequest) -> URLSession.DataTaskPublisher = { AppleAPI.Current.network.session.dataTaskPublisher(for: $0) } - public func dataTask(with request: URLRequest) -> URLSession.DataTaskPublisher { + public var dataTask: (URLRequest) -> AnyPublisher = { + AppleAPI.Current.network.session.dataTaskPublisher(for: $0) + .mapError { $0 as Error } + .eraseToAnyPublisher() + } + public func dataTask(with request: URLRequest) -> AnyPublisher { dataTask(request) } @@ -152,3 +157,11 @@ public struct Defaults { removeObject(key) } } + +private let helperClient = HelperClient() +public struct Helper { + var install: () -> Void = HelperInstaller.install + var checkIfLatestHelperIsInstalled: () -> AnyPublisher = helperClient.checkIfLatestHelperIsInstalled + var getVersion: () -> AnyPublisher = helperClient.getVersion + var switchXcodePath: (_ absolutePath: String) -> AnyPublisher = helperClient.switchXcodePath +} diff --git a/XcodesTests/Bundle+XcodesTests.swift b/XcodesTests/Bundle+XcodesTests.swift new file mode 100644 index 0000000..d26d2c5 --- /dev/null +++ b/XcodesTests/Bundle+XcodesTests.swift @@ -0,0 +1,9 @@ +import Foundation + +extension Bundle { + static var xcodesTests: Bundle { + Bundle(for: BundleMember.self) + } +} + +private class BundleMember {} diff --git a/XcodesTests/Environment+Mock.swift b/XcodesTests/Environment+Mock.swift new file mode 100644 index 0000000..4227eb7 --- /dev/null +++ b/XcodesTests/Environment+Mock.swift @@ -0,0 +1,111 @@ +import Combine +import Foundation +@testable import Xcodes + +extension Environment { + static var mock = Environment( + shell: .mock, + files: .mock, + network: .mock, + logging: .mock, + keychain: .mock, + defaults: .mock, + date: Date.mock, + helper: .mock + ) +} + +extension Shell { + static var processOutputMock: ProcessOutput = (0, "", "") + + static var mock = Shell( + unxip: { _ in return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + spctlAssess: { _ in return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + codesignVerify: { _ in return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + buildVersion: { return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + xcodeBuildVersion: { _ in return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + getUserCacheDir: { return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + touchInstallCheck: { _, _, _ in return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() }, + xcodeSelectPrintPath: { return Just(Shell.processOutputMock).setFailureType(to: Error.self).eraseToAnyPublisher() } + ) +} + +extension Files { + static var mock = Files( + fileExistsAtPath: { _ in return true }, + moveItem: { _, _ in return }, + contentsAtPath: { path in + if path.contains("Info.plist") { + let url = Bundle.xcodesTests.url(forResource: "Stub-0.0.0.Info", withExtension: "plist")! + return try? Data(contentsOf: url) + } + else if path.contains("version.plist") { + let url = Bundle.xcodesTests.url(forResource: "Stub.version", withExtension: "plist")! + return try? Data(contentsOf: url) + } + else { + return nil + } + }, + removeItem: { _ in }, + trashItem: { _ in return URL(fileURLWithPath: "\(NSHomeDirectory())/.Trash") }, + createFile: { _, _, _ in return true }, + createDirectory: { _, _, _ in }, + installedXcodes: { _ in [] } + ) +} + +extension Network { + static var mock = Network( + dataTask: { url in + Just((data: Data(), response: HTTPURLResponse(url: url.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! as URLResponse)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + }, + downloadTask: { url, saveLocation, _ in + return ( + Progress(), + Just((saveLocation, HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil)!)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + ) + } + ) +} + +extension Logging { + static var mock = Logging( + log: { print($0) } + ) +} + +extension Keychain { + static var mock = Keychain( + getString: { _ in return nil }, + set: { _, _ in }, + remove: { _ in } + ) +} + +extension Defaults { + static var mock = Defaults( + string: { _ in nil }, + date: { _ in nil }, + setDate: { _, _ in }, + set: { _, _ in }, + removeObject: { _ in } + ) +} + +extension Date { + static var mock = { Date(timeIntervalSince1970: 1609479735) } +} + +extension Helper { + static var mock = Helper( + install: { }, + checkIfLatestHelperIsInstalled: { Just(false).eraseToAnyPublisher() }, + getVersion: { Just("").setFailureType(to: Error.self).eraseToAnyPublisher() }, + switchXcodePath: { _ in Just(()).setFailureType(to: Error.self).eraseToAnyPublisher() } + ) +} diff --git a/XcodesTests/Fixtures/Stub-0.0.0.plist b/XcodesTests/Fixtures/Stub-0.0.0.plist new file mode 100644 index 0000000..031af12 --- /dev/null +++ b/XcodesTests/Fixtures/Stub-0.0.0.plist @@ -0,0 +1,10 @@ + + + + + CFBundleIdentifier + com.apple.dt.Xcode + CFBundleShortVersionString + 0.0.0 + + diff --git a/XcodesTests/Fixtures/Stub-version.plist b/XcodesTests/Fixtures/Stub-version.plist new file mode 100644 index 0000000..8070792 --- /dev/null +++ b/XcodesTests/Fixtures/Stub-version.plist @@ -0,0 +1,8 @@ + + + + + ProductBuildVersion + ABC123 + +