@@ -85,6 +85,77 @@ describe("createTestRenderer", () => {
8585 assert . ok ( ( bottom ?. rect . y ?? 0 ) >= ( top ?. rect . y ?? 0 ) + ( top ?. rect . h ?? 0 ) ) ;
8686 } ) ;
8787
88+ test ( "query rect origins are clamped to the viewport while raw layout keeps scroll offsets" , ( ) => {
89+ const viewport = { cols : 5 , rows : 3 } ;
90+ const renderer = createTestRenderer ( { viewport } ) ;
91+
92+ const result = renderer . render (
93+ ui . column (
94+ { id : "scroll" , width : 5 , height : 3 , overflow : "scroll" , scrollX : 99 , scrollY : 99 } ,
95+ [
96+ ui . box ( { id : "oversized" , border : "none" , mr : - 4 , mb : - 1 } , [
97+ ui . text ( "123456789" ) ,
98+ ui . text ( "line2" ) ,
99+ ui . text ( "line3" ) ,
100+ ui . text ( "line4" ) ,
101+ ] ) ,
102+ ] ,
103+ ) ,
104+ ) ;
105+
106+ for ( const node of result . nodes ) {
107+ assert . ok ( node . rect . x >= 0 , `${ node . kind } x should be inside viewport` ) ;
108+ assert . ok ( node . rect . y >= 0 , `${ node . kind } y should be inside viewport` ) ;
109+ assert . ok ( node . rect . w >= 0 , `${ node . kind } width should be non-negative` ) ;
110+ assert . ok ( node . rect . h >= 0 , `${ node . kind } height should be non-negative` ) ;
111+ }
112+
113+ assert . deepEqual ( result . findById ( "oversized" ) ?. rect , { x : 0 , y : 0 , w : 9 , h : 4 } ) ;
114+
115+ let rawOversizedX : number | null = null ;
116+ let rawOversizedY : number | null = null ;
117+ result . forEachLayoutNode ( ( rect , props ) => {
118+ if ( props . id === "oversized" ) {
119+ rawOversizedX = rect . x ;
120+ rawOversizedY = rect . y ;
121+ }
122+ } ) ;
123+ assert . notEqual ( rawOversizedX , null ) ;
124+ assert . notEqual ( rawOversizedY , null ) ;
125+ assert . ok ( ( rawOversizedX ?? 0 ) < 0 ) ;
126+ assert . ok ( ( rawOversizedY ?? 0 ) < 0 ) ;
127+ } ) ;
128+
129+ test ( "query rect origins preserve positive offscreen layout positions" , ( ) => {
130+ const viewport = { cols : 8 , rows : 2 } ;
131+ const renderer = createTestRenderer ( { viewport } ) ;
132+
133+ const result = renderer . render (
134+ ui . column ( { gap : 0 } , [
135+ ui . box ( { id : "anchor" , border : "none" , height : 1 } , [ ui . text ( "anchor" ) ] ) ,
136+ ui . box ( { id : "offscreen" , border : "none" , position : "absolute" , left : 11 , top : 4 } , [
137+ ui . text ( "offscreen" ) ,
138+ ] ) ,
139+ ] ) ,
140+ ) ;
141+
142+ const offscreen = result . findById ( "offscreen" ) ;
143+ assert . notEqual ( offscreen , null ) ;
144+ assert . ok ( ( offscreen ?. rect . x ?? 0 ) > viewport . cols ) ;
145+ assert . ok ( ( offscreen ?. rect . y ?? 0 ) > viewport . rows ) ;
146+
147+ let rawOffscreenX : number | null = null ;
148+ let rawOffscreenY : number | null = null ;
149+ result . forEachLayoutNode ( ( rect , props ) => {
150+ if ( props . id === "offscreen" ) {
151+ rawOffscreenX = rect . x ;
152+ rawOffscreenY = rect . y ;
153+ }
154+ } ) ;
155+ assert . equal ( offscreen ?. rect . x , rawOffscreenX ) ;
156+ assert . equal ( offscreen ?. rect . y , rawOffscreenY ) ;
157+ } ) ;
158+
88159 test ( "trace callback receives render timing and detail payload" , ( ) => {
89160 const events : unknown [ ] = [ ] ;
90161 const renderer = createTestRenderer ( {
0 commit comments