feat(settings): 开关改为可点击卡片 — 高亮即启用

- 清单项(侧边栏内容/文件预览)、嵌套相关设置与「新会话默认打开」
  全部从复选框行改为 <button aria-pressed> 卡片:整卡点击切换,
  视觉状态即状态(启用 = 品牌描边 + 交互选中填充 + 勾选徽标;
  禁用 = 中性 hairline + 文字降级),悬停仅在禁用卡上显示
- 嵌套相关设置渲染为更小的缩进卡片(仍仅父级启用时显示);
  宽度输入保持原生设置行(数值非开关)
- 设置导航齿轮为宿主 shell navIcon 硬编码(无插件注入点),
  按用户选择不改宿主、保持现状
- 新增 settingsGeneralTitle 文案;规格测试改为断言 aria-pressed
- 文档(README/AGENTS.md/设计文档偏差记录)同步
This commit is contained in:
Menghuan1918
2026-08-11 14:19:18 +08:00
parent 89ddf8a9e7
commit 1b0d2085bb
7 changed files with 185 additions and 129 deletions
+27 -22
View File
@@ -1,11 +1,13 @@
/**
* Side card settings section render tests: the section is DECLARATIVE —
* every row (icon, title, type id, extensions, enable switch) derives from
* every card (icon, title, type id, extensions, on/off state) derives from
* the sidebar service's tab/viewer registries instead of hardcoded copy.
* These specs pin that derivation with a fake store + a small registry:
* The toggles are CARDS: the whole card is the switch, the visual state IS
* the state (highlighted = enabled), announced via `aria-pressed`. These
* specs pin that derivation with a fake store + a small registry:
* registered tabs/viewers appear with their declared icon/title/settings,
* disabled states render unchecked, and a disabled tab hides its declared
* nested toggles.
* disabled states render pressed=false, and a disabled tab hides its
* declared nested cards.
*
* Rendered with renderToString (mount effects — the settings RPC sync — do
* not run in SSR; the initial store prefs are the render input).
@@ -60,58 +62,61 @@ function renderSection(store: SidebarStore, service: BetterSidebarService): stri
))
}
/** Count `aria-pressed` occurrences of one value in the rendered HTML. */
function pressedCount(html: string, value: string): number {
return html.match(new RegExp(`aria-pressed="${value}"`, 'g'))?.length ?? 0
}
describe('SideCardSection declarative inventory', () => {
it('renders one row per registered tab: icon + title + type id + switch', () => {
it('renders one card per registered tab: icon + title + type id + pressed state', () => {
const { store, service } = mount()
const html = renderSection(store, service)
expect(html).toContain('data-icon="explorer"')
expect(html).toContain('>Explorer<')
// The type id is the row's desc (the declarative "type" surface).
// The type id is the card's desc (the declarative "type" surface).
expect(html).toContain('>explorer<')
expect(html).toContain('data-icon="subagent"')
expect(html).toContain('>Subagents<')
// Both tabs are enabled by default: their switches render checked.
expect(html.match(/type="checkbox"/g)?.length).toBeGreaterThanOrEqual(4)
// Default prefs: openByDefault + both tabs + the nested auto-open + the
// image viewer are all enabled → 5 cards pressed, none pressed=false.
expect(pressedCount(html, 'true')).toBe(5)
expect(pressedCount(html, 'false')).toBe(0)
})
it('renders the declared nested toggle under an enabled tab', () => {
it('renders the declared nested card under an enabled tab', () => {
const { store, service } = mount()
const html = renderSection(store, service)
// The subagent tab's declared related setting appears while enabled,
// bound to its prefs key (default true → checked).
// bound to its prefs key (default true → pressed).
expect(html).toContain('Auto-open Subagents')
expect(html).toContain('Expand on new subagent')
})
it('hides the nested toggles while the parent tab is disabled', () => {
it('hides the nested cards while the parent tab is disabled', () => {
const { store, service } = mount()
store.setPrefs({ ...store.getPrefs(), tabsEnabled: { subagent: false } })
const html = renderSection(store, service)
// The parent row stays (so the user can re-enable it) but unchecked.
// The parent card stays (so the user can re-enable it) but pressed=false.
expect(html).toContain('>Subagents<')
expect(html).not.toContain('Auto-open Subagents')
expect(pressedCount(html, 'false')).toBe(1)
})
it('renders one row per registered viewer: icon + title + exts + switch', () => {
it('renders one card per registered viewer: icon + title + exts', () => {
const { store, service } = mount()
const html = renderSection(store, service)
expect(html).toContain('data-icon="image"')
expect(html).toContain('>Image<')
// The covered extensions are the row's desc.
// The covered extensions are the card's desc.
expect(html).toContain('png · jpg')
})
it('a disabled viewer renders unchecked', () => {
it('a disabled viewer renders pressed=false', () => {
const { store, service } = mount()
store.setPrefs({ ...store.getPrefs(), viewersEnabled: { image: false } })
const html = renderSection(store, service)
expect(html).toContain('>Image<')
})
it('a disabled tab renders unchecked (its switch reflects the prefs map)', () => {
const { store, service } = mount()
store.setPrefs({ ...store.getPrefs(), tabsEnabled: { explorer: false } })
const html = renderSection(store, service)
expect(html).toContain('>explorer<')
expect(pressedCount(html, 'false')).toBe(1)
expect(pressedCount(html, 'true')).toBe(4)
})
})