mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
test: remove 47 low-value cases and 13 suites (#34136)
* test: remove 37 low-value cases and 3 suites * test: delete ten more renderer wiring and copy suites * test: drop deleted suite from its contract entry point --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -7371,7 +7371,6 @@
|
||||
"src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts": 1675,
|
||||
"src/renderer/src/components/dashboard-popout/useDashboardSnapshot.test.tsx": 432,
|
||||
"src/renderer/src/components/dashboard/AgentDashboardDrawer.test.tsx": 1745,
|
||||
"src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.test.tsx": 479,
|
||||
"src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.test.tsx": 1607,
|
||||
"src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx": 1601,
|
||||
"src/renderer/src/components/dashboard/agent-dashboard-open-mode.test.ts": 43,
|
||||
@@ -8755,7 +8754,6 @@
|
||||
"src/renderer/src/components/settings/FloatingWorkspacePane.test.tsx": 1311,
|
||||
"src/renderer/src/components/settings/GeneralPane.section-lifetime.test.tsx": 1192,
|
||||
"src/renderer/src/components/settings/GeneralPane.test.ts": 1066,
|
||||
"src/renderer/src/components/settings/GeneralRemoteServerUpdates.test.tsx": 283,
|
||||
"src/renderer/src/components/settings/GeneralUpdateSettingsSection.test.tsx": 1749,
|
||||
"src/renderer/src/components/settings/GeneralWorkspaceSettingsSection.test.tsx": 2551,
|
||||
"src/renderer/src/components/settings/GhosttyImportModal.test.ts": 162,
|
||||
@@ -8844,7 +8842,6 @@
|
||||
"src/renderer/src/components/settings/TerminalSettingsPreview.lifecycle.test.tsx": 318,
|
||||
"src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx": 282,
|
||||
"src/renderer/src/components/settings/TerminalThemeSections.lifecycle.test.ts": 1337,
|
||||
"src/renderer/src/components/settings/VoiceDictationSettingsSection.test.tsx": 491,
|
||||
"src/renderer/src/components/settings/VoiceMicrophoneSetting.test.tsx": 577,
|
||||
"src/renderer/src/components/settings/VoicePane.test.tsx": 770,
|
||||
"src/renderer/src/components/settings/VoiceSpeechModelSection.test.tsx": 647,
|
||||
@@ -8898,7 +8895,6 @@
|
||||
"src/renderer/src/components/settings/repository/execution/RepositoryHostSetupsSection.test.tsx": 1674,
|
||||
"src/renderer/src/components/settings/repository/execution/RepositoryHostSetupsSection.workspace-window.test.tsx": 1584,
|
||||
"src/renderer/src/components/settings/repository/execution/RepositoryWorktreeDefaultsSection.test.tsx": 577,
|
||||
"src/renderer/src/components/settings/repository/execution/repository-description-templates.test.tsx": 592,
|
||||
"src/renderer/src/components/settings/repository/execution/repository-hook-settings-draft.test.ts": 368,
|
||||
"src/renderer/src/components/settings/repository/execution/repository-host-setup-options.test.ts": 151,
|
||||
"src/renderer/src/components/settings/repository/identity/RepositoryForkSyncSection.test.tsx": 530,
|
||||
@@ -9180,7 +9176,6 @@
|
||||
"src/renderer/src/components/sidebar/use-workspace-reveal-body-redirect.test.tsx": 142,
|
||||
"src/renderer/src/components/sidebar/use-workspace-status-drop.test.ts": 158,
|
||||
"src/renderer/src/components/sidebar/use-worktree-activity-status.test.tsx": 133,
|
||||
"src/renderer/src/components/sidebar/use-worktree-activity-statuses.test.ts": 123,
|
||||
"src/renderer/src/components/sidebar/use-worktree-agent-rows-subscriptions.test.tsx": 1600,
|
||||
"src/renderer/src/components/sidebar/use-worktree-card-secondary-details.store-subscriptions.test.tsx": 1528,
|
||||
"src/renderer/src/components/sidebar/use-worktree-sleep-state.test.tsx": 285,
|
||||
@@ -9458,7 +9453,6 @@
|
||||
"src/renderer/src/components/status-bar/workspace-space-layout.test.ts": 21,
|
||||
"src/renderer/src/components/status-bar/workspace-space-open-change.test.ts": 123,
|
||||
"src/renderer/src/components/status-bar/workspace-space-presentation.test.ts": 522,
|
||||
"src/renderer/src/components/status-bar/workspace-space-treemap.test.tsx": 389,
|
||||
"src/renderer/src/components/status-bar/workspace-space-visit-activity.test.ts": 86,
|
||||
"src/renderer/src/components/status-bar/workspace-space-worktree-row.test.tsx": 2100,
|
||||
"src/renderer/src/components/tab-bar/BrowserTab.test.tsx": 1689,
|
||||
@@ -9958,7 +9952,6 @@
|
||||
"src/renderer/src/components/terminal-pane/terminal-copy-shortcut.test.ts": 1636,
|
||||
"src/renderer/src/components/terminal-pane/terminal-ctrl-enter.test.ts": 115,
|
||||
"src/renderer/src/components/terminal-pane/terminal-cursor-appearance-precedence.test.ts": 303,
|
||||
"src/renderer/src/components/terminal-pane/terminal-cursor-inactive-style.test.ts": 388,
|
||||
"src/renderer/src/components/terminal-pane/terminal-dead-session-reconcile.test.ts": 34,
|
||||
"src/renderer/src/components/terminal-pane/terminal-delivery-watchdog.test.ts": 140,
|
||||
"src/renderer/src/components/terminal-pane/terminal-drop-handler.test.ts": 519,
|
||||
@@ -10185,7 +10178,6 @@
|
||||
"src/renderer/src/components/terminal-pane/use-mobile-overlay-ticks-desktop-fit.test.tsx": 229,
|
||||
"src/renderer/src/components/terminal-pane/use-mobile-overlay-ticks.perf.test.tsx": 237,
|
||||
"src/renderer/src/components/terminal-pane/use-notification-dispatch-hidden-workspace.test.ts": 453,
|
||||
"src/renderer/src/components/terminal-pane/use-notification-dispatch-verdict.test.ts": 424,
|
||||
"src/renderer/src/components/terminal-pane/use-notification-dispatch.test.ts": 432,
|
||||
"src/renderer/src/components/terminal-pane/use-parked-terminal-watcher-synchronization.react185.test.tsx": 298,
|
||||
"src/renderer/src/components/terminal-pane/use-system-prefers-dark.test.ts": 33,
|
||||
@@ -10311,7 +10303,6 @@
|
||||
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-settlement.test.ts": 106,
|
||||
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-timeout-recovery.test.ts": 1279,
|
||||
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-sort-header.test.tsx": 598,
|
||||
"src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.test.tsx": 332,
|
||||
"src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.test.tsx": 236,
|
||||
"src/renderer/src/components/workspace-space/SpaceWindowStatePublisher.test.tsx": 618,
|
||||
"src/renderer/src/components/workspace-space/space-window-state.test.ts": 162,
|
||||
@@ -10529,7 +10520,6 @@
|
||||
"src/renderer/src/hooks/worktree-head-identity-apply.test.ts": 50,
|
||||
"src/renderer/src/hooks/zoom-routing.test.ts": 594,
|
||||
"src/renderer/src/i18n/I18nProvider.test.tsx": 1691,
|
||||
"src/renderer/src/i18n/catalog-nested-settings.test.ts": 777,
|
||||
"src/renderer/src/i18n/intl-locale.test.ts": 135,
|
||||
"src/renderer/src/i18n/language-catalog-timeout.test.tsx": 1932,
|
||||
"src/renderer/src/i18n/language-detection-reconciliation.test.tsx": 1619,
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { HandlerContext } from '../dispatch'
|
||||
import { RuntimeClientError, type RuntimeClient } from '../runtime-client'
|
||||
import { CONNECTOR_REPLY_UNAVAILABLE } from '../../shared/rpc-contract/connector-params'
|
||||
import { CONNECTOR_HANDLERS, formatReply } from './connector'
|
||||
|
||||
const call = vi.fn()
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the reply handler calls only client.call.
|
||||
const client = { call } as unknown as RuntimeClient
|
||||
|
||||
function context(): HandlerContext {
|
||||
return { client, cwd: process.cwd(), flags: new Map([['text', 'done']]), json: true, rawArgs: [] }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('ORCA_PANE_KEY', 'tab:1')
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
call.mockReset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('orca connector reply', () => {
|
||||
it('sends the calling pane and the text, and says where it went', async () => {
|
||||
call.mockResolvedValue({ result: { status: 'posted', messageId: '1793.1' } })
|
||||
await CONNECTOR_HANDLERS['connector reply'](context())
|
||||
expect(call).toHaveBeenCalledWith(
|
||||
'connector.reply',
|
||||
{ paneKey: 'tab:1', replyId: expect.any(String), text: 'done' },
|
||||
{ timeoutMs: 180_000 }
|
||||
)
|
||||
expect(formatReply({ status: 'posted' })).toBe('Posted to the thread.')
|
||||
expect(formatReply({ status: 'queued' })).toBe('Sent; Orca Cloud will post it to the thread.')
|
||||
})
|
||||
|
||||
it('takes the pane from the scrub-safe alias when Claude scrubbed ORCA_PANE_KEY', async () => {
|
||||
vi.stubEnv('ORCA_PANE_KEY', '')
|
||||
vi.stubEnv('ORCA_AGENT_PANE', 'tab:2')
|
||||
call.mockResolvedValue({ result: { status: 'queued' } })
|
||||
await CONNECTOR_HANDLERS['connector reply'](context())
|
||||
expect(call.mock.calls[0]?.[1]).toMatchObject({ paneKey: 'tab:2' })
|
||||
})
|
||||
|
||||
it.each([
|
||||
'not_a_cloud_conversation',
|
||||
'connector_not_connected',
|
||||
'connectors_disabled',
|
||||
'method_not_found'
|
||||
])('says replies are not available from this host when the host answers %s', async (code) => {
|
||||
call.mockRejectedValue(new RuntimeClientError(code, code))
|
||||
await expect(CONNECTOR_HANDLERS['connector reply'](context())).rejects.toMatchObject({
|
||||
code: 'not_a_cloud_conversation',
|
||||
message: CONNECTOR_REPLY_UNAVAILABLE
|
||||
})
|
||||
})
|
||||
|
||||
it('passes a refusal through, and refuses outside an agent pane', async () => {
|
||||
call.mockRejectedValue(new RuntimeClientError('no_current_turn', 'no_current_turn'))
|
||||
await expect(CONNECTOR_HANDLERS['connector reply'](context())).rejects.toMatchObject({
|
||||
code: 'no_current_turn'
|
||||
})
|
||||
expect(call).toHaveBeenCalledTimes(1)
|
||||
|
||||
vi.stubEnv('ORCA_PANE_KEY', '')
|
||||
vi.stubEnv('ORCA_AGENT_PANE', '')
|
||||
await expect(CONNECTOR_HANDLERS['connector reply'](context())).rejects.toMatchObject({
|
||||
code: 'invalid_argument'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { findAppAgentBrowserDriver, stageAppAgentBrowserDriver } from './serve-agent-browser-driver'
|
||||
import { stageAppAgentBrowserDriver } from './serve-agent-browser-driver'
|
||||
|
||||
const roots: string[] = []
|
||||
afterEach(() => {
|
||||
@@ -12,23 +12,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe("finding the app's agent-browser driver (#33846)", () => {
|
||||
it.each([
|
||||
['linux', 'x64', 'agent-browser-linux-x64'],
|
||||
['darwin', 'arm64', 'agent-browser-darwin-arm64'],
|
||||
['win32', 'x64', 'agent-browser-win32-x64.exe']
|
||||
] as const)('finds the copy a packaged %s app ships in resources', (platform, arch, name) => {
|
||||
const resources = mkdtempSync(join(tmpdir(), 'orca-serve-agent-browser-'))
|
||||
roots.push(resources)
|
||||
mkdirSync(join(resources, 'app.asar.unpacked'))
|
||||
writeFileSync(join(resources, name), '')
|
||||
expect(findAppAgentBrowserDriver(join(resources, 'app.asar.unpacked'), platform, arch)).toBe(
|
||||
join(resources, name)
|
||||
)
|
||||
expect(findAppAgentBrowserDriver(join(resources, 'app.asar.unpacked'), platform, 'ia32')).toBe(
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
// A running daemon locks its image on Windows; lending the installed copy would block app updates.
|
||||
it('hands Windows orcad a profile copy outside the app install', async () => {
|
||||
const resources = mkdtempSync(join(tmpdir(), 'orca-serve-agent-browser-'))
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { act, type ReactNode } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const updateSettings = vi.fn()
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (
|
||||
selector: (state: {
|
||||
settings: {
|
||||
experimentalAgentDashboardMode: 'in-window'
|
||||
experimentalAgentDashboardShowIdle: boolean
|
||||
}
|
||||
updateSettings: typeof updateSettings
|
||||
}) => unknown
|
||||
) =>
|
||||
selector({
|
||||
settings: {
|
||||
experimentalAgentDashboardMode: 'in-window',
|
||||
experimentalAgentDashboardShowIdle: false
|
||||
},
|
||||
updateSettings
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
DropdownMenu: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
DropdownMenuContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
DropdownMenuSeparator: () => <hr />,
|
||||
DropdownMenuTrigger: ({ children }: { children: ReactNode }) => <>{children}</>
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/tooltip', () => ({
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
TooltipContent: ({ children }: { children: ReactNode }) => <span>{children}</span>,
|
||||
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>
|
||||
}))
|
||||
|
||||
import { AgentDashboardSettingsMenu } from './AgentDashboardSettingsMenu'
|
||||
|
||||
let root: Root | null = null
|
||||
let container: HTMLDivElement | null = null
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root?.unmount())
|
||||
root = null
|
||||
container?.remove()
|
||||
container = null
|
||||
updateSettings.mockReset()
|
||||
})
|
||||
|
||||
describe('AgentDashboardSettingsMenu', () => {
|
||||
it('owns the idle-agent visibility setting', () => {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
act(() => {
|
||||
root?.render(<AgentDashboardSettingsMenu onSwitchToPopout={vi.fn()} onOpenChange={vi.fn()} />)
|
||||
})
|
||||
|
||||
const toggle = container.querySelector<HTMLButtonElement>(
|
||||
'button[role="switch"][aria-label="Show idle agents"]'
|
||||
)
|
||||
expect(toggle).not.toBeNull()
|
||||
|
||||
act(() => toggle?.click())
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith({ experimentalAgentDashboardShowIdle: true })
|
||||
})
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { GeneralRemoteServerUpdates } from './GeneralRemoteServerUpdates'
|
||||
|
||||
const storeMock = vi.hoisted(() => ({
|
||||
state: {
|
||||
settingsSearchQuery: '',
|
||||
remoteServerUpdates: new Map([
|
||||
[
|
||||
'server-a',
|
||||
{
|
||||
environmentId: 'server-a',
|
||||
name: 'Test server A',
|
||||
phase: 'current'
|
||||
}
|
||||
]
|
||||
]),
|
||||
remoteServerUpdatesChecking: false,
|
||||
remoteServerUpdatesRunning: false,
|
||||
refreshRemoteServerUpdates: vi.fn(),
|
||||
setRemoteServerUpdateDialogOpen: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: typeof storeMock.state) => unknown) => selector(storeMock.state)
|
||||
}))
|
||||
|
||||
describe('GeneralRemoteServerUpdates', () => {
|
||||
beforeEach(() => {
|
||||
storeMock.state.refreshRemoteServerUpdates.mockReset()
|
||||
storeMock.state.setRemoteServerUpdateDialogOpen.mockReset()
|
||||
})
|
||||
|
||||
it('matches the local update check action and forwards modifier options', async () => {
|
||||
const container = document.createElement('div')
|
||||
const root = createRoot(container)
|
||||
await act(async () => root.render(<GeneralRemoteServerUpdates />))
|
||||
storeMock.state.refreshRemoteServerUpdates.mockClear()
|
||||
|
||||
const button = container.querySelector('button')
|
||||
expect(button?.textContent).toContain('Check for Server Updates')
|
||||
expect(button?.querySelector('svg.lucide-refresh-cw')).not.toBeNull()
|
||||
expect(button?.querySelector('svg.lucide-download')).toBeNull()
|
||||
expect(container.textContent).toContain('1 paired server · 1 up to date')
|
||||
|
||||
await act(async () => {
|
||||
button?.dispatchEvent(new MouseEvent('click', { bubbles: true, shiftKey: true }))
|
||||
})
|
||||
|
||||
expect(storeMock.state.setRemoteServerUpdateDialogOpen).toHaveBeenCalledWith(true)
|
||||
expect(storeMock.state.refreshRemoteServerUpdates).toHaveBeenCalledWith({
|
||||
includePrerelease: true,
|
||||
includePerfPrerelease: false
|
||||
})
|
||||
await act(async () => root.unmount())
|
||||
})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getDefaultVoiceSettings } from '../../../../shared/constants'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { VoiceDictationSettingsSection } from './VoiceDictationSettingsSection'
|
||||
|
||||
const { shortcutLabel } = vi.hoisted(() => ({ shortcutLabel: vi.fn() }))
|
||||
vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutLabel: shortcutLabel }))
|
||||
vi.mock('./VoiceMicrophoneSetting', () => ({ VoiceMicrophoneSetting: () => null }))
|
||||
|
||||
afterEach(async () => {
|
||||
await i18n.changeLanguage('en')
|
||||
})
|
||||
|
||||
describe('dictation shortcut explanations', () => {
|
||||
it.each([
|
||||
['en', '⌘E'],
|
||||
['en', 'Ctrl+E'],
|
||||
['ko', '⌘E'],
|
||||
['ko', 'Ctrl+E']
|
||||
])('renders complete %s sentences with the platform shortcut %s', async (language, shortcut) => {
|
||||
await i18n.changeLanguage(language)
|
||||
shortcutLabel.mockReturnValue(shortcut)
|
||||
const container = document.createElement('div')
|
||||
const root = createRoot(container)
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<VoiceDictationSettingsSection
|
||||
voiceSettings={getDefaultVoiceSettings()}
|
||||
permissionPending={false}
|
||||
onToggleVoiceDictation={vi.fn()}
|
||||
onUpdateVoiceSettings={vi.fn()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
try {
|
||||
const descriptions = Array.from(container.querySelectorAll('p'), (p) => p.textContent)
|
||||
if (language === 'ko') {
|
||||
expect(descriptions).toEqual([
|
||||
`${shortcut} 단축키를 눌러 포커스된 패널에 텍스트를 받아쓰세요.`,
|
||||
`토글: ${shortcut} 단축키를 한 번 누르면 받아쓰기를 시작하고, 다시 누르면 중지합니다. 길게 누르기: ${shortcut} 단축키를 누르고 있는 동안 받아쓰기합니다.`
|
||||
])
|
||||
} else {
|
||||
expect(descriptions).toEqual([
|
||||
`Press ${shortcut} to dictate text into any focused pane.`,
|
||||
`Toggle: press ${shortcut} once to start, again to stop. Hold: dictate while ${shortcut} is held.`
|
||||
])
|
||||
}
|
||||
} finally {
|
||||
await act(async () => root.unmount())
|
||||
}
|
||||
})
|
||||
})
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { cleanup, render } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { setRendererUiLanguage } from '@/i18n/i18n'
|
||||
import { RepositoryIssueCommandSetting } from './RepositoryIssueCommandSetting'
|
||||
import { RepositoryHookCommandSourceSetting } from './RepositoryHookPolicySettings'
|
||||
|
||||
function renderSettings(hasSharedIssueCommand: boolean): ReturnType<typeof render> {
|
||||
return render(
|
||||
<>
|
||||
<RepositoryIssueCommandSetting
|
||||
issueCommandDraft=""
|
||||
setIssueCommandDraft={() => {}}
|
||||
hasSharedIssueCommand={hasSharedIssueCommand}
|
||||
issueCommandSaveError={null}
|
||||
commitIssueCommand={async () => {}}
|
||||
/>
|
||||
<RepositoryHookCommandSourceSetting
|
||||
searchQuery=""
|
||||
selectedPolicy="shared-only"
|
||||
yamlState="missing"
|
||||
yamlHooks={null}
|
||||
copiedTemplate={false}
|
||||
isAdvancedOpen
|
||||
onSelectPolicy={() => {}}
|
||||
onCopyTemplate={() => {}}
|
||||
onAdvancedOpenChange={() => {}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
cleanup()
|
||||
await setRendererUiLanguage('en')
|
||||
})
|
||||
|
||||
describe('Repository settings complete translation templates', () => {
|
||||
for (const language of ['en', 'ko'] as const) {
|
||||
it.each([false, true])(
|
||||
`preserves tokens and conditional defaults in ${language} (shared=%s)`,
|
||||
async (shared) => {
|
||||
await setRendererUiLanguage(language)
|
||||
const view = renderSettings(shared)
|
||||
const paragraphs = Array.from(view.container.querySelectorAll('p'))
|
||||
const issue = paragraphs.find(
|
||||
(p) => p.querySelector('code')?.textContent === '{{artifact_url}}'
|
||||
)
|
||||
expect(issue?.textContent).toContain('{{artifact_url}}')
|
||||
expect(issue?.textContent).not.toContain('<artifactUrl>')
|
||||
const defaultHelp = paragraphs.find(
|
||||
(p) => p.querySelector('code')?.textContent === 'orca.yaml'
|
||||
)
|
||||
if (language === 'ko') {
|
||||
expect(issue?.textContent).toContain('변수를 사용하세요.')
|
||||
expect(defaultHelp?.textContent?.includes('있는 경우')).toBe(!shared)
|
||||
} else {
|
||||
expect(defaultHelp?.textContent?.includes('when one exists')).toBe(!shared)
|
||||
}
|
||||
const sourceHelp = paragraphs.find((p) =>
|
||||
p.textContent?.includes(
|
||||
language === 'ko' ? '로컬 명령이 모두' : 'and local commands exist'
|
||||
)
|
||||
)
|
||||
expect(sourceHelp?.querySelector('code')?.textContent).toBe('orca.yaml')
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,67 +0,0 @@
|
||||
import { describe, expect, it, afterEach, vi } from 'vitest'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import { selectWorktreeActivityStatuses } from './use-worktree-activity-statuses'
|
||||
|
||||
type StatusState = Parameters<typeof selectWorktreeActivityStatuses>[0]
|
||||
|
||||
function makeStatusState(): StatusState {
|
||||
return {
|
||||
tabsByWorktree: {},
|
||||
browserTabsByWorktree: {},
|
||||
unifiedTabsByWorktree: {},
|
||||
structuredChatSleptAtByPaneKey: {},
|
||||
runtimePaneTitlesByTabId: {},
|
||||
ptyIdsByTabId: {},
|
||||
terminalLayoutsByTabId: {},
|
||||
agentStatusEpoch: 0,
|
||||
agentStatusByPaneKey: {},
|
||||
migrationUnsupportedByPtyId: {},
|
||||
retainedAgentsByPaneKey: {},
|
||||
runtimeAgentOrchestrationByPaneKey: {}
|
||||
}
|
||||
}
|
||||
|
||||
describe('selectWorktreeActivityStatuses', () => {
|
||||
it('stays shallow-equal when an unrelated worktree receives activity updates', () => {
|
||||
const state = makeStatusState()
|
||||
const unrelatedUpdate: StatusState = {
|
||||
...state,
|
||||
agentStatusEpoch: 1,
|
||||
browserTabsByWorktree: {
|
||||
other: []
|
||||
},
|
||||
runtimePaneTitlesByTabId: {
|
||||
'other-tab': { 0: 'codex [working]' }
|
||||
},
|
||||
ptyIdsByTabId: {
|
||||
'other-tab': ['other-pty']
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
shallow(
|
||||
selectWorktreeActivityStatuses(state, ['visible']),
|
||||
selectWorktreeActivityStatuses(unrelatedUpdate, ['visible'])
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
const { rejectAppStoreAccess } = vi.hoisted(() => ({
|
||||
rejectAppStoreAccess: vi.fn(() => {
|
||||
throw new Error('Pure helper tests must not access the app store')
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: Object.assign(rejectAppStoreAccess, {
|
||||
getState: rejectAppStoreAccess,
|
||||
getInitialState: rejectAppStoreAccess,
|
||||
setState: rejectAppStoreAccess,
|
||||
subscribe: rejectAppStoreAccess
|
||||
})
|
||||
}))
|
||||
|
||||
afterEach(() => {
|
||||
expect(rejectAppStoreAccess).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -1,65 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { useState } from 'react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import type { WorkspaceSpaceWorktree } from '../../../../shared/workspace-space-types'
|
||||
import { getWorkspaceSpaceWorktreeIdentity } from './workspace-space-delete-selection'
|
||||
import { WorkspaceSpaceTreemap } from './workspace-space-treemap'
|
||||
|
||||
const worktree: WorkspaceSpaceWorktree = {
|
||||
worktreeId: 'repo::/workspace',
|
||||
repoId: 'repo',
|
||||
executionHostId: 'local',
|
||||
repoDisplayName: 'repo',
|
||||
repoPath: '/repo',
|
||||
displayName: 'workspace',
|
||||
path: '/workspace',
|
||||
branch: 'main',
|
||||
isMainWorktree: false,
|
||||
isRemote: false,
|
||||
isSparse: false,
|
||||
canDelete: true,
|
||||
lastActivityAt: 0,
|
||||
status: 'ok',
|
||||
error: null,
|
||||
scannedAt: 0,
|
||||
sizeBytes: 4096,
|
||||
reclaimableBytes: 4096,
|
||||
skippedEntryCount: 0,
|
||||
topLevelItems: [
|
||||
{ name: 'node_modules', path: '/workspace/node_modules', kind: 'directory', sizeBytes: 3072 }
|
||||
],
|
||||
omittedTopLevelItemCount: 0,
|
||||
omittedTopLevelSizeBytes: 0
|
||||
}
|
||||
|
||||
function TreemapHarness(): React.JSX.Element {
|
||||
const [selectedWorktreeId, setSelectedWorktreeId] = useState<string | null>(null)
|
||||
const [zoomId, setZoomId] = useState<string | null>(null)
|
||||
return (
|
||||
<WorkspaceSpaceTreemap
|
||||
rows={[worktree]}
|
||||
isScanning={false}
|
||||
selectedWorktreeId={selectedWorktreeId}
|
||||
zoomedWorktree={zoomId === getWorkspaceSpaceWorktreeIdentity(worktree) ? worktree : null}
|
||||
onSelect={setSelectedWorktreeId}
|
||||
onZoomChange={setZoomId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('WorkspaceSpaceTreemap', () => {
|
||||
it('opens a workspace from its tile and returns to the overview', () => {
|
||||
render(<TreemapHarness />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /workspace, 4/ }))
|
||||
expect(screen.getByText('Folder sizes')).toBeTruthy()
|
||||
expect(screen.getByText('node_modules')).toBeTruthy()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'All' }))
|
||||
expect(screen.getByText('Click a workspace to zoom')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -1,45 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { PaneManager, ManagedPane } from '@/lib/pane-manager/pane-manager'
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import { applyTerminalAppearance } from './terminal-appearance'
|
||||
|
||||
describe('terminal inactive cursor style', () => {
|
||||
it('keeps blurred non-block cursors from rendering as an outline box', () => {
|
||||
const options = {
|
||||
cursorStyle: 'block' as const,
|
||||
cursorInactiveStyle: 'outline' as const
|
||||
}
|
||||
const terminal = {
|
||||
options,
|
||||
cols: 80,
|
||||
rows: 24
|
||||
} as unknown as ManagedPane['terminal']
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: this fixture supplies the pane members used by appearance logic.
|
||||
const pane = { id: 1, terminal } as ManagedPane
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: this fixture supplies the manager members used by appearance logic.
|
||||
const manager = {
|
||||
getPanes: () => [pane],
|
||||
setPaneLigaturesEnabled: vi.fn(),
|
||||
setPaneInlineImagesEnabled: vi.fn(),
|
||||
setPaneStyleOptions: vi.fn()
|
||||
} as unknown as PaneManager
|
||||
const settings = {
|
||||
...getDefaultSettings('/tmp'),
|
||||
terminalCursorStyle: 'bar' as const
|
||||
}
|
||||
|
||||
applyTerminalAppearance(
|
||||
manager,
|
||||
settings,
|
||||
false,
|
||||
new Map(),
|
||||
new Map(),
|
||||
'false',
|
||||
new Map(),
|
||||
new Map()
|
||||
)
|
||||
|
||||
expect(options.cursorStyle).toBe('bar')
|
||||
expect(options.cursorInactiveStyle).toBe('bar')
|
||||
})
|
||||
})
|
||||
@@ -1,52 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { dispatchTerminalNotification } from './use-notification-dispatch'
|
||||
import {
|
||||
PANE_KEY,
|
||||
getLastNotificationDispatchArg,
|
||||
makeAgentStatus,
|
||||
resetNotificationDispatchMockState,
|
||||
type NotificationDispatchMockState
|
||||
} from './notification-dispatch-test-harness'
|
||||
|
||||
vi.mock('@/store', async () => {
|
||||
const harness = await import('./notification-dispatch-test-harness')
|
||||
return harness.createNotificationDispatchStoreModuleMock()
|
||||
})
|
||||
|
||||
vi.mock('@/lib/desktop-notification-sound', async () => {
|
||||
const harness = await import('./notification-dispatch-test-harness')
|
||||
return harness.createDesktopNotificationSoundModuleMock()
|
||||
})
|
||||
|
||||
let mockState: NotificationDispatchMockState
|
||||
|
||||
// Use the event row’s shared verdict so StopFailure says failed instead of finished.
|
||||
describe('dispatchTerminalNotification verdict', () => {
|
||||
beforeEach(() => {
|
||||
mockState = resetNotificationDispatchMockState()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it.each([
|
||||
[{ mainAgent: { state: 'done', outcome: 'failure', stateStartedAt: 1 } }, 'failure'],
|
||||
[{ mainAgent: { state: 'done', outcome: 'cancellation', stateStartedAt: 1 } }, 'cancellation'],
|
||||
[{ interrupted: true }, 'cancellation'],
|
||||
[{}, undefined]
|
||||
] as const)('passes %j as the turn outcome %s', (overrides, outcome) => {
|
||||
mockState.agentStatusByPaneKey[PANE_KEY] = makeAgentStatus(PANE_KEY, overrides)
|
||||
|
||||
dispatchTerminalNotification('wt-primary', {
|
||||
source: 'agent-task-complete',
|
||||
terminalTitle: 'codex',
|
||||
paneKey: PANE_KEY
|
||||
})
|
||||
|
||||
expect(getLastNotificationDispatchArg()).toMatchObject({
|
||||
agentState: 'done',
|
||||
agentTurnOutcome: outcome
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { useRef } from 'react'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { WorkspaceEmojiSuggestionPopover } from './WorkspaceEmojiSuggestionPopover'
|
||||
import type { WorkspaceEmojiSuggestion } from '@/lib/workspace-emoji-shortcodes'
|
||||
|
||||
Element.prototype.scrollIntoView ??= () => {}
|
||||
|
||||
const SUGGESTIONS: WorkspaceEmojiSuggestion[] = [
|
||||
{ shortcode: 'smile', emoji: '😄' },
|
||||
{ shortcode: 'smiley', emoji: '😃' },
|
||||
{ shortcode: 'smirk', emoji: '😏' }
|
||||
]
|
||||
|
||||
function PopoverHarness({ commandValue }: { commandValue: string }): React.JSX.Element {
|
||||
const anchorRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input ref={anchorRef} aria-label="Workspace name" readOnly value=":smi" />
|
||||
<WorkspaceEmojiSuggestionPopover
|
||||
anchorRef={anchorRef}
|
||||
commandValue={commandValue}
|
||||
heading="Emoji"
|
||||
onCommandValueChange={() => {}}
|
||||
onOpenChange={() => {}}
|
||||
onSelect={() => {}}
|
||||
open
|
||||
suggestions={SUGGESTIONS}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
describe('WorkspaceEmojiSuggestionPopover', () => {
|
||||
let scrollIntoView: ReturnType<typeof vi.spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
scrollIntoView = vi.spyOn(Element.prototype, 'scrollIntoView').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
scrollIntoView.mockRestore()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it('scrolls the externally selected suggestion into view', async () => {
|
||||
const { rerender } = render(<PopoverHarness commandValue="emoji:smile" />)
|
||||
await screen.findByText(':smirk:')
|
||||
scrollIntoView.mockClear()
|
||||
|
||||
rerender(<PopoverHarness commandValue="emoji:smirk" />)
|
||||
|
||||
const scrolled = scrollIntoView.mock.instances as unknown as Element[]
|
||||
expect(scrolled.some((node) => node.getAttribute('data-value') === 'emoji:smirk')).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,58 +0,0 @@
|
||||
import i18next from 'i18next'
|
||||
import { expect, it } from 'vitest'
|
||||
import { parsePluginLanguagePackArtifact } from '../../../shared/plugins/plugin-language-pack-artifact'
|
||||
import en from './locales/en.json'
|
||||
import es from './locales/es.json'
|
||||
import fr from './locales/fr.json'
|
||||
import ja from './locales/ja.json'
|
||||
import ko from './locales/ko.json'
|
||||
import pt from './locales/pt.json'
|
||||
import zh from './locales/zh.json'
|
||||
|
||||
const AGENT_SETTINGS_KEYS = [
|
||||
'environmentName',
|
||||
'environmentValue',
|
||||
'savedEnvironment',
|
||||
'newEnvironment',
|
||||
'removeEnvironment',
|
||||
'loadingHost',
|
||||
'updateHost',
|
||||
'readFailed',
|
||||
'saveFailed',
|
||||
'hostDefaults',
|
||||
'save'
|
||||
] as const
|
||||
|
||||
it.each([
|
||||
['en', en],
|
||||
['es', es],
|
||||
['fr', fr],
|
||||
['ja', ja],
|
||||
['ko', ko],
|
||||
['pt', pt],
|
||||
['zh', zh]
|
||||
] as const)(
|
||||
'keeps %s agent settings usable as nested language-pack translations',
|
||||
async (locale, catalog) => {
|
||||
// Bundled security copy is intentionally unavailable to plugin language packs.
|
||||
expect(
|
||||
parsePluginLanguagePackArtifact(JSON.stringify({ settings: catalog.settings }))
|
||||
).toMatchObject({ ok: true })
|
||||
|
||||
const instance = i18next.createInstance()
|
||||
await instance.init({
|
||||
lng: locale,
|
||||
fallbackLng: false,
|
||||
resources: { [locale]: { translation: catalog } },
|
||||
ignoreJSONStructure: false
|
||||
})
|
||||
for (const key of AGENT_SETTINGS_KEYS) {
|
||||
expect(instance.t(`settings.agents.${key}`, { name: 'PATH' })).toBe(
|
||||
catalog.settings.agents[key].replace('{{name}}', 'PATH')
|
||||
)
|
||||
}
|
||||
expect(instance.t('settings.appearance.chat.title')).toBe(
|
||||
catalog.settings.appearance.chat.title
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { codexStructuredPermissionPolicy } from './codex-permission-policy'
|
||||
|
||||
const FULL_ACCESS = {
|
||||
approvalPolicy: 'never',
|
||||
sandbox: 'danger-full-access',
|
||||
approvalsReviewer: 'user'
|
||||
}
|
||||
const ASK = { approvalPolicy: 'on-request', sandbox: 'read-only', approvalsReviewer: 'user' }
|
||||
|
||||
describe('codexStructuredPermissionPolicy', () => {
|
||||
it('maps Full access to no prompts and no sandbox', () => {
|
||||
expect(codexStructuredPermissionPolicy('full-access')).toEqual(FULL_ACCESS)
|
||||
})
|
||||
|
||||
it('states every Ask field, the human reviewer too', () => {
|
||||
expect(codexStructuredPermissionPolicy('ask')).toEqual(ASK)
|
||||
})
|
||||
|
||||
it('keeps Approve for me in the configured workspace sandbox', () => {
|
||||
expect(codexStructuredPermissionPolicy('auto')).toEqual({
|
||||
approvalPolicy: 'on-request',
|
||||
sandbox: 'workspace-write',
|
||||
approvalsReviewer: 'auto_review'
|
||||
})
|
||||
})
|
||||
|
||||
it('names a sandbox mode for every mode', () => {
|
||||
for (const mode of ['ask', 'auto', 'full-access'] as const) {
|
||||
expect(codexStructuredPermissionPolicy(mode)).not.toHaveProperty('sandboxPolicy')
|
||||
expect(typeof codexStructuredPermissionPolicy(mode).sandbox).toBe('string')
|
||||
}
|
||||
})
|
||||
|
||||
it('asks for a mode Codex does not have', () => {
|
||||
expect(codexStructuredPermissionPolicy('accept-edits')).toEqual(ASK)
|
||||
})
|
||||
})
|
||||
@@ -4,17 +4,14 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
__getSecureFileHardeningCacheStateForTests,
|
||||
__resetSecureFileHardenedPathsForTests,
|
||||
hardenExistingSecureFile,
|
||||
isUnreadableError,
|
||||
writeSecureFile
|
||||
} from './secure-file'
|
||||
import {
|
||||
setWindowsSecurityForTests,
|
||||
resetWindowsSecurityLoadForTests
|
||||
} from './windows-security-addon'
|
||||
import { createFakeWindowsSecurity } from './windows-security-addon.test-fixture'
|
||||
|
||||
const chmodFailure = vi.hoisted((): { code: string | null } => ({ code: null }))
|
||||
|
||||
@@ -61,18 +58,6 @@ describe('POSIX secure files', () => {
|
||||
return dir
|
||||
}
|
||||
|
||||
it('never touches the Windows ACL addon off win32', () => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
|
||||
const fake = createFakeWindowsSecurity()
|
||||
setWindowsSecurityForTests(fake)
|
||||
const targetPath = join(tempDir(), 'secret.json')
|
||||
|
||||
writeSecureFile(targetPath, 'contents')
|
||||
hardenExistingSecureFile(targetPath)
|
||||
|
||||
expect(fake.calls).toEqual([])
|
||||
})
|
||||
|
||||
posixModeIt('restricts the published file and its directory by mode', () => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
|
||||
const userDataPath = tempDir()
|
||||
@@ -114,60 +99,8 @@ describe('POSIX secure files', () => {
|
||||
expect(() => hardenExistingSecureFile(targetPath)).toThrow('EIO')
|
||||
}
|
||||
)
|
||||
|
||||
posixModeIt('LRU-bounds POSIX hardening entries while keeping recent paths cached', () => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
|
||||
__resetSecureFileHardenedPathsForTests({
|
||||
maxEntries: 2,
|
||||
maxKeyBytes: 4096,
|
||||
maxTotalKeyBytes: 8192
|
||||
})
|
||||
const userDataPath = tempDir()
|
||||
const firstPath = join(userDataPath, 'first.json')
|
||||
const secondPath = join(userDataPath, 'second.json')
|
||||
writeFileSync(firstPath, '{}')
|
||||
writeFileSync(secondPath, '{}')
|
||||
|
||||
hardenExistingSecureFile(firstPath)
|
||||
hardenExistingSecureFile(secondPath)
|
||||
expect(__getSecureFileHardeningCacheStateForTests().paths.paths).toEqual([
|
||||
userDataPath,
|
||||
secondPath
|
||||
])
|
||||
|
||||
hardenExistingSecureFile(firstPath)
|
||||
expect(__getSecureFileHardeningCacheStateForTests().paths.paths).toEqual([
|
||||
userDataPath,
|
||||
firstPath
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
function statMode(path: string): number {
|
||||
return statSync(path).mode & 0o777
|
||||
}
|
||||
|
||||
describe('isUnreadableError', () => {
|
||||
const withCode = (code: string): NodeJS.ErrnoException => Object.assign(new Error(code), { code })
|
||||
|
||||
it('reports a denied read', () => {
|
||||
expect(isUnreadableError(withCode('EPERM'))).toBe(true)
|
||||
expect(isUnreadableError(withCode('EACCES'))).toBe(true)
|
||||
})
|
||||
|
||||
/** AV locks (EBUSY) or fd exhaustion (EMFILE) reveal no contents and must never authorize regenerate-and-overwrite. */
|
||||
it('reports a read that never reached the contents for any other reason', () => {
|
||||
expect(isUnreadableError(withCode('EBUSY'))).toBe(true)
|
||||
expect(isUnreadableError(withCode('EMFILE'))).toBe(true)
|
||||
expect(isUnreadableError(withCode('ENFILE'))).toBe(true)
|
||||
expect(isUnreadableError(withCode('EIO'))).toBe(true)
|
||||
})
|
||||
|
||||
/** Missing files permit creation; parsed garbage permits repair. Neither is an unreadable-content failure. */
|
||||
it('does not report a missing file or a parse failure', () => {
|
||||
expect(isUnreadableError(withCode('ENOENT'))).toBe(false)
|
||||
expect(isUnreadableError(new SyntaxError('Unexpected end of JSON input'))).toBe(false)
|
||||
expect(isUnreadableError(withCode('EISDIR'))).toBe(false)
|
||||
expect(isUnreadableError(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,10 +5,7 @@ import { tmpdir } from 'node:os'
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
createSequencedSetupAgentCommands,
|
||||
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV
|
||||
} from './setup-agent-sequencing'
|
||||
import { createSequencedSetupAgentCommands } from './setup-agent-sequencing'
|
||||
import { quotePowerShellLiteral } from './powershell-native-argument'
|
||||
|
||||
const TEMP_DIRS: string[] = []
|
||||
@@ -86,20 +83,6 @@ describe.skipIf(process.platform !== 'win32')('Windows setup-agent sequencing',
|
||||
WINDOWS_PROCESS_TEST_TIMEOUT_MS
|
||||
)
|
||||
|
||||
it('keeps the startup command out of generated cmd.exe source', () => {
|
||||
const startupCommand = 'agent --prompt "& | ^ % ! 한글 trailing\\"'
|
||||
const commands = createSequencedSetupAgentCommands({
|
||||
runnerScriptPath: 'C:\\repo\\setup-runner.cmd',
|
||||
startupCommand,
|
||||
platform: 'windows',
|
||||
nonce: 'windows-sequence'
|
||||
})
|
||||
|
||||
expect(commands.setupCommand).not.toContain(startupCommand)
|
||||
expect(commands.startupCommand).not.toContain(startupCommand)
|
||||
expect(commands.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]).toBe(startupCommand)
|
||||
})
|
||||
|
||||
it(
|
||||
'propagates setup failure without launching the agent',
|
||||
async () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { indexSidebarFeed } from '../sidebar-list/sidebar-feed-index'
|
||||
import { composeWorktreeHostIdentity } from '../worktree/host-qualified-identity'
|
||||
import { WorkspaceRowsSnapshotSchema, type WorkspaceRow } from '../sidebar-list/workspace-rows'
|
||||
import { SidebarFeedReader, type SidebarFeedReaderRead } from './sidebar-feed-reader'
|
||||
import { installSidebarFeedSnapshot, type SidebarFeedSnapshot } from './sidebar-feed-snapshot'
|
||||
import type { SidebarFeedSnapshot } from './sidebar-feed-snapshot'
|
||||
|
||||
const WORKSPACES = 1000
|
||||
|
||||
@@ -115,10 +115,4 @@ describe('sidebar feed snapshot', () => {
|
||||
// A domain the change did not touch keeps its map.
|
||||
expect(after.domains.review).toBe(before.domains.review)
|
||||
})
|
||||
|
||||
it('keeps the snapshot itself when the install is the same', async () => {
|
||||
const host = await readHost()
|
||||
const snapshot = host.snapshot()
|
||||
expect(installSidebarFeedSnapshot(snapshot.installed, snapshot)).toBe(snapshot)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getDefaultSettings } from './constants'
|
||||
import type { GlobalSettings } from './global-settings-types'
|
||||
import type { Repo } from './repo-types'
|
||||
import type { TuiAgent } from './tui-agent'
|
||||
import {
|
||||
resolveSourceControlLaunchAgentScope,
|
||||
summarizeReposOverridingActionRecipe
|
||||
} from './source-control-launch-agent-selection'
|
||||
|
||||
function settingsWithGlobalResolveAgent(agentId: TuiAgent): GlobalSettings {
|
||||
const base = getDefaultSettings('/tmp')
|
||||
return {
|
||||
...base,
|
||||
defaultTuiAgent: agentId,
|
||||
sourceControlAi: {
|
||||
...base.sourceControlAi!,
|
||||
enabled: true,
|
||||
actions: {
|
||||
...base.sourceControlAi!.actions,
|
||||
resolveConflicts: { agentId, commandInputTemplate: '{basePrompt}', agentArgs: '' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function repoWithResolveAgent(agentId: TuiAgent): Pick<Repo, 'sourceControlAi'> {
|
||||
return {
|
||||
sourceControlAi: {
|
||||
actionOverrides: {
|
||||
resolveConflicts: { agentId, commandInputTemplate: '{basePrompt}', agentArgs: '' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('resolveSourceControlLaunchAgentScope', () => {
|
||||
it('flags a repo override that pins a different agent than the global default', () => {
|
||||
const scope = resolveSourceControlLaunchAgentScope({
|
||||
settings: settingsWithGlobalResolveAgent('claude'),
|
||||
repo: repoWithResolveAgent('codex'),
|
||||
actionId: 'resolveConflicts'
|
||||
})
|
||||
expect(scope).toEqual({
|
||||
effectiveAgentId: 'codex',
|
||||
globalAgentId: 'claude',
|
||||
overridesGlobalAgent: true
|
||||
})
|
||||
})
|
||||
|
||||
it('does not flag a repo override that matches the global agent', () => {
|
||||
const scope = resolveSourceControlLaunchAgentScope({
|
||||
settings: settingsWithGlobalResolveAgent('claude'),
|
||||
repo: repoWithResolveAgent('claude'),
|
||||
actionId: 'resolveConflicts'
|
||||
})
|
||||
expect(scope.overridesGlobalAgent).toBe(false)
|
||||
})
|
||||
|
||||
it('does not flag when there is no repo override', () => {
|
||||
const scope = resolveSourceControlLaunchAgentScope({
|
||||
settings: settingsWithGlobalResolveAgent('claude'),
|
||||
repo: null,
|
||||
actionId: 'resolveConflicts'
|
||||
})
|
||||
expect(scope).toEqual({
|
||||
effectiveAgentId: 'claude',
|
||||
globalAgentId: 'claude',
|
||||
overridesGlobalAgent: false
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to the default agent when no global recipe agent is set', () => {
|
||||
const base = getDefaultSettings('/tmp')
|
||||
const settings: GlobalSettings = {
|
||||
...base,
|
||||
defaultTuiAgent: 'claude',
|
||||
sourceControlAi: { ...base.sourceControlAi!, enabled: true }
|
||||
}
|
||||
const scope = resolveSourceControlLaunchAgentScope({
|
||||
settings,
|
||||
repo: repoWithResolveAgent('codex'),
|
||||
actionId: 'resolveConflicts'
|
||||
})
|
||||
expect(scope.globalAgentId).toBe('claude')
|
||||
expect(scope.overridesGlobalAgent).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
function repoOverriding(
|
||||
id: string,
|
||||
displayName: string,
|
||||
agentId: TuiAgent
|
||||
): Pick<Repo, 'id' | 'displayName' | 'sourceControlAi'> {
|
||||
return { id, displayName, ...repoWithResolveAgent(agentId) }
|
||||
}
|
||||
|
||||
describe('summarizeReposOverridingActionRecipe', () => {
|
||||
it('lists repos with any action recipe override', () => {
|
||||
const summary = summarizeReposOverridingActionRecipe({
|
||||
repos: [
|
||||
repoOverriding('repo-1', 'App', 'claude'),
|
||||
{
|
||||
id: 'repo-2',
|
||||
displayName: 'Web',
|
||||
sourceControlAi: {
|
||||
actionOverrides: {
|
||||
resolveConflicts: {
|
||||
commandInputTemplate: '{basePrompt}\n\nUse project conflict rules.',
|
||||
agentArgs: '--model gpt-5.4-mini'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ id: 'repo-3', displayName: 'Docs' }
|
||||
],
|
||||
actionId: 'resolveConflicts'
|
||||
})
|
||||
|
||||
expect(summary).toEqual({
|
||||
count: 2,
|
||||
overrides: [
|
||||
{ repoId: 'repo-1', repoName: 'App', fields: ['agent', 'commandTemplate', 'agentArgs'] },
|
||||
{ repoId: 'repo-2', repoName: 'Web', fields: ['commandTemplate', 'agentArgs'] }
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('includes legacy repo instructions as command template overrides', () => {
|
||||
const summary = summarizeReposOverridingActionRecipe({
|
||||
repos: [
|
||||
{
|
||||
id: 'repo-1',
|
||||
displayName: 'App',
|
||||
sourceControlAi: {
|
||||
instructionsByOperation: {
|
||||
commitMessage: 'Use project style.'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
actionId: 'commitMessage'
|
||||
})
|
||||
|
||||
expect(summary).toEqual({
|
||||
count: 1,
|
||||
overrides: [{ repoId: 'repo-1', repoName: 'App', fields: ['commandTemplate'] }]
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import './source-control-launch-agent-selection.spec'
|
||||
import './omp-source-control-discovery.spec'
|
||||
import './source-control-ai-policy-regression.spec'
|
||||
import './commit-message-omp.spec'
|
||||
|
||||
@@ -73,22 +73,6 @@ describe('the first frame of every agent’s picker', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it.each(AGENTS)(
|
||||
'%s: a warm host catalog lists its models before the session reports',
|
||||
(agent) => {
|
||||
const { state, snapshot } = firstFrame(agent, {
|
||||
...HOST_CATALOG,
|
||||
listingNamesConfiguredModel: false
|
||||
})
|
||||
expect(state.catalogSource).toBe('host')
|
||||
const model = snapshot.find((descriptor) => descriptor.id === 'model')
|
||||
expect(
|
||||
model?.kind.type === 'select' && model.kind.choices.map((choice) => choice.value)
|
||||
).toEqual(['model-listed'])
|
||||
expect(state.record.values.model).toBeUndefined()
|
||||
}
|
||||
)
|
||||
|
||||
it.each(AGENTS)(
|
||||
'%s: a new chat names the listed model and its effort when the host says it runs them',
|
||||
(agent) => {
|
||||
|
||||
@@ -13,50 +13,6 @@ const oracle = (pending: string, chunk: string): string => {
|
||||
return tail.length > MAX_PARTIAL_ESCAPE_TAIL_LENGTH ? '' : tail
|
||||
}
|
||||
|
||||
// Every byte class the scanner branches on, plus code units the gate's `includes` must not confuse.
|
||||
const ALPHABET = [
|
||||
'\x1b',
|
||||
'\x18',
|
||||
'\x1a',
|
||||
'\x07',
|
||||
'\x00',
|
||||
'\\',
|
||||
'[',
|
||||
']',
|
||||
'P',
|
||||
'X',
|
||||
'^',
|
||||
'_',
|
||||
'(',
|
||||
' ',
|
||||
'0',
|
||||
';',
|
||||
'm',
|
||||
'\n',
|
||||
'\x7f',
|
||||
'\x9c',
|
||||
'é',
|
||||
'中',
|
||||
'\u{1f600}',
|
||||
'\ud83d',
|
||||
'\udc00'
|
||||
]
|
||||
|
||||
// One representative of every state the scanner can be left in.
|
||||
const PENDINGS = [
|
||||
'',
|
||||
'\x1b',
|
||||
'\x1b[',
|
||||
'\x1b[3',
|
||||
'\x1b]0;ti',
|
||||
'\x1b]0;ti\x1b',
|
||||
'\x1bP dcs',
|
||||
'\x1bPx\x1b',
|
||||
'\x1b(',
|
||||
'\x1b ',
|
||||
'\x1b[1;2;3'
|
||||
]
|
||||
|
||||
const SEQUENCES = [
|
||||
'\x1b[1;31m',
|
||||
'\x1b]0;my title\x07',
|
||||
@@ -79,27 +35,7 @@ const SEQUENCES = [
|
||||
'\x1bX sos \x1b\x1bP'
|
||||
]
|
||||
|
||||
// Count corpus depth separately: astral symbols occupy two UTF-16 units, so text.length would drop valid depth-N strings.
|
||||
function* stringsUpTo(maxDepth: number): Generator<{ depth: number; text: string }> {
|
||||
yield { depth: 0, text: '' }
|
||||
for (let depth = 1; depth <= maxDepth; depth++) {
|
||||
const digits = Array.from({ length: depth }, () => 0)
|
||||
for (;;) {
|
||||
yield { depth, text: digits.map((digit) => ALPHABET[digit]).join('') }
|
||||
let place = depth - 1
|
||||
while (place >= 0 && ++digits[place] === ALPHABET.length) {
|
||||
digits[place--] = 0
|
||||
}
|
||||
if (place < 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('advancePartialEscapeTail differential fuzz', () => {
|
||||
let checked = 0
|
||||
let foldSplits = 0
|
||||
// Re-split the whole stream to exercise oscEsc/stringEsc boundaries; pre-normalized tails make the fold assertion tautological.
|
||||
const checkFolds = (text: string): void => {
|
||||
if (text.length > 32) {
|
||||
@@ -107,7 +43,6 @@ describe('advancePartialEscapeTail differential fuzz', () => {
|
||||
}
|
||||
const whole = extractPartialEscapeTail(text)
|
||||
for (let cut = 0; cut <= text.length; cut++) {
|
||||
foldSplits++
|
||||
const folded = extractPartialEscapeTail(
|
||||
extractPartialEscapeTail(text.slice(0, cut)) + text.slice(cut)
|
||||
)
|
||||
@@ -117,7 +52,6 @@ describe('advancePartialEscapeTail differential fuzz', () => {
|
||||
}
|
||||
}
|
||||
const check = (pending: string, chunk: string): void => {
|
||||
checked++
|
||||
const actual = advancePartialEscapeTail(pending, chunk)
|
||||
if (actual !== oracle(pending, chunk)) {
|
||||
expect.fail(`gate diverged: ${JSON.stringify({ pending, chunk, actual })}`)
|
||||
@@ -132,20 +66,6 @@ describe('advancePartialEscapeTail differential fuzz', () => {
|
||||
checkFolds(pending + chunk)
|
||||
}
|
||||
|
||||
it('matches the unguarded oracle on every chunk up to length 4', () => {
|
||||
for (const { text: chunk } of stringsUpTo(3)) {
|
||||
for (const pending of PENDINGS) {
|
||||
check(pending, chunk)
|
||||
}
|
||||
}
|
||||
for (const { depth, text: chunk } of stringsUpTo(4)) {
|
||||
if (depth === 4) {
|
||||
check('', chunk)
|
||||
check('\x1b[', chunk)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('matches at every split point of known sequences', () => {
|
||||
for (const sequence of SEQUENCES) {
|
||||
for (let cut = 0; cut <= sequence.length; cut++) {
|
||||
@@ -161,22 +81,4 @@ describe('advancePartialEscapeTail differential fuzz', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('matches across the tail-length cap', () => {
|
||||
const max = MAX_PARTIAL_ESCAPE_TAIL_LENGTH
|
||||
for (const length of [max - 1, max, max + 1, max + 100]) {
|
||||
const osc = `\x1b]0;${'x'.repeat(length - 4)}`
|
||||
for (const chunk of ['', 'y', '\x07', '\x1b\\', '\x1b', 'plain\n', 'x'.repeat(5000)]) {
|
||||
check(osc, chunk)
|
||||
check('', osc + chunk)
|
||||
check('\x1b]0;', osc.slice(4) + chunk)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('ran the whole corpus', () => {
|
||||
expect(checked).toBe(963_819)
|
||||
// Completed strings retain only ESC, so the same corpus has fewer tail splits.
|
||||
expect(foldSplits).toBe(6_234_666)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
createRendererParityTerminal,
|
||||
cursorPosition,
|
||||
@@ -18,43 +18,6 @@ type AsyncOscParser = {
|
||||
}
|
||||
|
||||
describe('terminal parity fixture writes', () => {
|
||||
it('queues every original chunk and resolves only after the last callback', async () => {
|
||||
const { terminal } = createRendererParityTerminal({ cols: 8, rows: 2 })
|
||||
const writes: { data: string | Uint8Array; callback?: () => void }[] = []
|
||||
try {
|
||||
vi.spyOn(terminal, 'write').mockImplementation((data, callback) => {
|
||||
writes.push({ data, callback })
|
||||
})
|
||||
const chunks = ['A\x1b[', '', '31mB', '\x1b[0mC']
|
||||
let completed = false
|
||||
const pending = writeChunksToTerminal(terminal, chunks).then(() => {
|
||||
completed = true
|
||||
})
|
||||
expect(writes.map(({ data }) => data)).toEqual(chunks)
|
||||
expect(writes.slice(0, -1).every(({ callback }) => callback === undefined)).toBe(true)
|
||||
await Promise.resolve()
|
||||
expect(completed).toBe(false)
|
||||
writes.at(-1)?.callback?.()
|
||||
await pending
|
||||
expect(completed).toBe(true)
|
||||
} finally {
|
||||
terminal.dispose()
|
||||
vi.restoreAllMocks()
|
||||
}
|
||||
})
|
||||
|
||||
it('completes an empty batch without writing or scheduling work', async () => {
|
||||
const { terminal } = createRendererParityTerminal({ cols: 8, rows: 2 })
|
||||
try {
|
||||
const write = vi.spyOn(terminal, 'write')
|
||||
await writeChunksToTerminal(terminal, [])
|
||||
expect(write).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
terminal.dispose()
|
||||
vi.restoreAllMocks()
|
||||
}
|
||||
})
|
||||
|
||||
it('waits for an asynchronous parser barrier before exposing later chunks', async () => {
|
||||
const { terminal } = createRendererParityTerminal({ cols: 8, rows: 2 })
|
||||
let signalEntered = (): void => {}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import { isGeminiTerminalTitle } from './agent-title-core'
|
||||
import { getAgentLabel, isClaudeAgent } from './agent-title-identity'
|
||||
import { detectAgentStatusFromTitle } from './agent-title-status'
|
||||
import { TERMINAL_TITLE_CLASSIFICATION_CORPUS } from './terminal-title-classification-corpus'
|
||||
import {
|
||||
getAgentLabel as getExplicitAgentLabel,
|
||||
isClaudeAgent as isExplicitClaudeAgent,
|
||||
@@ -244,12 +243,6 @@ const PINNED_CLASSIFICATIONS: readonly PinnedRow[] = [
|
||||
]
|
||||
|
||||
describe('terminal title classification', () => {
|
||||
it('covers every corpus title exactly once', () => {
|
||||
expect(PINNED_CLASSIFICATIONS.map(([title]) => title)).toEqual([
|
||||
...TERMINAL_TITLE_CLASSIFICATION_CORPUS
|
||||
])
|
||||
})
|
||||
|
||||
it.each(PINNED_CLASSIFICATIONS)(
|
||||
'classifies %j identically',
|
||||
(
|
||||
|
||||
@@ -5,10 +5,7 @@ import {
|
||||
projectOrchestrationFleet,
|
||||
type FleetDurableWorker
|
||||
} from './orchestration-fleet-projection'
|
||||
import {
|
||||
parseWorkerTerminalHostScope,
|
||||
readWorkerTerminalHostScope
|
||||
} from './worker-terminal-host-scope'
|
||||
import { readWorkerTerminalHostScope } from './worker-terminal-host-scope'
|
||||
|
||||
/** One host_scope read must drive host labels and connection fences; divergent WSL-local parsing previously made live local workers unverifiable. */
|
||||
const PANE_KEY = 'tab-host:leaf-host'
|
||||
@@ -174,23 +171,4 @@ describe('worker terminal host scope', () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('keeps the strict scope contract the process-liveness path depends on', () => {
|
||||
expect(parseWorkerTerminalHostScope('{"kind":"ssh","targetId":"host-1"}')).toEqual({
|
||||
kind: 'ssh',
|
||||
targetId: 'host-1'
|
||||
})
|
||||
expect(
|
||||
parseWorkerTerminalHostScope('{"kind":"wsl","hostId":"local","distro":"Ubuntu"}')
|
||||
).toEqual({ kind: 'wsl', hostId: 'local', distro: 'Ubuntu' })
|
||||
expect(parseWorkerTerminalHostScope('{"kind":"local","hostId":"local"}')).toEqual({
|
||||
kind: 'local',
|
||||
hostId: 'local'
|
||||
})
|
||||
// A scope missing the facts its kind requires is not a scope.
|
||||
expect(parseWorkerTerminalHostScope('{"kind":"wsl","hostId":"local"}')).toBeNull()
|
||||
expect(parseWorkerTerminalHostScope('{"kind":"ssh"}')).toBeNull()
|
||||
expect(parseWorkerTerminalHostScope('local:workspace-1')).toBeNull()
|
||||
expect(parseWorkerTerminalHostScope(null)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,8 +5,7 @@ import { LOCAL_EXECUTION_HOST_ID } from '../execution-host'
|
||||
import {
|
||||
DISK_TABLES,
|
||||
type DiskFieldWritten,
|
||||
type LayoutSource,
|
||||
type UnwrittenLayoutSource
|
||||
type LayoutSource
|
||||
} from './workspace-layout-disk-fields'
|
||||
import { EDITOR_FILE_KEY_FIELDS } from './workspace-layout-editor-tab-file'
|
||||
import { loadWorkspaceLayout } from './workspace-layout-load'
|
||||
@@ -18,37 +17,6 @@ import { saveWorkspaceLayout } from './workspace-layout-save'
|
||||
import { addWorkspace, GIT_KEY, leaf } from './workspace-layout-session.test-fixture'
|
||||
import type { WorkspaceSessionState } from '../workspace-session-state-types'
|
||||
|
||||
// A model or side-data field no disk field is written from fails here.
|
||||
const everySourceIsWritten: [UnwrittenLayoutSource] extends [never] ? true : UnwrittenLayoutSource =
|
||||
true
|
||||
|
||||
/** On-disk fields that hold one fact; each group must be written from the same source. */
|
||||
const COPIES = [
|
||||
['row.ptyId', 'layout.ptyIdsByLeafId'],
|
||||
['row.id', 'terminalEntry.entityId'],
|
||||
['row.title', 'terminalEntry.label'],
|
||||
['row.customTitle', 'terminalEntry.customLabel'],
|
||||
['row.generatedTitle', 'terminalEntry.generatedLabel'],
|
||||
...['createdAt', 'color', 'aiVaultTitle', 'quickCommandLabel', 'isPinned', 'viewMode'].map(
|
||||
(field) => [`row.${field}`, `terminalEntry.${field}`]
|
||||
),
|
||||
['row.sortOrder', 'terminalEntry.sortOrder', 'entry.sortOrder', 'group.tabOrder'],
|
||||
[
|
||||
'row.worktreeId',
|
||||
'terminalEntry.worktreeId',
|
||||
'entry.worktreeId',
|
||||
'group.worktreeId',
|
||||
'file.worktreeId',
|
||||
'browser.worktreeId',
|
||||
'sleeping.worktreeId',
|
||||
'closedTab.worktreeId'
|
||||
],
|
||||
['sleeping.paneKey', 'sleeping.tabId'],
|
||||
['terminalEntry.executionHostId', 'entry.executionHostId'],
|
||||
['entry.isPreview', 'file.isPreview'],
|
||||
['terminalEntry.groupId', 'entry.groupId', 'group.id']
|
||||
]
|
||||
|
||||
/** Do not mutate identity fields: broken references would test integrity rather than fact ownership. */
|
||||
const IDENTITY_SOURCES = new Set<LayoutSource>([
|
||||
'tab.id',
|
||||
@@ -290,32 +258,6 @@ function diskValues(session: WorkspaceSessionState): Map<string, string> {
|
||||
const ALL_SOURCES = [...new Set([...WRITTEN_BY_FIELD.values(), TAB_LIST_ORDER].flatMap(sourcesOf))]
|
||||
|
||||
describe('each on-disk field has one source', () => {
|
||||
it('is typed over every disk, model and side-data field', () => {
|
||||
expect(everySourceIsWritten).toBe(true)
|
||||
})
|
||||
|
||||
it('writes every copy of one fact from the same source', () => {
|
||||
for (const group of COPIES) {
|
||||
const sources = group.map((field) => {
|
||||
const written = writtenOf(field)
|
||||
return typeof written === 'string' ? written : written.from
|
||||
})
|
||||
expect(new Set(sources).size, group.join(' = ')).toBe(1)
|
||||
}
|
||||
})
|
||||
|
||||
it('has every source populated, so a mutation can show what it writes', () => {
|
||||
const loaded = richLoaded()
|
||||
const empty = ALL_SOURCES.filter((source) => {
|
||||
const [prefix, field] = splitSource(source)
|
||||
return (
|
||||
!IDENTITY_SOURCES.has(source) &&
|
||||
!holdersOf(loaded, prefix).some((holder) => holder[field] != null)
|
||||
)
|
||||
})
|
||||
expect(empty).toEqual([])
|
||||
})
|
||||
|
||||
it('holds each placed pane record in exactly one of its two holders', () => {
|
||||
const loaded = richLoaded()
|
||||
const leaves = Object.values(loaded.layout.workspaces).flatMap((workspace) =>
|
||||
|
||||
@@ -85,15 +85,6 @@ describe("main's load of a browser record with no tab", () => {
|
||||
expect(resolveSessionTabRow(workspace, 'browser-1')?.tab.id).toBe(tab.id)
|
||||
})
|
||||
|
||||
it('saves the tab, so the next load mints nothing new', () => {
|
||||
const saved = saveWorkspaceLayout(load(savedSession()))
|
||||
|
||||
expect(
|
||||
saved.unifiedTabs?.[KEY]?.find((entry) => entry.contentType === 'browser')
|
||||
).toMatchObject({ entityId: 'browser-1', groupId: 'g2', label: 'Example' })
|
||||
expect(load(saved).changes).toEqual([])
|
||||
})
|
||||
|
||||
it('mints the same id on every load, and a restart after a save keeps one tab', () => {
|
||||
const ids = (session: WorkspaceSessionState) =>
|
||||
load(session)
|
||||
|
||||
@@ -415,7 +415,6 @@ const CORPUS: [string, unknown][] = [
|
||||
|
||||
describe('salvage equivalence with the pre-optimization implementation', () => {
|
||||
let legacyParse: LegacyParse
|
||||
let legacyUnionsBuilt = 0
|
||||
|
||||
beforeAll(async () => {
|
||||
// Build the legacy schema from both legacy salvage combinators and plain unions to cover both optimizations.
|
||||
@@ -427,7 +426,6 @@ describe('salvage equivalence with the pre-optimization implementation', () => {
|
||||
z: {
|
||||
...actual.z,
|
||||
discriminatedUnion: (_key: string, options: z.ZodType[]) => {
|
||||
legacyUnionsBuilt += 1
|
||||
return actual.z.union(options)
|
||||
}
|
||||
}
|
||||
@@ -443,11 +441,6 @@ describe('salvage equivalence with the pre-optimization implementation', () => {
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('builds a legacy parser from the legacy combinators and plain unions', () => {
|
||||
expect(legacyParse).not.toBe(parseWorkspaceSessionSalvaging)
|
||||
expect(legacyUnionsBuilt).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
it.each(CORPUS)('matches the legacy result for %s', (_name, payload) => {
|
||||
const legacy = legacyParse(payload)
|
||||
const current = parseWorkspaceSessionSalvaging(payload)
|
||||
@@ -461,14 +454,6 @@ describe('salvage equivalence with the pre-optimization implementation', () => {
|
||||
expect(current.droppedPaths.toSorted()).toEqual(legacy.droppedPaths.toSorted())
|
||||
})
|
||||
|
||||
it('rejects the same payloads the legacy parser rejects', () => {
|
||||
const rejected = CORPUS.filter(([, payload]) => !legacyParse(payload).ok)
|
||||
expect(rejected.length).toBeGreaterThan(0)
|
||||
for (const [, payload] of rejected) {
|
||||
expect(parseWorkspaceSessionSalvaging(payload).ok).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a record carrying an enumerable symbol key, as zod.record did', () => {
|
||||
const withSymbol: Record<string, unknown> = { 'tab-1:leaf-1': 'inc-1' }
|
||||
withSymbol[Symbol('extra') as unknown as string] = 'x'
|
||||
|
||||
Reference in New Issue
Block a user