summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-02-26 17:46:11 -0600
committerCraig Jennings <c@cjennings.net>2026-02-26 17:46:11 -0600
commit46af687f2444754657000116178eeb80addd5a52 (patch)
tree8ccbf758ad9ce58fedd149dec06508b1b860c7bd /tests
parentc43805f86a6f6b87a3f75ab8ece2610905344ec9 (diff)
feat(recording): show sinks with active audio indicators in quick-setup
Quick-setup (C-; r s) is now a two-step flow: pick a mic, then pick an audio output sink. Sinks display 󰕾/󰖁 icons with green/dim coloring to indicate which have active audio streams, with active sinks sorted to the top. The chosen sink's .monitor is set as the system audio device. This replaces the old auto-default-sink approach, letting users see where audio is actually going and pick the right sink in one command.
Diffstat (limited to 'tests')
-rw-r--r--tests/fixtures/pactl-sinks-verbose-muted.txt30
-rw-r--r--tests/fixtures/pactl-sinks-verbose-normal.txt30
-rw-r--r--tests/test-video-audio-recording--get-available-sinks.el80
-rw-r--r--tests/test-video-audio-recording--parse-pactl-sinks-verbose.el93
-rw-r--r--tests/test-video-audio-recording-quick-setup.el182
-rw-r--r--tests/test-video-audio-recording-validate-system-audio.el3
6 files changed, 367 insertions, 51 deletions
diff --git a/tests/fixtures/pactl-sinks-verbose-muted.txt b/tests/fixtures/pactl-sinks-verbose-muted.txt
new file mode 100644
index 00000000..fcfb7246
--- /dev/null
+++ b/tests/fixtures/pactl-sinks-verbose-muted.txt
@@ -0,0 +1,30 @@
+Sink #65
+ State: RUNNING
+ Name: alsa_output.usb-JDS_Labs-00.analog-stereo
+ Description: JDS Labs Element IV Analog Stereo
+ Driver: PipeWire
+ Sample Specification: s32le 2ch 48000Hz
+ Channel Map: front-left,front-right
+ Mute: no
+ Volume: front-left: 32768 / 50% / -18.06 dB, front-right: 32768 / 50% / -18.06 dB
+ balance 0.00
+Sink #73
+ State: SUSPENDED
+ Name: alsa_output.usb-Shure_MV7-00.analog-stereo
+ Description: Shure MV7+ Analog Stereo
+ Driver: PipeWire
+ Sample Specification: s32le 2ch 48000Hz
+ Channel Map: front-left,front-right
+ Mute: yes
+ Volume: front-left: 0 / 0% / -inf dB, front-right: 0 / 0% / -inf dB
+ balance 0.00
+Sink #81
+ State: SUSPENDED
+ Name: alsa_output.pci-0000_0e_00.4.analog-stereo
+ Description: Ryzen HD Audio Controller Analog Stereo
+ Driver: PipeWire
+ Sample Specification: s32le 2ch 48000Hz
+ Channel Map: front-left,front-right
+ Mute: no
+ Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB
+ balance 0.00
diff --git a/tests/fixtures/pactl-sinks-verbose-normal.txt b/tests/fixtures/pactl-sinks-verbose-normal.txt
new file mode 100644
index 00000000..8c093568
--- /dev/null
+++ b/tests/fixtures/pactl-sinks-verbose-normal.txt
@@ -0,0 +1,30 @@
+Sink #65
+ State: RUNNING
+ Name: alsa_output.usb-JDS_Labs-00.analog-stereo
+ Description: JDS Labs Element IV Analog Stereo
+ Driver: PipeWire
+ Sample Specification: s32le 2ch 48000Hz
+ Channel Map: front-left,front-right
+ Mute: no
+ Volume: front-left: 32768 / 50% / -18.06 dB, front-right: 32768 / 50% / -18.06 dB
+ balance 0.00
+Sink #73
+ State: SUSPENDED
+ Name: alsa_output.usb-Shure_MV7-00.analog-stereo
+ Description: Shure MV7+ Analog Stereo
+ Driver: PipeWire
+ Sample Specification: s32le 2ch 48000Hz
+ Channel Map: front-left,front-right
+ Mute: no
+ Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB
+ balance 0.00
+Sink #81
+ State: SUSPENDED
+ Name: alsa_output.pci-0000_0e_00.4.analog-stereo
+ Description: Ryzen HD Audio Controller Analog Stereo
+ Driver: PipeWire
+ Sample Specification: s32le 2ch 48000Hz
+ Channel Map: front-left,front-right
+ Mute: no
+ Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB
+ balance 0.00
diff --git a/tests/test-video-audio-recording--get-available-sinks.el b/tests/test-video-audio-recording--get-available-sinks.el
new file mode 100644
index 00000000..540c4f0f
--- /dev/null
+++ b/tests/test-video-audio-recording--get-available-sinks.el
@@ -0,0 +1,80 @@
+;;; test-video-audio-recording--get-available-sinks.el --- Tests for sink discovery -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/recording--get-available-sinks.
+;; Verifies that available sinks are discovered correctly:
+;; - Muted sinks are excluded
+;; - Friendly descriptions from PulseAudio are used
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+(require 'video-audio-recording)
+
+;;; Helper
+
+(defun test-sinks--make-pactl-output (sinks)
+ "Build fake `pactl list sinks' output from SINKS.
+Each sink is (name description mute state)."
+ (mapconcat (lambda (s)
+ (format "Sink #1\n\tState: %s\n\tName: %s\n\tDescription: %s\n\tMute: %s\n"
+ (nth 3 s) (nth 0 s) (nth 1 s) (nth 2 s)))
+ sinks ""))
+
+;;; Normal Cases
+
+(ert-deftest test-get-available-sinks-normal-filters-muted ()
+ "Test that muted sinks are excluded from sink list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-sinks--make-pactl-output
+ '(("active-sink" "Active Sink" "no" "RUNNING")
+ ("muted-sink" "Muted Sink" "yes" "SUSPENDED"))))))
+ (let ((sinks (cj/recording--get-available-sinks)))
+ (should (= 1 (length sinks)))
+ (should (equal "active-sink" (car (car sinks)))))))
+
+(ert-deftest test-get-available-sinks-normal-uses-descriptions ()
+ "Test that friendly descriptions are returned as cdr."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-sinks--make-pactl-output
+ '(("raw-sink-name" "Friendly Sink Name" "no" "IDLE"))))))
+ (let ((sinks (cj/recording--get-available-sinks)))
+ (should (equal "Friendly Sink Name" (cdr (car sinks)))))))
+
+(ert-deftest test-get-available-sinks-normal-multiple-sinks ()
+ "Test that multiple non-muted sinks are returned."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-sinks--make-pactl-output
+ '(("sink-a" "JDS Labs" "no" "RUNNING")
+ ("sink-b" "Shure MV7+" "no" "SUSPENDED")
+ ("muted-sink" "Muted" "yes" "SUSPENDED"))))))
+ (let ((sinks (cj/recording--get-available-sinks)))
+ (should (= 2 (length sinks))))))
+
+;;; Boundary Cases
+
+(ert-deftest test-get-available-sinks-boundary-empty-output ()
+ "Test that empty pactl output returns empty list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd) "")))
+ (should (null (cj/recording--get-available-sinks)))))
+
+(ert-deftest test-get-available-sinks-boundary-all-muted ()
+ "Test that if all sinks are muted, returns empty list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-sinks--make-pactl-output
+ '(("muted-a" "Sink A" "yes" "SUSPENDED")
+ ("muted-b" "Sink B" "yes" "SUSPENDED"))))))
+ (should (null (cj/recording--get-available-sinks)))))
+
+(provide 'test-video-audio-recording--get-available-sinks)
+;;; test-video-audio-recording--get-available-sinks.el ends here
diff --git a/tests/test-video-audio-recording--parse-pactl-sinks-verbose.el b/tests/test-video-audio-recording--parse-pactl-sinks-verbose.el
new file mode 100644
index 00000000..8a2cba2d
--- /dev/null
+++ b/tests/test-video-audio-recording--parse-pactl-sinks-verbose.el
@@ -0,0 +1,93 @@
+;;; test-video-audio-recording--parse-pactl-sinks-verbose.el --- Tests for verbose pactl sinks parser -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/recording--parse-pactl-sinks-verbose.
+;; Parses the verbose output of `pactl list sinks' into structured tuples
+;; of (name description mute state).
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+(require 'video-audio-recording)
+
+;;; Helper
+
+(defvar test-sinks--dir
+ (file-name-directory (or load-file-name
+ (locate-library "test-video-audio-recording--parse-pactl-sinks-verbose")))
+ "Directory containing this test file.")
+
+(defun test-sinks--fixture (filename)
+ "Read fixture FILENAME from tests/fixtures/ directory."
+ (let ((path (expand-file-name (concat "fixtures/" filename) test-sinks--dir)))
+ (with-temp-buffer
+ (insert-file-contents path)
+ (buffer-string))))
+
+;;; Normal Cases
+
+(ert-deftest test-parse-pactl-sinks-verbose-normal-multiple-sinks ()
+ "Test parsing multiple sink entries from fixture."
+ (let* ((output (test-sinks--fixture "pactl-sinks-verbose-normal.txt"))
+ (result (cj/recording--parse-pactl-sinks-verbose output)))
+ (should (= 3 (length result)))
+ (should (equal "alsa_output.usb-JDS_Labs-00.analog-stereo" (nth 0 (nth 0 result))))
+ (should (equal "JDS Labs Element IV Analog Stereo" (nth 1 (nth 0 result))))
+ (should (equal "no" (nth 2 (nth 0 result))))
+ (should (equal "RUNNING" (nth 3 (nth 0 result))))))
+
+(ert-deftest test-parse-pactl-sinks-verbose-normal-single-sink ()
+ "Test parsing a single sink entry."
+ (let* ((output "Sink #65\n\tState: SUSPENDED\n\tName: alsa_output.usb-JDS-00.analog-stereo\n\tDescription: JDS Labs Element IV\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sinks-verbose output)))
+ (should (= 1 (length result)))
+ (should (equal "alsa_output.usb-JDS-00.analog-stereo" (nth 0 (car result))))
+ (should (equal "JDS Labs Element IV" (nth 1 (car result))))
+ (should (equal "no" (nth 2 (car result))))
+ (should (equal "SUSPENDED" (nth 3 (car result))))))
+
+(ert-deftest test-parse-pactl-sinks-verbose-normal-muted-sink ()
+ "Test that muted sinks are parsed (filtering is done by caller)."
+ (let* ((output (test-sinks--fixture "pactl-sinks-verbose-muted.txt"))
+ (result (cj/recording--parse-pactl-sinks-verbose output)))
+ (should (= 3 (length result)))
+ ;; Second sink is muted
+ (should (equal "yes" (nth 2 (nth 1 result))))))
+
+;;; Boundary Cases
+
+(ert-deftest test-parse-pactl-sinks-verbose-boundary-empty-input ()
+ "Test that empty input returns empty list."
+ (should (null (cj/recording--parse-pactl-sinks-verbose ""))))
+
+(ert-deftest test-parse-pactl-sinks-verbose-boundary-extra-fields ()
+ "Test that extra fields between sinks are ignored."
+ (let* ((output (concat "Sink #65\n\tState: IDLE\n\tName: sink-a\n\tDescription: Sink A\n\tMute: no\n"
+ "\tDriver: PipeWire\n\tSample Specification: s16le 2ch 48000Hz\n"
+ "Sink #66\n\tState: SUSPENDED\n\tName: sink-b\n\tDescription: Sink B\n\tMute: no\n"))
+ (result (cj/recording--parse-pactl-sinks-verbose output)))
+ (should (= 2 (length result)))
+ (should (equal "sink-a" (nth 0 (car result))))
+ (should (equal "sink-b" (nth 0 (cadr result))))))
+
+(ert-deftest test-parse-pactl-sinks-verbose-boundary-description-with-parens ()
+ "Test descriptions containing parentheses are captured fully."
+ (let* ((output "Sink #91\n\tState: SUSPENDED\n\tName: hdmi-output\n\tDescription: Radeon (HDMI 2) Output\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sinks-verbose output)))
+ (should (equal "Radeon (HDMI 2) Output" (nth 1 (car result))))))
+
+;;; Error Cases
+
+(ert-deftest test-parse-pactl-sinks-verbose-error-malformed-no-name ()
+ "Test that sink entries without Name field are skipped."
+ (let* ((output "Sink #65\n\tState: SUSPENDED\n\tDescription: Orphan\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sinks-verbose output)))
+ (should (null result))))
+
+(provide 'test-video-audio-recording--parse-pactl-sinks-verbose)
+;;; test-video-audio-recording--parse-pactl-sinks-verbose.el ends here
diff --git a/tests/test-video-audio-recording-quick-setup.el b/tests/test-video-audio-recording-quick-setup.el
index 8b46b9b0..1b2e6e53 100644
--- a/tests/test-video-audio-recording-quick-setup.el
+++ b/tests/test-video-audio-recording-quick-setup.el
@@ -2,8 +2,10 @@
;;; Commentary:
;; Unit tests for cj/recording-quick-setup function.
-;; The quick setup shows available mics and auto-selects the default
-;; sink's monitor for system audio capture.
+;; The quick setup is a two-step flow:
+;; Step 1: Pick a microphone
+;; Step 2: Pick an audio output (sink) with active/inactive indicators
+;; The chosen sink's .monitor source is set as the system audio device.
;;; Code:
@@ -34,26 +36,32 @@
"Test that selecting a mic sets cj/recording-mic-device."
(test-quick-setup-setup)
(unwind-protect
- (cl-letf (((symbol-function 'cj/recording--get-available-mics)
- (lambda () '(("jabra-input" . "Jabra SPEAK 510 Mono")
- ("builtin-input" . "Built-in Analog"))))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "jds-labs.monitor"))
- ((symbol-function 'completing-read)
- (lambda (_prompt table &rest _args)
- (car (all-completions "" table)))))
- (cj/recording-quick-setup)
- (should (equal "jabra-input" cj/recording-mic-device)))
+ (let ((call-count 0))
+ (cl-letf (((symbol-function 'cj/recording--get-available-mics)
+ (lambda () '(("jabra-input" . "Jabra SPEAK 510 Mono")
+ ("builtin-input" . "Built-in Analog"))))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("jds-labs" . "JDS Labs Element IV"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
+ ((symbol-function 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (setq call-count (1+ call-count))
+ (car (all-completions "" table)))))
+ (cj/recording-quick-setup)
+ (should (equal "jabra-input" cj/recording-mic-device))))
(test-quick-setup-teardown)))
-(ert-deftest test-video-audio-recording-quick-setup-normal-sets-system-to-default-monitor ()
- "Test that system device is set to the default sink's monitor."
+(ert-deftest test-video-audio-recording-quick-setup-normal-sets-system-to-sink-monitor ()
+ "Test that system device is set to the chosen sink's .monitor."
(test-quick-setup-setup)
(unwind-protect
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
(lambda () '(("jabra-input" . "Jabra SPEAK 510 Mono"))))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "alsa_output.usb-JDS_Labs-00.analog-stereo.monitor"))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("alsa_output.usb-JDS_Labs-00.analog-stereo" . "JDS Labs Element IV"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
((symbol-function 'completing-read)
(lambda (_prompt table &rest _args)
(car (all-completions "" table)))))
@@ -62,26 +70,78 @@
cj/recording-system-device)))
(test-quick-setup-teardown)))
-(ert-deftest test-video-audio-recording-quick-setup-normal-presents-descriptions ()
- "Test that completing-read receives friendly descriptions and Cancel option."
+(ert-deftest test-video-audio-recording-quick-setup-normal-two-completing-reads ()
+ "Test that completing-read is called twice (mic + sink)."
(test-quick-setup-setup)
(unwind-protect
- (let ((presented-candidates nil))
+ (let ((call-count 0))
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
- (lambda () '(("raw-device-1" . "Jabra SPEAK 510 Mono")
- ("raw-device-2" . "Built-in Analog"))))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "default.monitor"))
+ (lambda () '(("mic-1" . "Mic One"))))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("sink-1" . "Sink One"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
((symbol-function 'completing-read)
(lambda (_prompt table &rest _args)
- (setq presented-candidates (all-completions "" table))
- (car presented-candidates))))
+ (setq call-count (1+ call-count))
+ (car (all-completions "" table)))))
(cj/recording-quick-setup)
- ;; Candidates should have friendly descriptions
- (should (member "Jabra SPEAK 510 Mono" presented-candidates))
- (should (member "Built-in Analog" presented-candidates))
- ;; Cancel option should be present
- (should (member "Cancel" presented-candidates))))
+ (should (= 2 call-count))))
+ (test-quick-setup-teardown)))
+
+(ert-deftest test-video-audio-recording-quick-setup-normal-active-sink-indicator ()
+ "Test that active sinks get the active icon in their label."
+ (test-quick-setup-setup)
+ (unwind-protect
+ (let ((sink-candidates nil)
+ (call-count 0))
+ (cl-letf (((symbol-function 'cj/recording--get-available-mics)
+ (lambda () '(("mic-1" . "Mic One"))))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("active-sink" . "Active Sink")
+ ("inactive-sink" . "Inactive Sink"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (name) (equal name "active-sink")))
+ ((symbol-function 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (setq call-count (1+ call-count))
+ (let ((candidates (all-completions "" table)))
+ (when (= call-count 2)
+ (setq sink-candidates candidates))
+ (car candidates)))))
+ (cj/recording-quick-setup)
+ ;; Active sink should have 󰕾 icon (substring-no-properties strips faces but keeps text)
+ (should (cl-some (lambda (c) (and (string-match-p "Active Sink" c)
+ (string-match-p "󰕾" c)))
+ sink-candidates))
+ ;; Inactive sink should have 󰖁 icon
+ (should (cl-some (lambda (c) (and (string-match-p "Inactive Sink" c)
+ (string-match-p "󰖁" c)))
+ sink-candidates))))
+ (test-quick-setup-teardown)))
+
+(ert-deftest test-video-audio-recording-quick-setup-normal-active-sorted-first ()
+ "Test that active sinks are sorted to the top of the list."
+ (test-quick-setup-setup)
+ (unwind-protect
+ (let ((sink-candidates nil))
+ (cl-letf (((symbol-function 'cj/recording--get-available-mics)
+ (lambda () '(("mic-1" . "Mic One"))))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("inactive-sink" . "Inactive Sink")
+ ("active-sink" . "Active Sink"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (name) (equal name "active-sink")))
+ ((symbol-function 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (let ((candidates (all-completions "" table)))
+ (when (cl-some (lambda (c) (string-match-p "Sink" c)) candidates)
+ (setq sink-candidates candidates))
+ (car candidates)))))
+ (cj/recording-quick-setup)
+ ;; First non-Cancel candidate should be the active sink
+ (let ((first-sink (car sink-candidates)))
+ (should (string-match-p "Active Sink" first-sink)))))
(test-quick-setup-teardown)))
(ert-deftest test-video-audio-recording-quick-setup-normal-confirmation-message ()
@@ -91,8 +151,10 @@
(let ((message-text nil))
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
(lambda () '(("jabra-input" . "Jabra SPEAK 510 Mono"))))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "jds-labs.analog-stereo.monitor"))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("jds-labs.analog-stereo" . "JDS Labs Element IV"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
((symbol-function 'completing-read)
(lambda (_prompt table &rest _args)
(car (all-completions "" table))))
@@ -101,8 +163,7 @@
(setq message-text (apply #'format fmt args)))))
(cj/recording-quick-setup)
(should (string-match-p "Recording ready" message-text))
- (should (string-match-p "Jabra SPEAK 510 Mono" message-text))
- (should (string-match-p "default output monitor" message-text))))
+ (should (string-match-p ".monitor" message-text))))
(test-quick-setup-teardown)))
;;; Boundary Cases
@@ -111,30 +172,34 @@
"Test that with only one mic, it still presents selection."
(test-quick-setup-setup)
(unwind-protect
- (let ((read-called nil))
+ (let ((read-called 0))
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
(lambda () '(("sole-mic" . "Only Mic Available"))))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "default.monitor"))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("sole-sink" . "Only Sink"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
((symbol-function 'completing-read)
(lambda (_prompt table &rest _args)
- (setq read-called t)
+ (setq read-called (1+ read-called))
(car (all-completions "" table)))))
(cj/recording-quick-setup)
- (should read-called)
+ (should (= 2 read-called))
(should (equal "sole-mic" cj/recording-mic-device))))
(test-quick-setup-teardown)))
;;; Error Cases
-(ert-deftest test-video-audio-recording-quick-setup-error-cancel-selected ()
- "Test that selecting Cancel signals user-error and does not set devices."
+(ert-deftest test-video-audio-recording-quick-setup-error-cancel-mic ()
+ "Test that cancelling mic selection signals user-error and does not set devices."
(test-quick-setup-setup)
(unwind-protect
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
(lambda () '(("jabra-input" . "Jabra SPEAK 510 Mono"))))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "default.monitor"))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("sink-1" . "Sink One"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
((symbol-function 'completing-read)
(lambda (_prompt _choices &rest _args)
"Cancel")))
@@ -143,14 +208,35 @@
(should (null cj/recording-system-device)))
(test-quick-setup-teardown)))
+(ert-deftest test-video-audio-recording-quick-setup-error-cancel-sink ()
+ "Test that cancelling sink selection signals user-error."
+ (test-quick-setup-setup)
+ (unwind-protect
+ (let ((call-count 0))
+ (cl-letf (((symbol-function 'cj/recording--get-available-mics)
+ (lambda () '(("jabra-input" . "Jabra SPEAK 510 Mono"))))
+ ((symbol-function 'cj/recording--get-available-sinks)
+ (lambda () '(("sink-1" . "Sink One"))))
+ ((symbol-function 'cj/recording--sink-active-p)
+ (lambda (_name) nil))
+ ((symbol-function 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (setq call-count (1+ call-count))
+ (if (= call-count 1)
+ ;; First call: select mic
+ (car (all-completions "" table))
+ ;; Second call: cancel sink
+ "Cancel"))))
+ (should-error (cj/recording-quick-setup) :type 'user-error)
+ (should (null cj/recording-system-device))))
+ (test-quick-setup-teardown)))
+
(ert-deftest test-video-audio-recording-quick-setup-error-no-mics ()
"Test that function signals error when no mics are found."
(test-quick-setup-setup)
(unwind-protect
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
- (lambda () nil))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "default.monitor")))
+ (lambda () nil)))
(should-error (cj/recording-quick-setup) :type 'user-error))
(test-quick-setup-teardown)))
@@ -159,9 +245,7 @@
(test-quick-setup-setup)
(unwind-protect
(cl-letf (((symbol-function 'cj/recording--get-available-mics)
- (lambda () nil))
- ((symbol-function 'cj/recording--get-default-sink-monitor)
- (lambda () "default.monitor")))
+ (lambda () nil)))
(condition-case err
(cj/recording-quick-setup)
(user-error
diff --git a/tests/test-video-audio-recording-validate-system-audio.el b/tests/test-video-audio-recording-validate-system-audio.el
index fd5eafb3..a8aaed12 100644
--- a/tests/test-video-audio-recording-validate-system-audio.el
+++ b/tests/test-video-audio-recording-validate-system-audio.el
@@ -127,10 +127,9 @@
(setq logged (apply #'format fmt args)))))
(cj/recording--validate-system-audio)
;; Echo area should show the warning
- (should (cl-some (lambda (m) (string-match-p "No audio detected" m)) messages))
+ (should (cl-some (lambda (m) (string-match-p "No audio connected" m)) messages))
;; Messages buffer should have diagnostic steps
(should logged)
- (should (string-match-p "C-; r w" logged))
(should (string-match-p "C-; r s" logged))))
(test-validate-teardown)))