Skip to content

Commit 57138c8

Browse files
fix: reload active pr context when readding pat. (#51)
1 parent 73d913c commit 57138c8

3 files changed

Lines changed: 269 additions & 2 deletions

File tree

playwright/github-pr-drawer.spec.ts

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
8295
test('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+
700946
test('Active PR context recovers when saved head branch is missing but PR metadata exists', async ({
701947
page,
702948
}) => {

src/modules/github-byot-controls.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
saveGitHubToken,
66
} from './github-token-store.js'
77
import { listWritableRepositories } from './github-api.js'
8+
import { findRepositoryWithActivePrContext } from './github-pr-drawer.js'
89

910
const selectedRepositoryStorageKey = 'knighted:develop:github-repository'
1011

@@ -243,7 +244,7 @@ export const createGitHubByotControls = ({
243244

244245
const selectedRepositoryFullName = hasStoredSelection
245246
? lastSelectedRepository
246-
: repos[0].fullName
247+
: (findRepositoryWithActivePrContext(repos) ?? repos[0].fullName)
247248

248249
saveSelectedRepository(selectedRepositoryFullName)
249250
lastSelectedRepository = selectedRepositoryFullName

src/modules/github-pr-drawer.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const defaultCommitMessage = 'chore: sync editor updates from @knighted/develop'
2727
const supportedRenderModes = new Set(['dom', 'react'])
2828
const supportedStyleModes = new Set(['css', 'module', 'less', 'sass'])
2929

30+
const toSafeText = value => (typeof value === 'string' ? value.trim() : '')
31+
3032
const normalizeRenderMode = value => {
3133
const mode = toSafeText(value).toLowerCase()
3234
return supportedRenderModes.has(mode) ? mode : 'dom'
@@ -154,7 +156,25 @@ const getActiveRepositoryPrContext = repositoryFullName => {
154156
}
155157
}
156158

157-
const toSafeText = value => (typeof value === 'string' ? value.trim() : '')
159+
export const findRepositoryWithActivePrContext = repositories => {
160+
if (!Array.isArray(repositories) || repositories.length === 0) {
161+
return null
162+
}
163+
164+
for (const repository of repositories) {
165+
const repositoryFullName = toSafeText(repository?.fullName)
166+
167+
if (!repositoryFullName) {
168+
continue
169+
}
170+
171+
if (getActiveRepositoryPrContext(repositoryFullName)) {
172+
return repositoryFullName
173+
}
174+
}
175+
176+
return null
177+
}
158178

159179
const normalizeFilePath = value =>
160180
toSafeText(value).replace(/\\/g, '/').replace(/\/+/g, '/')

0 commit comments

Comments
 (0)