damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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