98 lines
7.0 KiB
TypeScript
98 lines
7.0 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { existsSync, readFileSync } from 'node:fs'
|
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
import { closeWorkbenchTab, collectWorkbenchArtifacts, emptyWorkbenchSession, openWorkbenchTab, reorderWorkbenchTabs,
|
|
workbenchOwner, type WorkbenchTab } from '../src/renderer/src/workbuddy/workbench/workbench-state'
|
|
import { useWorkbenchStore } from '../src/renderer/src/stores/workbench'
|
|
import { useChatStore } from '../src/renderer/src/stores/chat'
|
|
import { useSettingsStore } from '../src/renderer/src/stores/settings'
|
|
import { SourceWorkbench } from '../src/renderer/src/workbuddy/workbench/SourceWorkbench'
|
|
import { BrowserEntryIcon, CloseIcon } from '../src/renderer/src/workbuddy/workbench/SourceWorkbenchIcons'
|
|
import { WorkbenchFileIcon } from '../src/renderer/src/workbuddy/workbench/WorkbenchFileIcon'
|
|
import { convertWorkBuddyJsonl } from '../src/shared/workbuddy-import'
|
|
import type { WorkspaceResource } from '../src/shared/resources'
|
|
|
|
async function main() {
|
|
const tab = (id: string, kind: WorkbenchTab['kind'] = 'file'): WorkbenchTab => ({ id, kind, label: id, owner: 'a' })
|
|
let state = openWorkbenchTab(emptyWorkbenchSession(), tab('blank', 'newTab'))
|
|
state = openWorkbenchTab(state, tab('file-a'))
|
|
assert.deepEqual(state.tabs.map(item => item.id), ['file-a'], 'replace the active blank tab in place')
|
|
state = openWorkbenchTab(state, tab('file-b'))
|
|
state = openWorkbenchTab(state, tab('file-c'))
|
|
state = openWorkbenchTab(state, tab('file-b'))
|
|
assert.equal(state.tabs.length, 3, 'repeat file selection reuses its tab')
|
|
state = closeWorkbenchTab(state, 'file-b')
|
|
assert.equal(state.activeTabId, 'file-c', 'closing active selects its right neighbor')
|
|
state = reorderWorkbenchTabs(state, ['file-c', 'missing', 'file-c'])
|
|
assert.deepEqual(state.tabs.map(item => item.id), ['file-c', 'file-a'])
|
|
assert.equal(state.recent[0].id, 'file-b')
|
|
assert.notEqual(workbenchOwner(null, 1), workbenchOwner(null, 2))
|
|
|
|
let resolveA!: (resource: WorkspaceResource) => void, resolveOld!: (resource: WorkspaceResource) => void
|
|
const requests: Array<{ path: string; sessionId?: string }> = []
|
|
let aReads = 0
|
|
;(globalThis as any).window = { ycode: { resources: { read: (path: string, _range: unknown, sessionId?: string) => {
|
|
requests.push({ path, sessionId })
|
|
if (sessionId === 'a') return new Promise<WorkspaceResource>(resolve => { if (aReads++ === 0) resolveOld = resolve; else resolveA = resolve })
|
|
return Promise.resolve({ kind: 'text', path, name: 'b', content: 'B', firstLine: 1, totalLines: 1, truncated: false })
|
|
} } } }
|
|
const api = useWorkbenchStore.getState()
|
|
const old = api.openResource('a', 'a', 'C:\\space\\file.txt')
|
|
const newer = api.openResource('a', 'a', 'c:/space/file.txt')
|
|
await api.openResource('b', 'b', 'C:/space/file.txt')
|
|
resolveA({ kind: 'text', path: 'c:/space/file.txt', name: 'latest', content: 'LATEST', firstLine: 1, totalLines: 1, truncated: false })
|
|
await newer
|
|
resolveOld({ kind: 'text', path: 'C:/space/file.txt', name: 'old', content: 'STALE', firstLine: 1, totalLines: 1, truncated: false })
|
|
await old
|
|
assert.equal(api.sessions.a, undefined, 'state snapshots are immutable')
|
|
const bucketA = useWorkbenchStore.getState().sessions.a, bucketB = useWorkbenchStore.getState().sessions.b
|
|
assert.equal(bucketA.tabs.length, 1)
|
|
assert.equal(bucketA.tabs[0].label, 'latest')
|
|
assert.equal(bucketB.tabs[0].label, 'b')
|
|
assert.deepEqual(requests.map(request => request.sessionId), ['a', 'a', 'b'])
|
|
api.openView('a', 'fileTree'); api.openView('a', 'fileTree')
|
|
assert.equal(useWorkbenchStore.getState().sessions.a.tabs.filter(item => item.kind === 'fileTree').length, 1)
|
|
api.closeOthers('a', 'fileTree:workspace')
|
|
assert.deepEqual(useWorkbenchStore.getState().sessions.a.tabs.map(item => item.kind), ['fileTree'])
|
|
api.close('a', 'fileTree:workspace')
|
|
assert.equal(useWorkbenchStore.getState().sessions.a.tabs.length, 0)
|
|
api.ensure('a')
|
|
assert.equal(useWorkbenchStore.getState().sessions.a.tabs[0].kind, 'newTab')
|
|
|
|
const actualPath = '.tmp/rea-workbuddy-session-export.json'
|
|
if (existsSync(actualPath)) {
|
|
const evidence = JSON.parse(readFileSync(actualPath, 'utf8'))
|
|
const result = convertWorkBuddyJsonl(evidence.normalized_result.file.text, { sessionId: 'workbench-copy', title: 'copy',
|
|
sourceSessionId: evidence.normalized_result.session_id, evidenceId: evidence.evidence_id,
|
|
sourceSha256: evidence.normalized_result.file.sha256, importedAt: 5000, branchMode: 'visible' })
|
|
const data = collectWorkbenchArtifacts(result.messages, 'C:/Users/Administrator/code/YCode')
|
|
assert.equal(data.artifacts.length, 13, 'observed source new-tab list has 13 distinct presented files')
|
|
assert.equal(new Set(data.artifacts.map(item => item.id)).size, 13)
|
|
// Visible file identities from REA ev_d6e6920ff4e47f73a182fac5312c5f02393366a035445d18c8a6733a8c8a11ed.
|
|
// JSONL gives only the shared assistant timestamp for same-turn file batches;
|
|
// per-artifact update times (and the three tied items' exact order) need REA evidence.
|
|
assert.deepEqual(data.artifacts.map(item => item.name).sort(), ['视觉对齐实测.md', 'ycode-home.png', 'wb-home-reference.png',
|
|
'ycode-settings.png', 'ycode-notif.png', 'ycode-shell.png', '本轮汇报与决策记录.md', 'ycode-final.png',
|
|
'ycode-empty.png', 'ycode-titlebar.png', 'ycode-ui.png', 'WorkBuddy-UI复刻可行性评估.md', 'WorkBuddy-AI-复刻可行性评估.md'].sort())
|
|
assert.ok(data.artifacts.some(item => item.name === '视觉对齐实测.md'))
|
|
assert.ok(data.changes.length > 0)
|
|
useChatStore.setState({ currentSessionId: 'qa', sessions: [{ id: 'qa', title: 'copy', createdAt: 1, updatedAt: 1, cwd: 'C:/Users/Administrator/code/YCode' }], messages: result.messages })
|
|
useSettingsStore.setState({ workspace: 'C:/Users/Administrator/code/YCode' })
|
|
api.openView('qa', 'newTab')
|
|
const markup = renderToStaticMarkup(<SourceWorkbench theme="light" onClose={() => {}} />)
|
|
assert.match(markup, /sidebar-next-main-header/)
|
|
assert.match(markup, /查看工作空间/)
|
|
assert.doesNotMatch(markup, /打开浏览器|打开终端/, 'missing native hosts must not promise fake success')
|
|
console.log(JSON.stringify({ evidenceId: evidence.evidence_id, artifacts: data.artifacts.length, historyChangeFiles: data.changes.length }))
|
|
}
|
|
assert.match(renderToStaticMarkup(<CloseIcon />), /M5\.5166 4\.7555/)
|
|
assert.match(renderToStaticMarkup(<BrowserEntryIcon />), /viewBox="0 0 16 16"/)
|
|
assert.match(renderToStaticMarkup(<WorkbenchFileIcon path="sample.md" />), /#13B2BF/)
|
|
assert.match(renderToStaticMarkup(<WorkbenchFileIcon path="sample.png" />), /#30ACFF/)
|
|
const sourceCss = readFileSync('src/renderer/src/workbuddy/workbench/workbench-source.css', 'utf8')
|
|
assert.match(sourceCss, /@media\s*\(prefers-reduced-motion/)
|
|
assert.match(sourceCss, /@container\s*\(width<=80px\)/)
|
|
console.log('Workbench tab ownership, race isolation, history projection and source-shell SSR regression passed')
|
|
}
|
|
void main().catch(error => { console.error(error); process.exitCode = 1 })
|