summaryrefslogtreecommitdiff
path: root/tests/test-integration-recording-modeline-sync.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-14 02:35:00 -0600
committerCraig Jennings <c@cjennings.net>2025-11-14 02:35:00 -0600
commit0febc3bb65462fd95a75387c2ec97ffa374efc00 (patch)
tree95ff3ba5c3ab2da2a889530432d75e69ed993efa /tests/test-integration-recording-modeline-sync.el
parent9d55ed149e100b4fb3ef6f5a79d263dcb26ce835 (diff)
Revert "checking in modified/removed tests and other misc changes"
This reverts commit 9d55ed149e100b4fb3ef6f5a79d263dcb26ce835.
Diffstat (limited to 'tests/test-integration-recording-modeline-sync.el')
-rw-r--r--tests/test-integration-recording-modeline-sync.el384
1 files changed, 384 insertions, 0 deletions
diff --git a/tests/test-integration-recording-modeline-sync.el b/tests/test-integration-recording-modeline-sync.el
new file mode 100644
index 00000000..fab442bd
--- /dev/null
+++ b/tests/test-integration-recording-modeline-sync.el
@@ -0,0 +1,384 @@
+;;; test-integration-recording-modeline-sync.el --- Integration tests for modeline sync -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Integration tests validating that the modeline indicator NEVER desyncs
+;; from the actual recording state throughout the entire toggle lifecycle.
+;;
+;; This tests the critical requirement: modeline must always accurately
+;; reflect whether recording is happening, with NO desyncs.
+;;
+;; Components integrated:
+;; - cj/audio-recording-toggle (state changes)
+;; - cj/video-recording-toggle (state changes)
+;; - cj/recording-modeline-indicator (UI state display)
+;; - cj/ffmpeg-record-audio (process creation)
+;; - cj/ffmpeg-record-video (process creation)
+;; - cj/recording-process-sentinel (auto-updates modeline)
+;; - cj/audio-recording-stop (cleanup triggers update)
+;; - cj/video-recording-stop (cleanup triggers update)
+;; - force-mode-line-update (explicit refresh calls)
+;;
+;; Validates:
+;; - Modeline updates immediately on toggle start
+;; - Modeline updates immediately on toggle stop
+;; - Modeline updates when sentinel runs (process dies)
+;; - Modeline shows correct state for audio, video, or both
+;; - Modeline never shows stale state
+;; - process-live-p check prevents desync on dead processes
+
+;;; Code:
+
+(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Stub directory variables
+(defvar video-recordings-dir "/tmp/video-recordings/")
+(defvar audio-recordings-dir "/tmp/audio-recordings/")
+
+;; Now load the actual production module
+(require 'video-audio-recording)
+
+;;; Setup and Teardown
+
+(defun test-integration-modeline-setup ()
+ "Reset all variables before each test."
+ (setq cj/video-recording-ffmpeg-process nil)
+ (setq cj/audio-recording-ffmpeg-process nil)
+ (setq cj/recording-mic-device "test-mic")
+ (setq cj/recording-system-device "test-monitor"))
+
+(defun test-integration-modeline-teardown ()
+ "Clean up after each test."
+ (when cj/video-recording-ffmpeg-process
+ (ignore-errors (delete-process cj/video-recording-ffmpeg-process)))
+ (when cj/audio-recording-ffmpeg-process
+ (ignore-errors (delete-process cj/audio-recording-ffmpeg-process)))
+ (setq cj/video-recording-ffmpeg-process nil)
+ (setq cj/audio-recording-ffmpeg-process nil)
+ (setq cj/recording-mic-device nil)
+ (setq cj/recording-system-device nil))
+
+;;; Integration Tests - Modeline Sync on Toggle
+
+(ert-deftest test-integration-recording-modeline-sync-audio-start-updates-immediately ()
+ "Test that modeline updates immediately when audio recording starts.
+
+When user toggles audio recording on:
+1. Process is created
+2. Modeline indicator updates to show 🔴Audio
+3. State is in sync immediately (not delayed)
+
+Components integrated:
+- cj/audio-recording-toggle
+- cj/ffmpeg-record-audio (calls force-mode-line-update)
+- cj/recording-modeline-indicator
+
+Validates:
+- Modeline syncs on start
+- No delay or race condition"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ (make-process :name name :command '("sleep" "1000")))))
+
+ ;; Before toggle: no recording
+ (should (equal "" (cj/recording-modeline-indicator)))
+
+ ;; Toggle on
+ (cj/audio-recording-toggle nil)
+
+ ;; Immediately after toggle: modeline should show recording
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+
+ ;; Process should be alive
+ (should (process-live-p cj/audio-recording-ffmpeg-process)))
+ (test-integration-modeline-teardown)))
+
+(ert-deftest test-integration-recording-modeline-sync-audio-stop-updates-immediately ()
+ "Test that modeline updates immediately when audio recording stops.
+
+When user toggles audio recording off:
+1. Process is interrupted
+2. Variable is cleared
+3. Modeline indicator updates to show empty
+4. State is in sync immediately
+
+Components integrated:
+- cj/audio-recording-toggle (stop path)
+- cj/audio-recording-stop (calls force-mode-line-update)
+- cj/recording-modeline-indicator
+
+Validates:
+- Modeline syncs on stop
+- No stale indicator after stop"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ (make-process :name name :command '("sleep" "1000")))))
+
+ ;; Start recording
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+
+ ;; Stop recording
+ (cj/audio-recording-toggle nil)
+
+ ;; Immediately after stop: modeline should be empty
+ (should (equal "" (cj/recording-modeline-indicator)))
+
+ ;; Process should be nil
+ (should (null cj/audio-recording-ffmpeg-process)))
+ (test-integration-modeline-teardown)))
+
+(ert-deftest test-integration-recording-modeline-sync-video-lifecycle ()
+ "Test modeline sync through complete video recording lifecycle.
+
+Components integrated:
+- cj/video-recording-toggle (both start and stop)
+- cj/ffmpeg-record-video
+- cj/video-recording-stop
+- cj/recording-modeline-indicator
+
+Validates:
+- Video recording follows same sync pattern as audio
+- Modeline shows 🔴Video correctly"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ (make-process :name name :command '("sleep" "1000")))))
+
+ ;; Initial state
+ (should (equal "" (cj/recording-modeline-indicator)))
+
+ ;; Start video
+ (cj/video-recording-toggle nil)
+ (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+
+ ;; Stop video
+ (cj/video-recording-toggle nil)
+ (should (equal "" (cj/recording-modeline-indicator))))
+ (test-integration-modeline-teardown)))
+
+;;; Integration Tests - Modeline Sync with Both Recordings
+
+(ert-deftest test-integration-recording-modeline-sync-both-recordings-transitions ()
+ "Test modeline sync through all possible state transitions.
+
+Tests transitions:
+- none → audio → both → video → none
+- Validates modeline updates at every transition
+
+Components integrated:
+- cj/audio-recording-toggle
+- cj/video-recording-toggle
+- cj/recording-modeline-indicator (handles all states)
+
+Validates:
+- Modeline accurately reflects all combinations
+- Transitions are clean with no stale state"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ (make-process :name name :command '("sleep" "1000")))))
+
+ ;; State 1: None
+ (should (equal "" (cj/recording-modeline-indicator)))
+
+ ;; State 2: Audio only
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+
+ ;; State 3: Both
+ (cj/video-recording-toggle nil)
+ (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+
+ ;; State 4: Video only (stop audio)
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+
+ ;; State 5: None (stop video)
+ (cj/video-recording-toggle nil)
+ (should (equal "" (cj/recording-modeline-indicator))))
+ (test-integration-modeline-teardown)))
+
+;;; Integration Tests - Modeline Sync with Sentinel
+
+(ert-deftest test-integration-recording-modeline-sync-sentinel-updates-on-crash ()
+ "Test that modeline syncs when process dies and sentinel runs.
+
+When recording process crashes:
+1. Sentinel detects process death
+2. Sentinel clears variable
+3. Sentinel calls force-mode-line-update
+4. Modeline indicator shows no recording
+
+Components integrated:
+- cj/ffmpeg-record-audio (attaches sentinel)
+- cj/recording-process-sentinel (cleanup + modeline update)
+- cj/recording-modeline-indicator
+
+Validates:
+- Sentinel updates modeline on process death
+- Modeline syncs automatically without user action
+- Critical: prevents desync when process crashes"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ ;; Create process that exits immediately
+ (make-process :name name :command '("sh" "-c" "exit 1")))))
+
+ ;; Start recording
+ (cj/audio-recording-toggle nil)
+
+ ;; Immediately after start: should show recording
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+
+ ;; Wait for process to exit and sentinel to run
+ (sit-for 0.3)
+
+ ;; After sentinel runs: modeline should be clear
+ (should (equal "" (cj/recording-modeline-indicator)))
+
+ ;; Variable should be nil
+ (should (null cj/audio-recording-ffmpeg-process)))
+ (test-integration-modeline-teardown)))
+
+(ert-deftest test-integration-recording-modeline-sync-dead-process-not-shown ()
+ "Test that modeline never shows dead process as recording.
+
+The modeline indicator uses process-live-p to check if process is ACTUALLY
+alive, not just if the variable is set. This prevents desync.
+
+Components integrated:
+- cj/recording-modeline-indicator (uses process-live-p)
+
+Validates:
+- Dead process doesn't show as recording
+- process-live-p check prevents desync
+- Critical: if variable is set but process is dead, shows empty"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (let ((dead-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0"))))
+ ;; Set variable to dead process (simulating race condition)
+ (setq cj/audio-recording-ffmpeg-process dead-process)
+
+ ;; Wait for process to die
+ (sit-for 0.1)
+
+ ;; Modeline should NOT show recording (process is dead)
+ (should (equal "" (cj/recording-modeline-indicator)))
+
+ ;; Even though variable is set
+ (should (eq dead-process cj/audio-recording-ffmpeg-process))
+
+ ;; Process is dead
+ (should-not (process-live-p dead-process)))
+ (test-integration-modeline-teardown)))
+
+;;; Integration Tests - Modeline Sync Under Rapid Toggling
+
+(ert-deftest test-integration-recording-modeline-sync-rapid-toggle-stays-synced ()
+ "Test modeline stays synced under rapid start/stop toggling.
+
+When user rapidly toggles recording on and off:
+- Modeline should stay in sync at every step
+- No race conditions or stale state
+
+Components integrated:
+- cj/audio-recording-toggle (rapid calls)
+- cj/ffmpeg-record-audio
+- cj/audio-recording-stop
+- cj/recording-modeline-indicator
+
+Validates:
+- Modeline syncs even with rapid state changes
+- No race conditions in update logic"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ (make-process :name name :command '("sleep" "1000")))))
+
+ ;; Rapid toggling
+ (dotimes (_i 5)
+ ;; Start
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+ (should cj/audio-recording-ffmpeg-process)
+
+ ;; Stop
+ (cj/audio-recording-toggle nil)
+ (should (equal "" (cj/recording-modeline-indicator)))
+ (should (null cj/audio-recording-ffmpeg-process))))
+ (test-integration-modeline-teardown)))
+
+(ert-deftest test-integration-recording-modeline-sync-both-recordings-independent ()
+ "Test that audio and video modeline updates are independent.
+
+When one recording stops, the other's indicator persists.
+When one recording starts, both indicators combine correctly.
+
+Components integrated:
+- cj/audio-recording-toggle
+- cj/video-recording-toggle
+- cj/recording-modeline-indicator (combines states)
+
+Validates:
+- Independent recordings don't interfere
+- Modeline correctly shows: audio-only, video-only, or both
+- Stopping one doesn't affect other's indicator"
+ (test-integration-modeline-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'file-directory-p)
+ (lambda (_dir) t))
+ ((symbol-function 'start-process-shell-command)
+ (lambda (name _buffer _cmd)
+ (make-process :name name :command '("sleep" "1000")))))
+
+ ;; Start audio
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+
+ ;; Add video - modeline should combine
+ (cj/video-recording-toggle nil)
+ (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+
+ ;; Stop audio - video indicator should persist
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
+
+ ;; Start audio again - should recombine
+ (cj/audio-recording-toggle nil)
+ (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
+
+ ;; Stop video - audio indicator should persist
+ (cj/video-recording-toggle nil)
+ (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
+
+ ;; Stop audio - should be empty
+ (cj/audio-recording-toggle nil)
+ (should (equal "" (cj/recording-modeline-indicator))))
+ (test-integration-modeline-teardown)))
+
+(provide 'test-integration-recording-modeline-sync)
+;;; test-integration-recording-modeline-sync.el ends here