Files
apes-sidebar/tests/side-card-section.spec.tsx
T
Menghuan1918 1b0d2085bb feat(settings): 开关改为可点击卡片 — 高亮即启用
- 清单项(侧边栏内容/文件预览)、嵌套相关设置与「新会话默认打开」
  全部从复选框行改为 <button aria-pressed> 卡片:整卡点击切换,
  视觉状态即状态(启用 = 品牌描边 + 交互选中填充 + 勾选徽标;
  禁用 = 中性 hairline + 文字降级),悬停仅在禁用卡上显示
- 嵌套相关设置渲染为更小的缩进卡片(仍仅父级启用时显示);
  宽度输入保持原生设置行(数值非开关)
- 设置导航齿轮为宿主 shell navIcon 硬编码(无插件注入点),
  按用户选择不改宿主、保持现状
- 新增 settingsGeneralTitle 文案;规格测试改为断言 aria-pressed
- 文档(README/AGENTS.md/设计文档偏差记录)同步
2026-08-11 14:19:18 +08:00

123 lines
4.9 KiB
TypeScript

/**
* Side card settings section render tests: the section is DECLARATIVE —
* every card (icon, title, type id, extensions, on/off state) derives from
* the sidebar service's tab/viewer registries instead of hardcoded copy.
* 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 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).
*/
import { describe, expect, it } from 'vitest'
import { renderToString } from 'react-dom/server'
import { createElement } from 'react'
import { createSidebarStore, type SidebarStore } from '../src/client/state.ts'
import { createBetterSidebarService, type BetterSidebarService } from '../src/client/service.ts'
import { SideCardSection, type SideCardSectionProps } from '../src/client/SideCardSection.tsx'
/** One tab + one viewer + the subagent-style nested toggle under a tab. */
function mount(): { store: SidebarStore; service: BetterSidebarService } {
const store = createSidebarStore()
const service = createBetterSidebarService(store)
service.registerTab({
id: 'explorer',
title: () => 'Explorer',
icon: () => createElement('svg', { 'data-icon': 'explorer' }),
order: 10,
component: () => null,
})
service.registerTab({
id: 'subagent',
title: () => 'Subagents',
icon: () => createElement('svg', { 'data-icon': 'subagent' }),
order: 30,
settings: {
toggles: [{
key: 'autoOpenSubagent',
title: () => 'Auto-open Subagents',
desc: () => 'Expand on new subagent',
}],
},
component: () => null,
})
service.registerFileViewer({
id: 'image',
title: () => 'Image',
icon: () => createElement('svg', { 'data-icon': 'image' }),
exts: ['png', 'jpg'],
fetchStrategy: 'mediaUrl',
component: () => null,
})
return { store, service }
}
function renderSection(store: SidebarStore, service: BetterSidebarService): string {
return renderToString(createElement(
SideCardSection,
{ store, service } as unknown as SideCardSectionProps,
))
}
/** 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 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 card's desc (the declarative "type" surface).
expect(html).toContain('>explorer<')
expect(html).toContain('data-icon="subagent"')
expect(html).toContain('>Subagents<')
// 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 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 → pressed).
expect(html).toContain('Auto-open Subagents')
expect(html).toContain('Expand on new subagent')
})
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 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 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 card's desc.
expect(html).toContain('png · jpg')
})
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<')
expect(pressedCount(html, 'false')).toBe(1)
expect(pressedCount(html, 'true')).toBe(4)
})
})