From 46af687f2444754657000116178eeb80addd5a52 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 26 Feb 2026 17:46:11 -0600 Subject: feat(recording): show sinks with active audio indicators in quick-setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ...o-audio-recording--parse-pactl-sinks-verbose.el | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/test-video-audio-recording--parse-pactl-sinks-verbose.el (limited to 'tests/test-video-audio-recording--parse-pactl-sinks-verbose.el') 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 -- cgit v1.2.3