import XCTest @testable import Ten31Transcripts final class TimelineBuilderTests: XCTestCase { private func obs(_ name: String, _ speaking: Bool, _ t: Double, _ conf: Double = 0.9) -> SpeakerObservation { SpeakerObservation(name: name, speaking: speaking, bbox: .zero, confidence: conf, t: t) } func testOpensAfterKFramesAndClosesAfterMQuiet() { let b = TimelineBuilder(openFrames: 2, closeFrames: 2) b.ingest([obs("A", true, 0)], at: 0) b.ingest([obs("A", true, 1)], at: 1) b.ingest([obs("A", true, 2)], at: 2) b.ingest([], at: 3) b.ingest([], at: 4) b.finish() XCTAssertEqual(b.segments.count, 1) XCTAssertEqual(b.segments.first?.name, "A") XCTAssertEqual(b.segments.first?.start ?? -1, 0, accuracy: 0.001) XCTAssertEqual(b.segments.first?.end ?? -1, 2, accuracy: 0.001) XCTAssertEqual(b.segments.first?.source, "vision") } func testSingleFlickerDoesNotOpen() { let b = TimelineBuilder(openFrames: 2, closeFrames: 2) b.ingest([obs("A", true, 0)], at: 0) b.ingest([], at: 1) b.finish() XCTAssertTrue(b.segments.isEmpty) } func testAllowsOverlap() { let b = TimelineBuilder(openFrames: 1, closeFrames: 1) b.ingest([obs("A", true, 0), obs("B", true, 0)], at: 0) b.ingest([obs("A", true, 1), obs("B", true, 1)], at: 1) b.ingest([], at: 2) b.finish() XCTAssertEqual(b.segments.count, 2) XCTAssertEqual(Set(b.segments.map { $0.name }), ["A", "B"]) } func testMergesSelfSpans() { let b = TimelineBuilder() b.mergeSelfSpans([VADSpan(start: 0, end: 4.5, confidence: 0.97)], selfName: "Grant") b.finish() XCTAssertEqual(b.segments.count, 1) XCTAssertEqual(b.segments.first?.name, "Grant") XCTAssertEqual(b.segments.first?.source, "mic_vad") } func testNormalizesAlias() { let b = TimelineBuilder(openFrames: 1, closeFrames: 1) b.addAlias("Sarah J", canonical: "Sarah Jones") b.ingest([obs("Sarah J", true, 0)], at: 0) b.ingest([obs("Sarah J", true, 1)], at: 1) b.ingest([], at: 2) b.finish() XCTAssertEqual(b.segments.first?.name, "Sarah Jones") } }