Files
2026-07-13 13:05:33 +08:00

195 lines
7.2 KiB
TypeScript

/**
* E2E tests for opening files and markdown preview from the right sidebar.
*
* User Prompt:
* - you can open files (from the right sidebar)
* - you can open .md files and they show up as preview (from the right sidebar)
*/
import { test, expect } from './helpers/orca-app'
import {
waitForSessionReady,
waitForActiveWorktree,
getActiveWorktreeId,
getActiveTabType,
getOpenFiles,
ensureTerminalVisible
} from './helpers/store'
import { clickFileInExplorer, openFileExplorer } from './helpers/file-explorer'
async function switchToTerminal(
page: Parameters<typeof getActiveWorktreeId>[0],
worktreeId: string
): Promise<void> {
await page.evaluate((targetWorktreeId) => {
const store = window.__store
if (!store) {
return
}
const state = store.getState()
const terminalTab = (state.tabsByWorktree[targetWorktreeId] ?? [])[0]
if (terminalTab) {
state.setActiveTab(terminalTab.id)
}
state.setActiveTabType('terminal')
}, worktreeId)
}
async function switchToEditor(
page: Parameters<typeof getActiveWorktreeId>[0],
fileId: string
): Promise<void> {
await page.evaluate((targetFileId) => {
const store = window.__store
if (!store) {
return
}
const state = store.getState()
if (state.openFiles.some((file) => file.id === targetFileId)) {
state.setActiveFile(targetFileId)
state.setActiveTabType('editor')
}
}, fileId)
}
test.describe('File Open & Markdown Preview', () => {
test.beforeEach(async ({ orcaPage }) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
})
/**
* User Prompt:
* - you can open files (from the right sidebar)
*/
test('opening the right sidebar shows file explorer', async ({ orcaPage }) => {
await openFileExplorer(orcaPage)
// Why: the load-bearing check is that `FileExplorer` actually mounted.
// `data-orca-explorer-shell` is the stable marker the component renders
// on its root shell div — a store-only `rightSidebarTab === 'explorer'`
// check would pass even if the explorer crashed on mount and the panel
// painted empty.
await expect(orcaPage.locator('[data-orca-explorer-shell]')).toBeVisible({ timeout: 5_000 })
})
/**
* User Prompt:
* - you can open files (from the right sidebar)
*/
test('clicking a file in the file explorer opens it in an editor tab', async ({ orcaPage }) => {
const worktreeId = (await getActiveWorktreeId(orcaPage))!
await openFileExplorer(orcaPage)
const filesBefore = await getOpenFiles(orcaPage, worktreeId)
// Click a known non-directory file
const clickedFile = await clickFileInExplorer(orcaPage, [
'package.json',
'tsconfig.json',
'.gitignore',
'README.md'
])
expect(clickedFile).not.toBeNull()
// Wait for the file to be opened in the editor
await expect.poll(async () => getActiveTabType(orcaPage), { timeout: 5_000 }).toBe('editor')
// There should be a new open file
await expect
.poll(async () => (await getOpenFiles(orcaPage, worktreeId)).length, { timeout: 5_000 })
.toBeGreaterThan(filesBefore.length)
// Why: the load-bearing check is that the editor panel actually rendered
// the opened file. `.editor-header-path` is emitted by EditorPanel's
// header row and contains the file's full absolute path (which ends in
// the clicked file's name); a store-only `activeTabType === 'editor'`
// check would pass even if EditorPanel crashed on mount and the surface
// is blank. Timeout is generous (20s) because EditorPanel is lazy-loaded
// the first time the editor opens in a session — headless Electron runs
// routinely take 10s+ to hydrate that chunk plus the inner Monaco/Rich
// Markdown chunks, during which the outer Suspense shows "Loading
// editor…" and `.editor-header-path` is not yet in the DOM.
await expect(orcaPage.locator('.editor-header-path').first()).toContainText(clickedFile!, {
timeout: 20_000
})
})
/**
* User Prompt:
* - you can open .md files and they show up as preview (from the right sidebar)
*/
test('opening a .md file shows markdown content', async ({ orcaPage }) => {
await openFileExplorer(orcaPage)
const clickedFile = await clickFileInExplorer(orcaPage, ['README.md', 'CLAUDE.md'])
expect(clickedFile).not.toBeNull()
// Wait for the editor tab to become active
await expect.poll(async () => getActiveTabType(orcaPage), { timeout: 5_000 }).toBe('editor')
// The seeded README.md starts with `# Orca E2E Test Repo`, so the rich
// markdown editor should render a real <h1> with that text. Asserting on
// the rendered heading (not `markdownViewMode` in the store) is the whole
// point of this spec — a store-only check passes even if
// RichMarkdownEditor failed to mount and the editor surface is blank.
// Fall back to CLAUDE.md's first heading when that file was opened
// instead: the seeded `CLAUDE.md` starts with `# CLAUDE.md`.
const expectedHeading = clickedFile?.endsWith('README.md')
? /Orca E2E Test Repo/i
: /CLAUDE\.md/i
// Why 25s: first-time markdown open in a headless Electron session waits
// on two lazy chunks (EditorPanel → RichMarkdownEditor) plus ProseMirror
// boot + file read. Real-run traces show the heading reliably paints
// within ~10-15s but with enough variance that a 15s bound flakes. The
// user-facing guarantee is just "the rich markdown surface eventually
// paints the file's first heading" — giving it 25s keeps the assertion
// meaningful without turning every run into a flake risk.
await expect(orcaPage.getByRole('heading', { name: expectedHeading, level: 1 })).toBeVisible({
timeout: 25_000
})
})
/**
* User Prompt:
* - you can open files (from the right sidebar)
* - files retain state when switching tabs
*/
test('editor tab retains state when switching to terminal and back', async ({ orcaPage }) => {
const worktreeId = (await getActiveWorktreeId(orcaPage))!
await openFileExplorer(orcaPage)
// Click a file to open it
const clickedFile = await clickFileInExplorer(orcaPage, [
'package.json',
'tsconfig.json',
'.gitignore'
])
expect(clickedFile).not.toBeNull()
// Wait for editor to become active
await expect.poll(async () => getActiveTabType(orcaPage), { timeout: 5_000 }).toBe('editor')
// Record what files are open
const openFilesBefore = await getOpenFiles(orcaPage, worktreeId)
expect(openFilesBefore.length).toBeGreaterThan(0)
const editorFileId = openFilesBefore[0].id
// Switch to a terminal tab
await switchToTerminal(orcaPage, worktreeId)
await expect.poll(async () => getActiveTabType(orcaPage), { timeout: 3_000 }).not.toBe('editor')
// Switch back to the same editor tab
await switchToEditor(orcaPage, editorFileId)
await expect.poll(async () => getActiveTabType(orcaPage), { timeout: 3_000 }).toBe('editor')
// The same files should still be open
const openFilesAfter = await getOpenFiles(orcaPage, worktreeId)
expect(openFilesAfter.length).toBe(openFilesBefore.length)
expect(openFilesAfter[0].filePath).toBe(openFilesBefore[0].filePath)
})
})