diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-org-config-finalize-task.el | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/tests/test-org-config-finalize-task.el b/tests/test-org-config-finalize-task.el new file mode 100644 index 00000000..a0932919 --- /dev/null +++ b/tests/test-org-config-finalize-task.el @@ -0,0 +1,140 @@ +;;; test-org-config-finalize-task.el --- Tests for cj/org-finalize-task -*- lexical-binding: t; -*- + +;;; Commentary: +;; Covers `cj/org-finalize-task' and its helpers: the dated-rewrite vs +;; close-in-place dispatch, the two heading transforms, and the guard. +;; +;; The journal-copy hook (`org-after-todo-state-change-hook') is bound to +;; nil in every buffer test so the org-roam daily side effect never fires -- +;; that hook is the external boundary, mocked out here. `org-todo-keywords' +;; is set to the project's full sequence so DOING / VERIFY / CANCELLED resolve +;; as keywords inside the temp buffers regardless of load state. + +;;; Code: + +(require 'ert) +(require 'org) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'org-config) + +;; Fixed instant so the stamp is deterministic in shape. The exact value and +;; tz offset are still system-dependent, so assertions match the FORMAT, not a +;; literal string (per testing.md: assert behavior, not exact text). +(defconst test-finalize--time (encode-time 24 18 14 22 5 2026) + "2026-05-22 14:18:24, local time, for deterministic stamp shape.") + +(defmacro test-finalize--with-heading (text &rest body) + "Insert TEXT in a temp Org buffer, point on the first heading, run BODY. +Returns the resulting buffer string. Binds the project keyword set, nils +the todo-state-change hook (no journal side effect), and inhibits logging." + (declare (indent 1)) + `(with-temp-buffer + (let ((org-todo-keywords + '((sequence "TODO" "PROJECT" "DOING" "WAITING" "VERIFY" + "STALLED" "DELEGATED" "|" "FAILED" "DONE" "CANCELLED"))) + (org-mode-hook nil) ; isolate from unrelated hooks (org-tidy etc.) + (org-after-todo-state-change-hook nil) + (org-inhibit-logging t)) + (insert ,text) + (org-mode) + (goto-char (point-min)) + ,@body + (buffer-string)))) + +;; ---------------------------- predicate -------------------------------------- + +(ert-deftest test-org-finalize-dated-p-level3-true () + "Normal: a level-3 sub-task takes the dated rewrite." + (should (cj/--org-finalize-dated-p 3 "TODO"))) + +(ert-deftest test-org-finalize-dated-p-level2-false () + "Normal: a level-2 task stays task-shaped (close in place)." + (should-not (cj/--org-finalize-dated-p 2 "TODO"))) + +(ert-deftest test-org-finalize-dated-p-verify-true-at-level2 () + "Boundary: VERIFY flips to dated at all depths (todo-format exception)." + (should (cj/--org-finalize-dated-p 2 "VERIFY"))) + +(ert-deftest test-org-finalize-dated-p-level1-false () + "Boundary: a level-1 heading is not dated." + (should-not (cj/--org-finalize-dated-p 1 "TODO"))) + +;; ------------------------- dated rewrite ------------------------------------- + +(ert-deftest test-org-finalize-rewrite-dated-strips-and-prepends () + "Normal: keyword and cookie gone, tag kept, stamp prepended in exact format." + (let ((out (test-finalize--with-heading "*** TODO [#A] Buy milk :shop:\nbody\n" + (cj/--org-finalize-rewrite-dated test-finalize--time)))) + (should (string-match-p + "^\\*\\*\\* [0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [A-Z][a-z]\\{2\\} @ [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\} [-+][0-9]\\{4\\} Buy milk[ \t]+:shop:" + out)) + (should-not (string-match-p "TODO" out)) + (should-not (string-match-p "\\[#A\\]" out)))) + +(ert-deftest test-org-finalize-rewrite-dated-no-priority () + "Boundary: a heading without a priority cookie still rewrites." + (let ((out (test-finalize--with-heading "*** DOING Refactor thing\n" + (cj/--org-finalize-rewrite-dated test-finalize--time)))) + (should (string-match-p "Refactor thing" out)) + (should-not (string-match-p "DOING" out)))) + +(ert-deftest test-org-finalize-rewrite-dated-no-tags () + "Boundary: a heading without tags rewrites cleanly." + (let ((out (test-finalize--with-heading "*** TODO Plain task\n" + (cj/--org-finalize-rewrite-dated test-finalize--time)))) + (should (string-match-p "Plain task" out)) + (should-not (string-match-p "TODO" out)))) + +;; ------------------------- close in place ------------------------------------ + +(ert-deftest test-org-finalize-close-in-place-adds-date-only-closed () + "Normal: a date-only CLOSED line is added; keyword retained, no time part." + (let ((out (test-finalize--with-heading "** DONE [#A] Ship it :rel:\n" + (cj/--org-finalize-close-in-place test-finalize--time)))) + (should (string-match-p + "CLOSED: \\[[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [A-Z][a-z]\\{2\\}\\]" out)) + (should (string-match-p "DONE" out)) + (should-not (string-match-p "CLOSED: \\[[0-9-]+ [A-Z][a-z]+ [0-9]+:[0-9]+" out)))) + +;; ------------------------- command / guard ----------------------------------- + +(ert-deftest test-org-finalize-task-errors-outside-org () + "Error: the command refuses to run outside Org." + (with-temp-buffer + (fundamental-mode) + (should-error (cj/org-finalize-task "DONE") :type 'user-error))) + +(ert-deftest test-org-finalize-task-errors-on-non-task-heading () + "Error: a heading with no actionable keyword cannot be finalized." + (with-temp-buffer + (let ((org-mode-hook nil) + (org-after-todo-state-change-hook nil)) + (insert "* Just a section\n") + (org-mode) + (goto-char (point-min)) + (should-error (cj/org-finalize-task "DONE") :type 'user-error)))) + +(ert-deftest test-org-finalize-task-level3-produces-dated-entry () + "Integration: finalizing a level-3 task yields a dated entry, no CLOSED cruft." + (let ((out (test-finalize--with-heading "*** TODO [#B] Wire the thing :feat:\n" + (cj/org-finalize-task "DONE" test-finalize--time)))) + (should (string-match-p "^\\*\\*\\* [0-9]\\{4\\}-.*Wire the thing" out)) + (should-not (string-match-p "TODO\\|\\[#B\\]\\|CLOSED" out)))) + +(ert-deftest test-org-finalize-task-level2-keeps-keyword-adds-closed () + "Integration: finalizing a level-2 task keeps the keyword and adds CLOSED." + (let ((out (test-finalize--with-heading "** TODO [#A] Big task :proj:\n" + (cj/org-finalize-task "DONE" test-finalize--time)))) + (should (string-match-p "DONE" out)) + (should (string-match-p "CLOSED: \\[" out)) + (should-not (string-match-p "TODO" out)))) + +;; ------------------------- keybinding wiring --------------------------------- + +(ert-deftest test-org-finalize-task-bound-on-org-map () + "Normal: the command is bound to `d' under the org prefix (C-; O d)." + (should (eq (keymap-lookup cj/org-map "d") #'cj/org-finalize-task))) + +(provide 'test-org-config-finalize-task) +;;; test-org-config-finalize-task.el ends here |
