@@ -48,6 +48,8 @@ vi.mock("@legendapp/list/react", async () => {
4848
4949import { MessagesTimeline } from "./MessagesTimeline" ;
5050
51+ const MESSAGE_CREATED_AT = "2026-04-13T12:00:00.000Z" ;
52+
5153function buildProps ( ) {
5254 return {
5355 isWorking : false ,
@@ -73,6 +75,27 @@ function buildProps() {
7375 } ;
7476}
7577
78+ function buildLongUserMessageText ( tail = "deep hidden detail only after expand" ) {
79+ return Array . from ( { length : 9 } , ( _ , index ) =>
80+ index === 8 ? tail : `Line ${ index + 1 } : ${ "verbose prompt content " . repeat ( 8 ) . trim ( ) } ` ,
81+ ) . join ( "\n" ) ;
82+ }
83+
84+ function buildUserTimelineEntry ( text : string ) {
85+ return {
86+ id : "entry-1" ,
87+ kind : "message" as const ,
88+ createdAt : MESSAGE_CREATED_AT ,
89+ message : {
90+ id : "message-1" as never ,
91+ role : "user" as const ,
92+ text,
93+ createdAt : MESSAGE_CREATED_AT ,
94+ streaming : false ,
95+ } ,
96+ } ;
97+ }
98+
7699describe ( "MessagesTimeline" , ( ) => {
77100 afterEach ( ( ) => {
78101 scrollToEndSpy . mockReset ( ) ;
@@ -157,4 +180,87 @@ describe("MessagesTimeline", () => {
157180 await screen . unmount ( ) ;
158181 }
159182 } ) ;
183+
184+ it ( "starts long user messages collapsed by default" , async ( ) => {
185+ const screen = await render (
186+ < MessagesTimeline
187+ { ...buildProps ( ) }
188+ timelineEntries = { [ buildUserTimelineEntry ( buildLongUserMessageText ( ) ) ] }
189+ /> ,
190+ ) ;
191+
192+ try {
193+ const toggle = page . getByRole ( "button" , { name : "Show full message" } ) ;
194+ await expect . element ( toggle ) . toBeVisible ( ) ;
195+ await expect . element ( toggle ) . toHaveAttribute ( "aria-expanded" , "false" ) ;
196+
197+ const messageBody = document . querySelector (
198+ "[data-user-message-body='true']" ,
199+ ) as HTMLDivElement | null ;
200+ expect ( messageBody ?. getAttribute ( "data-user-message-collapsed" ) ) . toBe ( "true" ) ;
201+ expect ( messageBody ?. className ) . toContain ( "max-h-44" ) ;
202+ expect ( messageBody ?. className ) . toContain ( "overflow-hidden" ) ;
203+ expect ( messageBody ?. getAttribute ( "data-user-message-fade" ) ) . toBe ( "true" ) ;
204+ expect ( messageBody ?. style . maskImage ) . toContain ( "linear-gradient" ) ;
205+ } finally {
206+ await screen . unmount ( ) ;
207+ }
208+ } ) ;
209+
210+ it ( "expands and re-collapses long user messages from the toggle" , async ( ) => {
211+ const screen = await render (
212+ < MessagesTimeline
213+ { ...buildProps ( ) }
214+ timelineEntries = { [ buildUserTimelineEntry ( buildLongUserMessageText ( ) ) ] }
215+ /> ,
216+ ) ;
217+
218+ try {
219+ const expandButton = page . getByRole ( "button" , { name : "Show full message" } ) ;
220+ await expect . element ( expandButton ) . toBeVisible ( ) ;
221+
222+ expect ( document . body . textContent ?? "" ) . toContain ( "deep hidden detail only after expand" ) ;
223+
224+ await expandButton . click ( ) ;
225+
226+ const collapseButton = page . getByRole ( "button" , { name : "Show less" } ) ;
227+ await expect . element ( collapseButton ) . toBeVisible ( ) ;
228+ await expect . element ( collapseButton ) . toHaveAttribute ( "aria-expanded" , "true" ) ;
229+
230+ let messageBody = document . querySelector ( "[data-user-message-body='true']" ) ;
231+ expect ( messageBody ?. getAttribute ( "data-user-message-collapsed" ) ) . toBe ( "false" ) ;
232+ expect ( messageBody ?. className ) . not . toContain ( "max-h-44" ) ;
233+ expect ( messageBody ?. getAttribute ( "data-user-message-fade" ) ) . toBe ( "false" ) ;
234+ expect ( ( messageBody as HTMLDivElement | null ) ?. style . maskImage ?? "" ) . toBe ( "" ) ;
235+
236+ await collapseButton . click ( ) ;
237+
238+ await expect . element ( page . getByRole ( "button" , { name : "Show full message" } ) ) . toBeVisible ( ) ;
239+ messageBody = document . querySelector ( "[data-user-message-body='true']" ) ;
240+ expect ( messageBody ?. getAttribute ( "data-user-message-collapsed" ) ) . toBe ( "true" ) ;
241+ expect ( messageBody ?. className ) . toContain ( "max-h-44" ) ;
242+ expect ( messageBody ?. getAttribute ( "data-user-message-fade" ) ) . toBe ( "true" ) ;
243+ expect ( ( messageBody as HTMLDivElement | null ) ?. style . maskImage ) . toContain ( "linear-gradient" ) ;
244+ } finally {
245+ await screen . unmount ( ) ;
246+ }
247+ } ) ;
248+
249+ it ( "starts the newest long user prompt collapsed" , async ( ) => {
250+ const screen = await render (
251+ < MessagesTimeline
252+ { ...buildProps ( ) }
253+ timelineEntries = { [ buildUserTimelineEntry ( buildLongUserMessageText ( "latest long prompt" ) ) ] }
254+ /> ,
255+ ) ;
256+
257+ try {
258+ await expect . element ( page . getByRole ( "button" , { name : "Show full message" } ) ) . toBeVisible ( ) ;
259+
260+ const messageBody = document . querySelector ( "[data-user-message-body='true']" ) ;
261+ expect ( messageBody ?. getAttribute ( "data-user-message-collapsed" ) ) . toBe ( "true" ) ;
262+ } finally {
263+ await screen . unmount ( ) ;
264+ }
265+ } ) ;
160266} ) ;
0 commit comments