import XCTest @testable import LChatCore // πŸ€– Sidebar snippet (previewText) must mirror the web (previewText in // shell.js): a non-image attachment reads as a file chip, not "πŸ“· Photo". final class PreviewTextTests: XCTestCase { private func att(_ name: String, _ type: String?) -> Attachment { Attachment(id: "b", name: name, type: type) } func testTextTakesPrecedence() { var p = MessagePayload() p.text = " hello world \n" p.attachments = [att("photo.jpg", "image/jpeg")] XCTAssertEqual(previewText(kind: "dm", payload: p), "hello world") } func testSingleImage() { var p = MessagePayload() p.attachments = [att("photo.jpg", "image/jpeg")] XCTAssertEqual(previewText(kind: "dm", payload: p), "πŸ“· Photo") } func testMultipleImages() { var p = MessagePayload() p.attachments = [att("a.jpg", "image/jpeg"), att("b.png", "image/png")] XCTAssertEqual(previewText(kind: "dm", payload: p), "πŸ“· 2 photos") } func testSingleFileIsNotAPhoto() { var p = MessagePayload() p.attachments = [att("report.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")] XCTAssertEqual(previewText(kind: "dm", payload: p), "πŸ“Ž report.docx") } func testMultipleFiles() { var p = MessagePayload() p.attachments = [att("a.docx", "application/msword"), att("b.pdf", "application/pdf")] XCTAssertEqual(previewText(kind: "dm", payload: p), "πŸ“Ž 2 files") } func testMixedImageAndFileFallsToFiles() { var p = MessagePayload() p.attachments = [att("a.jpg", "image/jpeg"), att("b.pdf", "application/pdf")] XCTAssertEqual(previewText(kind: "dm", payload: p), "πŸ“Ž 2 files") } // A typeless attachment (legacy) isn't an image β†’ file chip with its name. func testUntypedAttachment() { var p = MessagePayload() p.attachments = [att("mystery.bin", nil)] XCTAssertEqual(previewText(kind: "dm", payload: p), "πŸ“Ž mystery.bin") } func testGroupPrefix() { var p = MessagePayload() p.name = "Ada" p.attachments = [att("report.docx", "application/msword")] XCTAssertEqual(previewText(kind: "group", payload: p), "Ada: πŸ“Ž report.docx") } // A reaction has no text of its own β€” the row used to go blank (in a group, // a bare "Ada: "). It says what happened instead, quoting the snippet the // reacting client stamped inside the ciphertext. Web twin: reactionLine in // format.js + the reaction branch in shell.js previewText. private func reaction(_ name: String, _ emoji: String, on: Bool, target: String?) -> MessagePayload { var p = MessagePayload() p.type = "reaction" p.name = name p.emoji = emoji p.on = on p.targetSid = 12345 p.targetText = target return p } func testReactionQuotesItsTarget() { let p = reaction("Ada", "πŸ‘", on: true, target: "buy milk") XCTAssertEqual(previewText(kind: "dm", payload: p), "Ada reacted πŸ‘ to β€œbuy milk”") } // It names the actor itself, so the group prefix never doubles up. func testReactionTakesNoGroupPrefix() { let p = reaction("Ada", "πŸ‘", on: true, target: "buy milk") XCTAssertEqual(previewText(kind: "group", payload: p), "Ada reacted πŸ‘ to β€œbuy milk”") } func testMyOwnReactionSaysYou() { let p = reaction("Ada", "πŸŽ‰", on: true, target: "shipped it") XCTAssertEqual(previewText(kind: "group", payload: p, mine: true), "You reacted πŸŽ‰ to β€œshipped it”") } // An un-react speaks too: on reload this event is all the sidebar has, so // silence would mean a different row after a refresh than the one shown live. func testUnReactSpeaks() { let p = reaction("Bob", "❀️", on: false, target: "shipped it πŸŽ‰") XCTAssertEqual(previewText(kind: "dm", payload: p), "Bob removed ❀️ from β€œshipped it πŸŽ‰β€") } // Reactions from clients that predate targetText (and every reaction // already in a database) carry no snippet β€” the bare form still reads. func testReactionWithoutASnippet() { XCTAssertEqual(previewText(kind: "dm", payload: reaction("Ada", "πŸ‘", on: true, target: nil)), "Ada reacted πŸ‘") XCTAssertEqual(previewText(kind: "dm", payload: reaction("Ada", "πŸ‘", on: false, target: nil)), "Ada removed πŸ‘") } func testReactionQuoteIsClamped() { let p = reaction("Ada", "πŸ‘", on: true, target: String(repeating: "a", count: 200)) let line = previewText(kind: "dm", payload: p) XCTAssertTrue(line.hasPrefix("Ada reacted πŸ‘ to β€œ")) XCTAssertTrue(line.hasSuffix("…”")) XCTAssertLessThan(line.count, 70, "the sidebar line stays one line's worth") } // The caller resolves the actor by uid (ThreadStore's displayLabels β€” my // name for them, #577), and that wins over the payload's snapshot. Without // it, a reaction from a device that stamped no screen name β€” common on real // data β€” reads "Someone reacted πŸ‘". func testActorResolvesByUidOverThePayloadName() { var p = reaction("", "πŸ˜‚", on: true, target: nil) p.name = "" XCTAssertEqual(previewText(kind: "group", payload: p, actor: "Paul"), "Paul reacted πŸ˜‚") } func testResolvedActorBeatsAStalePayloadName() { let p = reaction("Old Name", "πŸ‘", on: true, target: "buy milk") XCTAssertEqual(previewText(kind: "dm", payload: p, actor: "My Name For Them"), "My Name For Them reacted πŸ‘ to β€œbuy milk”") } // Nobody to resolve (they left the space, and the payload named no one). func testReactionWithoutANameSaysSomeone() { var p = reaction("", "πŸ‘", on: true, target: "buy milk") p.name = nil XCTAssertEqual(previewText(kind: "dm", payload: p), "Someone reacted πŸ‘ to β€œbuy milk”") } // The group prefix takes the same by-uid resolution as the reaction line: // my name for them, so the row survives a rename and never renders a bare // ": hello" for a sender whose device stamped no screen name. Web twin: // previewText in shell.js resolves `who` once and uses it for both. func testGroupPrefixUsesTheResolvedActor() { var p = MessagePayload() p.name = "Old Name" p.text = "hello" XCTAssertEqual(previewText(kind: "group", payload: p, actor: "My Name For Them"), "My Name For Them: hello") } func testGroupPrefixFallsBackToThePayloadName() { var p = MessagePayload() p.name = "Ada" p.text = "hello" XCTAssertEqual(previewText(kind: "group", payload: p), "Ada: hello") } // Nothing to resolve and nothing stamped: no stray colon. func testGroupPrefixIsSkippedWithNoNameAtAll() { var p = MessagePayload() p.text = "hello" XCTAssertEqual(previewText(kind: "group", payload: p), "hello") } // An action line names its own actor β€” prefixing it doubles the name up. func testActionLineTakesNoGroupPrefix() { var p = MessagePayload() p.name = "Ada" p.text = "waves" p.action = true XCTAssertEqual(previewText(kind: "group", payload: p, actor: "Ada"), "waves") } }