PostViewTests.swift (15803B)
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 func testQuoteRepost() { 175 let post = build_post(state: test_damus_state, post: .init(), action: .quoting(test_note), uploadedMedias: [], pubkeys: []) 176 177 XCTAssertEqual(post.tags, [["q", test_note.id.hex(), "", jack_keypair.pubkey.hex()], ["p", jack_keypair.pubkey.hex()]]) 178 } 179 180 func testBuildPostRecognizesStringsAsNpubs() throws { 181 // given 182 let expectedLink = "nostr:\(test_pubkey.npub)" 183 let content = NSMutableAttributedString(string: "@test", attributes: [ 184 NSAttributedString.Key.link: "damus:\(expectedLink)" 185 ]) 186 187 // when 188 let post = build_post( 189 state: test_damus_state, 190 post: content, 191 action: .posting(.user(test_pubkey)), 192 uploadedMedias: [], 193 pubkeys: [] 194 ) 195 196 // then 197 XCTAssertEqual(post.content, expectedLink) 198 } 199 200 func testBuildPostRecognizesUrlsAsNpubs() throws { 201 // given 202 guard let npubUrl = URL(string: "damus:nostr:\(test_pubkey.npub)") else { 203 return XCTFail("Could not create URL") 204 } 205 let content = NSMutableAttributedString(string: "@test", attributes: [ 206 NSAttributedString.Key.link: npubUrl 207 ]) 208 209 // when 210 let post = build_post( 211 state: test_damus_state, 212 post: content, 213 action: .posting(.user(test_pubkey)), 214 uploadedMedias: [], 215 pubkeys: [] 216 ) 217 218 // then 219 XCTAssertEqual(post.content, "nostr:\(test_pubkey.npub)") 220 } 221 } 222 223 func checkMentionLinkEditorHandling( 224 content: NSMutableAttributedString, 225 replacementText: String, 226 replacementRange: NSRange, 227 shouldBeAbleToChangeAutomatically: Bool, 228 expectedNewCursorIndex: Int? = nil, 229 handleNewContent: ((NSMutableAttributedString) -> Void)? = nil) { 230 let bindingContent: Binding<NSMutableAttributedString> = Binding(get: { 231 return content 232 }, set: { newValue in 233 handleNewContent?(newValue) 234 }) 235 let coordinator: TextViewWrapper.Coordinator = TextViewWrapper.Coordinator(attributedText: bindingContent, getFocusWordForMention: nil, updateCursorPosition: { newCursorIndex in 236 if let expectedNewCursorIndex { 237 XCTAssertEqual(newCursorIndex, expectedNewCursorIndex) 238 } 239 }, initialTextSuffix: nil) 240 let textView = UITextView() 241 textView.attributedText = content 242 243 XCTAssertEqual(coordinator.textView(textView, shouldChangeTextIn: replacementRange, replacementText: replacementText), shouldBeAbleToChangeAutomatically, "Expected shouldChangeTextIn to return \(shouldBeAbleToChangeAutomatically), but was \(!shouldBeAbleToChangeAutomatically)") 244 } 245 246 func testAddingStringAfterLink(str: String) { 247 var content: NSMutableAttributedString 248 249 content = NSMutableAttributedString(string: "Hello @user test") 250 content.addAttribute(.link, value: "damus:1234", range: NSRange(location: 6, length: 5)) 251 checkMentionLinkEditorHandling(content: content, replacementText: str, replacementRange: NSRange(location: 11, length: 0), shouldBeAbleToChangeAutomatically: false, expectedNewCursorIndex: 12, handleNewContent: { newManuallyEditedContent in 252 XCTAssertEqual(newManuallyEditedContent.string, "Hello @user" + str + " test") 253 XCTAssertNil(newManuallyEditedContent.attribute(.link, at: 11, effectiveRange: nil)) 254 }) 255 } 256 257