@@ -27,6 +27,8 @@ type PullRequestCreateBody = {
2727 base ?: string
2828}
2929
30+ type BranchesByRepo = Record < string , string [ ] >
31+
3032const waitForAppReady = async ( page : Page , path = appEntryPath ) => {
3133 await page . goto ( path )
3234 await expect ( page . getByRole ( 'heading' , { name : '@knighted/develop' } ) ) . toBeVisible ( )
@@ -147,6 +149,28 @@ const ensureOpenPrDrawerOpen = async (page: Page) => {
147149 await expect ( page . locator ( '#github-pr-drawer' ) ) . toBeVisible ( )
148150}
149151
152+ const mockRepositoryBranches = async (
153+ page : Page ,
154+ branchesByRepo : BranchesByRepo = { } ,
155+ ) => {
156+ await page . route ( 'https://api.github.com/repos/**/branches**' , async route => {
157+ const url = new URL ( route . request ( ) . url ( ) )
158+ const match = url . pathname . match ( / ^ \/ r e p o s \/ ( [ ^ / ] + ) \/ ( [ ^ / ] + ) \/ b r a n c h e s $ / )
159+ const repositoryKey = match ? `${ match [ 1 ] } /${ match [ 2 ] } ` : ''
160+
161+ const branchNames =
162+ branchesByRepo [ repositoryKey ] && branchesByRepo [ repositoryKey ] . length > 0
163+ ? branchesByRepo [ repositoryKey ]
164+ : [ 'main' ]
165+
166+ await route . fulfill ( {
167+ status : 200 ,
168+ contentType : 'application/json' ,
169+ body : JSON . stringify ( branchNames . map ( name => ( { name } ) ) ) ,
170+ } )
171+ } )
172+ }
173+
150174const connectByotWithSingleRepo = async ( page : Page ) => {
151175 await page . route ( 'https://api.github.com/user/repos**' , async route => {
152176 await route . fulfill ( {
@@ -165,6 +189,10 @@ const connectByotWithSingleRepo = async (page: Page) => {
165189 } )
166190 } )
167191
192+ await mockRepositoryBranches ( page , {
193+ 'knightedcodemonkey/develop' : [ 'main' , 'release' ] ,
194+ } )
195+
168196 await page . locator ( '#github-token-input' ) . fill ( 'github_pat_fake_chat_1234567890' )
169197 await page . locator ( '#github-token-add' ) . click ( )
170198 await expect ( page . locator ( '#status' ) ) . toHaveText ( 'Loaded 1 writable repositories' )
@@ -528,6 +556,11 @@ test('BYOT remembers selected repository across reloads', async ({ page }) => {
528556 } )
529557 } )
530558
559+ await mockRepositoryBranches ( page , {
560+ 'knightedcodemonkey/develop' : [ 'main' , 'release' ] ,
561+ 'knightedcodemonkey/css' : [ 'main' , 'release/1.x' ] ,
562+ } )
563+
531564 await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
532565
533566 await page . locator ( '#github-token-input' ) . fill ( 'github_pat_fake_1234567890' )
@@ -577,6 +610,10 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
577610 } )
578611 } )
579612
613+ await mockRepositoryBranches ( page , {
614+ 'knightedcodemonkey/develop' : [ 'main' , 'release' ] ,
615+ } )
616+
580617 await page . route (
581618 'https://api.github.com/repos/knightedcodemonkey/develop/git/ref/**' ,
582619 async route => {
@@ -717,6 +754,82 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
717754 )
718755} )
719756
757+ test ( 'Open PR drawer base dropdown updates from mocked repo branches' , async ( {
758+ page,
759+ } ) => {
760+ const branchRequestUrls : string [ ] = [ ]
761+
762+ await page . route ( 'https://api.github.com/user/repos**' , async route => {
763+ await route . fulfill ( {
764+ status : 200 ,
765+ contentType : 'application/json' ,
766+ body : JSON . stringify ( [
767+ {
768+ id : 2 ,
769+ owner : { login : 'knightedcodemonkey' } ,
770+ name : 'develop' ,
771+ full_name : 'knightedcodemonkey/develop' ,
772+ default_branch : 'main' ,
773+ permissions : { push : true } ,
774+ } ,
775+ {
776+ id : 1 ,
777+ owner : { login : 'knightedcodemonkey' } ,
778+ name : 'css' ,
779+ full_name : 'knightedcodemonkey/css' ,
780+ default_branch : 'stable' ,
781+ permissions : { push : true } ,
782+ } ,
783+ ] ) ,
784+ } )
785+ } )
786+
787+ await page . route ( 'https://api.github.com/repos/**/branches**' , async route => {
788+ const url = route . request ( ) . url ( )
789+ branchRequestUrls . push ( url )
790+
791+ const branchNames = url . includes ( '/repos/knightedcodemonkey/css/branches' )
792+ ? [ 'stable' , 'release/1.x' ]
793+ : [ 'main' , 'develop-next' ]
794+
795+ await route . fulfill ( {
796+ status : 200 ,
797+ contentType : 'application/json' ,
798+ body : JSON . stringify ( branchNames . map ( name => ( { name } ) ) ) ,
799+ } )
800+ } )
801+
802+ await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
803+
804+ await page . locator ( '#github-token-input' ) . fill ( 'github_pat_fake_1234567890' )
805+ await page . locator ( '#github-token-add' ) . click ( )
806+ await expect ( page . locator ( '#status' ) ) . toHaveText ( 'Loaded 2 writable repositories' )
807+
808+ await ensureOpenPrDrawerOpen ( page )
809+
810+ const repoSelect = page . locator ( '#github-pr-repo-select' )
811+ const baseSelect = page . locator ( '#github-pr-base-branch' )
812+
813+ await repoSelect . selectOption ( 'knightedcodemonkey/develop' )
814+ await expect ( baseSelect ) . toHaveValue ( 'main' )
815+ await expect ( baseSelect . locator ( 'option' ) ) . toHaveText ( [ 'main' , 'develop-next' ] )
816+
817+ await repoSelect . selectOption ( 'knightedcodemonkey/css' )
818+ await expect ( baseSelect ) . toHaveValue ( 'stable' )
819+ await expect ( baseSelect . locator ( 'option' ) ) . toHaveText ( [ 'stable' , 'release/1.x' ] )
820+
821+ expect (
822+ branchRequestUrls . some ( url =>
823+ url . includes ( 'https://api.github.com/repos/knightedcodemonkey/develop/branches' ) ,
824+ ) ,
825+ ) . toBe ( true )
826+ expect (
827+ branchRequestUrls . some ( url =>
828+ url . includes ( 'https://api.github.com/repos/knightedcodemonkey/css/branches' ) ,
829+ ) ,
830+ ) . toBe ( true )
831+ } )
832+
720833test ( 'Open PR drawer validates unsafe filepaths' , async ( { page } ) => {
721834 await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
722835 await connectByotWithSingleRepo ( page )
0 commit comments