mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
fix(desktop): keep cleared session metadata in sync
Reset session messageCount when /clear is confirmed so active headers and sidebars do not keep stale message totals after the transcript is cleared. Also wait for the restored project chip in the live desktop smoke lane before filling the prompt, preventing the test from racing against EmptySession and creating a default-home session. Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/stores/sessionStore.test.ts Tested: bun test scripts/quality-gate/desktop-smoke/execute.test.ts Tested: bun run check:desktop Tested: SKIP_INSTALL=1 MAC_TARGETS=zip desktop/scripts/build-macos-arm64.sh Tested: bun run quality:smoke --provider-model codingplan:main:codingplan-main Not-tested: bun run verify Confidence: high Scope-risk: narrow
This commit is contained in:
parent
3edee33b54
commit
782c32c1fc
@ -19,6 +19,7 @@ const {
|
||||
updateTabTitleMock,
|
||||
updateTabStatusMock,
|
||||
updateSessionTitleMock,
|
||||
updateSessionMessageCountMock,
|
||||
updateSessionPermissionModeMock,
|
||||
sessionStoreSnapshot,
|
||||
cliTaskStoreSnapshot,
|
||||
@ -39,6 +40,7 @@ const {
|
||||
updateTabTitleMock: vi.fn(),
|
||||
updateTabStatusMock: vi.fn(),
|
||||
updateSessionTitleMock: vi.fn(),
|
||||
updateSessionMessageCountMock: vi.fn(),
|
||||
updateSessionPermissionModeMock: vi.fn(),
|
||||
sessionStoreSnapshot: {
|
||||
sessions: [] as Array<{
|
||||
@ -105,6 +107,7 @@ vi.mock('./sessionStore', () => ({
|
||||
getState: () => ({
|
||||
sessions: sessionStoreSnapshot.sessions,
|
||||
updateSessionTitle: updateSessionTitleMock,
|
||||
updateSessionMessageCount: updateSessionMessageCountMock,
|
||||
updateSessionPermissionMode: updateSessionPermissionModeMock,
|
||||
}),
|
||||
},
|
||||
@ -276,6 +279,7 @@ describe('chatStore history mapping', () => {
|
||||
updateTabTitleMock.mockReset()
|
||||
updateTabStatusMock.mockReset()
|
||||
updateSessionTitleMock.mockReset()
|
||||
updateSessionMessageCountMock.mockReset()
|
||||
vi.mocked(sessionsApi.getMessages).mockReset()
|
||||
vi.mocked(sessionsApi.getMessages).mockResolvedValue({ messages: [] })
|
||||
sessionStoreSnapshot.sessions = []
|
||||
@ -3000,6 +3004,8 @@ describe('chatStore history mapping', () => {
|
||||
expect(session?.streamingResponseChars).toBe(0)
|
||||
expect(session?.slashCommands).toEqual([])
|
||||
expect(clearTasksMock).toHaveBeenCalledWith(TEST_SESSION_ID)
|
||||
expect(updateSessionTitleMock).toHaveBeenCalledWith(TEST_SESSION_ID, 'New Session')
|
||||
expect(updateSessionMessageCountMock).toHaveBeenCalledWith(TEST_SESSION_ID, 0)
|
||||
|
||||
vi.advanceTimersByTime(60)
|
||||
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.streamingText).toBe('')
|
||||
|
||||
@ -2171,6 +2171,7 @@ export const useChatStore = create<ChatStore>((set, get) => ({
|
||||
clearPendingToolParentUseIds(sessionId)
|
||||
useCLITaskStore.getState().clearTasks(sessionId)
|
||||
useSessionStore.getState().updateSessionTitle(sessionId, 'New Session')
|
||||
useSessionStore.getState().updateSessionMessageCount(sessionId, 0)
|
||||
useTabStore.getState().updateTabTitle(sessionId, 'New Session')
|
||||
useTabStore.getState().updateTabStatus(sessionId, 'idle')
|
||||
}
|
||||
|
||||
@ -165,6 +165,21 @@ describe('sessionStore', () => {
|
||||
expect(useTabStore.getState().tabs[0]?.title).toBe('使用bash写一个shell,随便写点什么东西')
|
||||
})
|
||||
|
||||
it('updates a session message count without changing other metadata', () => {
|
||||
useSessionStore.setState({
|
||||
sessions: [makeSession('session-count-1', '2026-05-07T00:00:00.000Z', 'Working session')],
|
||||
})
|
||||
|
||||
useSessionStore.getState().updateSessionMessageCount('session-count-1', 0)
|
||||
|
||||
expect(useSessionStore.getState().sessions[0]).toMatchObject({
|
||||
id: 'session-count-1',
|
||||
title: 'Working session',
|
||||
messageCount: 0,
|
||||
workDir: '/workspace/project',
|
||||
})
|
||||
})
|
||||
|
||||
it('requests a large default session page for noisy history directories', async () => {
|
||||
listMock.mockResolvedValue({
|
||||
sessions: [makeSession('session-newest', '2026-05-07T00:00:03.000Z')],
|
||||
|
||||
@ -47,6 +47,7 @@ type SessionStore = {
|
||||
clearSessionSelection: () => void
|
||||
renameSession: (id: string, title: string) => Promise<void>
|
||||
updateSessionTitle: (id: string, title: string) => void
|
||||
updateSessionMessageCount: (id: string, messageCount: number) => void
|
||||
updateSessionPermissionMode: (id: string, mode: PermissionMode) => void
|
||||
setActiveSession: (id: string | null) => void
|
||||
}
|
||||
@ -219,6 +220,14 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
|
||||
}))
|
||||
},
|
||||
|
||||
updateSessionMessageCount: (id, messageCount) => {
|
||||
set((s) => ({
|
||||
sessions: s.sessions.map((session) =>
|
||||
session.id === id ? { ...session, messageCount } : session,
|
||||
),
|
||||
}))
|
||||
},
|
||||
|
||||
updateSessionPermissionMode: (id, mode) => {
|
||||
set((s) => ({
|
||||
sessions: s.sessions.map((session) =>
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { buildDesktopSmokeBrowserEnv, resolveDesktopSmokeRuntimeSelection } from './execute'
|
||||
import {
|
||||
buildDesktopSmokeBrowserEnv,
|
||||
desktopSmokeTextShowsProject,
|
||||
resolveDesktopSmokeRuntimeSelection,
|
||||
} from './execute'
|
||||
|
||||
describe('desktop smoke runtime selection', () => {
|
||||
test('lets current-runtime use the desktop default active provider', () => {
|
||||
@ -54,3 +58,20 @@ describe('desktop smoke browser environment', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('desktop smoke restored session detection', () => {
|
||||
test('waits for the target project chip instead of the first empty-session textarea', () => {
|
||||
expect(desktopSmokeTextShowsProject([
|
||||
'新建会话',
|
||||
'随便问点什么...',
|
||||
'folder_open 选择项目...',
|
||||
].join('\n'), 'project')).toBe(false)
|
||||
|
||||
expect(desktopSmokeTextShowsProject([
|
||||
'Untitled Session',
|
||||
'让 Claude 编辑、调试或解释代码...',
|
||||
'folder',
|
||||
'project',
|
||||
].join('\n'), 'project')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@ import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, wr
|
||||
import { mkdtemp } from 'node:fs/promises'
|
||||
import { createServer } from 'node:net'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { basename, join } from 'node:path'
|
||||
import treeKill from 'tree-kill'
|
||||
import { changedFiles, writeDiffPatch } from '../baseline/execute'
|
||||
import type { BaselineTarget, LaneResult } from '../types'
|
||||
@ -16,7 +16,7 @@ const PROMPT = [
|
||||
'When the tests pass, briefly say done.',
|
||||
].join(' ')
|
||||
|
||||
type DesktopSmokeStage = 'open' | 'eval' | 'reload' | 'wait' | 'screenshot' | 'fill' | 'press' | 'verify'
|
||||
type DesktopSmokeStage = 'open' | 'eval' | 'reload' | 'wait' | 'session-ready' | 'screenshot' | 'fill' | 'press' | 'verify'
|
||||
|
||||
type DesktopSmokeFailureContext = {
|
||||
stage: DesktopSmokeStage
|
||||
@ -334,6 +334,40 @@ async function waitForVerifiedProject(
|
||||
throw new Error(`Timed out waiting for desktop project verification: ${lastVerificationError}`)
|
||||
}
|
||||
|
||||
export function desktopSmokeTextShowsProject(text: string, projectName: string) {
|
||||
const lines = text
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
return lines.includes(projectName)
|
||||
}
|
||||
|
||||
async function waitForDesktopSmokeSessionReady(
|
||||
browserEnv: Record<string, string>,
|
||||
browserLogPath: string,
|
||||
rootDir: string,
|
||||
projectDir: string,
|
||||
timeoutMs: number,
|
||||
) {
|
||||
const projectName = basename(projectDir)
|
||||
const deadline = Date.now() + timeoutMs
|
||||
let lastText = ''
|
||||
while (Date.now() < deadline) {
|
||||
const body = await runLoggedCommand(agentBrowserCommand(['get', 'text', '#content-area']), {
|
||||
cwd: rootDir,
|
||||
env: browserEnv,
|
||||
logPath: browserLogPath,
|
||||
timeoutMs: 15_000,
|
||||
allowFailure: true,
|
||||
maxLogChars: 4_000,
|
||||
})
|
||||
lastText = `${body.stdout}\n${body.stderr}`
|
||||
if (desktopSmokeTextShowsProject(lastText, projectName)) return
|
||||
await Bun.sleep(1_000)
|
||||
}
|
||||
throw new Error(`Timed out waiting for desktop smoke session to restore project "${projectName}". Last content text: ${lastText.slice(0, 500)}`)
|
||||
}
|
||||
|
||||
async function verifyProject(originalDir: string, projectDir: string, artifactDir: string) {
|
||||
await writeDiffPatch(originalDir, projectDir, join(artifactDir, 'diff.patch'))
|
||||
const changed = changedFiles(originalDir, projectDir)
|
||||
@ -484,6 +518,21 @@ export async function executeDesktopSmoke(
|
||||
logPath: browserLogPath,
|
||||
timeoutMs: 30_000,
|
||||
}, browserStepContext)
|
||||
await runBrowserStep('session-ready', ['get', 'text', '#content-area'], {
|
||||
cwd: rootDir,
|
||||
env: browserEnv,
|
||||
logPath: browserLogPath,
|
||||
timeoutMs: 15_000,
|
||||
allowFailure: true,
|
||||
maxLogChars: 4_000,
|
||||
}, browserStepContext)
|
||||
await waitForDesktopSmokeSessionReady(
|
||||
browserEnv,
|
||||
browserLogPath,
|
||||
rootDir,
|
||||
projectDir,
|
||||
30_000,
|
||||
)
|
||||
await runBrowserStep('screenshot', ['screenshot', join(artifactDir, 'initial.png')], {
|
||||
cwd: rootDir,
|
||||
env: browserEnv,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user