File size: 2,823 Bytes
3e05655 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | import { describe, expect, test } from "bun:test"
import type { AssistantMessage, Message, UserMessage } from "@opencode-ai/sdk/v2"
import { isTimelineReady, loadOlderTimeline, selectUserMessages, selectVisibleUserMessages } from "./model"
const user = (id: string) => ({ id, role: "user" }) as UserMessage
const assistant = (id: string) => ({ id, role: "assistant" }) as AssistantMessage
describe("timeline model", () => {
test("selects users and applies the revert boundary", () => {
const messages: Message[] = [user("msg_z"), assistant("msg_a"), user("msg_b"), user("msg_c")]
const users = selectUserMessages(messages)
expect(users.map((message) => message.id)).toEqual(["msg_z", "msg_b", "msg_c"])
expect(selectVisibleUserMessages(users, "msg_b").map((message) => message.id)).toEqual(["msg_z"])
expect(selectVisibleUserMessages(users)).toBe(users)
})
test("waits for an assistant-only load to hydrate its user root", () => {
expect(isTimelineReady([assistant("msg_2")], true)).toBe(false)
expect(isTimelineReady([user("msg_1"), assistant("msg_2")], true)).toBe(true)
expect(isTimelineReady([], false)).toBe(true)
})
test("loads exactly one opaque cursor page", async () => {
let calls = 0
const anchors: Array<string | boolean> = []
await loadOlderTimeline({
sessionID: () => "ses_test",
more: () => true,
loading: () => false,
loadMore: async () => {
calls += 1
},
before: () => anchors.push("before"),
after: (done) => anchors.push("after", done),
})
expect(calls).toBe(1)
expect(anchors).toEqual(["before", "after", true])
})
test("stops when a page adds no raw messages", async () => {
let calls = 0
await loadOlderTimeline({
sessionID: () => "ses_test",
more: () => true,
loading: () => false,
loadMore: async () => {
calls += 1
},
})
expect(calls).toBe(1)
})
test("does not restore an anchor after the session changes", async () => {
let sessionID = "ses_old"
let restore = 0
await loadOlderTimeline({
sessionID: () => sessionID,
more: () => true,
loading: () => false,
loadMore: async () => {
sessionID = "ses_new"
},
after: () => {
restore += 1
},
})
expect(restore).toBe(0)
})
test("releases the anchor when loading history fails", async () => {
let restore = 0
await expect(
loadOlderTimeline({
sessionID: () => "ses_test",
more: () => true,
loading: () => false,
loadMore: async () => {
throw new Error("history failed")
},
after: () => {
restore += 1
},
}),
).rejects.toThrow("history failed")
expect(restore).toBe(1)
})
})
|