eleven / ocho Public

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

Plain git — no account needed to clone.

Download

Download this file

1import XCTest
2@testable import LChatCore
3
4// 🤖 The dismiss-a-labelled-ask contract (#1375), the native twin of the web's
5// "every cancel fires the cancelled hook" test: waving the question off clears
6// the composer pill (label + hint) AND disarms the flow's waiter, so the next
7// plain message is an ordinary message — never a swallowed answer. The parked
8// step re-arms on the next cold open, exactly like the web after a reload.
9@MainActor
10final class ElevenConversationModelTests: XCTestCase {
11
12 // origin "" → no baseURL → api/socket are nil and every network path is a
13 // quiet no-op, so the model exercises pure conversation state.
14 private func makeModel() throws -> ElevenConversationModel {
15 let instance = SavedInstance(origin: "", deviceToken: "t", screenName: "Paul",
16 userPub: "", userPriv: "")
17 let thread = ChatThread(id: "t1", kind: "self", title: "Eleven", preview: "",
18 lastAt: 0, unread: false, pinned: false, hidden: false,
19 muted: nil, members: [], threadKeyB64: "x", avatar: nil,
20 isApp: true, appId: "setup", glyph: nil, badge: nil)
21 return try XCTUnwrap(ElevenConversationModel(instance: instance, thread: thread,
22 myUserId: "u1"))
23 }
24
25 func testDismissClearsPillAndDisarmsPendingAnswer() async throws {
26 let model = try makeModel()
27 model.composerLabel = "Your name:"
28 model.composerHint = "Type your name"
29 var answered: [String] = []
30 model.ask { answered.append($0.text) }
31
32 model.cancelAnswer()
33
34 XCTAssertNil(model.composerLabel, "dismiss drops the inset label")
35 XCTAssertNil(model.composerHint, "dismiss drops the hint with it")
36 await model.send("just a message")
37 XCTAssertEqual(answered, [], "a later plain message is never a swallowed answer")
38 guard case .mine(let text)? = model.rows.last?.kind else {
39 return XCTFail("the plain message still lands in the transcript")
40 }
41 XCTAssertEqual(text, "just a message")
42 }
43
44 func testDismissWithNoWaiterStillClearsThePill() throws {
45 // The web twin: cancelAnswer on an idle thread still clears the label.
46 let model = try makeModel()
47 model.composerLabel = "Your name:"
48 model.composerHint = "Type your name"
49 model.cancelAnswer()
50 XCTAssertNil(model.composerLabel)
51 XCTAssertNil(model.composerHint)
52 }
53
54 func testSendDeliversAnArmedAnswerAndDropsThePill() async throws {
55 // The undismissed path keeps working: an armed ask consumes the send.
56 let model = try makeModel()
57 model.composerLabel = "Your name:"
58 var answered: [String] = []
59 model.ask { answered.append($0.text) }
60 await model.send("Paul")
61 XCTAssertEqual(answered, ["Paul"])
62 XCTAssertNil(model.composerLabel, "the answered ask retires its label")
63 }
64}
65