Hello! TLDR: I wrote a test suite for org habit. See attached.
I am interested in adding some features to org habit. However, it would be much easier if org habit used the org-element API so I am interested in re-writing org habit. However, re-writing org habit would be much easier if there was a comprehensive test suite. I have now written such a test suite. I have used testcover to ensure my test suite covers nearly every single code path at least once (100% coverage is always hard to achieve and I believe impossible in this case without a couple code changes). I ran the test suite using this guix command which used emacs version 30.2 in a container. =guix shell -C emacs git coreutils texinfo make glibc tzdata -- make test= I also tried it with the emacs-next package which is built from the development branch at commit 9663c959c73 (2025-04-07). Please let me know what you think. Thanks, Morgan
>From a7472679650688670f11f574dea6102afea31cb1 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Thu, 23 Jan 2025 15:35:50 -0500 Subject: [PATCH] testing/lisp/test-org-habit.el: Add org habit tests * testing/lisp/test-org-habit.el: New file full of tests. --- testing/lisp/test-org-habit.el | 340 +++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 testing/lisp/test-org-habit.el diff --git a/testing/lisp/test-org-habit.el b/testing/lisp/test-org-habit.el new file mode 100644 index 000000000..652a2ca70 --- /dev/null +++ b/testing/lisp/test-org-habit.el @@ -0,0 +1,340 @@ +;;; test-org-habit.el --- Tests for org-habit.el -*- lexical-binding: t ; -*- + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Unit tests for Org Habits. + +;;; Code: + +(require 'org-test "../testing/org-test") +(require 'org-agenda) +(require 'org-habit) +(require 'test-org-agenda) + + +;; Tests + +(defvar org-test-habit-no-fluff-agenda + '(("f" "no fluff" agenda "" + ((org-agenda-overriding-header "") + (org-agenda-format-date "") + (org-agenda-span 'day) + (org-agenda-show-all-dates nil) + (org-agenda-todo-keyword-format "") + (org-agenda-prefix-format ""))))) + +(defun org-test-habit-agenda-string (repeater-type-string repeater-deadline?) + "Return an org habit test string. +REPEATER-TYPE-STRING is used as the repeater type (ex. \".+\"). +When REPEATER-DEADLINE? is non-nil, add a repeater deadline. +Order is determined by `org-log-states-order-reversed'." + (concat + "* TODO Shave +SCHEDULED: <2009-10-17 Sat " repeater-type-string "2d" + (if repeater-deadline? + "/4d" + "") + "> +:PROPERTIES: +:STYLE: habit +:LAST_REPEAT: [2009-10-19 Mon 00:36] +:END: +" + + (if org-log-states-order-reversed + "- State \"DONE\" from \"TODO\" [2009-10-15 Thu] +- State \"DONE\" from \"TODO\" [2009-10-12 Mon] +- CLOSING NOTE [2009-10-10 Sat] \\ + this style occurs when `org-log-done' is `note'. +- State \"DONE\" from \"TODO\" [2009-10-04 Sun] +- State \"DONE\" from \"TODO\" [2009-10-02 Fri] +- State \"DONE\" from \"TODO\" [2009-09-29 Tue] +- State \"DONE\" from \"TODO\" [2009-09-25 Fri] +- State \"DONE\" from \"TODO\" [2009-09-19 Sat] +- State \"DONE\" from \"TODO\" [2009-09-16 Wed] +- State \"DONE\" from \"TODO\" [2009-09-12 Sat]" + + "- State \"DONE\" from \"TODO\" [2009-09-12 Sat] +- State \"DONE\" from \"TODO\" [2009-09-16 Wed] +- State \"DONE\" from \"TODO\" [2009-09-19 Sat] +- State \"DONE\" from \"TODO\" [2009-09-25 Fri] +- State \"DONE\" from \"TODO\" [2009-09-29 Tue] +- State \"DONE\" from \"TODO\" [2009-10-02 Fri] +- State \"DONE\" from \"TODO\" [2009-10-04 Sun] +- CLOSING NOTE [2009-10-10 Sat] \\ + this style occurs when `org-log-done' is `note'. +- State \"DONE\" from \"TODO\" [2009-10-12 Mon] +- State \"DONE\" from \"TODO\" [2009-10-15 Thu]"))) + +(defmacro org-test-habit (&rest body) + "Run BODY multiple times for testing habits. +Add agenda from `org-test-habit-no-fluff-agenda' to +`org-agenda-custom-commands'. + +Use habit data from `org-test-habit-agenda-string' both with and without +a repeater deadline and the the log data reversed and not-reversed." + (declare (indent 0)) + `(let ((org-agenda-custom-commands + org-test-habit-no-fluff-agenda)) + (dolist (org-log-states-order-reversed '(t nil)) + (dolist (repeater-deadline? '(nil t)) + (dolist (repeater-type-string '(".+" "+" "++")) + (org-test-agenda-with-agenda + (org-test-habit-agenda-string repeater-type-string repeater-deadline?) + ,@body)))))) + +(ert-deftest test-org-habit/simple-habit () + "Test the agenda view for a simple habit." + (org-test-at-time "2009-10-22" + (let ((org-agenda-custom-commands + org-test-habit-no-fluff-agenda)) + (org-test-agenda-with-agenda + "* TODO habit +SCHEDULED: <2009-10-21 Sat ++2d> +:PROPERTIES: +:STYLE: habit +:END: +- State \"DONE\" from \"TODO\" [2009-10-19 Sun] +- State \"DONE\" from \"TODO\" [2009-10-17 Sun]" + (should + (string-equal + "\nhabit * * ! \n" + (progn + (org-agenda nil "f") + (buffer-string)))))))) + +(ert-deftest test-org-habit/habit () + "Test the agenda view for a habit." + (org-test-at-time "2009-10-17" + (org-test-habit + (should + (string-equal + "\nShave * * * * * * ! \n" + (progn + (org-agenda nil "f") + (buffer-string))))))) + +(ert-deftest test-org-habit/graph-column () + "Test how modifiying `org-habit-graph-column' affects habits in the agenda." + (org-test-at-time "2009-10-17" + (org-test-habit + (dolist (org-habit-graph-column '(0 1 2 3 10 20 40 100)) + (should + (string-equal + (cl-case org-habit-graph-column + (0 "\n * * * * * * ! \n") + (1 "\nS * * * * * * ! \n") + (2 "\nSh * * * * * * ! \n") + (3 "\nSha * * * * * * ! \n") + ((10 20 40 100) (concat "\nShave" + (make-string (- org-habit-graph-column 2) 32) + "* * * * * * ! \n")) + (t (cl-assert nil nil "Missing case!"))) + (progn + (org-agenda nil "f") + (buffer-string)))))))) + +(ert-deftest test-org-habit/preceding-days () + "Test how modifiying `org-habit-preceding-days' affects habits in the agenda." + (org-test-at-time "2009-10-17" + (org-test-habit + (dolist (org-habit-preceding-days '(0 1 2 3 10 20 40 100)) + (should + (string-equal + (cl-case org-habit-preceding-days + (0 "\nShave ! \n") + (1 "\nShave ! \n") + (2 "\nShave * ! \n") + (3 "\nShave * ! \n") + (10 "\nShave * * * ! \n") + (20 "\nShave * * * * * * ! \n") + ((40 100) (concat "\nShave" + (make-string org-habit-preceding-days 32) + "* * * * * * * * * * ! \n")) + (t (cl-assert nil nil "Missing case!"))) + (progn + (org-agenda nil "f") + (buffer-string)))))))) + +(ert-deftest test-org-habit/following-days () + "Test how modifiying `org-habit-following-days' affects habits in the agenda." + (org-test-at-time "2009-10-17" + (org-test-habit + (dolist (org-habit-following-days '(0 1 2 3 10 20 40 100)) + (should + (string-equal + (cl-case org-habit-following-days + (0 "\nShave * * * * * * \n") + ((1 2 3 10 20 40 100) + (concat "\nShave * * * * * * !" + (make-string org-habit-following-days 32) + "\n")) + (t (cl-assert nil nil "Missing case!"))) + (progn + (org-agenda nil "f") + (buffer-string)))))))) + +(ert-deftest test-org-habit/show-habits () + "Test displaying habits in the agenda at various points in time. +Also test modifying the variables `org-habit-show-habits', +`org-habit-show-habits-only-for-today', and `org-habit-show-all-today'." + (org-test-habit + (dolist (org-habit-show-habits '(nil t)) + (dolist (org-habit-show-habits-only-for-today '(nil t)) + (dolist (org-habit-show-all-today '(nil t)) + (dolist (test-time '(2009-10-15 + 2009-10-16 + 2009-10-17 + 2009-10-18 + 2009-10-19 + 2009-10-20 + 2009-10-21 + 2009-10-22)) + (let ((expected-output-string + (cl-case test-time + (2009-10-15 + "\nShave * * * * * * * \n") + (2009-10-16 + "\nShave * * * * * * *! \n") + (2009-10-17 + "\nShave * * * * * * ! \n") + (2009-10-18 + "\nShave * * * * * * ! \n") + (2009-10-19 + "\nShave * * * * * * ! \n") + (2009-10-20 + "\nShave * * * * * * ! \n") + (2009-10-21 + "\nShave * * * * * ! \n") + (2009-10-22 + "\nShave * * * * * ! \n") + (t (cl-assert nil t "Missing case for: %S!" (symbol-name test-time)))))) + (org-test-at-time (symbol-name test-time) + (should + (string-equal + (if org-habit-show-habits + (cl-case test-time + ((2009-10-15 2009-10-16) + (if org-habit-show-all-today + expected-output-string + "")) + ((2009-10-17 2009-10-18 2009-10-19 2009-10-20 2009-10-21 2009-10-22) + expected-output-string) + (t (cl-assert nil t "Missing case for: %S!" (symbol-name test-time)))) + "") + (progn + (org-agenda nil "f") + (buffer-string)))))))))))) + +(ert-deftest test-org-habit/toggle-display-in-agenda () + "Test the agenda view for a habit." + (let ((org-agenda-custom-commands + '(("f" "no fluff" agenda "" + ;; This differs from `org-test-habit-no-fluff-agenda' by + ;; adding this header. Without this we have cases where + ;; the agenda buffer is completly empty and that causes + ;; funny things to happen + ((org-agenda-overriding-header "h") + (org-agenda-format-date "") + (org-agenda-span 'day) + (org-agenda-show-all-dates nil) + (org-agenda-todo-keyword-format "") + (org-agenda-prefix-format ""))))) + (org-habit-graph-column 7) + (org-habit-following-days 1) + (org-habit-preceding-days 5)) + ;; (test-time . expected-string) + (dolist (test-data '(("2009-10-15" . "h\n\nShave * * * \n") + ("2009-10-17" . "h\n\nShave * * ! \n"))) + (org-test-at-time (car test-data) + (org-test-agenda-with-agenda + (org-test-habit-agenda-string "++" nil) + (org-agenda nil "f") + (should + (string-equal + (if (string-equal (car test-data) "2009-10-17") + (cdr test-data) + "h\n") + (buffer-string))) + (org-habit-toggle-display-in-agenda nil) + (should + (string-equal + "h\n" + (buffer-string))) + (org-habit-toggle-display-in-agenda nil) + (should + (string-equal + (if (string-equal (car test-data) "2009-10-17") + (cdr test-data) + "h\n") + (buffer-string))) + (org-habit-toggle-display-in-agenda t) + (should + (string-equal + (cdr test-data) + (buffer-string)))))))) + +;;; Bad habits + +(ert-deftest test-org-habit/bad-habit-no-repeater () + "Test a habit without a repeater." + (org-test-agenda-with-agenda + "* TODO no repeater +SCHEDULED: <2009-10-17 Sat> +:PROPERTIES: +:STYLE: habit +:END:" + (should-error + (org-agenda nil "a")))) + +(ert-deftest test-org-habit/bad-habit-short-repeater () + "Test a habit with a period of less then 1 day." + (org-test-agenda-with-agenda + "* TODO repeat period less then 1 day +SCHEDULED: <2009-10-17 Sat +0d> +:PROPERTIES: +:STYLE: habit +:END:" + (should-error + (org-agenda nil "a")))) + +(ert-deftest test-org-habit/bad-habit-no-scheduled () + "Test a habit that is not scheduled." + (org-test-agenda-with-agenda + "* TODO no scheduled <2009-10-17 Sat +1d> +:PROPERTIES: +:STYLE: habit +:END:" + (should-error + (org-agenda nil "a")))) + +(ert-deftest test-org-habit/bad-habit-deadline-less-scheduled () + "Test a habit where the deadline is less then or equal to the scheduled." + (dolist (deadline '("1d" "2d")) + (org-test-agenda-with-agenda + (concat + "* TODO deadline < or = to scheduled +SCHEDULED: <2009-10-17 Sat +2d/" deadline "> +:PROPERTIES: +:STYLE: habit +:END:") + (should-error + (org-agenda nil "a"))))) + + +(provide 'test-org-habit) + +;;; test-org-habit.el ends here base-commit: 76ce3fcc573e79d186e4ab6b8d6c06c9d078e498 -- 2.51.0
