diff --git a/firefox-ios/Client/Extensions/CGRect+Extension.swift b/BrowserKit/Sources/Shared/Extensions/CGRectExtensions.swift similarity index 65% rename from firefox-ios/Client/Extensions/CGRect+Extension.swift rename to BrowserKit/Sources/Shared/Extensions/CGRectExtensions.swift index 5790a6aa972cb..f04e36b928538 100644 --- a/firefox-ios/Client/Extensions/CGRect+Extension.swift +++ b/BrowserKit/Sources/Shared/Extensions/CGRectExtensions.swift @@ -5,7 +5,15 @@ import UIKit extension CGRect { - var center: CGPoint { + public init(width: CGFloat, height: CGFloat) { + self.init(x: 0, y: 0, width: width, height: height) + } + + public init(size: CGSize) { + self.init(origin: .zero, size: size) + } + + public var center: CGPoint { get { return CGPoint(x: size.width / 2, y: size.height / 2) } diff --git a/BrowserKit/Sources/Shared/Extensions/UIImageExtensions.swift b/BrowserKit/Sources/Shared/Extensions/UIImageExtensions.swift index 54deb932d4b19..1d3bbae453535 100644 --- a/BrowserKit/Sources/Shared/Extensions/UIImageExtensions.swift +++ b/BrowserKit/Sources/Shared/Extensions/UIImageExtensions.swift @@ -4,16 +4,6 @@ import UIKit -extension CGRect { - public init(width: CGFloat, height: CGFloat) { - self.init(x: 0, y: 0, width: width, height: height) - } - - public init(size: CGSize) { - self.init(origin: .zero, size: size) - } -} - extension Data { public var isGIF: Bool { return [0x47, 0x49, 0x46].elementsEqual(prefix(3)) diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 6858bae185d36..be3985a6174aa 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -254,6 +254,8 @@ 212985E72A72B39D00546684 /* ThemeSettingsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212985E52A72B22800546684 /* ThemeSettingsControllerTests.swift */; }; 213047E62F2153B1007ECEDA /* PageZoomSettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213047E52F2153AB007ECEDA /* PageZoomSettingsViewModelTests.swift */; }; 21304C8C2F217A59007ECEDA /* ThemeSettingsStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21304C8B2F217A59007ECEDA /* ThemeSettingsStateTests.swift */; }; + 213046B92F202F27007ECEDA /* UIFontExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213046B82F202F27007ECEDA /* UIFontExtensionsTests.swift */; }; + 213046BB2F212BCC007ECEDA /* CGRectExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213046BA2F212BCC007ECEDA /* CGRectExtensionsTests.swift */; }; 21371FA228A6C4A200BC3F37 /* OnboardingTelemetryUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21371FA128A6C4A200BC3F37 /* OnboardingTelemetryUtilityTests.swift */; }; 21371FA428AA7A8D00BC3F37 /* OnboardingViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21371FA328AA7A8D00BC3F37 /* OnboardingViewModelProtocol.swift */; }; 2137785D297F1F2800D01309 /* DownloadedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2137785C297F1F2800D01309 /* DownloadedFile.swift */; }; @@ -1893,8 +1895,6 @@ E13C072D2C2189B80087E404 /* ToolbarActionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13C072C2C2189B70087E404 /* ToolbarActionConfiguration.swift */; }; E13F8C342928194800BDC8B4 /* PhotonActionSheetSiteHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F8C332928194800BDC8B4 /* PhotonActionSheetSiteHeaderView.swift */; }; E143BF652CE36FFD00A1D2D9 /* A11yTabTrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E143BF642CE36FF800A1D2D9 /* A11yTabTrayTests.swift */; }; - E1442FBF294782B6003680B0 /* CGRect+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1442FBE294782B6003680B0 /* CGRect+Extension.swift */; }; - E1442FC0294782B6003680B0 /* CGRect+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1442FBE294782B6003680B0 /* CGRect+Extension.swift */; }; E1442FCF294782D9003680B0 /* UIView+Screenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1442FC4294782D7003680B0 /* UIView+Screenshot.swift */; }; E1442FD0294782D9003680B0 /* UIAlertController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1442FC5294782D7003680B0 /* UIAlertController+Extension.swift */; }; E1442FD1294782D9003680B0 /* UIModalPresentationStyle+Photon.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1442FC6294782D7003680B0 /* UIModalPresentationStyle+Photon.swift */; }; @@ -2940,6 +2940,8 @@ 212985E52A72B22800546684 /* ThemeSettingsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsControllerTests.swift; sourceTree = ""; }; 213047E52F2153AB007ECEDA /* PageZoomSettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageZoomSettingsViewModelTests.swift; sourceTree = ""; }; 21304C8B2F217A59007ECEDA /* ThemeSettingsStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsStateTests.swift; sourceTree = ""; }; + 213046B82F202F27007ECEDA /* UIFontExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFontExtensionsTests.swift; sourceTree = ""; }; + 213046BA2F212BCC007ECEDA /* CGRectExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtensionsTests.swift; sourceTree = ""; }; 21371FA128A6C4A200BC3F37 /* OnboardingTelemetryUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTelemetryUtilityTests.swift; sourceTree = ""; }; 21371FA328AA7A8D00BC3F37 /* OnboardingViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModelProtocol.swift; sourceTree = ""; }; 2137785C297F1F2800D01309 /* DownloadedFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadedFile.swift; sourceTree = ""; }; @@ -10741,7 +10743,6 @@ E13C072C2C2189B70087E404 /* ToolbarActionConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarActionConfiguration.swift; sourceTree = ""; }; E13F8C332928194800BDC8B4 /* PhotonActionSheetSiteHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotonActionSheetSiteHeaderView.swift; sourceTree = ""; }; E143BF642CE36FF800A1D2D9 /* A11yTabTrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = A11yTabTrayTests.swift; sourceTree = ""; }; - E1442FBE294782B6003680B0 /* CGRect+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+Extension.swift"; sourceTree = ""; }; E1442FC4294782D7003680B0 /* UIView+Screenshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Screenshot.swift"; sourceTree = ""; }; E1442FC5294782D7003680B0 /* UIAlertController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extension.swift"; sourceTree = ""; }; E1442FC6294782D7003680B0 /* UIModalPresentationStyle+Photon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIModalPresentationStyle+Photon.swift"; sourceTree = ""; }; @@ -12090,7 +12091,6 @@ isa = PBXGroup; children = ( 9609F4C926B57CE800F81493 /* Calendar+Extension.swift */, - E1442FBE294782B6003680B0 /* CGRect+Extension.swift */, 4393931F29AC6CE900DC5A85 /* EnvironmentValues+Extension.swift */, E1A6AB4528CA6A4C00EBEBDD /* String+Extension.swift */, E1442FC5294782D7003680B0 /* UIAlertController+Extension.swift */, @@ -13601,6 +13601,8 @@ 8A96C4B728F9DD0600B75884 /* Extensions */ = { isa = PBXGroup; children = ( + 213046BA2F212BCC007ECEDA /* CGRectExtensionsTests.swift */, + 213046B82F202F27007ECEDA /* UIFontExtensionsTests.swift */, 8ADED7EB27691351009C19E6 /* CalendarExtensionsTests.swift */, 2F44FA1A1A9D426A00FD20CC /* TestHashExtensions.swift */, A83E5B1C1C1DA8D80026D912 /* UIPasteboardExtensionsTests.swift */, @@ -18289,7 +18291,6 @@ E1442FDC2947836E003680B0 /* UIView+Extension.swift in Sources */, C874FB3A2660E8B900EBE86E /* CredentialProviderPresenter.swift in Sources */, 8A9F4F112DC8F5D7004644B9 /* TabsProvider.swift in Sources */, - E1442FC0294782B6003680B0 /* CGRect+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -18870,7 +18871,6 @@ 0E6C1E212C909AD7001A43BB /* PasswordGeneratorAction.swift in Sources */, F637D6732D8CBB6900C5C500 /* BasicAnimationControllerDelegate.swift in Sources */, 8A9F4F062DC8F592004644B9 /* TabsProvider.swift in Sources */, - E1442FBF294782B6003680B0 /* CGRect+Extension.swift in Sources */, C283EECE2E4D4DC7008EB540 /* RemoteSummarizerConfigSource.swift in Sources */, 96F8DA49280452CA00E53239 /* GleanPlumbContextProvider.swift in Sources */, 0E6C1E232C909E04001A43BB /* PasswordGeneratorState.swift in Sources */, @@ -19724,6 +19724,7 @@ 4A59B58AD11B5EE1F80BBDEB /* TestHistory.swift in Sources */, A83E5B1D1C1DA8D80026D912 /* UIPasteboardExtensionsTests.swift in Sources */, 8A01FE402DF0CE4E002C483B /* MockDateProvider.swift in Sources */, + 213046BB2F212BCC007ECEDA /* CGRectExtensionsTests.swift in Sources */, C27EF61F2EBA395A00BED719 /* MockLanguageSampleSource.swift in Sources */, 8A97E6EF2C584C4900F94793 /* AddressLocaleFeatureValidatorTests.swift in Sources */, C714D7B62E4673A300FC02E6 /* ShortcutsLibraryDiffableDataSourceTests.swift in Sources */, @@ -19780,6 +19781,7 @@ E1463D042982D0240074E16E /* NotificationManagerTests.swift in Sources */, 8AA0A6682CAC747500AC7EB3 /* HomepageDiffableDataSourceTests.swift in Sources */, C8E1BC0A28085AA700C62964 /* NimbusFeatureFlagLayerTests.swift in Sources */, + 213046B92F202F27007ECEDA /* UIFontExtensionsTests.swift in Sources */, 032CE5F62EBA0C04007CCC0D /* ToUExperiencePointsCalculatorTests.swift in Sources */, AAB5AF382EDDCF600051EF6E /* MockLocaleProvider.swift in Sources */, F80D53CF2A09A3350047ED14 /* RustSyncManagerTests.swift in Sources */, diff --git a/firefox-ios/Client/Frontend/Widgets/ToggleButton.swift b/firefox-ios/Client/Frontend/Widgets/ToggleButton.swift index 50786b3b9f6c9..b9d7cfe699101 100644 --- a/firefox-ios/Client/Frontend/Widgets/ToggleButton.swift +++ b/firefox-ios/Client/Frontend/Widgets/ToggleButton.swift @@ -4,6 +4,7 @@ import UIKit import Common +import Shared private struct UX { // The amount of pixels the toggle button will expand over the normal size. diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Extensions/CGRectExtensionsTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Extensions/CGRectExtensionsTests.swift new file mode 100644 index 0000000000000..d99dccfc92988 --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Extensions/CGRectExtensionsTests.swift @@ -0,0 +1,196 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import XCTest + +@testable import Client + +final class CGRectExtensionsTests: XCTestCase { + // MARK: - init(width:height:) + + func testInitWithWidthHeight_createsRectAtOrigin() { + let rect = CGRect(width: 100, height: 50) + + XCTAssertEqual(rect.origin.x, 0) + XCTAssertEqual(rect.origin.y, 0) + XCTAssertEqual(rect.size.width, 100) + XCTAssertEqual(rect.size.height, 50) + } + + func testInitWithWidthHeight_withZeroValues() { + let rect = CGRect(width: 0, height: 0) + + XCTAssertEqual(rect, .zero) + } + + func testInitWithWidthHeight_withNegativeValues() { + let rect = CGRect(width: -10, height: -20) + + XCTAssertEqual(rect.size.width, -10) + XCTAssertEqual(rect.size.height, -20) + } + + // MARK: - init(size:) + + func testInitWithSize_createsRectAtZeroOrigin() { + let size = CGSize(width: 200, height: 150) + let rect = CGRect(size: size) + + XCTAssertEqual(rect.origin, .zero) + XCTAssertEqual(rect.size, size) + } + + func testInitWithSize_withEmptySize() { + let rect = CGRect(size: .zero) + + XCTAssertEqual(rect, .zero) + } + + // MARK: - center (getter) + + func testCenterGetter_returnsCorrectCenter() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 50) + + let center = rect.center + + XCTAssertEqual(center.x, 50) + XCTAssertEqual(center.y, 25) + } + + func testCenterGetter_withNonZeroOrigin() { + let rect = CGRect(x: 20, y: 30, width: 100, height: 50) + + let center = rect.center + + // Center should be relative to size, not absolute position + XCTAssertEqual(center.x, 50) + XCTAssertEqual(center.y, 25) + } + + func testCenterGetter_withZeroRect() { + let rect = CGRect.zero + + let center = rect.center + + XCTAssertEqual(center, .zero) + } + + // MARK: - center (setter) + + func testCenterSetter_updatesOrigin() { + var rect = CGRect(x: 0, y: 0, width: 100, height: 50) + + rect.center = CGPoint(x: 150, y: 100) + + // Origin should be adjusted so center is at (150, 100) + XCTAssertEqual(rect.origin.x, 100) // 150 - 50 (half width) + XCTAssertEqual(rect.origin.y, 75) // 100 - 25 (half height) + XCTAssertEqual(rect.size.width, 100) + XCTAssertEqual(rect.size.height, 50) + } + + func testCenterSetter_preservesSize() { + var rect = CGRect(x: 0, y: 0, width: 200, height: 100) + let originalSize = rect.size + + rect.center = CGPoint(x: 50, y: 50) + + XCTAssertEqual(rect.size, originalSize) + } + + func testCenterSetter_withZeroCenter() { + var rect = CGRect(x: 100, y: 100, width: 80, height: 60) + + rect.center = .zero + + // Expected 80 / 2 = 40 + XCTAssertEqual(rect.origin.x, -40) + // Expected 60 / 2 = 30 + XCTAssertEqual(rect.origin.y, -30) + } + + // MARK: - updateWidth(byPercentage:) + + func testUpdateWidth_with100Percent_returnsOriginalWidth() { + let rect = CGRect(x: 10, y: 20, width: 100, height: 50) + + let updated = rect.updateWidth(byPercentage: 1.0) + + XCTAssertEqual(updated.size.width, 100) + XCTAssertEqual(updated.origin, rect.origin) + XCTAssertEqual(updated.size.height, rect.size.height) + } + + func testUpdateWidth_with50Percent_halvesWidth() { + let rect = CGRect(x: 10, y: 20, width: 100, height: 50) + + let updated = rect.updateWidth(byPercentage: 0.5) + + XCTAssertEqual(updated.size.width, 50) + XCTAssertEqual(updated.origin, rect.origin) + XCTAssertEqual(updated.size.height, rect.size.height) + } + + func testUpdateWidth_with200Percent_doublesWidth() { + let rect = CGRect(x: 5, y: 10, width: 50, height: 30) + + let updated = rect.updateWidth(byPercentage: 2.0) + + XCTAssertEqual(updated.size.width, 100) + XCTAssertEqual(updated.origin, rect.origin) + XCTAssertEqual(updated.size.height, rect.size.height) + } + + func testUpdateWidth_withZeroPercent_returnsZeroWidth() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 50) + + let updated = rect.updateWidth(byPercentage: 0.0) + + XCTAssertEqual(updated.size.width, 0) + XCTAssertEqual(updated.size.height, rect.size.height) + } + + func testUpdateWidth_preservesOriginAndHeight() { + let rect = CGRect(x: 25, y: 35, width: 80, height: 60) + + let updated = rect.updateWidth(byPercentage: 0.75) + + XCTAssertEqual(updated.origin.x, 25) + XCTAssertEqual(updated.origin.y, 35) + XCTAssertEqual(updated.size.height, 60) + // Expected 80 * 0.75 = 60 + XCTAssertEqual(updated.size.width, 60) + } + + func testUpdateWidth_withNegativePercentage() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 50) + + let updated = rect.updateWidth(byPercentage: -0.5) + + XCTAssertEqual(updated.size.width, -50) + } + + // MARK: - Integration Tests + + func testCombinedOperations_initWithSizeThenUpdateCenter() { + var rect = CGRect(size: CGSize(width: 100, height: 50)) + rect.center = CGPoint(x: 200, y: 100) + + XCTAssertEqual(rect.origin.x, 150) + XCTAssertEqual(rect.origin.y, 75) + XCTAssertEqual(rect.size.width, 100) + XCTAssertEqual(rect.size.height, 50) + } + + func testCombinedOperations_updateWidthThenGetCenter() { + let rect = CGRect(x: 0, y: 0, width: 100, height: 50) + let updated = rect.updateWidth(byPercentage: 0.5) + + let center = updated.center + + // Half of new width and height (50) + XCTAssertEqual(center.x, 25) + XCTAssertEqual(center.y, 25) + } +} diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Extensions/UIFontExtensionsTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Extensions/UIFontExtensionsTests.swift new file mode 100644 index 0000000000000..a5a7e6b85b3ca --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Extensions/UIFontExtensionsTests.swift @@ -0,0 +1,116 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import XCTest + +@testable import Client + +final class UIFontExtensionsTests: XCTestCase { + // MARK: - bolded() + func testBolded_addsTraitBold() { + let font = UIFont.systemFont(ofSize: 16) + let boldFont = font.bolded() + + guard let boldFont else { + XCTFail("Expected valid bold font") + return + } + + XCTAssertTrue(boldFont.fontDescriptor.symbolicTraits.contains(.traitBold)) + } + + func testBolded_preservesFontSize() { + let originalSize: CGFloat = 24 + let font = UIFont.systemFont(ofSize: originalSize) + + guard let boldFont = font.bolded() else { + XCTFail("Expected valid bold font") + return + } + + XCTAssertEqual(boldFont.pointSize, originalSize) + } + + func testBolded_onAlreadyBoldFont_maintainsBold() { + let boldFont = UIFont.boldSystemFont(ofSize: 16) + + guard let doubleBoldFont = boldFont.bolded() else { + XCTFail("Expected valid bold font") + return + } + XCTAssertTrue(doubleBoldFont.fontDescriptor.symbolicTraits.contains(.traitBold)) + } + + // MARK: - italicized() + func testItalicized_addsTraitItalic() { + let font = UIFont.systemFont(ofSize: 16) + + guard let italicFont = font.italicized() else { + XCTFail("Expected valid italic font") + return + } + + XCTAssertTrue(italicFont.fontDescriptor.symbolicTraits.contains(.traitItalic)) + } + + func testItalicized_preservesFontSize() { + let originalSize: CGFloat = 18 + let font = UIFont.systemFont(ofSize: originalSize) + + guard let italicFont = font.italicized() else { + XCTFail("Expected valid italic font") + return + } + + XCTAssertEqual(italicFont.pointSize, originalSize) + } + + func testItalicized_onAlreadyItalicFont_maintainsItalic() { + let italicFont = UIFont.italicSystemFont(ofSize: 16) + guard let doubleItalicFont = italicFont.italicized() else { + XCTFail("Expected valid italic font") + return + } + + XCTAssertTrue(doubleItalicFont.fontDescriptor.symbolicTraits.contains(.traitItalic)) + } + + // MARK: - Combined traits + func testBolded_thenItalicized_hasBothTraits() { + let font = UIFont.systemFont(ofSize: 16) + + guard let boldFont = font.bolded(), + let boldItalicFont = boldFont.italicized() else { + XCTFail("Expected valid bold and italic font") + return + } + + let traits = boldItalicFont.fontDescriptor.symbolicTraits + XCTAssertTrue(traits.contains(.traitBold)) + XCTAssertTrue(traits.contains(.traitItalic)) + } + + func testItalicized_thenBolded_hasBothTraits() { + let font = UIFont.systemFont(ofSize: 16) + + guard let italicFont = font.italicized(), + let italicBoldFont = italicFont.bolded() else { + XCTFail("Expected valid bold and italic font") + return + } + + let traits = italicBoldFont.fontDescriptor.symbolicTraits + XCTAssertTrue(traits.contains(.traitItalic)) + XCTAssertTrue(traits.contains(.traitBold)) + } + + func testCombinedTraits_preserveOriginalSize() { + let originalSize: CGFloat = 20 + let font = UIFont.systemFont(ofSize: originalSize) + + let styledFont = font.bolded()?.italicized() + XCTAssertNotNil(styledFont, "Expected valid bold and italic font") + XCTAssertEqual(styledFont?.pointSize, originalSize) + } +}