Skip to content

Commit e1ee07c

Browse files
committed
feat: add new models and testing
1 parent 2ae6adf commit e1ee07c

10 files changed

+500
-112
lines changed

Sources/DeviceIdentificator/Models/IPadModel+Processor.swift

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,54 @@ import Foundation
33
public extension DeviceModel.IPadModel {
44
var processor: DeviceModel.Processor {
55
switch self {
6-
default:
7-
return .appleA10XFusion
6+
// iPads
7+
case .gen1Wifi, .gen1Cellular: return .appleA4
8+
case .gen2Wifi, .gen2GSM, .gen2CDMA, .gen2NewRev: return .appleA5
9+
case .gen3Wifi, .gen3CDMA, .gen3GSM: return .appleA5X
10+
case .gen4Wifi, .gen4GSM, .gen4CDMA: return .appleA6X
11+
case .gen5Wifi, .gen5Cellular: return .appleA9
12+
case .gen6Wifi, .gen6Cellular: return .appleA10Fusion
13+
case .gen7Wifi, .gen7Cellular: return .appleA10Fusion
14+
case .gen8Wifi, .gen8Cellular: return .appleA12Bionic
15+
case .gen9Wifi, .gen9Cellular: return .appleA13Bionic
16+
case .gen10Wifi, .gen10Cellular: return .appleA14Bionic
17+
case .gen11Wifi, .gen11Cellular: return .appleA14Bionic // Asumido, basado en el iPad 10
18+
19+
// Minis
20+
case .mini1Wifi, .mini1GSM, .mini1CDMA: return .appleA5
21+
case .mini2Wifi, .mini2GSMCDMA, .mini2China: return .appleA7
22+
case .mini3Wifi, .mini3GSMCDMA, .mini3China: return .appleA7
23+
case .mini4Wifi, .mini4Cellular: return .appleA8
24+
case .mini5Wifi, .mini5Cellular: return .appleA12Bionic
25+
case .mini6Wifi, .mini6Cellular: return .appleA15Bionic
26+
case .miniA17ProWifi, .miniA17ProCellular: return .appleA17Pro
27+
28+
// Airs
29+
case .air1Wifi, .air1GSMCDMA, .air1China: return .appleA7
30+
case .air2Wifi, .air2Cellular: return .appleA8X
31+
case .air3Wifi, .air3Cellular: return .appleA12Bionic
32+
case .air4Wifi, .air4Cellular: return .appleA14Bionic
33+
case .air5Wifi, .air5Cellular: return .appleM1
34+
case .air11InchM2Wifi, .air11InchM2Cellular,
35+
.air13InchM2Wifi, .air13InchM2Cellular: return .appleM2
36+
case .air11InchM3Wifi, .air11InchM3Cellular,
37+
.air13InchM3Wifi, .air13InchM3Cellular: return .appleM3
38+
39+
// Pros
40+
case .pro1_9d7inchWifi, .pro1_9d7inchCellular,
41+
.pro1_12d9inchWifi, .pro1_12d9inchCellular: return .appleA9X
42+
case .pro1_10d5inchWifi, .pro1_10d5inchCellular,
43+
.pro2_12d9inchWifi, .pro2_12d9inchCellular: return .appleA10XFusion
44+
case .pro1_11inchWifi, .pro1_11inchWifiTera, .pro1_11inchCellular, .pro1_11inchCellularTera,
45+
.pro3_12d9inchWifi, .pro3_12d9inchWifiTera, .pro3_12d9inchCellular, .pro3_12d9inchCellularTera: return .appleA12XBionic
46+
case .pro2_11inchWifi, .pro2_11inchCellular,
47+
.pro4_12d9inchWifi, .pro4_12d9inchCellular: return .appleA12ZBionic
48+
case .pro3_11inchWifi, .pro3_11inchWifiTera, .pro3_11inchCellular, .pro3_11inchCellularTera,
49+
.pro5_12d9inchWifi, .pro5_12d9inchWifiTera, .pro5_12d9inchCellular, .pro5_12d9inchCellularTera: return .appleM1
50+
case .pro4_11inchWifi, .pro4_11inchCellular,
51+
.pro6_12d9inchWifi, .pro6_12d9inchCellular: return .appleM2
52+
case .pro_11inchM4Wifi, .pro_11inchM4Cellular,
53+
.pro_13inchM4Wifi, .pro_13inchM4Cellular: return .appleM4
854
}
955
}
1056
}

