commit f1fdae59573f6961b3c1f241c3f8949b88918e7c
parent f96647fa407b3df2842dac0632b9368eba4f131b
Author: Terry Yiu <git@tyiu.xyz>
Date: Sat, 1 Mar 2025 16:30:25 -0500
Fix note rendering for those that contain previewable items or leading and trailing whitespaces
Changelog-Fixed: Fixed note rendering for those that contain previewable items or leading and trailing whitespaces
Closes: https://github.com/damus-io/damus/issues/2187
Signed-off-by: Terry Yiu <git@tyiu.xyz>
Diffstat:
7 files changed, 387 insertions(+), 66 deletions(-)
diff --git a/damus/Components/TranslateView.swift b/damus/Components/TranslateView.swift
@@ -160,7 +160,7 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set
// Render translated note
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
- let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
+ let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true)
// and cache it
return .translated(Translated(artifacts: artifacts, language: note_lang))
diff --git a/damus/Models/NoteContent.swift b/damus/Models/NoteContent.swift
@@ -73,85 +73,129 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, keypair: Keypair) -
return .longform(LongformContent(ev.content))
}
- return .separated(render_blocks(blocks: blocks, profiles: profiles))
+ return .separated(render_blocks(blocks: blocks, profiles: profiles, can_hide_last_previewable_refs: true))
}
-func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSeparated {
+func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated {
var invoices: [Invoice] = []
var urls: [UrlType] = []
let blocks = bs.blocks
-
- let one_note_ref = blocks
- .filter({
- if case .mention(let mention) = $0,
- case .note = mention.ref {
- return true
- }
- else {
- return false
+
+ var end_mention_count = 0
+ var end_url_count = 0
+
+ // Search backwards until we find the beginning index of the chain of previewables that reach the end of the content.
+ var hide_text_index = blocks.endIndex
+ if can_hide_last_previewable_refs {
+ outerLoop: for (i, block) in blocks.enumerated().reversed() {
+ if block.is_previewable {
+ switch block {
+ case .mention:
+ end_mention_count += 1
+
+ // If there is more than one previewable mention,
+ // do not hide anything because we allow rich rendering of only one mention currently.
+ // This should be fixed in the future to show events inline instead.
+ if end_mention_count > 1 {
+ hide_text_index = blocks.endIndex
+ break outerLoop
+ }
+ case .url(let url):
+ let url_type = classify_url(url)
+ if case .link = url_type {
+ end_url_count += 1
+
+ // If there is more than one link, do not hide anything because we allow rich rendering of only
+ // one link.
+ if end_url_count > 1 {
+ hide_text_index = blocks.endIndex
+ break outerLoop
+ }
+ }
+ default:
+ break
+ }
+ hide_text_index = i
+ } else if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+ hide_text_index = i
+ } else {
+ break
}
- })
- .count == 1
-
+ }
+ }
+
var ind: Int = -1
let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in
ind = ind + 1
-
+
+ // Add the rendered previewable blocks to their type-specific lists.
switch block {
- case .mention(let m):
- if case .note = m.ref, one_note_ref {
+ case .invoice(let invoice):
+ invoices.append(invoice)
+ case .url(let url):
+ let url_type = classify_url(url)
+ urls.append(url_type)
+ default:
+ break
+ }
+
+ if can_hide_last_previewable_refs {
+ // If there are previewable blocks that occur before the consecutive sequence of them at the end of the content,
+ // we should not hide the text representation of any previewable block to avoid altering the format of the note.
+ if ind < hide_text_index && block.is_previewable {
+ hide_text_index = blocks.endIndex
+ }
+
+ // No need to show the text representation of the block if the only previewables are the sequence of them
+ // found at the end of the content.
+ // This is to save unnecessary use of screen space.
+ if ind >= hide_text_index {
return str
}
+ }
+
+ switch block {
+ case .mention(let m):
return str + mention_str(m, profiles: profiles)
case .text(let txt):
- return str + CompatibleText(stringLiteral: reduce_text_block(blocks: blocks, ind: ind, txt: txt, one_note_ref: one_note_ref))
-
+ return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: hide_text_index, txt: txt))
case .relay(let relay):
return str + CompatibleText(stringLiteral: relay)
-
case .hashtag(let htag):
return str + hashtag_str(htag)
case .invoice(let invoice):
- invoices.append(invoice)
- return str
+ return str + invoice_str(invoice)
case .url(let url):
- let url_type = classify_url(url)
- switch url_type {
- case .media:
- urls.append(url_type)
- return str
- case .link(let url):
- urls.append(url_type)
- return str + url_str(url)
- }
+ return str + url_str(url)
}
}
return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices)
}
-func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Bool) -> String {
+func reduce_text_block(ind: Int, hide_text_index: Int, txt: String) -> String {
var trimmed = txt
-
- if let prev = blocks[safe: ind-1],
- case .url(let u) = prev,
- classify_url(u).is_media != nil {
- trimmed = " " + trim_prefix(trimmed)
+
+ // Trim leading whitespaces.
+ if ind == 0 {
+ trimmed = trim_prefix(trimmed)
}
-
- if let next = blocks[safe: ind+1] {
- if case .url(let u) = next, classify_url(u).is_media != nil {
- trimmed = trim_suffix(trimmed)
- } else if case .mention(let m) = next,
- case .note = m.ref,
- one_note_ref {
- trimmed = trim_suffix(trimmed)
- }
+
+ // Trim trailing whitespaces if the following blocks will be hidden or if this is the last block.
+ if ind == hide_text_index - 1 {
+ trimmed = trim_suffix(trimmed)
}
-
+
return trimmed
}
+func invoice_str(_ invoice: Invoice) -> CompatibleText {
+ var attributedString = AttributedString(stringLiteral: abbrev_identifier(invoice.string))
+ attributedString.foregroundColor = DamusColors.purple
+
+ return CompatibleText(attributed: attributedString)
+}
+
func url_str(_ url: URL) -> CompatibleText {
var attributedString = AttributedString(stringLiteral: url.absoluteString)
attributedString.link = url
@@ -194,11 +238,11 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText
let display_str: String = {
switch m.ref {
case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles)
- case .note: return abbrev_pubkey(bech32String)
- case .nevent: return abbrev_pubkey(bech32String)
+ case .note: return abbrev_identifier(bech32String)
+ case .nevent: return abbrev_identifier(bech32String)
case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles)
case .nrelay(let url): return url
- case .naddr: return abbrev_pubkey(bech32String)
+ case .naddr: return abbrev_identifier(bech32String)
}
}()
diff --git a/damus/Types/Block.swift b/damus/Types/Block.swift
@@ -37,7 +37,23 @@ enum Block: Equatable {
return false
}
}
-
+
+ var is_previewable: Bool {
+ switch self {
+ case .mention(let m):
+ switch m.ref {
+ case .note, .nevent: return true
+ default: return false
+ }
+ case .invoice:
+ return true
+ case .url:
+ return true
+ default:
+ return false
+ }
+ }
+
case text(String)
case mention(Mention<MentionRef>)
case hashtag(String)
diff --git a/damus/Util/DisplayName.swift b/damus/Util/DisplayName.swift
@@ -80,9 +80,9 @@ func parse_display_name(name: String?, display_name: String?, pubkey: Pubkey) ->
}
func abbrev_bech32_pubkey(pubkey: Pubkey) -> String {
- return abbrev_pubkey(String(pubkey.npub.dropFirst(4)))
+ return abbrev_identifier(String(pubkey.npub.dropFirst(4)))
}
-func abbrev_pubkey(_ pubkey: String, amount: Int = 8) -> String {
+func abbrev_identifier(_ pubkey: String, amount: Int = 8) -> String {
return pubkey.prefix(amount) + ":" + pubkey.suffix(amount)
}
diff --git a/damus/Views/PubkeyView.swift b/damus/Views/PubkeyView.swift
@@ -46,7 +46,7 @@ struct PubkeyView: View {
let bech32 = pubkey.npub
HStack {
- Text(verbatim: "\(abbrev_pubkey(bech32, amount: sidemenu ? 12 : 16))")
+ Text(verbatim: "\(abbrev_identifier(bech32, amount: sidemenu ? 12 : 16))")
.font(sidemenu ? .system(size: 10) : .footnote)
.foregroundColor(keyColor())
.padding(5)
diff --git a/damusTests/NoteContentViewTests.swift b/damusTests/NoteContentViewTests.swift
@@ -10,28 +10,290 @@ import SwiftUI
@testable import damus
class NoteContentViewTests: XCTestCase {
- func testRenderBlocksWithNonLatinHashtags() {
+ func testRenderBlocksWithNonLatinHashtags() throws {
let content = "Damusはかっこいいです #cool #かっこいい"
- let note = NostrEvent(content: content, keypair: test_keypair, tags: [["t", "かっこいい"]])!
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair, tags: [["t", "かっこいい"]]))
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
let testState = test_damus_state
- let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
+ let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
let attributedText: AttributedString = text.content.attributed
let runs: AttributedString.Runs = attributedText.runs
let runArray: [AttributedString.Runs.Run] = Array(runs)
print(runArray.description)
XCTAssertEqual(runArray[1].link?.absoluteString, "damus:t:cool", "Latin-character hashtag is missing. Runs description :\(runArray.description)")
- XCTAssertEqual(runArray[3].link?.absoluteString.removingPercentEncoding!, "damus:t:かっこいい", "Non-latin-character hashtag is missing. Runs description :\(runArray.description)")
+ XCTAssertEqual(runArray[3].link?.absoluteString.removingPercentEncoding, "damus:t:かっこいい", "Non-latin-character hashtag is missing. Runs description :\(runArray.description)")
}
-
+
+ func testRenderBlocksWithLeadingAndTrailingWhitespacesTrimmed() throws {
+ let content = " \n\n Hello, \nworld! \n\n "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+ let text = attributedText.description
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+
+ XCTAssertEqual(runArray.count, 1)
+ XCTAssertTrue(text.contains("Hello, \nworld!"))
+ XCTAssertFalse(text.contains(content))
+ }
+
+ func testRenderBlocksWithMediaBlockInMiddleRendered() throws {
+ let content = " Check this out: https://damus.io/image.png Isn't this cool? "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 3)
+ XCTAssertTrue(runArray[0].description.contains("Check this out: "))
+ XCTAssertTrue(runArray[1].description.contains("https://damus.io/image.png "))
+ XCTAssertEqual(runArray[1].link?.absoluteString, "https://damus.io/image.png")
+ XCTAssertTrue(runArray[2].description.contains(" Isn't this cool?"))
+
+ XCTAssertEqual(noteArtifactsSeparated.images.count, 1)
+ XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/image.png")
+ }
+
+ func testRenderBlocksWithInvoiceInMiddleAbbreviated() throws {
+ let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
+ let content = " Donations appreciated: \(invoiceString) Pura Vida "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 3)
+ XCTAssertTrue(runArray[0].description.contains("Donations appreciated: "))
+ XCTAssertTrue(runArray[1].description.contains("lnbc100n:qpsql29r"))
+ XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
+ }
+
+ func testRenderBlocksWithNoteIdInMiddleAreRendered() throws {
+ let noteId = test_note.id.bech32
+ let content = " Check this out: nostr:\(noteId) Pura Vida "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 3)
+ XCTAssertTrue(runArray[0].description.contains("Check this out: "))
+ XCTAssertTrue(runArray[1].description.contains("note1qqq:qqn2l0z3"))
+ XCTAssertEqual(runArray[1].link?.absoluteString, "damus:nostr:\(noteId)")
+ XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
+ }
+
+ func testRenderBlocksWithNeventInMiddleAreRendered() throws {
+ let nevent = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
+ let content = " Check this out: nostr:\(nevent) Pura Vida "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 3)
+ XCTAssertTrue(runArray[0].description.contains("Check this out: "))
+ XCTAssertTrue(runArray[1].description.contains("nevent1q:t5nxnepm"))
+ XCTAssertEqual(runArray[1].link?.absoluteString, "damus:nostr:\(nevent)")
+ XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
+ }
+
+ func testRenderBlocksWithPreviewableBlocksAtEndAreHidden() throws {
+ let noteId = test_note.id.bech32
+ let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
+ let content = " Check this out. \nhttps://hidden.tld/\nhttps://damus.io/hidden1.png\n\(invoiceString)\nhttps://damus.io/hidden2.png\nnostr:\(noteId) "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 1)
+ XCTAssertTrue(runArray[0].description.contains("Check this out."))
+ XCTAssertFalse(runArray[0].description.contains("https://hidden.tld/"))
+ XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden1.png"))
+ XCTAssertFalse(runArray[0].description.contains("lnbc100n:qpsql29r"))
+ XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden2.png"))
+ XCTAssertFalse(runArray[0].description.contains("note1qqq:qqn2l0z3"))
+
+ XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/hidden1.png")
+ XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/hidden2.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/hidden1.png")
+ XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/hidden2.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.links.count, 1)
+ XCTAssertEqual(noteArtifactsSeparated.links[0].absoluteString, "https://hidden.tld/")
+
+ XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
+ XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
+ }
+
+ func testRenderBlocksWithMultipleLinksAtEndAreNotHidden() throws {
+ let noteId = test_note.id.bech32
+ let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
+ let content = " Check this out. \nhttps://nothidden1.tld/\nhttps://nothidden2.tld/\nhttps://damus.io/nothidden1.png\n\(invoiceString)\nhttps://damus.io/nothidden2.png\nnostr:\(noteId) "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 12)
+ XCTAssertTrue(runArray[0].description.contains("Check this out."))
+ XCTAssertTrue(runArray[1].description.contains("https://nothidden1.tld/"))
+ XCTAssertTrue(runArray[3].description.contains("https://nothidden2.tld/"))
+ XCTAssertTrue(runArray[5].description.contains("https://damus.io/nothidden1.png"))
+ XCTAssertTrue(runArray[7].description.contains("lnbc100n:qpsql29r"))
+ XCTAssertTrue(runArray[9].description.contains("https://damus.io/nothidden2.png"))
+ XCTAssertTrue(runArray[11].description.contains("note1qqq:qqn2l0z3"))
+
+ XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden1.png")
+ XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/nothidden2.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/nothidden1.png")
+ XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/nothidden2.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.links.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.links[0].absoluteString, "https://nothidden1.tld/")
+ XCTAssertEqual(noteArtifactsSeparated.links[1].absoluteString, "https://nothidden2.tld/")
+
+ XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
+ XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
+ }
+
+ func testRenderBlocksWithMultipleEventsAtEndAreNotHidden() throws {
+ let noteId = test_note.id.bech32
+ let nevent = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
+ let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
+ let content = " Check this out. \nnostr:\(noteId)\nnostr:\(nevent)\nhttps://damus.io/nothidden1.png\n\(invoiceString)\nhttps://damus.io/nothidden2.png "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 10)
+ XCTAssertTrue(runArray[0].description.contains("Check this out."))
+ XCTAssertTrue(runArray[1].description.contains("note1qqq:qqn2l0z3"))
+ XCTAssertTrue(runArray[3].description.contains("nevent1q:t5nxnepm"))
+ XCTAssertTrue(runArray[5].description.contains("https://damus.io/nothidden1.png"))
+ XCTAssertTrue(runArray[7].description.contains("lnbc100n:qpsql29r"))
+ XCTAssertTrue(runArray[9].description.contains("https://damus.io/nothidden2.png"))
+
+ XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden1.png")
+ XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/nothidden2.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/nothidden1.png")
+ XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/nothidden2.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.links.count, 0)
+
+ XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
+ XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
+ }
+
+ func testRenderBlocksWithPreviewableBlocksAtEndAreNotHiddenWhenMediaBlockPrecedesThem() throws {
+ let content = " Check this out: https://damus.io/image.png Isn't this cool? \nhttps://damus.io/nothidden.png "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 4)
+ XCTAssertTrue(runArray[0].description.contains("Check this out: "))
+ XCTAssertTrue(runArray[1].description.contains("https://damus.io/image.png "))
+ XCTAssertEqual(runArray[1].link?.absoluteString, "https://damus.io/image.png")
+ XCTAssertTrue(runArray[2].description.contains(" Isn't this cool?"))
+ XCTAssertTrue(runArray[3].description.contains("https://damus.io/nothidden.png"))
+ XCTAssertEqual(runArray[3].link?.absoluteString, "https://damus.io/nothidden.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
+ XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/image.png")
+ XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/nothidden.png")
+ }
+
+ func testRenderBlocksWithPreviewableBlocksAtEndAreNotHiddenWhenInvoicePrecedesThem() throws {
+ let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
+ let content = " Donations appreciated: \(invoiceString) Pura Vida \nhttps://damus.io/nothidden.png "
+ let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
+ let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
+
+ let testState = test_damus_state
+
+ let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
+ let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
+
+ let runs: AttributedString.Runs = attributedText.runs
+ let runArray: [AttributedString.Runs.Run] = Array(runs)
+ XCTAssertEqual(runArray.count, 4)
+ XCTAssertTrue(runArray[0].description.contains("Donations appreciated: "))
+ XCTAssertTrue(runArray[1].description.contains("lnbc100n:qpsql29r"))
+ XCTAssertTrue(runArray[2].description.contains(" Pura Vida"))
+ XCTAssertTrue(runArray[3].description.contains("https://damus.io/nothidden.png"))
+ XCTAssertEqual(runArray[3].link?.absoluteString, "https://damus.io/nothidden.png")
+
+ XCTAssertEqual(noteArtifactsSeparated.images.count, 1)
+ XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden.png")
+ }
+
/// Based on https://github.com/damus-io/damus/issues/1468
/// Tests whether a note content view correctly parses an image block when url in JSON content contains optional escaped slashes
- func testParseImageBlockInContentWithEscapedSlashes() {
+ func testParseImageBlockInContentWithEscapedSlashes() throws {
let testJSONWithEscapedSlashes = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}"
- let testNote = NostrEvent.owned_from_json(json: testJSONWithEscapedSlashes)!
+ let testNote = try XCTUnwrap(NostrEvent.owned_from_json(json: testJSONWithEscapedSlashes))
let parsed = parse_note_content(content: .init(note: testNote, keypair: test_keypair))
XCTAssertTrue((parsed.blocks[0].asURL != nil), "NoteContentView does not correctly parse an image block when url in JSON content contains optional escaped slashes.")
@@ -69,9 +331,9 @@ class NoteContentViewTests: XCTestCase {
}
func testMentionStr_Note_ContainsFullBech32() {
- let compatableText = createCompatibleText(test_note.id.bech32)
+ let compatibleText = createCompatibleText(test_note.id.bech32)
- assertCompatibleTextHasExpectedString(compatibleText: compatableText, expected: test_note.id.bech32)
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: test_note.id.bech32)
}
func testMentionStr_Nevent_ContainsAbbreviated() {
diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift
@@ -36,10 +36,9 @@ class damusTests: XCTestCase {
XCTAssertEqual(bytes.count, 32)
}
- func testTrimmingFunctions() {
+ func testTrimSuffix() {
let txt = " bobs "
- XCTAssertEqual(trim_prefix(txt), "bobs ")
XCTAssertEqual(trim_suffix(txt), " bobs")
}