diff options
Diffstat (limited to 'tests/test-integration-recording-modeline-sync.el')
| -rw-r--r-- | tests/test-integration-recording-modeline-sync.el | 384 |
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 |
