PostViewTests.swift (14246B)
1 // 2 // PostViewTests.swift 3 // damusTests 4 // 5 // Created by Daniel D’Aquino on 2023-08-19. 6 // 7 import Foundation 8 import XCTest 9 import SnapshotTesting 10 import SwiftUI 11 @testable import damus 12 import SwiftUI 13 14 final class PostViewTests: XCTestCase { 15 16 override func setUpWithError() throws { 17 // Put setup code here. This method is called before the invocation of each test method in the class. 18 } 19 20 override func tearDownWithError() throws { 21 // Put teardown code here. This method is called after the invocation of each test method in the class. 22 } 23 24 /* 25 func testTextWrapperViewWillWrapText() { 26 // Setup test variables to be passed into the TextViewWrapper 27 let tagModel: TagModel = TagModel() 28 var textHeight: CGFloat? = nil 29 let textHeightBinding: Binding<CGFloat?> = Binding(get: { 30 return textHeight 31 }, set: { newValue in 32 textHeight = newValue 33 }) 34 35 // Setup the test view 36 let textEditorView = TextViewWrapper( 37 attributedText: .constant(NSMutableAttributedString(string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")), 38 textHeight: textHeightBinding, 39 initialTextSuffix: nil, 40 cursorIndex: 9, 41 updateCursorPosition: { _ in return } 42 ).environmentObject(tagModel) 43 let hostView = UIHostingController(rootView: textEditorView) 44 45 // Run snapshot check 46 assertSnapshot(matching: hostView, as: .image(on: .iPhoneSe(.portrait))) 47 } 48 */ 49 50 /// Based on https://github.com/damus-io/damus/issues/1375 51 /// Tests whether the editor properly handles mention links after they have been added, to avoid manual editing of attributed links 52 func testMentionLinkEditorHandling() throws { 53 var content: NSMutableAttributedString 54 55 // Test normal insertion 56 checkMentionLinkEditorHandling(content: NSMutableAttributedString(string: "Hello"), replacementText: "@", replacementRange: NSRange(location: 0, length: 0), shouldBeAbleToChangeAutomatically: true) 57 checkMentionLinkEditorHandling(content: NSMutableAttributedString(string: "Hello "), replacementText: "@", replacementRange: NSRange(location: 6, length: 0), shouldBeAbleToChangeAutomatically: true) 58 checkMentionLinkEditorHandling(content: NSMutableAttributedString(string: "Helo "), replacementText: "l", replacementRange: NSRange(location: 3, length: 0), shouldBeAbleToChangeAutomatically: true) 59 60 // Test normal backspacing 61 checkMentionLinkEditorHandling(content: NSMutableAttributedString(string: "Hello"), replacementText: "", replacementRange: NSRange(location: 5, length: 1), shouldBeAbleToChangeAutomatically: true) 62 checkMentionLinkEditorHandling(content: NSMutableAttributedString(string: "Hello "), replacementText: "", replacementRange: NSRange(location: 6, length: 1), shouldBeAbleToChangeAutomatically: true) 63 checkMentionLinkEditorHandling(content: NSMutableAttributedString(string: "Helo "), replacementText: "", replacementRange: NSRange(location: 3, length: 1), shouldBeAbleToChangeAutomatically: true) 64 65 // Test normal insertion after mention link 66 content = NSMutableAttributedString(string: "Hello @user ") 67 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 68 checkMentionLinkEditorHandling(content: content, replacementText: "a", replacementRange: NSRange(location: 12, length: 0), shouldBeAbleToChangeAutomatically: true) 69 70 // Test insertion right at the end of a mention link, at the end of the text 71 content = NSMutableAttributedString(string: "Hello @user") 72 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 73 checkMentionLinkEditorHandling(content: content, replacementText: ",", replacementRange: NSRange(location: 11, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 12, handleNewContent: { newManuallyEditedContent in 74 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user,") 75 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 76 }) 77 78 // Test insertion right at the end of a mention link, in the middle of the text 79 content = NSMutableAttributedString(string: "Hello @user how are you?") 80 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 81 checkMentionLinkEditorHandling(content: content, replacementText: ",", replacementRange: NSRange(location: 11, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 12, handleNewContent: { newManuallyEditedContent in 82 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user, how are you?") 83 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 84 }) 85 86 // Test insertion in the middle of a mention link to check if the link is removed 87 content = NSMutableAttributedString(string: "Hello @user how are you?") 88 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 89 checkMentionLinkEditorHandling(content: content, replacementText: "a", replacementRange: NSRange(location: 8, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 9, handleNewContent: { newManuallyEditedContent in 90 XCTAssertEqual(newManuallyEditedContent.string, "Hello @uaser how are you?") 91 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 8, effectiveRange: nil)) 92 }) 93 94 // Test insertion in the middle of a mention link to check if the link is removed, at the end of the text 95 content = NSMutableAttributedString(string: "Hello @user") 96 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 97 checkMentionLinkEditorHandling(content: content, replacementText: "a", replacementRange: NSRange(location: 8, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 9, handleNewContent: { newManuallyEditedContent in 98 XCTAssertEqual(newManuallyEditedContent.string, "Hello @uaser") 99 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 8, effectiveRange: nil)) 100 }) 101 102 // Test backspacing right at the end of a mention link, at the end of the text 103 content = NSMutableAttributedString(string: "Hello @user") 104 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 105 checkMentionLinkEditorHandling(content: content, replacementText: "", replacementRange: NSRange(location: 10, length: 1), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 10, handleNewContent: { newManuallyEditedContent in 106 XCTAssertEqual(newManuallyEditedContent.string, "Hello @use") 107 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 6, effectiveRange: nil)) 108 }) 109 110 // Test adding text right at the start of a mention link, to check that the link is removed 111 content = NSMutableAttributedString(string: "Hello @user") 112 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 113 checkMentionLinkEditorHandling(content: content, replacementText: "a", replacementRange: NSRange(location: 6, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 7, handleNewContent: { newManuallyEditedContent in 114 XCTAssertEqual(newManuallyEditedContent.string, "Hello a@user") 115 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 7, effectiveRange: nil)) 116 }) 117 118 // Test that removing one link does not affect the other 119 content = NSMutableAttributedString(string: "Hello @user1 @user2") 120 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 6)) 121 content.addAttribute(.link, value: "damus:5678", range: NSRange(location: 13, length: 6)) 122 checkMentionLinkEditorHandling(content: content, replacementText: "", replacementRange: NSRange(location: 18, length: 1), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 18, handleNewContent: { newManuallyEditedContent in 123 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user1 @user") 124 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 13, effectiveRange: nil)) 125 XCTAssertNotNil(newManuallyEditedContent.attribute(.link, at: 6, effectiveRange: nil)) 126 }) 127 128 // Test that replacing a whole range intersecting with two links removes both links 129 content = NSMutableAttributedString(string: "Hello @user1 @user2") 130 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 6)) 131 content.addAttribute(.link, value: "damus:5678", range: NSRange(location: 13, length: 6)) 132 checkMentionLinkEditorHandling(content: content, replacementText: "a", replacementRange: NSRange(location: 10, length: 4), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 11, handleNewContent: { newManuallyEditedContent in 133 XCTAssertEqual(newManuallyEditedContent.string, "Hello @useauser2") 134 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 6, effectiveRange: nil)) 135 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 136 }) 137 138 // Test that replacing a whole range including two links removes both links naturally 139 content = NSMutableAttributedString(string: "Hello @user1 @user2, how are you?") 140 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 6)) 141 content.addAttribute(.link, value: "damus:5678", range: NSRange(location: 13, length: 6)) 142 checkMentionLinkEditorHandling(content: content, replacementText: "", replacementRange: NSRange(location: 5, length: 28), shouldBeAbleToChangeAutomatically: true) 143 144 } 145 146 func testMentionLinkEditorHandling_noWhitespaceAfterLink1_addsWhitespace() { 147 var content: NSMutableAttributedString 148 149 content = NSMutableAttributedString(string: "Hello @user ") 150 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 151 checkMentionLinkEditorHandling(content: content, replacementText: "up", replacementRange: NSRange(location: 11, length: 1), shouldBeAbleToChangeAutomatically: true, expectedNewCursorIndex: 13, handleNewContent: { newManuallyEditedContent in 152 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user up") 153 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 154 }) 155 } 156 157 func testMentionLinkEditorHandling_noWhitespaceAfterLink2_addsWhitespace() { 158 var content: NSMutableAttributedString 159 160 content = NSMutableAttributedString(string: "Hello @user test") 161 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 162 checkMentionLinkEditorHandling(content: content, replacementText: "up", replacementRange: NSRange(location: 11, length: 1), shouldBeAbleToChangeAutomatically: true, expectedNewCursorIndex: 13, handleNewContent: { newManuallyEditedContent in 163 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user uptest") 164 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 165 }) 166 } 167 168 func testMentionLinkEditorHandling_nonAlphanumericAfterLink_noWhitespaceAdded() { 169 let nonAlphaNumerics = [" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "@", "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~"] 170 171 nonAlphaNumerics.forEach { testAddingStringAfterLink(str: $0)} 172 } 173 } 174 175 func checkMentionLinkEditorHandling( 176 content: NSMutableAttributedString, 177 replacementText: String, 178 replacementRange: NSRange, 179 shouldBeAbleToChangeAutomatically: Bool, 180 expectedNewCursorIndex: Int? = nil, 181 handleNewContent: ((NSMutableAttributedString) -> Void)? = nil) { 182 let bindingContent: Binding<NSMutableAttributedString> = Binding(get: { 183 return content 184 }, set: { newValue in 185 handleNewContent?(newValue) 186 }) 187 let coordinator: TextViewWrapper.Coordinator = TextViewWrapper.Coordinator(attributedText: bindingContent, getFocusWordForMention: nil, updateCursorPosition: { newCursorIndex in 188 if let expectedNewCursorIndex { 189 XCTAssertEqual(newCursorIndex, expectedNewCursorIndex) 190 } 191 }, initialTextSuffix: nil) 192 let textView = UITextView() 193 textView.attributedText = content 194 195 XCTAssertEqual(coordinator.textView(textView, shouldChangeTextIn: replacementRange, replacementText: replacementText), shouldBeAbleToChangeAutomatically, "Expected shouldChangeTextIn to return \(shouldBeAbleToChangeAutomatically), but was \(!shouldBeAbleToChangeAutomatically)") 196 } 197 198 func testAddingStringAfterLink(str: String) { 199 var content: NSMutableAttributedString 200 201 content = NSMutableAttributedString(string: "Hello @user test") 202 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 203 checkMentionLinkEditorHandling(content: content, replacementText: str, replacementRange: NSRange(location: 11, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 12, handleNewContent: { newManuallyEditedContent in 204 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user" + str + " test") 205 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 206 }) 207 } 208 209