diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-config-utilities-format-build-time.el | 83 | ||||
| -rw-r--r-- | tests/test-custom-case-downcase-dwim.el | 86 | ||||
| -rw-r--r-- | tests/test-custom-case-title-case-region.el | 199 | ||||
| -rw-r--r-- | tests/test-custom-case-upcase-dwim.el | 86 | ||||
| -rw-r--r-- | tests/test-custom-datetime-all-methods.el | 112 | ||||
| -rw-r--r-- | tests/test-dupre-theme.el | 8 | ||||
| -rw-r--r-- | tests/test-integration-mousetrap-mode-lighter-click.el | 14 | ||||
| -rw-r--r-- | tests/test-integration-mousetrap-mode-profiles.el | 16 | ||||
| -rw-r--r-- | tests/test-integration-recording-monitor-capture-interactive.el | 186 | ||||
| -rw-r--r-- | tests/test-music-config--completion-table.el | 3 | ||||
| -rw-r--r-- | tests/test-org-capture-config-date-prefix.el | 89 | ||||
| -rw-r--r-- | tests/test-org-capture-config-event-content.el | 89 |
12 files changed, 765 insertions, 206 deletions
diff --git a/tests/test-config-utilities-format-build-time.el b/tests/test-config-utilities-format-build-time.el new file mode 100644 index 00000000..70473e2d --- /dev/null +++ b/tests/test-config-utilities-format-build-time.el @@ -0,0 +1,83 @@ +;;; test-config-utilities-format-build-time.el --- Tests for cj/emacs-build--format-build-time -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for cj/emacs-build--format-build-time from config-utilities.el. +;; +;; Pure function that converts various time value representations to a +;; human-readable string. Branches on input type: nil, string, cons of +;; integers (Emacs time value), number (epoch seconds), and fallback. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'config-utilities) + +;;; Normal Cases + +(ert-deftest test-config-utilities-format-build-time-normal-nil-returns-unknown () + "Nil input should return \"unknown\"." + (should (equal (cj/emacs-build--format-build-time nil) "unknown"))) + +(ert-deftest test-config-utilities-format-build-time-normal-string-returned-as-is () + "String input should be returned unchanged." + (should (equal (cj/emacs-build--format-build-time "2026-01-15 10:30:00 CST") + "2026-01-15 10:30:00 CST"))) + +(ert-deftest test-config-utilities-format-build-time-normal-cons-formats-timestamp () + "Cons of integers (Emacs time value) should produce formatted date string." + (let* ((time-val (encode-time 0 30 14 15 2 2026)) + (result (cj/emacs-build--format-build-time time-val))) + (should (string-match-p "2026-02-15" result)) + (should (string-match-p "14:30:00" result)))) + +(ert-deftest test-config-utilities-format-build-time-normal-number-formats-timestamp () + "Numeric epoch seconds should produce formatted date string." + (let* ((time-val (float-time (encode-time 0 30 14 15 2 2026))) + (result (cj/emacs-build--format-build-time time-val))) + (should (string-match-p "2026-02-15" result)) + (should (string-match-p "14:30:00" result)))) + +(ert-deftest test-config-utilities-format-build-time-normal-fallback-formats-with-format () + "Unrecognized types should be stringified via `format'." + (should (equal (cj/emacs-build--format-build-time 'some-symbol) "some-symbol"))) + +;;; Boundary Cases + +(ert-deftest test-config-utilities-format-build-time-boundary-empty-string () + "Empty string input should be returned as empty string." + (should (equal (cj/emacs-build--format-build-time "") ""))) + +(ert-deftest test-config-utilities-format-build-time-boundary-zero-epoch () + "Zero epoch (Unix epoch start) should produce a valid formatted date string." + (let ((result (cj/emacs-build--format-build-time 0))) + (should (stringp result)) + (should (string-match-p "19[67][0-9]" result)))) + +(ert-deftest test-config-utilities-format-build-time-boundary-integer-epoch () + "Integer (not float) epoch should be handled by numberp branch." + (let* ((time-val (truncate (float-time (encode-time 0 0 12 1 1 2025)))) + (result (cj/emacs-build--format-build-time time-val))) + (should (string-match-p "2025-01-01" result)) + (should (string-match-p "12:00:00" result)))) + +(ert-deftest test-config-utilities-format-build-time-boundary-cons-not-integers () + "Cons where car is not integer should hit the fallback branch." + (let ((result (cj/emacs-build--format-build-time '("not" "integers")))) + (should (equal result "(not integers)")))) + +(ert-deftest test-config-utilities-format-build-time-boundary-vector-fallback () + "Vector input should hit the fallback branch." + (should (equal (cj/emacs-build--format-build-time [1 2 3]) "[1 2 3]"))) + +(ert-deftest test-config-utilities-format-build-time-boundary-cons-and-number-agree () + "Cons time value and its float-time equivalent should produce the same output." + (let* ((cons-time (encode-time 0 30 14 15 2 2026)) + (num-time (float-time cons-time)) + (result-cons (cj/emacs-build--format-build-time cons-time)) + (result-num (cj/emacs-build--format-build-time num-time))) + (should (equal result-cons result-num)))) + +(provide 'test-config-utilities-format-build-time) +;;; test-config-utilities-format-build-time.el ends here diff --git a/tests/test-custom-case-downcase-dwim.el b/tests/test-custom-case-downcase-dwim.el new file mode 100644 index 00000000..78c79b7e --- /dev/null +++ b/tests/test-custom-case-downcase-dwim.el @@ -0,0 +1,86 @@ +;;; test-custom-case-downcase-dwim.el --- Tests for cj/downcase-dwim -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/downcase-dwim function from custom-case.el. +;; +;; Mirror of upcase-dwim: if a region is active, downcase the region; +;; otherwise downcase the symbol at point. Signals user-error if no symbol. +;; Tests focus on the dispatch logic, not on Emacs's downcase-region. + +;;; Code: + +(require 'ert) + +(unless (boundp 'cj/custom-keymap) + (defvar cj/custom-keymap (make-sparse-keymap))) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'custom-case) + +;;; Normal Cases + +(ert-deftest test-custom-case-downcase-dwim-normal-region-downcased () + "Active region should be downcased." + (with-temp-buffer + (insert "HELLO WORLD") + (set-mark (point-min)) + (goto-char (point-max)) + (activate-mark) + (cj/downcase-dwim) + (should (equal (buffer-string) "hello world")))) + +(ert-deftest test-custom-case-downcase-dwim-normal-partial-region () + "Only the selected region should be downcased." + (with-temp-buffer + (insert "HELLO WORLD") + (set-mark 1) + (goto-char 6) + (activate-mark) + (cj/downcase-dwim) + (should (equal (buffer-string) "hello WORLD")))) + +(ert-deftest test-custom-case-downcase-dwim-normal-symbol-at-point () + "Without region, symbol at point should be downcased." + (with-temp-buffer + (insert "HELLO WORLD") + (goto-char 1) + (cj/downcase-dwim) + (should (equal (buffer-string) "hello WORLD")))) + +(ert-deftest test-custom-case-downcase-dwim-normal-symbol-mid-word () + "Point in the middle of a symbol should downcase the whole symbol." + (with-temp-buffer + (insert "HELLO WORLD") + (goto-char 3) + (cj/downcase-dwim) + (should (equal (buffer-string) "hello WORLD")))) + +(ert-deftest test-custom-case-downcase-dwim-normal-already-lowercase () + "Already lowercase text should remain unchanged." + (with-temp-buffer + (insert "hello WORLD") + (goto-char 1) + (cj/downcase-dwim) + (should (equal (buffer-string) "hello WORLD")))) + +;;; Boundary Cases + +(ert-deftest test-custom-case-downcase-dwim-boundary-single-char-symbol () + "Single character symbol should be downcased." + (with-temp-buffer + (insert "A") + (goto-char 1) + (cj/downcase-dwim) + (should (equal (buffer-string) "a")))) + +;;; Error Cases + +(ert-deftest test-custom-case-downcase-dwim-error-no-symbol-signals-error () + "With no region and no symbol at point, should signal user-error." + (with-temp-buffer + (insert " ") + (goto-char 2) + (should-error (cj/downcase-dwim) :type 'user-error))) + +(provide 'test-custom-case-downcase-dwim) +;;; test-custom-case-downcase-dwim.el ends here diff --git a/tests/test-custom-case-title-case-region.el b/tests/test-custom-case-title-case-region.el new file mode 100644 index 00000000..b2d5d1e0 --- /dev/null +++ b/tests/test-custom-case-title-case-region.el @@ -0,0 +1,199 @@ +;;; test-custom-case-title-case-region.el --- Tests for cj/title-case-region -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/title-case-region function from custom-case.el. +;; +;; This function title-cases text in the active region (or current line if no +;; region). Major words are capitalized; minor words (a, an, and, as, at, but, +;; by, for, if, in, is, nor, of, on, or, so, the, to, yet) are lowercased +;; except: at the start of the text, or after a skip-reset character (: ! ?). +;; Characters directly after separators (- \ ' .) are NOT capitalized. +;; The region is downcased first, so all-caps input is handled. + +;;; Code: + +(require 'ert) + +;; Ensure custom-case is loadable without keybinding dependencies +(unless (boundp 'cj/custom-keymap) + (defvar cj/custom-keymap (make-sparse-keymap))) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'custom-case) + +;; Helper: apply title case to STRING via region and return result +(defun test-title-case--on-string (string) + "Apply cj/title-case-region to STRING and return the result." + (with-temp-buffer + (insert string) + (set-mark (point-min)) + (goto-char (point-max)) + (activate-mark) + (cj/title-case-region) + (buffer-string))) + +;; Helper: apply title case to current line (no region) and return result +(defun test-title-case--on-line (string) + "Insert STRING, place point on the line, call cj/title-case-region without +an active region, and return the result." + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (cj/title-case-region) + (buffer-string))) + +;;; Normal Cases + +(ert-deftest test-custom-case-title-case-region-normal-simple-sentence () + "Simple sentence should capitalize major words." + (should (equal (test-title-case--on-string "the quick brown fox") + "The Quick Brown Fox"))) + +(ert-deftest test-custom-case-title-case-region-normal-minor-words-lowercased () + "Minor words (articles, short prepositions, conjunctions) should be lowercase." + (should (equal (test-title-case--on-string "war and peace in the modern age") + "War and Peace in the Modern Age"))) + +(ert-deftest test-custom-case-title-case-region-normal-first-word-always-capitalized () + "First word should always be capitalized, even if it's a minor word." + (should (equal (test-title-case--on-string "the art of war") + "The Art of War"))) + +(ert-deftest test-custom-case-title-case-region-normal-all-minor-words () + "All minor words in the skip list should be lowercased in mid-sentence." + (should (equal (test-title-case--on-string + "go a an and as at but by for if in is nor of on or so the to yet go") + "Go a an and as at but by for if in is nor of on or so the to yet Go"))) + +(ert-deftest test-custom-case-title-case-region-normal-four-letter-words-capitalized () + "Words of four or more letters should always be capitalized. +Note: 'is' is explicitly in the minor word list, so it stays lowercase." + (should (equal (test-title-case--on-string "this is from that with over") + "This is From That With Over"))) + +(ert-deftest test-custom-case-title-case-region-normal-allcaps-input () + "All-caps input should be downcased first, then title-cased." + (should (equal (test-title-case--on-string "THE QUICK BROWN FOX") + "The Quick Brown Fox"))) + +(ert-deftest test-custom-case-title-case-region-normal-mixed-case-input () + "Mixed case input should be normalized to title case." + (should (equal (test-title-case--on-string "tHe qUiCk BrOwN fOx") + "The Quick Brown Fox"))) + +(ert-deftest test-custom-case-title-case-region-normal-colon-resets-capitalization () + "Words after a colon should be capitalized, even minor words." + (should (equal (test-title-case--on-string "warning: an important message") + "Warning: An Important Message"))) + +(ert-deftest test-custom-case-title-case-region-normal-exclamation-resets () + "Words after an exclamation mark should be capitalized." + (should (equal (test-title-case--on-string "wow! the crowd goes wild") + "Wow! The Crowd Goes Wild"))) + +(ert-deftest test-custom-case-title-case-region-normal-question-resets () + "Word immediately after a question mark should be capitalized, even if minor." + (should (equal (test-title-case--on-string "really? the answer is no") + "Really? The Answer is No"))) + +(ert-deftest test-custom-case-title-case-region-normal-hyphenated-word () + "Second part of a hyphenated word should NOT be capitalized." + (should (equal (test-title-case--on-string "a well-known fact") + "A Well-known Fact"))) + +(ert-deftest test-custom-case-title-case-region-normal-apostrophe () + "Letters after an apostrophe should NOT be capitalized." + (should (equal (test-title-case--on-string "it's a wonderful life") + "It's a Wonderful Life"))) + +(ert-deftest test-custom-case-title-case-region-normal-no-region-uses-line () + "Without an active region, should title-case the current line." + (should (equal (test-title-case--on-line "the quick brown fox") + "The Quick Brown Fox"))) + +;;; Boundary Cases + +(ert-deftest test-custom-case-title-case-region-boundary-single-word () + "Single word should be capitalized." + (should (equal (test-title-case--on-string "hello") "Hello"))) + +(ert-deftest test-custom-case-title-case-region-boundary-single-minor-word () + "Single minor word should still be capitalized (it's the first word)." + (should (equal (test-title-case--on-string "the") "The"))) + +(ert-deftest test-custom-case-title-case-region-boundary-single-character () + "Single character should be capitalized." + (should (equal (test-title-case--on-string "a") "A"))) + +(ert-deftest test-custom-case-title-case-region-boundary-empty-string () + "Empty string should remain empty." + (should (equal (test-title-case--on-string "") ""))) + +(ert-deftest test-custom-case-title-case-region-boundary-only-whitespace () + "Whitespace-only string should remain unchanged." + (should (equal (test-title-case--on-string " ") " "))) + +(ert-deftest test-custom-case-title-case-region-boundary-multiple-spaces () + "Multiple spaces between words should be preserved." + (should (equal (test-title-case--on-string "the quick fox") + "The Quick Fox"))) + +(ert-deftest test-custom-case-title-case-region-boundary-unicode-words () + "Unicode characters should pass through without error." + (should (equal (test-title-case--on-string "the café is nice") + "The Café is Nice"))) + +(ert-deftest test-custom-case-title-case-region-boundary-numbers-in-text () + "Numbers mixed with text should not break title casing." + (should (equal (test-title-case--on-string "chapter 3 of the book") + "Chapter 3 of the Book"))) + +(ert-deftest test-custom-case-title-case-region-boundary-backslash-separator () + "Backslash should act as separator, preventing capitalization after it." + (should (equal (test-title-case--on-string "foo\\bar baz") + "Foo\\bar Baz"))) + +(ert-deftest test-custom-case-title-case-region-boundary-period-separator () + "Period should act as separator, preventing capitalization after it." + (should (equal (test-title-case--on-string "foo.bar baz") + "Foo.bar Baz"))) + +(ert-deftest test-custom-case-title-case-region-boundary-multiple-colons () + "Multiple colons should each reset capitalization." + (should (equal (test-title-case--on-string "part: the first: an overview") + "Part: The First: An Overview"))) + +(ert-deftest test-custom-case-title-case-region-boundary-colon-with-minor-word () + "Minor word immediately after colon should be capitalized." + (should (equal (test-title-case--on-string "note: a brief summary") + "Note: A Brief Summary"))) + +(ert-deftest test-custom-case-title-case-region-boundary-partial-region () + "Only the selected region should be affected." + (with-temp-buffer + (insert "the quick brown fox jumps over") + ;; Select only "quick brown fox" + (set-mark 5) + (goto-char 20) + (activate-mark) + (cj/title-case-region) + (should (equal (buffer-string) "the Quick Brown Fox jumps over")))) + +(ert-deftest test-custom-case-title-case-region-boundary-long-title () + "Long title with many minor words should be handled correctly." + (should (equal (test-title-case--on-string + "the lord of the rings: the return of the king") + "The Lord of the Rings: The Return of the King"))) + +;;; Error Cases + +(ert-deftest test-custom-case-title-case-region-error-numeric-only () + "String of only numbers should not error." + (should (equal (test-title-case--on-string "12345") "12345"))) + +(ert-deftest test-custom-case-title-case-region-error-punctuation-only () + "String of only punctuation should not error." + (should (equal (test-title-case--on-string "!@#$%") "!@#$%"))) + +(provide 'test-custom-case-title-case-region) +;;; test-custom-case-title-case-region.el ends here diff --git a/tests/test-custom-case-upcase-dwim.el b/tests/test-custom-case-upcase-dwim.el new file mode 100644 index 00000000..19f47050 --- /dev/null +++ b/tests/test-custom-case-upcase-dwim.el @@ -0,0 +1,86 @@ +;;; test-custom-case-upcase-dwim.el --- Tests for cj/upcase-dwim -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/upcase-dwim function from custom-case.el. +;; +;; This is a thin wrapper: if a region is active, upcase the region; +;; otherwise upcase the symbol at point. Signals user-error if no symbol. +;; Tests focus on the dispatch logic, not on Emacs's upcase-region. + +;;; Code: + +(require 'ert) + +(unless (boundp 'cj/custom-keymap) + (defvar cj/custom-keymap (make-sparse-keymap))) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'custom-case) + +;;; Normal Cases + +(ert-deftest test-custom-case-upcase-dwim-normal-region-upcased () + "Active region should be upcased." + (with-temp-buffer + (insert "hello world") + (set-mark (point-min)) + (goto-char (point-max)) + (activate-mark) + (cj/upcase-dwim) + (should (equal (buffer-string) "HELLO WORLD")))) + +(ert-deftest test-custom-case-upcase-dwim-normal-partial-region () + "Only the selected region should be upcased." + (with-temp-buffer + (insert "hello world") + (set-mark 1) + (goto-char 6) + (activate-mark) + (cj/upcase-dwim) + (should (equal (buffer-string) "HELLO world")))) + +(ert-deftest test-custom-case-upcase-dwim-normal-symbol-at-point () + "Without region, symbol at point should be upcased." + (with-temp-buffer + (insert "hello world") + (goto-char 1) + (cj/upcase-dwim) + (should (equal (buffer-string) "HELLO world")))) + +(ert-deftest test-custom-case-upcase-dwim-normal-symbol-mid-word () + "Point in the middle of a symbol should upcase the whole symbol." + (with-temp-buffer + (insert "hello world") + (goto-char 3) + (cj/upcase-dwim) + (should (equal (buffer-string) "HELLO world")))) + +(ert-deftest test-custom-case-upcase-dwim-normal-already-uppercase () + "Already uppercase text should remain unchanged." + (with-temp-buffer + (insert "HELLO world") + (goto-char 1) + (cj/upcase-dwim) + (should (equal (buffer-string) "HELLO world")))) + +;;; Boundary Cases + +(ert-deftest test-custom-case-upcase-dwim-boundary-single-char-symbol () + "Single character symbol should be upcased." + (with-temp-buffer + (insert "a") + (goto-char 1) + (cj/upcase-dwim) + (should (equal (buffer-string) "A")))) + +;;; Error Cases + +(ert-deftest test-custom-case-upcase-dwim-error-no-symbol-signals-error () + "With no region and no symbol at point, should signal user-error." + (with-temp-buffer + (insert " ") + (goto-char 2) + (should-error (cj/upcase-dwim) :type 'user-error))) + +(provide 'test-custom-case-upcase-dwim) +;;; test-custom-case-upcase-dwim.el ends here diff --git a/tests/test-custom-datetime-all-methods.el b/tests/test-custom-datetime-all-methods.el new file mode 100644 index 00000000..c9cfa41e --- /dev/null +++ b/tests/test-custom-datetime-all-methods.el @@ -0,0 +1,112 @@ +;;; test-custom-datetime-all-methods.el --- Tests for custom-datetime.el -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for all six insert functions in custom-datetime.el. +;; +;; All functions follow the same pattern: (insert (format-time-string FMT (current-time))) +;; They are thin wrappers, so tests focus on: +;; - Each function inserts text matching its format variable +;; - Custom format variables are respected +;; - Trailing space convention is preserved where present +;; +;; We mock current-time to a fixed value for deterministic output. + +;;; Code: + +(require 'ert) + +(unless (boundp 'cj/custom-keymap) + (defvar cj/custom-keymap (make-sparse-keymap))) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'custom-datetime) + +;; Fixed time: 2026-02-15 14:30:45 CST (UTC-6) +(defconst test-datetime--fixed-time (encode-time 45 30 14 15 2 2026) + "Fixed time value for deterministic tests.") + +(defmacro test-datetime--with-fixed-time (&rest body) + "Execute BODY with current-time mocked to test-datetime--fixed-time." + `(cl-letf (((symbol-function 'current-time) + (lambda () test-datetime--fixed-time))) + ,@body)) + +;; Helper: call FUNC in a temp buffer with mocked time and return inserted text +(defun test-datetime--run (func) + "Call FUNC with mocked time and return the buffer contents." + (with-temp-buffer + (test-datetime--with-fixed-time + (funcall func)) + (buffer-string))) + +;;; Normal Cases — Each function inserts expected format + +(ert-deftest test-custom-datetime-all-methods-normal-readable-date-time () + "cj/insert-readable-date-time should insert human-readable date and time." + (let ((result (test-datetime--run #'cj/insert-readable-date-time))) + (should (string-match-p "Sunday, February 15, 2026" result)) + (should (string-match-p "02:30:45 PM" result)))) + +(ert-deftest test-custom-datetime-all-methods-normal-sortable-date-time () + "cj/insert-sortable-date-time should insert ISO-style date and time." + (let ((result (test-datetime--run #'cj/insert-sortable-date-time))) + (should (string-match-p "2026-02-15" result)) + (should (string-match-p "14:30:45" result)))) + +(ert-deftest test-custom-datetime-all-methods-normal-sortable-time () + "cj/insert-sortable-time should insert time with AM/PM and timezone." + (let ((result (test-datetime--run #'cj/insert-sortable-time))) + (should (string-match-p "02:30:45 PM" result)))) + +(ert-deftest test-custom-datetime-all-methods-normal-readable-time () + "cj/insert-readable-time should insert short time with AM/PM." + (let ((result (test-datetime--run #'cj/insert-readable-time))) + (should (string-match-p "2:30 PM" result)))) + +(ert-deftest test-custom-datetime-all-methods-normal-sortable-date () + "cj/insert-sortable-date should insert ISO date with day abbreviation." + (let ((result (test-datetime--run #'cj/insert-sortable-date))) + (should (string-match-p "2026-02-15 Sun" result)))) + +(ert-deftest test-custom-datetime-all-methods-normal-readable-date () + "cj/insert-readable-date should insert full human-readable date." + (let ((result (test-datetime--run #'cj/insert-readable-date))) + (should (equal result "Sunday, February 15, 2026")))) + +;;; Boundary Cases + +(ert-deftest test-custom-datetime-all-methods-boundary-trailing-space-convention () + "Functions with trailing space in format should include it in output." + ;; These formats have trailing spaces by default + (let ((result-rdt (test-datetime--run #'cj/insert-readable-date-time)) + (result-sdt (test-datetime--run #'cj/insert-sortable-date-time)) + (result-st (test-datetime--run #'cj/insert-sortable-time)) + (result-rt (test-datetime--run #'cj/insert-readable-time))) + (should (string-suffix-p " " result-rdt)) + (should (string-suffix-p " " result-sdt)) + (should (string-suffix-p " " result-st)) + (should (string-suffix-p " " result-rt)))) + +(ert-deftest test-custom-datetime-all-methods-boundary-no-trailing-space () + "Functions without trailing space in format should not add one." + (let ((result-sd (test-datetime--run #'cj/insert-sortable-date)) + (result-rd (test-datetime--run #'cj/insert-readable-date))) + (should-not (string-suffix-p " " result-sd)) + (should-not (string-suffix-p " " result-rd)))) + +(ert-deftest test-custom-datetime-all-methods-boundary-custom-format-override () + "Overriding a format variable should change the output." + (let ((readable-date-format "%d/%m/%Y")) + (should (equal (test-datetime--run #'cj/insert-readable-date) + "15/02/2026")))) + +(ert-deftest test-custom-datetime-all-methods-boundary-inserts-at-point () + "Inserted text should appear at point, not replacing buffer contents." + (with-temp-buffer + (insert "before ") + (test-datetime--with-fixed-time + (cj/insert-sortable-date)) + (should (string-prefix-p "before 2026-02-15" (buffer-string))))) + +(provide 'test-custom-datetime-all-methods) +;;; test-custom-datetime-all-methods.el ends here diff --git a/tests/test-dupre-theme.el b/tests/test-dupre-theme.el index d044cd80..32fa437e 100644 --- a/tests/test-dupre-theme.el +++ b/tests/test-dupre-theme.el @@ -176,16 +176,16 @@ ;;; Rainbow-delimiters tests (skip if package not available) (ert-deftest dupre-theme-rainbow-depth-1 () - "Rainbow depth 1 should use yellow." + "Rainbow depth 1 should use blue." (skip-unless (require 'rainbow-delimiters nil t)) (load-theme 'dupre t) - (should (string= (face-attribute 'rainbow-delimiters-depth-1-face :foreground) "#d7af5f"))) + (should (string= (face-attribute 'rainbow-delimiters-depth-1-face :foreground) "#67809c"))) (ert-deftest dupre-theme-rainbow-depth-2 () - "Rainbow depth 2 should use blue." + "Rainbow depth 2 should use gray+2." (skip-unless (require 'rainbow-delimiters nil t)) (load-theme 'dupre t) - (should (string= (face-attribute 'rainbow-delimiters-depth-2-face :foreground) "#67809c"))) + (should (string= (face-attribute 'rainbow-delimiters-depth-2-face :foreground) "#d0cbc0"))) ;;; Error/warning face tests diff --git a/tests/test-integration-mousetrap-mode-lighter-click.el b/tests/test-integration-mousetrap-mode-lighter-click.el index fcae89a6..b9f32fda 100644 --- a/tests/test-integration-mousetrap-mode-lighter-click.el +++ b/tests/test-integration-mousetrap-mode-lighter-click.el @@ -14,7 +14,7 @@ (ert-deftest test-integration-lighter-click-enables-mode-in-dashboard () "Test clicking lighter in dashboard-mode enables mode with correct profile. -Dashboard uses primary-click profile which blocks scrolling but allows mouse-1." +Dashboard uses scroll+primary profile which allows scrolling and mouse-1." (with-temp-buffer (let ((major-mode 'dashboard-mode) (mouse-trap-mode nil)) @@ -27,12 +27,12 @@ Dashboard uses primary-click profile which blocks scrolling but allows mouse-1." ;; Mode should be enabled (should mouse-trap-mode) - ;; Keymap should be built for dashboard (primary-click profile) + ;; Keymap should be built for dashboard (scroll+primary profile) (should (keymapp mouse-trap-mode-map)) - ;; Verify profile-specific behavior: mouse-1 allowed, scroll blocked + ;; Verify profile-specific behavior: mouse-1 and scroll allowed (should (eq (lookup-key mouse-trap-mode-map (kbd "<mouse-1>")) nil)) - (should (eq (lookup-key mouse-trap-mode-map (kbd "<wheel-up>")) 'ignore)) + (should (eq (lookup-key mouse-trap-mode-map (kbd "<wheel-up>")) nil)) ;; Keymap should be in minor-mode-map-alist (should (assq 'mouse-trap-mode minor-mode-map-alist))))) @@ -68,10 +68,10 @@ the keymap based on the CURRENT major mode's profile." (mouse-trap-mode 1) (should mouse-trap-mode) - ;; Should have dashboard profile (primary-click) + ;; Should have dashboard profile (scroll+primary) (let ((map1 mouse-trap-mode-map)) - (should (eq (lookup-key map1 (kbd "<mouse-1>")) nil)) ; allowed - (should (eq (lookup-key map1 (kbd "<wheel-up>")) 'ignore)) ; blocked + (should (eq (lookup-key map1 (kbd "<mouse-1>")) nil)) ; allowed + (should (eq (lookup-key map1 (kbd "<wheel-up>")) nil)) ; allowed ;; Disable (mouse-trap-mode -1) diff --git a/tests/test-integration-mousetrap-mode-profiles.el b/tests/test-integration-mousetrap-mode-profiles.el index 6abd3ad2..741df442 100644 --- a/tests/test-integration-mousetrap-mode-profiles.el +++ b/tests/test-integration-mousetrap-mode-profiles.el @@ -55,26 +55,26 @@ Validates: (should (eq (lookup-key map (kbd "<wheel-up>")) nil)) (should (eq (lookup-key map (kbd "<drag-mouse-1>")) nil))))) -(ert-deftest test-integration-mousetrap-mode-profiles-dashboard-primary-click-only () - "Test dashboard-mode gets primary-click profile. +(ert-deftest test-integration-mousetrap-mode-profiles-dashboard-scroll-and-primary () + "Test dashboard-mode gets scroll+primary profile. Components integrated: - mouse-trap--get-profile-for-mode (lookup) - mouse-trap--build-keymap (selective event binding) Validates: -- Primary-click profile allows mouse-1 -- Blocks mouse-2/3 and scroll events" +- scroll+primary profile allows mouse-1 and scrolling +- Blocks mouse-2/3" (let ((major-mode 'dashboard-mode)) (let ((profile (mouse-trap--get-profile-for-mode)) (map (mouse-trap--build-keymap))) - (should (eq 'primary-click profile)) + (should (eq 'scroll+primary profile)) ;; mouse-1 allowed (should (eq (lookup-key map (kbd "<mouse-1>")) nil)) + ;; scroll allowed + (should (eq (lookup-key map (kbd "<wheel-up>")) nil)) ;; mouse-2/3 blocked - (should (eq (lookup-key map (kbd "<mouse-2>")) 'ignore)) - ;; scroll blocked - (should (eq (lookup-key map (kbd "<wheel-up>")) 'ignore))))) + (should (eq (lookup-key map (kbd "<mouse-2>")) 'ignore))))) (ert-deftest test-integration-mousetrap-mode-profiles-emacs-lisp-uses-default-disabled () "Test unmapped mode uses default disabled profile. diff --git a/tests/test-integration-recording-monitor-capture-interactive.el b/tests/test-integration-recording-monitor-capture-interactive.el deleted file mode 100644 index eb7cbf26..00000000 --- a/tests/test-integration-recording-monitor-capture-interactive.el +++ /dev/null @@ -1,186 +0,0 @@ -;;; test-integration-recording-monitor-capture-interactive.el --- Interactive recording test -*- lexical-binding: t; -*- - -;; Author: Craig Jennings <c@cjennings.net> -;; Created: 2025-11-14 - -;;; Commentary: -;; -;; **INTERACTIVE TEST - Run from within Emacs** -;; -;; This test must be run from an interactive Emacs session where recording -;; devices are already configured (C-; r s). -;; -;; USAGE: -;; 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 -;; -;; OR simply: -;; M-x ert RET test-integration-recording-monitor-capture RET -;; -;; The test will: -;; - Play test audio through your speakers (5 seconds) -;; - Record it -;; - Transcribe it -;; - Verify the transcription contains the expected text -;; -;; This verifies that phone call audio (speaker output) is being captured correctly. - -;;; Code: - -(require 'video-audio-recording) -(require 'transcription-config) - -(defvar test-recording--test-audio - (expand-file-name "tests/fixtures/audio/speaker-output-test.wav" user-emacs-directory) - "Test audio file for speaker output testing.") - -(defvar test-recording--expected-phrases - '("hear me" "testing" "one") - "Expected phrases in transcription (partial match OK). -Based on actual recording: 'Can you hear me? Testing, one, two, three.'") - -(defun test-recording--cleanup-files (recording-file) - "Clean up RECORDING-FILE and associated files." - (when (and recording-file (file-exists-p recording-file)) - (let* ((base (file-name-sans-extension recording-file)) - (txt-file (concat base ".txt")) - (log-file (concat base ".log"))) - (when (file-exists-p recording-file) (delete-file recording-file)) - (when (file-exists-p txt-file) (delete-file txt-file)) - (when (file-exists-p log-file) (delete-file log-file))))) - -(defun test-recording--wait-for-file (file timeout) - "Wait for FILE to exist and have content, up to TIMEOUT seconds. -Returns FILE path if successful, nil if timeout." - (let ((deadline (time-add (current-time) (seconds-to-time timeout)))) - (while (and (time-less-p (current-time) deadline) - (or (not (file-exists-p file)) - (= 0 (file-attribute-size (file-attributes file))))) - (sleep-for 1) - (message "Waiting for %s... (%d sec remaining)" - (file-name-nondirectory file) - (ceiling (float-time (time-subtract deadline (current-time)))))) - (when (and (file-exists-p file) - (> (file-attribute-size (file-attributes file)) 0)) - file))) - -;;;###autoload -(defun test-recording-monitor-now () - "Test recording monitor capture interactively. -This function can be called with M-x to test recording without ERT framework." - (interactive) - - ;; Pre-flight checks - (unless (executable-find "paplay") - (user-error "paplay not found. Install pulseaudio-utils")) - (unless (executable-find "ffmpeg") - (user-error "ffmpeg not found. Install ffmpeg")) - (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 s first")) - - (let ((test-dir (make-temp-file "recording-test-" t)) - (recording-file nil) - (playback-proc nil)) - (unwind-protect - (progn - (message "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - (message "RECORDING MONITOR CAPTURE TEST") - (message "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") - (message "Configuration:") - (message " Mic: %s" cj/recording-mic-device) - (message " Monitor: %s" cj/recording-system-device) - (message " Backend: %s\n" cj/transcribe-backend) - - ;; Step 1: Start recording - (message "[1/6] Starting recording...") - (cj/ffmpeg-record-audio test-dir) - (sleep-for 1) - (unless (process-live-p cj/audio-recording-ffmpeg-process) - (error "Failed to start recording")) - (message "✓ Recording started\n") - - ;; Step 2: Play test audio - (message "[2/6] Playing test audio through speakers...") - (setq playback-proc (start-process "test-playback" "*test-playback*" - "paplay" test-recording--test-audio)) - (message "✓ Playback started\n") - - ;; Step 3: Wait for playback - (message "[3/6] Waiting for playback to complete...") - (let ((waited 0)) - (while (and (process-live-p playback-proc) (< waited 10)) - (sleep-for 0.5) - (setq waited (+ waited 0.5))) - (when (process-live-p playback-proc) - (kill-process playback-proc) - (error "Playback timed out"))) - (sleep-for 1) - (message "✓ Playback completed\n") - - ;; Step 4: Stop recording - (message "[4/6] Stopping recording...") - (cj/audio-recording-stop) - (sleep-for 1) - - ;; Find recording file - (let ((files (directory-files test-dir t "\\.m4a$"))) - (unless (= 1 (length files)) - (error "Expected 1 recording file, found %d" (length files))) - (setq recording-file (car files))) - - (message "✓ Recording stopped") - (message " File: %s" recording-file) - (message " Size: %d bytes\n" - (file-attribute-size (file-attributes recording-file))) - - ;; Step 5: Transcribe - (message "[5/6] Transcribing (this may take 30-60 seconds)...") - (cj/transcribe-audio recording-file) - - (let ((txt-file (concat (file-name-sans-extension recording-file) ".txt"))) - (unless (test-recording--wait-for-file txt-file 120) - (error "Transcription timed out or failed")) - (message "✓ Transcription completed\n") - - ;; Step 6: Verify - (message "[6/6] Verifying transcription...") - (let ((transcript (with-temp-buffer - (insert-file-contents txt-file) - (downcase (buffer-string)))) - (matches 0)) - (message "Transcript (%d chars): %s..." - (length transcript) - (substring transcript 0 (min 80 (length transcript)))) - - (dolist (phrase test-recording--expected-phrases) - (when (string-match-p phrase transcript) - (setq matches (1+ matches)) - (message " ✓ Found: '%s'" phrase))) - - (message "\nMatched %d/%d expected phrases" - matches (length test-recording--expected-phrases)) - - (if (>= matches 2) - (progn - (message "\n✓✓✓ TEST PASSED ✓✓✓") - (message "Monitor is correctly capturing speaker audio!")) - (error "TEST FAILED: Only matched %d/%d phrases" - matches (length test-recording--expected-phrases))))))) - - ;; Cleanup - (when (and playback-proc (process-live-p playback-proc)) - (kill-process playback-proc)) - (when (and cj/audio-recording-ffmpeg-process - (process-live-p cj/audio-recording-ffmpeg-process)) - (cj/audio-recording-stop)) - (when recording-file - (test-recording--cleanup-files recording-file)) - (when (file-exists-p test-dir) - (delete-directory test-dir t)) - (message "\nCleanup complete.")))) - -(provide 'test-integration-recording-monitor-capture-interactive) -;;; test-integration-recording-monitor-capture-interactive.el ends here diff --git a/tests/test-music-config--completion-table.el b/tests/test-music-config--completion-table.el index 5be0479d..5e33e655 100644 --- a/tests/test-music-config--completion-table.el +++ b/tests/test-music-config--completion-table.el @@ -103,7 +103,8 @@ (ert-deftest test-music-config--completion-table-boundary-mixed-case-candidates () "Completion table with mixed-case duplicate candidates." - (let* ((candidates '("Rock" "ROCK" "rock")) + (let* ((completion-ignore-case nil) + (candidates '("Rock" "ROCK" "rock")) (table (cj/music--completion-table candidates)) (result (funcall table "R" nil t))) ;; All start with "R", but exact case matters for complete-with-action diff --git a/tests/test-org-capture-config-date-prefix.el b/tests/test-org-capture-config-date-prefix.el new file mode 100644 index 00000000..0f5ecbe9 --- /dev/null +++ b/tests/test-org-capture-config-date-prefix.el @@ -0,0 +1,89 @@ +;;; test-org-capture-config-date-prefix.el --- Tests for cj/org-capture--date-prefix -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/org-capture--date-prefix function from org-capture-config.el. +;; +;; Pure function: takes an org timestamp string, returns "YY-MM-DD: " prefix +;; or nil if the timestamp is unparseable. Extracted from +;; cj/org-capture-format-event-headline for testability. + +;;; Code: + +(require 'ert) +(require 'org) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'org-capture-config) + +;;; Normal Cases + +(ert-deftest test-org-capture-config-date-prefix-normal-standard-timestamp () + "Standard org timestamp should produce YY-MM-DD prefix." + (should (equal (cj/org-capture--date-prefix "<2026-02-15 Sun>") + "26-02-15: "))) + +(ert-deftest test-org-capture-config-date-prefix-normal-timestamp-with-time () + "Timestamp with time component should still produce date-only prefix." + (should (equal (cj/org-capture--date-prefix "<2026-02-15 Sun 14:30>") + "26-02-15: "))) + +(ert-deftest test-org-capture-config-date-prefix-normal-weekday-irrelevant () + "Different weekday abbreviations should not affect the date prefix." + (should (equal (cj/org-capture--date-prefix "<2026-03-01 Mon>") + "26-03-01: "))) + +(ert-deftest test-org-capture-config-date-prefix-normal-year-2000 () + "Year 2000 should produce 00 prefix." + (should (equal (cj/org-capture--date-prefix "<2000-06-15 Thu>") + "00-06-15: "))) + +(ert-deftest test-org-capture-config-date-prefix-normal-end-of-year () + "December 31 should format correctly." + (should (equal (cj/org-capture--date-prefix "<2026-12-31 Wed>") + "26-12-31: "))) + +(ert-deftest test-org-capture-config-date-prefix-normal-start-of-year () + "January 1 should format correctly." + (should (equal (cj/org-capture--date-prefix "<2026-01-01 Thu>") + "26-01-01: "))) + +;;; Boundary Cases + +(ert-deftest test-org-capture-config-date-prefix-boundary-no-day-name () + "Timestamp without day name should still parse." + (should (equal (cj/org-capture--date-prefix "<2026-02-15>") + "26-02-15: "))) + +(ert-deftest test-org-capture-config-date-prefix-boundary-single-digit-month-day () + "Single-digit month and day should be zero-padded." + (should (equal (cj/org-capture--date-prefix "<2026-01-05 Mon>") + "26-01-05: "))) + +(ert-deftest test-org-capture-config-date-prefix-boundary-year-wraps-at-100 () + "Year 2099 should produce 99; year 2100 should produce 00." + (should (equal (cj/org-capture--date-prefix "<2099-06-15 Sun>") + "99-06-15: ")) + (should (equal (cj/org-capture--date-prefix "<2100-06-15 Mon>") + "00-06-15: "))) + +(ert-deftest test-org-capture-config-date-prefix-boundary-timestamp-with-range () + "Timestamp with time range should still extract the date." + (should (equal (cj/org-capture--date-prefix "<2026-02-15 Sun 09:00-17:00>") + "26-02-15: "))) + +;;; Error Cases + +(ert-deftest test-org-capture-config-date-prefix-error-nil-returns-nil () + "Nil input should return nil." + (should (null (cj/org-capture--date-prefix nil)))) + +(ert-deftest test-org-capture-config-date-prefix-error-empty-string-returns-nil () + "Empty string should return nil." + (should (null (cj/org-capture--date-prefix "")))) + +(ert-deftest test-org-capture-config-date-prefix-error-garbage-string-returns-nil () + "Non-timestamp string should return nil." + (should (null (cj/org-capture--date-prefix "not a timestamp")))) + +(provide 'test-org-capture-config-date-prefix) +;;; test-org-capture-config-date-prefix.el ends here diff --git a/tests/test-org-capture-config-event-content.el b/tests/test-org-capture-config-event-content.el new file mode 100644 index 00000000..d32b9ef8 --- /dev/null +++ b/tests/test-org-capture-config-event-content.el @@ -0,0 +1,89 @@ +;;; test-org-capture-config-event-content.el --- Tests for cj/org-capture-event-content -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/org-capture-event-content function from org-capture-config.el. +;; +;; Returns selected text for event capture, prioritizing: +;; 1. org-store-link-plist :initial (from browser via org-protocol) +;; 2. org-capture-plist :initial (from Emacs region) +;; 3. Empty string (no selection) +;; +;; Note: org-capture-plist is defined by org-capture at runtime. We ensure +;; it's declared before tests so let-binding works in batch mode. + +;;; Code: + +(require 'ert) +(require 'org) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'org-capture-config) + +;; Ensure org-capture-plist is declared (normally defined by org-capture at runtime) +(defvar org-capture-plist nil) + +;;; Normal Cases + +(ert-deftest test-org-capture-config-event-content-normal-browser-selection () + "Browser selection via org-protocol should be returned with leading newline." + (let ((org-store-link-plist '(:initial "selected from browser")) + (org-capture-plist '(:initial ""))) + (should (equal (cj/org-capture-event-content) + "\nselected from browser")))) + +(ert-deftest test-org-capture-config-event-content-normal-emacs-region () + "Emacs region selection should be returned with leading newline." + (let ((org-store-link-plist nil) + (org-capture-plist '(:initial "selected in emacs"))) + (should (equal (cj/org-capture-event-content) + "\nselected in emacs")))) + +(ert-deftest test-org-capture-config-event-content-normal-no-selection () + "No selection in either plist should return empty string." + (let ((org-store-link-plist nil) + (org-capture-plist '(:initial ""))) + (should (equal (cj/org-capture-event-content) "")))) + +(ert-deftest test-org-capture-config-event-content-normal-browser-takes-priority () + "Browser selection should take priority over Emacs region." + (let ((org-store-link-plist '(:initial "from browser")) + (org-capture-plist '(:initial "from emacs"))) + (should (equal (cj/org-capture-event-content) + "\nfrom browser")))) + +;;; Boundary Cases + +(ert-deftest test-org-capture-config-event-content-boundary-store-link-no-initial () + "org-store-link-plist without :initial should fall through to capture plist." + (let ((org-store-link-plist '(:url "http://example.com")) + (org-capture-plist '(:initial "from emacs"))) + (should (equal (cj/org-capture-event-content) + "\nfrom emacs")))) + +(ert-deftest test-org-capture-config-event-content-boundary-store-link-empty-initial () + "org-store-link-plist with empty :initial should fall through, not produce stray newline." + (let ((org-store-link-plist '(:initial "")) + (org-capture-plist '(:initial ""))) + (should (equal (cj/org-capture-event-content) "")))) + +(ert-deftest test-org-capture-config-event-content-boundary-capture-plist-nil-initial () + "Nil :initial in capture plist should return empty string." + (let ((org-store-link-plist nil) + (org-capture-plist '(:initial nil))) + (should (equal (cj/org-capture-event-content) "")))) + +(ert-deftest test-org-capture-config-event-content-boundary-multiline-selection () + "Multi-line selection should be preserved." + (let ((org-store-link-plist nil) + (org-capture-plist '(:initial "line one\nline two\nline three"))) + (should (equal (cj/org-capture-event-content) + "\nline one\nline two\nline three")))) + +(ert-deftest test-org-capture-config-event-content-boundary-both-plists-nil () + "Both plists nil should return empty string." + (let ((org-store-link-plist nil) + (org-capture-plist nil)) + (should (equal (cj/org-capture-event-content) "")))) + +(provide 'test-org-capture-config-event-content) +;;; test-org-capture-config-event-content.el ends here |
