Repository Public
1import { describe, expect, it } from 'vitest';2import {3 BRANDING_KEY, DEFAULT_BRANDING, brandOf, confirmBranding, continueCommand, initialBranding, loginHint, parseBranding,4 readCachedBranding, serverBranding, skillKindsPhrase, skillsEmptyHint,5} from '@/lib/branding';67const CODEX = { agent: 'codex', productName: 'Codex Remote', agentName: 'Codex' } as const;8const CLAUDE = { agent: 'claude', productName: 'Claude Remote', agentName: 'Claude Code' } as const;910function memory(initial?: string) {11 const data = new Map<string, string>(initial === undefined ? [] : [[BRANDING_KEY, initial]]);12 return { getItem: (k: string) => data.get(k) ?? null, setItem: (k: string, v: string) => void data.set(k, v), data };13}1415describe('branding resolution', () => {16 it('defaults to the Claude product', () => {17 expect(DEFAULT_BRANDING).toEqual(CLAUDE);18 expect(initialBranding(memory())).toEqual(CLAUDE);19 expect(initialBranding(null)).toEqual(CLAUDE);20 });2122 it('paints with a valid cached value', () => {23 expect(initialBranding(memory(JSON.stringify(CODEX)))).toEqual(CODEX);24 });2526 it('ignores a corrupted cache', () => {27 for (const bad of ['{not json', 'null', '"codex"', '{"agent":"gpt","productName":"X","agentName":"Y"}', '{"agent":"codex"}', JSON.stringify({ ...CODEX, productName: '' }), JSON.stringify({ ...CODEX, productName: 'x'.repeat(41) })]) {28 expect(readCachedBranding(memory(bad))).toBeNull();29 expect(initialBranding(memory(bad))).toEqual(CLAUDE);30 }31 });3233 it('survives a storage that throws', () => {34 const broken = { getItem: () => { throw new Error('denied'); }, setItem: () => { throw new Error('denied'); } };35 expect(initialBranding(broken)).toEqual(CLAUDE);36 expect(confirmBranding(broken, CODEX)).toEqual(CODEX);37 });3839 it('falls back to Claude when the server sends no (or an invalid) branding', () => {40 expect(serverBranding(undefined)).toEqual(CLAUDE);41 expect(serverBranding({ agent: 'other', productName: 'X', agentName: 'Y' })).toEqual(CLAUDE);42 // an older server (no `branding`) behind a Codex cache: the cache is corrected43 const store = memory(JSON.stringify(CODEX));44 expect(confirmBranding(store, undefined)).toEqual(CLAUDE);45 expect(initialBranding(store)).toEqual(CLAUDE);46 });4748 it('lets the server value override the cache and rewrites it', () => {49 const store = memory(JSON.stringify(CLAUDE));50 expect(initialBranding(store)).toEqual(CLAUDE);51 expect(confirmBranding(store, CODEX)).toEqual(CODEX);52 expect(JSON.parse(store.data.get(BRANDING_KEY) ?? 'null')).toEqual(CODEX);53 expect(initialBranding(store)).toEqual(CODEX);54 });5556 it('trims names and keeps only the known fields', () => {57 expect(parseBranding({ agent: 'codex', productName: ' Codex Remote ', agentName: 'Codex', extra: 1 })).toEqual(CODEX);58 });59});6061describe('brand wording', () => {62 const claude = brandOf(CLAUDE);63 const codex = brandOf(CODEX);6465 it('derives names and capabilities from the profile', () => {66 expect([claude.product, claude.agentName, claude.short, claude.binary]).toEqual(['Claude Remote', 'Claude Code', 'Claude', 'claude']);67 expect([codex.product, codex.agentName, codex.short, codex.binary]).toEqual(['Codex Remote', 'Codex', 'Codex', 'codex']);68 expect(codex.caps.mcpHeaders).toBe(false);69 expect(codex.caps.mcpTransports).not.toContain('sse');70 expect(claude.caps.projectMcpScope).toBe(true);71 });7273 it('words the login hint per agent', () => {74 const text = (h: { before: string; code: string; after: string }) => h.before + h.code + h.after;75 expect(text(loginHint(claude))).toBe('Open a Claude session and run /login.');76 expect(text(loginHint(codex))).toBe('Run codex login in a shell session.');77 });7879 it('words the conversation and skills hints per agent', () => {80 expect(continueCommand(claude)).toBe('claude --continue');81 expect(continueCommand(codex)).toBe('codex resume --last');82 expect(skillKindsPhrase(claude.caps.skillKinds)).toBe('Skills, slash commands and sub-agents');83 expect(skillKindsPhrase(codex.caps.skillKinds)).toBe('Skills and sub-agents');84 expect(skillsEmptyHint(claude)).toBe('Skills in ~/.claude/skills, commands and agents appear here.');85 expect(skillsEmptyHint(codex)).toBe('Skills in ~/.codex/skills and agents appear here.');86 });87});88