/** * apes:* tab registration tests (Phase 1/1b): the fork registers its four * information-layer tabs (tasks / agents / apps / 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 store: ReturnType 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 4 apes tabs with the apes: namespace', () => { const { service, dispose } = setup() try { expect(service.getTabs().map(t => t.id).sort()).toEqual( ['apes:agents', 'apes:apps', '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:apps', '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, apps: service.getTab('apes:apps')?.order, profile: service.getTab('apes:profile')?.order, } expect(orders.tasks).toBeGreaterThanOrEqual(900) expect(orders.agents).toBeGreaterThanOrEqual(900) expect(orders.apps).toBeGreaterThanOrEqual(900) expect(orders.profile).toBeGreaterThanOrEqual(900) // Strictly ordered: tasks < agents < apps < profile. expect(orders.tasks!).toBeLessThan(orders.agents!) expect(orders.agents!).toBeLessThan(orders.apps!) expect(orders.apps!).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(4) 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('apesApps')).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('apesApps')).toBe('Apps') 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 duration strings carry {seconds} params — assert the mechanism via // a real key (the t() contract, not a synthetic one). expect(t('apesDurationSeconds', { seconds: 5 })).toBe('5 秒') expect(t('apesDurationMinutes', { minutes: 2, seconds: 7 })).toBe('2 分 7 秒') }) })