summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-integration-recording-modeline-sync.el30
-rw-r--r--tests/test-integration-recording-monitor-capture-interactive.el6
-rw-r--r--tests/test-integration-recording-monitor-capture.el2
-rw-r--r--tests/test-integration-recording-toggle-workflow.el26
-rw-r--r--tests/test-video-audio-recording--get-available-mics.el102
-rw-r--r--tests/test-video-audio-recording--get-default-sink-monitor.el56
-rw-r--r--tests/test-video-audio-recording--parse-pactl-sources-verbose.el79
-rw-r--r--tests/test-video-audio-recording--wait-for-exit.el80
-rw-r--r--tests/test-video-audio-recording-ffmpeg-functions.el87
-rw-r--r--tests/test-video-audio-recording-get-devices.el95
-rw-r--r--tests/test-video-audio-recording-modeline-indicator.el8
-rw-r--r--tests/test-video-audio-recording-process-cleanup.el219
-rw-r--r--tests/test-video-audio-recording-quick-setup-for-calls.el144
-rw-r--r--tests/test-video-audio-recording-quick-setup.el172
-rw-r--r--tests/test-video-audio-recording-test-mic.el2
-rw-r--r--tests/test-video-audio-recording-test-monitor.el2
16 files changed, 764 insertions, 346 deletions
diff --git a/tests/test-integration-recording-modeline-sync.el b/tests/test-integration-recording-modeline-sync.el
index fab442bd..6bb0e99e 100644
--- a/tests/test-integration-recording-modeline-sync.el
+++ b/tests/test-integration-recording-modeline-sync.el
@@ -68,7 +68,7 @@
When user toggles audio recording on:
1. Process is created
-2. Modeline indicator updates to show 🔴Audio
+2. Modeline indicator updates to show 󰍬 (mic icon)
3. State is in sync immediately (not delayed)
Components integrated:
@@ -94,7 +94,7 @@ Validates:
(cj/audio-recording-toggle nil)
;; Immediately after toggle: modeline should show recording
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; Process should be alive
(should (process-live-p cj/audio-recording-ffmpeg-process)))
@@ -127,7 +127,7 @@ Validates:
;; Start recording
(cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; Stop recording
(cj/audio-recording-toggle nil)
@@ -150,7 +150,7 @@ Components integrated:
Validates:
- Video recording follows same sync pattern as audio
-- Modeline shows 🔴Video correctly"
+- Modeline shows 󰃽correctly"
(test-integration-modeline-setup)
(unwind-protect
(cl-letf (((symbol-function 'file-directory-p)
@@ -164,7 +164,7 @@ Validates:
;; Start video
(cj/video-recording-toggle nil)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+ (should (equal " 󰃽 " (cj/recording-modeline-indicator)))
;; Stop video
(cj/video-recording-toggle nil)
@@ -201,15 +201,15 @@ Validates:
;; State 2: Audio only
(cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; State 3: Both
(cj/video-recording-toggle nil)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬󰃽 " (cj/recording-modeline-indicator)))
;; State 4: Video only (stop audio)
(cj/audio-recording-toggle nil)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+ (should (equal " 󰃽 " (cj/recording-modeline-indicator)))
;; State 5: None (stop video)
(cj/video-recording-toggle nil)
@@ -249,7 +249,7 @@ Validates:
(cj/audio-recording-toggle nil)
;; Immediately after start: should show recording
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; Wait for process to exit and sentinel to run
(sit-for 0.3)
@@ -323,7 +323,7 @@ Validates:
(dotimes (_i 5)
;; Start
(cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
(should cj/audio-recording-ffmpeg-process)
;; Stop
@@ -357,23 +357,23 @@ Validates:
;; Start audio
(cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; Add video - modeline should combine
(cj/video-recording-toggle nil)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬󰃽 " (cj/recording-modeline-indicator)))
;; Stop audio - video indicator should persist
(cj/audio-recording-toggle nil)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+ (should (equal " 󰃽 " (cj/recording-modeline-indicator)))
;; Start audio again - should recombine
(cj/audio-recording-toggle nil)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬󰃽 " (cj/recording-modeline-indicator)))
;; Stop video - audio indicator should persist
(cj/video-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; Stop audio - should be empty
(cj/audio-recording-toggle nil)
diff --git a/tests/test-integration-recording-monitor-capture-interactive.el b/tests/test-integration-recording-monitor-capture-interactive.el
index ece8b79e..eb7cbf26 100644
--- a/tests/test-integration-recording-monitor-capture-interactive.el
+++ b/tests/test-integration-recording-monitor-capture-interactive.el
@@ -8,10 +8,10 @@
;; **INTERACTIVE TEST - Run from within Emacs**
;;
;; This test must be run from an interactive Emacs session where recording
-;; devices are already configured (C-; r c).
+;; devices are already configured (C-; r s).
;;
;; USAGE:
-;; 1. Ensure devices are configured: C-; r c
+;; 1. Ensure devices are configured: C-; r s
;; 2. Load this file: M-x load-file RET tests/test-integration-recording-monitor-capture-interactive.el RET
;; 3. Run test: M-x test-recording-monitor-now RET
;;
@@ -79,7 +79,7 @@ This function can be called with M-x to test recording without ERT framework."
(unless (file-exists-p test-recording--test-audio)
(user-error "Test audio file not found: %s" test-recording--test-audio))
(unless (and cj/recording-mic-device cj/recording-system-device)
- (user-error "Recording devices not configured. Run C-; r c first"))
+ (user-error "Recording devices not configured. Run C-; r s first"))
(let ((test-dir (make-temp-file "recording-test-" t))
(recording-file nil)
diff --git a/tests/test-integration-recording-monitor-capture.el b/tests/test-integration-recording-monitor-capture.el
index 7d7c5dfb..9a0c6380 100644
--- a/tests/test-integration-recording-monitor-capture.el
+++ b/tests/test-integration-recording-monitor-capture.el
@@ -16,7 +16,7 @@
;;
;; Requirements:
;; - Audio system must be working (PulseAudio/PipeWire)
-;; - Recording devices must be configured (C-; r c)
+;; - Recording devices must be configured (C-; r s)
;; - paplay must be available
;; - Transcription backend must be configured
;;
diff --git a/tests/test-integration-recording-toggle-workflow.el b/tests/test-integration-recording-toggle-workflow.el
index c61698c5..0e000b68 100644
--- a/tests/test-integration-recording-toggle-workflow.el
+++ b/tests/test-integration-recording-toggle-workflow.el
@@ -10,7 +10,7 @@
;; - cj/audio-recording-toggle (entry point)
;; - cj/video-recording-toggle (entry point)
;; - cj/recording-get-devices (device prompting and setup)
-;; - cj/recording-quick-setup-for-calls (device selection workflow)
+;; - cj/recording-quick-setup (device selection workflow)
;; - cj/ffmpeg-record-audio (process creation and ffmpeg command)
;; - cj/ffmpeg-record-video (process creation and ffmpeg command)
;; - cj/recording-modeline-indicator (UI state display)
@@ -80,7 +80,7 @@ When user presses C-; r a for the first time:
Components integrated:
- cj/audio-recording-toggle (toggles start/stop)
- cj/recording-get-devices (prompts for setup on first use)
-- cj/recording-quick-setup-for-calls (device selection)
+- cj/recording-quick-setup (device selection)
- cj/ffmpeg-record-audio (creates recording process)
- cj/recording-modeline-indicator (UI state)
- cj/audio-recording-stop (cleanup)
@@ -96,10 +96,8 @@ Validates:
(let ((setup-called nil)
(ffmpeg-cmd nil)
(process-created nil))
- ;; Mock the device setup to simulate user choosing quick setup
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t)) ; User says yes to quick setup
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ ;; Mock the device setup to simulate quick setup
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda ()
(setq setup-called t)
(setq cj/recording-mic-device "test-mic")
@@ -130,7 +128,7 @@ Validates:
(should (string-match-p "test-monitor" ffmpeg-cmd))
;; Verify modeline shows recording
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; STEP 2: Second toggle - should stop recording
(cj/audio-recording-toggle nil)
@@ -168,7 +166,7 @@ Validates:
(let ((setup-called nil)
(ffmpeg-cmd nil))
- (cl-letf (((symbol-function 'cj/recording-quick-setup-for-calls)
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda () (setq setup-called t)))
((symbol-function 'file-directory-p)
(lambda (_dir) t))
@@ -207,9 +205,7 @@ Validates:
(test-integration-toggle-setup)
(unwind-protect
(let ((setup-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda ()
(setq setup-called t)
(setq cj/recording-mic-device "test-mic")
@@ -226,7 +222,7 @@ Validates:
;; Verify setup and recording
(should setup-called)
(should cj/video-recording-ffmpeg-process)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+ (should (equal " 󰃽 " (cj/recording-modeline-indicator)))
;; Stop recording
(cj/video-recording-toggle nil)
@@ -271,7 +267,7 @@ Validates:
;; Verify both are recording
(should cj/audio-recording-ffmpeg-process)
(should cj/video-recording-ffmpeg-process)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬󰃽 " (cj/recording-modeline-indicator)))
;; Stop audio only
(cj/audio-recording-toggle nil)
@@ -279,7 +275,7 @@ Validates:
;; Verify only video still recording
(should (null cj/audio-recording-ffmpeg-process))
(should cj/video-recording-ffmpeg-process)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+ (should (equal " 󰃽 " (cj/recording-modeline-indicator)))
;; Stop video
(cj/video-recording-toggle nil)
@@ -329,7 +325,7 @@ Validates:
;; Verify recording started
(should cj/audio-recording-ffmpeg-process)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should (equal " 󰍬 " (cj/recording-modeline-indicator)))
;; Wait for process to exit (sentinel should run)
(sit-for 0.3)
diff --git a/tests/test-video-audio-recording--get-available-mics.el b/tests/test-video-audio-recording--get-available-mics.el
new file mode 100644
index 00000000..2fea0d7a
--- /dev/null
+++ b/tests/test-video-audio-recording--get-available-mics.el
@@ -0,0 +1,102 @@
+;;; test-video-audio-recording--get-available-mics.el --- Tests for mic discovery -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/recording--get-available-mics.
+;; Verifies that available microphones are discovered correctly:
+;; - Monitor sources are excluded (they capture output, not input)
+;; - Muted sources 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-mics--make-pactl-output (sources)
+ "Build fake `pactl list sources' output from SOURCES.
+Each source is (name description mute state)."
+ (mapconcat (lambda (src)
+ (format "Source #1\n\tState: %s\n\tName: %s\n\tDescription: %s\n\tMute: %s\n"
+ (nth 3 src) (nth 0 src) (nth 1 src) (nth 2 src)))
+ sources ""))
+
+;;; Normal Cases
+
+(ert-deftest test-video-audio-recording--get-available-mics-normal-filters-monitors ()
+ "Test that monitor sources are excluded from mic list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-mics--make-pactl-output
+ '(("alsa_input.usb-Jabra.mono" "Jabra Mono" "no" "SUSPENDED")
+ ("alsa_output.usb-Jabra.monitor" "Monitor of Jabra" "no" "SUSPENDED"))))))
+ (let ((mics (cj/recording--get-available-mics)))
+ (should (= 1 (length mics)))
+ (should (equal "alsa_input.usb-Jabra.mono" (car (car mics)))))))
+
+(ert-deftest test-video-audio-recording--get-available-mics-normal-filters-muted ()
+ "Test that muted sources are excluded from mic list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-mics--make-pactl-output
+ '(("active-mic" "Active Mic" "no" "SUSPENDED")
+ ("muted-mic" "Muted Mic" "yes" "SUSPENDED"))))))
+ (let ((mics (cj/recording--get-available-mics)))
+ (should (= 1 (length mics)))
+ (should (equal "active-mic" (car (car mics)))))))
+
+(ert-deftest test-video-audio-recording--get-available-mics-normal-uses-descriptions ()
+ "Test that friendly descriptions are returned as cdr."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-mics--make-pactl-output
+ '(("raw-device-name" "Friendly Device Name" "no" "IDLE"))))))
+ (let ((mics (cj/recording--get-available-mics)))
+ (should (equal "Friendly Device Name" (cdr (car mics)))))))
+
+(ert-deftest test-video-audio-recording--get-available-mics-normal-multiple-mics ()
+ "Test that multiple non-muted, non-monitor mics are returned."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-mics--make-pactl-output
+ '(("mic-a" "Jabra Mono" "no" "SUSPENDED")
+ ("mic-b" "Built-in" "no" "IDLE")
+ ("alsa_output.jabra.monitor" "Monitor of Jabra" "no" "SUSPENDED")
+ ("muted-mic" "Muted Mic" "yes" "SUSPENDED"))))))
+ (let ((mics (cj/recording--get-available-mics)))
+ (should (= 2 (length mics))))))
+
+;;; Boundary Cases
+
+(ert-deftest test-video-audio-recording--get-available-mics-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-mics)))))
+
+(ert-deftest test-video-audio-recording--get-available-mics-boundary-all-monitors ()
+ "Test that if all sources are monitors, returns empty list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-mics--make-pactl-output
+ '(("sink-a.monitor" "Monitor A" "no" "SUSPENDED")
+ ("sink-b.monitor" "Monitor B" "no" "SUSPENDED"))))))
+ (should (null (cj/recording--get-available-mics)))))
+
+(ert-deftest test-video-audio-recording--get-available-mics-boundary-all-muted ()
+ "Test that if all non-monitor sources are muted, returns empty list."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd)
+ (test-mics--make-pactl-output
+ '(("muted-a" "Mic A" "yes" "SUSPENDED")
+ ("muted-b" "Mic B" "yes" "SUSPENDED"))))))
+ (should (null (cj/recording--get-available-mics)))))
+
+(provide 'test-video-audio-recording--get-available-mics)
+;;; test-video-audio-recording--get-available-mics.el ends here
diff --git a/tests/test-video-audio-recording--get-default-sink-monitor.el b/tests/test-video-audio-recording--get-default-sink-monitor.el
new file mode 100644
index 00000000..c5cf1abb
--- /dev/null
+++ b/tests/test-video-audio-recording--get-default-sink-monitor.el
@@ -0,0 +1,56 @@
+;;; test-video-audio-recording--get-default-sink-monitor.el --- Tests for default sink monitor -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/recording--get-default-sink-monitor.
+;; This function returns the monitor source name for the default audio
+;; output, which captures "what you hear" (call audio, music, etc.).
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+(require 'video-audio-recording)
+
+;;; Normal Cases
+
+(ert-deftest test-video-audio-recording--get-default-sink-monitor-normal-appends-monitor ()
+ "Test that .monitor is appended to the default sink name."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd) "alsa_output.usb-JDS_Labs-00.analog-stereo\n")))
+ (should (equal "alsa_output.usb-JDS_Labs-00.analog-stereo.monitor"
+ (cj/recording--get-default-sink-monitor)))))
+
+(ert-deftest test-video-audio-recording--get-default-sink-monitor-normal-trims-whitespace ()
+ "Test that trailing whitespace/newlines are stripped from sink name."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd) " alsa_output.pci-0000.analog-stereo \n")))
+ (should (equal "alsa_output.pci-0000.analog-stereo.monitor"
+ (cj/recording--get-default-sink-monitor)))))
+
+(ert-deftest test-video-audio-recording--get-default-sink-monitor-normal-bluetooth-sink ()
+ "Test with a bluetooth default sink."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd) "bluez_output.AA_BB_CC_DD_EE_FF.a2dp-sink\n")))
+ (should (equal "bluez_output.AA_BB_CC_DD_EE_FF.a2dp-sink.monitor"
+ (cj/recording--get-default-sink-monitor)))))
+
+;;; Error Cases
+
+(ert-deftest test-video-audio-recording--get-default-sink-monitor-error-empty-output ()
+ "Test that empty pactl output signals user-error."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd) "")))
+ (should-error (cj/recording--get-default-sink-monitor) :type 'user-error)))
+
+(ert-deftest test-video-audio-recording--get-default-sink-monitor-error-whitespace-only ()
+ "Test that whitespace-only output signals user-error."
+ (cl-letf (((symbol-function 'shell-command-to-string)
+ (lambda (_cmd) " \n ")))
+ (should-error (cj/recording--get-default-sink-monitor) :type 'user-error)))
+
+(provide 'test-video-audio-recording--get-default-sink-monitor)
+;;; test-video-audio-recording--get-default-sink-monitor.el ends here
diff --git a/tests/test-video-audio-recording--parse-pactl-sources-verbose.el b/tests/test-video-audio-recording--parse-pactl-sources-verbose.el
new file mode 100644
index 00000000..e856f29a
--- /dev/null
+++ b/tests/test-video-audio-recording--parse-pactl-sources-verbose.el
@@ -0,0 +1,79 @@
+;;; test-video-audio-recording--parse-pactl-sources-verbose.el --- Tests for verbose pactl parser -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/recording--parse-pactl-sources-verbose.
+;; Parses the verbose output of `pactl list sources' 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)
+
+;;; Normal Cases
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-normal-single-source ()
+ "Test parsing a single source entry."
+ (let* ((output "Source #65\n\tState: SUSPENDED\n\tName: alsa_input.usb-Jabra-00.mono\n\tDescription: Jabra SPEAK 510 Mono\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sources-verbose output)))
+ (should (= 1 (length result)))
+ (should (equal "alsa_input.usb-Jabra-00.mono" (nth 0 (car result))))
+ (should (equal "Jabra SPEAK 510 Mono" (nth 1 (car result))))
+ (should (equal "no" (nth 2 (car result))))
+ (should (equal "SUSPENDED" (nth 3 (car result))))))
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-normal-multiple-sources ()
+ "Test parsing multiple source entries."
+ (let* ((output (concat "Source #65\n\tState: SUSPENDED\n\tName: device-a\n\tDescription: Device A\n\tMute: no\n"
+ "Source #66\n\tState: RUNNING\n\tName: device-b\n\tDescription: Device B\n\tMute: yes\n"))
+ (result (cj/recording--parse-pactl-sources-verbose output)))
+ (should (= 2 (length result)))
+ (should (equal "device-a" (nth 0 (car result))))
+ (should (equal "Device B" (nth 1 (cadr result))))
+ (should (equal "yes" (nth 2 (cadr result))))))
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-normal-monitors-included ()
+ "Test that monitor sources are parsed (filtering is done by caller)."
+ (let* ((output "Source #67\n\tState: SUSPENDED\n\tName: alsa_output.jds.monitor\n\tDescription: Monitor of JDS Labs\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sources-verbose output)))
+ (should (= 1 (length result)))
+ (should (string-match-p "\\.monitor$" (nth 0 (car result))))))
+
+;;; Boundary Cases
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-boundary-empty-input ()
+ "Test that empty input returns empty list."
+ (should (null (cj/recording--parse-pactl-sources-verbose ""))))
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-boundary-extra-fields ()
+ "Test that extra fields between sources are ignored."
+ (let* ((output (concat "Source #65\n\tState: IDLE\n\tName: dev-a\n\tDescription: Dev A\n\tMute: no\n"
+ "\tDriver: PipeWire\n\tSample Specification: s16le 2ch 48000Hz\n"
+ "\tChannel Map: front-left,front-right\n"
+ "Source #66\n\tState: SUSPENDED\n\tName: dev-b\n\tDescription: Dev B\n\tMute: no\n"))
+ (result (cj/recording--parse-pactl-sources-verbose output)))
+ (should (= 2 (length result)))
+ (should (equal "dev-a" (nth 0 (car result))))
+ (should (equal "dev-b" (nth 0 (cadr result))))))
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-boundary-description-with-parens ()
+ "Test descriptions containing parentheses are captured fully."
+ (let* ((output "Source #91\n\tState: SUSPENDED\n\tName: hdmi.monitor\n\tDescription: Monitor of Radeon (HDMI 2)\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sources-verbose output)))
+ (should (equal "Monitor of Radeon (HDMI 2)" (nth 1 (car result))))))
+
+;;; Error Cases
+
+(ert-deftest test-video-audio-recording--parse-pactl-sources-verbose-error-malformed-no-name ()
+ "Test that source entries without Name field are skipped."
+ (let* ((output "Source #65\n\tState: SUSPENDED\n\tDescription: Orphan\n\tMute: no\n")
+ (result (cj/recording--parse-pactl-sources-verbose output)))
+ (should (null result))))
+
+(provide 'test-video-audio-recording--parse-pactl-sources-verbose)
+;;; test-video-audio-recording--parse-pactl-sources-verbose.el ends here
diff --git a/tests/test-video-audio-recording--wait-for-exit.el b/tests/test-video-audio-recording--wait-for-exit.el
new file mode 100644
index 00000000..adacd828
--- /dev/null
+++ b/tests/test-video-audio-recording--wait-for-exit.el
@@ -0,0 +1,80 @@
+;;; test-video-audio-recording--wait-for-exit.el --- Tests for recording wait-for-exit -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for `cj/recording--wait-for-exit', the function that polls
+;; for process exit instead of using a blind `sit-for' delay.
+;;
+;; This function is critical to the recording fix: without proper waiting,
+;; ffmpeg cannot finalize container metadata, resulting in zero-byte files.
+;;
+;; Tests use real short-lived processes (`true', `sleep') to exercise the
+;; actual polling logic rather than mocking `process-live-p'.
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Now load the actual production module
+(require 'video-audio-recording)
+
+;;; Normal Cases
+
+(ert-deftest test-video-audio-recording--wait-for-exit-normal-process-exits-returns-t ()
+ "Test that wait-for-exit returns t when process exits within timeout.
+Uses `true' which exits immediately."
+ (let ((proc (make-process :name "test-exit" :command '("true"))))
+ (sit-for 0.2) ; Let it exit
+ (should (eq t (cj/recording--wait-for-exit proc 2)))))
+
+(ert-deftest test-video-audio-recording--wait-for-exit-normal-polls-until-exit ()
+ "Test that wait-for-exit polls and returns t after process exits.
+Uses a short sleep to verify polling works across multiple iterations."
+ (let ((proc (make-process :name "test-short" :command '("sleep" "0.2"))))
+ (should (eq t (cj/recording--wait-for-exit proc 3)))))
+
+;;; Boundary Cases
+
+(ert-deftest test-video-audio-recording--wait-for-exit-boundary-already-dead-returns-t ()
+ "Test that wait-for-exit returns t immediately for already-dead process."
+ (let ((proc (make-process :name "test-dead" :command '("true"))))
+ (sit-for 0.2) ; Ensure it's dead
+ (should-not (process-live-p proc))
+ (should (eq t (cj/recording--wait-for-exit proc 1)))))
+
+(ert-deftest test-video-audio-recording--wait-for-exit-boundary-zero-timeout-dead-process ()
+ "Test that zero timeout with dead process returns t."
+ (let ((proc (make-process :name "test-zero-dead" :command '("true"))))
+ (sit-for 0.2)
+ (should (eq t (cj/recording--wait-for-exit proc 0)))))
+
+(ert-deftest test-video-audio-recording--wait-for-exit-boundary-zero-timeout-live-process ()
+ "Test that zero timeout with live process returns nil (timed out)."
+ (let ((proc (make-process :name "test-zero-live" :command '("sleep" "1000"))))
+ (unwind-protect
+ (should (eq nil (cj/recording--wait-for-exit proc 0)))
+ (delete-process proc))))
+
+(ert-deftest test-video-audio-recording--wait-for-exit-boundary-timeout-exceeded-returns-nil ()
+ "Test that wait-for-exit returns nil when timeout is exceeded.
+Uses a long-running process with a very short timeout."
+ (let ((proc (make-process :name "test-timeout" :command '("sleep" "1000"))))
+ (unwind-protect
+ (should (eq nil (cj/recording--wait-for-exit proc 0.2)))
+ (delete-process proc))))
+
+;;; Error Cases
+
+(ert-deftest test-video-audio-recording--wait-for-exit-error-deleted-process-returns-t ()
+ "Test that wait-for-exit handles a deleted (not just exited) process.
+A process killed via `delete-process' should be detected as not live."
+ (let ((proc (make-process :name "test-deleted" :command '("sleep" "1000"))))
+ (delete-process proc)
+ (sit-for 0.1)
+ (should (eq t (cj/recording--wait-for-exit proc 1)))))
+
+(provide 'test-video-audio-recording--wait-for-exit)
+;;; test-video-audio-recording--wait-for-exit.el ends here
diff --git a/tests/test-video-audio-recording-ffmpeg-functions.el b/tests/test-video-audio-recording-ffmpeg-functions.el
index e82614e2..a3bac0cf 100644
--- a/tests/test-video-audio-recording-ffmpeg-functions.el
+++ b/tests/test-video-audio-recording-ffmpeg-functions.el
@@ -166,17 +166,20 @@
;;; Stop Functions - Normal Cases
(ert-deftest test-video-audio-recording-video-stop-normal-interrupts-process ()
- "Test that stopping video recording interrupts the process."
+ "Test that stopping video recording sends SIGINT to process group."
(test-ffmpeg-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
- (interrupt-called nil))
+ (signal-called nil))
(setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (setq interrupt-called t))))
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil))
+ ((symbol-function 'signal-process)
+ (lambda (_pid _sig) (setq signal-called t) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) t)))
(cj/video-recording-stop)
- (should interrupt-called))
- (delete-process fake-process))
+ (should signal-called))
+ (ignore-errors (delete-process fake-process)))
(test-ffmpeg-teardown)))
(ert-deftest test-video-audio-recording-video-stop-normal-clears-variable ()
@@ -205,17 +208,19 @@
(test-ffmpeg-teardown)))
(ert-deftest test-video-audio-recording-audio-stop-normal-interrupts-process ()
- "Test that stopping audio recording interrupts the process."
+ "Test that stopping audio recording sends SIGINT to process group."
(test-ffmpeg-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
- (interrupt-called nil))
+ (signal-called nil))
(setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (setq interrupt-called t))))
+ (cl-letf (((symbol-function 'signal-process)
+ (lambda (_pid _sig) (setq signal-called t) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) t)))
(cj/audio-recording-stop)
- (should interrupt-called))
- (delete-process fake-process))
+ (should signal-called))
+ (ignore-errors (delete-process fake-process)))
(test-ffmpeg-teardown)))
(ert-deftest test-video-audio-recording-audio-stop-normal-clears-variable ()
@@ -257,70 +262,68 @@
;;; Error Cases
-(ert-deftest test-video-audio-recording-video-stop-error-interrupt-process-fails ()
- "Test that video stop handles interrupt-process failure gracefully."
+(ert-deftest test-video-audio-recording-video-stop-error-signal-process-fails ()
+ "Test that video stop handles signal-process failure gracefully."
(test-ffmpeg-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
(error-raised nil))
(setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (error "Interrupt failed"))))
- ;; Should handle the error without crashing
- (condition-case err
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil))
+ ((symbol-function 'signal-process)
+ (lambda (_pid _sig) (error "Signal failed"))))
+ (condition-case _err
(cj/video-recording-stop)
(error (setq error-raised t)))
- ;; Error should propagate (function doesn't catch it)
(should error-raised))
- (delete-process fake-process))
+ (ignore-errors (delete-process fake-process)))
(test-ffmpeg-teardown)))
-(ert-deftest test-video-audio-recording-audio-stop-error-interrupt-process-fails ()
- "Test that audio stop handles interrupt-process failure gracefully."
+(ert-deftest test-video-audio-recording-audio-stop-error-signal-process-fails ()
+ "Test that audio stop handles signal-process failure gracefully."
(test-ffmpeg-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
(error-raised nil))
(setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (error "Interrupt failed"))))
- ;; Should handle the error without crashing
- (condition-case err
+ (cl-letf (((symbol-function 'signal-process)
+ (lambda (_pid _sig) (error "Signal failed"))))
+ (condition-case _err
(cj/audio-recording-stop)
(error (setq error-raised t)))
- ;; Error should propagate (function doesn't catch it)
(should error-raised))
- (delete-process fake-process))
+ (ignore-errors (delete-process fake-process)))
(test-ffmpeg-teardown)))
-(ert-deftest test-video-audio-recording-video-stop-error-dead-process-raises-error ()
- "Test that video stop raises error if process is already dead.
-This documents current behavior - interrupt-process on dead process errors.
-The sentinel should clear the variable before this happens in practice."
+(ert-deftest test-video-audio-recording-video-stop-error-dead-process-handles-gracefully ()
+ "Test that video stop handles already-dead process gracefully.
+The new implementation guards against nil process-id, so stopping a dead
+process should clean up without error."
(test-ffmpeg-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
(setq cj/video-recording-ffmpeg-process fake-process)
- ;; Kill process before calling stop
(delete-process fake-process)
(sit-for 0.1)
- ;; Calling stop on dead process raises error
- (should-error (cj/video-recording-stop)))
+ ;; Should not error - process-id returns nil, guarded by `when pid'
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil)))
+ (cj/video-recording-stop))
+ (should (null cj/video-recording-ffmpeg-process)))
(test-ffmpeg-teardown)))
-(ert-deftest test-video-audio-recording-audio-stop-error-dead-process-raises-error ()
- "Test that audio stop raises error if process is already dead.
-This documents current behavior - interrupt-process on dead process errors.
-The sentinel should clear the variable before this happens in practice."
+(ert-deftest test-video-audio-recording-audio-stop-error-dead-process-handles-gracefully ()
+ "Test that audio stop handles already-dead process gracefully.
+The new implementation guards against nil process-id, so stopping a dead
+process should clean up without error."
(test-ffmpeg-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
(setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Kill process before calling stop
(delete-process fake-process)
(sit-for 0.1)
- ;; Calling stop on dead process raises error
- (should-error (cj/audio-recording-stop)))
+ ;; Should not error - process-id returns nil, guarded by `when pid'
+ (cj/audio-recording-stop)
+ (should (null cj/audio-recording-ffmpeg-process)))
(test-ffmpeg-teardown)))
(ert-deftest test-video-audio-recording-ffmpeg-record-video-boundary-skips-if-already-recording ()
diff --git a/tests/test-video-audio-recording-get-devices.el b/tests/test-video-audio-recording-get-devices.el
index ba7d95b9..0af02bb3 100644
--- a/tests/test-video-audio-recording-get-devices.el
+++ b/tests/test-video-audio-recording-get-devices.el
@@ -4,9 +4,8 @@
;; Unit tests for cj/recording-get-devices function.
;; Tests device prompting and validation workflow.
;;
-;; NOTE: This function was refactored to use interactive prompts instead of
-;; auto-detection. It now prompts the user with y-or-n-p and calls either
-;; cj/recording-quick-setup-for-calls or cj/recording-select-devices.
+;; NOTE: This function goes straight into quick setup (mic selection)
+;; when devices aren't configured — no confirmation dialog.
;;; Code:
@@ -46,29 +45,12 @@
(should (equal "preset-monitor" (cdr result)))))
(test-get-devices-teardown)))
-(ert-deftest test-video-audio-recording-get-devices-normal-prompts-when-not-configured ()
- "Test that function prompts user when devices not configured."
- (test-get-devices-setup)
- (unwind-protect
- (let ((prompt-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) (setq prompt-called t) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq cj/recording-mic-device "quick-mic")
- (setq cj/recording-system-device "quick-monitor"))))
- (cj/recording-get-devices)
- (should prompt-called)))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-normal-calls-quick-setup-on-yes ()
- "Test that function calls quick setup when user answers yes."
+(ert-deftest test-video-audio-recording-get-devices-normal-calls-quick-setup ()
+ "Test that function calls quick setup when devices not configured."
(test-get-devices-setup)
(unwind-protect
(let ((quick-setup-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda ()
(setq quick-setup-called t)
(setq cj/recording-mic-device "quick-mic")
@@ -77,29 +59,11 @@
(should quick-setup-called)))
(test-get-devices-teardown)))
-(ert-deftest test-video-audio-recording-get-devices-normal-calls-select-devices-on-no ()
- "Test that function calls manual selection when user answers no."
- (test-get-devices-setup)
- (unwind-protect
- (let ((select-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) nil))
- ((symbol-function 'cj/recording-select-devices)
- (lambda ()
- (setq select-called t)
- (setq cj/recording-mic-device "manual-mic")
- (setq cj/recording-system-device "manual-monitor"))))
- (cj/recording-get-devices)
- (should select-called)))
- (test-get-devices-teardown)))
-
(ert-deftest test-video-audio-recording-get-devices-normal-returns-cons-cell ()
"Test that function returns (mic . monitor) cons cell."
(test-get-devices-setup)
(unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda ()
(setq cj/recording-mic-device "test-mic")
(setq cj/recording-system-device "test-monitor"))))
@@ -111,40 +75,38 @@
;;; Boundary Cases
-(ert-deftest test-video-audio-recording-get-devices-boundary-only-mic-set-prompts ()
- "Test that function prompts even when only mic is set."
+(ert-deftest test-video-audio-recording-get-devices-boundary-only-mic-set-calls-setup ()
+ "Test that function calls setup even when only mic is set."
(test-get-devices-setup)
(unwind-protect
(progn
(setq cj/recording-mic-device "preset-mic")
(setq cj/recording-system-device nil)
- (let ((prompt-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) (setq prompt-called t) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (let ((quick-setup-called nil))
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda ()
+ (setq quick-setup-called t)
(setq cj/recording-mic-device "new-mic")
(setq cj/recording-system-device "new-monitor"))))
(cj/recording-get-devices)
- (should prompt-called))))
+ (should quick-setup-called))))
(test-get-devices-teardown)))
-(ert-deftest test-video-audio-recording-get-devices-boundary-only-monitor-set-prompts ()
- "Test that function prompts even when only monitor is set."
+(ert-deftest test-video-audio-recording-get-devices-boundary-only-monitor-set-calls-setup ()
+ "Test that function calls setup even when only monitor is set."
(test-get-devices-setup)
(unwind-protect
(progn
(setq cj/recording-mic-device nil)
(setq cj/recording-system-device "preset-monitor")
- (let ((prompt-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) (setq prompt-called t) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (let ((quick-setup-called nil))
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda ()
+ (setq quick-setup-called t)
(setq cj/recording-mic-device "new-mic")
(setq cj/recording-system-device "new-monitor"))))
(cj/recording-get-devices)
- (should prompt-called))))
+ (should quick-setup-called))))
(test-get-devices-teardown)))
;;; Error Cases
@@ -153,9 +115,7 @@
"Test that function signals error when setup fails to set devices."
(test-get-devices-setup)
(unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda () nil))) ;; Setup fails - doesn't set devices
(should-error (cj/recording-get-devices) :type 'user-error))
(test-get-devices-teardown)))
@@ -164,27 +124,14 @@
"Test that error message guides user to setup commands."
(test-get-devices-setup)
(unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
+ (cl-letf (((symbol-function 'cj/recording-quick-setup)
(lambda () nil)))
(condition-case err
(cj/recording-get-devices)
(user-error
- (should (string-match-p "C-; r c" (error-message-string err)))
+ (should (string-match-p "C-; r s" (error-message-string err)))
(should (string-match-p "C-; r s" (error-message-string err))))))
(test-get-devices-teardown)))
-(ert-deftest test-video-audio-recording-get-devices-error-select-devices-fails ()
- "Test that function signals error when manual selection fails."
- (test-get-devices-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) nil))
- ((symbol-function 'cj/recording-select-devices)
- (lambda () nil))) ;; Manual selection fails
- (should-error (cj/recording-get-devices) :type 'user-error))
- (test-get-devices-teardown)))
-
(provide 'test-video-audio-recording-get-devices)
;;; test-video-audio-recording-get-devices.el ends here
diff --git a/tests/test-video-audio-recording-modeline-indicator.el b/tests/test-video-audio-recording-modeline-indicator.el
index f7f3bbff..f717af7f 100644
--- a/tests/test-video-audio-recording-modeline-indicator.el
+++ b/tests/test-video-audio-recording-modeline-indicator.el
@@ -45,7 +45,7 @@
(let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
(setq cj/audio-recording-ffmpeg-process fake-process)
(let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴Audio " result)))
+ (should (equal " 󰍬 " result)))
(delete-process fake-process))
(test-modeline-indicator-teardown)))
@@ -56,7 +56,7 @@
(let ((fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
(setq cj/video-recording-ffmpeg-process fake-process)
(let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴Video " result)))
+ (should (equal " 󰃽 " result)))
(delete-process fake-process))
(test-modeline-indicator-teardown)))
@@ -69,7 +69,7 @@
(setq cj/audio-recording-ffmpeg-process audio-proc)
(setq cj/video-recording-ffmpeg-process video-proc)
(let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴A+V " result)))
+ (should (equal " 󰍬󰃽 " result)))
(delete-process audio-proc)
(delete-process video-proc))
(test-modeline-indicator-teardown)))
@@ -115,7 +115,7 @@
(delete-process dead-proc)
(sit-for 0.1)
(let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴Video " result)))
+ (should (equal " 󰃽 " result)))
(delete-process alive-proc))
(test-modeline-indicator-teardown)))
diff --git a/tests/test-video-audio-recording-process-cleanup.el b/tests/test-video-audio-recording-process-cleanup.el
index 10b17f2c..d1cd442c 100644
--- a/tests/test-video-audio-recording-process-cleanup.el
+++ b/tests/test-video-audio-recording-process-cleanup.el
@@ -1,11 +1,14 @@
;;; test-video-audio-recording-process-cleanup.el --- Tests for recording process cleanup -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests that verify proper cleanup of recording processes, especially
-;; wf-recorder on Wayland which can become orphaned if not explicitly killed.
+;; Tests that verify proper cleanup of recording processes.
;;
-;; Unit tests verify pkill is called at the right times.
-;; Integration tests verify no orphan processes remain after stop.
+;; Key behaviors tested:
+;; - SIGINT is sent to the process group (negative PID), not just the shell
+;; - Video stop waits for the process to actually exit before declaring done
+;; - On Wayland, wf-recorder is killed AFTER ffmpeg exits (safety net)
+;; - On X11, pkill is not called (no wf-recorder involved)
+;; - Starting a recording cleans up orphan wf-recorder from previous crashes
;;; Code:
@@ -38,29 +41,89 @@
(setq cj/recording-mic-device nil)
(setq cj/recording-system-device nil))
-;;; Unit Tests - Verify pkill is called
+;;; Unit Tests - Signal delivery
-(ert-deftest test-video-recording-stop-wayland-calls-pkill-wf-recorder ()
- "Test that stopping video on Wayland calls pkill to kill wf-recorder."
+(ert-deftest test-video-recording-stop-sends-sigint-to-process-group ()
+ "Test that stopping sends SIGINT to the process group via negative PID."
(test-cleanup-setup)
(unwind-protect
(let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
- (pkill-called nil)
- (pkill-args nil))
+ (signaled-pid nil)
+ (signaled-sig nil))
+ (setq cj/video-recording-ffmpeg-process fake-process)
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil))
+ ((symbol-function 'signal-process)
+ (lambda (pid sig)
+ (setq signaled-pid pid)
+ (setq signaled-sig sig)
+ 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) t)))
+ (cj/video-recording-stop)
+ ;; Should send signal 2 (SIGINT) to negative PID (process group)
+ (should signaled-pid)
+ (should (< signaled-pid 0))
+ (should (= signaled-sig 2)))
+ (ignore-errors (delete-process fake-process)))
+ (test-cleanup-teardown)))
+
+;;; Unit Tests - Wayland wf-recorder cleanup
+
+(ert-deftest test-video-recording-stop-wayland-kills-wf-recorder-before-signal ()
+ "Test that on Wayland, pkill wf-recorder is called BEFORE signal-process.
+Orderly pipeline shutdown requires killing the producer (wf-recorder) first
+so ffmpeg sees EOF on its video input pipe and starts finalizing the file."
+ (test-cleanup-setup)
+ (unwind-protect
+ (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
+ (call-order nil))
(setq cj/video-recording-ffmpeg-process fake-process)
(cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () t))
((symbol-function 'call-process)
(lambda (program &rest args)
(when (equal program "pkill")
- (setq pkill-called t)
- (setq pkill-args args))
+ (push (cons 'pkill args) call-order))
+ 0))
+ ((symbol-function 'signal-process)
+ (lambda (_pid _sig)
+ (push 'signal call-order)
0))
- ((symbol-function 'interrupt-process) (lambda (_proc) nil)))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) t)))
(cj/video-recording-stop)
- (should pkill-called)
- ;; Should call: pkill -INT wf-recorder
- (should (member "-INT" pkill-args))
- (should (member "wf-recorder" pkill-args)))
+ ;; call-order is reversed (push adds to front), so last element is first call
+ (let ((order (reverse call-order)))
+ ;; First call should be pkill (kill producer first)
+ (should (consp (car order)))
+ (should (eq 'pkill (caar order)))
+ ;; signal-process should come after pkill
+ (should (eq 'signal (cadr order)))))
+ (ignore-errors (delete-process fake-process)))
+ (test-cleanup-teardown)))
+
+(ert-deftest test-video-recording-stop-wayland-calls-pkill-with-correct-args ()
+ "Test that Wayland stop calls pkill -INT wf-recorder."
+ (test-cleanup-setup)
+ (unwind-protect
+ (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
+ (pkill-args-list nil))
+ (setq cj/video-recording-ffmpeg-process fake-process)
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () t))
+ ((symbol-function 'call-process)
+ (lambda (program &rest args)
+ (when (equal program "pkill")
+ (push args pkill-args-list))
+ 0))
+ ((symbol-function 'signal-process) (lambda (_pid _sig) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) t)))
+ (cj/video-recording-stop)
+ ;; Should call pkill at least once with -INT wf-recorder
+ (should pkill-args-list)
+ (should (cl-some (lambda (args)
+ (and (member "-INT" args)
+ (member "wf-recorder" args)))
+ pkill-args-list)))
(ignore-errors (delete-process fake-process)))
(test-cleanup-teardown)))
@@ -77,13 +140,16 @@
(when (equal program "pkill")
(setq pkill-called t))
0))
- ((symbol-function 'interrupt-process) (lambda (_proc) nil)))
+ ((symbol-function 'signal-process) (lambda (_pid _sig) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) t)))
(cj/video-recording-stop)
- ;; Should NOT call pkill on X11
(should-not pkill-called))
(ignore-errors (delete-process fake-process)))
(test-cleanup-teardown)))
+;;; Unit Tests - Start cleans orphans
+
(ert-deftest test-video-recording-start-wayland-kills-orphans ()
"Test that starting video on Wayland kills orphan wf-recorder processes."
(test-cleanup-setup)
@@ -102,7 +168,6 @@
(lambda (_name _buffer _command)
(make-process :name "fake-video" :command '("sleep" "1000")))))
(cj/ffmpeg-record-video video-recordings-dir)
- ;; Should call pkill to clean up orphans before starting
(should pkill-called)
(should (member "-INT" pkill-args))
(should (member "wf-recorder" pkill-args))))
@@ -123,10 +188,95 @@
(lambda (_name _buffer _command)
(make-process :name "fake-video" :command '("sleep" "1000")))))
(cj/ffmpeg-record-video video-recordings-dir)
- ;; Should NOT call pkill on X11
(should-not pkill-called)))
(test-cleanup-teardown)))
+;;; Unit Tests - Wait for exit
+
+(ert-deftest test-video-recording-stop-waits-for-process-exit ()
+ "Test that stop waits for the process to exit instead of blind sit-for."
+ (test-cleanup-setup)
+ (unwind-protect
+ (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
+ (wait-called nil)
+ (wait-timeout nil))
+ (setq cj/video-recording-ffmpeg-process fake-process)
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil))
+ ((symbol-function 'signal-process) (lambda (_pid _sig) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc timeout)
+ (setq wait-called t)
+ (setq wait-timeout timeout)
+ t)))
+ (cj/video-recording-stop)
+ (should wait-called)
+ ;; Video uses 5 second timeout for MKV finalization
+ (should (= wait-timeout 5)))
+ (ignore-errors (delete-process fake-process)))
+ (test-cleanup-teardown)))
+
+(ert-deftest test-video-recording-stop-timeout-shows-warning ()
+ "Test that video stop shows warning when process doesn't exit in time."
+ (test-cleanup-setup)
+ (unwind-protect
+ (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
+ (warning-shown nil))
+ (setq cj/video-recording-ffmpeg-process fake-process)
+ (cl-letf (((symbol-function 'cj/recording--wayland-p) (lambda () nil))
+ ((symbol-function 'signal-process) (lambda (_pid _sig) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) nil)) ; Simulate timeout
+ ((symbol-function 'message)
+ (lambda (fmt &rest args)
+ (let ((msg (apply #'format fmt args)))
+ (when (string-match-p "did not exit" msg)
+ (setq warning-shown t))))))
+ (cj/video-recording-stop)
+ (should warning-shown))
+ (ignore-errors (delete-process fake-process)))
+ (test-cleanup-teardown)))
+
+(ert-deftest test-audio-recording-stop-timeout-shows-warning ()
+ "Test that audio stop shows warning when process doesn't exit in time."
+ (test-cleanup-setup)
+ (unwind-protect
+ (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
+ (warning-shown nil))
+ (setq cj/audio-recording-ffmpeg-process fake-process)
+ (cl-letf (((symbol-function 'signal-process) (lambda (_pid _sig) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc _timeout) nil)) ; Simulate timeout
+ ((symbol-function 'message)
+ (lambda (fmt &rest args)
+ (let ((msg (apply #'format fmt args)))
+ (when (string-match-p "did not exit" msg)
+ (setq warning-shown t))))))
+ (cj/audio-recording-stop)
+ (should warning-shown))
+ (ignore-errors (delete-process fake-process)))
+ (test-cleanup-teardown)))
+
+(ert-deftest test-audio-recording-stop-waits-for-process-exit ()
+ "Test that audio stop waits for the process to exit."
+ (test-cleanup-setup)
+ (unwind-protect
+ (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
+ (wait-called nil)
+ (wait-timeout nil))
+ (setq cj/audio-recording-ffmpeg-process fake-process)
+ (cl-letf (((symbol-function 'signal-process) (lambda (_pid _sig) 0))
+ ((symbol-function 'cj/recording--wait-for-exit)
+ (lambda (_proc timeout)
+ (setq wait-called t)
+ (setq wait-timeout timeout)
+ t)))
+ (cj/audio-recording-stop)
+ (should wait-called)
+ ;; Audio uses 3 second timeout for M4A finalization
+ (should (= wait-timeout 3)))
+ (ignore-errors (delete-process fake-process)))
+ (test-cleanup-teardown)))
+
;;; Integration Tests - Verify actual process cleanup
;;; These tests require wf-recorder to be installed and a Wayland session
@@ -150,21 +300,13 @@ This is an integration test that requires wf-recorder and Wayland."
(test-cleanup-setup)
(unwind-protect
(let ((initial-count (test-cleanup--count-wf-recorder-processes)))
- ;; Start recording
(cj/ffmpeg-record-video video-recordings-dir)
- (sit-for 1.0) ; Let it start
-
- ;; Should have at least one wf-recorder running
+ (sit-for 1.0)
(should (> (test-cleanup--count-wf-recorder-processes) initial-count))
-
- ;; Stop recording
(cj/video-recording-stop)
- (sit-for 1.0) ; Let cleanup complete
-
- ;; Should be back to initial count (no orphans)
+ (sit-for 1.0)
(should (= (test-cleanup--count-wf-recorder-processes) initial-count)))
(test-cleanup-teardown)
- ;; Extra cleanup in case test failed mid-way
(ignore-errors (call-process "pkill" nil nil nil "-INT" "wf-recorder"))))
(ert-deftest test-integration-video-recording-start-cleans-orphans ()
@@ -179,28 +321,17 @@ This is an integration test that requires wf-recorder and Wayland."
;; Create an orphan wf-recorder (simulating a crash)
(start-process "orphan-wf" nil "wf-recorder" "-c" "libx264" "-m" "matroska" "-f" "/dev/null")
(sit-for 0.5)
-
(let ((orphan-pids (test-cleanup--get-wf-recorder-pids)))
- (should orphan-pids) ; Verify orphan was created
-
- ;; Start recording - should clean up orphan
+ (should orphan-pids)
(cj/ffmpeg-record-video video-recordings-dir)
(sit-for 1.0)
-
- ;; The orphan PIDs should no longer exist
(let ((current-pids (test-cleanup--get-wf-recorder-pids)))
- ;; Old orphan should be gone (new recording process may exist)
(dolist (orphan-pid orphan-pids)
- ;; Check if old orphan is still in current pids
- ;; It's OK if there's a NEW wf-recorder, just not the orphan
(let ((still-running (member orphan-pid current-pids)))
(should-not still-running))))
-
- ;; Clean up the recording we started
(cj/video-recording-stop)
(sit-for 0.5)))
(test-cleanup-teardown)
- ;; Extra cleanup
(ignore-errors (call-process "pkill" nil nil nil "-INT" "wf-recorder"))))
(ert-deftest test-integration-video-recording-multiple-start-stop-cycles ()
@@ -212,17 +343,13 @@ This is an integration test that requires wf-recorder and Wayland."
(test-cleanup-setup)
(unwind-protect
(let ((initial-count (test-cleanup--count-wf-recorder-processes)))
- ;; Do 3 start/stop cycles
(dotimes (_ 3)
(cj/ffmpeg-record-video video-recordings-dir)
(sit-for 0.5)
(cj/video-recording-stop)
(sit-for 0.5))
-
- ;; Should be back to initial count - no accumulated orphans
(should (= (test-cleanup--count-wf-recorder-processes) initial-count)))
(test-cleanup-teardown)
- ;; Extra cleanup
(ignore-errors (call-process "pkill" nil nil nil "-INT" "wf-recorder"))))
(provide 'test-video-audio-recording-process-cleanup)
diff --git a/tests/test-video-audio-recording-quick-setup-for-calls.el b/tests/test-video-audio-recording-quick-setup-for-calls.el
deleted file mode 100644
index 70980ad4..00000000
--- a/tests/test-video-audio-recording-quick-setup-for-calls.el
+++ /dev/null
@@ -1,144 +0,0 @@
-;;; test-video-audio-recording-quick-setup-for-calls.el --- Tests for cj/recording-quick-setup-for-calls -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-quick-setup-for-calls function.
-;; Tests quick device setup workflow for call recording.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-quick-setup-setup ()
- "Reset device variables before each test."
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-(defun test-quick-setup-teardown ()
- "Clean up device variables after each test."
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-normal-sets-both-devices ()
- "Test that function sets both mic and system device variables."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Bluetooth Headset" . ("bluez_input.00:1B:66" . "bluez_output.00_1B_66.monitor")))))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Bluetooth Headset")))
- (cj/recording-quick-setup-for-calls)
- (should (equal "bluez_input.00:1B:66" cj/recording-mic-device))
- (should (equal "bluez_output.00_1B_66.monitor" cj/recording-system-device))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-normal-presents-friendly-names ()
- "Test that function presents friendly device names to user."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Jabra SPEAK 510 USB" . ("usb-input" . "usb-monitor"))
- ("Built-in Audio" . ("pci-input" . "pci-monitor"))))
- (presented-choices nil))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (setq presented-choices choices)
- (car choices))))
- (cj/recording-quick-setup-for-calls)
- (should (member "Jabra SPEAK 510 USB" presented-choices))
- (should (member "Built-in Audio" presented-choices))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-normal-displays-confirmation ()
- "Test that function displays confirmation message with device details."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Bluetooth Headset" . ("bluez_input.00:1B:66" . "bluez_output.00_1B_66.monitor"))))
- (message-text nil))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Bluetooth Headset"))
- ((symbol-function 'message)
- (lambda (fmt &rest args) (setq message-text (apply #'format fmt args)))))
- (cj/recording-quick-setup-for-calls)
- (should (string-match-p "Call recording ready" message-text))
- (should (string-match-p "Bluetooth Headset" message-text))))
- (test-quick-setup-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-boundary-single-device-no-prompt ()
- "Test that with single device, selection still happens."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Built-in Audio" . ("pci-input" . "pci-monitor")))))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Built-in Audio")))
- (cj/recording-quick-setup-for-calls)
- (should (equal "pci-input" cj/recording-mic-device))
- (should (equal "pci-monitor" cj/recording-system-device))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-boundary-device-name-with-special-chars ()
- "Test that device names with special characters are handled correctly."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Device (USB-C)" . ("special-input" . "special-monitor")))))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Device (USB-C)")))
- (cj/recording-quick-setup-for-calls)
- (should (equal "special-input" cj/recording-mic-device))
- (should (equal "special-monitor" cj/recording-system-device))))
- (test-quick-setup-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-error-no-devices-signals-error ()
- "Test that function signals user-error when no complete devices are found."
- (test-quick-setup-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () nil)))
- (should-error (cj/recording-quick-setup-for-calls) :type 'user-error))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-error-message-mentions-both-devices ()
- "Test that error message mentions need for both mic and monitor."
- (test-quick-setup-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () nil)))
- (condition-case err
- (cj/recording-quick-setup-for-calls)
- (user-error
- (should (string-match-p "both mic and monitor" (error-message-string err))))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-error-empty-device-list ()
- "Test that empty device list from grouping is handled gracefully."
- (test-quick-setup-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () '())))
- (should-error (cj/recording-quick-setup-for-calls) :type 'user-error))
- (test-quick-setup-teardown)))
-
-(provide 'test-video-audio-recording-quick-setup-for-calls)
-;;; test-video-audio-recording-quick-setup-for-calls.el ends here
diff --git a/tests/test-video-audio-recording-quick-setup.el b/tests/test-video-audio-recording-quick-setup.el
new file mode 100644
index 00000000..8b46b9b0
--- /dev/null
+++ b/tests/test-video-audio-recording-quick-setup.el
@@ -0,0 +1,172 @@
+;;; test-video-audio-recording-quick-setup.el --- Tests for cj/recording-quick-setup -*- lexical-binding: t; -*-
+
+;;; 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.
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Now load the actual production module
+(require 'video-audio-recording)
+
+;;; Setup and Teardown
+
+(defun test-quick-setup-setup ()
+ "Reset device variables before each test."
+ (setq cj/recording-mic-device nil)
+ (setq cj/recording-system-device nil))
+
+(defun test-quick-setup-teardown ()
+ "Clean up device variables after each test."
+ (setq cj/recording-mic-device nil)
+ (setq cj/recording-system-device nil))
+
+;;; Normal Cases
+
+(ert-deftest test-video-audio-recording-quick-setup-normal-sets-mic-device ()
+ "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)))
+ (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."
+ (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 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (car (all-completions "" table)))))
+ (cj/recording-quick-setup)
+ (should (equal "alsa_output.usb-JDS_Labs-00.analog-stereo.monitor"
+ 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."
+ (test-quick-setup-setup)
+ (unwind-protect
+ (let ((presented-candidates nil))
+ (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"))
+ ((symbol-function 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (setq presented-candidates (all-completions "" table))
+ (car presented-candidates))))
+ (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))))
+ (test-quick-setup-teardown)))
+
+(ert-deftest test-video-audio-recording-quick-setup-normal-confirmation-message ()
+ "Test that confirmation message mentions the selected mic and monitor."
+ (test-quick-setup-setup)
+ (unwind-protect
+ (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 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (car (all-completions "" table))))
+ ((symbol-function 'message)
+ (lambda (fmt &rest args)
+ (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))))
+ (test-quick-setup-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-video-audio-recording-quick-setup-boundary-single-mic ()
+ "Test that with only one mic, it still presents selection."
+ (test-quick-setup-setup)
+ (unwind-protect
+ (let ((read-called nil))
+ (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 'completing-read)
+ (lambda (_prompt table &rest _args)
+ (setq read-called t)
+ (car (all-completions "" table)))))
+ (cj/recording-quick-setup)
+ (should 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."
+ (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 'completing-read)
+ (lambda (_prompt _choices &rest _args)
+ "Cancel")))
+ (should-error (cj/recording-quick-setup) :type 'user-error)
+ (should (null cj/recording-mic-device))
+ (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")))
+ (should-error (cj/recording-quick-setup) :type 'user-error))
+ (test-quick-setup-teardown)))
+
+(ert-deftest test-video-audio-recording-quick-setup-error-no-mics-message ()
+ "Test that error message mentions mic and unmuted."
+ (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")))
+ (condition-case err
+ (cj/recording-quick-setup)
+ (user-error
+ (should (string-match-p "mic" (error-message-string err))))))
+ (test-quick-setup-teardown)))
+
+(provide 'test-video-audio-recording-quick-setup)
+;;; test-video-audio-recording-quick-setup.el ends here
diff --git a/tests/test-video-audio-recording-test-mic.el b/tests/test-video-audio-recording-test-mic.el
index 5aa794bb..60b9eb0b 100644
--- a/tests/test-video-audio-recording-test-mic.el
+++ b/tests/test-video-audio-recording-test-mic.el
@@ -124,7 +124,7 @@
(condition-case err
(cj/recording-test-mic)
(user-error
- (should (string-match-p "C-; r c" (error-message-string err))))))
+ (should (string-match-p "C-; r s" (error-message-string err))))))
(test-mic-teardown)))
(ert-deftest test-video-audio-recording-test-mic-error-ffmpeg-failure-handled ()
diff --git a/tests/test-video-audio-recording-test-monitor.el b/tests/test-video-audio-recording-test-monitor.el
index f1476577..d821600f 100644
--- a/tests/test-video-audio-recording-test-monitor.el
+++ b/tests/test-video-audio-recording-test-monitor.el
@@ -125,7 +125,7 @@
(condition-case err
(cj/recording-test-monitor)
(user-error
- (should (string-match-p "C-; r c" (error-message-string err))))))
+ (should (string-match-p "C-; r s" (error-message-string err))))))
(test-monitor-teardown)))
(ert-deftest test-video-audio-recording-test-monitor-error-ffmpeg-failure-handled ()