eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1import XCTest
2@testable import LChatCore
3
4// Presence wire contract: the "presence" hub frame and the roster's stamped
5// presence field decode as the server sends them (presence.go / PR #413), so
6// the native dots read the same states the web does.
7final class PresenceTests: XCTestCase {
8 func testPresenceFrameDecodes() throws {
9 let json = #"{"type":"presence","user_id":"u-alice","presence":"away"}"#
10 let frame = try JSONDecoder().decode(ChatSocket.Frame.self, from: Data(json.utf8))
11 XCTAssertEqual(frame.type, "presence")
12 XCTAssertEqual(frame.user_id, "u-alice")
13 XCTAssertEqual(frame.presence, "away")
14 XCTAssertNil(frame.thread_id) // presence is roster-wide, never thread-scoped
15 }
16
17 // Older frames (msg/typing/…) carry no presence fields — they must keep
18 // decoding with the new optionals absent.
19 func testFramesWithoutPresenceStillDecode() throws {
20 let json = #"{"type":"msg","thread_id":"t1","id":7,"created_at":1700000000,"blob":"x"}"#
21 let frame = try JSONDecoder().decode(ChatSocket.Frame.self, from: Data(json.utf8))
22 XCTAssertEqual(frame.type, "msg")
23 XCTAssertNil(frame.user_id)
24 XCTAssertNil(frame.presence)
25 }
26
27 // The pure filter rules mirror web/static/ui.js — same cases as ui.test.mjs,
28 // so web and native can't drift on what "Online only" or a category means.
29 func testOnlineFilterKeeps() {
30 // People decide: online keeps, away/offline drop.
31 XCTAssertTrue(onlineFilterKeeps([.init(agent: false, presence: "online")]))
32 XCTAssertFalse(onlineFilterKeeps([.init(agent: false, presence: "away")]))
33 XCTAssertFalse(onlineFilterKeeps([.init(agent: false, presence: "offline")]))
34 // A chat with ONLY bots falls back to the bot's own status (sidecar health).
35 XCTAssertTrue(onlineFilterKeeps([.init(agent: true, presence: "online")]))
36 XCTAssertFalse(onlineFilterKeeps([.init(agent: true, presence: "offline")]))
37 // In a mixed room only the people count — an always-on bot must not make
38 // every room it sits in read "someone's online".
39 XCTAssertFalse(onlineFilterKeeps([
40 .init(agent: true, presence: "online"), .init(agent: false, presence: "offline"),
41 ]))
42 XCTAssertTrue(onlineFilterKeeps([
43 .init(agent: true, presence: "offline"), .init(agent: false, presence: "online"),
44 ]))
45 // Notes/app threads (no other members) never match.
46 XCTAssertFalse(onlineFilterKeeps([]))
47 }
48
49 func testChatCategory() {
50 XCTAssertEqual(chatCategory(isApp: true, others: []), .apps)
51 XCTAssertEqual(chatCategory(isApp: true, others: [.init(agent: true, presence: "online")]), .apps)
52 XCTAssertEqual(chatCategory(isApp: false, others: [.init(agent: true, presence: "offline")]), .bots)
53 // Any person in the room makes it a people chat, bots present or not.
54 XCTAssertEqual(chatCategory(isApp: false, others: [
55 .init(agent: true, presence: "online"), .init(agent: false, presence: "offline"),
56 ]), .people)
57 XCTAssertEqual(chatCategory(isApp: false, others: []), .people) // your own Notes
58 }
59
60 // The inline filter chip's copy — one shared derivation, so iOS and Mac say
61 // the same words for the same flags.
62 func testChatFilterSummary() {
63 XCTAssertNil(chatFilterSummary(onlineOnly: false, people: true, bots: true, apps: true))
64 XCTAssertEqual(chatFilterSummary(onlineOnly: true, people: true, bots: true, apps: true),
65 "Online only")
66 XCTAssertEqual(chatFilterSummary(onlineOnly: false, people: true, bots: true, apps: false),
67 "Hiding apps")
68 XCTAssertEqual(chatFilterSummary(onlineOnly: false, people: true, bots: false, apps: false),
69 "Hiding bots & apps")
70 XCTAssertEqual(chatFilterSummary(onlineOnly: false, people: false, bots: false, apps: false),
71 "Hiding people, bots & apps")
72 XCTAssertEqual(chatFilterSummary(onlineOnly: true, people: true, bots: false, apps: false),
73 "Online only · hiding bots & apps")
74 }
75
76 func testPresenceText() {
77 XCTAssertEqual(presenceText("online"), "Online")
78 XCTAssertEqual(presenceText("away"), "Away")
79 XCTAssertEqual(presenceText("offline"), "Offline")
80 XCTAssertNil(presenceText(""))
81 XCTAssertNil(presenceText(nil))
82 }
83
84 func testRosterEntryCarriesPresence() throws {
85 let json = #"{"user_id":"u1","label":"Alice","public_key":"pk","presence":"online"}"#
86 let entry = try JSONDecoder().decode(RosterEntry.self, from: Data(json.utf8))
87 XCTAssertEqual(entry.presence, "online")
88
89 // The field is omitempty on the server — absent must decode too.
90 let legacy = #"{"user_id":"u2","label":"Bob","public_key":"pk2"}"#
91 let bob = try JSONDecoder().decode(RosterEntry.self, from: Data(legacy.utf8))
92 XCTAssertNil(bob.presence)
93 }
94}
95