diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-30 07:27:44 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-30 07:27:44 -0500 |
| commit | 9e90517a98785c450cd13cd940bd1787a4771529 (patch) | |
| tree | 5c20f077bd3dcc28661711c3fdcad5ab90a5a3fa /tests | |
| parent | 5c7bb207081278e41122e62d8f6c282a18574665 (diff) | |
| download | gloss-9e90517a98785c450cd13cd940bd1787a4771529.tar.gz gloss-9e90517a98785c450cd13cd940bd1787a4771529.zip | |
refactor: rework gloss-add UX to single side-window buffer
The previous shape opened a regular (non-side) buffer for body input
and showed the saved entry in the side window after C-c C-c. That
left an extra window split during the typing phase and a side popup
the user didn't ask for.
New shape, modeled on `org-capture':
- `gloss-add' renders the term and underline as a read-only header in
*gloss-add: TERM*, leaves the body region beneath it editable, and
pops the buffer in the side-window slot. Point lands at the body
start so the user can type immediately.
- `gloss-add-finish' reads the body via the `gloss-add--body-start'
marker, saves with source `manual', kills the buffer, closes the
side window, and echoes `gloss-add: saved TERM' for confirmation.
- `gloss-add-abort' kills the buffer and closes the side window.
- The shared `gloss--add-cleanup' helper handles kill + window-close
for both finish and abort.
Read-only header uses text properties (`read-only', `front-sticky',
`rear-nonsticky') rather than narrowing, so the user can't escape the
restriction with `C-x n w'.
`gloss--add-finish-internal' no longer calls show-entry — the save
is its only responsibility. The display decision (show or not) is
the caller's, which lets `gloss-add-finish' choose "save and close"
while `gloss-lookup' still chooses "save and show."
The previous saved-window-config approach is dropped — the side-slot
takeover means there's nothing to restore. Layout returns to its
pre-add state on either C-c C-c or C-c C-k.
Adds `tests/test-gloss--add-flow-smoke.el' covering the four
interactive moments (open, finish, abort, empty-term guard) plus the
read-only-header invariant. Updates the
`gloss--add-finish-internal' tests to drop the show-entry assertion.
129 tests pass in 0.27s.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-gloss--add-finish-internal.el | 47 | ||||
| -rw-r--r-- | tests/test-gloss--add-flow-smoke.el | 115 |
2 files changed, 134 insertions, 28 deletions
diff --git a/tests/test-gloss--add-finish-internal.el b/tests/test-gloss--add-finish-internal.el index 688d6f2..bb7e4ee 100644 --- a/tests/test-gloss--add-finish-internal.el +++ b/tests/test-gloss--add-finish-internal.el @@ -15,47 +15,38 @@ (require 'gloss) (require 'testutil-gloss) -(ert-deftest test-gloss-add-finish-internal-saves-and-shows () - "Normal: a fresh term + body is saved with source `manual' and shown." +(ert-deftest test-gloss-add-finish-internal-saves-with-manual-source () + "Normal: a fresh term + body is saved with source `manual'. +Display is the caller's responsibility, not this function's." (gloss-test--with-missing-glossary - (let (shown) - (cl-letf (((symbol-function 'gloss-display-show-entry) - (lambda (term body) (setq shown (list term body))))) - (gloss--add-finish-internal "newterm" "A new definition.") - (let ((saved (gloss-core-lookup "newterm"))) - (should saved) - (should (equal (plist-get saved :body) "A new definition.")) - (should (eq (plist-get saved :source) 'manual))) - (should (equal shown '("newterm" "A new definition."))))))) + (let ((saved (gloss--add-finish-internal "newterm" "A new definition."))) + (should saved) + (should (equal (plist-get saved :body) "A new definition.")) + (should (eq (plist-get saved :source) 'manual)) + (should (equal (gloss-core-lookup "newterm") saved))))) (ert-deftest test-gloss-add-finish-internal-empty-term-raises () "Error: empty TERM raises `user-error'." (gloss-test--with-missing-glossary - (cl-letf (((symbol-function 'gloss-display-show-entry) - (lambda (_ _) nil))) - (should-error (gloss--add-finish-internal "" "Body.") - :type 'user-error) - (should-error (gloss--add-finish-internal " " "Body.") - :type 'user-error)))) + (should-error (gloss--add-finish-internal "" "Body.") + :type 'user-error) + (should-error (gloss--add-finish-internal " " "Body.") + :type 'user-error))) (ert-deftest test-gloss-add-finish-internal-empty-body-raises () "Error: empty BODY raises `user-error'." (gloss-test--with-missing-glossary - (cl-letf (((symbol-function 'gloss-display-show-entry) - (lambda (_ _) nil))) - (should-error (gloss--add-finish-internal "term" "") - :type 'user-error) - (should-error (gloss--add-finish-internal "term" " \n ") - :type 'user-error)))) + (should-error (gloss--add-finish-internal "term" "") + :type 'user-error) + (should-error (gloss--add-finish-internal "term" " \n ") + :type 'user-error))) (ert-deftest test-gloss-add-finish-internal-trims-body-whitespace () "Boundary: leading/trailing whitespace in BODY is trimmed before save." (gloss-test--with-missing-glossary - (cl-letf (((symbol-function 'gloss-display-show-entry) - (lambda (_ _) nil))) - (gloss--add-finish-internal "term" " Body content.\n\n") - (let ((saved (gloss-core-lookup "term"))) - (should (equal (plist-get saved :body) "Body content.")))))) + (gloss--add-finish-internal "term" " Body content.\n\n") + (let ((saved (gloss-core-lookup "term"))) + (should (equal (plist-get saved :body) "Body content."))))) (provide 'test-gloss--add-finish-internal) ;;; test-gloss--add-finish-internal.el ends here diff --git a/tests/test-gloss--add-flow-smoke.el b/tests/test-gloss--add-flow-smoke.el new file mode 100644 index 0000000..a472ebb --- /dev/null +++ b/tests/test-gloss--add-flow-smoke.el @@ -0,0 +1,115 @@ +;;; test-gloss--add-flow-smoke.el --- Smoke tests for the gloss-add interactive flow -*- lexical-binding: t -*- + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;;; Commentary: +;; Smoke tests for the `gloss-add' interactive flow. The buffer pops +;; up in the side-window slot with a read-only header (term + +;; underline + blank line) and an editable body region beneath it. +;; `gloss-add-finish' reads the body region, saves, and shows the +;; rendered entry in the same side slot. `gloss-add-abort' kills the +;; buffer and closes the side window. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'gloss) +(require 'testutil-gloss) + +(defmacro gloss-test--with-add-display-mocks (&rest body) + "Run BODY with `pop-to-buffer' replaced by a no-op that keeps the +buffer current, and `gloss-display-show-entry' / `delete-window' as +capturing no-ops." + (declare (indent 0) (debug t)) + `(cl-letf (((symbol-function 'pop-to-buffer) + (lambda (buf &rest _) (set-buffer buf) (current-buffer))) + ((symbol-function 'get-buffer-window) + (lambda (&rest _) nil)) + ((symbol-function 'gloss-display-show-entry) + (lambda (_ _) nil))) + ,@body)) + +(ert-deftest test-gloss-add-opens-buffer-with-read-only-header () + "Smoke: gloss-add buffer renders the header read-only and exposes a +live `gloss-add--body-start' marker pointing past the header." + (gloss-test--with-missing-glossary + (gloss-test--with-add-display-mocks + (gloss-add "newterm") + (let ((buf (get-buffer "*gloss-add: newterm*"))) + (unwind-protect + (with-current-buffer buf + (should (eq major-mode 'gloss-add-mode)) + (should (equal gloss-add--term "newterm")) + (should (markerp gloss-add--body-start)) + ;; Header content is the rendered entry with empty body. + (let ((header (buffer-substring-no-properties + (point-min) gloss-add--body-start))) + (should (string-match-p "^newterm\n=+\n\n" header))) + ;; Header range is read-only. + (should (get-text-property (point-min) 'read-only)) + ;; Body-start position is NOT read-only (rear-nonsticky). + (should-not (get-text-property gloss-add--body-start 'read-only)) + ;; Editing the header raises text-read-only. + (should-error (let ((inhibit-read-only nil)) + (goto-char (point-min)) + (insert "x")) + :type 'text-read-only)) + (when (buffer-live-p buf) + (let ((kill-buffer-query-functions nil)) + (kill-buffer buf)))))))) + +(ert-deftest test-gloss-add-empty-term-raises-and-opens-no-buffer () + "Error: empty term raises `user-error' and never opens an add buffer." + (gloss-test--with-missing-glossary + (gloss-test--with-add-display-mocks + (should-error (gloss-add "") :type 'user-error) + (should-error (gloss-add " ") :type 'user-error) + (should-not (get-buffer "*gloss-add: *")) + (should-not (get-buffer "*gloss-add: *"))))) + +(ert-deftest test-gloss-add-finish-reads-body-saves-without-showing () + "Smoke: finish reads body from after the header, saves, kills the +buffer, and does NOT pop a side display. Add is a save-and-return +flow (org-capture style); only `gloss-lookup' shows the side entry." + (gloss-test--with-missing-glossary + (let (show-called) + (cl-letf (((symbol-function 'pop-to-buffer) + (lambda (buf &rest _) (set-buffer buf))) + ((symbol-function 'gloss-display-show-entry) + (lambda (_ _) (setq show-called t)))) + (gloss-add "term") + (with-current-buffer (get-buffer "*gloss-add: term*") + (goto-char (point-max)) + (let ((inhibit-read-only nil)) + (insert "Definition body.")) + (gloss-add-finish)) + (should-not (get-buffer "*gloss-add: term*")) + (let ((saved (gloss-core-lookup "term"))) + (should saved) + (should (equal (plist-get saved :body) "Definition body."))) + (should-not show-called))))) + +(ert-deftest test-gloss-add-abort-kills-buffer-without-save () + "Smoke: abort kills the buffer with no save and no show. +The window-cleanup path (`delete-window' on the side window) only runs +when the buffer is actually displayed; in batch mode it isn't, so we +verify the save and show side effects rather than the window state." + (gloss-test--with-missing-glossary + (let (show-called) + (cl-letf (((symbol-function 'pop-to-buffer) + (lambda (buf &rest _) (set-buffer buf))) + ((symbol-function 'gloss-display-show-entry) + (lambda (_ _) (setq show-called t)))) + (gloss-add "term") + (with-current-buffer (get-buffer "*gloss-add: term*") + (goto-char (point-max)) + (let ((inhibit-read-only nil)) + (insert "Body the user typed but abandoned.")) + (gloss-add-abort)) + (should-not (get-buffer "*gloss-add: term*")) + (should-not (gloss-core-lookup "term")) + (should-not show-called))))) + +(provide 'test-gloss--add-flow-smoke) +;;; test-gloss--add-flow-smoke.el ends here |
