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
1 change: 1 addition & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ if (isCI || includeWebKit) {

export default defineConfig({
testDir: 'playwright',
fullyParallel: isCI,
timeout: isCI ? 120_000 : 20_000,
retries: isCI ? 1 : 0,
workers: isCI ? 1 : undefined,
Expand Down
15 changes: 15 additions & 0 deletions playwright/github-byot-ai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,28 @@ test('PR/BYOT controls are visible and chat stays hidden until token connect', a
exact: true,
includeHidden: true,
})
const workspacesToggle = page.getByRole('button', {
name: 'Workspaces',
exact: true,
includeHidden: true,
})
await expect(byotControls).toBeVisible()
await expect(page.getByRole('textbox', { name: 'GitHub token' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Add GitHub token' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Chat' })).toBeHidden()
await expect(page.getByRole('heading', { name: 'AI Chat' })).toBeHidden()
await expect(prToggle).toHaveCount(1)
await expect(prToggle).toBeHidden()
await expect(workspacesToggle).toHaveCount(1)
await expect(workspacesToggle).toBeHidden()
})

test('chat becomes available after token connect', async ({ page }) => {
await waitForAppReady(page)
await connectByotWithSingleRepo(page)

await expect(page.getByRole('button', { name: 'Open pull request' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Workspaces' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Chat' })).toBeVisible()
})

Expand All @@ -49,12 +57,19 @@ test('BYOT controls render with default app entry', async ({ page }) => {
exact: true,
includeHidden: true,
})
const workspacesToggle = page.getByRole('button', {
name: 'Workspaces',
exact: true,
includeHidden: true,
})
await expect(byotControls).toBeVisible()
await expect(page.getByRole('textbox', { name: 'GitHub token' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Add GitHub token' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Chat' })).toBeHidden()
await expect(prToggle).toHaveCount(1)
await expect(prToggle).toBeHidden()
await expect(workspacesToggle).toHaveCount(1)
await expect(workspacesToggle).toBeHidden()
})

