@@ -496,6 +496,179 @@ test('Open PR drawer does not prune saved PR context on repo switch before save'
496496 expect ( contexts [ 0 ] ?. parsed ?. componentFilePath ) . toBe ( 'examples/develop/App.tsx' )
497497} )
498498
499+ test ( 'Active PR context disconnect uses local-only confirmation flow' , async ( {
500+ page,
501+ } ) => {
502+ let closePullRequestRequestCount = 0
503+
504+ await page . route ( 'https://api.github.com/user/repos**' , async route => {
505+ await route . fulfill ( {
506+ status : 200 ,
507+ contentType : 'application/json' ,
508+ body : JSON . stringify ( [
509+ {
510+ id : 11 ,
511+ owner : { login : 'knightedcodemonkey' } ,
512+ name : 'develop' ,
513+ full_name : 'knightedcodemonkey/develop' ,
514+ default_branch : 'main' ,
515+ permissions : { push : true } ,
516+ } ,
517+ ] ) ,
518+ } )
519+ } )
520+
521+ await mockRepositoryBranches ( page , {
522+ 'knightedcodemonkey/develop' : [ 'main' , 'release' ] ,
523+ } )
524+
525+ await page . route (
526+ 'https://api.github.com/repos/knightedcodemonkey/develop/pulls/2' ,
527+ async route => {
528+ if ( route . request ( ) . method ( ) === 'PATCH' ) {
529+ closePullRequestRequestCount += 1
530+ await route . fulfill ( {
531+ status : 200 ,
532+ contentType : 'application/json' ,
533+ body : JSON . stringify ( {
534+ number : 2 ,
535+ state : 'closed' ,
536+ title : 'Existing PR context from storage' ,
537+ html_url : 'https://github.com/knightedcodemonkey/develop/pull/2' ,
538+ head : { ref : 'develop/open-pr-test' } ,
539+ base : { ref : 'main' } ,
540+ } ) ,
541+ } )
542+ return
543+ }
544+
545+ await route . fulfill ( {
546+ status : 200 ,
547+ contentType : 'application/json' ,
548+ body : JSON . stringify ( {
549+ number : 2 ,
550+ state : 'open' ,
551+ title : 'Existing PR context from storage' ,
552+ html_url : 'https://github.com/knightedcodemonkey/develop/pull/2' ,
553+ head : { ref : 'develop/open-pr-test' } ,
554+ base : { ref : 'main' } ,
555+ } ) ,
556+ } )
557+ } ,
558+ )
559+
560+ await page . route (
561+ 'https://api.github.com/repos/knightedcodemonkey/develop/git/ref/**' ,
562+ async route => {
563+ await route . fulfill ( {
564+ status : 200 ,
565+ contentType : 'application/json' ,
566+ body : JSON . stringify ( {
567+ ref : 'refs/heads/develop/open-pr-test' ,
568+ object : { type : 'commit' , sha : 'existing-head-sha' } ,
569+ } ) ,
570+ } )
571+ } ,
572+ )
573+
574+ await waitForAppReady ( page , `${ appEntryPath } ` )
575+
576+ await page . evaluate ( ( ) => {
577+ localStorage . setItem (
578+ 'knighted:develop:github-pr-config:knightedcodemonkey/develop' ,
579+ JSON . stringify ( {
580+ componentFilePath : 'examples/component/App.tsx' ,
581+ stylesFilePath : 'examples/styles/app.css' ,
582+ renderMode : 'react' ,
583+ baseBranch : 'main' ,
584+ headBranch : 'develop/open-pr-test' ,
585+ prTitle : 'Existing PR context from storage' ,
586+ prBody : 'Saved body' ,
587+ isActivePr : true ,
588+ pullRequestNumber : 2 ,
589+ pullRequestUrl : 'https://github.com/knightedcodemonkey/develop/pull/2' ,
590+ } ) ,
591+ )
592+ } )
593+
594+ await connectByotWithSingleRepo ( page )
595+
596+ await expect (
597+ page . getByRole ( 'button' , { name : 'Disconnect active pull request context' } ) ,
598+ ) . toBeVisible ( )
599+
600+ await page
601+ . getByRole ( 'button' , { name : 'Disconnect active pull request context' } )
602+ . click ( )
603+
604+ const dialog = page . getByRole ( 'dialog' )
605+ await expect ( dialog ) . toBeVisible ( )
606+ await expect ( dialog ) . toContainText ( 'Disconnect PR context?' )
607+ await expect ( dialog ) . toContainText (
608+ 'This will disconnect the active pull request context in this app only.' ,
609+ )
610+ await expect ( dialog ) . toContainText ( 'Your pull request will stay open on GitHub.' )
611+ await expect ( dialog ) . toContainText (
612+ 'Your GitHub token and selected repository will stay connected.' ,
613+ )
614+
615+ await dialog . getByRole ( 'button' , { name : 'Cancel' } ) . click ( )
616+
617+ await expect (
618+ page . getByRole ( 'button' , { name : 'Push commit to active pull request branch' } ) ,
619+ ) . toBeVisible ( )
620+
621+ const savedActiveStateAfterCancel = await page . evaluate ( ( ) => {
622+ const raw = localStorage . getItem (
623+ 'knighted:develop:github-pr-config:knightedcodemonkey/develop' ,
624+ )
625+
626+ if ( ! raw ) {
627+ return null
628+ }
629+
630+ try {
631+ const parsed = JSON . parse ( raw )
632+ return parsed ?. isActivePr === true
633+ } catch {
634+ return null
635+ }
636+ } )
637+
638+ expect ( savedActiveStateAfterCancel ) . toBe ( true )
639+
640+ await page
641+ . getByRole ( 'button' , { name : 'Disconnect active pull request context' } )
642+ . click ( )
643+ await dialog . getByRole ( 'button' , { name : 'Disconnect' } ) . click ( )
644+
645+ await expect ( page . getByRole ( 'button' , { name : 'Open pull request' } ) ) . toBeVisible ( )
646+ await expect (
647+ page . getByRole ( 'button' , { name : 'Disconnect active pull request context' } ) ,
648+ ) . toBeHidden ( )
649+
650+ const savedContextAfterDisconnect = await page . evaluate ( ( ) => {
651+ const raw = localStorage . getItem (
652+ 'knighted:develop:github-pr-config:knightedcodemonkey/develop' ,
653+ )
654+
655+ if ( ! raw ) {
656+ return null
657+ }
658+
659+ try {
660+ return JSON . parse ( raw )
661+ } catch {
662+ return null
663+ }
664+ } )
665+
666+ expect ( savedContextAfterDisconnect ) . not . toBeNull ( )
667+ expect ( savedContextAfterDisconnect ?. isActivePr ) . toBe ( false )
668+ expect ( savedContextAfterDisconnect ?. pullRequestNumber ) . toBe ( 2 )
669+ expect ( closePullRequestRequestCount ) . toBe ( 0 )
670+ } )
671+
499672test ( 'Active PR context updates controls and can be closed from AI controls' , async ( {
500673 page,
501674} ) => {
0 commit comments