Files
orca/tests/e2e/add-project-default-checkout.spec.ts
T
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00

192 lines
7.5 KiB
TypeScript

import { execFileSync } from 'node:child_process'
import { mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import { mkdtemp } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { test, expect } from './helpers/orca-app'
import { waitForSessionReady } from './helpers/store'
const tempRoots: string[] = []
async function createCloneFixture(): Promise<{
sourcePath: string
destinationParent: string
}> {
// Why: realpathSync so the path the test asserts on matches the store's
// repo.path on macOS, where os.tmpdir() (/var/...) symlinks to /private/var/...
// and the app canonicalizes repo.path via `git rev-parse --show-toplevel`.
const rootPath = realpathSync(
await mkdtemp(path.join(os.tmpdir(), 'orca-e2e-add-project-clone-'))
)
tempRoots.push(rootPath)
const sourcePath = path.join(rootPath, 'default-checkout-source')
const destinationParent = path.join(rootPath, 'clones')
mkdirSync(sourcePath, { recursive: true })
mkdirSync(destinationParent, { recursive: true })
execFileSync('git', ['init'], { cwd: sourcePath, stdio: 'pipe' })
execFileSync('git', ['config', 'user.email', 'e2e@test.local'], {
cwd: sourcePath,
stdio: 'pipe'
})
execFileSync('git', ['config', 'user.name', 'E2E Test'], { cwd: sourcePath, stdio: 'pipe' })
writeFileSync(path.join(sourcePath, 'README.md'), '# Default checkout source\n')
execFileSync('git', ['add', 'README.md'], { cwd: sourcePath, stdio: 'pipe' })
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: sourcePath, stdio: 'pipe' })
execFileSync('git', ['branch', '-M', 'main'], { cwd: sourcePath, stdio: 'pipe' })
return { sourcePath, destinationParent }
}
async function createLinkedWorktreeFixture(): Promise<{
mainPath: string
siblingPath: string
}> {
// Why: realpathSync so mainPath matches the store's repo.path on macOS, where
// os.tmpdir() (/var/...) symlinks to /private/var/... and the app canonicalizes
// repo.path via `git rev-parse --show-toplevel` on add.
const rootPath = realpathSync(
await mkdtemp(path.join(os.tmpdir(), 'orca-e2e-add-project-linked-'))
)
tempRoots.push(rootPath)
const mainPath = path.join(rootPath, 'linked-source')
const siblingPath = path.join(rootPath, 'linked-feature')
mkdirSync(mainPath, { recursive: true })
execFileSync('git', ['init'], { cwd: mainPath, stdio: 'pipe' })
execFileSync('git', ['config', 'user.email', 'e2e@test.local'], {
cwd: mainPath,
stdio: 'pipe'
})
execFileSync('git', ['config', 'user.name', 'E2E Test'], { cwd: mainPath, stdio: 'pipe' })
writeFileSync(path.join(mainPath, 'README.md'), '# Linked source\n')
execFileSync('git', ['add', 'README.md'], { cwd: mainPath, stdio: 'pipe' })
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: mainPath, stdio: 'pipe' })
execFileSync('git', ['branch', '-M', 'main'], { cwd: mainPath, stdio: 'pipe' })
execFileSync('git', ['worktree', 'add', '-b', 'feature', siblingPath], {
cwd: mainPath,
stdio: 'pipe'
})
return { mainPath, siblingPath }
}
test.afterEach(() => {
for (const root of tempRoots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
})
test.describe('Add project default checkout', () => {
test('clones a repo and opens the default checkout without the setup-choice modal', async ({
orcaPage
}) => {
await waitForSessionReady(orcaPage)
const fixture = await createCloneFixture()
await orcaPage
.getByRole('button', { name: /Add Project/i })
.first()
.click()
const addDialog = orcaPage.getByRole('dialog', { name: /Add a project/i })
await expect(addDialog).toBeVisible()
await addDialog.getByRole('button', { name: /Clone from URL/i }).click()
const cloneDialog = orcaPage.getByRole('dialog', { name: /Clone from URL/i })
await expect(cloneDialog).toBeVisible()
await cloneDialog.getByPlaceholder('https://github.com/user/repo.git').fill(fixture.sourcePath)
await cloneDialog.getByPlaceholder('/path/to/destination').fill(fixture.destinationParent)
await cloneDialog.getByRole('button', { name: /^Clone$/ }).click()
await expect(orcaPage.getByRole('dialog', { name: /Repo added/i })).toBeHidden()
await expect(orcaPage.getByText('Use existing worktrees')).toBeHidden()
await expect(orcaPage.getByText('Create a new worktree')).toBeHidden()
await expect
.poll(
() =>
orcaPage.evaluate((cloneName) => {
const state = window.__store?.getState()
if (!state) {
return null
}
const repo = state.repos.find((candidate) => candidate.displayName === cloneName)
if (!repo) {
return null
}
const worktrees = state.worktreesByRepo[repo.id] ?? []
const defaultCheckout = worktrees.find((worktree) => worktree.isMainWorktree)
const normalizedDefaultCheckoutPath = defaultCheckout?.path.replace(/\\/g, '/') ?? null
return {
activeCheckoutIsDefault: state.activeWorktreeId === defaultCheckout?.id,
defaultCheckoutLooksCloned:
normalizedDefaultCheckoutPath?.endsWith(`/clones/${cloneName}`) ?? false,
oldSetupModalOpen: state.activeModal === 'project-added'
}
}, path.basename(fixture.sourcePath)),
{
timeout: 30_000,
message: 'cloned repo default checkout was not opened'
}
)
.toEqual({
activeCheckoutIsDefault: true,
defaultCheckoutLooksCloned: true,
oldSetupModalOpen: false
})
})
test('reveals sibling git worktrees before opening the default checkout', async ({
orcaPage
}) => {
await waitForSessionReady(orcaPage)
const fixture = await createLinkedWorktreeFixture()
await orcaPage.evaluate((folderPath) => {
window.__store?.getState().openModal('confirm-add-project-from-folder', { folderPath })
}, fixture.mainPath)
const addProjectDialog = orcaPage.getByRole('dialog', { name: /^Add Project$/i })
await expect(addProjectDialog).toBeVisible()
await addProjectDialog.getByRole('button', { name: /^Add Project$/ }).click()
await expect(addProjectDialog).toBeHidden()
await expect(orcaPage.getByRole('dialog', { name: /Repo added/i })).toBeHidden()
await expect(orcaPage.getByText('Use existing worktrees')).toBeHidden()
await expect
.poll(
() =>
orcaPage.evaluate((mainPath) => {
const state = window.__store?.getState()
if (!state) {
return null
}
const repo = state.repos.find((candidate) => candidate.path === mainPath)
if (!repo) {
return null
}
const worktrees = state.worktreesByRepo[repo.id] ?? []
const defaultCheckout = worktrees.find((worktree) => worktree.isMainWorktree)
return {
activeCheckoutIsDefault: state.activeWorktreeId === defaultCheckout?.id,
linkedRepoVisibility: repo.externalWorktreeVisibility,
visibleBranches: worktrees.map((worktree) => worktree.branch).sort(),
visibleCount: worktrees.length
}
}, fixture.mainPath),
{
timeout: 30_000,
message: 'linked worktrees were not revealed before opening the default checkout'
}
)
.toEqual({
activeCheckoutIsDefault: true,
linkedRepoVisibility: 'show',
visibleBranches: ['refs/heads/feature', 'refs/heads/main'],
visibleCount: 2
})
})
})