See attached two files.

0001-address-comments.patch is simply the changes I've made since the
last review and is not meant to be applied (sorry about the whitespace
noise).

The other file is meant to be applied.

Tested on emacs 28, 29, and 30 inside of containers.  Tested on an emacs
31.0.5 development build outside of any containers.

Ihor Radchenko <[email protected]> writes:

> Morgan Smith <[email protected]> writes:
>
>> TLDR: I wrote a test suite for org habit.  See attached.
>
> Thanks!
> Two comments:
>
> 1. One of the tests is failing on my side:
>    FAILED  test-org-habit/following-days

Org habit has bugs.  Both with respect to daylight savings and
`org-extend-today-until'.  I have codified these bugs in the test suite
and added TODO's.  I am learning that getting time right is very tricky
and while the solution will likely be only a few lines, figuring it out
will be quite difficult.

> 2. I feel that
>    "\nShave                                      *  * *     * *  * !       \n"
>    and similar string are rather hard to read and may create headaches
>    if we ever change the default value of `org-habit-graph-column' for
>    any reason. IMHO, A helper function to format those expected strings
>    will improve the code.

I have shaved off a good deal of the strings (and decoupled those from
`org-habit-graph-column').  I've left a couple as they are.  I feel
testing the complete default configuration at least once is important.

>From e68af3f741c059f4884e756ad840a940b3a8fb83 Mon Sep 17 00:00:00 2001
From: Morgan Smith <[email protected]>
Date: Sat, 18 Oct 2025 21:19:50 -0400
Subject: [PATCH] address comments

---
 testing/lisp/test-org-habit.el | 231 +++++++++++++++++++++------------
 1 file changed, 146 insertions(+), 85 deletions(-)

diff --git a/testing/lisp/test-org-habit.el b/testing/lisp/test-org-habit.el
index 652a2ca70..37bf1d3c2 100644
--- a/testing/lisp/test-org-habit.el
+++ b/testing/lisp/test-org-habit.el
@@ -100,7 +100,8 @@ 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-habit-no-fluff-agenda)
+          (org-habit-graph-column 5))
       (org-test-agenda-with-agenda
           "* TODO habit
 SCHEDULED: <2009-10-21 Sat ++2d>
@@ -111,122 +112,179 @@ test-org-habit/simple-habit
 - State \"DONE\"       from \"TODO\"       [2009-10-17 Sun]"
         (should
          (string-equal
-          "\nhabit                                                   * *  !       \n"
+          "\nhabit                * *  !       \n"
           (progn
             (org-agenda nil "f")
             (buffer-string))))))))
 
+(ert-deftest test-org-habit/org-extend-today-until ()
+  "Test habit graph with `org-extend-today-until' set."
+  (org-test-at-time "2009-10-20"
+    (let ((org-agenda-custom-commands
+           org-test-habit-no-fluff-agenda)
+          (org-habit-preceding-days 5)
+          (org-habit-following-days 5)
+          (org-habit-graph-column 5)
+          (org-habit-show-all-today t))
+      (dolist (org-extend-today-until '(0 1 2))
+        (org-test-agenda-with-agenda
+            "* TODO habit
+SCHEDULED: <2009-10-20 Sat ++1d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-19 Sun 00:20]
+- State \"DONE\"       from \"TODO\"       [2009-10-17 Sun 01:20]"
+          (should
+           (string-equal
+            (cl-case org-extend-today-until
+              (0 "\nhabit  * *!     \n")
+              ;; TODO: This is not correct at all.  Should be the following
+              ;; (1 "\nhabit  ** !   \n")
+              ;; (2 "\nhabit * * !   \n")
+              (t "\nhabit   * *     \n"))
+            (progn
+              (org-agenda nil "f")
+              (buffer-string)))))))))
+
+(ert-deftest test-org-habit/dst ()
+  "Test the habit graph traversing a daylight savings time transition."
+  ;; DST transition [2009-11-01 01:59] -> [2009-11-01 01:00]
+  (org-test-with-timezone "America/New_York"
+    (org-test-at-time "2009-10-28"
+      (let ((org-agenda-custom-commands
+             org-test-habit-no-fluff-agenda)
+            (org-habit-preceding-days 5)
+            (org-habit-following-days 5)
+            (org-habit-graph-column 5))
+        (org-test-agenda-with-agenda
+            "* TODO habit
+SCHEDULED: <2009-10-28 Sat ++1d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-27 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-25 Sun]"
+          (should
+           (string-equal
+            ;; TODO: we lost a day in the transition! should be:
+            ;; "\nhabit  * *!     \n"
+            "\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)))))))
+     (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))))))))
+     (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))))))))
+     (dolist (org-habit-preceding-days '(0 1 2 3 10 20 40 100))
+       (should
+        (string-equal
+         (cl-case org-habit-preceding-days
+           (0 " !       \n")
+           (1 "  !       \n")
+           (2 " * !       \n")
+           (3 "  * !       \n")
+           (10 "    * *  * !       \n")
+           (20 "   *  * *     * *  * !       \n")
+           ((40 100) (concat (make-string (- org-habit-preceding-days 34) 32)
+                             "*   *  *     *   *  * *     * *  * !       \n"))
+           (t (cl-assert nil nil "Missing case!")))
+         (progn
+           (org-agenda nil "f")
+           (buffer-substring (+ 1 org-habit-graph-column) (point-max)))))))))
 
 (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))))))))
