From 6a8a91c96957cef441036a177e140c9495c93125 Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 12:53:44 -0500 Subject: [PATCH 1/8] Add inheritance tests file --- Example/Tests/InheritanceTests.swift | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Example/Tests/InheritanceTests.swift diff --git a/Example/Tests/InheritanceTests.swift b/Example/Tests/InheritanceTests.swift new file mode 100644 index 0000000..011bf6b --- /dev/null +++ b/Example/Tests/InheritanceTests.swift @@ -0,0 +1,28 @@ +// +// InheritanceTests.swift +// +// +// Created by Jonathan Cole on 11/30/21. +// + +import Stackable +import XCTest + +class InheritingStackView: UIStackView {} + +/** + In these tests, we make sure that classes inheriting from + UIStackView also have full Stackable capabilities applied. + */ +class InheritanceTests: XCTestCase { + + func testDerivingClassHasStackableAddMethod() { + let stack = InheritingStackView() + stack.stackable.add([ + CGFloat(0), + ]) + + XCTAssert(true, "Passes if it compiles.") + } + +} From c722c4b7950e3d928459144b59ae6705f6c5f41f Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 13:25:56 -0500 Subject: [PATCH 2/8] Add inheritance tests to example project --- Example/Stackable.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Example/Stackable.xcodeproj/project.pbxproj b/Example/Stackable.xcodeproj/project.pbxproj index 1ac6997..0f04fc2 100644 --- a/Example/Stackable.xcodeproj/project.pbxproj +++ b/Example/Stackable.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 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 */; }; + B45CA0132756A40000D0AA70 /* InheritanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B45CA0122756A40000D0AA70 /* InheritanceTests.swift */; }; CC72A6DDDA4E7D960CD3D10E /* Pods_Stackable_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0285B0ADF6F8DDE8F69E6BD5 /* Pods_Stackable_Tests.framework */; }; EC5575EECB7557FB0D2EFDA7 /* Pods_Stackable_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 680380F6848D1F3CCB8C984C /* Pods_Stackable_Example.framework */; }; /* End PBXBuildFile section */ @@ -52,6 +53,7 @@ 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 680380F6848D1F3CCB8C984C /* Pods_Stackable_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stackable_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B32187B246E3626547391D4F /* Pods-Stackable_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stackable_Example.debug.xcconfig"; path = "Target Support Files/Pods-Stackable_Example/Pods-Stackable_Example.debug.xcconfig"; sourceTree = ""; }; + B45CA0122756A40000D0AA70 /* InheritanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InheritanceTests.swift; sourceTree = ""; }; C0FB5FA864AC1DCB745092FA /* Pods-Stackable_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stackable_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Stackable_Tests/Pods-Stackable_Tests.debug.xcconfig"; sourceTree = ""; }; C8B792787C4B69CB30904496 /* Pods-Stackable_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stackable_Example.release.xcconfig"; path = "Target Support Files/Pods-Stackable_Example/Pods-Stackable_Example.release.xcconfig"; sourceTree = ""; }; EA360EB26E10CF4FC56BDD6E /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; @@ -143,6 +145,7 @@ 607FACE81AFB9204008FA782 /* Tests */ = { isa = PBXGroup; children = ( + B45CA0122756A40000D0AA70 /* InheritanceTests.swift */, 607FACEB1AFB9204008FA782 /* Tests.swift */, 00392CDF24EAF2FC001E2F99 /* MemoryTests.swift */, 607FACE91AFB9204008FA782 /* Supporting Files */, @@ -364,6 +367,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B45CA0132756A40000D0AA70 /* InheritanceTests.swift in Sources */, 00392CE024EAF2FC001E2F99 /* MemoryTests.swift in Sources */, 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, ); From df00c235284c6d62ae6dfae22273eb288269495d Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 13:33:40 -0500 Subject: [PATCH 3/8] Add testing target to SPM package --- Package.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Package.swift b/Package.swift index 5931a0c..ccc09e6 100644 --- a/Package.swift +++ b/Package.swift @@ -21,5 +21,9 @@ let package = Package( name: "Stackable", dependencies: [], path: "Stackable"), + .testTarget( + name: "StackableTests", + dependencies: ["Stackable"], + path: "Example/Tests"), ] ) From 3d601776fefbcc0155a46bcd5c80384707aec540 Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 14:00:56 -0500 Subject: [PATCH 4/8] Remove _Pods.xcodeproj This is an alias that doesn't get used. We need to remove it, or xcodebuild won't be able to parse schemes from Package.swift. --- _Pods.xcodeproj | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _Pods.xcodeproj diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj deleted file mode 120000 index 3c5a8e7..0000000 --- a/_Pods.xcodeproj +++ /dev/null @@ -1 +0,0 @@ -Example/Pods/Pods.xcodeproj \ No newline at end of file From 86e3c591db1be367d5324bc2d95a1c17e917c740 Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 14:22:39 -0500 Subject: [PATCH 5/8] Add GitHub workflow for testing package --- .github/workflows/PackageTests.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/PackageTests.yml diff --git a/.github/workflows/PackageTests.yml b/.github/workflows/PackageTests.yml new file mode 100644 index 0000000..8b8a229 --- /dev/null +++ b/.github/workflows/PackageTests.yml @@ -0,0 +1,17 @@ +name: PackageTests + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + build: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + - name: Run tests + run: xcodebuild test -scheme RPStackable -destination 'platform=iOS Simulator,name=iPhone 12' From f4612d1e96b414e467a53840eec9eef0e111a94f Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 14:32:07 -0500 Subject: [PATCH 6/8] Fix inheritance issues as defined by new tests --- Stackable/Stackable+A11y.swift | 2 +- Stackable/Stackable+Debug.swift | 2 +- Stackable/Stackable+Hairlines.swift | 25 +++++++++++++++++++------ Stackable/Stackable+Spacing.swift | 2 +- Stackable/Stackable.swift | 2 +- Stackable/UIStackView+Utilities.swift | 2 +- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Stackable/Stackable+A11y.swift b/Stackable/Stackable+A11y.swift index 474fde1..4e3cd11 100644 --- a/Stackable/Stackable+A11y.swift +++ b/Stackable/Stackable+A11y.swift @@ -21,7 +21,7 @@ internal enum DebugAccessibilityID { public static let margin = "com.rightpoint.stackable.debug.margin" } -public extension StackableExtension where ExtendedType == UIStackView { +public extension StackableExtension where ExtendedType: UIStackView { typealias axID = StackableAccessibilityID diff --git a/Stackable/Stackable+Debug.swift b/Stackable/Stackable+Debug.swift index 9a95d95..aa00bf6 100644 --- a/Stackable/Stackable+Debug.swift +++ b/Stackable/Stackable+Debug.swift @@ -8,7 +8,7 @@ import UIKit -public extension StackableExtension where ExtendedType == UIStackView { +public extension StackableExtension where ExtendedType: UIStackView { /// Debug API extension point var debug: DebugStackableExtension { diff --git a/Stackable/Stackable+Hairlines.swift b/Stackable/Stackable+Hairlines.swift index ceca851..1f77c7e 100644 --- a/Stackable/Stackable+Hairlines.swift +++ b/Stackable/Stackable+Hairlines.swift @@ -46,7 +46,7 @@ public struct StackableHairline { } // MARK: - Public API -public extension StackableExtension where ExtendedType == UIStackView { +public extension StackableExtension where ExtendedType: UIStackView { /// Add a hairline to the stackView static var hairline: StackableHairline { return .init(type: .next) @@ -411,6 +411,10 @@ public extension UIStackView { static var hairlineColor = "hairlineColor" static var hairlineThickness = "hairlineThickness" static var hairlineProvider = "hairlineProvider" + + static var staticHairlineColor = "staticHairlineColor" + static var staticHairlineThickness = "staticHairlineThickness" + static var staticHairlineProvider = "staticHairlineProvider" } fileprivate struct Default { @@ -419,7 +423,7 @@ public extension UIStackView { } } -public extension StackableExtension where ExtendedType == UIStackView { +public extension StackableExtension where ExtendedType: UIStackView { /// Instance-based `UIStackView` color override. Ultimate styling will prefer hairline-instance config first, but will prefer this over `UIStackView` static config. var hairlineColor: UIColor? { @@ -438,14 +442,23 @@ public extension StackableExtension where ExtendedType == UIStackView { get { return objc_getAssociatedObject(base, &type(of: base).AssociatedKeys.hairlineProvider) as? StackableHairlineProvider } set { objc_setAssociatedObject(base, &type(of: base).AssociatedKeys.hairlineProvider, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } - + /// Static `UIStackView` color override. Ultimate styling will prefer hairline-instance config first, then `UIStackView` instance config. - static var hairlineColor: UIColor? + static var hairlineColor: UIColor? { + get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineColor) as? UIColor } + set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + } /// Static `UIStackView` thickness override. Ultimate styling will prefer hairline-instance config first, then `UIStackView` instance config. - static var hairlineThickness: CGFloat? + static var hairlineThickness: CGFloat? { + get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineThickness) as? CGFloat } + set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineThickness, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + } /// Static `UIStackView` hairlineProvider override. Ultimate styling will prefer `UIStackView` instance config. - static var hairlineProvider: StackableHairlineProvider? + static var hairlineProvider: StackableHairlineProvider? { + get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineProvider) as? StackableHairlineProvider } + set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineProvider, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + } } diff --git a/Stackable/Stackable+Spacing.swift b/Stackable/Stackable+Spacing.swift index 3f08fba..c75ecf1 100644 --- a/Stackable/Stackable+Spacing.swift +++ b/Stackable/Stackable+Spacing.swift @@ -53,7 +53,7 @@ public enum StackableFlexibleSpace { // MARK: - Public API -public extension StackableExtension where ExtendedType == UIStackView { +public extension StackableExtension where ExtendedType: UIStackView { /// Add a space to the stackView. If added after a view, will mirror the visibility of that view. /// - Parameter space: The size of the space. /// - Returns: A `Stackable` that represents the space. diff --git a/Stackable/Stackable.swift b/Stackable/Stackable.swift index d85e8a8..7358cc1 100644 --- a/Stackable/Stackable.swift +++ b/Stackable/Stackable.swift @@ -102,7 +102,7 @@ extension StackableView { // MARK: - UIStackView Public API extension UIView: StackableExtended {} -extension StackableExtension where ExtendedType == UIStackView { +extension StackableExtension where ExtendedType: UIStackView { /** Adds a `Stackable` item to the stackView. diff --git a/Stackable/UIStackView+Utilities.swift b/Stackable/UIStackView+Utilities.swift index 57e3264..4991b53 100644 --- a/Stackable/UIStackView+Utilities.swift +++ b/Stackable/UIStackView+Utilities.swift @@ -9,7 +9,7 @@ import UIKit // MARK: - UIStackView Utilities -extension StackableExtension where ExtendedType == UIStackView { +extension StackableExtension where ExtendedType: UIStackView { /// Removes all `stack.arrangedSubviews` from the `UIStackView` public func removeAllArrangedSubviews() { From 648aa1c3171b2edcf8379976455c77ca895b47a4 Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Tue, 30 Nov 2021 14:42:56 -0500 Subject: [PATCH 7/8] Remove unnecessary keys --- Stackable/Stackable+Hairlines.swift | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Stackable/Stackable+Hairlines.swift b/Stackable/Stackable+Hairlines.swift index 1f77c7e..a9dd0cb 100644 --- a/Stackable/Stackable+Hairlines.swift +++ b/Stackable/Stackable+Hairlines.swift @@ -411,10 +411,6 @@ public extension UIStackView { static var hairlineColor = "hairlineColor" static var hairlineThickness = "hairlineThickness" static var hairlineProvider = "hairlineProvider" - - static var staticHairlineColor = "staticHairlineColor" - static var staticHairlineThickness = "staticHairlineThickness" - static var staticHairlineProvider = "staticHairlineProvider" } fileprivate struct Default { @@ -445,20 +441,20 @@ public extension StackableExtension where ExtendedType: UIStackView { /// Static `UIStackView` color override. Ultimate styling will prefer hairline-instance config first, then `UIStackView` instance config. static var hairlineColor: UIColor? { - get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineColor) as? UIColor } - set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.hairlineColor) as? UIColor } + set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.hairlineColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /// Static `UIStackView` thickness override. Ultimate styling will prefer hairline-instance config first, then `UIStackView` instance config. static var hairlineThickness: CGFloat? { - get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineThickness) as? CGFloat } - set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineThickness, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.hairlineThickness) as? CGFloat } + set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.hairlineThickness, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /// Static `UIStackView` hairlineProvider override. Ultimate styling will prefer `UIStackView` instance config. static var hairlineProvider: StackableHairlineProvider? { - get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineProvider) as? StackableHairlineProvider } - set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.staticHairlineProvider, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + get { return objc_getAssociatedObject(self, &UIStackView.AssociatedKeys.hairlineProvider) as? StackableHairlineProvider } + set { objc_setAssociatedObject(self, &UIStackView.AssociatedKeys.hairlineProvider, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } From ede08f5d7e93e1d846d5c3eb1619956d66f1b25f Mon Sep 17 00:00:00 2001 From: Jonathan Cole Date: Wed, 1 Dec 2021 14:03:20 -0500 Subject: [PATCH 8/8] Add tests to verify hairline config works properly after changes --- Example/Tests/StackableTests.swift | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Example/Tests/StackableTests.swift diff --git a/Example/Tests/StackableTests.swift b/Example/Tests/StackableTests.swift new file mode 100644 index 0000000..5d9e284 --- /dev/null +++ b/Example/Tests/StackableTests.swift @@ -0,0 +1,48 @@ +// +// StackableTests.swift +// +// +// Created by Jonathan Cole on 12/1/21. +// + +@testable import Stackable +import XCTest + +class StackableTests: XCTestCase { + + func testHairlineConfigLogic() { + // Test Global config + UIStackView.stackable.hairlineColor = .blue + let stack1 = UIStackView() + stack1.stackable.add([ + UIStackView.stackable.hairline, + ]) + let hairline1 = stack1.arrangedSubviews.first as! StackableHairlineView + XCTAssert(hairline1.backgroundColor == .blue) + + // Test instance config + let stack2 = UIStackView() + stack2.stackable.hairlineColor = .brown + stack2.stackable.add([ + UIStackView.stackable.hairline, + ]) + let hairline2 = stack2.arrangedSubviews.first as! StackableHairlineView + XCTAssert(hairline2.backgroundColor == .brown) + + // Test per-hairline Config + let stack3 = UIStackView() + stack3.stackable.hairlineColor = .brown + stack3.stackable.add([ + UIStackView.stackable.hairline, + UIStackView.stackable.hairline + .color(.yellow), + ]) + let hairline3 = stack3.arrangedSubviews.first as! StackableHairlineView + XCTAssert(hairline3.backgroundColor == .brown) + + let hairline4 = stack3.arrangedSubviews.last as! StackableHairlineView + XCTAssert(hairline4.backgroundColor == .yellow) + + } + +}