mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
The chat surface now relies on the current-turn changes card for file rollback, so the older per-message hover rewind affordance was removed to avoid two competing rollback models. Constraint: Current-turn undo still depends on the existing checkpoint rewind API. Rejected: Keep both rewind entry points | duplicate rollback affordances make the product harder to explain and test. Confidence: high Scope-risk: moderate Directive: Prefer adding rollback behavior through the current-turn change card instead of restoring per-message hover rewind. Tested: bun test src/server/__tests__/sessions.test.ts Tested: cd desktop && bun run test Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Not-tested: Browser E2E scripts were updated but not executed in this commit.
178 lines
4.5 KiB
TypeScript
178 lines
4.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { act, fireEvent, render, screen } from '@testing-library/react'
|
|
|
|
const { sendMock, openSettingsMock } = vi.hoisted(() => ({
|
|
sendMock: vi.fn(),
|
|
openSettingsMock: vi.fn(async () => ({ ok: true })),
|
|
}))
|
|
|
|
vi.mock('../../api/websocket', () => ({
|
|
wsManager: {
|
|
connect: vi.fn(),
|
|
disconnect: vi.fn(),
|
|
onMessage: vi.fn(() => () => {}),
|
|
clearHandlers: vi.fn(),
|
|
send: sendMock,
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../api/sessions', () => ({
|
|
sessionsApi: {
|
|
getMessages: vi.fn(async () => ({ messages: [] })),
|
|
getSlashCommands: vi.fn(async () => ({ commands: [] })),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../stores/teamStore', () => ({
|
|
useTeamStore: {
|
|
getState: () => ({
|
|
getMemberBySessionId: vi.fn(() => null),
|
|
sendMessageToMember: vi.fn(async () => {}),
|
|
handleTeamCreated: vi.fn(),
|
|
handleTeamUpdate: vi.fn(),
|
|
handleTeamDeleted: vi.fn(),
|
|
}),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../stores/tabStore', () => ({
|
|
useTabStore: {
|
|
getState: () => ({
|
|
updateTabStatus: vi.fn(),
|
|
updateTabTitle: vi.fn(),
|
|
}),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../stores/sessionStore', () => ({
|
|
useSessionStore: {
|
|
getState: () => ({
|
|
updateSessionTitle: vi.fn(),
|
|
}),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../stores/cliTaskStore', () => ({
|
|
useCLITaskStore: {
|
|
getState: () => ({
|
|
fetchSessionTasks: vi.fn(),
|
|
tasks: [],
|
|
clearTasks: vi.fn(),
|
|
setTasksFromTodos: vi.fn(),
|
|
markCompletedAndDismissed: vi.fn(),
|
|
resetCompletedTasks: vi.fn(async () => {}),
|
|
refreshTasks: vi.fn(),
|
|
}),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../api/computerUse', () => ({
|
|
computerUseApi: {
|
|
openSettings: openSettingsMock,
|
|
},
|
|
}))
|
|
|
|
import { useChatStore } from '../../stores/chatStore'
|
|
import { useSettingsStore } from '../../stores/settingsStore'
|
|
import { ComputerUsePermissionModal } from './ComputerUsePermissionModal'
|
|
|
|
describe('ComputerUsePermissionModal', () => {
|
|
beforeEach(() => {
|
|
sendMock.mockReset()
|
|
openSettingsMock.mockReset()
|
|
useSettingsStore.setState({ locale: 'en' })
|
|
useChatStore.setState({ sessions: {} })
|
|
})
|
|
|
|
it('returns a full approval payload for resolved apps and requested flags', () => {
|
|
render(
|
|
<ComputerUsePermissionModal
|
|
sessionId="session-1"
|
|
request={{
|
|
requestId: 'cu-1',
|
|
reason: 'Open Finder and inspect a file',
|
|
apps: [
|
|
{
|
|
requestedName: 'Finder',
|
|
resolved: {
|
|
bundleId: 'com.apple.finder',
|
|
displayName: 'Finder',
|
|
},
|
|
isSentinel: false,
|
|
alreadyGranted: false,
|
|
proposedTier: 'full',
|
|
},
|
|
{
|
|
requestedName: 'Missing App',
|
|
isSentinel: false,
|
|
alreadyGranted: false,
|
|
proposedTier: 'full',
|
|
},
|
|
],
|
|
requestedFlags: {
|
|
clipboardRead: true,
|
|
systemKeyCombos: true,
|
|
},
|
|
screenshotFiltering: 'native',
|
|
willHide: [{ bundleId: 'com.apple.TextEdit', displayName: 'TextEdit' }],
|
|
autoUnhideEnabled: true,
|
|
}}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Allow for session' }))
|
|
|
|
expect(sendMock).toHaveBeenCalledTimes(1)
|
|
expect(sendMock).toHaveBeenCalledWith('session-1', {
|
|
type: 'computer_use_permission_response',
|
|
requestId: 'cu-1',
|
|
response: {
|
|
granted: [
|
|
expect.objectContaining({
|
|
bundleId: 'com.apple.finder',
|
|
displayName: 'Finder',
|
|
tier: 'full',
|
|
}),
|
|
],
|
|
denied: [
|
|
{
|
|
bundleId: 'Missing App',
|
|
reason: 'not_installed',
|
|
},
|
|
],
|
|
flags: {
|
|
clipboardRead: true,
|
|
clipboardWrite: false,
|
|
systemKeyCombos: true,
|
|
},
|
|
userConsented: true,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('opens System Settings from the macOS permission panel', async () => {
|
|
render(
|
|
<ComputerUsePermissionModal
|
|
sessionId="session-1"
|
|
request={{
|
|
requestId: 'cu-1',
|
|
reason: '',
|
|
apps: [],
|
|
requestedFlags: {},
|
|
screenshotFiltering: 'native',
|
|
tccState: {
|
|
accessibility: false,
|
|
screenRecording: true,
|
|
},
|
|
}}
|
|
/>,
|
|
)
|
|
|
|
await act(async () => {
|
|
fireEvent.click(screen.getByRole('button', { name: 'Open Accessibility' }))
|
|
})
|
|
|
|
expect(openSettingsMock).toHaveBeenCalledWith('Privacy_Accessibility')
|
|
})
|
|
})
|