summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-02-15 19:58:15 -0600
committerCraig Jennings <c@cjennings.net>2026-02-15 19:58:15 -0600
commit61bfb79f2c55983697f87b9ba3961a9fb46de2fe (patch)
tree6a9ebc025dcd71cba68f4612aef7b658781f4b8f /tests
parent8eab9ccde339829c1866a40f51f942a6b36f2b02 (diff)
test: add 86 new tests, fix 3 production bugs, fix 8 stale tests
New test coverage (86 tests across 7 files): - custom-case: 43 tests (title-case-region, upcase-dwim, downcase-dwim) - custom-datetime: 10 tests (all insert methods with mocked time) - config-utilities: 11 tests (format-build-time type branching) - org-capture-config: 22 tests (date-prefix + event-content) Production bugs found and fixed: - custom-case: title-case-region crashes on whitespace/punctuation-only input (char-after returns nil when no word chars found) - org-capture-config: browser branch missing empty-string guard on :initial plist value, producing stray newline in capture output - mousetrap-mode: keymap never registered in minor-mode-map-alist, so mode was silently not blocking any mouse events. Now pushes/removes on toggle. Additional fixes: - local-repository: fix cons cell syntax bug in localrepo-initialize (was calling vars as functions instead of using cons) - dupre-theme tests: update rainbow-delimiter color expectations - mousetrap tests: update dashboard profile (primary-click → scroll+primary) - music completion test: bind completion-ignore-case to prevent pollution - Delete redundant interactive recording test (duplicate of ERT version) Refactoring: - org-capture-config: extract cj/org-capture--date-prefix pure function from cj/org-capture-format-event-headline for testability Test checklist: todo.org updated to [11/25], removed untestable modules
Diffstat (limited to 'tests')
-rw-r--r--tests/test-config-utilities-format-build-time.el83
-rw-r--r--tests/test-custom-case-downcase-dwim.el86
-rw-r--r--tests/test-custom-case-title-case-region.el199
-rw-r--r--tests/test-custom-case-upcase-dwim.el86
-rw-r--r--tests/test-custom-datetime-all-methods.el112
-rw-r--r--tests/test-dupre-theme.el8
-rw-r--r--tests/test-integration-mousetrap-mode-lighter-click.el14
-rw-r--r--tests/test-integration-mousetrap-mode-profiles.el16
-rw-r--r--tests/test-integration-recording-monitor-capture-interactive.el186
-rw-r--r--tests/test-music-config--completion-table.el3
-rw-r--r--tests/test-org-capture-config-date-prefix.el89
-rw-r--r--tests/test-org-capture-config-event-content.el89
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