+  (org-test-with-timezone "UTC0" ;; Avoid DST.  See `test-org-habit/dst'.
+    (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   "    *  * *     * *  *  \n")
+             ((1 2 3 10 20 40 100)
+              (concat "    *  * *     * *  * !"
+                      (make-string org-habit-following-days 32)
+                      "\n"))
+             (t (cl-assert nil nil "Missing case!")))
+           (progn
+             (org-agenda nil "f")
+             (buffer-substring (+ 1 org-habit-graph-column) (point-max))))))))))
 
 (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
+   (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")
+                     " *   *  * *     * *  *       \n")
                     (2009-10-16
-                     "\nShave                                   *   *  * *     * *  *!       \n")
+                     "*   *  * *     * *  *!       \n")
                     (2009-10-17
-                     "\nShave                                      *  * *     * *  * !       \n")
+                     "   *  * *     * *  * !       \n")
                     (2009-10-18
-                     "\nShave                                     *  * *     * *  *  !       \n")
+                     "  *  * *     * *  *  !       \n")
                     (2009-10-19
-                     "\nShave                                    *  * *     * *  *   !       \n")
+                     " *  * *     * *  *   !       \n")
                     (2009-10-20
-                     "\nShave                                   *  * *     * *  *    !       \n")
+                     "*  * *     * *  *    !       \n")
                     (2009-10-21
-                     "\nShave                                     * *     * *  *     !       \n")
+                     "  * *     * *  *     !       \n")
                     (2009-10-22
-                     "\nShave                                    * *     * *  *      !       \n")
+                     " * *     * *  *      !       \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
+             (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
@@ -234,10 +292,13 @@ test-org-habit/show-habits
                        ((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))))))))))))
+                   "")
+                 (progn
+                   (org-agenda nil "f")
+                   (let ((result (buffer-string)))
+                     (if (string-empty-p result)
+                         result
+                       (substring result (+ 1 org-habit-graph-column)))))))))))))))
 
 (ert-deftest test-org-habit/toggle-display-in-agenda ()
   "Test the agenda view for a habit."
-- 
2.51.0

>From da3cf8e92264c0ac1c0d04c1c0562c4a059e47ce 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 | 401 +++++++++++++++++++++++++++++++++
 1 file changed, 401 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..37bf1d3c2
--- /dev/null
+++ b/testing/lisp/test-org-habit.el
@@ -0,0 +1,401 @@
+;;; 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-habit-graph-column 5))
+      (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/org-extend-today-until ()
+  "Test habit graph with `org-extend-today-until' set."
+  (org-test-at-time "2009-10-20"
+    (let ((org-agenda-custom-commands
+           org-test-habit-no-fluff-agenda)
+          (org-habit-preceding-days 5)
+          (org-habit-following-days 5)
+          (org-habit-graph-column 5)
+          (org-habit-show-all-today t))
+      (dolist (org-extend-today-until '(0 1 2))
+        (org-test-agenda-with-agenda
+            "* TODO habit
+SCHEDULED: <2009-10-20 Sat ++1d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-19 Sun 00:20]
+- State \"DONE\"       from \"TODO\"       [2009-10-17 Sun 01:20]"
+          (should
+           (string-equal
+            (cl-case org-extend-today-until
+              (0 "\nhabit  * *!     \n")
+              ;; TODO: This is not correct at all.  Should be the following
+              ;; (1 "\nhabit  ** !   \n")
+              ;; (2 "\nhabit * * !   \n")
+              (t "\nhabit   * *     \n"))
+            (progn
+              (org-agenda nil "f")
+              (buffer-string)))))))))
+
+(ert-deftest test-org-habit/dst ()
+  "Test the habit graph traversing a daylight savings time transition."
+  ;; DST transition [2009-11-01 01:59] -> [2009-11-01 01:00]
+  (org-test-with-timezone "America/New_York"
+    (org-test-at-time "2009-10-28"
+      (let ((org-agenda-custom-commands
+             org-test-habit-no-fluff-agenda)
+            (org-habit-preceding-days 5)
+            (org-habit-following-days 5)
+            (org-habit-graph-column 5))
+        (org-test-agenda-with-agenda
+            "* TODO habit
+SCHEDULED: <2009-10-28 Sat ++1d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-27 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-25 Sun]"
+          (should
+           (string-equal
+            ;; TODO: we lost a day in the transition! should be:
+            ;; "\nhabit  * *!     \n"
+            "\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 " !       \n")
+           (1 "  !       \n")
+           (2 " * !       \n")
+           (3 "  * !       \n")
+           (10 "    * *  * !       \n")
+           (20 "   *  * *     * *  * !       \n")
+           ((40 100) (concat (make-string (- org-habit-preceding-days 34) 32)
+                             "*   *  *     *   *  * *     * *  * !       \n"))
+           (t (cl-assert nil nil "Missing case!")))
+         (progn
+           (org-agenda nil "f")
+           (buffer-substring (+ 1 org-habit-graph-column) (point-max)))))))))
+
+(ert-deftest test-org-habit/following-days ()
+  "Test how modifiying `org-habit-following-days' affects habits in the agenda."
+  (org-test-with-timezone "UTC0" ;; Avoid DST.  See `test-org-habit/dst'.
+    (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   "    *  * *     * *  *  \n")
+             ((1 2 3 10 20 40 100)
+              (concat "    *  * *     * *  * !"
+                      (make-string org-habit-following-days 32)
+                      "\n"))
+             (t (cl-assert nil nil "Missing case!")))
+           (progn
+             (org-agenda nil "f")
+             (buffer-substring (+ 1 org-habit-graph-column) (point-max))))))))))
+
+(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
+                     " *   *  * *     * *  *       \n")
+                    (2009-10-16
+                     "*   *  * *     * *  *!       \n")
+                    (2009-10-17
+                     "   *  * *     * *  * !       \n")
+                    (2009-10-18
+                     "  *  * *     * *  *  !       \n")
+                    (2009-10-19
+                     " *  * *     * *  *   !       \n")
+                    (2009-10-20
+                     "*  * *     * *  *    !       \n")
+                    (2009-10-21
+                     "  * *     * *  *     !       \n")
+                    (2009-10-22
+                     " * *     * *  *      !       \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")
+                   (let ((result (buffer-string)))
+                     (if (string-empty-p result)
+                         result
+                       (substring result (+ 1 org-habit-graph-column)))))))))))))))
+
+(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
-- 
2.51.0

Reply via email to