diff --git a/Example/TSAlertController.xcodeproj/project.pbxproj b/Example/TSAlertController.xcodeproj/project.pbxproj index 88001d8..2225584 100644 --- a/Example/TSAlertController.xcodeproj/project.pbxproj +++ b/Example/TSAlertController.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; - 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; + 607FACEC1AFB9204008FA782 /* TSAlertControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* TSAlertControllerTests.swift */; }; B9CB7BB683CE0BF2C0A6D730 /* Pods_TSAlertController_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01E2986C7734B41FD672F351 /* Pods_TSAlertController_Tests.framework */; }; C7692F1E2D5CB521009AB67F /* Wanderlust.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C7692F1D2D5CB521009AB67F /* Wanderlust.ttf */; }; /* End PBXBuildFile section */ @@ -44,14 +44,25 @@ 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 607FACE51AFB9204008FA782 /* TSAlertController_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TSAlertController_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; + 607FACEB1AFB9204008FA782 /* TSAlertControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAlertControllerTests.swift; sourceTree = ""; }; 7C048B6697456CBE0C3CE64E /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; C7692F1D2D5CB521009AB67F /* Wanderlust.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Wanderlust.ttf; sourceTree = ""; }; F0B7234F0869C770636141FB /* Pods-TSAlertController_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSAlertController_Tests.debug.xcconfig"; path = "Target Support Files/Pods-TSAlertController_Tests/Pods-TSAlertController_Tests.debug.xcconfig"; sourceTree = ""; }; F20D66B1383ED02E54413689 /* Pods-TSAlertController_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSAlertController_Example.release.xcconfig"; path = "Target Support Files/Pods-TSAlertController_Example/Pods-TSAlertController_Example.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + C755164E2D6C6C2E003A34D7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + MockViewController.swift, + ); + target = 607FACCF1AFB9204008FA782 /* TSAlertController_Example */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ + C755164A2D6C6C23003A34D7 /* Mock */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (C755164E2D6C6C2E003A34D7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Mock; sourceTree = ""; }; C7C7C55D2D5AD7E00099D5CB /* CustomView */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CustomView; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ @@ -130,7 +141,8 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( - 607FACEB1AFB9204008FA782 /* Tests.swift */, + C755164A2D6C6C23003A34D7 /* Mock */, + 607FACEB1AFB9204008FA782 /* TSAlertControllerTests.swift */, 607FACE91AFB9204008FA782 /* Supporting Files */, ); path = Tests; @@ -230,6 +242,9 @@ dependencies = ( 607FACE71AFB9204008FA782 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + C755164A2D6C6C23003A34D7 /* Mock */, + ); name = TSAlertController_Tests; productName = Tests; productReference = 607FACE51AFB9204008FA782 /* TSAlertController_Tests.xctest */; @@ -378,7 +393,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, + 607FACEC1AFB9204008FA782 /* TSAlertControllerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Tests/Mock/MockViewController.swift b/Example/Tests/Mock/MockViewController.swift new file mode 100644 index 0000000..987c7f6 --- /dev/null +++ b/Example/Tests/Mock/MockViewController.swift @@ -0,0 +1,24 @@ +// +// MockViewController.swift +// TSAlertController +// +// Created by 김건우 on 2/24/25. +// Copyright © 2025 CocoaPods. All rights reserved. +// + +import UIKit + +final class MockViewController: UIViewController { + var presentViewControllerTarget: UIViewController? + + override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + // + viewControllerToPresent.loadViewIfNeeded() + // + viewControllerToPresent.beginAppearanceTransition(true, animated: flag) + viewControllerToPresent.endAppearanceTransition() + + // + presentViewControllerTarget = viewControllerToPresent + } +} diff --git a/Example/Tests/TSAlertControllerTests.swift b/Example/Tests/TSAlertControllerTests.swift new file mode 100644 index 0000000..3d0bf96 --- /dev/null +++ b/Example/Tests/TSAlertControllerTests.swift @@ -0,0 +1,221 @@ +import XCTest +@testable import TSAlertController + +final class TSAlertControllerTests: XCTestCase { + + func testTSAlertController_WhenInitialized_ShouldSetTitleMessageAndPreferredStyle() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + message: "Descriptive text that provides more details about the reason for the alert.", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + // when + mockViewController.present(alertController, animated: true) + + // then + XCTAssertEqual(alertController.title, "The title of the alert") + XCTAssertEqual(alertController.message, "Descriptive text that provides more details about the reason for the alert.") + XCTAssertEqual(alertController.preferredStyle, .alert) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenIntializedWithTextFields_ShouldSetTextFields() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + alertController.addTextField() + alertController.addTextField() + + // when + mockViewController.present(alertController, animated: true) + + // then + XCTAssertEqual(alertController.textFields?.count, 2) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenInitializedWithTwoButtonsAndAutomaticAxis_ShouldSetCorrectActionsOrderAndCount() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + let ok = TSAlertAction(title: "OK", style: .default) + let cancel = TSAlertAction(title: "Cancel", style: .cancel) + alertController.addAction(ok) + alertController.addAction(cancel) + + // when + mockViewController.present(alertController, animated: true) + + // then + let actions = alertController.actions + XCTAssertEqual(actions.count, 2) + XCTAssertEqual(actions[0], cancel) // In LTR, the Cancel button is positioned at the far right. + XCTAssertEqual(alertController.viewConfiguration.buttonGroupAxis, .horizontal) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenIntializedWithFourButtonsAndAutomaticAxis_ShouldSetCorrectActionsOrderAndCount() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + let home = TSAlertAction(title: "Go to Home", style: .default) + let profile = TSAlertAction(title: "Go to Profile", style: .default) + let settings = TSAlertAction(title: "Go to Settings", style: .default) + let cancel = TSAlertAction(title: "Cancel", style: .cancel) + alertController.addAction(home) + alertController.addAction(profile) + alertController.addAction(cancel) + alertController.addAction(settings) + + // when + mockViewController.present(alertController, animated: true) + + // then + let actions = alertController.actions + XCTAssertEqual(actions.count, 4) + XCTAssertEqual(actions[3], cancel) // If the button axis is vertical, the Cancel button is positioned at the very bottom. + XCTAssertEqual(actions, [home, profile, settings, cancel]) + XCTAssertEqual(alertController.viewConfiguration.buttonGroupAxis, .vertical) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenInitalizedWithFourButtonsAndAutomaticAxis_ShouldSetCorrectActionsOrderAndCount() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + alertController.addAction(.init(title: "Cancel", style: .cancel)) + } + + + func testTSAlertController_WhenIntlaizedWithAlertStyle_ShouldSetDefaultConfiguration() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + // when + mockViewController.present(alertController, animated: true) + + // then + let config = alertController.configuration + XCTAssertEqual(config.enteringTransition, .fadeInAndScaleDown) + XCTAssertEqual(config.exitingTransition, .fadeOut) + XCTAssertEqual(config.headerAnimation, .none) + XCTAssertEqual(config.buttonGroupAnimation, .none) + XCTAssertEqual(config.prefersGrabberVisible, false) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenIntializedWithAlertStyle_SouldSetEnforceCorrectConfiguration() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + // when + alertController.configuration.prefersGrabberVisible = true + mockViewController.present(alertController, animated: true) + + // then + let config = alertController.configuration + XCTAssertEqual(config.prefersGrabberVisible, false) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenIntializedWithFloatingSheetStyle_ShouldSetDefaultConfiguration() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .floatingSheet + ) + let mockViewController = MockViewController() + + // when + mockViewController.present(alertController, animated: true) + + // then + let config = alertController.configuration + XCTAssertEqual(config.enteringTransition, .slideUp) + XCTAssertEqual(config.exitingTransition, .slideDown) + XCTAssertEqual(config.headerAnimation, .none) + XCTAssertEqual(config.buttonGroupAnimation, .none) + XCTAssertEqual(config.prefersGrabberVisible, true) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenIntilizedWithFloatinSheetStyle_ShouldSetEnforceCorrectConfiguration() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .floatingSheet + ) + let mockViewController = MockViewController() + + // when + mockViewController.present(alertController, animated: true) + + // then + XCTAssertTrue(true) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenIntializedWithAlertStyle_ShouldSetDefaultViewConfiguration() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .alert + ) + let mockViewController = MockViewController() + + // when + mockViewController.present(alertController, animated: true) + + // then + let viewConfig = alertController.viewConfiguration + XCTAssertEqual(viewConfig.size.width, .proportional(minimumRatio: 0.75, maximumRatio: 0.75)) + XCTAssertEqual(viewConfig.spacing.keyboardSpacing, 100) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + + func testTSAlertController_WhenInitializedWithFloatingSheetStyle_ShouldSetDefaultViewConfiguration() { + // given + let alertController = TSAlertController( + title: "The title of the alert", + preferredStyle: .floatingSheet + ) + let mockViewController = MockViewController() + + // when + mockViewController.present(alertController, animated: true) + + // then + let viewConfig = alertController.viewConfiguration + XCTAssertEqual(viewConfig.size.width, .proportional(minimumRatio: 0.95, maximumRatio: 0.95)) + XCTAssertEqual(viewConfig.spacing.keyboardSpacing, 20) + XCTAssertEqual(mockViewController.presentViewControllerTarget, alertController) + } + +} diff --git a/Example/Tests/Tests.swift b/Example/Tests/Tests.swift deleted file mode 100644 index a0bbb30..0000000 --- a/Example/Tests/Tests.swift +++ /dev/null @@ -1,28 +0,0 @@ -import XCTest -import TSAlertController - -class Tests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - XCTAssert(true, "Pass") - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure() { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Sources/TSAlert/TSAlertController+AnimationType.swift b/Sources/TSAlert/TSAlertController+AnimationType.swift index 1b887dd..a14a164 100644 --- a/Sources/TSAlert/TSAlertController+AnimationType.swift +++ b/Sources/TSAlert/TSAlertController+AnimationType.swift @@ -70,3 +70,19 @@ public extension TSAlertController { } } } + + +// MARK: - Equatable + +extension TSAlertController.AnimationType: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.fadeIn, .fadeIn), (.slideUp, .slideUp): + return true + case let (.custom(lhsTransform, lhsAlpha), .custom(rhsTransform, rhsAlpha)): + return lhsTransform == rhsTransform && lhsAlpha == rhsAlpha + default: + return false + } + } +} diff --git a/Sources/TSAlert/TSAlertController+TransitionType.swift b/Sources/TSAlert/TSAlertController+TransitionType.swift index 5d181fd..43f1dc4 100644 --- a/Sources/TSAlert/TSAlertController+TransitionType.swift +++ b/Sources/TSAlert/TSAlertController+TransitionType.swift @@ -77,3 +77,31 @@ public extension TSAlertController { } } } + + +// MARK: - Equatable + +extension TSAlertController.EnteringTransitionType: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.fadeInAndScaleDown, .fadeInAndScaleDown), (.slideUp, .slideUp): + return true + case let (.custom(lhsTransition), .custom(rhsTransition)): + return ObjectIdentifier(lhsTransition) == ObjectIdentifier(rhsTransition) + default: + return false + } + } +} +extension TSAlertController.ExitingTransitionType: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.fadeOut, .fadeOut), (.slideDown, .slideDown): + return true + case let (.custom(lhsTransition), .custom(rhsTransition)): + return ObjectIdentifier(lhsTransition) == ObjectIdentifier(rhsTransition) + default: + return false + } + } +} diff --git a/Sources/TSAlertAction/TSAlertAction.swift b/Sources/TSAlertAction/TSAlertAction.swift index 3aa51df..ded86e5 100644 --- a/Sources/TSAlertAction/TSAlertAction.swift +++ b/Sources/TSAlertAction/TSAlertAction.swift @@ -160,3 +160,12 @@ private extension TSAlertAction { } } } + + +// MARK: - Equtable + +extension TSAlertAction: Equatable { + public static func == (lhs: TSAlertAction, rhs: TSAlertAction) -> Bool { + return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + } +}