mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
The workspace panel now treats an empty changed-files status as a browsing case, so opening the panel shows the project tree instead of an empty changed-files view. The panel header controls were also reduced to keep the right workspace rail visually proportional in compact layouts. Constraint: Users still need to manually inspect the empty changed-files view when they explicitly choose it. Rejected: Always render the empty changed-files state | it makes the opened workspace feel broken when no files changed. Confidence: high Scope-risk: narrow Directive: Do not auto-switch the workspace view after a user has explicitly selected a workspace view. Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx src/stores/workspacePanelStore.test.ts Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Not-tested: Browser E2E visual screenshot pass was not rerun for this small header sizing change.
637 lines
23 KiB
TypeScript
637 lines
23 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
type HoistedVi = typeof vi & {
|
|
hoisted?: <T>(factory: () => T) => T
|
|
}
|
|
|
|
if (typeof (vi as HoistedVi).hoisted !== 'function') {
|
|
;(vi as HoistedVi).hoisted = <T>(factory: () => T) => factory()
|
|
}
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
getWorkspaceStatusMock: vi.fn(),
|
|
getWorkspaceTreeMock: vi.fn(),
|
|
getWorkspaceFileMock: vi.fn(),
|
|
getWorkspaceDiffMock: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('../api/sessions', () => ({
|
|
sessionsApi: {
|
|
getWorkspaceStatus: mocks.getWorkspaceStatusMock,
|
|
getWorkspaceTree: mocks.getWorkspaceTreeMock,
|
|
getWorkspaceFile: mocks.getWorkspaceFileMock,
|
|
getWorkspaceDiff: mocks.getWorkspaceDiffMock,
|
|
},
|
|
}))
|
|
|
|
import { sessionsApi } from '../api/sessions'
|
|
import {
|
|
WORKSPACE_PANEL_DEFAULT_WIDTH,
|
|
WORKSPACE_PANEL_MAX_WIDTH,
|
|
WORKSPACE_PANEL_MIN_WIDTH,
|
|
getWorkspacePreviewTabId,
|
|
useWorkspacePanelStore,
|
|
} from './workspacePanelStore'
|
|
|
|
function deferred<T>() {
|
|
let resolve!: (value: T) => void
|
|
let reject!: (reason?: unknown) => void
|
|
const promise = new Promise<T>((res, rej) => {
|
|
resolve = res
|
|
reject = rej
|
|
})
|
|
return { promise, resolve, reject }
|
|
}
|
|
|
|
describe('workspacePanelStore', () => {
|
|
const initialState = useWorkspacePanelStore.getInitialState()
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
useWorkspacePanelStore.setState(initialState, true)
|
|
})
|
|
|
|
afterEach(() => {
|
|
useWorkspacePanelStore.setState(initialState, true)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('uses hoisted session api mocks', () => {
|
|
expect(sessionsApi.getWorkspaceStatus).toBe(mocks.getWorkspaceStatusMock)
|
|
expect(sessionsApi.getWorkspaceTree).toBe(mocks.getWorkspaceTreeMock)
|
|
expect(sessionsApi.getWorkspaceFile).toBe(mocks.getWorkspaceFileMock)
|
|
expect(sessionsApi.getWorkspaceDiff).toBe(mocks.getWorkspaceDiffMock)
|
|
})
|
|
|
|
it('keeps panel open state and active view isolated per session', () => {
|
|
const store = useWorkspacePanelStore.getState()
|
|
|
|
expect(store.isPanelOpen('session-a')).toBe(false)
|
|
expect(store.getActiveView('session-a')).toBe('changed')
|
|
expect(store.width).toBe(WORKSPACE_PANEL_DEFAULT_WIDTH)
|
|
|
|
store.openPanel('session-a')
|
|
store.setActiveView('session-a', 'all')
|
|
|
|
expect(useWorkspacePanelStore.getState().isPanelOpen('session-a')).toBe(true)
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-a')).toBe('all')
|
|
expect(useWorkspacePanelStore.getState().isPanelOpen('session-b')).toBe(false)
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-b')).toBe('changed')
|
|
|
|
store.togglePanel('session-b')
|
|
expect(useWorkspacePanelStore.getState().isPanelOpen('session-b')).toBe(true)
|
|
expect(useWorkspacePanelStore.getState().isPanelOpen('session-a')).toBe(true)
|
|
|
|
store.closePanel('session-a')
|
|
expect(useWorkspacePanelStore.getState().isPanelOpen('session-a')).toBe(false)
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-a')).toBe('all')
|
|
|
|
store.setWidth(120)
|
|
expect(useWorkspacePanelStore.getState().width).toBe(WORKSPACE_PANEL_MIN_WIDTH)
|
|
store.setWidth(1200)
|
|
expect(useWorkspacePanelStore.getState().width).toBe(WORKSPACE_PANEL_MAX_WIDTH)
|
|
})
|
|
|
|
it('loads workspace status successfully', async () => {
|
|
mocks.getWorkspaceStatusMock.mockResolvedValue({
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'main',
|
|
isGitRepo: true,
|
|
changedFiles: [
|
|
{
|
|
path: 'src/a.ts',
|
|
status: 'modified',
|
|
additions: 3,
|
|
deletions: 1,
|
|
},
|
|
],
|
|
})
|
|
|
|
await useWorkspacePanelStore.getState().loadStatus('session-1')
|
|
|
|
expect(mocks.getWorkspaceStatusMock).toHaveBeenCalledWith('session-1')
|
|
expect(useWorkspacePanelStore.getState().statusBySession['session-1']).toMatchObject({
|
|
branch: 'main',
|
|
changedFiles: [{ path: 'src/a.ts' }],
|
|
})
|
|
expect(useWorkspacePanelStore.getState().loading.statusBySession['session-1']).toBe(false)
|
|
expect(useWorkspacePanelStore.getState().errors.statusBySession['session-1']).toBeNull()
|
|
})
|
|
|
|
it('defaults an empty changed-files status to the all-files view', async () => {
|
|
mocks.getWorkspaceStatusMock.mockResolvedValue({
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'main',
|
|
isGitRepo: true,
|
|
changedFiles: [],
|
|
})
|
|
|
|
useWorkspacePanelStore.getState().openPanel('session-empty-changes')
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-empty-changes')).toBe('changed')
|
|
|
|
await useWorkspacePanelStore.getState().loadStatus('session-empty-changes')
|
|
|
|
expect(useWorkspacePanelStore.getState().statusBySession['session-empty-changes']?.changedFiles).toEqual([])
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-empty-changes')).toBe('all')
|
|
})
|
|
|
|
it('keeps the changed-files view when status contains changes', async () => {
|
|
mocks.getWorkspaceStatusMock.mockResolvedValue({
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'main',
|
|
isGitRepo: true,
|
|
changedFiles: [
|
|
{
|
|
path: 'src/app.ts',
|
|
status: 'modified',
|
|
additions: 2,
|
|
deletions: 1,
|
|
},
|
|
],
|
|
})
|
|
|
|
useWorkspacePanelStore.getState().openPanel('session-has-changes')
|
|
await useWorkspacePanelStore.getState().loadStatus('session-has-changes')
|
|
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-has-changes')).toBe('changed')
|
|
})
|
|
|
|
it('does not override an explicit changed-files selection when status is empty', async () => {
|
|
mocks.getWorkspaceStatusMock.mockResolvedValue({
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'main',
|
|
isGitRepo: true,
|
|
changedFiles: [],
|
|
})
|
|
|
|
useWorkspacePanelStore.getState().openPanel('session-explicit-changed')
|
|
useWorkspacePanelStore.getState().setActiveView('session-explicit-changed', 'changed')
|
|
await useWorkspacePanelStore.getState().loadStatus('session-explicit-changed')
|
|
|
|
expect(useWorkspacePanelStore.getState().getActiveView('session-explicit-changed')).toBe('changed')
|
|
})
|
|
|
|
it('captures workspace status request errors', async () => {
|
|
mocks.getWorkspaceStatusMock.mockRejectedValue(new Error('status failed'))
|
|
|
|
await useWorkspacePanelStore.getState().loadStatus('session-err')
|
|
|
|
expect(useWorkspacePanelStore.getState().statusBySession['session-err']).toBeUndefined()
|
|
expect(useWorkspacePanelStore.getState().loading.statusBySession['session-err']).toBe(false)
|
|
expect(useWorkspacePanelStore.getState().errors.statusBySession['session-err']).toBe('status failed')
|
|
})
|
|
|
|
it('lazily loads tree nodes when expanding and reuses cached results', async () => {
|
|
mocks.getWorkspaceTreeMock.mockResolvedValue({
|
|
state: 'ok',
|
|
path: '',
|
|
entries: [
|
|
{ name: 'src', path: 'src', isDirectory: true },
|
|
{ name: 'README.md', path: 'README.md', isDirectory: false },
|
|
],
|
|
})
|
|
|
|
await useWorkspacePanelStore.getState().toggleTreeNode('session-tree', '')
|
|
|
|
expect(mocks.getWorkspaceTreeMock).toHaveBeenCalledTimes(1)
|
|
expect(mocks.getWorkspaceTreeMock).toHaveBeenCalledWith('session-tree', '')
|
|
expect(useWorkspacePanelStore.getState().expandedPathsBySession['session-tree']).toEqual([''])
|
|
expect(useWorkspacePanelStore.getState().treeBySessionPath['session-tree']?.['']).toMatchObject({
|
|
entries: [{ path: 'src' }, { path: 'README.md' }],
|
|
})
|
|
|
|
await useWorkspacePanelStore.getState().toggleTreeNode('session-tree', '')
|
|
expect(useWorkspacePanelStore.getState().expandedPathsBySession['session-tree']).toEqual([])
|
|
|
|
await useWorkspacePanelStore.getState().toggleTreeNode('session-tree', '')
|
|
expect(mocks.getWorkspaceTreeMock).toHaveBeenCalledTimes(1)
|
|
expect(useWorkspacePanelStore.getState().expandedPathsBySession['session-tree']).toEqual([''])
|
|
})
|
|
|
|
it('ignores stale tree responses for the same session path', async () => {
|
|
const first = deferred<{ state: 'ok'; path: string; entries: Array<{ name: string; path: string; isDirectory: boolean }> }>()
|
|
const second = deferred<{ state: 'ok'; path: string; entries: Array<{ name: string; path: string; isDirectory: boolean }> }>()
|
|
|
|
mocks.getWorkspaceTreeMock
|
|
.mockReturnValueOnce(first.promise)
|
|
.mockReturnValueOnce(second.promise)
|
|
|
|
const firstLoad = useWorkspacePanelStore.getState().loadTree('session-tree-race', 'src')
|
|
const secondLoad = useWorkspacePanelStore.getState().loadTree('session-tree-race', 'src')
|
|
|
|
second.resolve({
|
|
state: 'ok',
|
|
path: 'src',
|
|
entries: [{ name: 'new.ts', path: 'src/new.ts', isDirectory: false }],
|
|
})
|
|
await secondLoad
|
|
|
|
first.resolve({
|
|
state: 'ok',
|
|
path: 'src',
|
|
entries: [{ name: 'old.ts', path: 'src/old.ts', isDirectory: false }],
|
|
})
|
|
await firstLoad
|
|
|
|
expect(useWorkspacePanelStore.getState().treeBySessionPath['session-tree-race']?.src?.entries).toEqual([
|
|
{ name: 'new.ts', path: 'src/new.ts', isDirectory: false },
|
|
])
|
|
})
|
|
|
|
it('opens preview tabs, supports multiple kinds, and reuses duplicates without persistence', async () => {
|
|
const storage = typeof globalThis.localStorage === 'undefined' ? null : globalThis.localStorage
|
|
const setItemSpy = storage ? vi.spyOn(storage, 'setItem') : null
|
|
|
|
mocks.getWorkspaceFileMock.mockResolvedValue({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
content: 'export const a = 1',
|
|
language: 'typescript',
|
|
size: 18,
|
|
})
|
|
mocks.getWorkspaceDiffMock.mockResolvedValue({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
diff: '@@ -1 +1 @@',
|
|
})
|
|
|
|
useWorkspacePanelStore.getState().openPanel('session-preview')
|
|
await useWorkspacePanelStore.getState().openPreview('session-preview', 'src/a.ts', 'file')
|
|
await useWorkspacePanelStore.getState().openPreview('session-preview', 'src/a.ts', 'diff')
|
|
await useWorkspacePanelStore.getState().openPreview('session-preview', 'src/a.ts', 'file')
|
|
|
|
expect(mocks.getWorkspaceFileMock).toHaveBeenCalledTimes(1)
|
|
expect(mocks.getWorkspaceDiffMock).toHaveBeenCalledTimes(1)
|
|
|
|
const tabs = useWorkspacePanelStore.getState().previewTabsBySession['session-preview']
|
|
expect(tabs).toBeDefined()
|
|
expect(tabs).toHaveLength(2)
|
|
expect(tabs![0]).toMatchObject({
|
|
id: 'file:src/a.ts',
|
|
kind: 'file',
|
|
path: 'src/a.ts',
|
|
content: 'export const a = 1',
|
|
language: 'typescript',
|
|
})
|
|
expect(tabs![1]).toMatchObject({
|
|
id: 'diff:src/a.ts',
|
|
kind: 'diff',
|
|
path: 'src/a.ts',
|
|
diff: '@@ -1 +1 @@',
|
|
})
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-preview']).toBe('file:src/a.ts')
|
|
if (setItemSpy) {
|
|
expect(setItemSpy).not.toHaveBeenCalled()
|
|
} else {
|
|
expect(storage).toBeNull()
|
|
}
|
|
})
|
|
|
|
it('closes exact tab id and preserves sibling preview for the same path', async () => {
|
|
mocks.getWorkspaceFileMock.mockResolvedValue({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
content: 'export const a = 1',
|
|
language: 'typescript',
|
|
size: 18,
|
|
})
|
|
mocks.getWorkspaceDiffMock.mockResolvedValue({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
diff: '@@ -1 +1 @@',
|
|
})
|
|
|
|
await useWorkspacePanelStore.getState().openPreview('session-close-path', 'src/a.ts', 'file')
|
|
await useWorkspacePanelStore.getState().openPreview('session-close-path', 'src/a.ts', 'diff')
|
|
|
|
useWorkspacePanelStore.getState().closePreview('session-close-path', 'diff:src/a.ts')
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-close-path']).toEqual([
|
|
expect.objectContaining({ id: 'file:src/a.ts' }),
|
|
])
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-close-path']).toBe('file:src/a.ts')
|
|
})
|
|
|
|
it('chooses the next sensible active preview when closing the current tab', async () => {
|
|
mocks.getWorkspaceFileMock
|
|
.mockResolvedValueOnce({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
content: 'a',
|
|
language: 'typescript',
|
|
size: 1,
|
|
})
|
|
.mockResolvedValueOnce({
|
|
state: 'ok',
|
|
path: 'src/b.ts',
|
|
content: 'b',
|
|
language: 'typescript',
|
|
size: 1,
|
|
})
|
|
.mockResolvedValueOnce({
|
|
state: 'ok',
|
|
path: 'src/c.ts',
|
|
content: 'c',
|
|
language: 'typescript',
|
|
size: 1,
|
|
})
|
|
|
|
await useWorkspacePanelStore.getState().openPreview('session-close', 'src/a.ts', 'file')
|
|
await useWorkspacePanelStore.getState().openPreview('session-close', 'src/b.ts', 'file')
|
|
await useWorkspacePanelStore.getState().openPreview('session-close', 'src/c.ts', 'file')
|
|
|
|
useWorkspacePanelStore.getState().closePreview('session-close', 'file:src/b.ts')
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-close']).toMatchObject([
|
|
{ id: 'file:src/a.ts' },
|
|
{ id: 'file:src/c.ts' },
|
|
])
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-close']).toBe('file:src/c.ts')
|
|
|
|
useWorkspacePanelStore.getState().closePreview('session-close', 'file:src/c.ts')
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-close']).toBe('file:src/a.ts')
|
|
})
|
|
|
|
it('closes preview tabs by context-menu scope', () => {
|
|
useWorkspacePanelStore.setState((state) => ({
|
|
...state,
|
|
previewTabsBySession: {
|
|
...state.previewTabsBySession,
|
|
'session-preview-scope': [
|
|
{ id: 'file:a.ts', path: 'a.ts', kind: 'file', title: 'a.ts', state: 'ok', language: 'typescript', size: 1 },
|
|
{ id: 'file:b.ts', path: 'b.ts', kind: 'file', title: 'b.ts', state: 'ok', language: 'typescript', size: 1 },
|
|
{ id: 'file:c.ts', path: 'c.ts', kind: 'file', title: 'c.ts', state: 'ok', language: 'typescript', size: 1 },
|
|
{ id: 'file:d.ts', path: 'd.ts', kind: 'file', title: 'd.ts', state: 'ok', language: 'typescript', size: 1 },
|
|
],
|
|
},
|
|
activePreviewTabIdBySession: {
|
|
...state.activePreviewTabIdBySession,
|
|
'session-preview-scope': 'file:d.ts',
|
|
},
|
|
loading: {
|
|
...state.loading,
|
|
previewByTabId: {
|
|
...state.loading.previewByTabId,
|
|
'session-preview-scope::file:c.ts': true,
|
|
'session-preview-scope::file:d.ts': true,
|
|
},
|
|
},
|
|
errors: {
|
|
...state.errors,
|
|
previewByTabId: {
|
|
...state.errors.previewByTabId,
|
|
'session-preview-scope::file:c.ts': 'loading',
|
|
'session-preview-scope::file:d.ts': 'loading',
|
|
},
|
|
},
|
|
}))
|
|
|
|
useWorkspacePanelStore.getState().closePreviewTabs('session-preview-scope', 'file:b.ts', 'right')
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-preview-scope']).toMatchObject([
|
|
{ id: 'file:a.ts' },
|
|
{ id: 'file:b.ts' },
|
|
])
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-preview-scope']).toBe('file:b.ts')
|
|
expect(useWorkspacePanelStore.getState().loading.previewByTabId['session-preview-scope::file:c.ts']).toBeUndefined()
|
|
expect(useWorkspacePanelStore.getState().errors.previewByTabId['session-preview-scope::file:d.ts']).toBeUndefined()
|
|
|
|
useWorkspacePanelStore.getState().closePreviewTabs('session-preview-scope', 'file:b.ts', 'others')
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-preview-scope']).toMatchObject([
|
|
{ id: 'file:b.ts' },
|
|
])
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-preview-scope']).toBe('file:b.ts')
|
|
|
|
useWorkspacePanelStore.getState().closePreviewTabs('session-preview-scope', 'file:b.ts', 'all')
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-preview-scope']).toBeUndefined()
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-preview-scope']).toBeNull()
|
|
})
|
|
|
|
it('ignores stale status responses for the same session', async () => {
|
|
const first = deferred<{
|
|
state: 'ok'
|
|
workDir: string
|
|
repoName: string | null
|
|
branch: string | null
|
|
isGitRepo: boolean
|
|
changedFiles: []
|
|
error?: string
|
|
}>()
|
|
const second = deferred<{
|
|
state: 'ok'
|
|
workDir: string
|
|
repoName: string | null
|
|
branch: string | null
|
|
isGitRepo: boolean
|
|
changedFiles: []
|
|
error?: string
|
|
}>()
|
|
|
|
mocks.getWorkspaceStatusMock
|
|
.mockReturnValueOnce(first.promise)
|
|
.mockReturnValueOnce(second.promise)
|
|
|
|
const firstLoad = useWorkspacePanelStore.getState().loadStatus('session-race')
|
|
const secondLoad = useWorkspacePanelStore.getState().loadStatus('session-race')
|
|
|
|
second.resolve({
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'new',
|
|
isGitRepo: true,
|
|
changedFiles: [],
|
|
})
|
|
await secondLoad
|
|
|
|
first.resolve({
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'old',
|
|
isGitRepo: true,
|
|
changedFiles: [],
|
|
})
|
|
await firstLoad
|
|
|
|
expect(useWorkspacePanelStore.getState().statusBySession['session-race']?.branch).toBe('new')
|
|
})
|
|
|
|
it('ignores stale preview responses after close and reopen', async () => {
|
|
const first = deferred<{ state: 'ok'; path: string; content: string; language: string; size: number }>()
|
|
const second = deferred<{ state: 'ok'; path: string; content: string; language: string; size: number }>()
|
|
|
|
mocks.getWorkspaceFileMock
|
|
.mockReturnValueOnce(first.promise)
|
|
.mockReturnValueOnce(second.promise)
|
|
|
|
const firstOpen = useWorkspacePanelStore.getState().openPreview('session-preview-race', 'src/a.ts', 'file')
|
|
useWorkspacePanelStore.getState().closePreview('session-preview-race', 'file:src/a.ts')
|
|
const secondOpen = useWorkspacePanelStore.getState().openPreview('session-preview-race', 'src/a.ts', 'file')
|
|
|
|
second.resolve({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
content: 'new',
|
|
language: 'typescript',
|
|
size: 3,
|
|
})
|
|
await secondOpen
|
|
|
|
first.resolve({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
content: 'old',
|
|
language: 'typescript',
|
|
size: 3,
|
|
})
|
|
await firstOpen
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-preview-race']).toEqual([
|
|
expect.objectContaining({ id: 'file:src/a.ts', content: 'new' }),
|
|
])
|
|
})
|
|
|
|
it('does not recreate loading or error state when a loading preview is closed before completion', async () => {
|
|
const pending = deferred<{ state: 'ok'; path: string; diff: string }>()
|
|
const tabId = getWorkspacePreviewTabId('src/a.ts', 'diff')
|
|
const previewKey = `session-preview-close::${tabId}`
|
|
|
|
mocks.getWorkspaceDiffMock.mockReturnValueOnce(pending.promise)
|
|
|
|
const openPromise = useWorkspacePanelStore.getState().openPreview('session-preview-close', 'src/a.ts', 'diff')
|
|
useWorkspacePanelStore.getState().closePreview('session-preview-close', tabId)
|
|
|
|
pending.resolve({
|
|
state: 'ok',
|
|
path: 'src/a.ts',
|
|
diff: '@@ -1 +1 @@',
|
|
})
|
|
await openPromise
|
|
|
|
expect(useWorkspacePanelStore.getState().previewTabsBySession['session-preview-close']).toBeUndefined()
|
|
expect(useWorkspacePanelStore.getState().activePreviewTabIdBySession['session-preview-close']).toBeNull()
|
|
expect(useWorkspacePanelStore.getState().loading.previewByTabId[previewKey]).toBeUndefined()
|
|
expect(useWorkspacePanelStore.getState().errors.previewByTabId[previewKey]).toBeUndefined()
|
|
})
|
|
|
|
it('clears session UI and cached data for clearSession and resetSessionUi', () => {
|
|
useWorkspacePanelStore.setState((state) => ({
|
|
...state,
|
|
panelBySession: {
|
|
'session-clear': { isOpen: true, activeView: 'all' },
|
|
'session-reset': { isOpen: true, activeView: 'all' },
|
|
},
|
|
statusBySession: {
|
|
'session-clear': {
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'main',
|
|
isGitRepo: true,
|
|
changedFiles: [],
|
|
},
|
|
'session-reset': {
|
|
state: 'ok',
|
|
workDir: '/repo',
|
|
repoName: 'repo',
|
|
branch: 'main',
|
|
isGitRepo: true,
|
|
changedFiles: [],
|
|
},
|
|
},
|
|
expandedPathsBySession: {
|
|
'session-clear': ['src'],
|
|
'session-reset': ['src'],
|
|
},
|
|
treeBySessionPath: {
|
|
'session-clear': {
|
|
src: { state: 'ok', path: 'src', entries: [] },
|
|
},
|
|
'session-reset': {
|
|
src: { state: 'ok', path: 'src', entries: [] },
|
|
},
|
|
},
|
|
previewTabsBySession: {
|
|
'session-clear': [
|
|
{ id: 'file:src/a.ts', path: 'src/a.ts', kind: 'file', title: 'a.ts' },
|
|
],
|
|
'session-reset': [
|
|
{ id: 'file:src/b.ts', path: 'src/b.ts', kind: 'file', title: 'b.ts' },
|
|
],
|
|
},
|
|
activePreviewTabIdBySession: {
|
|
'session-clear': 'file:src/a.ts',
|
|
'session-reset': 'file:src/b.ts',
|
|
},
|
|
loading: {
|
|
statusBySession: {
|
|
'session-clear': true,
|
|
'session-reset': true,
|
|
},
|
|
treeBySessionPath: {
|
|
'session-clear::src': true,
|
|
'session-reset::src': true,
|
|
},
|
|
previewByTabId: {
|
|
'session-clear::file:src/a.ts': true,
|
|
'session-reset::file:src/b.ts': true,
|
|
},
|
|
},
|
|
errors: {
|
|
statusBySession: {
|
|
'session-clear': 'bad',
|
|
'session-reset': 'bad',
|
|
},
|
|
treeBySessionPath: {
|
|
'session-clear::src': 'bad',
|
|
'session-reset::src': 'bad',
|
|
},
|
|
previewByTabId: {
|
|
'session-clear::file:src/a.ts': 'bad',
|
|
'session-reset::file:src/b.ts': 'bad',
|
|
},
|
|
},
|
|
}))
|
|
|
|
useWorkspacePanelStore.getState().clearSession('session-clear')
|
|
useWorkspacePanelStore.getState().resetSessionUi('session-reset')
|
|
|
|
const state = useWorkspacePanelStore.getState()
|
|
expect(state.panelBySession['session-clear']).toBeUndefined()
|
|
expect(state.panelBySession['session-reset']).toBeUndefined()
|
|
expect(state.statusBySession['session-clear']).toBeUndefined()
|
|
expect(state.statusBySession['session-reset']).toBeUndefined()
|
|
expect(state.expandedPathsBySession['session-clear']).toBeUndefined()
|
|
expect(state.expandedPathsBySession['session-reset']).toBeUndefined()
|
|
expect(state.treeBySessionPath['session-clear']).toBeUndefined()
|
|
expect(state.treeBySessionPath['session-reset']).toBeUndefined()
|
|
expect(state.previewTabsBySession['session-clear']).toBeUndefined()
|
|
expect(state.previewTabsBySession['session-reset']).toBeUndefined()
|
|
expect(state.activePreviewTabIdBySession['session-clear']).toBeUndefined()
|
|
expect(state.activePreviewTabIdBySession['session-reset']).toBeUndefined()
|
|
expect(state.loading.statusBySession['session-clear']).toBeUndefined()
|
|
expect(state.loading.statusBySession['session-reset']).toBeUndefined()
|
|
expect(state.loading.treeBySessionPath['session-clear::src']).toBeUndefined()
|
|
expect(state.loading.treeBySessionPath['session-reset::src']).toBeUndefined()
|
|
expect(state.loading.previewByTabId['session-clear::file:src/a.ts']).toBeUndefined()
|
|
expect(state.loading.previewByTabId['session-reset::file:src/b.ts']).toBeUndefined()
|
|
expect(state.errors.statusBySession['session-clear']).toBeUndefined()
|
|
expect(state.errors.statusBySession['session-reset']).toBeUndefined()
|
|
expect(state.errors.treeBySessionPath['session-clear::src']).toBeUndefined()
|
|
expect(state.errors.treeBySessionPath['session-reset::src']).toBeUndefined()
|
|
expect(state.errors.previewByTabId['session-clear::file:src/a.ts']).toBeUndefined()
|
|
expect(state.errors.previewByTabId['session-reset::file:src/b.ts']).toBeUndefined()
|
|
})
|
|
})
|