Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions playwright/github-pr-drawer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ const expectOpenPrConfirmationPrompt = async (page: Page) => {
await expect(dialog).toBeVisible()
}

const removeSavedGitHubToken = async (page: Page) => {
await page.getByRole('button', { name: 'Delete GitHub token' }).click()

const dialog = page.getByRole('dialog', {
name: 'Remove saved GitHub token?',
includeHidden: true,
})

await expect(dialog).toHaveAttribute('open', '')
await dialog.getByRole('button', { name: 'Remove' }).click()
await expect(dialog).not.toHaveAttribute('open', '')
}

test('Open PR drawer confirms and submits component/styles filepaths', async ({
page,
}) => {
Expand Down Expand Up @@ -697,6 +710,239 @@ test('Active PR context is disabled on load when pull request is closed', async
expect(isActivePr).toBe(false)
})

test('Active PR context rehydrates after token remove and re-add', async ({ page }) => {
await page.route('https://api.github.com/user/repos**', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{
id: 11,
owner: { login: 'knightedcodemonkey' },
name: 'develop',
full_name: 'knightedcodemonkey/develop',
default_branch: 'main',
permissions: { push: true },
},
{
id: 12,
owner: { login: 'knightedcodemonkey' },
name: 'css',
full_name: 'knightedcodemonkey/css',
default_branch: 'main',
permissions: { push: true },
},
]),
})
})

await mockRepositoryBranches(page, {
'knightedcodemonkey/develop': ['main', 'release'],
'knightedcodemonkey/css': ['main', 'release', 'css/rehydrate-test'],
})

await page.route(
'https://api.github.com/repos/knightedcodemonkey/css/pulls/7',
async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
number: 7,
state: 'open',
title: 'Saved css PR context',
html_url: 'https://github.com/knightedcodemonkey/css/pull/7',
head: { ref: 'css/rehydrate-test' },
base: { ref: 'main' },
}),
})
},
)

await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)

await page.evaluate(() => {
localStorage.setItem('knighted:develop:github-repository', 'knightedcodemonkey/css')
localStorage.setItem(
'knighted:develop:github-pr-config:knightedcodemonkey/css',
JSON.stringify({
componentFilePath: 'examples/component/App.tsx',
stylesFilePath: 'examples/styles/app.css',
renderMode: 'react',
baseBranch: 'main',
headBranch: 'css/rehydrate-test',
prTitle: 'Saved css PR context',
prBody: 'Saved body',
isActivePr: true,
pullRequestNumber: 7,
pullRequestUrl: 'https://github.com/knightedcodemonkey/css/pull/7',
}),
)
})

await page
.getByRole('textbox', { name: 'GitHub token' })
.fill('github_pat_fake_1234567890')
await page.getByRole('button', { name: 'Add GitHub token' }).click()

await ensureOpenPrDrawerOpen(page)
await expect(page.getByLabel('Pull request repository')).toHaveValue(
'knightedcodemonkey/css',
)
await expect(
page.getByRole('button', { name: 'Push commit to active pull request branch' }),
).toBeVisible()

await removeSavedGitHubToken(page)
await expect(page.getByRole('status', { name: 'App status' })).toHaveText(
'GitHub token removed',
)

await page
.getByRole('textbox', { name: 'GitHub token' })
.fill('github_pat_fake_1234567890')
await page.getByRole('button', { name: 'Add GitHub token' }).click()

await ensureOpenPrDrawerOpen(page)
await expect(page.getByLabel('Pull request repository')).toHaveValue(
'knightedcodemonkey/css',
)
await expect(
page.getByRole('button', { name: 'Push commit to active pull request branch' }),
).toBeVisible()

const selectedRepository = await page.evaluate(() =>
localStorage.getItem('knighted:develop:github-repository'),
)
expect(selectedRepository).toBe('knightedcodemonkey/css')
})

test('Active PR context deactivates after token remove and re-add when PR is closed', async ({
page,
}) => {
let useClosedPullRequest = false

await page.route('https://api.github.com/user/repos**', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{
id: 11,
owner: { login: 'knightedcodemonkey' },
name: 'develop',
full_name: 'knightedcodemonkey/develop',
default_branch: 'main',
permissions: { push: true },
},
{
id: 12,
owner: { login: 'knightedcodemonkey' },
name: 'css',
full_name: 'knightedcodemonkey/css',
default_branch: 'main',
permissions: { push: true },
},
]),
})
})

await mockRepositoryBranches(page, {
'knightedcodemonkey/develop': ['main', 'release'],
'knightedcodemonkey/css': ['main', 'release', 'css/rehydrate-test'],
})

