aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-29 23:59:30 -0500
committerCraig Jennings <c@cjennings.net>2026-04-29 23:59:30 -0500
commit735ecf082273c0cba2f5c5dd25876e2ccd83b0f7 (patch)
tree5cdaca8277f0d2397682d4279fd5fbaa2e8e3162 /tests
parent01b75599a500d6276a962b47744166abb25d846c (diff)
downloadgloss-735ecf082273c0cba2f5c5dd25876e2ccd83b0f7.tar.gz
gloss-735ecf082273c0cba2f5c5dd25876e2ccd83b0f7.zip
test: add gloss-display test suite (red phase)
Four test files covering the gloss-display public API and pure helpers. All 23 tests fail at this commit because the implementation is still a stub. Format-candidate gets full N/B/E coverage. Render-entry gets the pure-helper treatment. Pick-definition mocks completing-read at the boundary. Show-entry has a single smoke test.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-gloss-display--format-candidate.el84
-rw-r--r--tests/test-gloss-display--pick-definition.el85
-rw-r--r--tests/test-gloss-display--render-entry.el64
-rw-r--r--tests/test-gloss-display--show-entry-smoke.el47
4 files changed, 280 insertions, 0 deletions
diff --git a/tests/test-gloss-display--format-candidate.el b/tests/test-gloss-display--format-candidate.el
new file mode 100644
index 0000000..29e077d
--- /dev/null
+++ b/tests/test-gloss-display--format-candidate.el
@@ -0,0 +1,84 @@
+;;; test-gloss-display--format-candidate.el --- Tests for gloss-display--format-candidate -*- lexical-binding: t -*-
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;;; Commentary:
+;; Tests for the pure helper `gloss-display--format-candidate' covering
+;; Normal/Boundary/Error cases. The helper takes a definition plist
+;; (:source SYM :text STRING) and returns a single-line "[source] text"
+;; row suitable for display in `completing-read'.
+
+;;; Code:
+
+(require 'ert)
+(require 'gloss-display)
+
+(ert-deftest test-gloss-display-format-candidate-normal-short-text ()
+ "Normal: short text formats as \"[source] text\" with no truncation."
+ (let ((result (gloss-display--format-candidate
+ '(:source wiktionary :text "Hello world."))))
+ (should (equal result "[wiktionary] Hello world."))))
+
+(ert-deftest test-gloss-display-format-candidate-normal-different-source ()
+ "Normal: source symbol is rendered as its name inside brackets."
+ (let ((result (gloss-display--format-candidate
+ '(:source dictionary-api :text "Some text."))))
+ (should (string-prefix-p "[dictionary-api] " result))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-empty-text ()
+ "Boundary: empty text yields the prefix only."
+ (let ((result (gloss-display--format-candidate
+ '(:source wiktionary :text ""))))
+ (should (equal result "[wiktionary] "))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-single-char-text ()
+ "Boundary: one-character text is preserved verbatim."
+ (let ((result (gloss-display--format-candidate
+ '(:source wiktionary :text "a"))))
+ (should (equal result "[wiktionary] a"))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-unicode-text ()
+ "Boundary: unicode characters in text are preserved."
+ (let ((result (gloss-display--format-candidate
+ '(:source wiktionary :text "café — naïve résumé"))))
+ (should (equal result "[wiktionary] café — naïve résumé"))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-long-text-truncated ()
+ "Boundary: text longer than the cap is truncated with an ellipsis."
+ (let* ((long-text (make-string 200 ?x))
+ (result (gloss-display--format-candidate
+ (list :source 'wiktionary :text long-text))))
+ (should (<= (length result) gloss-display--candidate-max-length))
+ (should (string-suffix-p "..." result))
+ (should (string-prefix-p "[wiktionary] " result))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-text-at-cap-not-truncated ()
+ "Boundary: text that fits exactly at the cap is not truncated."
+ (let* ((prefix-len (length "[wiktionary] "))
+ (text (make-string (- gloss-display--candidate-max-length prefix-len) ?y))
+ (result (gloss-display--format-candidate
+ (list :source 'wiktionary :text text))))
+ (should (= (length result) gloss-display--candidate-max-length))
+ (should-not (string-suffix-p "..." result))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-missing-source ()
+ "Boundary: missing :source falls back to the literal \"unknown\" tag."
+ (let ((result (gloss-display--format-candidate
+ '(:text "Some text."))))
+ (should (string-prefix-p "[unknown] " result))
+ (should (string-suffix-p "Some text." result))))
+
+(ert-deftest test-gloss-display-format-candidate-boundary-newlines-collapsed ()
+ "Boundary: embedded newlines are collapsed to spaces (single-line row)."
+ (let ((result (gloss-display--format-candidate
+ '(:source wiktionary :text "First line.\nSecond line."))))
+ (should-not (string-match-p "\n" result))
+ (should (string-match-p "First line. Second line." result))))
+
+(ert-deftest test-gloss-display-format-candidate-error-non-list-input ()
+ "Error: passing a non-list raises `wrong-type-argument'."
+ (should-error (gloss-display--format-candidate "not a plist")
+ :type 'wrong-type-argument))
+
+(provide 'test-gloss-display--format-candidate)
+;;; test-gloss-display--format-candidate.el ends here
diff --git a/tests/test-gloss-display--pick-definition.el b/tests/test-gloss-display--pick-definition.el
new file mode 100644
index 0000000..0c11fe1
--- /dev/null
+++ b/tests/test-gloss-display--pick-definition.el
@@ -0,0 +1,85 @@
+;;; test-gloss-display--pick-definition.el --- Tests for gloss-display-pick-definition -*- lexical-binding: t -*-
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;;; Commentary:
+;; Tests for `gloss-display-pick-definition' covering Normal/Boundary/Error
+;; cases. The picker mocks `completing-read' at the boundary; the function
+;; under test is responsible for building the alist of formatted-string ->
+;; plist and mapping the user's choice back to the chosen plist.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'gloss-display)
+
+(defmacro gloss-test--with-completing-read (return-value &rest body)
+ "Run BODY with `completing-read' mocked to return RETURN-VALUE.
+If RETURN-VALUE is the symbol `quit', simulate the user pressing C-g
+by signalling `quit' inside the mock."
+ (declare (indent 1) (debug t))
+ `(cl-letf (((symbol-function 'completing-read)
+ (lambda (&rest _args)
+ ,(if (eq return-value 'quit)
+ '(signal 'quit nil)
+ return-value))))
+ ,@body))
+
+(ert-deftest test-gloss-display-pick-definition-normal-pick-first-of-three ()
+ "Normal: choosing the first row returns the first definition plist."
+ (let* ((def1 '(:source wiktionary :text "First sense."))
+ (def2 '(:source wiktionary :text "Second sense."))
+ (def3 '(:source wiktionary :text "Third sense."))
+ (defs (list def1 def2 def3))
+ (chosen-row (gloss-display--format-candidate def1)))
+ (gloss-test--with-completing-read chosen-row
+ (should (equal (gloss-display-pick-definition "term" defs) def1)))))
+
+(ert-deftest test-gloss-display-pick-definition-normal-pick-middle-of-three ()
+ "Normal: choosing the middle row returns the middle definition plist."
+ (let* ((def1 '(:source wiktionary :text "First sense."))
+ (def2 '(:source wiktionary :text "Second sense."))
+ (def3 '(:source wiktionary :text "Third sense."))
+ (defs (list def1 def2 def3))
+ (chosen-row (gloss-display--format-candidate def2)))
+ (gloss-test--with-completing-read chosen-row
+ (should (equal (gloss-display-pick-definition "term" defs) def2)))))
+
+(ert-deftest test-gloss-display-pick-definition-boundary-single-definition ()
+ "Boundary: a single-definition list still returns that plist."
+ (let* ((def '(:source wiktionary :text "Only sense."))
+ (chosen-row (gloss-display--format-candidate def)))
+ (gloss-test--with-completing-read chosen-row
+ (should (equal (gloss-display-pick-definition "term" (list def))
+ def)))))
+
+(ert-deftest test-gloss-display-pick-definition-boundary-empty-list ()
+ "Boundary: an empty definition list returns nil without prompting."
+ (let ((completing-read-called nil))
+ (cl-letf (((symbol-function 'completing-read)
+ (lambda (&rest _args)
+ (setq completing-read-called t) "")))
+ (should-not (gloss-display-pick-definition "term" nil))
+ (should-not completing-read-called))))
+
+(ert-deftest test-gloss-display-pick-definition-error-quit-returns-nil ()
+ "Error: C-g during the picker (quit signal) returns nil, not propagate."
+ (let ((def '(:source wiktionary :text "Only sense.")))
+ (gloss-test--with-completing-read quit
+ (should-not (gloss-display-pick-definition "term" (list def))))))
+
+(ert-deftest test-gloss-display-pick-definition-prompt-mentions-term ()
+ "Normal: the completing-read prompt names the term being disambiguated."
+ (let* ((captured-prompt nil)
+ (def '(:source wiktionary :text "Only sense."))
+ (chosen-row (gloss-display--format-candidate def)))
+ (cl-letf (((symbol-function 'completing-read)
+ (lambda (prompt &rest _args)
+ (setq captured-prompt prompt)
+ chosen-row)))
+ (gloss-display-pick-definition "anaphora" (list def))
+ (should (string-match-p "anaphora" captured-prompt)))))
+
+(provide 'test-gloss-display--pick-definition)
+;;; test-gloss-display--pick-definition.el ends here
diff --git a/tests/test-gloss-display--render-entry.el b/tests/test-gloss-display--render-entry.el
new file mode 100644
index 0000000..5656660
--- /dev/null
+++ b/tests/test-gloss-display--render-entry.el
@@ -0,0 +1,64 @@
+;;; test-gloss-display--render-entry.el --- Tests for gloss-display--render-entry -*- lexical-binding: t -*-
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;;; Commentary:
+;; Tests for the pure helper `gloss-display--render-entry' covering
+;; Normal/Boundary/Error cases. The helper takes a TERM and BODY and
+;; returns the formatted string that `gloss-display-show-entry' inserts
+;; into the side buffer.
+
+;;; Code:
+
+(require 'ert)
+(require 'gloss-display)
+
+(ert-deftest test-gloss-display-render-entry-normal-term-and-body ()
+ "Normal: render produces the term, an underline, a blank, and the body."
+ (let ((result (gloss-display--render-entry
+ "anaphora"
+ "Reference to something earlier in the discourse.")))
+ (should (string-match-p "^anaphora\n" result))
+ (should (string-match-p "^=+\n" (substring result (length "anaphora\n"))))
+ (should (string-match-p "Reference to something earlier" result))))
+
+(ert-deftest test-gloss-display-render-entry-normal-underline-matches-term-length ()
+ "Normal: the underline length matches the term length."
+ (let* ((term "abc")
+ (result (gloss-display--render-entry term "Body."))
+ (lines (split-string result "\n")))
+ (should (equal (nth 0 lines) term))
+ (should (= (length (nth 1 lines)) (length term)))
+ (should (string-match-p "^=+$" (nth 1 lines)))))
+
+(ert-deftest test-gloss-display-render-entry-boundary-multi-line-body ()
+ "Boundary: a multi-line body is preserved verbatim."
+ (let ((result (gloss-display--render-entry
+ "term"
+ "First paragraph.\n\nSecond paragraph.")))
+ (should (string-match-p "First paragraph\\." result))
+ (should (string-match-p "Second paragraph\\." result))
+ (should (string-match-p "First paragraph\\.\n\nSecond paragraph\\." result))))
+
+(ert-deftest test-gloss-display-render-entry-boundary-empty-body ()
+ "Boundary: an empty body still renders the term and underline."
+ (let ((result (gloss-display--render-entry "lonely" "")))
+ (should (string-match-p "^lonely\n=+\n" result))))
+
+(ert-deftest test-gloss-display-render-entry-boundary-unicode-term ()
+ "Boundary: unicode characters in the term are preserved."
+ (let* ((term "café")
+ (result (gloss-display--render-entry term "A coffeehouse.")))
+ (should (string-match-p (regexp-quote term) result))
+ (should (string-match-p "A coffeehouse\\." result))))
+
+(ert-deftest test-gloss-display-render-entry-boundary-very-long-term ()
+ "Boundary: a long term gets a matching long underline."
+ (let* ((term (make-string 60 ?z))
+ (result (gloss-display--render-entry term "Body."))
+ (lines (split-string result "\n")))
+ (should (equal (nth 0 lines) term))
+ (should (= (length (nth 1 lines)) 60))))
+
+(provide 'test-gloss-display--render-entry)
+;;; test-gloss-display--render-entry.el ends here
diff --git a/tests/test-gloss-display--show-entry-smoke.el b/tests/test-gloss-display--show-entry-smoke.el
new file mode 100644
index 0000000..07d7a97
--- /dev/null
+++ b/tests/test-gloss-display--show-entry-smoke.el
@@ -0,0 +1,47 @@
+;;; test-gloss-display--show-entry-smoke.el --- Smoke test for gloss-display-show-entry -*- lexical-binding: t -*-
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;;; Commentary:
+;; Smoke test for `gloss-display-show-entry'. Exercises the side-buffer
+;; setup end to end: buffer creation, content rendering, mode activation,
+;; and the read-only invariant. Per the design and project testing rules,
+;; framework primitives like `display-buffer' and major-mode mechanics are
+;; not re-tested.
+
+;;; Code:
+
+(require 'ert)
+(require 'gloss-display)
+
+(defmacro gloss-test--with-display-buffer-mocked (&rest body)
+ "Run BODY with `display-buffer' replaced by a no-op.
+Avoids opening a real side window during batch tests, while still letting
+`gloss-display-show-entry' populate and configure the entry buffer."
+ (declare (indent 0) (debug t))
+ `(cl-letf (((symbol-function 'display-buffer)
+ (lambda (buffer &rest _args) (get-buffer buffer))))
+ ,@body))
+
+(ert-deftest test-gloss-display-show-entry-creates-buffer-with-mode-and-content ()
+ "Smoke: show-entry creates a named buffer in `gloss-mode' with the entry."
+ (gloss-test--with-display-buffer-mocked
+ (unwind-protect
+ (let ((buf (gloss-display-show-entry
+ "anaphora"
+ "Reference to something earlier in the discourse.")))
+ (should (buffer-live-p buf))
+ (with-current-buffer buf
+ (should (derived-mode-p 'gloss-mode))
+ (should buffer-read-only)
+ (let ((contents (buffer-substring-no-properties
+ (point-min) (point-max))))
+ (should (string-match-p "anaphora" contents))
+ (should (string-match-p "Reference to something earlier"
+ contents)))))
+ (when-let ((buf (get-buffer "*gloss: anaphora*")))
+ (let ((kill-buffer-query-functions nil))
+ (kill-buffer buf))))))
+
+(provide 'test-gloss-display--show-entry-smoke)
+;;; test-gloss-display--show-entry-smoke.el ends here