Sources/DeviceIdentificator/Processor+Specs.swift

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,122 @@ import Foundation
33
public extension DeviceModel.Processor {
44
var architecture: String {
55
switch self {
6-
default:
7-
return "arm64" // Not really, check: https://apple.fandom.com/wiki/List_of_Apple_processors
6+
// Early 32-bit ARMv7 processors
7+
case .APL0278, .APL0298, .APL2298,
8+
.appleA4, .appleA5, .appleA5X:
9+
return "armv7"
10+
11+
// Custom 32-bit ARMv7s architecture by Apple
12+
case .appleA6, .appleA6X:
13+
return "armv7s"
14+
15+
// First generation of 64-bit ARMv8-A processors
16+
case .appleA7, .appleA8, .appleA8X, .appleA9, .appleA9X,
17+
.appleA10Fusion, .appleA10XFusion, .appleA11Bionic:
18+
return "arm64"
19+
20+
// Processors with ARMv8.3-A (Pointer Authentication - arm64e) and newer
21+
case .appleA12Bionic, .appleA12XBionic, .appleA12ZBionic,
22+
.appleA13Bionic, .appleA14Bionic, .appleA15Bionic,
23+
.appleA16Bionic, .appleA17Pro:
24+
return "arm64e"
25+
26+
// Future processors (estimated to be arm64e or a successor)
27+
case .appleA18, .appleA18Pro, .appleA19, .appleA19Pro:
28+
return "arm64e" // Estimation
29+
30+
// Apple Silicon for Mac/iPad
31+
case .appleM1, .appleM1Pro, .appleM1Max, .appleM1Ultra,
32+
.appleM2, .appleM2Pro, .appleM2Max, .appleM2Ultra,
33+
.appleM3, .appleM3Pro, .appleM3Max,
34+
.appleM4, .appleM4Pro, .appleM4Max:
35+
return "arm64e"
36+
37+
// Vision Pro Co-processor
38+
case .appleR1:
39+
return "arm64" // Specific architecture not public, but it's an ARM-based co-processor.
40+
41+
// Apple Watch processors
42+
case .appleS1, .appleS1P, .appleS2:
43+
return "armv7k" // 32-bit architecture for WatchOS
44+
case .appleS3:
45+
return "armv8-a" // 64-bit (32-bit compatibility mode)
46+
case .appleS4, .appleS5,
47+
.appleS6, .appleS7, .appleS8,
48+
.appleS9, .appleS10:
49+
return "arm64e" // 64-bit with Pointer Authentication
50+
51+
// Very early 32-bit ARMv6 processor
52+
case .APL0098:
53+
return "armv6"
854
}
955
}
10-
56+
57+
/// The year the processor was introduced.
1158
var introducedYear: Int? {
1259
switch self {
13-
default:
14-
return nil //Check the same list
60+
// Initial Samsung processors
61+
case .APL0098: return 2007
62+
case .APL0278: return 2008
63+
case .APL0298: return 2009
64+
case .APL2298: return 2010
65+
66+
// A-series
67+
case .appleA4: return 2010
68+
case .appleA5: return 2011
69+
case .appleA5X: return 2012
70+
case .appleA6: return 2012
71+
case .appleA6X: return 2012
72+
case .appleA7: return 2013
73+
case .appleA8: return 2014
74+
case .appleA8X: return 2014
75+
case .appleA9: return 2015
76+
case .appleA9X: return 2015
77+
case .appleA10Fusion: return 2016
78+
case .appleA10XFusion: return 2017
79+
case .appleA11Bionic: return 2017
80+
case .appleA12Bionic: return 2018
81+
case .appleA12XBionic: return 2018
82+
case .appleA12ZBionic: return 2020
83+
case .appleA13Bionic: return 2019
84+
case .appleA14Bionic: return 2020
85+
case .appleA15Bionic: return 2021
86+
case .appleA16Bionic: return 2022
87+
case .appleA17Pro: return 2023
88+
case .appleA18, .appleA18Pro: return 2024 // Estimated
89+
case .appleA19, .appleA19Pro: return 2025 // Estimated
90+
91+
// M-series
92+
case .appleM1: return 2020
93+
case .appleM1Pro: return 2021
94+
case .appleM1Max: return 2021
95+
case .appleM1Ultra: return 2022
96+
case .appleM2: return 2022
97+
case .appleM2Pro: return 2023
98+
case .appleM2Max: return 2023
99+
case .appleM2Ultra: return 2023
100+
case .appleM3: return 2023
101+
case .appleM3Pro: return 2023
102+
case .appleM3Max: return 2023
103+
case .appleM4: return 2024
104+
case .appleM4Pro: return 2024 // Estimated
105+
case .appleM4Max: return 2024 // Estimated
106+
107+
// R-series
108+
case .appleR1: return 2024
109+
110+
// S-series
111+
case .appleS1: return 2015
112+
case .appleS1P: return 2016
113+
case .appleS2: return 2016
114+
case .appleS3: return 2017
115+
case .appleS4: return 2018
116+
case .appleS5: return 2019
117+
case .appleS6: return 2020
118+
case .appleS7: return 2021
119+
case .appleS8: return 2022
120+
case .appleS9: return 2023
121+
case .appleS10: return 2024 // Estimated
15122
}
16123
}
17124
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Testing
2+
@testable import DeviceIdentificator
3+
4+
@Suite("DeviceModel+Architecture Tests")
5+
struct DeviceModelArchitectureTests {
6+
7+
@Test("Verifies architecture for all device types")
8+
func testArchitectureForAllTypes() {
9+
#expect(DeviceModel.iPod(.touch7G).architecture == "arm64")
10+
#expect(DeviceModel.iPhone(.iPhone15Pro).architecture == "arm64e")
11+
#expect(DeviceModel.iPad(.pro_11inchM4Wifi).architecture == "arm64e")
12+
#expect(DeviceModel.appleWatch(.series9_41mmGPS).architecture == "arm64e")
13+
#expect(DeviceModel.appleTV(.tv4K3G).architecture == "arm64e")
14+
#expect(DeviceModel.mac(.macBookProM4).architecture == "arm64e")
15+
}
16+
17+
@Test("Verifies architecture for special cases")
18+
func testArchitectureForSpecialCases() {
19+
#expect(DeviceModel.macCatalyst.architecture == "arm64")
20+
#expect(DeviceModel.macDesignedForIpad.architecture == "arm64")
21+
#expect(DeviceModel.unknown(model: "Test").architecture == nil)
22+
}
23+
24+
@Test("Verifies architecture for simulator")
25+
func testArchitectureForSimulator() {
26+
let simulator = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64")
27+
#expect(simulator.architecture == "arm64")
28+
}
29+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Testing
2+
@testable import DeviceIdentificator
3+
4+
@Suite("DeviceModel+Capabilities Tests")
5+
struct DeviceModelCapabilitiesTests {
6+
7+
@Test("Device has rounded display corners")
8+
func hasRoundedDisplayCorners() {
9+
let iPhoneX = DeviceModel.iPhone(.iPhoneXGlobal)
10+
#expect(iPhoneX.hasRoundedDisplayCorners == true)
11+
12+
let iPhoneXSimulator = DeviceModel.simulator(iPhoneX, arch: "arm64")
13+
#expect(iPhoneXSimulator.hasRoundedDisplayCorners == true)
14+
}
15+
16+
@Test("Device does not have rounded display corners")
17+
func doesNotHaveRoundedDisplayCorners() {
18+
let iPhone8 = DeviceModel.iPhone(.iPhone8Global)
19+
#expect(iPhone8.hasRoundedDisplayCorners == false)
20+
21+
let iPhone8Simulator = DeviceModel.simulator(iPhone8, arch: "arm64")
22+
#expect(iPhone8Simulator.hasRoundedDisplayCorners == false)
23+
}
24+
25+
@Test("Device has dynamic island")
26+
func hasDynamicIsland() {
27+
let iPhone15 = DeviceModel.iPhone(.iPhone15)
28+
#expect(iPhone15.hasDynamicIsland == true)
29+
30+
let iPhone15Simulator = DeviceModel.simulator(iPhone15, arch: "arm64")
31+
#expect(iPhone15Simulator.hasDynamicIsland == true)
32+
}
33+
34+
@Test("Device does not have dynamic island")
35+
func doesNotHaveDynamicIsland() {
36+
let iPhone13 = DeviceModel.iPhone(.iPhone13)
37+
#expect(iPhone13.hasDynamicIsland == false)
38+
39+
let iPhone13Simulator = DeviceModel.simulator(iPhone13, arch: "arm64")
40+
#expect(iPhone13Simulator.hasDynamicIsland == false)
41+
}
42+
43+
@Test("`allDevicesWithRoundedDisplayCorners` contains correct models")
44+
func allDevicesWithRoundedCorners() {
45+
let all = DeviceModel.allDevicesWithRoundedDisplayCorners
46+
#expect(all.contains(.iPhone(.iPhone15Pro)))
47+
#expect(!all.contains(.iPhone(.iPhone8Global)))
48+
}
49+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Testing
2+
@testable import DeviceIdentificator
3+
4+
@Suite("DeviceModel+CaseIterable Tests")
5+
struct DeviceModelCaseIterableTests {
6+
7+
@Test("`allCases` contains all model types")
8+
func allCasesContainsAllTypes() {
9+
let allCases = DeviceModel.allCases
10+
11+
#expect(allCases.contains { $0.isIphone && !$0.isSimulator })
12+
#expect(allCases.contains { $0.isIpad && !$0.isSimulator })
13+
#expect(allCases.contains { $0.isWatch && !$0.isSimulator })
14+
#expect(allCases.contains { $0.isAppleTV && !$0.isSimulator })
15+
#expect(allCases.contains { if case .mac = $0 { return true } else { return false } })
16+
17+
#expect(allCases.contains { $0.isSimulator })
18+
#expect(allCases.contains(.macCatalyst))
19+
#expect(allCases.contains(.macDesignedForIpad))
20+
}
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Testing
2+
@testable import DeviceIdentificator
3+
4+
@Suite("DeviceModel.current Tests")
5+
struct DeviceModelCurrentTests {
6+
7+
@Test("`current` property returns a valid model")
8+
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
9+
func currentDeviceModel() {
10+
let current = DeviceModel.current
11+
12+
// Esta prueba verifica que `current` no sea un caso inesperado o no inicializado.
13+
// La aserción específica dependerá de la plataforma en la que se ejecuten los tests.
14+
#expect(current != .unknown(model: ""), "Current device should not be an empty unknown model.")
15+
16+
#if targetEnvironment(simulator)
17+
#expect(current.isSimulator, "Should be identified as a simulator.")
18+
#else
19+
#expect(!current.isSimulator, "Should be identified as a real device.")
20+
#endif
21+
22+
// La siguiente prueba es un ejemplo y solo pasará si ejecutas los tests en un iPhone 15.
23+
// El objetivo es demostrar que el valor es coherente.
24+
// #expect(current == .iPhone(.iPhone15))
25+
}
26+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Testing
2+
@testable import DeviceIdentificator
3+
import Foundation
4+
5+
@Suite("DeviceModel+DeviceIdentifier Tests")
6+
struct DeviceModelDeviceIdentifierTests {
7+
8+
@Test("Initialize from known device identifier")
9+
func initWithKnownIdentifier() {
10+
#expect(DeviceModel(deviceIdentifier: "iPhone5,2") == .iPhone(.iPhone5Global))
11+
#expect(DeviceModel(deviceIdentifier: "iPod9,1") == .iPod(.touch7G))
12+
#expect(DeviceModel(deviceIdentifier: "iPad14,6") == .iPad(.pro6_12d9inchCellular))
13+
#expect(DeviceModel(deviceIdentifier: "Watch6,18") == .appleWatch(.ultra))
14+
#expect(DeviceModel(deviceIdentifier: "AppleTV14,1") == .appleTV(.tv4K3G))
15+
#expect(DeviceModel(deviceIdentifier: "Mac14,8") == .mac(.macPro))
16+
}
17+
18+
@Test("Initialize from unknown device identifier")
19+
func initWithUnknownIdentifier() {
20+
let device = DeviceModel(deviceIdentifier: "hakuna")
21+
#expect(device == .unknown(model: "hakuna"))
22+
}
23+
24+
@Test("Initialize from simulator identifier")
25+
func initWithSimulatorIdentifier() {
26+
// Simula el entorno de un simulador
27+
let originalEnv = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]
28+
setenv("SIMULATOR_MODEL_IDENTIFIER", "iPhone15,4", 1) // iPhone 15
29+
30+
let simulatorDevice = DeviceModel(deviceIdentifier: "arm64")
31+
let expectedModel = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64")
32+
#expect(simulatorDevice == expectedModel)
33+
34+
// Limpia la variable de entorno
35+
if let originalEnv {
36+
setenv("SIMULATOR_MODEL_IDENTIFIER", originalEnv, 1)
37+
} else {
38+
unsetenv("SIMULATOR_MODEL_IDENTIFIER")
39+
}
40+
}
41+
42+
@Test("`deviceIdentifier` property returns correct value")
43+
func deviceIdentifierProperty() {
44+
#expect(DeviceModel.iPhone(.iPhone5Global).deviceIdentifier == "iPhone5,2")
45+
#expect(DeviceModel.macCatalyst.deviceIdentifier == nil)
46+
#expect(DeviceModel.macDesignedForIpad.deviceIdentifier == nil)
47+
#expect(DeviceModel.unknown(model: "hakuna").deviceIdentifier == "hakuna")
48+
49+
let simulator = DeviceModel.simulator(.iPhone(.iPhone15), arch: "arm64")
50+
#expect(simulator.deviceIdentifier == "iPhone15,4")
51+
}
52+
53+
@Test("All real models can be initialized from their identifier")
54+
func allRealModelsInitFromIdentifier() {
55+
let realModels = DeviceModel.allCases.filter { !$0.isSimulator && $0.deviceIdentifier != nil }
56+
for model in realModels {
57+
let identifier = model.deviceIdentifier!
58+
#expect(DeviceModel(deviceIdentifier: identifier) == model, "Failed for \(model.name) with identifier \(identifier)")
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)