await page.route(
'https://api.github.com/repos/knightedcodemonkey/css/pulls/7',
async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
number: 7,
state: useClosedPullRequest ? 'closed' : 'open',
title: 'Saved css PR context',
html_url: 'https://github.com/knightedcodemonkey/css/pull/7',
head: { ref: 'css/rehydrate-test' },
base: { ref: 'main' },
}),
})
},
)

await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)

await page.evaluate(() => {
localStorage.setItem('knighted:develop:github-repository', 'knightedcodemonkey/css')
localStorage.setItem(
'knighted:develop:github-pr-config:knightedcodemonkey/css',
JSON.stringify({
componentFilePath: 'examples/component/App.tsx',
stylesFilePath: 'examples/styles/app.css',
renderMode: 'react',
baseBranch: 'main',
headBranch: 'css/rehydrate-test',
prTitle: 'Saved css PR context',
prBody: 'Saved body',
isActivePr: true,
pullRequestNumber: 7,
pullRequestUrl: 'https://github.com/knightedcodemonkey/css/pull/7',
}),
)
})

await page
.getByRole('textbox', { name: 'GitHub token' })
.fill('github_pat_fake_1234567890')
await page.getByRole('button', { name: 'Add GitHub token' }).click()
await expect(
page.getByRole('button', { name: 'Push commit to active pull request branch' }),
).toBeVisible()

await removeSavedGitHubToken(page)
await expect(page.getByRole('status', { name: 'App status' })).toHaveText(
'GitHub token removed',
)

useClosedPullRequest = true
await page
.getByRole('textbox', { name: 'GitHub token' })
.fill('github_pat_fake_1234567890')
await page.getByRole('button', { name: 'Add GitHub token' }).click()

await ensureOpenPrDrawerOpen(page)
await expect(page.getByLabel('Pull request repository')).toHaveValue(
'knightedcodemonkey/css',
)
await expect(
page.getByRole('button', { name: 'Open pull request', exact: true }),
).toBeVisible()
await expect(
page.getByRole('button', { name: 'Close active pull request context' }),
).toBeHidden()
await expect(
page.getByRole('status', { name: 'Open pull request status', includeHidden: true }),
).toContainText('Saved pull request context is not open on GitHub.')

const isActivePr = await page.evaluate(() => {
const raw = localStorage.getItem(
'knighted:develop:github-pr-config:knightedcodemonkey/css',
)
if (!raw) {
return null
}

try {
const parsed = JSON.parse(raw)
return parsed?.isActivePr === true
} catch {
return null
}
})

expect(isActivePr).toBe(false)
})

test('Active PR context recovers when saved head branch is missing but PR metadata exists', async ({
page,
}) => {
Expand Down
3 changes: 2 additions & 1 deletion src/modules/github-byot-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
saveGitHubToken,
} from './github-token-store.js'
import { listWritableRepositories } from './github-api.js'
import { findRepositoryWithActivePrContext } from './github-pr-drawer.js'

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

Expand Down Expand Up @@ -243,7 +244,7 @@ export const createGitHubByotControls = ({

const selectedRepositoryFullName = hasStoredSelection
? lastSelectedRepository
: repos[0].fullName
: (findRepositoryWithActivePrContext(repos) ?? repos[0].fullName)

saveSelectedRepository(selectedRepositoryFullName)
lastSelectedRepository = selectedRepositoryFullName
Expand Down
22 changes: 21 additions & 1 deletion src/modules/github-pr-drawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const defaultCommitMessage = 'chore: sync editor updates from @knighted/develop'
const supportedRenderModes = new Set(['dom', 'react'])
const supportedStyleModes = new Set(['css', 'module', 'less', 'sass'])

const toSafeText = value => (typeof value === 'string' ? value.trim() : '')

const normalizeRenderMode = value => {
const mode = toSafeText(value).toLowerCase()
return supportedRenderModes.has(mode) ? mode : 'dom'
Expand Down Expand Up @@ -154,7 +156,25 @@ const getActiveRepositoryPrContext = repositoryFullName => {
}
}

const toSafeText = value => (typeof value === 'string' ? value.trim() : '')
export const findRepositoryWithActivePrContext = repositories => {
if (!Array.isArray(repositories) || repositories.length === 0) {
return null
}

for (const repository of repositories) {
const repositoryFullName = toSafeText(repository?.fullName)

if (!repositoryFullName) {
continue
}

if (getActiveRepositoryPrContext(repositoryFullName)) {
return repositoryFullName
}
}

return null
}

const normalizeFilePath = value =>
toSafeText(value).replace(/\\/g, '/').replace(/\/+/g, '/')
Expand Down
Loading