Skip to content

Commit 919b63a

Browse files
feat: commit message. (#48)
1 parent 1253eed commit 919b63a

File tree

7 files changed

+374
-66
lines changed

7 files changed

+374
-66
lines changed

.github/workflows/playwright.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
name: Playwright
22

33
on:
4-
push:
4+
pull_request:
55
branches:
66
- main
7-
paths:
7+
types:
8+
- opened
9+
- synchronize
10+
paths: &playwright_paths
811
- 'src/**'
912
- 'playwright/**'
1013
- 'package-lock.json'
1114
- 'playwright.config.ts'
1215
- '.github/workflows/playwright.yml'
16+
push:
17+
branches:
18+
- main
19+
paths: *playwright_paths
1320
workflow_dispatch:
1421

22+
concurrency:
23+
group: playwright-${{ github.workflow }}-${{ github.ref }}
24+
cancel-in-progress: true
25+
1526
jobs:
1627
e2e:
1728
name: ${{ matrix.jobName }}

playwright/github-pr-drawer.spec.ts

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1719
const 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) => {
8082
test('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(/^feat\/component-[a-z0-9]{4}$/)
250+
await expect(page.getByLabel('PR title')).toHaveValue('')
251+
await expect(page.getByLabel('PR description')).toHaveValue('')
252+
})
253+
235254
test('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+
681773
test('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

851971
test('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

10121136
test('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(

playwright/helpers/app-test-helpers.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,19 @@ export type BranchesByRepo = Record<string, string[]>
3333
export const waitForAppReady = async (page: Page, path = appEntryPath) => {
3434
await page.goto(path)
3535
await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible()
36-
await expect(page.locator('#cdn-loading')).toHaveAttribute('hidden', '')
3736
await expect
38-
.poll(() => page.getByRole('status', { name: 'App status' }).textContent())
39-
.not.toBe('Idle')
37+
.poll(async () => {
38+
const statusText = (
39+
await page.getByRole('status', { name: 'App status' }).textContent()
40+
)?.trim()
41+
42+
return (
43+
statusText === 'Rendered' ||
44+
statusText?.startsWith('Rendered (Type errors:') ||
45+
statusText === 'Error'
46+
)
47+
})
48+
.toBe(true)
4049
}
4150

4251
export const waitForInitialRender = async (page: Page) => {
@@ -215,7 +224,8 @@ export const connectByotWithSingleRepo = async (page: Page) => {
215224
await page.getByRole('button', { name: 'Add GitHub token' }).click()
216225

217226
const repoSelect = page.getByLabel('Pull request repository')
218-
await expect(repoSelect).toBeEnabled({ timeout: 60_000 })
227+
await expect(repoSelect).toHaveValue('knightedcodemonkey/develop')
228+
219229
await expect(repoSelect).toHaveValue('knightedcodemonkey/develop')
220230

221231
await expect(

src/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const githubPrComponentPath = document.getElementById('github-pr-component-path'
6060
const githubPrStylesPath = document.getElementById('github-pr-styles-path')
6161
const githubPrTitle = document.getElementById('github-pr-title')
6262
const githubPrBody = document.getElementById('github-pr-body')
63+
const githubPrCommitMessage = document.getElementById('github-pr-commit-message')
6364
const githubPrIncludeAppWrapper = document.getElementById('github-pr-include-app-wrapper')
6465
const githubPrSubmit = document.getElementById('github-pr-submit')
6566
const componentPrSyncIcon = document.getElementById('component-pr-sync-icon')
@@ -801,6 +802,7 @@ prDrawerController = createGitHubPrDrawer({
801802
stylesPathInput: githubPrStylesPath,
802803
prTitleInput: githubPrTitle,
803804
prBodyInput: githubPrBody,
805+
commitMessageInput: githubPrCommitMessage,
804806
includeAppWrapperToggle: githubPrIncludeAppWrapper,
805807
submitButton: githubPrSubmit,
806808
titleNode: openPrTitle,

src/index.html

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ <h2 id="open-pr-title">Open Pull Request</h2>
798798
type="text"
799799
autocomplete="off"
800800
spellcheck="false"
801-
placeholder="develop/repo/editor-sync-yyyymmdd"
801+
placeholder="feat/component-ab12"
802802
/>
803803
</label>
804804

@@ -857,6 +857,20 @@ <h2 id="open-pr-title">Open Pull Request</h2>
857857
placeholder="Describe the generated editor updates."
858858
></textarea>
859859
</label>
860+
861+
<label
862+
class="github-pr-field github-pr-field--full"
863+
for="github-pr-commit-message"
864+
>
865+
<span>Commit message</span>
866+
<input
867+
id="github-pr-commit-message"
868+
type="text"
869+
autocomplete="off"
870+
spellcheck="false"
871+
placeholder="chore: sync editor updates from @knighted/develop"
872+
/>
873+
</label>
860874
</div>
861875

862876
<div class="github-pr-drawer__actions">
@@ -881,7 +895,7 @@ <h2 id="open-pr-title">Open Pull Request</h2>
881895
<div class="cdn-loading" id="cdn-loading" role="status" aria-live="polite">
882896
<div class="cdn-loading-card">
883897
<div class="cdn-loading-spinner" aria-hidden="true"></div>
884-
<p class="cdn-loading-title">Loading playground assets…</p>
898+
<p class="cdn-loading-title">Loading IDE assets…</p>
885899
<p class="cdn-loading-copy">
886900
Fetching runtimes and compilers from CDN. This can take a moment.
887901
</p>

0 commit comments

Comments
 (0)