Files
Termind/TFMTests/TerminalBellTests.swift

133 lines
5.4 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
import GhosttyTerminal
import TermindApplication
import TermindDomain
import TermindPresentation
import Testing
@testable import Termind
@Suite @MainActor
struct TerminalBellTests {
@Test func observationIgnoresHistoryCoalescesBurstsAndStopsOnRelease() async {
let state = TerminalViewState(configSource: .none)
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 1 })
var rings = 0
var observation: TerminalBellObservation? = TerminalBellObservation(state: state) { rings += 1 }
#expect(observation != nil)
#expect(rings == 0)
for _ in 0..<4 { state.terminalDidRingBell() }
#expect(await eventually { state.bellCount == 5 })
#expect(rings == 1)
observation = nil
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 6 })
#expect(rings == 1)
}
@Test func hiddenPanesUseLatestSettingsAndClosingStopsFeedback() async throws {
var sounds = 0
let host = Host(name: "Bell", host: "test.invalid")
let app = AppModel(hosts: HostLibraryModel(hosts: [host]), terminalBellSound: { sounds += 1 })
let window = WindowModel()
app.registerWindow(window)
let pane = Pane.sshSession(host: host)
let workspace = Workspace(pane: pane)
window.open(workspace, select: false)
let state = try #require(pane.sshSession?.terminalState)
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 1 })
#expect(sounds == 0)
app.terminalExperienceSettings.set(true, for: \.bellSound)
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 2 })
#expect(sounds == 1)
app.terminalExperienceSettings.set(false, for: \.bellSound)
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 3 })
#expect(sounds == 1)
app.terminalExperienceSettings.set(true, for: \.bellSound)
pane.disconnectConnection()
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 4 })
#expect(sounds == 1)
}
@Test func reconfiguringPaneReplacesRatherThanDuplicatesItsListener() async throws {
let pane = Pane.sshSession(host: Host(name: "Bell", host: "test.invalid"))
defer { pane.disconnectConnection() }
let state = try #require(pane.sshSession?.terminalState)
var obsolete = 0
var current = 0
pane.configureTerminalBell { obsolete += 1 }
pane.configureTerminalBell { current += 1 }
state.terminalDidRingBell()
#expect(await eventually { state.bellCount == 1 })
#expect(obsolete == 0)
#expect(current == 1)
}
@Test func windowsShareLivePreferencesAndMovingOrReopeningNeverReplaysBells() async throws {
var sounds = 0
let host = Host(name: "Bell", host: "test.invalid")
let app = AppModel(hosts: HostLibraryModel(hosts: [host]), terminalBellSound: { sounds += 1 })
let firstWindow = WindowModel()
let secondWindow = WindowModel()
app.registerWindow(firstWindow)
app.registerWindow(secondWindow)
let first = Pane.sshSession(host: host)
let second = Pane.sshSession(host: host)
let workspace = Workspace(pane: first)
firstWindow.open(workspace, select: false)
secondWindow.open(Workspace(pane: second), select: false)
defer {
first.disconnectConnection()
second.disconnectConnection()
}
let firstState = try #require(first.sshSession?.terminalState)
let secondState = try #require(second.sshSession?.terminalState)
app.terminalExperienceSettings.set(true, for: \.bellSound)
firstState.terminalDidRingBell()
secondState.terminalDidRingBell()
#expect(await eventually { firstState.bellCount == 1 && secondState.bellCount == 1 })
#expect(sounds == 2)
let moved = try #require(firstWindow.takeWorkspaceForMove(workspace.id))
secondWindow.open(moved, select: false)
#expect(sounds == 2)
firstState.terminalDidRingBell()
#expect(await eventually { firstState.bellCount == 2 })
#expect(sounds == 3)
app.terminalExperienceSettings.set(false, for: \.bellSound)
firstState.terminalDidRingBell()
secondState.terminalDidRingBell()
#expect(await eventually { firstState.bellCount == 3 && secondState.bellCount == 2 })
#expect(sounds == 3)
let closed = try #require(secondWindow.takeWorkspaceForMove(workspace.id))
closed.disconnectConnections()
firstState.terminalDidRingBell()
#expect(await eventually { firstState.bellCount == 4 })
closed.prepareForReopen()
app.terminalExperienceSettings.set(true, for: \.bellSound)
firstWindow.open(closed, select: false)
#expect(sounds == 3)
let reopenedState = try #require(first.sshSession?.terminalState)
reopenedState.terminalDidRingBell()
#expect(await eventually { reopenedState.bellCount == 1 })
#expect(sounds == 4)
}
private func eventually(_ predicate: () -> Bool) async -> Bool {
for _ in 0..<100 {
if predicate() { return true }
try? await Task.sleep(for: .milliseconds(10))
}
return predicate()
}
}