107 lines
4.6 KiB
TypeScript
107 lines
4.6 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import type { CaptionSegment } from '@d3ro/core/types'
|
|
import {
|
|
applyTranscriptSegmentEdit,
|
|
parseTranscriptSegments,
|
|
transcriptSegmentIndex,
|
|
} from '../../src/renderer/components/meeting/transcript-segments'
|
|
import {
|
|
isActiveMeetingState,
|
|
recordingStartFromElapsed,
|
|
replaceSegmentText,
|
|
rollbackSegmentText,
|
|
} from '../../src/renderer/pages/meeting/live-session'
|
|
|
|
describe('상세 뷰 세그먼트 편집 — 전사 재조립', () => {
|
|
const raw = '[00:01] [화자 1] 안녕하세요\n[00:05] 오늘 안건은\n[01:10] 예산입니다'
|
|
|
|
it('세그먼트 id 는 줄 순번이다', () => {
|
|
const segs = parseTranscriptSegments(raw, null)
|
|
expect(segs.map((s) => s.id)).toEqual(['seg-0', 'seg-1', 'seg-2'])
|
|
expect(segs[0].speaker).toBe('화자 1')
|
|
expect(transcriptSegmentIndex('seg-12')).toBe(12)
|
|
expect(transcriptSegmentIndex('x-1')).toBeNull()
|
|
expect(transcriptSegmentIndex('seg-')).toBeNull()
|
|
})
|
|
|
|
it('고친 줄만 바뀐 전사를 돌려준다(시각·화자 유지)', () => {
|
|
const next = applyTranscriptSegmentEdit(raw, 'seg-1', '오늘 안건은 두 가지')
|
|
expect(next).toBe('[00:01] [화자 1] 안녕하세요\n[00:05] 오늘 안건은 두 가지\n[01:10] 예산입니다')
|
|
// 재조립한 전사를 다시 나누면 수정이 그대로 보인다 — 원문으로 되돌아가지 않는다
|
|
expect(parseTranscriptSegments(raw, next)[1].text).toBe('오늘 안건은 두 가지')
|
|
})
|
|
|
|
it('기존 수정본 위에 이어서 고친다', () => {
|
|
const first = applyTranscriptSegmentEdit(raw, 'seg-0', '반갑습니다')
|
|
const second = applyTranscriptSegmentEdit(first, 'seg-2', '예산 확정')
|
|
const texts = parseTranscriptSegments(raw, second).map((s) => s.text)
|
|
expect(texts).toEqual(['반갑습니다', '오늘 안건은', '예산 확정'])
|
|
})
|
|
|
|
it('없는 세그먼트·빈 내용·같은 내용이면 null', () => {
|
|
expect(applyTranscriptSegmentEdit(raw, 'seg-9', 'x')).toBeNull()
|
|
expect(applyTranscriptSegmentEdit(raw, 'bogus', 'x')).toBeNull()
|
|
expect(applyTranscriptSegmentEdit(raw, 'seg-0', ' ')).toBeNull()
|
|
expect(applyTranscriptSegmentEdit(raw, 'seg-0', '안녕하세요')).toBeNull()
|
|
})
|
|
|
|
it('줄바꿈은 한 줄로 접어 다른 세그먼트를 만들지 않는다', () => {
|
|
const next = applyTranscriptSegmentEdit(raw, 'seg-1', '둘\n셋')
|
|
expect(parseTranscriptSegments(raw, next)).toHaveLength(3)
|
|
})
|
|
})
|
|
|
|
describe('녹음 뷰 — 세션 복원과 실시간 편집 롤백', () => {
|
|
const segs: CaptionSegment[] = [
|
|
{ id: 'a', text: '하나', timestamp: 0, isFinal: true },
|
|
{ id: 'b', text: '둘', timestamp: 0, isFinal: true },
|
|
]
|
|
|
|
it('녹음·후처리 중이면 녹음 뷰를 복원한다', () => {
|
|
expect(isActiveMeetingState('recording')).toBe(true)
|
|
expect(isActiveMeetingState('processing')).toBe(true)
|
|
expect(isActiveMeetingState('idle')).toBe(false)
|
|
expect(isActiveMeetingState(null)).toBe(false)
|
|
})
|
|
|
|
it('경과 시간으로 시작 시각을 되짚는다', () => {
|
|
expect(recordingStartFromElapsed(5_000, 100_000)).toBe(95_000)
|
|
expect(recordingStartFromElapsed(-1, 100_000)).toBe(100_000)
|
|
expect(recordingStartFromElapsed(Number.NaN, 100_000)).toBe(100_000)
|
|
})
|
|
|
|
it('실패한 편집만 되돌리고, 그 사이 다시 고친 내용은 덮어쓰지 않는다', () => {
|
|
const edited = replaceSegmentText(segs, 'b', '둘 수정')
|
|
expect(edited[1].text).toBe('둘 수정')
|
|
expect(rollbackSegmentText(edited, 'b', '둘 수정', '둘')[1].text).toBe('둘')
|
|
|
|
const reEdited = replaceSegmentText(edited, 'b', '둘 재수정')
|
|
expect(rollbackSegmentText(reEdited, 'b', '둘 수정', '둘')[1].text).toBe('둘 재수정')
|
|
})
|
|
})
|
|
|
|
describe('MeetingModePage 구조 회귀', () => {
|
|
const source = fs.readFileSync(
|
|
path.join(__dirname, '../../src/renderer/pages/MeetingModePage.tsx'),
|
|
'utf-8',
|
|
)
|
|
|
|
it('목록 뷰도 오류 다이얼로그(강제 초기화 버튼)를 렌더한다', () => {
|
|
const start = source.search(/if \(view === 'list'\) \{\s*return \(/)
|
|
expect(start).toBeGreaterThan(-1)
|
|
const end = source.indexOf("if (view === 'recording')", start)
|
|
expect(end).toBeGreaterThan(start)
|
|
const listBranch = source.slice(start, end)
|
|
expect(listBranch).toContain('{errorDialog}')
|
|
})
|
|
|
|
it('마운트 시 main 의 회의 상태를 물어 진행 중 녹음을 복원한다', () => {
|
|
expect(source).toMatch(/meetingMode\s*\.getState\(\)/)
|
|
})
|
|
|
|
it('설정 딥링크에 숫자 인덱스를 쓰지 않는다', () => {
|
|
expect(source).not.toMatch(/detail:\s*\{\s*tab:\s*\d/)
|
|
})
|
|
})
|