mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
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.
101 lines
4.7 KiB
Swift
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)
|
|
}
|
|
}
|