Settings → Codex Bağlama runs the bundled bridge for an OpenAI Codex install on the same computer: the IDE's models and combos appear in Codex and switching models keeps the conversation. The IDE points Codex's openai_base_url at the bridge only after the bridge answers, keeps it running across logins, and removes every trace when the setting is turned off. A Codex that already uses another base URL is left untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDGW1LiKcDUZYUdKBoWYW
194 lines
7.1 KiB
TypeScript
194 lines
7.1 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||
|
||
import { I18nProvider } from '@/i18n/context'
|
||
|
||
import type { CodexLinkStatus } from '../../../electron/codex-link-types'
|
||
|
||
import { CodexLinkSettings } from './codex-link-settings'
|
||
|
||
const notifyError = vi.hoisted(() => vi.fn())
|
||
vi.mock('@/store/notifications', () => ({ notifyError }))
|
||
|
||
const RUNNING: CodexLinkStatus = {
|
||
enabled: true,
|
||
codexFound: true,
|
||
venvReady: true,
|
||
foreign: false,
|
||
bridgeRunning: true,
|
||
ideDurum: 'tamam',
|
||
ideModel: 12,
|
||
ozet: true,
|
||
lastError: null
|
||
}
|
||
|
||
const get = vi.fn<() => Promise<CodexLinkStatus>>()
|
||
const set = vi.fn<(patch: { enabled?: boolean; sync?: boolean }) => Promise<CodexLinkStatus>>()
|
||
|
||
function renderPage(locale: 'en' | 'tr' = 'tr') {
|
||
return render(
|
||
<I18nProvider configClient={null} initialLocale={locale}>
|
||
<CodexLinkSettings />
|
||
</I18nProvider>
|
||
)
|
||
}
|
||
|
||
function statusTexts() {
|
||
return screen.getAllByRole('listitem').map(item => item.textContent)
|
||
}
|
||
|
||
beforeEach(() => {
|
||
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = { codexLink: { get, set } }
|
||
})
|
||
|
||
afterEach(() => {
|
||
cleanup()
|
||
vi.resetAllMocks()
|
||
delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop
|
||
})
|
||
|
||
describe('Codex Link settings', () => {
|
||
it('explains the feature and shows a healthy link in Turkish', async () => {
|
||
get.mockResolvedValue(RUNNING)
|
||
renderPage()
|
||
|
||
expect(await screen.findByText('Köprü çalışıyor.')).toBeTruthy()
|
||
expect(statusTexts()).toEqual(['Bu bilgisayarda Codex bulundu.', 'Köprü çalışıyor.', "Codex'e 12 model eklendi."])
|
||
expect(
|
||
screen.getByText(
|
||
"AITURK IDE'deki modelleri ve comboları bu bilgisayardaki Codex'e ekler; modeller arasında geçerken sohbet bağlamını korur."
|
||
)
|
||
).toBeTruthy()
|
||
expect(screen.getByText("Codex'te yeni modelleri görmek için Codex'i yeniden açın.")).toBeTruthy()
|
||
expect(screen.getByRole('switch', { name: 'Codex Bağlama' }).getAttribute('aria-checked')).toBe('true')
|
||
expect(set).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('renders in English for other locales', async () => {
|
||
get.mockResolvedValue(RUNNING)
|
||
renderPage('en')
|
||
|
||
expect(await screen.findByText('The bridge is running.')).toBeTruthy()
|
||
expect(statusTexts()).toEqual([
|
||
'Codex was found on this computer.',
|
||
'The bridge is running.',
|
||
'12 models added to Codex.'
|
||
])
|
||
expect(screen.getByRole('button', { name: 'Sync now' })).toBeTruthy()
|
||
})
|
||
|
||
it('says only that Codex is missing when it is not installed', async () => {
|
||
get.mockResolvedValue({ ...RUNNING, codexFound: false, bridgeRunning: false, ideDurum: null, ideModel: null })
|
||
renderPage()
|
||
|
||
expect(await screen.findByText('Bu bilgisayarda Codex bulunamadı.')).toBeTruthy()
|
||
expect(statusTexts()).toEqual(['Bu bilgisayarda Codex bulunamadı.'])
|
||
})
|
||
|
||
it('says another bridge is in charge and does not claim ours is running', async () => {
|
||
get.mockResolvedValue({ ...RUNNING, foreign: true })
|
||
renderPage()
|
||
|
||
expect(await screen.findByText(/başka bir köprü kullanıyor/)).toBeTruthy()
|
||
expect(statusTexts()).toEqual([
|
||
'Bu bilgisayarda Codex bulundu.',
|
||
'Bu bilgisayarda Codex başka bir köprü kullanıyor; ayarına dokunulmadı.'
|
||
])
|
||
})
|
||
|
||
it('says it is waiting for the IDE setup', async () => {
|
||
get.mockResolvedValue({ ...RUNNING, venvReady: false, bridgeRunning: false, ideDurum: null, ideModel: null })
|
||
renderPage()
|
||
|
||
expect(await screen.findByText('IDE kurulumu tamamlanınca etkinleşecek.')).toBeTruthy()
|
||
expect(screen.queryByText('Köprü çalışmıyor.')).toBeNull()
|
||
})
|
||
|
||
it('asks for a sign-in when the bridge has no IDE session', async () => {
|
||
get.mockResolvedValue({ ...RUNNING, ideDurum: 'anahtar-yok', ideModel: 0 })
|
||
renderPage()
|
||
|
||
expect(await screen.findByText('IDE oturumu bulunamadı, giriş yapın.')).toBeTruthy()
|
||
expect(screen.queryByText(/model eklendi/)).toBeNull()
|
||
})
|
||
|
||
it('shows a stopped bridge together with the last error', async () => {
|
||
get.mockResolvedValue({
|
||
...RUNNING,
|
||
bridgeRunning: false,
|
||
ideDurum: null,
|
||
ideModel: null,
|
||
lastError: 'bridge-start-failed'
|
||
})
|
||
renderPage()
|
||
|
||
expect(await screen.findByText('Köprü çalışmıyor.')).toBeTruthy()
|
||
expect(screen.getByText('Köprü başlatılamadı; Codex ayarına dokunulmadı.')).toBeTruthy()
|
||
})
|
||
|
||
it('turns the link off through the bridge and adopts the reply', async () => {
|
||
get.mockResolvedValue(RUNNING)
|
||
set.mockResolvedValue({ ...RUNNING, enabled: false, bridgeRunning: false, ideDurum: null, ideModel: null })
|
||
renderPage()
|
||
|
||
await screen.findByText('Köprü çalışıyor.')
|
||
fireEvent.click(screen.getByRole('switch', { name: 'Codex Bağlama' }))
|
||
|
||
expect(set).toHaveBeenCalledWith({ enabled: false })
|
||
expect(await screen.findByText('Codex Bağlama kapalı. Codex kendi ayarlarıyla çalışıyor.')).toBeTruthy()
|
||
expect(screen.getByRole('switch', { name: 'Codex Bağlama' }).getAttribute('aria-checked')).toBe('false')
|
||
})
|
||
|
||
it('rolls the switch back and reports when the change fails', async () => {
|
||
get.mockResolvedValue(RUNNING)
|
||
set.mockRejectedValue(new Error('codex-link:unavailable'))
|
||
renderPage()
|
||
|
||
await screen.findByText('Köprü çalışıyor.')
|
||
fireEvent.click(screen.getByRole('switch', { name: 'Codex Bağlama' }))
|
||
|
||
await waitFor(() => expect(notifyError).toHaveBeenCalledWith(expect.any(Error), 'Codex Bağlama değiştirilemedi.'))
|
||
expect(screen.getByRole('switch', { name: 'Codex Bağlama' }).getAttribute('aria-checked')).toBe('true')
|
||
expect(screen.getByText('Köprü çalışıyor.')).toBeTruthy()
|
||
})
|
||
|
||
it('syncs on demand and keeps the button disabled while it runs', async () => {
|
||
get.mockResolvedValue({ ...RUNNING, bridgeRunning: false, ideDurum: null, ideModel: null })
|
||
|
||
let finish: (status: CodexLinkStatus) => void = () => undefined
|
||
|
||
set.mockReturnValue(
|
||
new Promise<CodexLinkStatus>(resolve => {
|
||
finish = resolve
|
||
})
|
||
)
|
||
renderPage()
|
||
|
||
await screen.findByText('Köprü çalışmıyor.')
|
||
fireEvent.click(screen.getByRole('button', { name: 'Şimdi eşitle' }))
|
||
|
||
expect(set).toHaveBeenCalledTimes(1)
|
||
expect(set).toHaveBeenCalledWith({ sync: true })
|
||
|
||
const pending = screen.getByRole('button', { name: 'Eşitleniyor…' }) as HTMLButtonElement
|
||
|
||
expect(pending.disabled).toBe(true)
|
||
fireEvent.click(pending)
|
||
expect(set).toHaveBeenCalledTimes(1)
|
||
|
||
finish(RUNNING)
|
||
|
||
expect(await screen.findByText('Köprü çalışıyor.')).toBeTruthy()
|
||
expect((screen.getByRole('button', { name: 'Şimdi eşitle' }) as HTMLButtonElement).disabled).toBe(false)
|
||
})
|
||
|
||
it('offers nothing to click where the desktop bridge is absent', () => {
|
||
delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop
|
||
renderPage()
|
||
|
||
expect(screen.getByText('Codex Bağlama yalnızca Windows masaüstü uygulamasında kullanılabilir.')).toBeTruthy()
|
||
expect(screen.queryByRole('switch')).toBeNull()
|
||
expect(get).not.toHaveBeenCalled()
|
||
})
|
||
})
|