> > I added more tests for week and month repeaters. > > Previously, `test-org/auto-repeat-maybe' only had tests for year, day and > > hour repeaters. > > More comments inline :) > > > + (let* ((headline (org-element-lineage > > + (org-element-at-point) > > + 'headline t)) > > You missed inlinetasks.
Added. > > > + (ts (match-string 0)) > > Do you really still need TS? Can't you reuse the timestamp object? > Done. My previous attempts at removing TS failed but this time I succeeded. > > + (cl-macrolet ((test-auto-repeat-maybe (buffer-text test-time expected) > > + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) > > + `(org-test-with-temp-text ,buffer-text > > Why not `(let ...)? > Fixed. > > + (org-test-at-time ,test-time > > + (org-todo "DONE") > > + (should (string-match-p (regexp-quote ,expected) (buffer-string)))))))) > > + "Test `org-auto-repeat-maybe' specifications." > Looks like docstring got displaced. Fixed. Le dim. 5 juil. 2026 à 10:00, Ihor Radchenko <[email protected]> a écrit : > Earl Chase <[email protected]> writes: > > > I added more tests for week and month repeaters. > > Previously, `test-org/auto-repeat-maybe' only had tests for year, day and > > hour repeaters. > > More comments inline :) > > > + (let* ((headline (org-element-lineage > > + (org-element-at-point) > > + 'headline t)) > > You missed inlinetasks. > > > + (ts (match-string 0)) > > Do you really still need TS? Can't you reuse the timestamp object? > > > + (cl-macrolet ((test-auto-repeat-maybe (buffer-text test-time expected) > > + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) > > + `(org-test-with-temp-text ,buffer-text > > Why not `(let ...)? > > > + (org-test-at-time ,test-time > > + (org-todo "DONE") > > + (should (string-match-p (regexp-quote > ,expected) (buffer-string)))))))) > > + "Test `org-auto-repeat-maybe' specifications." > > Looks like docstring got displaced. > > -- > Ihor Radchenko // yantar92, > Org mode maintainer, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> >
From 3385e3b5809ecbcbfd4c7469e2a81df782939a07 Mon Sep 17 00:00:00 2001 From: ApollonDeParnasse <[email protected]> Date: Wed, 1 Jul 2026 17:44:58 -0500 Subject: [PATCH] lisp/org.el: Transition `org-auto-repeat-maybe' to org-element API * lisp/org.el (org-auto-repeat-maybe): Use org-element API to get repeater data for timestamps. * testing/lisp/test-org.el (test-org/auto-repeat-maybe): New tests for `org-auto-repeat-maybe'. --- lisp/org.el | 149 ++++++----- testing/lisp/test-org.el | 534 ++++++++++++++++++++++----------------- 2 files changed, 391 insertions(+), 292 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 294a5eb52..81d91cee4 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -10481,7 +10481,6 @@ This function is run automatically after each state change to a DONE state." (aa (assoc org-last-state org-todo-kwd-alist)) (interpret (nth 1 aa)) (head (nth 2 aa)) - (whata '(("h" . hour) ("d" . day) ("m" . month) ("y" . year))) (msg "Entry repeats: ") (org-log-done nil) (org-todo-log-states nil) @@ -10525,75 +10524,109 @@ This function is run automatically after each state change to a DONE state." ;; a SCHEDULED timestamp without one is removed, as they are no ;; longer relevant. (save-excursion - (let ((scheduled (org-entry-get (point) "SCHEDULED"))) - (when (and scheduled (not (string-match-p org-repeat-re scheduled))) + (let* ((headline (org-element-lineage + (org-element-at-point) + (list 'headline 'inlinetasks) t)) + (scheduled (org-element-property + :scheduled + headline)) + (repeater-unit (org-element-property + :repeater-unit + scheduled)) + (repeater-value (org-element-property + :repeater-value + scheduled)) + (repeater-type (org-element-property + :repeater-type + scheduled)) + (has-valid-repeater (and repeater-unit + repeater-value + repeater-type))) + (when (and scheduled (not has-valid-repeater)) (org-remove-timestamp-with-keyword org-scheduled-string 'planning)))) ;; Update every timestamp with a repeater in the entry. (let ((planning-re (regexp-opt (list org-scheduled-string org-deadline-string)))) (while (re-search-forward org-repeat-re end t) - (let* ((ts (match-string 0)) + (let* ((timestamp (save-match-data + (save-excursion + (goto-char (match-beginning 0)) + (org-element-timestamp-parser)))) + (repeater-type (org-element-property + :repeater-type + timestamp)) + (repeater-unit (org-element-property + :repeater-unit + timestamp)) + (repeater-value (org-element-property + :repeater-value + timestamp)) + (has-start-time (and (org-element-property + :hour-start + timestamp) + (org-element-property + :minute-start + timestamp))) + (time (org-timestamp-to-time timestamp)) + (time-in-seconds (float-time time)) (type (if (not (org-at-planning-p)) "Plain:" (save-excursion (re-search-backward planning-re (line-beginning-position) t) (match-string 0))))) (when (and (org-at-timestamp-p 'agenda) - (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts)) - (let ((n (string-to-number (match-string 2 ts))) - (what (match-string 3 ts))) - (when (equal what "w") (setq n (* n 7) what "d")) - (when (and (equal what "h") - (not (string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}" - ts))) - (user-error - "Cannot repeat in %d hour(s) because no hour has been set" - n)) - ;; Preparation, see if we need to modify the start - ;; date for the change. - (when (match-end 1) - (let ((time (save-match-data (org-time-string-to-time ts))) - (repeater-type (match-string 1 ts))) - (cond - ((equal "." repeater-type) - ;; Shift starting date to today, or now if - ;; repeater is by hours. - (if (equal what "h") - (org-timestamp-change - (floor (- (org-timestamp-to-now ts t)) 60) 'minute) - (org-timestamp-change - (- (org-today) (time-to-days time)) 'day))) - ((equal "+" repeater-type) - (let ((nshiftmax 10) - (nshift 0)) - (while (or (= nshift 0) - (if (equal what "h") - (not (time-less-p nil time)) - (>= (org-today) - (time-to-days time)))) - (when (= nshiftmax (cl-incf nshift)) - (or (y-or-n-p - (format "%d repeater intervals were not \ + repeater-unit + repeater-value) + (when (equal repeater-unit `week) + (setq repeater-value (* repeater-value 7) + repeater-unit 'day)) + (when (and (equal repeater-unit `hour) + (not has-start-time)) + (user-error + "Cannot repeat in %d hour(s) because no hour has been set" + repeater-value)) + ;; Preparation, see if we need to modify the start + ;; date for the change. + (cond + ((equal `restart repeater-type) + ;; Shift starting date to today, or now if + ;; repeater is by hours. + (if (equal repeater-unit `hour) + (org-timestamp-change + (floor (- (- time-in-seconds (float-time))) 60) 'minute) + (org-timestamp-change + (- (org-today) (time-to-days time)) 'day))) + ((equal `catch-up repeater-type) + (let ((nshiftmax 10) + (nshift 0)) + (while (or (= nshift 0) + (if (equal repeater-unit `hour) + (not (time-less-p nil time)) + (>= (org-today) + (time-to-days time)))) + (when (= nshiftmax (cl-incf nshift)) + (or (y-or-n-p + (format "%d repeater intervals were not \ enough to shift date past today. Continue? " - nshift)) - (user-error "Abort"))) - (org-timestamp-change n (cdr (assoc what whata))) - (org-in-regexp org-ts-regexp3) - (setq ts (match-string 1)) - (setq time - (save-match-data - (org-time-string-to-time ts))))) - (org-timestamp-change (- n) (cdr (assoc what whata))) - ;; Rematch, so that we have everything in place - ;; for the real shift. - (org-in-regexp org-ts-regexp3) - (setq ts (match-string 1)) - (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" - ts))))) - (save-excursion - (org-timestamp-change n (cdr (assoc what whata)) nil t)) - (setq msg - (concat msg type " " org-last-changed-timestamp " "))))))) + nshift)) + (user-error "Abort"))) + (org-timestamp-change repeater-value repeater-unit) + (org-in-regexp org-ts-regexp3) + (setq timestamp (save-match-data + (save-excursion + (goto-char (match-beginning 0)) + (org-element-timestamp-parser)))) + (setq time + (save-match-data + (org-timestamp-to-time timestamp))))) + (org-timestamp-change (- repeater-value) repeater-unit) + ;; Rematch, so that we have everything in place + ;; for the real shift. + (org-in-regexp org-ts-regexp3))) + (save-excursion + (org-timestamp-change repeater-value repeater-unit nil t)) + (setq msg + (concat msg type " " org-last-changed-timestamp " ")))))) (save-excursion (run-hooks 'org-todo-repeat-hook)) (setq org-log-post-message msg) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 9e9be9ebc..ed5e8a3fe 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -8906,267 +8906,333 @@ should not be touched.")) (ert-deftest test-org/auto-repeat-maybe () "Test `org-auto-repeat-maybe' specifications." - ;; Do not auto repeat when there is no valid time stamp with - ;; a repeater in the entry. - (should-not - (string-prefix-p - "* TODO H" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu>" - (org-todo "DONE") - (buffer-string))))) - (should-not - (string-prefix-p - "* TODO H" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text "* TODO H\n# <2012-03-29 Thu>" - (org-todo "DONE") - (buffer-string))))) - ;; When switching to DONE state, switch back to first TODO keyword - ;; in sequence, or the same keyword if they have different types. - (should - (string-prefix-p - "* TODO H" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - (should - (string-prefix-p - "* KWD1 H" - (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE")))) - (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - (should - (string-prefix-p - "* KWD2 H" - (let ((org-todo-keywords '((type "KWD1" "KWD2" "DONE")))) - (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - ;; If there was no TODO keyword in the first place, do not insert - ;; any either. - (should - (string-prefix-p - "* H" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text "* H\n<2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - ;; Revert to REPEAT_TO_STATE, if set. - (should - (string-prefix-p - "* KWD2 H" - (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE")))) - (org-test-with-temp-text - "* KWD2 H + (cl-macrolet ((test-auto-repeat-maybe (buffer-text test-time expected) + `(let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text ,buffer-text + (org-test-at-time ,test-time + (org-todo "DONE") + (should (string-match-p + (regexp-quote ,expected) + (buffer-string)))))))) + ;; Do not auto repeat when there is no valid time stamp with + ;; a repeater in the entry. + (should-not + (string-prefix-p + "* TODO H" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu>" + (org-todo "DONE") + (buffer-string))))) + (should-not + (string-prefix-p + "* TODO H" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text "* TODO H\n# <2012-03-29 Thu>" + (org-todo "DONE") + (buffer-string))))) + ;; When switching to DONE state, switch back to first TODO keyword + ;; in sequence, or the same keyword if they have different types. + (should + (string-prefix-p + "* TODO H" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>" + (org-todo "DONE") + (buffer-string))))) + (should + (string-prefix-p + "* KWD1 H" + (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE")))) + (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>" + (org-todo "DONE") + (buffer-string))))) + (should + (string-prefix-p + "* KWD2 H" + (let ((org-todo-keywords '((type "KWD1" "KWD2" "DONE")))) + (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>" + (org-todo "DONE") + (buffer-string))))) + ;; If there was no TODO keyword in the first place, do not insert + ;; any either. + (should + (string-prefix-p + "* H" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text "* H\n<2012-03-29 Thu +2y>" + (org-todo "DONE") + (buffer-string))))) + ;; Revert to REPEAT_TO_STATE, if set. + (should + (string-prefix-p + "* KWD2 H" + (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE")))) + (org-test-with-temp-text + "* KWD2 H :PROPERTIES: :REPEAT_TO_STATE: KWD2 :END: <2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - ;; When switching to DONE state, update base date. If there are - ;; multiple repeated time stamps, update them all. - (should - (string-match-p - "<2014-03-29 .* \\+2y>" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - (should - (string-match-p - "<2015-03-04 .* \\+1y>" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text - "* TODO H\n<2012-03-29 Thu. +2y>\n<2014-03-04 Tue +1y>" - (org-todo "DONE") - (buffer-string))))) - ;; Throw an error if repeater unit is the hour and no time is - ;; provided in the timestamp. - (should-error - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2h>" - (org-todo "DONE") - (buffer-string)))) - ;; Handle every repeater type using hours step. - (should - (string-match-p - (regexp-quote "<2014-03-04 02:00 +8h>") - (org-test-without-dow - (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 +8h>" - (org-todo "DONE") - (buffer-string)))))) - (should - (string-match-p - (regexp-quote "<2014-03-04 10:00 ++8h>") - (org-test-without-dow - (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>" - (org-todo "DONE") - (buffer-string)))))) - (should - (string-match-p - (regexp-quote "<2014-03-04 10:35 .+8h>") - (org-test-without-dow - (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>" - (org-todo "DONE") - (buffer-string)))))) - ;; Handle `org-extend-today-until'. - (should - (string-match-p - (regexp-quote "<2014-03-04 ++1d>") - (let ((org-extend-today-until 4)) - (org-test-without-dow - (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 ++1d>" (org-todo "DONE") - (buffer-string))))))) - (should - (string-match-p - (regexp-quote "<2014-03-06 17:00 ++1d>") - (let ((org-extend-today-until 4)) - (org-test-without-dow - (org-test-at-time "<2014-03-05 18:00>" - (org-test-with-temp-text "* TODO H\n<2014-03-04 17:00 ++1d>" + (buffer-string))))) + ;; When switching to DONE state, update base date. If there are + ;; multiple repeated time stamps, update them all. + (should + (string-match-p + "<2014-03-29 .* \\+2y>" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>" (org-todo "DONE") - (buffer-string))))))) - (should - (string-match-p - (regexp-quote "<2014-03-04 10:00 ++8h>") - (let ((org-extend-today-until 4)) + (buffer-string))))) + ;; week tests + (test-auto-repeat-maybe + "* TODO Not Done Yet\n<2026-02-01 Sun 23:29 +2w>" + "2026-02-04 Mon 08:00" + "<2026-02-15 Sun 23:29 +2w>") + (test-auto-repeat-maybe + "* TODO Pending <2026-03-16 Mon .+3w>" + "2026-03-16 Mon 13:11" + "<2026-04-06 Mon .+3w>") + (test-auto-repeat-maybe + "* TODO Not Done Yet\nSCHEDULED: <2026-02-18 Wed 09:30 ++1w>" + "2026-02-19 Thu 09:30" + "<2026-02-25 Wed 09:30 ++1w") + ;; month tests + (test-auto-repeat-maybe + "* TODO Not Done Yet\n<2026-02-11 Wed 11:00 +2m>" + "<2026-02-12 Thu 11:18>" + "<2026-04-11 Sat 11:00 +2m>") + (test-auto-repeat-maybe + "* TODO Pending <2026-06-20 Sat .+3m>" + "<2026-06-20 Sat 02:12>" + "<2026-09-20 Sun .+3m>") + (test-auto-repeat-maybe + "* TODO Not Done Yet\nSCHEDULED: <2026-05-09 Wed 05:30 ++1m>" + "<2026-05-09 Sat 06:10>" + "<2026-06-09 Tue 05:30 ++1m") + (should + (string-match-p + "<2015-03-04 .* \\+1y>" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text + "* TODO H\n<2012-03-29 Thu. +2y>\n<2014-03-04 Tue +1y>" + (org-todo "DONE") + (buffer-string))))) + ;; Throw an error if repeater unit is the hour and no time is + ;; provided in the timestamp. + (should-error + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2h>" + (org-todo "DONE") + (buffer-string)))) + ;; Handle every repeater type using hours step. + (should + (string-match-p + (regexp-quote "<2014-03-04 02:00 +8h>") (org-test-without-dow (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>" - (org-todo "DONE") - (buffer-string))))))) - (should - (string-match-p - (regexp-quote "<2014-03-04 18:00 .+1d>") - (let ((org-extend-today-until 4)) + (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 +8h>" + (org-todo "DONE") + (buffer-string)))))) + (should + (string-match-p + (regexp-quote "<2014-03-04 10:00 ++8h>") (org-test-without-dow (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+1d>" - (org-todo "DONE") - (buffer-string))))))) - (should - (string-match-p - (regexp-quote "<2014-03-04 10:35 .+8h>") - (let ((org-extend-today-until 4)) + (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>" + (org-todo "DONE") + (buffer-string)))))) + (should + (string-match-p + (regexp-quote "<2014-03-04 10:35 .+8h>") (org-test-without-dow (org-test-at-time "<2014-03-04 02:35>" - (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>" + (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>" + (org-todo "DONE") + (buffer-string)))))) + ;; Handle `org-extend-today-until'. + (should + (string-match-p + (regexp-quote "<2014-03-04 ++1d>") + (let ((org-extend-today-until 4)) + (org-test-without-dow + (org-test-at-time "<2014-03-04 02:35>" + (org-test-with-temp-text "* TODO H\n<2014-03-03 ++1d>" + (org-todo "DONE") + (buffer-string))))))) + (should + (string-match-p + (regexp-quote "<2014-03-06 17:00 ++1d>") + (let ((org-extend-today-until 4)) + (org-test-without-dow + (org-test-at-time "<2014-03-05 18:00>" + (org-test-with-temp-text "* TODO H\n<2014-03-04 17:00 ++1d>" + (org-todo "DONE") + (buffer-string))))))) + (should + (string-match-p + (regexp-quote "<2014-03-04 10:00 ++8h>") + (let ((org-extend-today-until 4)) + (org-test-without-dow + (org-test-at-time "<2014-03-04 02:35>" + (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>" + (org-todo "DONE") + (buffer-string))))))) + (should + (string-match-p + (regexp-quote "<2014-03-04 18:00 .+1d>") + (let ((org-extend-today-until 4)) + (org-test-without-dow + (org-test-at-time "<2014-03-04 02:35>" + (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+1d>" + (org-todo "DONE") + (buffer-string))))))) + (should + (string-match-p + (regexp-quote "<2014-03-04 10:35 .+8h>") + (let ((org-extend-today-until 4)) + (org-test-without-dow + (org-test-at-time "<2014-03-04 02:35>" + (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>" + (org-todo "DONE") + (buffer-string))))))) + ;; week tests + (let ((org-extend-today-until 4)) + (test-auto-repeat-maybe + "* TODO Not Done Yet\n<2026-02-01 Sun 04:15 +2w>" + "2026-02-04 Mon 04:30" + "<2026-02-15 Sun 04:15 +2w>")) + (let ((org-extend-today-until 4)) + (test-auto-repeat-maybe + "* TODO Pending <2026-03-16 Mon 03:45 .+3w>" + "2026-03-16 Mon 03:45" + "<2026-04-05 Sun 03:45 .+3w>")) + (let ((org-extend-today-until 4)) + (test-auto-repeat-maybe + "* TODO Not Done Yet\nSCHEDULED: <2026-02-18 Wed 02:00 ++1w>" + "2026-02-19 Thu 02:00" + "<2026-02-25 Wed 02:00 ++1w")) + ;; month tests + (let ((org-extend-today-until 3)) + (test-auto-repeat-maybe + "* TODO Not Done Yet\n<2026-02-11 Wed 02:30 +2m>" + "<2026-02-12 Wed 3:18>" + "<2026-04-11 Sat 02:30 +2m>")) + (let ((org-extend-today-until 3)) + (test-auto-repeat-maybe + "* TODO Pending <2026-06-20 Sat 01:30 .+3m>" + "<2026-06-20 Sat 04:12>" + "<2026-09-20 Sun 01:30 .+3m>")) + (let ((org-extend-today-until 3)) + (test-auto-repeat-maybe + "* TODO Not Done Yet\nSCHEDULED: <2026-05-09 Wed 01:30 ++1m>" + "<2026-05-09 Sat 05:00>" + "<2026-06-09 Tue 01:30 ++1m")) + ;; Do not repeat inactive time stamps with a repeater. + (should-not + (string-match-p + "\\[2014-03-29 .* \\+2y\\]" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text + "* TODO H\n[2012-03-29 Thu. +2y]" (org-todo "DONE") - (buffer-string))))))) - ;; Do not repeat inactive time stamps with a repeater. - (should-not - (string-match-p - "\\[2014-03-29 .* \\+2y\\]" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text - "* TODO H\n[2012-03-29 Thu. +2y]" - (org-todo "DONE") - (buffer-string))))) - ;; Do not repeat commented time stamps. - (should-not - (string-prefix-p - "<2015-03-04 .* \\+1y>" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text - "* TODO H\n<2012-03-29 Thu +2y>\n# <2014-03-04 Tue +1y>" - (org-todo "DONE") - (buffer-string))))) - (should-not - (string-prefix-p - "<2015-03-04 .* \\+1y>" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text - "* TODO H + (buffer-string))))) + ;; Do not repeat commented time stamps. + (should-not + (string-prefix-p + "<2015-03-04 .* \\+1y>" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text + "* TODO H\n<2012-03-29 Thu +2y>\n# <2014-03-04 Tue +1y>" + (org-todo "DONE") + (buffer-string))))) + (should-not + (string-prefix-p + "<2015-03-04 .* \\+1y>" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text + "* TODO H <2012-03-29 Thu. +2y> #+BEGIN_EXAMPLE <2014-03-04 Tue +1y> #+END_EXAMPLE" - (org-todo "DONE") - (buffer-string))))) - ;; When `org-log-repeat' is non-nil or there is a CLOCK in the - ;; entry, record time of last repeat. - (should-not - (string-match-p - ":LAST_REPEAT:" - (let ((org-todo-keywords '((sequence "TODO" "DONE"))) - (org-log-repeat nil)) - (cl-letf (((symbol-function 'org-add-log-setup) - (lambda (&rest _args) nil))) - (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>" (org-todo "DONE") - (buffer-string)))))) - (should - (string-match-p - ":LAST_REPEAT:" - (let ((org-todo-keywords '((sequence "TODO" "DONE"))) - (org-log-repeat t)) - (cl-letf (((symbol-function 'org-add-log-setup) - (lambda (&rest _args) nil))) - (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>" - (org-todo "DONE") - (buffer-string)))))) - (should - (string-match-p - ":LAST_REPEAT:" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (cl-letf (((symbol-function 'org-add-log-setup) - (lambda (&rest _args) nil))) - (org-test-with-temp-text - "* TODO H\n<2012-03-29 Thu +2y>\nCLOCK: [2012-03-29 Thu 16:40]" + (buffer-string))))) + ;; When `org-log-repeat' is non-nil or there is a CLOCK in the + ;; entry, record time of last repeat. + (should-not + (string-match-p + ":LAST_REPEAT:" + (let ((org-todo-keywords '((sequence "TODO" "DONE"))) + (org-log-repeat nil)) + (cl-letf (((symbol-function 'org-add-log-setup) + (lambda (&rest _args) nil))) + (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>" + (org-todo "DONE") + (buffer-string)))))) + (should + (string-match-p + ":LAST_REPEAT:" + (let ((org-todo-keywords '((sequence "TODO" "DONE"))) + (org-log-repeat t)) + (cl-letf (((symbol-function 'org-add-log-setup) + (lambda (&rest _args) nil))) + (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>" + (org-todo "DONE") + (buffer-string)))))) + (should + (string-match-p + ":LAST_REPEAT:" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (cl-letf (((symbol-function 'org-add-log-setup) + (lambda (&rest _args) nil))) + (org-test-with-temp-text + "* TODO H\n<2012-03-29 Thu +2y>\nCLOCK: [2012-03-29 Thu 16:40]" + (org-todo "DONE") + (buffer-string)))))) + ;; When a SCHEDULED entry has no repeater, remove it upon repeating + ;; the entry as it is no longer relevant. + (should-not + (string-match-p + "^SCHEDULED:" + (let ((org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text + "* TODO H\nSCHEDULED: <2014-03-04 Tue>\n<2012-03-29 Thu +2y>" (org-todo "DONE") - (buffer-string)))))) - ;; When a SCHEDULED entry has no repeater, remove it upon repeating - ;; the entry as it is no longer relevant. - (should-not - (string-match-p - "^SCHEDULED:" - (let ((org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text - "* TODO H\nSCHEDULED: <2014-03-04 Tue>\n<2012-03-29 Thu +2y>" - (org-todo "DONE") - (buffer-string))))) - ;; Properly advance repeater even when a clock entry is specified - ;; and `org-log-repeat' is nil. - (should - (string-match-p - "SCHEDULED: <2014-03-29" - (let ((org-log-repeat nil) - (org-todo-keywords '((sequence "TODO" "DONE")))) - (org-test-with-temp-text - "* TODO H + (buffer-string))))) + ;; Properly advance repeater even when a clock entry is specified + ;; and `org-log-repeat' is nil. + (should + (string-match-p + "SCHEDULED: <2014-03-29" + (let ((org-log-repeat nil) + (org-todo-keywords '((sequence "TODO" "DONE")))) + (org-test-with-temp-text + "* TODO H SCHEDULED: <2012-03-29 Thu +2y> CLOCK: [2012-03-29 Thu 10:00]--[2012-03-29 Thu 16:40] => 6:40" - (org-todo "DONE") - (buffer-string))))) - ;; Make sure that logbook state change record does not get - ;; duplicated when `org-log-repeat' `org-log-done' are non-nil. - (should - (string-match-p - (rx "* TODO Read book + (org-todo "DONE") + (buffer-string))))) + ;; Make sure that logbook state change record does not get + ;; duplicated when `org-log-repeat' `org-log-done' are non-nil. + (should + (string-match-p + (rx "* TODO Read book SCHEDULED: <2021-06-16 " (1+ (not space)) " +1d> :PROPERTIES: :LAST_REPEAT:" (1+ nonl) " :END: - State \"DONE\" from \"TODO\"" (1+ nonl) buffer-end) - (let ((org-log-repeat 'time) - (org-todo-keywords '((sequence "TODO" "|" "DONE(d!)"))) - (org-log-into-drawer nil)) - (org-test-with-temp-text - "* TODO Read book + (let ((org-log-repeat 'time) + (org-todo-keywords '((sequence "TODO" "|" "DONE(d!)"))) + (org-log-into-drawer nil)) + (org-test-with-temp-text + "* TODO Read book SCHEDULED: <2021-06-15 Tue +1d>" - (org-todo "DONE") - (when (memq 'org-add-log-note (default-value 'post-command-hook)) - (org-add-log-note)) - (buffer-string)))))) + (org-todo "DONE") + (when (memq 'org-add-log-note (default-value 'post-command-hook)) + (org-add-log-note)) + (buffer-string))))))) (ert-deftest test-org/org-log-done () "Test `org-log-done' specifications. -- 2.54.0
