Skip to content

Commit 0a6d036

Browse files
sirily11claude
andcommitted
test(message-list): assert pinned turn follows bottom after fill
c466199 made the scroll anchor stay sticky through non-user layout settles, so isNearBottom no longer flips false→true during a pinned turn. The pinned-turn integration test asserted that obsolete flicker and broke CI. Rewrite it to assert the new intended behavior: once the streaming turn fills the reserved space, the list keeps following the bottom (the pin releases without stranding the view). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
1 parent 149ff70 commit 0a6d036

1 file changed

Lines changed: 12 additions & 32 deletions

File tree

Packages/Tests/MessageListTests/MessageListPinnedTurnSwiftUITests.swift

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import ViewInspector
77
@MainActor
88
@Suite("MessageList pinned turn SwiftUI behavior")
99
struct MessageListPinnedTurnSwiftUITests {
10-
@Test("Pinned user message releases when streaming content fills the reserved space")
11-
func pinnedUserMessageReleasesWhenStreamingContentFillsReservedSpace() async throws {
10+
@Test("Streaming content that fills the reserved space keeps the list following the bottom")
11+
func streamingContentFillingReservedSpaceFollowsBottom() async throws {
1212
let model = MessageListPinnedTurnModel()
1313
let view = MessageListPinnedTurnHarness(model: model)
1414

@@ -19,39 +19,34 @@ struct MessageListPinnedTurnSwiftUITests {
1919
)
2020
defer { ViewHosting.expel(function: #function) }
2121

22+
// A fresh user message pins to the top with reserved space below it.
2223
model.messages = [
2324
.init(text: "user", isUserMessage: true, height: 44),
2425
]
2526

2627
try await Task.sleep(for: .milliseconds(450))
27-
model.shouldObserveRelease = true
28+
29+
// The streaming response grows the turn until it outgrows the viewport,
30+
// collapsing the reserved space. The pin releases and the list must keep
31+
// following the bottom — it must not be stranded above the bottom.
2832
model.messages.append(contentsOf: [
2933
.init(text: "assistant 1", isUserMessage: false, height: 88),
3034
.init(text: "assistant 2", isUserMessage: false, height: 88),
3135
.init(text: "assistant 3", isUserMessage: false, height: 88),
3236
])
3337

34-
try await waitUntil(timeout: .seconds(2)) {
35-
model.observedBottomRelease
36-
}
38+
// Let the layout settle after the turn fills the viewport, then assert the
39+
// list reports it is following the bottom rather than stranded.
40+
try await Task.sleep(for: .milliseconds(600))
3741

38-
#expect(model.observedBottomRelease)
42+
#expect(model.isAtBottom)
3943
}
4044
}
4145

4246
@MainActor
4347
private final class MessageListPinnedTurnModel: ObservableObject {
4448
@Published var messages: [MessageListPinnedTurnMessage] = []
4549
@Published var isAtBottom = false
46-
var shouldObserveRelease = false
47-
var observedBottomRelease = false
48-
49-
func updateIsAtBottom(_ value: Bool) {
50-
isAtBottom = value
51-
if shouldObserveRelease, value {
52-
observedBottomRelease = true
53-
}
54-
}
5550
}
5651

5752
private struct MessageListPinnedTurnHarness: View {
@@ -63,7 +58,7 @@ private struct MessageListPinnedTurnHarness: View {
6358
isStreaming: true,
6459
isAtBottom: Binding(
6560
get: { model.isAtBottom },
66-
set: { model.updateIsAtBottom($0) }
61+
set: { model.isAtBottom = $0 }
6762
)
6863
) { message in
6964
Text(message.text)
@@ -78,19 +73,4 @@ private struct MessageListPinnedTurnMessage: MessageListItem {
7873
let isUserMessage: Bool
7974
let height: CGFloat
8075
}
81-
82-
private func waitUntil(
83-
timeout: Duration,
84-
interval: Duration = .milliseconds(20),
85-
condition: @MainActor @escaping () -> Bool
86-
) async throws {
87-
let start = ContinuousClock.now
88-
while !(await condition()) {
89-
if ContinuousClock.now - start >= timeout {
90-
Issue.record("Timed out waiting for condition")
91-
return
92-
}
93-
try await Task.sleep(for: interval)
94-
}
95-
}
9676
#endif

0 commit comments

Comments
 (0)