@@ -14,6 +14,8 @@ import {
1414 waitForAppReady ,
1515} from './helpers/app-test-helpers.js'
1616
17+ const defaultCommitMessage = 'chore: sync editor updates from @knighted/develop'
18+
1719const decodeGitHubFileBodyContent = ( body : Record < string , unknown > ) => {
1820 const encoded = typeof body . content === 'string' ? body . content : ''
1921 return Buffer . from ( encoded , 'base64' ) . toString ( 'utf8' )
@@ -80,6 +82,7 @@ const expectOpenPrConfirmationPrompt = async (page: Page) => {
8082test ( 'Open PR drawer confirms and submits component/styles filepaths' , async ( {
8183 page,
8284} ) => {
85+ const customCommitMessage = 'chore: sync develop editor outputs'
8386 let createdRefBody : CreateRefRequestBody | null = null
8487 const upsertRequests : Array < { path : string ; body : Record < string , unknown > } > = [ ]
8588 let pullRequestBody : PullRequestCreateBody | null = null
@@ -184,6 +187,7 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
184187 await page
185188 . getByLabel ( 'PR description' )
186189 . fill ( 'Generated from editor content in @knighted/develop.' )
190+ await page . getByLabel ( 'Commit message' ) . fill ( customCommitMessage )
187191
188192 await submitOpenPrAndConfirm ( page , {
189193 expectedSummaryLines : [
@@ -208,6 +212,8 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
208212 expect ( upsertRequests ) . toHaveLength ( 2 )
209213 expect ( upsertRequests [ 0 ] ?. path ) . toBe ( 'examples/component/App.tsx' )
210214 expect ( upsertRequests [ 1 ] ?. path ) . toBe ( 'examples/styles/app.css' )
215+ expect ( upsertRequests [ 0 ] ?. body . message ) . toBe ( customCommitMessage )
216+ expect ( upsertRequests [ 1 ] ?. body . message ) . toBe ( customCommitMessage )
211217 expect ( pullRequestPayload ?. head ) . toBe ( 'Develop/Open-Pr-Test' )
212218 expect ( pullRequestPayload ?. base ) . toBe ( 'main' )
213219
@@ -221,9 +227,9 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
221227 await expect ( page . getByLabel ( 'PR title' ) ) . toHaveValue (
222228 'Apply editor updates from develop' ,
223229 )
224- await expect ( page . getByLabel ( 'PR description' ) ) . toHaveValue (
225- 'Generated from editor content in @knighted/develop.' ,
226- )
230+ await expect ( page . getByLabel ( 'PR description' ) ) . toBeHidden ( )
231+ await expect ( page . getByLabel ( 'Commit message' ) ) . toBeVisible ( )
232+ await expect ( page . getByLabel ( 'Commit message' ) ) . toHaveValue ( customCommitMessage )
227233 await expect (
228234 page . getByRole ( 'button' , { name : 'Push commit to active pull request branch' } ) ,
229235 ) . toBeVisible ( )
@@ -232,6 +238,19 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
232238 ) . toBeVisible ( )
233239} )
234240
241+ test ( 'Open PR drawer starts with empty title/description and short default head' , async ( {
242+ page,
243+ } ) => {
244+ await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
245+ await connectByotWithSingleRepo ( page )
246+ await ensureOpenPrDrawerOpen ( page )
247+
248+ const headValue = await page . getByLabel ( 'Head' ) . inputValue ( )
249+ expect ( headValue ) . toMatch ( / ^ f e a t \/ c o m p o n e n t - [ a - z 0 - 9 ] { 4 } $ / )
250+ await expect ( page . getByLabel ( 'PR title' ) ) . toHaveValue ( '' )
251+ await expect ( page . getByLabel ( 'PR description' ) ) . toHaveValue ( '' )
252+ } )
253+
235254test ( 'Open PR drawer base dropdown updates from mocked repo branches' , async ( {
236255 page,
237256} ) => {
@@ -678,6 +697,79 @@ test('Active PR context is disabled on load when pull request is closed', async
678697 expect ( isActivePr ) . toBe ( false )
679698} )
680699
700+ test ( 'Active PR context recovers when saved head branch is missing but PR metadata exists' , async ( {
701+ page,
702+ } ) => {
703+ await page . route ( 'https://api.github.com/user/repos**' , async route => {
704+ await route . fulfill ( {
705+ status : 200 ,
706+ contentType : 'application/json' ,
707+ body : JSON . stringify ( [
708+ {
709+ id : 11 ,
710+ owner : { login : 'knightedcodemonkey' } ,
711+ name : 'develop' ,
712+ full_name : 'knightedcodemonkey/develop' ,
713+ default_branch : 'main' ,
714+ permissions : { push : true } ,
715+ } ,
716+ ] ) ,
717+ } )
718+ } )
719+
720+ await mockRepositoryBranches ( page , {
721+ 'knightedcodemonkey/develop' : [ 'main' , 'release' , 'develop/open-pr-test' ] ,
722+ } )
723+
724+ await page . route (
725+ 'https://api.github.com/repos/knightedcodemonkey/develop/pulls/2' ,
726+ async route => {
727+ await route . fulfill ( {
728+ status : 200 ,
729+ contentType : 'application/json' ,
730+ body : JSON . stringify ( {
731+ number : 2 ,
732+ state : 'open' ,
733+ title : 'Recovered PR context title' ,
734+ html_url : 'https://github.com/knightedcodemonkey/develop/pull/2' ,
735+ head : { ref : 'develop/open-pr-test' } ,
736+ base : { ref : 'main' } ,
737+ } ) ,
738+ } )
739+ } ,
740+ )
741+
742+ await waitForAppReady ( page , `${ appEntryPath } ?feature-ai=true` )
743+
744+ await page . evaluate ( ( ) => {
745+ localStorage . setItem (
746+ 'knighted:develop:github-pr-config:knightedcodemonkey/develop' ,
747+ JSON . stringify ( {
748+ componentFilePath : 'examples/component/App.tsx' ,
749+ stylesFilePath : 'examples/styles/app.css' ,
750+ renderMode : 'react' ,
751+ baseBranch : 'main' ,
752+ headBranch : '' ,
753+ prTitle : 'Recovered PR context title' ,
754+ prBody : 'Saved body' ,
755+ isActivePr : true ,
756+ pullRequestNumber : 2 ,
757+ pullRequestUrl : 'https://github.com/knightedcodemonkey/develop/pull/2' ,
758+ } ) ,
759+ )
760+ } )
761+
762+ await connectByotWithSingleRepo ( page )
763+
764+ await expect (
765+ page . getByRole ( 'button' , { name : 'Push commit to active pull request branch' } ) ,
766+ ) . toBeVisible ( )
767+
768+ await ensureOpenPrDrawerOpen ( page )
769+ await expect ( page . getByRole ( 'button' , { name : 'Push commit' } ) . last ( ) ) . toBeVisible ( )
770+ await expect ( page . getByLabel ( 'Head' ) ) . toHaveValue ( 'develop/open-pr-test' )
771+ } )
772+
681773test ( 'Active PR context uses Push commit flow without creating a new pull request' , async ( {
682774 page,
683775} ) => {
@@ -815,8 +907,34 @@ test('Active PR context uses Push commit flow without creating a new pull reques
815907 await connectByotWithSingleRepo ( page )
816908 await ensureOpenPrDrawerOpen ( page )
817909
910+ await expect ( page . getByLabel ( 'Pull request repository' ) ) . toBeDisabled ( )
911+ await expect ( page . getByLabel ( 'Pull request base branch' ) ) . toBeDisabled ( )
912+ await expect ( page . getByLabel ( 'Head' ) ) . toHaveJSProperty ( 'readOnly' , true )
913+ await expect ( page . getByLabel ( 'Component filename' ) ) . toHaveJSProperty ( 'readOnly' , true )
914+ await expect ( page . getByLabel ( 'Styles filename' ) ) . toHaveJSProperty ( 'readOnly' , true )
915+ await expect ( page . getByLabel ( 'PR title' ) ) . toHaveJSProperty ( 'readOnly' , true )
916+ await expect (
917+ page . getByLabel ( 'Include App wrapper in committed component source' ) ,
918+ ) . toBeEnabled ( )
919+ await expect ( page . getByLabel ( 'Commit message' ) ) . toBeEditable ( )
920+
921+ await expect ( page . getByLabel ( 'PR description' ) ) . toBeHidden ( )
922+ await expect ( page . getByLabel ( 'Commit message' ) ) . toBeVisible ( )
923+
924+ const includeWrapperToggle = page . getByLabel (
925+ 'Include App wrapper in committed component source' ,
926+ )
927+ await expect ( includeWrapperToggle ) . toBeEnabled ( )
928+ await includeWrapperToggle . check ( )
929+ await expect ( includeWrapperToggle ) . toBeChecked ( )
930+ await expect ( page . getByRole ( 'button' , { name : 'Push commit' } ) . last ( ) ) . toBeVisible ( )
931+ await expect ( page . getByLabel ( 'PR description' ) ) . toBeHidden ( )
932+ await expect ( page . getByLabel ( 'Commit message' ) ) . toBeVisible ( )
933+
818934 await setComponentEditorSource ( page , 'const commitMarker = 1' )
819935 await setStylesEditorSource ( page , '.commit-marker { color: red; }' )
936+ const pushCommitMessage = 'chore: push active context sync'
937+ await page . getByLabel ( 'Commit message' ) . fill ( pushCommitMessage )
820938
821939 await page . getByRole ( 'button' , { name : 'Push commit' } ) . last ( ) . click ( )
822940
@@ -846,6 +964,8 @@ test('Active PR context uses Push commit flow without creating a new pull reques
846964 expect ( upsertRequests ) . toHaveLength ( 2 )
847965 expect ( upsertRequests [ 0 ] ?. path ) . toBe ( 'examples/component/App.tsx' )
848966 expect ( upsertRequests [ 1 ] ?. path ) . toBe ( 'examples/styles/app.css' )
967+ expect ( upsertRequests [ 0 ] ?. body . message ) . toBe ( pushCommitMessage )
968+ expect ( upsertRequests [ 1 ] ?. body . message ) . toBe ( pushCommitMessage )
849969} )
850970
851971test ( 'Reloaded active PR context from URL metadata keeps Push mode and status reference' , async ( {
@@ -988,6 +1108,8 @@ test('Reloaded active PR context from URL metadata keeps Push mode and status re
9881108 await ensureOpenPrDrawerOpen ( page )
9891109 await expect ( page . getByRole ( 'button' , { name : 'Push commit' } ) . last ( ) ) . toBeVisible ( )
9901110 await expect ( page . getByLabel ( 'Head' ) ) . toHaveValue ( 'develop/open-pr-test' )
1111+ await expect ( page . getByLabel ( 'PR description' ) ) . toBeHidden ( )
1112+ await expect ( page . getByLabel ( 'Commit message' ) ) . toBeVisible ( )
9911113
9921114 await setComponentEditorSource ( page , 'const commitMarker = 1' )
9931115 await setStylesEditorSource ( page , '.commit-marker { color: red; }' )
@@ -1007,6 +1129,8 @@ test('Reloaded active PR context from URL metadata keeps Push mode and status re
10071129 expect ( upsertRequests ) . toHaveLength ( 2 )
10081130 expect ( upsertRequests [ 0 ] ?. path ) . toBe ( 'examples/component/App.tsx' )
10091131 expect ( upsertRequests [ 1 ] ?. path ) . toBe ( 'examples/styles/app.css' )
1132+ expect ( upsertRequests [ 0 ] ?. body . message ) . toBe ( defaultCommitMessage )
1133+ expect ( upsertRequests [ 1 ] ?. body . message ) . toBe ( defaultCommitMessage )
10101134} )
10111135
10121136test ( 'Reloaded active PR context syncs editor content from GitHub branch' , async ( {
@@ -1151,6 +1275,7 @@ test('Open PR drawer validates unsafe filepaths', async ({ page }) => {
11511275 await ensureOpenPrDrawerOpen ( page )
11521276
11531277 const componentPath = page . getByLabel ( 'Component filename' )
1278+ await page . getByLabel ( 'PR title' ) . fill ( 'Validate unsafe paths' )
11541279 await componentPath . fill ( '../outside/App.tsx' )
11551280 await expect ( componentPath ) . toHaveValue ( '../outside/App.tsx' )
11561281 await componentPath . blur ( )
@@ -1176,6 +1301,7 @@ test('Open PR drawer allows dotted file segments that are not traversal', async
11761301 await stylesPath . fill ( 'styles/foo..bar.css' )
11771302 await expect ( componentPath ) . toHaveValue ( 'docs/v1.0..v1.1/App.tsx' )
11781303 await expect ( stylesPath ) . toHaveValue ( 'styles/foo..bar.css' )
1304+ await page . getByLabel ( 'PR title' ) . fill ( 'Allow dotted file segments' )
11791305 await stylesPath . blur ( )
11801306
11811307 await expectOpenPrConfirmationPrompt ( page )
@@ -1190,6 +1316,7 @@ test('Open PR drawer rejects trailing slash file paths', async ({ page }) => {
11901316 await ensureOpenPrDrawerOpen ( page )
11911317
11921318 await page . getByLabel ( 'Component filename' ) . fill ( 'src/components/' )
1319+ await page . getByLabel ( 'PR title' ) . fill ( 'Reject trailing slash path' )
11931320 await clickOpenPrDrawerSubmit ( page )
11941321
11951322 await expect (
@@ -1325,6 +1452,7 @@ test('Open PR drawer strips App wrapper from committed component source by defau
13251452 await ensureOpenPrDrawerOpen ( page )
13261453
13271454 await page . getByLabel ( 'Head' ) . fill ( 'develop/repo/editor-sync-without-app' )
1455+ await page . getByLabel ( 'PR title' ) . fill ( 'Strip App wrapper by default' )
13281456 await submitOpenPrAndConfirm ( page )
13291457
13301458 await expect (
@@ -1455,6 +1583,7 @@ test('Open PR drawer includes App wrapper in committed source when toggled on',
14551583 await includeWrapperToggle . check ( )
14561584
14571585 await page . getByLabel ( 'Head' ) . fill ( 'develop/repo/editor-sync-with-app' )
1586+ await page . getByLabel ( 'PR title' ) . fill ( 'Include App wrapper in commit' )
14581587 await submitOpenPrAndConfirm ( page )
14591588
14601589 await expect (
0 commit comments