File size: 3,848 Bytes
b8370f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import Foundation
import SwiftUI
import Testing
@testable import OpenClawChatUI

struct ChatReaderScrollStateTests {
    #if os(iOS)
    @Test func `iOS initially restores to the live edge`() {
        #expect(chatReaderInitialRestorePolicy() == .liveEdge)
    }
    #elseif os(macOS)
    @Test func `macOS initially restores to the latest turn`() {
        #expect(chatReaderInitialRestorePolicy() == .latestTurn)
    }
    #endif

    @Test func `optimistic turn removal keeps the older user as the baseline`() {
        let olderUserID = UUID()
        let optimisticUserID = UUID()

        let transition = chatReaderUserTransition(
            previousID: optimisticUserID,
            visibleIDs: [olderUserID])

        #expect(transition == .removed(latestRemainingID: olderUserID))
    }

    @Test func `only user removal clears the user baseline`() {
        let optimisticUserID = UUID()

        let transition = chatReaderUserTransition(
            previousID: optimisticUserID,
            visibleIDs: [])

        #expect(transition == .removed(latestRemainingID: nil))
    }

    @Test func `new user after the existing baseline starts a turn`() {
        let previousUserID = UUID()
        let newUserID = UUID()

        let transition = chatReaderUserTransition(
            previousID: previousUserID,
            visibleIDs: [previousUserID, newUserID])

        #expect(transition == .added(newUserID))
    }

    @Test func `removed transient content does not offer a latest jump`() {
        let userID = UUID()

        let hasNewerContent = chatReaderHasNewerContent(
            after: userID,
            visibleIDs: [userID],
            hasTransientContent: false)

        #expect(!hasNewerContent)
    }

    @Test func `assistant or transient content after the user offers a latest jump`() {
        let userID = UUID()
        let assistantID = UUID()

        #expect(chatReaderHasNewerContent(
            after: userID,
            visibleIDs: [userID, assistantID],
            hasTransientContent: false))
        #expect(chatReaderHasNewerContent(
            after: userID,
            visibleIDs: [userID],
            hasTransientContent: true))
    }

    @Test func `drags and system animated scrolls release the follow target`() {
        #expect(chatReaderScrollReleasesFollow(.interacting))
        #expect(chatReaderScrollReleasesFollow(.animating))
    }

    @Test func `idle, touch-down, and deceleration phases keep the follow target`() {
        #expect(!chatReaderScrollReleasesFollow(.idle))
        #expect(!chatReaderScrollReleasesFollow(.tracking))
        #expect(!chatReaderScrollReleasesFollow(.decelerating))
    }

    @Test func `streaming at the live edge never offers a latest jump`() {
        // #108693: the first Writing tick of a turn makes structural "newer content" true
        // while the reader is still at the bottom; the geometry gate must win.
        #expect(!chatReaderShowsJumpToLatest(
            hasNewerContentBelow: true,
            isAtLiveEdge: true,
            hasVisibleContent: true,
            isLoading: false))
    }

    @Test func `newer content below the viewport offers a latest jump`() {
        #expect(chatReaderShowsJumpToLatest(
            hasNewerContentBelow: true,
            isAtLiveEdge: false,
            hasVisibleContent: true,
            isLoading: false))
    }

    @Test func `loading and empty transcripts suppress the latest jump`() {
        #expect(!chatReaderShowsJumpToLatest(
            hasNewerContentBelow: true,
            isAtLiveEdge: false,
            hasVisibleContent: true,
            isLoading: true))
        #expect(!chatReaderShowsJumpToLatest(
            hasNewerContentBelow: true,
            isAtLiveEdge: false,
            hasVisibleContent: false,
            isLoading: false))
    }
}