@@ -361,7 +361,12 @@ function untilNodes(targetY: number, hiddenBelow: boolean) {
361361 ] ;
362362}
363363
364- test ( 'bound scroll --until stops on the pass whose capture shows the selector on screen' , async ( ) => {
364+ /**
365+ * Route-level only: the executor's result envelope, the parse rejection, and admission. The loop's
366+ * own behavior — arrival, end-of-content, the pass budget, and every capture refusal — is covered
367+ * against the module in `scroll-until.test.ts` rather than duplicated through this harness.
368+ */
369+ test ( 'bound scroll --until reports the passes it spent and the selector it stopped on' , async ( ) => {
365370 const scrolls : string [ ] = [ ] ;
366371 const frames = [ untilNodes ( 2400 , true ) , untilNodes ( 1200 , true ) , untilNodes ( 300 , true ) ] ;
367372 const result = await runScroll (
@@ -377,118 +382,12 @@ test('bound scroll --until stops on the pass whose capture shows the selector on
377382 ) ;
378383
379384 assert . equal ( result . until , 'label=Email' ) ;
385+ assert . equal ( result . direction , 'down' ) ;
380386 assert . equal ( result . passes , 2 ) ;
381- assert . equal ( scrolls . length , 2 ) ;
387+ assert . deepEqual ( scrolls , [ 'down' , 'down' ] ) ;
382388 assert . match ( String ( result . message ) , / S c r o l l e d d o w n 2 p a s s e s u n t i l l a b e l = E m a i l w a s v i s i b l e / ) ;
383389} ) ;
384390
385- test ( 'bound scroll --until reports the end of the content rather than spending its budget' , async ( ) => {
386- await assert . rejects (
387- ( ) =>
388- runScroll (
389- [ 'down' ] ,
390- { until : 'label=Missing' } ,
391- {
392- captureSnapshot : async ( ) => ( { nodes : untilNodes ( 300 , false ) } ) ,
393- scroll : async ( ) => ( { } ) ,
394- } ,
395- ) ,
396- ( error : unknown ) => {
397- assert . ok ( error instanceof AppError ) ;
398- assert . equal ( error . details ?. reason , 'scroll_until_edge_reached' ) ;
399- return true ;
400- } ,
401- ) ;
402- } ) ;
403-
404- test ( 'bound scroll rejects --until on an edge direction before any device work' , async ( ) => {
405- await assert . rejects (
406- ( ) =>
407- runScroll (
408- [ 'bottom' ] ,
409- { until : 'label=Email' } ,
410- {
411- captureSnapshot : async ( ) => ( { nodes : untilNodes ( 300 , true ) } ) ,
412- scroll : async ( ) => {
413- throw new Error ( 'scroll should be rejected before the backend call' ) ;
414- } ,
415- } ,
416- ) ,
417- / s c r o l l b o t t o m a l r e a d y s c r o l l s t o t h e b o t t o m e d g e a n d c a n n o t t a k e - - u n t i l / ,
418- ) ;
419- } ) ;
420-
421- test ( 'bound scroll --until is refused when the owner advertises no capture' , async ( ) => {
422- const resolved = await resolveBoundScrollRuntime ( {
423- device : IOS_SIMULATOR ,
424- positionals : [ 'down' ] ,
425- context : { until : 'label=Email' } as DaemonCommandContext ,
426- ...bindings ( { scroll : async ( ) => ( { } ) } ) ,
427- } ) ;
428- assert . equal ( resolved . ok , false ) ;
429- } ) ;
430-
431- /** Same defect as the command runtime's: a failed read is not evidence that the content ran out. */
432- /**
433- * The owner's capture result spells the verdict `quality`, which is the shape this route actually
434- * receives — an earlier version of this test asserted through `snapshotQuality` and passed while
435- * the real field went unread. The scroll spy proves each refusal lands before any gesture, and the
436- * sparse tree deliberately has content below the fold, so an edge verdict would be wrong there too.
437- */
438- test ( 'bound scroll --until reports an unreadable capture as a capture failure, not end-of-content' , async ( ) => {
439- let scrolls = 0 ;
440- await assert . rejects (
441- ( ) =>
442- runScroll (
443- [ 'down' ] ,
444- { until : 'label=Email' } ,
445- {
446- captureSnapshot : async ( ) => ( { } ) ,
447- scroll : async ( ) => {
448- scrolls += 1 ;
449- return { } ;
450- } ,
451- } ,
452- ) ,
453- ( error : unknown ) => {
454- assert . ok ( error instanceof AppError ) ;
455- assert . equal ( error . details ?. reason , 'scroll_until_capture_unreadable' ) ;
456- assert . equal ( error . details ?. captureRefusal , 'no-capture' ) ;
457- return true ;
458- } ,
459- ) ;
460- assert . equal ( scrolls , 0 ) ;
461- } ) ;
462-
463- test ( 'bound scroll --until refuses a sparse capture before matching, edge analysis or scrolling' , async ( ) => {
464- let scrolls = 0 ;
465- await assert . rejects (
466- ( ) =>
467- runScroll (
468- [ 'down' ] ,
469- { until : 'label=Email' } ,
470- {
471- captureSnapshot : async ( ) => ( {
472- nodes : untilNodes ( 2400 , true ) ,
473- quality : { state : 'sparse' , backend : 'tree' , reason : 'AX bridge unavailable' } ,
474- } ) ,
475- scroll : async ( ) => {
476- scrolls += 1 ;
477- return { } ;
478- } ,
479- } ,
480- ) ,
481- ( error : unknown ) => {
482- assert . ok ( error instanceof AppError ) ;
483- assert . equal ( error . details ?. reason , 'scroll_until_capture_unreadable' ) ;
484- assert . equal ( error . details ?. captureRefusal , 'sparse-tree' ) ;
485- assert . match ( String ( error . message ) , / A X b r i d g e u n a v a i l a b l e / ) ;
486- return true ;
487- } ,
488- ) ;
489- assert . equal ( scrolls , 0 ) ;
490- } ) ;
491-
492391test ( 'bound scroll rejects --until on an edge direction before any device work' , async ( ) => {
493392 await assert . rejects (
494393 ( ) =>
@@ -502,11 +401,11 @@ test('bound scroll rejects --until on an edge direction before any device work',
502401 } ,
503402 } ,
504403 ) ,
505- / s c r o l l b o t t o m a l r e a d y s c r o l l s t o t h e b o t t o m e d g e a n d c a n n o t t a k e - - u n t i l / ,
404+ / c a n n o t t a k e - - u n t i l / ,
506405 ) ;
507406} ) ;
508407
509- test ( 'bound scroll --until is refused when the owner advertises no capture' , async ( ) => {
408+ test ( 'bound scroll --until is refused at admission when the owner declares no capture' , async ( ) => {
510409 const resolved = await resolveBoundScrollRuntime ( {
511410 device : IOS_SIMULATOR ,
512411 positionals : [ 'down' ] ,
@@ -515,46 +414,3 @@ test('bound scroll --until is refused when the owner advertises no capture', asy
515414 } ) ;
516415 assert . equal ( resolved . ok , false ) ;
517416} ) ;
518-
519- /** Same defect as the command runtime's: a failed read is not evidence that the content ran out. */
520- test ( 'bound scroll --until reports an unreadable capture as a capture failure, not end-of-content' , async ( ) => {
521- await assert . rejects (
522- ( ) =>
523- runScroll (
524- [ 'down' ] ,
525- { until : 'label=Email' } ,
526- {
527- captureSnapshot : async ( ) => ( { } ) ,
528- scroll : async ( ) => ( { } ) ,
529- } ,
530- ) ,
531- ( error : unknown ) => {
532- assert . ok ( error instanceof AppError ) ;
533- assert . equal ( error . details ?. reason , 'scroll_until_capture_unreadable' ) ;
534- assert . equal ( error . details ?. captureRefusal , 'no-capture' ) ;
535- return true ;
536- } ,
537- ) ;
538- } ) ;
539-
540- test ( 'bound scroll --until refuses a sparse capture rather than trusting its selectors' , async ( ) => {
541- await assert . rejects (
542- ( ) =>
543- runScroll (
544- [ 'down' ] ,
545- { until : 'label=Email' } ,
546- {
547- captureSnapshot : async ( ) => ( {
548- nodes : untilNodes ( 2400 , true ) ,
549- snapshotQuality : { state : 'sparse' , backend : 'tree' , reason : 'AX bridge unavailable' } ,
550- } ) ,
551- scroll : async ( ) => ( { } ) ,
552- } ,
553- ) ,
554- ( error : unknown ) => {
555- assert . ok ( error instanceof AppError ) ;
556- assert . equal ( error . details ?. captureRefusal , 'sparse-tree' ) ;
557- return true ;
558- } ,
559- ) ;
560- } ) ;
0 commit comments