Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Sources/OpenClawKit/TalkSystemSpeechSynthesizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ extension TalkSystemSpeechSynthesizer: AVSpeechSynthesizerDelegate {

#if DEBUG
extension TalkSystemSpeechSynthesizer {
// Package tests use their own synthesizer: suites run in parallel, so tests that configure
// `shared` would see each other's Now Playing and state reports.
static func _test_make() -> TalkSystemSpeechSynthesizer {
TalkSystemSpeechSynthesizer()
}

// Package tests drive the Now Playing lifecycle without native speech services.
func _test_simulateStart() {
let utterance = AVSpeechUtterance(string: "test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,8 @@ struct GatewayStateReportingWiringTests {
@Test
func systemSpeechReportsSpeakingThenIdle() {
let reporter = RecordingStateReporter()
let synthesizer = TalkSystemSpeechSynthesizer.shared
let previous = synthesizer.stateReporter
let synthesizer = TalkSystemSpeechSynthesizer._test_make()
synthesizer.stateReporter = reporter
defer { synthesizer.stateReporter = previous }
synthesizer._test_simulateStart()
synthesizer._test_simulateFinish()
let talk = reporter.transitions.filter { $0.domain == .talk }
Expand Down
30 changes: 8 additions & 22 deletions Tests/OpenClawKitTests/NowPlayingPublishingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ struct NowPlayingPublishingTests {
#endif
}

// Each test makes its own synthesizer. Suites run in parallel, so configuring `shared` here would
// leak Now Playing and state reports into (and from) other suites' tests.
@Suite("Talk system speech synthesizer", .serialized)
@MainActor
struct TalkSystemSpeechSynthesizerTests {
Expand Down Expand Up @@ -197,10 +199,9 @@ struct TalkSystemSpeechSynthesizerTests {

@Test("a pre-cancelled caller throws without touching the synthesizer")
func preCancelledCallerThrows() async {
let speaker = TalkSystemSpeechSynthesizer.shared
let speaker = TalkSystemSpeechSynthesizer._test_make()
let recorder = RecordingNowPlayingPublisher()
speaker.nowPlayingPublisher = recorder
defer { speaker.nowPlayingPublisher = nil }
let attempt = Task { @MainActor in
try await speaker.speak(text: "Cancelled successor speech.", language: "en-US")
}
Expand All @@ -213,17 +214,12 @@ struct TalkSystemSpeechSynthesizerTests {

@Test("speech publishes live Talk metadata on start and clears on finish")
func speechPublishesNowPlayingLifecycle() {
let speaker = TalkSystemSpeechSynthesizer.shared
let speaker = TalkSystemSpeechSynthesizer._test_make()
let recorder = RecordingNowPlayingPublisher()
var speaking: [Bool] = []
speaker.nowPlayingPublisher = recorder
speaker.nowPlayingTitle = "Molty"
speaker.onSpeakingChanged = { speaking.append($0) }
defer {
speaker.nowPlayingPublisher = nil
speaker.nowPlayingTitle = nil
speaker.onSpeakingChanged = nil
}

speaker._test_simulateStart()
speaker._test_simulateFinish()
Expand All @@ -246,10 +242,9 @@ struct TalkSystemSpeechSynthesizerTests {

@Test("remote pause stops speech and clears Now Playing")
func remotePauseStopsSpeech() {
let speaker = TalkSystemSpeechSynthesizer.shared
let speaker = TalkSystemSpeechSynthesizer._test_make()
let recorder = RecordingNowPlayingPublisher()
speaker.nowPlayingPublisher = recorder
defer { speaker.nowPlayingPublisher = nil }

speaker._test_simulateStart()
recorder.send(.pause)
Expand All @@ -260,13 +255,9 @@ struct TalkSystemSpeechSynthesizerTests {

@Test("an audio-session interruption publishes the interrupted state")
func interruptionPublishesInterruptedState() {
let speaker = TalkSystemSpeechSynthesizer.shared
let speaker = TalkSystemSpeechSynthesizer._test_make()
let recorder = RecordingNowPlayingPublisher()
speaker.nowPlayingPublisher = recorder
defer {
speaker.nowPlayingPublisher = nil
speaker.stop()
}

speaker._test_simulateStart()
speaker.interruptForAudioSession()
Expand All @@ -281,15 +272,11 @@ struct TalkSystemSpeechSynthesizerTests {

@Test("the audio session controller interrupts the synthesizer through the protocol requirement")
func controllerInterruptsSynthesizerThroughProtocol() {
let speaker = TalkSystemSpeechSynthesizer.shared
let speaker = TalkSystemSpeechSynthesizer._test_make()
let recorder = RecordingNowPlayingPublisher()
speaker.nowPlayingPublisher = recorder
let controller = TalkAudioSessionController(session: nil, observeSystemEvents: false)
controller.speech = speaker
defer {
speaker.nowPlayingPublisher = nil
speaker.stop()
}

speaker._test_simulateStart()
controller.handle(.interrupted)
Expand All @@ -304,10 +291,9 @@ struct TalkSystemSpeechSynthesizerTests {

@Test("an interruption with nothing playing publishes nothing")
func idleInterruptionPublishesNothing() {
let speaker = TalkSystemSpeechSynthesizer.shared
let speaker = TalkSystemSpeechSynthesizer._test_make()
let recorder = RecordingNowPlayingPublisher()
speaker.nowPlayingPublisher = recorder
defer { speaker.nowPlayingPublisher = nil }

speaker.interruptForAudioSession()

Expand Down
Loading