diff --git a/Sources/DeviceIdentificator/DeviceModel+Capabilities.swift b/Sources/DeviceIdentificator/DeviceModel+Capabilities.swift index f9afa65..1d3288e 100644 --- a/Sources/DeviceIdentificator/DeviceModel+Capabilities.swift +++ b/Sources/DeviceIdentificator/DeviceModel+Capabilities.swift @@ -46,8 +46,6 @@ public extension DeviceModel { .iPad(.air13InchM2Cellular), .iPad(.mini6Wifi), .iPad(.mini6Cellular), - .iPad(.mini7Wifi), - .iPad(.mini7Cellular), .iPad(.pro1_11inchWifi), .iPad(.pro1_11inchWifiTera), .iPad(.pro1_11inchCellular), @@ -135,4 +133,4 @@ public extension DeviceModel { return false } } -} \ No newline at end of file +} diff --git a/Sources/DeviceIdentificator/Models/AppleWatchModel+Name.swift b/Sources/DeviceIdentificator/Models/AppleWatchModel+Name.swift index 7f46dc5..0a0ace7 100644 --- a/Sources/DeviceIdentificator/Models/AppleWatchModel+Name.swift +++ b/Sources/DeviceIdentificator/Models/AppleWatchModel+Name.swift @@ -47,6 +47,15 @@ public extension DeviceModel.AppleWatchModel { case .SE2_44mmCellular: return "Apple Watch SE2 44mm" case .ultra: return "Apple Watch Ultra 1" case .ultra2: return "Apple Watch Ultra 2" + case .series10_42mmGPS: return "Apple Watch Series 10 42mm" + case .series10_46mmGPS: return "Apple Watch Series 10 46mm" + case .series10_42mmCellular: return "Apple Watch Series 10 42mm" + case .series10_46mmCellular: return "Apple Watch Series 10 46mm" + case .ultra3: return "Apple Watch Ultra 3" + case .series11_42mmGPS: return "Apple Watch Series 11 42mm" + case .series11_46mmGPS: return "Apple Watch Series 11 46mm" + case .series11_42mmCellular: return "Apple Watch Series 11 42mm" + case .series11_46mmCellular: return "Apple Watch Series 11 46mm" } } } diff --git a/Sources/DeviceIdentificator/Models/AppleWatchModel+Processor.swift b/Sources/DeviceIdentificator/Models/AppleWatchModel+Processor.swift index 4fd64e1..0e1ada5 100644 --- a/Sources/DeviceIdentificator/Models/AppleWatchModel+Processor.swift +++ b/Sources/DeviceIdentificator/Models/AppleWatchModel+Processor.swift @@ -37,6 +37,14 @@ public extension DeviceModel.AppleWatchModel { case .series9_41mmCellular: return .appleS9 case .series9_45mmGPS: return .appleS9 case .series9_45mmCellular: return .appleS9 + case .series10_42mmGPS: return .appleS10 + case .series10_42mmCellular: return .appleS10 + case .series10_46mmGPS: return .appleS10 + case .series10_46mmCellular: return .appleS10 + case .series11_42mmGPS: return .appleS10 + case .series11_42mmCellular: return .appleS10 + case .series11_46mmGPS: return .appleS10 + case .series11_46mmCellular: return .appleS10 case .SE_40mmGPS: return .appleS5 case .SE_40mmCellular: return .appleS5 case .SE_44mmGPS: return .appleS5 @@ -47,6 +55,7 @@ public extension DeviceModel.AppleWatchModel { case .SE2_44mmCellular: return .appleS8 case .ultra: return .appleS8 case .ultra2: return .appleS9 + case .ultra3: return .appleS10 } } } diff --git a/Sources/DeviceIdentificator/Models/AppleWatchModel.swift b/Sources/DeviceIdentificator/Models/AppleWatchModel.swift index ebca557..33ca2c4 100644 --- a/Sources/DeviceIdentificator/Models/AppleWatchModel.swift +++ b/Sources/DeviceIdentificator/Models/AppleWatchModel.swift @@ -46,5 +46,14 @@ public extension DeviceModel { case series9_41mmCellular = "Watch7,3" case series9_45mmCellular = "Watch7,4" case ultra2 = "Watch7,5" + case series10_42mmGPS = "Watch7,8" + case series10_46mmGPS = "Watch7,9" + case series10_42mmCellular = "Watch7,10" + case series10_46mmCellular = "Watch7,11" + case ultra3 = "Watch7,12" + case series11_42mmGPS = "Watch7,17" + case series11_46mmGPS = "Watch7,18" + case series11_42mmCellular = "Watch7,19" + case series11_46mmCellular = "Watch7,20" } } diff --git a/Sources/DeviceIdentificator/Models/IPadModel+Name.swift b/Sources/DeviceIdentificator/Models/IPadModel+Name.swift index 3bd68e7..0d8d074 100644 --- a/Sources/DeviceIdentificator/Models/IPadModel+Name.swift +++ b/Sources/DeviceIdentificator/Models/IPadModel+Name.swift @@ -28,6 +28,8 @@ public extension DeviceModel.IPadModel { case .gen9Cellular: return "iPad 9G (Cellular)" case .gen10Wifi: return "iPad 10G (Wifi)" case .gen10Cellular: return "iPad 10G (Cellular)" + case .gen11Wifi: return "iPad 11G (Wifi)" + case .gen11Cellular: return "iPad 11G (Cellular)" // Minis case .mini1Wifi: return "iPad Mini 1 (Wifi)" @@ -45,8 +47,8 @@ public extension DeviceModel.IPadModel { case .mini5Cellular: return "iPad Mini 5 (Cellular)" case .mini6Wifi: return "iPad Mini 6 (Wifi)" case .mini6Cellular: return "iPad Mini 6 (Cellular)" - case .mini7Wifi: return "iPad Mini 7 (Wifi)" - case .mini7Cellular: return "iPad Mini 7 (Cellular)" + case .miniA17ProWifi: return "iPad Mini A17 Pro (Wifi)" + case .miniA17ProCellular: return "iPad Mini A17 Pro (Cellular)" // Airs case .air1Wifi: return "iPad Air 1 (Wifi)" @@ -62,8 +64,12 @@ public extension DeviceModel.IPadModel { case .air5Cellular: return "iPad Air 5 (Cellular)" case .air11InchM2Wifi: return "iPad Air M2 11\" (Cellular)" case .air11InchM2Cellular: return "iPad Air M2 11\" (Cellular)" - case .air13InchM2Wifi: return "iPad Air M2 13\" (Cellular)" + case .air13InchM2Wifi: return "iPad Air M2 13\" (Wifi)" case .air13InchM2Cellular: return "iPad Air M2 13\" (Cellular)" + case .air11InchM3Wifi: return "iPad Air M3 11\" (Wifi)" + case .air11InchM3Cellular: return "iPad Air M3 11\" (Cellular)" + case .air13InchM3Wifi: return "iPad Air M3 13\" (Wifi)" + case .air13InchM3Cellular: return "iPad Air M3 13\" (Cellular)" // Pros case .pro1_9d7inchWifi: return "iPad Pro 1G 9.7\" (Wifi)" diff --git a/Sources/DeviceIdentificator/Models/IPadModel+Processor.swift b/Sources/DeviceIdentificator/Models/IPadModel+Processor.swift index 2634eaf..3511ef6 100644 --- a/Sources/DeviceIdentificator/Models/IPadModel+Processor.swift +++ b/Sources/DeviceIdentificator/Models/IPadModel+Processor.swift @@ -3,8 +3,54 @@ import Foundation public extension DeviceModel.IPadModel { var processor: DeviceModel.Processor { switch self { - default: - return .appleA10XFusion + // iPads + case .gen1Wifi, .gen1Cellular: return .appleA4 + case .gen2Wifi, .gen2GSM, .gen2CDMA, .gen2NewRev: return .appleA5 + case .gen3Wifi, .gen3CDMA, .gen3GSM: return .appleA5X + case .gen4Wifi, .gen4GSM, .gen4CDMA: return .appleA6X + case .gen5Wifi, .gen5Cellular: return .appleA9 + case .gen6Wifi, .gen6Cellular: return .appleA10Fusion + case .gen7Wifi, .gen7Cellular: return .appleA10Fusion + case .gen8Wifi, .gen8Cellular: return .appleA12Bionic + case .gen9Wifi, .gen9Cellular: return .appleA13Bionic + case .gen10Wifi, .gen10Cellular: return .appleA14Bionic + case .gen11Wifi, .gen11Cellular: return .appleA14Bionic // Assumed, based on iPad 10 + + // Minis + case .mini1Wifi, .mini1GSM, .mini1CDMA: return .appleA5 + case .mini2Wifi, .mini2GSMCDMA, .mini2China: return .appleA7 + case .mini3Wifi, .mini3GSMCDMA, .mini3China: return .appleA7 + case .mini4Wifi, .mini4Cellular: return .appleA8 + case .mini5Wifi, .mini5Cellular: return .appleA12Bionic + case .mini6Wifi, .mini6Cellular: return .appleA15Bionic + case .miniA17ProWifi, .miniA17ProCellular: return .appleA17Pro + + // Airs + case .air1Wifi, .air1GSMCDMA, .air1China: return .appleA7 + case .air2Wifi, .air2Cellular: return .appleA8X + case .air3Wifi, .air3Cellular: return .appleA12Bionic + case .air4Wifi, .air4Cellular: return .appleA14Bionic + case .air5Wifi, .air5Cellular: return .appleM1 + case .air11InchM2Wifi, .air11InchM2Cellular, + .air13InchM2Wifi, .air13InchM2Cellular: return .appleM2 + case .air11InchM3Wifi, .air11InchM3Cellular, + .air13InchM3Wifi, .air13InchM3Cellular: return .appleM3 + + // Pros + case .pro1_9d7inchWifi, .pro1_9d7inchCellular, + .pro1_12d9inchWifi, .pro1_12d9inchCellular: return .appleA9X + case .pro1_10d5inchWifi, .pro1_10d5inchCellular, + .pro2_12d9inchWifi, .pro2_12d9inchCellular: return .appleA10XFusion + case .pro1_11inchWifi, .pro1_11inchWifiTera, .pro1_11inchCellular, .pro1_11inchCellularTera, + .pro3_12d9inchWifi, .pro3_12d9inchWifiTera, .pro3_12d9inchCellular, .pro3_12d9inchCellularTera: return .appleA12XBionic + case .pro2_11inchWifi, .pro2_11inchCellular, + .pro4_12d9inchWifi, .pro4_12d9inchCellular: return .appleA12ZBionic + case .pro3_11inchWifi, .pro3_11inchWifiTera, .pro3_11inchCellular, .pro3_11inchCellularTera, + .pro5_12d9inchWifi, .pro5_12d9inchWifiTera, .pro5_12d9inchCellular, .pro5_12d9inchCellularTera: return .appleM1 + case .pro4_11inchWifi, .pro4_11inchCellular, + .pro6_12d9inchWifi, .pro6_12d9inchCellular: return .appleM2 + case .pro_11inchM4Wifi, .pro_11inchM4Cellular, + .pro_13inchM4Wifi, .pro_13inchM4Cellular: return .appleM4 } } } diff --git a/Sources/DeviceIdentificator/Models/IPadModel.swift b/Sources/DeviceIdentificator/Models/IPadModel.swift index bfc905f..4da8c53 100644 --- a/Sources/DeviceIdentificator/Models/IPadModel.swift +++ b/Sources/DeviceIdentificator/Models/IPadModel.swift @@ -27,6 +27,8 @@ public extension DeviceModel { case gen9Cellular = "iPad12,2" case gen10Wifi = "iPad13,18" case gen10Cellular = "iPad13,19" + case gen11Wifi = "iPad15,7" + case gen11Cellular = "iPad15,8" // Minis case mini1Wifi = "iPad2,5" @@ -44,8 +46,8 @@ public extension DeviceModel { case mini5Cellular = "iPad11,2" case mini6Wifi = "iPad14,1" case mini6Cellular = "iPad14,2" - case mini7Wifi = "iPad16,1" - case mini7Cellular = "iPad16,2" + case miniA17ProWifi = "iPad16,1" + case miniA17ProCellular = "iPad16,2" // Airs case air1Wifi = "iPad4,1" @@ -63,6 +65,10 @@ public extension DeviceModel { case air11InchM2Cellular = "iPad14,9" case air13InchM2Wifi = "iPad14,10" case air13InchM2Cellular = "iPad14,11" + case air11InchM3Wifi = "iPad15,3" + case air11InchM3Cellular = "iPad15,4" + case air13InchM3Wifi = "iPad15,5" + case air13InchM3Cellular = "iPad15,6" // Pros case pro1_9d7inchWifi = "iPad6,3" diff --git a/Sources/DeviceIdentificator/Models/IPhoneModel+Name.swift b/Sources/DeviceIdentificator/Models/IPhoneModel+Name.swift index 8eea7e9..b740e55 100644 --- a/Sources/DeviceIdentificator/Models/IPhoneModel+Name.swift +++ b/Sources/DeviceIdentificator/Models/IPhoneModel+Name.swift @@ -60,6 +60,10 @@ public extension DeviceModel.IPhoneModel { case .iPhone16Pro: return "iPhone 16 Pro" case .iPhone16ProMax: return "iPhone 16 Pro Max" case .iPhone16e: return "iPhone 16e" + case .iPhone17: return "iPhone 17" + case .iPhone17Pro: return "iPhone 17 Pro" + case .iPhone17ProMax: return "iPhone 17 Pro Max" + case .iPhoneAir: return "iPhone Air" } } } diff --git a/Sources/DeviceIdentificator/Models/IPhoneModel+Processor.swift b/Sources/DeviceIdentificator/Models/IPhoneModel+Processor.swift index ea5cf99..9ab58fe 100644 --- a/Sources/DeviceIdentificator/Models/IPhoneModel+Processor.swift +++ b/Sources/DeviceIdentificator/Models/IPhoneModel+Processor.swift @@ -60,6 +60,10 @@ public extension DeviceModel.IPhoneModel { case .iPhone16Pro: return .appleA18Pro case .iPhone16ProMax: return .appleA18Pro case .iPhone16e: return .appleA18 + case .iPhone17: return .appleA19 + case .iPhone17Pro: return .appleA19Pro + case .iPhone17ProMax: return .appleA19Pro + case .iPhoneAir: return .appleA19 } } } diff --git a/Sources/DeviceIdentificator/Models/IPhoneModel.swift b/Sources/DeviceIdentificator/Models/IPhoneModel.swift index 22daa01..246a733 100644 --- a/Sources/DeviceIdentificator/Models/IPhoneModel.swift +++ b/Sources/DeviceIdentificator/Models/IPhoneModel.swift @@ -59,5 +59,9 @@ public extension DeviceModel { case iPhone16Pro = "iPhone17,1" case iPhone16ProMax = "iPhone17,2" case iPhone16e = "iPhone17,5" + case iPhone17 = "iPhone18,3" + case iPhone17Pro = "iPhone18,1" + case iPhone17ProMax = "iPhone18,2" + case iPhoneAir = "iPhone18,4" } } diff --git a/Sources/DeviceIdentificator/Processor+Specs.swift b/Sources/DeviceIdentificator/Processor+Specs.swift index b731cda..122b646 100644 --- a/Sources/DeviceIdentificator/Processor+Specs.swift +++ b/Sources/DeviceIdentificator/Processor+Specs.swift @@ -3,15 +3,122 @@ import Foundation public extension DeviceModel.Processor { var architecture: String { switch self { - default: - return "arm64" // Not really, check: https://apple.fandom.com/wiki/List_of_Apple_processors + // Early 32-bit ARMv7 processors + case .APL0278, .APL0298, .APL2298, + .appleA4, .appleA5, .appleA5X: + return "armv7" + + // Custom 32-bit ARMv7s architecture by Apple + case .appleA6, .appleA6X: + return "armv7s" + + // First generation of 64-bit ARMv8-A processors + case .appleA7, .appleA8, .appleA8X, .appleA9, .appleA9X, + .appleA10Fusion, .appleA10XFusion, .appleA11Bionic: + return "arm64" + + // Processors with ARMv8.3-A (Pointer Authentication - arm64e) and newer + case .appleA12Bionic, .appleA12XBionic, .appleA12ZBionic, + .appleA13Bionic, .appleA14Bionic, .appleA15Bionic, + .appleA16Bionic, .appleA17Pro: + return "arm64e" + + // Future processors (estimated to be arm64e or a successor) + case .appleA18, .appleA18Pro, .appleA19, .appleA19Pro: + return "arm64e" // Estimation + + // Apple Silicon for Mac/iPad + case .appleM1, .appleM1Pro, .appleM1Max, .appleM1Ultra, + .appleM2, .appleM2Pro, .appleM2Max, .appleM2Ultra, + .appleM3, .appleM3Pro, .appleM3Max, + .appleM4, .appleM4Pro, .appleM4Max: + return "arm64e" + + // Vision Pro Co-processor + case .appleR1: + return "arm64" // Specific architecture not public, but it's an ARM-based co-processor. + + // Apple Watch processors + case .appleS1, .appleS1P, .appleS2: + return "armv7k" // 32-bit architecture for WatchOS + case .appleS3: + return "armv8-a" // 64-bit (32-bit compatibility mode) + case .appleS4, .appleS5, + .appleS6, .appleS7, .appleS8, + .appleS9, .appleS10: + return "arm64e" // 64-bit with Pointer Authentication + + // Very early 32-bit ARMv6 processor + case .APL0098: + return "armv6" } } - + + /// The year the processor was introduced. var introducedYear: Int? { switch self { - default: - return nil //Check the same list + // Initial Samsung processors + case .APL0098: return 2007 + case .APL0278: return 2008 + case .APL0298: return 2009 + case .APL2298: return 2010 + + // A-series + case .appleA4: return 2010 + case .appleA5: return 2011 + case .appleA5X: return 2012 + case .appleA6: return 2012 + case .appleA6X: return 2012 + case .appleA7: return 2013 + case .appleA8: return 2014 + case .appleA8X: return 2014 + case .appleA9: return 2015 + case .appleA9X: return 2015 + case .appleA10Fusion: return 2016 + case .appleA10XFusion: return 2017 + case .appleA11Bionic: return 2017 + case .appleA12Bionic: return 2018 + case .appleA12XBionic: return 2018 + case .appleA12ZBionic: return 2020 + case .appleA13Bionic: return 2019 + case .appleA14Bionic: return 2020 + case .appleA15Bionic: return 2021 + case .appleA16Bionic: return 2022 + case .appleA17Pro: return 2023 + case .appleA18, .appleA18Pro: return 2024 // Estimated + case .appleA19, .appleA19Pro: return 2025 // Estimated + + // M-series + case .appleM1: return 2020 + case .appleM1Pro: return 2021 + case .appleM1Max: return 2021 + case .appleM1Ultra: return 2022 + case .appleM2: return 2022 + case .appleM2Pro: return 2023 + case .appleM2Max: return 2023 + case .appleM2Ultra: return 2023 + case .appleM3: return 2023 + case .appleM3Pro: return 2023 + case .appleM3Max: return 2023 + case .appleM4: return 2024 + case .appleM4Pro: return 2024 // Estimated + case .appleM4Max: return 2024 // Estimated + + // R-series + case .appleR1: return 2024 + + // S-series + case .appleS1: return 2015 + case .appleS1P: return 2016 + case .appleS2: return 2016 + case .appleS3: return 2017 + case .appleS4: return 2018 + case .appleS5: return 2019 + case .appleS6: return 2020 + case .appleS7: return 2021 + case .appleS8: return 2022 + case .appleS9: return 2023 + case .appleS10: return 2024 // Estimated } } } diff --git a/Sources/DeviceIdentificator/Processor.swift b/Sources/DeviceIdentificator/Processor.swift index cfd075d..5ccdb10 100644 --- a/Sources/DeviceIdentificator/Processor.swift +++ b/Sources/DeviceIdentificator/Processor.swift @@ -50,6 +50,8 @@ public extension DeviceModel { case appleA17Pro case appleA18 case appleA18Pro + case appleA19 + case appleA19Pro // Vision PRO: case appleR1 @@ -65,6 +67,7 @@ public extension DeviceModel { case appleS7 case appleS8 case appleS9 + case appleS10 } } diff --git a/Tests/DeviceIdentificatorTests/AdditionalCoverageTests.swift b/Tests/DeviceIdentificatorTests/AdditionalCoverageTests.swift new file mode 100644 index 0000000..d2c278a --- /dev/null +++ b/Tests/DeviceIdentificatorTests/AdditionalCoverageTests.swift @@ -0,0 +1,75 @@ +import XCTest +@testable import DeviceIdentificator + +class AdditionalCoverageTests: XCTestCase { + + func test_description_is_name() { + DeviceModel.allCases.forEach { model in + XCTAssertEqual(model.description, model.name) + } + } + + func test_mac_catalyst_and_designed_for_ipad() { + let catalyst = DeviceModel.macCatalyst + XCTAssertEqual(catalyst.name, "Mac Catalyst") + XCTAssertNil(catalyst.deviceIdentifier) + XCTAssertEqual(catalyst.architecture, "arm64") + + let designedForIPad = DeviceModel.macDesignedForIpad + XCTAssertEqual(designedForIPad.name, "Mac Designed for iPad") + XCTAssertNil(designedForIPad.deviceIdentifier) + XCTAssertEqual(designedForIPad.architecture, "arm64") + } + + func test_unknown_model() { + let unknown = DeviceModel.unknown(model: "TestDevice1,1") + XCTAssertEqual(unknown.name, "Unknown device: TestDevice1,1") + XCTAssertEqual(unknown.deviceIdentifier, "TestDevice1,1") + XCTAssertNil(unknown.architecture) + XCTAssertFalse(unknown.hasRoundedDisplayCorners) + XCTAssertFalse(unknown.hasDynamicIsland) + } + + func test_allDevicesWithRoundedDisplayCorners() { + let devices = DeviceModel.allDevicesWithRoundedDisplayCorners + XCTAssertFalse(devices.isEmpty) + + // Verifica que todos los dispositivos en la lista realmente tengan la propiedad. + devices.forEach { + XCTAssertTrue($0.hasRoundedDisplayCorners) + } + + // Verifica que un dispositivo que no debería estar, no esté. + let iPhone8 = DeviceModel.iPhone(.iPhone8Global) + XCTAssertFalse(devices.contains(iPhone8)) + } + + func test_simpleName_strips_parentheses() { + let iPad = DeviceModel.iPad(.gen1Wifi) // "iPad 1G (Wifi)" + XCTAssertEqual(iPad.simpleName, "iPad 1G") + + let mac = DeviceModel.mac(.iMac2PortsM1) // "iMac (M1 2 ports)" + XCTAssertEqual(mac.simpleName, "iMac") + } + + func test_init_with_simulator_identifier() { + // Simulates simulator environment + let originalEnv = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] + setenv("SIMULATOR_MODEL_IDENTIFIER", "iPhone15,4", 1) // iPhone 15 + + let simulatorDevice = DeviceModel(deviceIdentifier: "arm64") + + let expectedModel = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64") + XCTAssertEqual(simulatorDevice, expectedModel) + XCTAssertEqual(simulatorDevice.name, "Simulator of iPhone 15 @ arm64") + XCTAssertTrue(simulatorDevice.isSimulator) + XCTAssertTrue(simulatorDevice.isIphone) + + // Clean up environment variable + if let originalEnv = originalEnv { + setenv("SIMULATOR_MODEL_IDENTIFIER", originalEnv, 1) + } else { + unsetenv("SIMULATOR_MODEL_IDENTIFIER") + } + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelArchitectureTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelArchitectureTests.swift new file mode 100644 index 0000000..a4310d2 --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelArchitectureTests.swift @@ -0,0 +1,113 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("DeviceModel+Architecture Tests") +struct DeviceModelArchitectureTests { + + @Test("Verifies architecture for all device models") + func testArchitectureForAllTypes() { + // Reference dictionary that maps each processor to its expected architecture. + // This is our "source of truth" for the test. + let expectedArchitectures: [DeviceModel.Processor: String] = [ + // Early Processors + .APL0098: "armv6", + .APL0278: "armv7", + .APL0298: "armv7", + .APL2298: "armv7", + + // A-Series + .appleA4: "armv7", + .appleA5: "armv7", + .appleA5X: "armv7", + .appleA6: "armv7s", + .appleA6X: "armv7s", + .appleA7: "arm64", + .appleA8: "arm64", + .appleA8X: "arm64", + .appleA9: "arm64", + .appleA9X: "arm64", + .appleA10Fusion: "arm64", + .appleA10XFusion: "arm64", + .appleA11Bionic: "arm64", + .appleA12Bionic: "arm64e", + .appleA12XBionic: "arm64e", + .appleA12ZBionic: "arm64e", + .appleA13Bionic: "arm64e", + .appleA14Bionic: "arm64e", + .appleA15Bionic: "arm64e", + .appleA16Bionic: "arm64e", + .appleA17Pro: "arm64e", + .appleA18: "arm64e", + .appleA18Pro: "arm64e", + .appleA19: "arm64e", + .appleA19Pro: "arm64e", + + // M-Series + .appleM1: "arm64e", .appleM1Pro: "arm64e", .appleM1Max: "arm64e", .appleM1Ultra: "arm64e", + .appleM2: "arm64e", .appleM2Pro: "arm64e", .appleM2Max: "arm64e", .appleM2Ultra: "arm64e", + .appleM3: "arm64e", .appleM3Pro: "arm64e", .appleM3Max: "arm64e", + .appleM4: "arm64e", .appleM4Pro: "arm64e", .appleM4Max: "arm64e", + + // R-Series + .appleR1: "arm64", + + // S-Series (Watch) + .appleS1: "armv7k", + .appleS1P: "armv7k", + .appleS2: "armv7k", + .appleS3: "armv8-a", + .appleS4: "arm64e", + .appleS5: "arm64e", + .appleS6: "arm64e", + .appleS7: "arm64e", + .appleS8: "arm64e", + .appleS9: "arm64e", + .appleS10: "arm64e" + ] + + // Iterate over all DeviceModel cases to verify their architecture. + for model in DeviceModel.allCases { + let calculatedArchitecture = model.architecture + var expectedArchitecture: String? + + switch model { + case .iPod(let model): + expectedArchitecture = expectedArchitectures[model.processor] + case .iPhone(let model): + expectedArchitecture = expectedArchitectures[model.processor] + case .iPad(let model): + expectedArchitecture = expectedArchitectures[model.processor] + case .appleWatch(let model): + expectedArchitecture = expectedArchitectures[model.processor] + case .appleTV(let model): + expectedArchitecture = expectedArchitectures[model.processor] + case .mac(let model): + expectedArchitecture = expectedArchitectures[model.processor] + case .macCatalyst, .macDesignedForIpad: + expectedArchitecture = "arm64" + case .simulator(_, let arch): + expectedArchitecture = arch + case .unknown: + expectedArchitecture = nil + } + + #expect( + calculatedArchitecture == expectedArchitecture, + "Architecture mismatch for \(model.name). Expected \(String(describing: expectedArchitecture)), got \(String(describing: calculatedArchitecture))" + ) + } + } + + @Test("Verifies architecture for special cases") + func testArchitectureForSpecialCases() { + #expect(DeviceModel.macCatalyst.architecture == "arm64") + #expect(DeviceModel.macDesignedForIpad.architecture == "arm64") + #expect(DeviceModel.unknown(model: "Test").architecture == nil) + } + + @Test("Verifies architecture for simulator") + func testArchitectureForSimulator() { + let simulator = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64") + #expect(simulator.architecture == "arm64") + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelCapabilitiesTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelCapabilitiesTests.swift new file mode 100644 index 0000000..c6b1158 --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelCapabilitiesTests.swift @@ -0,0 +1,49 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("DeviceModel+Capabilities Tests") +struct DeviceModelCapabilitiesTests { + + @Test("Device has rounded display corners") + func hasRoundedDisplayCorners() { + let iPhoneX = DeviceModel.iPhone(.iPhoneXGlobal) + #expect(iPhoneX.hasRoundedDisplayCorners == true) + + let iPhoneXSimulator = DeviceModel.simulator(iPhoneX, arch: "arm64") + #expect(iPhoneXSimulator.hasRoundedDisplayCorners == true) + } + + @Test("Device does not have rounded display corners") + func doesNotHaveRoundedDisplayCorners() { + let iPhone8 = DeviceModel.iPhone(.iPhone8Global) + #expect(iPhone8.hasRoundedDisplayCorners == false) + + let iPhone8Simulator = DeviceModel.simulator(iPhone8, arch: "arm64") + #expect(iPhone8Simulator.hasRoundedDisplayCorners == false) + } + + @Test("Device has dynamic island") + func hasDynamicIsland() { + let iPhone15 = DeviceModel.iPhone(.iPhone15) + #expect(iPhone15.hasDynamicIsland == true) + + let iPhone15Simulator = DeviceModel.simulator(iPhone15, arch: "arm64") + #expect(iPhone15Simulator.hasDynamicIsland == true) + } + + @Test("Device does not have dynamic island") + func doesNotHaveDynamicIsland() { + let iPhone13 = DeviceModel.iPhone(.iPhone13) + #expect(iPhone13.hasDynamicIsland == false) + + let iPhone13Simulator = DeviceModel.simulator(iPhone13, arch: "arm64") + #expect(iPhone13Simulator.hasDynamicIsland == false) + } + + @Test("`allDevicesWithRoundedDisplayCorners` contains correct models") + func allDevicesWithRoundedCorners() { + let all = DeviceModel.allDevicesWithRoundedDisplayCorners + #expect(all.contains(.iPhone(.iPhone15Pro))) + #expect(!all.contains(.iPhone(.iPhone8Global))) + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelCaseIterableTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelCaseIterableTests.swift new file mode 100644 index 0000000..c18364e --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelCaseIterableTests.swift @@ -0,0 +1,21 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("DeviceModel+CaseIterable Tests") +struct DeviceModelCaseIterableTests { + + @Test("`allCases` contains all model types") + func allCasesContainsAllTypes() { + let allCases = DeviceModel.allCases + + #expect(allCases.contains { $0.isIphone && !$0.isSimulator }) + #expect(allCases.contains { $0.isIpad && !$0.isSimulator }) + #expect(allCases.contains { $0.isWatch && !$0.isSimulator }) + #expect(allCases.contains { $0.isAppleTV && !$0.isSimulator }) + #expect(allCases.contains { if case .mac = $0 { return true } else { return false } }) + + #expect(allCases.contains { $0.isSimulator }) + #expect(allCases.contains(.macCatalyst)) + #expect(allCases.contains(.macDesignedForIpad)) + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelCurrentTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelCurrentTests.swift new file mode 100644 index 0000000..edf58f1 --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelCurrentTests.swift @@ -0,0 +1,26 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("DeviceModel.current Tests") +struct DeviceModelCurrentTests { + + @Test("`current` property returns a valid model") + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) + func currentDeviceModel() { + let current = DeviceModel.current + + // Esta prueba verifica que `current` no sea un caso inesperado o no inicializado. + // La aserción específica dependerá de la plataforma en la que se ejecuten los tests. + #expect(current != .unknown(model: ""), "Current device should not be an empty unknown model.") + +#if targetEnvironment(simulator) + #expect(current.isSimulator, "Should be identified as a simulator.") +#else + #expect(!current.isSimulator, "Should be identified as a real device.") +#endif + + // The following test is an example and will only pass if you run tests on an iPhone 15. + // The goal is to demonstrate that the value is consistent. + // #expect(current == .iPhone(.iPhone15)) + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelDeviceIdentifierTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelDeviceIdentifierTests.swift new file mode 100644 index 0000000..9df7006 --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelDeviceIdentifierTests.swift @@ -0,0 +1,61 @@ +import Testing +@testable import DeviceIdentificator +import Foundation + +@Suite("DeviceModel+DeviceIdentifier Tests") +struct DeviceModelDeviceIdentifierTests { + + @Test("Initialize from known device identifier") + func initWithKnownIdentifier() { + #expect(DeviceModel(deviceIdentifier: "iPhone5,2") == .iPhone(.iPhone5Global)) + #expect(DeviceModel(deviceIdentifier: "iPod9,1") == .iPod(.touch7G)) + #expect(DeviceModel(deviceIdentifier: "iPad14,6") == .iPad(.pro6_12d9inchCellular)) + #expect(DeviceModel(deviceIdentifier: "Watch6,18") == .appleWatch(.ultra)) + #expect(DeviceModel(deviceIdentifier: "AppleTV14,1") == .appleTV(.tv4K3G)) + #expect(DeviceModel(deviceIdentifier: "Mac14,8") == .mac(.macPro)) + } + + @Test("Initialize from unknown device identifier") + func initWithUnknownIdentifier() { + let device = DeviceModel(deviceIdentifier: "hakuna") + #expect(device == .unknown(model: "hakuna")) + } + + @Test("Initialize from simulator identifier") + func initWithSimulatorIdentifier() { + // Simulate the environment of a simulator + let originalEnv = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] + setenv("SIMULATOR_MODEL_IDENTIFIER", "iPhone15,4", 1) // iPhone 15 + + let simulatorDevice = DeviceModel(deviceIdentifier: "arm64") + let expectedModel = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64") + #expect(simulatorDevice == expectedModel) + + // Clean up environment variable + if let originalEnv { + setenv("SIMULATOR_MODEL_IDENTIFIER", originalEnv, 1) + } else { + unsetenv("SIMULATOR_MODEL_IDENTIFIER") + } + } + + @Test("`deviceIdentifier` property returns correct value") + func deviceIdentifierProperty() { + #expect(DeviceModel.iPhone(.iPhone5Global).deviceIdentifier == "iPhone5,2") + #expect(DeviceModel.macCatalyst.deviceIdentifier == nil) + #expect(DeviceModel.macDesignedForIpad.deviceIdentifier == nil) + #expect(DeviceModel.unknown(model: "hakuna").deviceIdentifier == "hakuna") + + let simulator = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64") + #expect(simulator.deviceIdentifier == "iPhone15,4") + } + + @Test("All real models can be initialized from their identifier") + func allRealModelsInitFromIdentifier() { + let realModels = DeviceModel.allCases.filter { !$0.isSimulator && $0.deviceIdentifier != nil } + for model in realModels { + let identifier = model.deviceIdentifier! + #expect(DeviceModel(deviceIdentifier: identifier) == model, "Failed for \(model.name) with identifier \(identifier)") + } + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelHelpersTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelHelpersTests.swift new file mode 100644 index 0000000..0e02b2a --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelHelpersTests.swift @@ -0,0 +1,40 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("DeviceModel+Helpers Tests") +struct DeviceModelHelpersTests { + + @Test("isSimulator") + func isSimulator() { + #expect(DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64").isSimulator == true) + #expect(DeviceModel.iPhone(.iPhone15).isSimulator == false) + } + + @Test("isAppleTV") + func isAppleTV() { + #expect(DeviceModel.appleTV(.tv4K).isAppleTV == true) + #expect(DeviceModel.simulator(.appleTV(.tv4K), arch: "arm64").isAppleTV == true) + #expect(DeviceModel.iPhone(.iPhone15).isAppleTV == false) + } + + @Test("isIpad") + func isIpad() { + #expect(DeviceModel.iPad(.gen10Wifi).isIpad == true) + #expect(DeviceModel.simulator(.iPad(.gen10Wifi), arch: "arm64").isIpad == true) + #expect(DeviceModel.iPhone(.iPhone15).isIpad == false) + } + + @Test("isIphone") + func isIphone() { + #expect(DeviceModel.iPhone(.iPhone15).isIphone == true) + #expect(DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64").isIphone == true) + #expect(DeviceModel.iPad(.gen10Wifi).isIphone == false) + } + + @Test("isWatch") + func isWatch() { + #expect(DeviceModel.appleWatch(.ultra2).isWatch == true) + #expect(DeviceModel.simulator(.appleWatch(.ultra2), arch: "arm64").isWatch == true) + #expect(DeviceModel.iPhone(.iPhone15).isWatch == false) + } +} diff --git a/Tests/DeviceIdentificatorTests/DeviceModelNameTests.swift b/Tests/DeviceIdentificatorTests/DeviceModelNameTests.swift new file mode 100644 index 0000000..e6ba7ed --- /dev/null +++ b/Tests/DeviceIdentificatorTests/DeviceModelNameTests.swift @@ -0,0 +1,39 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("DeviceModel+Name & CustomStringConvertible Tests") +struct DeviceModelNameTests { + + @Test("`name` property returns correct string for all cases") + func nameProperty() { + #expect(DeviceModel.iPod(.touch7G).name == "iPod touch 7G") + #expect(DeviceModel.iPhone(.iPhone15).name == "iPhone 15") + #expect(DeviceModel.iPad(.gen10Wifi).name == "iPad 10G (Wifi)") + #expect(DeviceModel.appleWatch(.ultra2).name == "Apple Watch Ultra 2") + #expect(DeviceModel.appleTV(.tv4K3G).name == "Apple TV 4K 3G") + #expect(DeviceModel.mac(.macBookAirM1).name == "MacBook Air (M1)") + #expect(DeviceModel.macCatalyst.name == "Mac Catalyst") + #expect(DeviceModel.macDesignedForIpad.name == "Mac Designed for iPad") + #expect(DeviceModel.unknown(model: "Test").name == "Unknown device: Test") + #expect(DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64").name == "Simulator of iPhone 15 @ arm64") + } + + @Test("`simpleName` property strips variants") + func simpleNameProperty() { + let iPadWithVariant = DeviceModel.iPad(.gen10Wifi) // "iPad 10G (Wifi)" + #expect(iPadWithVariant.simpleName == "iPad 10G") + + let macWithVariant = DeviceModel.mac(.iMac2PortsM1) // "iMac (M1 2 ports)" + #expect(macWithVariant.simpleName == "iMac") + + let iPhoneWithoutVariant = DeviceModel.iPhone(.iPhone15) // "iPhone 15" + #expect(iPhoneWithoutVariant.simpleName == "iPhone 15") + } + + @Test("`description` is the same as `name`") + func descriptionIsName() { + for model in DeviceModel.allCases { + #expect(model.description == model.name) + } + } +} diff --git a/Tests/DeviceIdentificatorTests/NewModelsTests.swift b/Tests/DeviceIdentificatorTests/NewModelsTests.swift new file mode 100644 index 0000000..68ac166 --- /dev/null +++ b/Tests/DeviceIdentificatorTests/NewModelsTests.swift @@ -0,0 +1,107 @@ +import Testing +@testable import DeviceIdentificator + +@Suite("New Models Properties Tests") +struct NewModelsTests { + + // MARK: - iPhone Tests + @Test("iPhone 17 properties") + func iPhone17() { + let model: DeviceModel.IPhoneModel = .iPhone17 + let device = DeviceModel.iPhone(model) + #expect(device.name == "iPhone 17") + #expect(device.deviceIdentifier == "iPhone18,3") + #expect(model.processor == .appleA19) + #expect(DeviceModel(deviceIdentifier: "iPhone18,3") == device) + } + + @Test("iPhone 17 Pro properties") + func iPhone17Pro() { + let model: DeviceModel.IPhoneModel = .iPhone17Pro + let device = DeviceModel.iPhone(model) + #expect(device.name == "iPhone 17 Pro") + #expect(device.deviceIdentifier == "iPhone18,1") + #expect(model.processor == .appleA19Pro) + #expect(DeviceModel(deviceIdentifier: "iPhone18,1") == device) + } + + @Test("iPhone 17 Pro Max properties") + func iPhone17ProMax() { + let model: DeviceModel.IPhoneModel = .iPhone17ProMax + let device = DeviceModel.iPhone(model) + #expect(device.name == "iPhone 17 Pro Max") + #expect(device.deviceIdentifier == "iPhone18,2") + #expect(model.processor == .appleA19Pro) + #expect(DeviceModel(deviceIdentifier: "iPhone18,2") == device) + } + + @Test("iPhone Air properties") + func iPhoneAir() { + let model: DeviceModel.IPhoneModel = .iPhoneAir + let device = DeviceModel.iPhone(model) + #expect(device.name == "iPhone Air") + #expect(device.deviceIdentifier == "iPhone18,4") + #expect(model.processor == .appleA19) + #expect(DeviceModel(deviceIdentifier: "iPhone18,4") == device) + } + + // MARK: - iPad Tests + @Test("iPad Gen 11 properties") + func iPadGen11() { + let wifiModel: DeviceModel.IPadModel = .gen11Wifi + let wifiDevice = DeviceModel.iPad(wifiModel) + #expect(wifiDevice.name == "iPad 11G (Wifi)") + #expect(wifiDevice.deviceIdentifier == "iPad15,7") + #expect(wifiModel.processor == .appleA14Bionic) + #expect(DeviceModel(deviceIdentifier: "iPad15,7") == wifiDevice) + + let cellularModel: DeviceModel.IPadModel = .gen11Cellular + let cellularDevice = DeviceModel.iPad(cellularModel) + #expect(cellularDevice.name == "iPad 11G (Cellular)") + #expect(cellularDevice.deviceIdentifier == "iPad15,8") + #expect(cellularModel.processor == .appleA14Bionic) + #expect(DeviceModel(deviceIdentifier: "iPad15,8") == cellularDevice) + } + + @Test("iPad Air M3 properties") + func iPadAirM3() { + let air11Wifi: DeviceModel.IPadModel = .air11InchM3Wifi + let air11WifiDevice = DeviceModel.iPad(air11Wifi) + #expect(air11WifiDevice.name == "iPad Air M3 11\" (Wifi)") + #expect(air11WifiDevice.deviceIdentifier == "iPad15,3") + #expect(air11Wifi.processor == .appleM3) + #expect(DeviceModel(deviceIdentifier: "iPad15,3") == air11WifiDevice) + // ... (additional Air M3 variants can be added here) + } + + @Test("iPad Mini A17 Pro properties") + func iPadMiniA17Pro() { + let wifiModel: DeviceModel.IPadModel = .miniA17ProWifi + let wifiDevice = DeviceModel.iPad(wifiModel) + #expect(wifiDevice.name == "iPad Mini A17 Pro (Wifi)") + #expect(wifiDevice.deviceIdentifier == "iPad16,1") + #expect(wifiModel.processor == .appleA17Pro) + #expect(DeviceModel(deviceIdentifier: "iPad16,1") == wifiDevice) + } + + // MARK: - Apple Watch Tests + @Test("Apple Watch Series 10 properties") + func appleWatchSeries10() { + let gpsModel: DeviceModel.AppleWatchModel = .series10_42mmGPS + let gpsDevice = DeviceModel.appleWatch(gpsModel) + #expect(gpsDevice.name == "Apple Watch Series 10 42mm") + #expect(gpsDevice.deviceIdentifier == "Watch7,8") + #expect(gpsModel.processor == .appleS10) + #expect(DeviceModel(deviceIdentifier: "Watch7,8") == gpsDevice) + } + + @Test("Apple Watch Ultra 3 properties") + func appleWatchUltra3() { + let modelEnum: DeviceModel.AppleWatchModel = .ultra3 + let device = DeviceModel.appleWatch(modelEnum) + #expect(device.name == "Apple Watch Ultra 3") + #expect(device.deviceIdentifier == "Watch7,12") + #expect(modelEnum.processor == .appleS10) + #expect(DeviceModel(deviceIdentifier: "Watch7,12") == device) + } +}