// tests/unit/renderer-tools-model.test.ts // 도구 화면(명령어·AI 대화·지식 베이스·사전)의 순수 판단: // - 명령어 요약·라디오 선택은 main 의 경로 규칙과 같다(지시문은 기본 액션 'custom' 일 때만) // - 시험 실행은 실제 경로를 따른다(원문 삽입 설정에서 'refine' 을 돌리지 않는다) // - AI 대화 상태 표지는 세션이 꺼져 있으면 없다 // - 지연 삭제 중인 항목은 목록에서 빠진다 import { describe, expect, it } from 'vitest' import { COMMAND_NONE, commandSelection, commandTestRequest, conversationBadge, documentIndexLabelKey, llmActionLabelKey, resolveCommandPipeline, withoutHidden, } from '../../src/renderer/pages/tools/tools-model' const polish = { id: 'i1', name: '정중하게', prompt: '{{text}} 를 정중하게' } const list = [polish, { id: 'i2', name: '요약', prompt: '{{text}} 요약' }] describe('resolveCommandPipeline', () => { it("기본 액션 'none' 이면 원문 그대로 — 활성 지시문 id 가 남아 있어도", () => { const p = resolveCommandPipeline({ defaultLLMAction: 'none', activeInstructionId: 'i1' }, list) expect(p.kind).toBe('raw') expect(commandSelection(p)).toBe(COMMAND_NONE) }) it("'custom' + 활성 지시문이면 그 지시문", () => { const p = resolveCommandPipeline({ defaultLLMAction: 'custom', activeInstructionId: 'i1' }, list) expect(p).toEqual({ kind: 'instruction', instruction: polish }) expect(commandSelection(p)).toBe('i1') }) it("'custom' 인데 지시문이 지워졌으면 '선택 안 됨'으로 정직하게", () => { const p = resolveCommandPipeline({ defaultLLMAction: 'custom', activeInstructionId: 'gone' }, list) expect(p.kind).toBe('instruction-missing') expect(commandSelection(p)).toBe('') }) it('일반 액션이 기본이면 활성 지시문이 있어도 그 액션이다(main 규칙과 같다) — 라디오는 아무것도 고르지 않는다', () => { const p = resolveCommandPipeline({ defaultLLMAction: 'refine', activeInstructionId: 'i1' }, list) expect(p).toEqual({ kind: 'action', action: 'refine' }) expect(commandSelection(p)).toBe('') expect(llmActionLabelKey('refine')).toBe('settings.action.refine') }) }) describe('commandTestRequest', () => { it('지시문 경로는 custom + 지시문 원문으로 요청한다(치환은 main 이 한다)', () => { const r = commandTestRequest({ kind: 'instruction', instruction: polish }, ' 안녕 ') expect(r).toEqual({ ok: true, params: { text: '안녕', action: 'custom', customPrompt: polish.prompt } }) }) it('원문 삽입 설정에서는 실행하지 않는다 — 예전엔 refine 결과를 보여 줬다', () => { expect(commandTestRequest({ kind: 'raw' }, '안녕')).toEqual({ ok: false, reason: 'raw' }) }) it('체인은 한 번의 LLM 호출로 흉내 내지 않는다', () => { expect(commandTestRequest({ kind: 'action', action: 'chain' }, '안녕')).toEqual({ ok: false, reason: 'chain' }) }) it('빈 입력은 실행하지 않는다', () => { expect(commandTestRequest({ kind: 'action', action: 'refine' }, ' ')).toEqual({ ok: false, reason: 'empty' }) }) }) describe('conversationBadge', () => { it('세션이 꺼져 있으면 표지가 없다 — 켜지지 않은 대화를 대기라 쓰지 않는다', () => { expect(conversationBadge({ active: false, state: 'idle', connecting: false })).toBeNull() }) it('강조색은 듣는 중에만', () => { expect(conversationBadge({ active: true, state: 'listening', connecting: false })?.tone).toBe('accent') expect(conversationBadge({ active: true, state: 'thinking', connecting: false })?.tone).toBe('default') }) it('연결 중이면 세션 상태보다 먼저 연결 중이라 쓴다', () => { expect(conversationBadge({ active: true, state: 'idle', connecting: true })?.labelKey).toBe( 'conversation.realtime.connecting', ) }) }) describe('withoutHidden / documentIndexLabelKey', () => { it('지연 삭제 중인 id 를 목록에서 뺀다', () => { expect(withoutHidden(list, new Set(['i1'])).map((i) => i.id)).toEqual(['i2']) expect(withoutHidden(list, new Set())).toHaveLength(2) }) it('색인이 끝나지 않은 문서는 진행 중일 때만 색인 중, 아니면 색인 안 됨', () => { expect(documentIndexLabelKey({ id: 'd', indexed: false }, 'd')).toBe('rag.indexing') expect(documentIndexLabelKey({ id: 'd', indexed: false }, null)).toBe('tools.knowledge.notIndexed') expect(documentIndexLabelKey({ id: 'd', indexed: true }, null)).toBe('rag.indexed') }) })