From ab6ea7a9c170a3c84fb8e353ae6c4ec995578ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Wed, 27 Aug 2025 17:06:54 -0700 Subject: [PATCH] Fix issue where repost and like counts would not appear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, HomeModel could listen to all subscriptions throughout the app, and it would handle reaction and repost counting. Once moved to the local relay model, HomeModel no longer had access to all subscriptions, causing those counts to disappear. The issue was fixed by doing the counting from ThreadModel itself, which better isolates concerns throughout the app. Signed-off-by: Daniel D’Aquino --- damus.xcodeproj/project.pbxproj | 4 ++ damus/Features/Chat/Models/ThreadModel.swift | 6 ++ .../ThreadModelTests.swift | 63 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 damusTests/NostrNetworkManagerTests/ThreadModelTests.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 84ecdfc8..c1ec36ec 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -1756,6 +1756,7 @@ D7EB00B12CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; }; D7EBF8BB2E59022A004EAE29 /* NostrNetworkManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EBF8BA2E5901F7004EAE29 /* NostrNetworkManagerTests.swift */; }; D7EBF8BE2E59470D004EAE29 /* test_notes.jsonl in Resources */ = {isa = PBXBuildFile; fileRef = D7EBF8BD2E594708004EAE29 /* test_notes.jsonl */; }; + D7EBF8C02E5D39DC004EAE29 /* ThreadModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EBF8BF2E5D39D1004EAE29 /* ThreadModelTests.swift */; }; D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; }; D7EDED162B1177840018B19C /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; }; D7EDED172B1177960018B19C /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; }; @@ -2692,6 +2693,7 @@ D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentFullScreenItemNotify.swift; sourceTree = ""; }; D7EBF8BA2E5901F7004EAE29 /* NostrNetworkManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrNetworkManagerTests.swift; sourceTree = ""; }; D7EBF8BD2E594708004EAE29 /* test_notes.jsonl */ = {isa = PBXFileReference; lastKnownFileType = text; path = test_notes.jsonl; sourceTree = ""; }; + D7EBF8BF2E5D39D1004EAE29 /* ThreadModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModelTests.swift; sourceTree = ""; }; D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = ""; }; D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = ""; }; D7EDED202B117DCA0018B19C /* SequenceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceUtils.swift; sourceTree = ""; }; @@ -5001,6 +5003,7 @@ children = ( D7EBF8BD2E594708004EAE29 /* test_notes.jsonl */, D7EBF8BA2E5901F7004EAE29 /* NostrNetworkManagerTests.swift */, + D7EBF8BF2E5D39D1004EAE29 /* ThreadModelTests.swift */, ); path = NostrNetworkManagerTests; sourceTree = ""; @@ -5934,6 +5937,7 @@ 4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */, 4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */, D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */, + D7EBF8C02E5D39DC004EAE29 /* ThreadModelTests.swift in Sources */, 4C0ED07F2D7A1E260020D8A2 /* Benchmarking.swift in Sources */, 3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */, D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */, diff --git a/damus/Features/Chat/Models/ThreadModel.swift b/damus/Features/Chat/Models/ThreadModel.swift index 332b11de..e9df2130 100644 --- a/damus/Features/Chat/Models/ThreadModel.swift +++ b/damus/Features/Chat/Models/ThreadModel.swift @@ -190,6 +190,12 @@ class ThreadModel: ObservableObject { self.add_event(ev, keypair: damus_state.keypair) } } + else if ev.known_kind == .boost { + damus_state.boosts.add_event(ev, target: original_event.id) + } + else if ev.known_kind == .like { + damus_state.likes.add_event(ev, target: original_event.id) + } } // MARK: External control interface diff --git a/damusTests/NostrNetworkManagerTests/ThreadModelTests.swift b/damusTests/NostrNetworkManagerTests/ThreadModelTests.swift new file mode 100644 index 00000000..ee6b582c --- /dev/null +++ b/damusTests/NostrNetworkManagerTests/ThreadModelTests.swift @@ -0,0 +1,63 @@ +// +// ThreadModelTests.swift +// damus +// +// Created by Daniel D’Aquino on 2025-08-25. +// + + +import XCTest +@testable import damus + +final class ThreadModelTests: XCTestCase { + var damusState: DamusState? = nil + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + damusState = generate_test_damus_state(mock_profile_info: nil) + + let notesJSONL = getTestNotesJSONL() + + for noteText in notesJSONL.split(separator: "\n") { + let _ = damusState!.ndb.process_event("[\"EVENT\",\"subid\",\(String(noteText))]") + } + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + damusState = nil + } + + func getTestNotesJSONL() -> String { + // Get the path for the test_notes.jsonl file in the same folder as this test file + let testBundle = Bundle(for: type(of: self)) + let fileURL = testBundle.url(forResource: "test_notes", withExtension: "jsonl")! + + // Load the contents of the file + return try! String(contentsOf: fileURL, encoding: .utf8) + } + + /// Tests loading up a thread and checking if the repost count loads as expected. + func testActionBarModel() throws { + let testNoteJson = """ +{"content":"https://smartflowsocial.s3.us-east-1.amazonaws.com/clients/cm7kdrwdk0000qyu6fwtd96ui/0cab65a9-0142-48e3-abd7-94d20e30d3b2.jpg\n\n","pubkey":"71ecabd8b6b33548e075ff01b31568ffda19d0ac2788067d99328c6de4885975","tags":[["t","meme"],["t","memes"],["t","memestr"],["t","plebchain"]],"created_at":1755694800,"id":"64b26d0a587f5f894470e1e4783756b4d8ba971226de975ee30ac1b69970d5a1","kind":1,"sig":"c000794da8c4f7549b546630b16ed17f6edc0af0269b8c46ce14f5b1937431e7575b78351bc152007ebab5720028e5fe4b738f99e8887f273d35dd2217d1cc3d"} +""" + let testShouldComplete = XCTestExpectation(description: "Test should complete") + Task { + let note = NostrEvent.owned_from_json(json: testNoteJson)! + let threadModel = await ThreadModel(event: note, damus_state: damusState!) + await threadModel.subscribe() + let actionBarModel = make_actionbar_model(ev: note.id, damus: damusState!) + while true { + try await Task.sleep(nanoseconds: 500_000_000) + actionBarModel.update(damus: damusState!, evid: note.id) + if actionBarModel.boosts >= 5 { + break + } + } + XCTAssertEqual(actionBarModel.boosts, 5) + testShouldComplete.fulfill() + } + wait(for: [testShouldComplete], timeout: 10.0) + } +}