diff --git a/Modules/App/Sources/UI/Navigation/DeepLinkRouteCoder.swift b/Modules/App/Sources/UI/Navigation/DeepLinkRouteCoder.swift index 5e9b09e213..3b0d7711c8 100644 --- a/Modules/App/Sources/UI/Navigation/DeepLinkRouteCoder.swift +++ b/Modules/App/Sources/UI/Navigation/DeepLinkRouteCoder.swift @@ -59,7 +59,7 @@ enum DeepLinkRouteCoder { guard let components = URLComponents(url: deepLink, resolvingAgainstBaseURL: true), let rawScheme = components.scheme, - let supportedScheme = Bundle.URLScheme(rawValue: rawScheme) + let supportedScheme = Bundle.URLScheme(caseInsensitive: rawScheme) else { return nil } diff --git a/Modules/InboxCore/.gitignore b/Modules/InboxCore/.gitignore index 09d0852c97..b14d34c09b 100644 --- a/Modules/InboxCore/.gitignore +++ b/Modules/InboxCore/.gitignore @@ -7,3 +7,4 @@ DerivedData/ .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .swiftpm/xcode/xcshareddata/ .netrc +Package.resolved diff --git a/Modules/InboxCore/Package.swift b/Modules/InboxCore/Package.swift index fc4a3a9d0e..95bf58d598 100644 --- a/Modules/InboxCore/Package.swift +++ b/Modules/InboxCore/Package.swift @@ -26,6 +26,12 @@ let package = Package( resources: [ .process("Resources") ] - ) + ), + .testTarget( + name: "InboxCoreTests", + dependencies: [ + .target(name: "InboxCore"), + ] + ), ] ) diff --git a/Modules/InboxCore/Sources/Bundle/Bundle+URLTypes.swift b/Modules/InboxCore/Sources/Bundle/Bundle+URLTypes.swift index 3d91bcbdff..5779844f40 100644 --- a/Modules/InboxCore/Sources/Bundle/Bundle+URLTypes.swift +++ b/Modules/InboxCore/Sources/Bundle/Bundle+URLTypes.swift @@ -18,7 +18,7 @@ import Foundation public extension Bundle { - enum URLScheme: String, Sendable { + enum URLScheme: String, Sendable, CaseInsensitiveRawRepresentable { case mailto case protonmail } diff --git a/Modules/InboxCore/Sources/CaseInsensitiveRawRepresentable.swift b/Modules/InboxCore/Sources/CaseInsensitiveRawRepresentable.swift new file mode 100644 index 0000000000..9998d70258 --- /dev/null +++ b/Modules/InboxCore/Sources/CaseInsensitiveRawRepresentable.swift @@ -0,0 +1,29 @@ +// Copyright (c) 2026 Proton Technologies AG +// +// This file is part of Proton Mail. +// +// Proton Mail is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail. If not, see https://www.gnu.org/licenses/. + +public protocol CaseInsensitiveRawRepresentable: RawRepresentable, CaseIterable where RawValue == String {} + +public extension CaseInsensitiveRawRepresentable { + init?(caseInsensitive rawValue: String) { + guard + let match = Self.allCases.first(where: { + $0.rawValue.caseInsensitiveCompare(rawValue) == .orderedSame + }) + else { return nil } + self = match + } +} diff --git a/Modules/InboxCore/Tests/CaseInsensitiveRawRepresentableTests.swift b/Modules/InboxCore/Tests/CaseInsensitiveRawRepresentableTests.swift new file mode 100644 index 0000000000..f6e7c1c912 --- /dev/null +++ b/Modules/InboxCore/Tests/CaseInsensitiveRawRepresentableTests.swift @@ -0,0 +1,69 @@ +// Copyright (c) 2026 Proton Technologies AG +// +// This file is part of Proton Mail. +// +// Proton Mail is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail. If not, see https://www.gnu.org/licenses/. + +import XCTest + +@testable import InboxCore + +final class CaseInsensitiveRawRepresentableTests: XCTestCase { + + private enum TestStatus: String, CaseInsensitiveRawRepresentable { + case active = "Active" + case inactive = "Inactive" + case pending = "PENDING" + case archived = "archived" + } + + func testInit_WithExactMatch_ReturnsCorrectCase() { + XCTAssertEqual(TestStatus(caseInsensitive: "Active"), .active) + XCTAssertEqual(TestStatus(caseInsensitive: "Inactive"), .inactive) + XCTAssertEqual(TestStatus(caseInsensitive: "PENDING"), .pending) + XCTAssertEqual(TestStatus(caseInsensitive: "archived"), .archived) + } + + func testInit_WithLowercaseInput_ReturnsCorrectCase() { + XCTAssertEqual(TestStatus(caseInsensitive: "active"), .active) + XCTAssertEqual(TestStatus(caseInsensitive: "inactive"), .inactive) + XCTAssertEqual(TestStatus(caseInsensitive: "pending"), .pending) + XCTAssertEqual(TestStatus(caseInsensitive: "archived"), .archived) + } + + func testInit_WithUppercaseInput_ReturnsCorrectCase() { + XCTAssertEqual(TestStatus(caseInsensitive: "ACTIVE"), .active) + XCTAssertEqual(TestStatus(caseInsensitive: "INACTIVE"), .inactive) + XCTAssertEqual(TestStatus(caseInsensitive: "PENDING"), .pending) + XCTAssertEqual(TestStatus(caseInsensitive: "ARCHIVED"), .archived) + } + + func testInit_WithMixedCaseInput_ReturnsCorrectCase() { + XCTAssertEqual(TestStatus(caseInsensitive: "AcTiVe"), .active) + XCTAssertEqual(TestStatus(caseInsensitive: "InAcTiVe"), .inactive) + XCTAssertEqual(TestStatus(caseInsensitive: "PeNdInG"), .pending) + XCTAssertEqual(TestStatus(caseInsensitive: "ArChIvEd"), .archived) + } + + func testInit_WithNonExistentValue_ReturnsNil() { + XCTAssertNil(TestStatus(caseInsensitive: "unknown")) + XCTAssertNil(TestStatus(caseInsensitive: "UNKNOWN")) + XCTAssertNil(TestStatus(caseInsensitive: "deleted")) + XCTAssertNil(TestStatus(caseInsensitive: "suspended")) + } + + func testInit_WithEmptyString_ReturnsNil() { + XCTAssertNil(TestStatus(caseInsensitive: "")) + } +} diff --git a/TestPlans/AllUnitAndSnapshotTests.xctestplan b/TestPlans/AllUnitAndSnapshotTests.xctestplan index 8d7cc932da..bef046476d 100644 --- a/TestPlans/AllUnitAndSnapshotTests.xctestplan +++ b/TestPlans/AllUnitAndSnapshotTests.xctestplan @@ -34,6 +34,13 @@ "name" : "InboxCoreUITests" } }, + { + "target" : { + "containerPath" : "container:Modules\/InboxCore", + "identifier" : "InboxCoreTests", + "name" : "InboxCoreTests" + } + }, { "target" : { "containerPath" : "container:Modules\/TestableShareExtension",