@@ -79,6 +79,19 @@ const expectOpenPrConfirmationPrompt = async (page: Page) => {
7979 await expect ( dialog ) . toBeVisible ( )
8080}
8181
82+ const removeSavedGitHubToken = async ( page : Page ) => {
83+ await page . getByRole ( 'button' , { name : 'Delete GitHub token' } ) . click ( )
84+
85+ const dialog = page . getByRole ( 'dialog' , {
86+ name : 'Remove saved GitHub token?' ,
87+ includeHidden : true ,
88+ } )
89+
90+ await expect ( dialog ) . toHaveAttribute ( 'open' , '' )
91+ await dialog . getByRole ( 'button' , { name : 'Remove' } ) . click ( )
92+ await expect ( dialog ) . not . toHaveAttribute ( 'open' , '' )
93+ }
94+
8295test ( 'Open PR drawer confirms and submits component/styles filepaths' , async ( {
8396 page,
8497} ) => {
@@ -697,6 +710,239 @@ test('Active PR context is disabled on load when pull request is closed', async
697710 expect ( isActivePr ) . toBe ( false )
698711} )
699712
713+ test ( 'Active PR context rehydrates after token remove and re-add' , async ( { page } ) => {
714+ await page . route ( 'https://api.github.com/user/repos**' , async route => {
715+ await route . fulfill ( {
716+ status : 200 ,
717+ contentType : 'application/json' ,
718+ body : JSON . stringify ( [
719+ {
720+ id : 11 ,
721+ owner : { login : 'knightedcodemonkey' } ,
722+ name : 'develop' ,
723+ full_name : 'knightedcodemonkey/develop' ,
724+ default_branch : 'main' ,
725+ permissions : { push : true } ,
726+ } ,
727+ {
728+ id : 12 ,
729+ owner : { login : 'knightedcodemonkey' } ,
730+ name : 'css' ,
731+ full_name : 'knightedcodemonkey/css' ,
732+ default_branch : 'main' ,
733+ permissions : { push : true } ,
734+ } ,
735+ ] ) ,
736+ } )
737+ } )
738+
739+ await mockRepositoryBranches ( page , {
740+ 'knightedcodemonkey/develop' : [ 'main' , 'release' ] ,
741+ 'knightedcodemonkey/css' : [ 'main' , 'release' , 'css/rehydrate-test' ] ,
742+ } )
743+
744+ await page . route (
745+ 'https://api.github.com/repos/knightedcodemonkey/css/pulls/7' ,
746+ async route => {
747+ await route . fulfill ( {
748+ status : 200 ,
749+ contentType : 'application/json' ,
750+ body : JSON . stringify ( {
751+ number : 7 ,
752+ state : 'open' ,
753+ title : 'Saved css PR context' ,
754+ html_url : 'https://github.com/knightedcodemonkey/css/pull/7' ,
755+ head : { ref : 'css/rehydrate-test' } ,
756+ base : { ref : 'main' } ,
757+ } ) ,
758+ } )
759+ } ,
760+ )
761+
762+ await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
763+
764+ await page . evaluate ( ( ) => {
765+ localStorage . setItem ( 'knighted:develop:github-repository' , 'knightedcodemonkey/css' )
766+ localStorage . setItem (
767+ 'knighted:develop:github-pr-config:knightedcodemonkey/css' ,
768+ JSON . stringify ( {
769+ componentFilePath : 'examples/component/App.tsx' ,
770+ stylesFilePath : 'examples/styles/app.css' ,
771+ renderMode : 'react' ,
772+ baseBranch : 'main' ,
773+ headBranch : 'css/rehydrate-test' ,
774+ prTitle : 'Saved css PR context' ,
775+ prBody : 'Saved body' ,
776+ isActivePr : true ,
777+ pullRequestNumber : 7 ,
778+ pullRequestUrl : 'https://github.com/knightedcodemonkey/css/pull/7' ,
779+ } ) ,
780+ )
781+ } )
782+
783+ await page
784+ . getByRole ( 'textbox' , { name : 'GitHub token' } )
785+ . fill ( 'github_pat_fake_1234567890' )
786+ await page . getByRole ( 'button' , { name : 'Add GitHub token' } ) . click ( )
787+
788+ await ensureOpenPrDrawerOpen ( page )
789+ await expect ( page . getByLabel ( 'Pull request repository' ) ) . toHaveValue (
790+ 'knightedcodemonkey/css' ,
791+ )
792+ await expect (
793+ page . getByRole ( 'button' , { name : 'Push commit to active pull request branch' } ) ,
794+ ) . toBeVisible ( )
795+
796+ await removeSavedGitHubToken ( page )
797+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText (
798+ 'GitHub token removed' ,
799+ )
800+
801+ await page
802+ . getByRole ( 'textbox' , { name : 'GitHub token' } )
803+ . fill ( 'github_pat_fake_1234567890' )
804+ await page . getByRole ( 'button' , { name : 'Add GitHub token' } ) . click ( )
805+
806+ await ensureOpenPrDrawerOpen ( page )
807+ await expect ( page . getByLabel ( 'Pull request repository' ) ) . toHaveValue (
808+ 'knightedcodemonkey/css' ,
809+ )
810+ await expect (
811+ page . getByRole ( 'button' , { name : 'Push commit to active pull request branch' } ) ,
812+ ) . toBeVisible ( )
813+
814+ const selectedRepository = await page . evaluate ( ( ) =>
815+ localStorage . getItem ( 'knighted:develop:github-repository' ) ,
816+ )
817+ expect ( selectedRepository ) . toBe ( 'knightedcodemonkey/css' )
818+ } )
819+
820+ test ( 'Active PR context deactivates after token remove and re-add when PR is closed' , async ( {
821+ page,
822+ } ) => {
823+ let useClosedPullRequest = false
824+
825+ await page . route ( 'https://api.github.com/user/repos**' , async route => {
826+ await route . fulfill ( {
827+ status : 200 ,
828+ contentType : 'application/json' ,
829+ body : JSON . stringify ( [
830+ {
831+ id : 11 ,
832+ owner : { login : 'knightedcodemonkey' } ,
833+ name : 'develop' ,
834+ full_name : 'knightedcodemonkey/develop' ,
835+ default_branch : 'main' ,
836+ permissions : { push : true } ,
837+ } ,
838+ {
839+ id : 12 ,
840+ owner : { login : 'knightedcodemonkey' } ,
841+ name : 'css' ,
842+ full_name : 'knightedcodemonkey/css' ,
843+ default_branch : 'main' ,
844+ permissions : { push : true } ,
845+ } ,
846+ ] ) ,
847+ } )
848+ } )
849+
850+ await mockRepositoryBranches ( page , {
851+ 'knightedcodemonkey/develop' : [ 'main' , 'release' ] ,
852+ 'knightedcodemonkey/css' : [ 'main' , 'release' , 'css/rehydrate-test' ] ,
853+ } )
854+
855+ await page . route (
856+ 'https://api.github.com/repos/knightedcodemonkey/css/pulls/7' ,
857+ async route => {
858+ await route . fulfill ( {
859+ status : 200 ,
860+ contentType : 'application/json' ,
861+ body : JSON . stringify ( {
862+ number : 7 ,
863+ state : useClosedPullRequest ? 'closed' : 'open' ,
864+ title : 'Saved css PR context' ,
865+ html_url : 'https://github.com/knightedcodemonkey/css/pull/7' ,
866+ head : { ref : 'css/rehydrate-test' } ,
867+ base : { ref : 'main' } ,
868+ } ) ,
869+ } )
870+ } ,
871+ )
872+
873+ await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
874+
875+ await page . evaluate ( ( ) => {
876+ localStorage . setItem ( 'knighted:develop:github-repository' , 'knightedcodemonkey/css' )
877+ localStorage . setItem (
878+ 'knighted:develop:github-pr-config:knightedcodemonkey/css' ,
879+ JSON . stringify ( {
880+ componentFilePath : 'examples/component/App.tsx' ,
881+ stylesFilePath : 'examples/styles/app.css' ,
882+ renderMode : 'react' ,
883+ baseBranch : 'main' ,
884+ headBranch : 'css/rehydrate-test' ,
885+ prTitle : 'Saved css PR context' ,
886+ prBody : 'Saved body' ,
887+ isActivePr : true ,
888+ pullRequestNumber : 7 ,
889+ pullRequestUrl : 'https://github.com/knightedcodemonkey/css/pull/7' ,
890+ } ) ,
891+ )
892+ } )
893+
894+ await page
895+ . getByRole ( 'textbox' , { name : 'GitHub token' } )
896+ . fill ( 'github_pat_fake_1234567890' )
897+ await page . getByRole ( 'button' , { name : 'Add GitHub token' } ) . click ( )
898+ await expect (
899+ page . getByRole ( 'button' , { name : 'Push commit to active pull request branch' } ) ,
900+ ) . toBeVisible ( )
901+
902+ await removeSavedGitHubToken ( page )
903+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText (
904+ 'GitHub token removed' ,
905+ )
906+
907+ useClosedPullRequest = true
908+ await page
909+ . getByRole ( 'textbox' , { name : 'GitHub token' } )
910+ . fill ( 'github_pat_fake_1234567890' )
911+ await page . getByRole ( 'button' , { name : 'Add GitHub token' } ) . click ( )
912+
913+ await ensureOpenPrDrawerOpen ( page )
914+ await expect ( page . getByLabel ( 'Pull request repository' ) ) . toHaveValue (
915+ 'knightedcodemonkey/css' ,
916+ )
917+ await expect (
918+ page . getByRole ( 'button' , { name : 'Open pull request' , exact : true } ) ,
919+ ) . toBeVisible ( )
920+ await expect (
921+ page . getByRole ( 'button' , { name : 'Close active pull request context' } ) ,
922+ ) . toBeHidden ( )
923+ await expect (
924+ page . getByRole ( 'status' , { name : 'Open pull request status' , includeHidden : true } ) ,
925+ ) . toContainText ( 'Saved pull request context is not open on GitHub.' )
926+
927+ const isActivePr = await page . evaluate ( ( ) => {
928+ const raw = localStorage . getItem (
929+ 'knighted:develop:github-pr-config:knightedcodemonkey/css' ,
930+ )
931+ if ( ! raw ) {
932+ return null
933+ }
934+
935+ try {
936+ const parsed = JSON . parse ( raw )
937+ return parsed ?. isActivePr === true
938+ } catch {
939+ return null
940+ }
941+ } )
942+
943+ expect ( isActivePr ) . toBe ( false )
944+ } )
945+
700946test ( 'Active PR context recovers when saved head branch is missing but PR metadata exists' , async ( {
701947 page,
702948} ) => {
0 commit comments