Files
hapi/ios/HapiTests/SessionRowStatusTests.swift
T
weishu 5ef4304a4a fix: synchronize read state across web and native clients
Track readable content revisions and durable read cursors in the hub. Acknowledge only rendered foreground tails, persist offline read operations, and preserve newer manual unread choices across devices.

Migrate existing history to a read baseline and cover pagination receipts with shared native conformance fixtures.
2026-11-01 09:52:51 +08:00

101 lines
4.7 KiB
Swift

import HapiClient
import HapiProtocol
import XCTest
@testable import Hapi
@MainActor
final class SessionRowStatusTests: XCTestCase {
func testAuthoritativeKindsDetermineAttentionWithoutGuessingFromTools() {
let cases: [([PendingRequestKind], SessionRowStatus)] = [
([.input], .needsReply(7)),
([.permission], .needsApproval(7)),
([.input, .permission], .needsAttention(7)),
([.permission, .input], .needsAttention(7)),
([], .needsAttention(7)),
]
for (kinds, expected) in cases {
var summary = HomeFilterTestData.summary("a", machine: "mac")
summary.pendingRequestsCount = 7
summary.pendingRequestKinds = kinds
// Deliberately misleading and capped: neither its length nor
// its first tool can replace the authoritative total/kinds.
summary.pendingRequests = [.init(id: "r", kind: .input, tool: "request_user_input", since: 0)]
XCTAssertEqual(SessionRowStatus(summary: summary), expected)
XCTAssertEqual(SessionRowStatus(summary: summary)?.count, 7)
}
}
func testPendingWinsOverThinkingAndSurvivesDisconnection() {
var summary = HomeFilterTestData.summary("a", machine: nil)
summary.thinking = true
XCTAssertEqual(SessionRowStatus(summary: summary), .running)
summary.pendingRequestsCount = 1
summary.pendingRequestKinds = [.input]
XCTAssertEqual(SessionRowStatus(summary: summary), .needsReply(1))
summary.active = false
XCTAssertEqual(SessionRowStatus(summary: summary), .needsReply(1))
summary.pendingRequestsCount = 0
XCTAssertNil(SessionRowStatus(summary: summary), "Stale kinds and thinking do not imply pending/connected activity")
summary.active = true
XCTAssertEqual(SessionRowStatus(summary: summary), .running)
summary.thinking = false
XCTAssertNil(SessionRowStatus(summary: summary))
}
func testReadingDoesNotResolveAttentionAndResolutionKeepsTheProject() async throws {
var summary = HomeFilterTestData.summary("a", machine: "mac")
summary.metadata?.summary = .init(text: "Keep the session context visible")
summary.contentRevision = 1
summary.pendingRequestsCount = 2
summary.pendingRequestKinds = [.permission]
let sessions = HomeFilterTestSessions([summary])
let model = HomeFilterTestData.model(sessions: sessions)
XCTAssertTrue(model.rows[0].unread)
XCTAssertEqual(model.rows[0].status, .needsApproval(2))
model.markAllRead()
try await Task.sleep(for: .milliseconds(30))
XCTAssertFalse(model.rows[0].unread)
XCTAssertEqual(model.rows[0].status, .needsApproval(2))
XCTAssertEqual(model.rows[0].project, "hapi")
sessions.sessions[0].pendingRequestsCount = 0
sessions.sessions[0].pendingRequestKinds = []
XCTAssertNil(model.rows[0].status)
XCTAssertEqual(model.rows[0].project, "hapi")
sessions.sessions[0].thinking = true
XCTAssertEqual(model.rows[0].status, .running)
sessions.sessions[0].updatedAt += 1
XCTAssertFalse(model.rows[0].unread, "Metadata changes do not create unread output")
}
func testMissingMetadataDoesNotCreatePlaceholderContent() {
var summary = HomeFilterTestData.summary("a", machine: nil)
summary.metadata = nil
let model = HomeFilterTestData.model(sessions: HomeFilterTestSessions([summary]))
XCTAssertNil(model.rows[0].status)
XCTAssertNil(model.rows[0].project)
}
func testProjectIsOnlyTheRepositoryNameIncludingWorktreesAndWindowsPaths() {
var summary = HomeFilterTestData.summary("a", machine: "mac")
XCTAssertEqual(SessionListModel.projectLabel(summary), "hapi")
summary.metadata?.path = "C:\\workspace\\hapi\\"
XCTAssertEqual(SessionListModel.projectLabel(summary), "hapi")
summary.metadata?.path = "/tmp/checkouts/generated-branch"
summary.metadata?.worktree = .init(basePath: "/workspace/hapi", branch: "feature", name: "generated-branch")
XCTAssertEqual(SessionListModel.projectLabel(summary), "hapi")
summary.metadata?.worktree = nil
summary.metadata?.path = "/"
XCTAssertNil(SessionListModel.projectLabel(summary))
}
func testStatusCopyIsHumanFacing() {
XCTAssertEqual(SessionRowStatus.needsReply(1).titleKey, "Needs reply")
XCTAssertEqual(SessionRowStatus.needsApproval(2).titleKey, "Needs approval")
XCTAssertEqual(SessionRowStatus.needsAttention(7).titleKey, "Needs attention")
XCTAssertEqual(SessionRowStatus.running.titleKey, "Running")
XCTAssertNil(SessionRowStatus.running.count)
}
}