test('GitHub token info panel reflects missing and present token states', async ({
Expand Down
34 changes: 22 additions & 12 deletions playwright/helpers/app-test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
export const waitForAppReady = async (page: Page, path = appEntryPath) => {
await navigateToApp(page, path)
await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible()
await expect

Check failure on line 77 in playwright/helpers/app-test-helpers.ts

View workflow job for this annotation

GitHub Actions / E2E (Playwright, webkit, shard 3/4)

[webkit] › playwright/rendering-modes.spec.ts:664:1 › auto render shows App-only error for standalone JSX expression

1) [webkit] › playwright/rendering-modes.spec.ts:664:1 › auto render shows App-only error for standalone JSX expression Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false Call Log: - Timeout 90000ms exceeded while waiting on the predicate at helpers/app-test-helpers.ts:77 75 | await navigateToApp(page, path) 76 | await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible() > 77 | await expect | ^ 78 | .poll(async () => { 79 | const statusText = ( 80 | await page.getByRole('status', { name: 'App status' }).textContent() at waitForAppReady (/home/runner/work/develop/develop/playwright/helpers/app-test-helpers.ts:77:3) at waitForInitialRender (/home/runner/work/develop/develop/playwright/helpers/app-test-helpers.ts:140:3) at /home/runner/work/develop/develop/playwright/rendering-modes.spec.ts:667:3
.poll(async () => {
const statusText = (
await page.getByRole('status', { name: 'App status' }).textContent()
Expand Down Expand Up @@ -171,6 +171,22 @@
await page.getByRole('button', { name: pattern }).click()
}

const replaceEditorSource = async ({
editorContent,
source,
}: {
editorContent: ReturnType<Page['locator']>
source: string
}) => {
for (let attempt = 0; attempt < 2; attempt += 1) {
await editorContent.fill('')
await editorContent.fill(source)
await editorContent.press('End')
await editorContent.type(' ')
await editorContent.press('Backspace')
}
}

export const reorderWorkspaceTabBefore = async (
page: Page,
{ from, to }: { from: string; to: string },
Expand Down Expand Up @@ -199,35 +215,29 @@
},
) => {
await openWorkspaceTab(page, fileName)
await expect(page.getByRole('region', { name: fileName })).toBeVisible()
const editorContent = page
.locator(`.editor-panel[data-editor-kind="${kind}"] .cm-content`)
.first()
await editorContent.fill(source)
await editorContent.press('End')
await editorContent.type(' ')
await editorContent.press('Backspace')
await replaceEditorSource({ editorContent, source })
}

export const setComponentEditorSource = async (page: Page, source: string) => {
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
await expect(page.getByRole('region', { name: 'App.tsx' })).toBeVisible()
const editorContent = page
.locator('.editor-panel[data-editor-kind="component"] .cm-content')
.first()
await editorContent.fill(source)
await editorContent.press('End')
await editorContent.type(' ')
await editorContent.press('Backspace')
await replaceEditorSource({ editorContent, source })
}

export const setStylesEditorSource = async (page: Page, source: string) => {
await page.getByRole('button', { name: 'Open tab app.css' }).click()
await expect(page.getByRole('region', { name: 'app.css' })).toBeVisible()
const editorContent = page
.locator('.editor-panel[data-editor-kind="styles"] .cm-content')
.first()
await editorContent.fill(source)
await editorContent.press('End')
await editorContent.type(' ')
await editorContent.press('Backspace')
await replaceEditorSource({ editorContent, source })
}

export const getActiveComponentEditorLineNumber = async (page: Page) => {
Expand Down
50 changes: 30 additions & 20 deletions playwright/rendering-modes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ test('shows App-only error when auto render is disabled and App is missing', asy
)
})

test('auto render implicitly wraps source with App in dom and react modes', async ({
test('auto render shows App-only error in dom and react modes when App is missing', async ({
page,
}) => {
await waitForInitialRender(page)
Expand All @@ -589,9 +589,9 @@ test('auto render implicitly wraps source with App in dom and react modes', asyn
'const Button = () => <button type="button">implicit app dom</button>',
)

await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
'implicit app dom',
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
await expect(page.locator('#preview-host pre')).toContainText(
'Expected a function or const named App.',
)

await page.getByRole('combobox', { name: 'Render mode' }).selectOption('react')
Expand All @@ -604,13 +604,13 @@ test('auto render implicitly wraps source with App in dom and react modes', asyn
page.locator('.editor-panel[data-editor-kind="component"] .cm-content').first(),
).toContainText('implicit app react')

await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
'implicit app react',
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
await expect(page.locator('#preview-host pre')).toContainText(
'Expected a function or const named App.',
)
})

test('auto render implicit App includes multiple component declarations', async ({
test('auto render renders successfully when explicit App is defined in dom and react modes', async ({
page,
}) => {
await waitForInitialRender(page)
Expand All @@ -619,15 +619,25 @@ test('auto render implicit App includes multiple component declarations', async

await setComponentEditorSource(
page,
[
'const OtherButton = () => <button type="button">bar</button>',
'const Button = () => <button type="button">foo</button>',
].join('\n'),
'const App = () => <button type="button">explicit app dom</button>',
)

await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
await expect(getPreviewFrame(page).getByRole('button')).toHaveCount(2)
await expect(getPreviewFrame(page).getByRole('button')).toContainText(['bar', 'foo'])
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
'explicit app dom',
)

await page.getByRole('combobox', { name: 'Render mode' }).selectOption('react')
await expect(page.getByRole('combobox', { name: 'Render mode' })).toHaveValue('react')
await setComponentEditorSource(
page,
'const App = () => <button type="button">explicit app react</button>',
)

await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
'explicit app react',
)
})

test('auto render does not treat lowercase helpers as implicit components', async ({
Expand All @@ -651,7 +661,7 @@ test('auto render does not treat lowercase helpers as implicit components', asyn
)
})

test('auto render wraps standalone JSX with trailing semicolon and comment', async ({
test('auto render shows App-only error for standalone JSX expression', async ({
page,
}) => {
await waitForInitialRender(page)
Expand All @@ -663,13 +673,13 @@ test('auto render wraps standalone JSX with trailing semicolon and comment', asy
'(<button type="button">implicit app from jsx expression</button>) as any; // trailing',
)

await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
'implicit app from jsx expression',
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
await expect(page.locator('#preview-host pre')).toContainText(
'Expected a function or const named App.',
)
})

test('auto render requires explicit App for declarations plus top-level JSX expression', async ({
test('auto render shows App-only error for declarations plus top-level JSX expression', async ({
page,
}) => {
await waitForInitialRender(page)
Expand All @@ -687,7 +697,7 @@ test('auto render requires explicit App for declarations plus top-level JSX expr

await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
await expect(page.locator('#preview-host pre')).toContainText(
'Top-level JSX with declarations or imports requires an explicit App component.',
'Expected a function or const named App.',
)
})

Expand Down
1 change: 1 addition & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ <h1>
aria-controls="workspaces-drawer"
title="Manage local workspaces"
disabled
hidden
>
<svg class="workspaces-toggle__icon" viewBox="0 0 24 24" aria-hidden="true">
<path
Expand Down
1 change: 0 additions & 1 deletion src/modules/app-core/app-composition-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ const createRuntimeCoreOptions = ({
cdnImports,
importFromCdnWithFallback,
renderMode,
isAutoRenderEnabled: () => autoRenderToggle.checked,
getJsxSource,
getWorkspaceTabs: () => buildWorkspaceTabsSnapshot(),
getPreviewHost,
Expand Down
28 changes: 21 additions & 7 deletions src/modules/app-core/github-pr-context-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,25 +111,35 @@ export const createGitHubPrContextUiController = ({
workspacesToggle.disabled = false
}

aiChatToggle?.removeAttribute('hidden')
if (aiChatToggle instanceof HTMLElement) {
aiChatToggle.hidden = false
}

githubPrToggle?.removeAttribute('hidden')
if (githubPrToggle instanceof HTMLElement) {
githubPrToggle.hidden = false
}
if (!contextState.activePrContext) {
workspacesToggle?.removeAttribute('hidden')
if (workspacesToggle instanceof HTMLElement) {
workspacesToggle.hidden = false
}
}

if (contextState.activePrContext) {
githubPrContextClose?.removeAttribute('hidden')
githubPrContextDisconnect?.removeAttribute('hidden')
workspacesToggle?.setAttribute('hidden', '')
if (workspacesToggle instanceof HTMLElement) {
workspacesToggle.hidden = true
}
} else {
githubPrContextClose?.setAttribute('hidden', '')
githubPrContextDisconnect?.setAttribute('hidden', '')
}
return
}

aiChatToggle?.setAttribute('hidden', '')
if (aiChatToggle instanceof HTMLElement) {
aiChatToggle.hidden = true
}
aiChatToggle?.setAttribute('aria-expanded', 'false')
if (workspacesToggle instanceof HTMLButtonElement) {
workspacesToggle.disabled = true
Expand All @@ -139,9 +149,13 @@ export const createGitHubPrContextUiController = ({
contextState.hasSyncedActivePrEditorContent = false
syncEditorPrContextIndicators(false)
setGitHubPrToggleVisual('open-pr')
githubPrToggle?.setAttribute('hidden', '')
if (githubPrToggle instanceof HTMLElement) {
githubPrToggle.hidden = true
}
githubPrToggle?.setAttribute('aria-expanded', 'false')
workspacesToggle?.setAttribute('hidden', '')
if (workspacesToggle instanceof HTMLElement) {
workspacesToggle.hidden = true
}
workspacesToggle?.setAttribute('aria-expanded', 'false')
githubPrContextClose?.setAttribute('hidden', '')
githubPrContextDisconnect?.setAttribute('hidden', '')
Expand Down
3 changes: 0 additions & 3 deletions src/modules/app-core/github-workflows.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,6 @@ const initializeGitHubWorkflows = ({
onActivePrContextChange: activeContext => {
prContextUi.setActivePrContext(activeContext)
prContextUi.syncAiChatTokenVisibility(getTokenForVisibility())
if (workspacesToggle instanceof HTMLButtonElement) {
workspacesToggle.hidden = Boolean(activeContext)
}

if (activeContext) {
closeWorkspacesDrawer()
Expand Down
4 changes: 0 additions & 4 deletions src/modules/app-core/workspace-controllers-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@ const createWorkspaceControllersSetup = ({
const setActiveWorkspaceTab = tabId =>
workspaceTabSelectionController.setActiveWorkspaceTab(tabId)

const syncEditorFromActiveWorkspaceTabDelegate = () =>
workspaceTabSelectionController.syncEditorFromActiveWorkspaceTab()

workspaceTabMutationsController = createWorkspaceTabMutationsController({
Comment thread
knightedcodemonkey marked this conversation as resolved.
toNonEmptyWorkspaceText,
workspaceTabsState,
Expand Down Expand Up @@ -186,7 +183,6 @@ const createWorkspaceControllersSetup = ({
getShouldShowEditedDesign,
workspaceTabsShell,
workspaceTabAddWrap,
syncEditorFromActiveWorkspaceTab: syncEditorFromActiveWorkspaceTabDelegate,
})

const workspaceContextController = createWorkspaceContextController({
Expand Down
10 changes: 0 additions & 10 deletions src/modules/app-core/workspace-tab-selection-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,8 @@ const createWorkspaceTabSelectionController = ({
})
}

const syncEditorFromActiveWorkspaceTab = () => {
const activeTab = getActiveWorkspaceTab()
if (!activeTab) {
return
}

loadWorkspaceTabIntoEditor(activeTab)
}

return {
setActiveWorkspaceTab,
syncEditorFromActiveWorkspaceTab,
}
}

Expand Down
3 changes: 0 additions & 3 deletions src/modules/app-core/workspace-tabs-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const createWorkspaceTabsRenderer = ({
getShouldShowEditedDesign,
workspaceTabsShell,
workspaceTabAddWrap,
syncEditorFromActiveWorkspaceTab,
}) => {
const clearWorkspaceTabDragState = () => {
setDraggedWorkspaceTabId('')
Expand Down Expand Up @@ -294,8 +293,6 @@ const createWorkspaceTabsRenderer = ({
renderWorkspaceTabs()
return
}

syncEditorFromActiveWorkspaceTab()
}

return {
Expand Down
Loading
Loading