feat(apes): Phase 1 — 注册 3 个 apes:* 信息层 tab (tasks/agents/profile)
- 独立 src/client/apes/ 模块 (D1=B), index.tsx 仅 3 处最小改动 - 3 个静态占位 tab (react-icons FiList/FiCpu/FiUser), order 900+ 排在上游 builtins 之后 - 隔离 i18n 命名空间 'apes' (不碰上游 betterSidebar 字典), zh/en 双语 - index.tsx: attachApesLocale + 字典注册 + registerApesTabs effect - tests/apes-tabs.spec.ts: 9 测试 (注册契约 + disposer + i18n zh/en/兜底) gate: typecheck + build + test 1250 passed / 9 skipped / 0 failed apps tab 延后 Phase 1b, messages 已砍 (无 DSH message-stream API)
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* apes:* tab registration tests (Phase 1): the fork registers its three
|
||||
* information-layer tabs (tasks / agents / profile) through the same
|
||||
* BetterSidebarService the upstream builtins use (dogfood). The spec pins
|
||||
* the registration contract (id / single / order / component presence) and
|
||||
* the isolated i18n contract (the apes namespace resolves zh vs en through
|
||||
* the attached locale service, and every apesZh key has an apesEn entry).
|
||||
*
|
||||
* Runs in the node vitest environment like builtins.spec.ts — no DOM, no
|
||||
* real DSH host (a live cordis scratch mount is Phase 2+).
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
// Browser globals before the client graph loads (mirror of builtins.spec.ts).
|
||||
import './browser-globals.ts'
|
||||
|
||||
import type { Context } from '../src/context-types.ts'
|
||||
import { createBetterSidebarService } from '../src/client/service.ts'
|
||||
import { createSidebarStore } from '../src/client/state.ts'
|
||||
import { registerApesTabs } from '../src/client/apes/index.ts'
|
||||
import { apesZh, apesEn, attachApesLocale, t, type ApesCopyKey } from '../src/client/apes/locales.ts'
|
||||
|
||||
function setup(): {
|
||||
service: ReturnType<typeof createBetterSidebarService>
|
||||
store: ReturnType<typeof createSidebarStore>
|
||||
dispose: () => void
|
||||
} {
|
||||
const store = createSidebarStore()
|
||||
const service = createBetterSidebarService(store)
|
||||
const dispose = registerApesTabs({} as Context, service)
|
||||
return { service, store, dispose }
|
||||
}
|
||||
|
||||
describe('apes:* tab registrations', () => {
|
||||
it('registers the 3 apes tabs with the apes: namespace', () => {
|
||||
const { service, dispose } = setup()
|
||||
try {
|
||||
expect(service.getTabs().map(t => t.id).sort()).toEqual(
|
||||
['apes:agents', 'apes:profile', 'apes:tasks'],
|
||||
)
|
||||
} finally {
|
||||
dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('every apes tab is single-instance, visible, and component-carrying', () => {
|
||||
const { service, dispose } = setup()
|
||||
try {
|
||||
for (const id of ['apes:tasks', 'apes:agents', 'apes:profile']) {
|
||||
const tab = service.getTab(id)
|
||||
expect(tab, `${id} registered`).toBeDefined()
|
||||
expect(tab?.single, `${id} single`).toBe(true)
|
||||
expect(tab?.hidden, `${id} hidden`).not.toBe(true)
|
||||
expect(tab?.component, `${id} component`).toBeDefined()
|
||||
expect(tab?.icon, `${id} icon`).toBeDefined()
|
||||
}
|
||||
} finally {
|
||||
dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('apes tabs sort after the upstream builtins (order 900+)', () => {
|
||||
const { service, dispose } = setup()
|
||||
try {
|
||||
const orders = {
|
||||
tasks: service.getTab('apes:tasks')?.order,
|
||||
agents: service.getTab('apes:agents')?.order,
|
||||
profile: service.getTab('apes:profile')?.order,
|
||||
}
|
||||
expect(orders.tasks).toBeGreaterThanOrEqual(900)
|
||||
expect(orders.agents).toBeGreaterThanOrEqual(900)
|
||||
expect(orders.profile).toBeGreaterThanOrEqual(900)
|
||||
// Strictly ordered: tasks < agents < profile.
|
||||
expect(orders.tasks!).toBeLessThan(orders.agents!)
|
||||
expect(orders.agents!).toBeLessThan(orders.profile!)
|
||||
} finally {
|
||||
dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('the disposer unregisters all apes tabs (HMR-safe)', () => {
|
||||
const store = createSidebarStore()
|
||||
const service = createBetterSidebarService(store)
|
||||
const dispose = registerApesTabs({} as Context, service)
|
||||
expect(service.getTabs()).toHaveLength(3)
|
||||
dispose()
|
||||
expect(service.getTabs()).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('apes i18n contract', () => {
|
||||
it('every apesZh key has an apesEn entry (no missing translations)', () => {
|
||||
const zhKeys = Object.keys(apesZh) as ApesCopyKey[]
|
||||
for (const key of zhKeys) {
|
||||
expect(apesEn[key], `en entry for ${key}`).toBeDefined()
|
||||
expect(typeof apesEn[key]).toBe('string')
|
||||
expect((apesEn[key] ?? '').length).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('resolves zh when the DSH preference is zh', () => {
|
||||
attachApesLocale({ getSnapshot: () => ({ active: 'zh-CN' }) })
|
||||
expect(t('apesTasks')).toBe('任务')
|
||||
expect(t('apesAgents')).toBe('智能体')
|
||||
expect(t('apesProfile')).toBe('画像')
|
||||
})
|
||||
|
||||
it('resolves en when the DSH preference is en', () => {
|
||||
attachApesLocale({ getSnapshot: () => ({ active: 'en' }) })
|
||||
expect(t('apesTasks')).toBe('Tasks')
|
||||
expect(t('apesAgents')).toBe('Agents')
|
||||
expect(t('apesProfile')).toBe('Profile')
|
||||
})
|
||||
|
||||
it('falls back to zh when no locale service is attached', () => {
|
||||
attachApesLocale(undefined)
|
||||
expect(t('apesTasks')).toBe('任务')
|
||||
})
|
||||
|
||||
it('supports {param} interpolation', () => {
|
||||
attachApesLocale({ getSnapshot: () => ({ active: 'zh' }) })
|
||||
// The placeholder hints carry no params today; assert the mechanism via
|
||||
// a synthetic key shape (the t() contract, not a real apes key).
|
||||
expect(typeof t('apesTasksHint')).toBe('string')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user