@@ -21,8 +21,8 @@ final class LCLabelTests: XCTestCase {
2121 ] )
2222 let label = createLabel (
2323 text: attStr,
24- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
25- label . numberOfLines = 1
24+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
25+ numberOfLines : 1 )
2626 let failure = verifySnapshot (
2727 matching: label,
2828 as: . image,
@@ -45,8 +45,8 @@ final class LCLabelTests: XCTestCase {
4545 ] )
4646 let label = createLabel (
4747 text: attStr,
48- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
49- label . numberOfLines = 1
48+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
49+ numberOfLines : 1 )
5050 let failure = verifySnapshot (
5151 matching: label,
5252 as: . image,
@@ -64,8 +64,8 @@ final class LCLabelTests: XCTestCase {
6464 ] )
6565 let label = createLabel (
6666 text: attStr,
67- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
68- label . numberOfLines = 1
67+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
68+ numberOfLines : 1 )
6969 label. centeringTextAlignment = . top
7070 let failure = verifySnapshot (
7171 matching: label,
@@ -84,8 +84,8 @@ final class LCLabelTests: XCTestCase {
8484 ] )
8585 let label = createLabel (
8686 text: attStr,
87- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
88- label . numberOfLines = 1
87+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
88+ numberOfLines : 1 )
8989 label. centeringTextAlignment = . bottom
9090 let failure = verifySnapshot (
9191 matching: label,
@@ -104,8 +104,8 @@ final class LCLabelTests: XCTestCase {
104104 ] )
105105 let label = createLabel (
106106 text: attStr,
107- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
108- label . numberOfLines = 1
107+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
108+ numberOfLines : 1 )
109109 label. lineBreakMode = . byTruncatingTail
110110 let failure = verifySnapshot (
111111 matching: label,
@@ -124,8 +124,8 @@ final class LCLabelTests: XCTestCase {
124124 ] )
125125 let label = createLabel (
126126 text: attStr,
127- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
128- label . numberOfLines = 1
127+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
128+ numberOfLines : 1 )
129129 label. lineBreakMode = . byTruncatingMiddle
130130 let failure = verifySnapshot (
131131 matching: label,
@@ -145,8 +145,8 @@ final class LCLabelTests: XCTestCase {
145145 ] )
146146 let label = createLabel (
147147 text: attStr,
148- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
149- label . numberOfLines = 1
148+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
149+ numberOfLines : 1 )
150150 label. textInsets = UIEdgeInsets ( top: 0 , left: 30 , bottom: 0 , right: 30 )
151151 let failure = verifySnapshot (
152152 matching: label,
@@ -171,8 +171,8 @@ final class LCLabelTests: XCTestCase {
171171 ] ) )
172172 let label = createLabel (
173173 text: attStr,
174- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
175- label . numberOfLines = 2
174+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
175+ numberOfLines : 2 )
176176 let failure = verifySnapshot (
177177 matching: label,
178178 as: . image,
@@ -196,9 +196,9 @@ final class LCLabelTests: XCTestCase {
196196 ] ) )
197197 let label = createLabel (
198198 text: attStr,
199- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
199+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
200+ numberOfLines: 0 )
200201 label. lineFragmentPadding = 10
201- label. numberOfLines = 0
202202 let failure = verifySnapshot (
203203 matching: label,
204204 as: . image,
@@ -222,8 +222,8 @@ final class LCLabelTests: XCTestCase {
222222 ] ) )
223223 let label = createLabel (
224224 text: attStr,
225- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
226- label . numberOfLines = 2
225+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
226+ numberOfLines : 2 )
227227 let failure = verifySnapshot (
228228 matching: label,
229229 as: . image,
@@ -248,8 +248,8 @@ final class LCLabelTests: XCTestCase {
248248 ] ) )
249249 let label = createLabel (
250250 text: attStr,
251- frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) )
252- label . numberOfLines = 2
251+ frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 40 ) ,
252+ numberOfLines : 2 )
253253 label. linkStyleValidation = . ensure
254254 let text = label. attributedText
255255 let range = NSRange ( location: 0 , length: attStr. length)
@@ -477,8 +477,8 @@ final class LCLabelTests: XCTestCase {
477477 context: nil )
478478 let label = createLabel (
479479 text: attStr,
480- frame: CGRect ( x: 0 , y: 0 , width: width, height: size. height) )
481- label . numberOfLines = 0
480+ frame: CGRect ( x: 0 , y: 0 , width: width, height: size. height) ,
481+ numberOfLines : 0 )
482482 let failure = verifySnapshot (
483483 matching: label,
484484 as: . image,
@@ -604,8 +604,8 @@ final class LCLabelTests: XCTestCase {
604604
605605 let label = createLabel (
606606 text: attStr,
607- frame: . zero)
608- label . numberOfLines = 0
607+ frame: . zero,
608+ numberOfLines : 0 )
609609 label. translatesAutoresizingMaskIntoConstraints = false
610610 let superview = UIView ( frame: CGRect ( x: 0 , y: 0 , width: 300 , height: 300 ) )
611611 superview. backgroundColor = . purple
@@ -818,8 +818,93 @@ final class LCLabelTests: XCTestCase {
818818 XCTAssertEqual ( label. hitTest ( CGPoint ( x: 20 , y: 25 ) , with: nil ) , label)
819819 }
820820
821+ func testMultilineLinkTaps( ) {
822+ let termsOfServiceString = " Terms of service "
823+ let privacyPolicyString = " Privacy policy "
824+ let termsOfServiceURL = URL ( string: " lclabel://tos " ) !
825+ let privacyPolicyURL = URL ( string: " lclabel://p " ) !
826+ let string =
827+ " You must accept \( termsOfServiceString) and \( privacyPolicyString) "
828+ let attributedString = NSMutableAttributedString ( string: string)
829+ let termsOfServiceRange = attributedString. mutableString
830+ . range ( of: termsOfServiceString)
831+ let privacyPolicyRange = attributedString. mutableString
832+ . range ( of: privacyPolicyString)
833+ let firstRowTermsOfServiceTouchPoint = CGPoint ( x: 115 , y: 15 )
834+ let secondRowTermsOfServiceTouchPoint = CGPoint ( x: 20 , y: 26 )
835+ let privacyPolicyTouchPoint = CGPoint ( x: 102 , y: 26 )
836+ let label = LCLabel ( )
837+ let containerView = UIView ( )
838+ let viewController = UIViewController ( )
839+ let window = makeVisibleInWindow ( viewController)
840+
841+ attributedString. addAttributes (
842+ [ . lclabelLink: termsOfServiceURL] ,
843+ range: termsOfServiceRange)
844+ attributedString. addAttributes (
845+ [ . lclabelLink: privacyPolicyURL] ,
846+ range: privacyPolicyRange)
847+
848+ label. translatesAutoresizingMaskIntoConstraints = false
849+ label. isUserInteractionEnabled = true
850+ label. textInsets = . zero
851+ label. lineFragmentPadding = 0
852+ label. numberOfLines = 0
853+ label. delegate = self
854+ label. setContentCompressionResistancePriority ( . required, for: . vertical)
855+ label. attributedText = attributedString
856+ label. font = . systemFont( ofSize: 11 )
857+
858+ containerView. translatesAutoresizingMaskIntoConstraints = false
859+
860+ viewController. view. addSubview ( containerView)
861+ containerView. addSubview ( label)
862+
863+ NSLayoutConstraint . activate ( [
864+ containerView. widthAnchor. constraint ( equalToConstant: 140 ) ,
865+ containerView. topAnchor
866+ . constraint ( equalTo: viewController. view. topAnchor) ,
867+ containerView. leadingAnchor
868+ . constraint ( equalTo: viewController. view. leadingAnchor) ,
869+
870+ label. heightAnchor. constraint ( equalToConstant: 40.0 ) ,
871+ label. topAnchor. constraint ( equalTo: containerView. topAnchor) ,
872+ label. leadingAnchor. constraint ( equalTo: containerView. leadingAnchor) ,
873+ label. bottomAnchor. constraint ( equalTo: containerView. bottomAnchor) ,
874+ label. trailingAnchor. constraint ( equalTo: containerView. trailingAnchor) ,
875+ ] )
876+
877+ let dispatchFinishedExpectation =
878+ expectation ( description: " waitForDispatch " )
879+
880+ XCTAssertNil ( tappedURL)
881+
882+ // Perform a async dispatch on main to allow a draw cycle to complete before performing tests on the label
883+ DispatchQueue . main. async {
884+ self . touch ( label, at: firstRowTermsOfServiceTouchPoint)
885+ XCTAssertEqual ( self . tappedURL, termsOfServiceURL)
886+
887+ self . touch ( label, at: privacyPolicyTouchPoint)
888+ XCTAssertEqual ( self . tappedURL, privacyPolicyURL)
889+
890+ self . touch ( label, at: secondRowTermsOfServiceTouchPoint)
891+ XCTAssertEqual ( self . tappedURL, termsOfServiceURL)
892+
893+ self . touch ( label, at: CGPoint ( x: - 1000 , y: - 1000 ) )
894+ XCTAssertEqual ( self . tappedURL, termsOfServiceURL)
895+
896+ window. isHidden = true
897+
898+ dispatchFinishedExpectation. fulfill ( )
899+ }
900+
901+ wait ( for: [ dispatchFinishedExpectation] , timeout: 1.0 )
902+ }
903+
821904 // MARK: Private
822905
906+ private var tappedURL : URL ?
907+
823908 private func createLabel(
824909 text: NSMutableAttributedString ,
825910 frame: CGRect ,
@@ -830,11 +915,25 @@ final class LCLabelTests: XCTestCase {
830915 label. frame = frame
831916 label. centeringTextAlignment = alignment
832917 label. isUserInteractionEnabled = true
833- label. numberOfLines = 1
918+ label. numberOfLines = numberOfLines
834919 label. attributedText = text
835920 label. backgroundColor = . black
836921 return label
837922 }
923+
924+ private func touch( _ label: LCLabel , at point: CGPoint ) {
925+ let mockTouch = MockTouch ( location: point)
926+ let touches : Set < UITouch > = [ mockTouch]
927+
928+ label. touchesBegan ( touches, with: nil )
929+ label. touchesEnded ( touches, with: nil )
930+ }
931+ }
932+
933+ extension LCLabelTests : LCLabelDelegate {
934+ func didPress( url: URL , at point: CGPoint ) {
935+ tappedURL = url
936+ }
838937}
839938
840939extension XCTestCase {
@@ -845,5 +944,33 @@ extension XCTestCase {
845944 . deletingLastPathComponent ( )
846945 return " \( fileUrl. path) /Sources/LCLabel/LCLabel.docc/Resources/__snapshots__ "
847946 }
947+ }
848948
949+ extension XCTestCase {
950+ func makeVisibleInWindow( _ viewController: UIViewController ) -> UIWindow {
951+ let window = UIWindow ( )
952+
953+ window. rootViewController = viewController
954+ window. makeKeyAndVisible ( )
955+
956+ return window
957+ }
958+ }
959+
960+ private class MockTouch : UITouch {
961+
962+ // MARK: Lifecycle
963+
964+ required init ( location: CGPoint ) {
965+ self . location = location
966+ super. init ( )
967+ }
968+
969+ // MARK: Internal
970+
971+ let location : CGPoint
972+
973+ override func location( in view: UIView ? ) -> CGPoint {
974+ location
975+ }
849976}
0 commit comments