feat(settings): 侧边栏设置声明式化 — 注册表驱动的开关清单 + 内置支持插头化
- SidebarPrefs 新增 tabsEnabled / viewersEnabled 开放 map(缺省 = 启用), PrefsSchema 用 z.dict 校验,旧文档零迁移 - TabDescriptor/FileViewerDescriptor 新增声明式设置面:viewer 补 title/icon, 两者补 settings.toggles(子代理声明 autoOpenSubagent、终端声明 agentTerminalTools) - 服务新增 isTabEnabled/isViewerEnabled;openTab 拒绝禁用类型、matchFileViewer 跳过禁用 viewer - SideCardSection 重写为注册表驱动清单:侧边栏内容(图标+类型id+开关+嵌套开关) + 文件预览(图标+标题+扩展名+开关),开关持久化 + 乐观提交回滚 - 消费点 gating:+ 菜单过滤、子代理自动展开、agent 终端 reconcile、 产出文件拦截(editor 关闭时回落默认行)、新会话 explorer 预置 - 内置 viewer 补 6 个手绘 outline 图标;store 快照携带 prefs 使开关变更即时生效 - 测试:service gating / builtins 声明 / parsePrefs map 校验 / schema 断言 / 新增 SideCardSection renderToString 规格 - 文档:README 设置小节、AGENTS.md 描述符与服务方法、新增设计文档;版本 0.5.0
This commit is contained in:
@@ -77,6 +77,57 @@ describe('BetterSidebar service', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('enable switches (declarative settings)', () => {
|
||||
/** A fresh store + service with one tab and one viewer registered. */
|
||||
const setup = () => {
|
||||
const store = createSidebarStore()
|
||||
const service = createBetterSidebarService(store)
|
||||
service.registerTab({ id: 'explorer', title: 'Explorer', component: () => null })
|
||||
service.registerFileViewer({ id: 'image', exts: ['png'], fetchStrategy: 'mediaUrl', component: () => null })
|
||||
return { store, service }
|
||||
}
|
||||
|
||||
it('an absent map key means enabled (default state)', () => {
|
||||
const { service } = setup()
|
||||
expect(service.isTabEnabled('explorer')).toBe(true)
|
||||
expect(service.isViewerEnabled('image')).toBe(true)
|
||||
// Unknown ids are enabled too (nothing registered → the check is open).
|
||||
expect(service.isTabEnabled('whatever')).toBe(true)
|
||||
})
|
||||
|
||||
it('only an explicit false disables a tab type', () => {
|
||||
const { store, service } = setup()
|
||||
store.setPrefs({ ...store.getPrefs(), tabsEnabled: { explorer: false } })
|
||||
expect(service.isTabEnabled('explorer')).toBe(false)
|
||||
store.setPrefs({ ...store.getPrefs(), tabsEnabled: { explorer: true } })
|
||||
expect(service.isTabEnabled('explorer')).toBe(true)
|
||||
})
|
||||
|
||||
it('openTab refuses a disabled tab type (no tab lands, no createTab)', () => {
|
||||
const { store, service } = setup()
|
||||
store.setPrefs({ ...store.getPrefs(), tabsEnabled: { explorer: false } })
|
||||
store.setSession('s1')
|
||||
service.openTab({ type: 'explorer', title: 'Explorer' })
|
||||
const tabs = allLeaves(store.getSnapshot().state!.splits).flatMap(l => l.tabs)
|
||||
expect(tabs).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('matchFileViewer skips a disabled viewer (files fall through)', () => {
|
||||
const { store, service } = setup()
|
||||
service.registerFileViewer({ id: 'code', exts: [], priority: -100, fetchStrategy: 'fsRead', component: () => null })
|
||||
// image claims .png; disabling it lets the catch-all take over.
|
||||
expect(service.matchFileViewer('photo.png')?.id).toBe('image')
|
||||
store.setPrefs({ ...store.getPrefs(), viewersEnabled: { image: false } })
|
||||
expect(service.matchFileViewer('photo.png')?.id).toBe('code')
|
||||
// Disabling the catch-all too → no viewer at all.
|
||||
store.setPrefs({ ...store.getPrefs(), viewersEnabled: { image: false, code: false } })
|
||||
expect(service.matchFileViewer('photo.png')).toBeUndefined()
|
||||
// Re-enabling restores the image viewer.
|
||||
store.setPrefs({ ...store.getPrefs(), viewersEnabled: {} })
|
||||
expect(service.matchFileViewer('photo.png')?.id).toBe('image')
|
||||
})
|
||||
})
|
||||
|
||||
describe('matchFileViewer', () => {
|
||||
it('matches by extension', () => {
|
||||
const store = createSidebarStore()
|
||||
|
||||
Reference in New Issue
Block a user