@@ -153,6 +153,145 @@ test('snapshot surfaces filtered-to-zero Android guidance for interactive snapsh
153153 }
154154} ) ;
155155
156+ test ( 'snapshot resolves @ref scope with the stored source after scoped output replaces refs' , async ( ) => {
157+ const sessionStore = makeSessionStore ( ) ;
158+ const sessionName = 'android-ref-scope-repeat' ;
159+ const session = makeSession ( sessionName , androidDevice ) ;
160+ session . snapshot = {
161+ nodes : [
162+ {
163+ ref : 'e1' ,
164+ index : 0 ,
165+ depth : 0 ,
166+ type : 'android.widget.FrameLayout' ,
167+ rect : { x : 0 , y : 0 , width : 390 , height : 844 } ,
168+ } ,
169+ {
170+ ref : 'e2' ,
171+ index : 1 ,
172+ depth : 1 ,
173+ parentIndex : 0 ,
174+ type : 'androidx.recyclerview.widget.RecyclerView' ,
175+ rect : { x : 0 , y : 80 , width : 390 , height : 600 } ,
176+ } ,
177+ {
178+ ref : 'e3' ,
179+ index : 2 ,
180+ depth : 2 ,
181+ parentIndex : 1 ,
182+ type : 'android.widget.TextView' ,
183+ label : 'Unable to load script. Make sure you are running Metro.' ,
184+ value : 'Unable to load script. Make sure you are running Metro.' ,
185+ rect : { x : 16 , y : 120 , width : 358 , height : 200 } ,
186+ } ,
187+ {
188+ ref : 'e4' ,
189+ index : 3 ,
190+ depth : 3 ,
191+ parentIndex : 2 ,
192+ type : 'android.widget.TextView' ,
193+ label : 'loadJSBundleFromAssets' ,
194+ rect : { x : 16 , y : 140 , width : 358 , height : 40 } ,
195+ } ,
196+ ] ,
197+ createdAt : Date . now ( ) ,
198+ backend : 'android' ,
199+ } ;
200+ sessionStore . set ( sessionName , session ) ;
201+
202+ mockDispatch . mockResolvedValue ( {
203+ nodes : [
204+ {
205+ index : 0 ,
206+ depth : 0 ,
207+ type : 'android.widget.TextView' ,
208+ label : 'Unable to load script. Make sure you are running Metro.' ,
209+ value : 'Unable to load script. Make sure you are running Metro.' ,
210+ rect : { x : 16 , y : 120 , width : 358 , height : 200 } ,
211+ } ,
212+ {
213+ index : 1 ,
214+ depth : 1 ,
215+ parentIndex : 0 ,
216+ type : 'android.widget.TextView' ,
217+ label : 'loadJSBundleFromAssets' ,
218+ rect : { x : 16 , y : 140 , width : 358 , height : 40 } ,
219+ } ,
220+ ] ,
221+ truncated : false ,
222+ backend : 'android' ,
223+ } ) ;
224+
225+ for ( let attempt = 0 ; attempt < 2 ; attempt += 1 ) {
226+ const response = await handleSnapshotCommands ( {
227+ req : {
228+ token : 't' ,
229+ session : sessionName ,
230+ command : 'snapshot' ,
231+ positionals : [ ] ,
232+ flags : { snapshotScope : '@e3' } ,
233+ } ,
234+ sessionName,
235+ logPath : '/tmp/daemon.log' ,
236+ sessionStore,
237+ } ) ;
238+
239+ expect ( response ?. ok ) . toBe ( true ) ;
240+ if ( response ?. ok ) {
241+ expect ( response . data ?. nodes ) . toHaveLength ( 2 ) ;
242+ }
243+ }
244+
245+ expect ( mockDispatch ) . toHaveBeenCalledTimes ( 2 ) ;
246+ for ( const call of mockDispatch . mock . calls ) {
247+ expect ( ( call [ 4 ] as { snapshotScope ?: string } ) ?. snapshotScope ) . toBe (
248+ 'Unable to load script. Make sure you are running Metro.' ,
249+ ) ;
250+ }
251+ const updated = sessionStore . get ( sessionName ) ;
252+ expect ( updated ?. snapshot ?. nodes ) . toHaveLength ( 2 ) ;
253+ expect ( updated ?. snapshotScopeSource ?. nodes [ 2 ] ?. ref ) . toBe ( 'e3' ) ;
254+ } ) ;
255+
256+ test ( 'empty @ref-scoped snapshot output does not replace the stored session snapshot' , async ( ) => {
257+ const sessionStore = makeSessionStore ( ) ;
258+ const sessionName = 'android-empty-scope-preserve' ;
259+ const session = makeSession ( sessionName , androidDevice ) ;
260+ session . snapshot = {
261+ nodes : [
262+ { ref : 'e1' , index : 0 , depth : 0 , type : 'android.widget.TextView' , label : 'Current screen' } ,
263+ ] ,
264+ createdAt : Date . now ( ) ,
265+ backend : 'android' ,
266+ } ;
267+ sessionStore . set ( sessionName , session ) ;
268+
269+ mockDispatch . mockResolvedValue ( {
270+ nodes : [ ] ,
271+ truncated : false ,
272+ backend : 'android' ,
273+ } ) ;
274+
275+ const response = await handleSnapshotCommands ( {
276+ req : {
277+ token : 't' ,
278+ session : sessionName ,
279+ command : 'snapshot' ,
280+ positionals : [ ] ,
281+ flags : { snapshotScope : '@e1' } ,
282+ } ,
283+ sessionName,
284+ logPath : '/tmp/daemon.log' ,
285+ sessionStore,
286+ } ) ;
287+
288+ expect ( response ?. ok ) . toBe ( true ) ;
289+ if ( response ?. ok ) {
290+ expect ( response . data ?. nodes ) . toEqual ( [ ] ) ;
291+ }
292+ expect ( sessionStore . get ( sessionName ) ?. snapshot ?. nodes [ 0 ] ?. label ) . toBe ( 'Current screen' ) ;
293+ } ) ;
294+
156295test ( 'snapshot warns when recent snapshot node count collapses sharply' , async ( ) => {
157296 const sessionStore = makeSessionStore ( ) ;
158297 const sessionName = 'android-stale-collapse' ;
0 commit comments