emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master 76ef9bf 13/13: Merge commit '2c5ac0cb808ae6953fbc74cc49724


From: Ian Dunn
Subject: [elpa] master 76ef9bf 13/13: Merge commit '2c5ac0cb808ae6953fbc74cc497245dafb51051f'
Date: Sun, 25 Nov 2018 14:09:49 -0500 (EST)

branch: master
commit 76ef9bf5f892c4c4f5145dcdfec84f922f906892
Merge: 7cacd8a 2c5ac0c
Author: Ian Dunn <address@hidden>
Commit: Ian Dunn <address@hidden>

    Merge commit '2c5ac0cb808ae6953fbc74cc497245dafb51051f'
---
 packages/org-edna/defaults.mk        |   26 -
 packages/org-edna/org-edna-tests.el  | 1416 ++++++++++++++++++++++++----------
 packages/org-edna/org-edna-tests.org |  217 +++++-
 packages/org-edna/org-edna.el        |  219 +++++-
 packages/org-edna/org-edna.info      |  527 +++++++++----
 packages/org-edna/org-edna.org       |  300 +++++--
 packages/org-edna/test.mk            |    1 +
 7 files changed, 2026 insertions(+), 680 deletions(-)

diff --git a/packages/org-edna/defaults.mk b/packages/org-edna/defaults.mk
deleted file mode 100644
index f599ee4..0000000
--- a/packages/org-edna/defaults.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-# This is part of org-edna
-#
-#  Copyright (C) 2017-2018 Free Software Foundation, Inc.
-#
-#  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 <http://www.gnu.org/licenses/>.
-
-emacs = emacs
-
-prefix = /usr/share
-
-org_path = $(prefix)/emacs/site-lisp/org
-
-info_dir = $(prefix)/info
-
-lisp_dir = $(prefix)/emacs/site-lisp/
diff --git a/packages/org-edna/org-edna-tests.el 
b/packages/org-edna/org-edna-tests.el
index da766a9..b6adf15 100644
--- a/packages/org-edna/org-edna-tests.el
+++ b/packages/org-edna/org-edna-tests.el
@@ -3,9 +3,6 @@
 ;; Copyright (C) 2017-2018 Free Software Foundation, Inc.
 
 ;; Author: Ian Dunn <address@hidden>
-;; Keywords: convenience, text, org
-;; Version: 1.0
-;; Package-Requires: ((emacs "25.1") (seq "2.19") (org "8.0"))
 
 ;; This file is NOT part of GNU Emacs.
 
@@ -29,6 +26,9 @@
 (require 'ert)
 (require 'org-id)
 
+(defvar org-edna-test-inhibit-messages nil
+  "Whether to inhibit messages (apart from ERT messages).")
+
 (defconst org-edna-test-dir
   (expand-file-name (file-name-directory (or load-file-name 
buffer-file-name))))
 
@@ -64,10 +64,87 @@
 (defconst org-edna-test-relative-archived-child 
"a4b6131e-0560-4201-86d5-f32b36363431")
 (defconst org-edna-test-relative-child-with-done 
"4a1d74a2-b032-47da-a823-b32f5cab0aae")
 
+(defun org-edna-test-restore-test-file ()
+  "Restore the test file back to its original state."
+  (with-current-buffer (get-file-buffer org-edna-test-file)
+    (revert-buffer nil t)))
+
+(defmacro org-edna-protect-test-file (&rest body)
+  (declare (indent 0))
+  `(unwind-protect
+       (progn ,@body)
+     ;; Change the test file back to its original state.
+     (org-edna-test-restore-test-file)))
+
+(defmacro org-edna-test-setup (&rest body)
+  "Common settings for tests."
+  (declare (indent 0))
+  ;; Override `current-time' so we can get a deterministic value
+  `(cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
+              ;; Only use the test file in the agenda
+              (org-agenda-files `(,org-edna-test-file))
+              ;; Ensure interactive modification of TODO states works.
+              (org-todo-keywords '((sequence "TODO" "|" "DONE")))
+              ;; Only block based on Edna
+              (org-blocker-hook 'org-edna-blocker-function)
+              ;; Only trigger based on Edna
+              (org-trigger-hook 'org-edna-trigger-function)
+              ;; Inhibit messages if indicated
+              (inhibit-message org-edna-test-inhibit-messages))
+     ,@body))
+
+(defmacro org-edna-with-point-at-test-heading (heading-id &rest body)
+  (declare (indent 1))
+  `(org-with-point-at (org-edna-find-test-heading ,heading-id)
+     ,@body))
+
+(defmacro org-edna-with-test-heading (heading-id &rest body)
+  "Establish a test case with test heading HEADING-ID.
+
+HEADING-ID is a UUID string of a heading to use.
+
+Moves point to the heading, protects the test file, sets default
+test settings, then runs BODY."
+  (declare (indent 1))
+  `(org-edna-test-setup
+     (org-edna-protect-test-file
+       (org-edna-with-point-at-test-heading ,heading-id
+         ,@body))))
+
 (defun org-edna-find-test-heading (id)
-  "Find the test heading with id ID."
+  "Find the test heading with id ID.
+
+This avoids org-id digging into its internal database."
   (org-id-find-id-in-file id org-edna-test-file t))
 
+;; _test exists to give more detailed reports in ERT output.
+(defun org-edna-test-compare-todos (pom expected-state _test)
+  (string-equal (org-entry-get pom "TODO") expected-state))
+
+(defun org-edna-test-change-todo-state (pom new-state)
+  (org-with-point-at pom (org-todo new-state)))
+
+(defun org-edna-test-check-block (pom _test)
+  "Check if the heading at point-or-marker POM is blocked."
+  (org-edna-test-change-todo-state pom "DONE")
+  (org-edna-test-compare-todos pom "TODO" _test))
+
+(defun org-edna-test-mark-done (&rest poms)
+  "Mark all points-or-markers in POMS as DONE."
+  (dolist (pom poms)
+    (org-edna-test-change-todo-state pom "DONE")))
+
+(defun org-edna-test-mark-todo (&rest poms)
+  "Mark all points-or-markers in POMS as TODO."
+  (dolist (pom poms)
+    (org-edna-test-change-todo-state pom "TODO")))
+
+(defun org-edna-test-children-marks ()
+  (org-edna-collect-descendants nil))
+
+
+;;; Parser Tests
+
 (ert-deftest org-edna-parse-form-no-arguments ()
   (let* ((input-string "test-string")
          (parsed       (org-edna-parse-string-form input-string)))
@@ -437,48 +514,48 @@
     (should (equal output-form expected-form))))
 
 
-;; Finders
+;;; Finders
 
 (defsubst org-edna-heading (pom)
   (org-with-point-at pom
     (org-get-heading t t t t)))
 
 (ert-deftest org-edna-finder/match-single-arg ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (targets (org-edna-finder/match "test&1")))
-    (should (= (length targets) 2))
-    (should (string-equal (org-edna-heading (nth 0 targets)) "Tagged Heading 
1"))
-    (should (string-equal (org-edna-heading (nth 1 targets)) "Tagged Heading 
2"))))
+  (org-edna-test-setup
+    (let* ((targets (org-edna-finder/match "test&1")))
+      (should (= (length targets) 2))
+      (should (string-equal (org-edna-heading (nth 0 targets)) "Tagged Heading 
1"))
+      (should (string-equal (org-edna-heading (nth 1 targets)) "Tagged Heading 
2")))))
 
 (ert-deftest org-edna-finder/ids-single ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (test-id "caccd0a6-d400-410a-9018-b0635b07a37e")
-         (targets (org-edna-finder/ids test-id)))
-    (should (= (length targets) 1))
-    (should (string-equal (org-edna-heading (nth 0 targets)) "Blocking Test"))
-    (should (string-equal (org-entry-get (nth 0 targets) "ID") test-id))))
+  (org-edna-test-setup
+    (let* ((test-id "caccd0a6-d400-410a-9018-b0635b07a37e")
+           (targets (org-edna-finder/ids test-id)))
+      (should (= (length targets) 1))
+      (should (string-equal (org-edna-heading (nth 0 targets)) "Blocking 
Test"))
+      (should (string-equal (org-entry-get (nth 0 targets) "ID") test-id)))))
 
 (ert-deftest org-edna-finder/ids-multiple ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (test-ids '("0d491588-7da3-43c5-b51a-87fbd34f79f7"
-                     "b010cbad-60dc-46ef-a164-eb155e62cbb2"))
-         (targets (apply 'org-edna-finder/ids test-ids)))
-    (should (= (length targets) 2))
-    (should (string-equal (org-edna-heading (nth 0 targets)) "ID Heading 1"))
-    (should (string-equal (org-entry-get (nth 0 targets) "ID") (nth 0 
test-ids)))
-    (should (string-equal (org-edna-heading (nth 1 targets)) "ID Heading 2"))
-    (should (string-equal (org-entry-get (nth 1 targets) "ID") (nth 1 
test-ids)))))
+  (org-edna-test-setup
+    (let* ((test-ids '("0d491588-7da3-43c5-b51a-87fbd34f79f7"
+                       "b010cbad-60dc-46ef-a164-eb155e62cbb2"))
+           (targets (apply 'org-edna-finder/ids test-ids)))
+      (should (= (length targets) 2))
+      (should (string-equal (org-edna-heading (nth 0 targets)) "ID Heading 1"))
+      (should (string-equal (org-entry-get (nth 0 targets) "ID") (nth 0 
test-ids)))
+      (should (string-equal (org-edna-heading (nth 1 targets)) "ID Heading 2"))
+      (should (string-equal (org-entry-get (nth 1 targets) "ID") (nth 1 
test-ids))))))
 
 (ert-deftest org-edna-finder/match-blocker ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (heading (org-id-find "caccd0a6-d400-410a-9018-b0635b07a37e" t))
-         (blocker (org-entry-get heading "BLOCKER"))
-         blocking-entry)
-    (should (string-equal "match(\"test&1\")" blocker))
-    (org-with-point-at heading
-      (setq blocking-entry (org-edna-process-form blocker 'condition)))
-    (should (string-equal (substring-no-properties blocking-entry)
-                          "TODO Tagged Heading 1 :1:test:"))))
+  (org-edna-test-setup
+    (let* ((heading (org-edna-find-test-heading 
"caccd0a6-d400-410a-9018-b0635b07a37e"))
+           (blocker (org-entry-get heading "BLOCKER"))
+           blocking-entry)
+      (should (string-equal "match(\"test&1\")" blocker))
+      (org-with-point-at heading
+        (setq blocking-entry (org-edna-process-form blocker 'condition)))
+      (should (string-equal (substring-no-properties blocking-entry)
+                            "TODO Tagged Heading 1 :1:test:")))))
 
 (ert-deftest org-edna-finder/file ()
   (let* ((targets (org-edna-finder/file org-edna-test-file)))
@@ -499,16 +576,16 @@
 
 (ert-deftest org-edna-finder/self ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find "82a4ac3d-9565-4f94-bc84-2bbfd8d7d96c" t))
+         (current (org-edna-find-test-heading 
"82a4ac3d-9565-4f94-bc84-2bbfd8d7d96c"))
          (targets (org-with-point-at current (org-edna-finder/self))))
     (should (= (length targets) 1))
     (should (equal current (nth 0 targets)))))
 
 (ert-deftest org-edna-finder/siblings ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-sibling-one-id t))
+         (current (org-edna-find-test-heading org-edna-test-sibling-one-id))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     `(,org-edna-test-sibling-one-id
                       ,org-edna-test-sibling-two-id
                       ,org-edna-test-sibling-three-id)))
@@ -518,9 +595,9 @@
 
 (ert-deftest org-edna-finder/siblings-wrap ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find "72534efa-e932-460b-ae2d-f044a0074815" t))
+         (current (org-edna-find-test-heading 
"72534efa-e932-460b-ae2d-f044a0074815"))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     '("06aca55e-ce09-46df-80d7-5b52e55d6505"
                       "82a4ac3d-9565-4f94-bc84-2bbfd8d7d96c")))
          (targets (org-with-point-at current
@@ -530,9 +607,9 @@
 
 (ert-deftest org-edna-finder/rest-of-siblings ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find "72534efa-e932-460b-ae2d-f044a0074815" t))
+         (current (org-edna-find-test-heading 
"72534efa-e932-460b-ae2d-f044a0074815"))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     '("06aca55e-ce09-46df-80d7-5b52e55d6505")))
          (targets (org-with-point-at current
                     (org-edna-finder/rest-of-siblings))))
@@ -541,9 +618,9 @@
 
 (ert-deftest org-edna-finder/next-sibling ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find "72534efa-e932-460b-ae2d-f044a0074815" t))
+         (current (org-edna-find-test-heading 
"72534efa-e932-460b-ae2d-f044a0074815"))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     '("06aca55e-ce09-46df-80d7-5b52e55d6505")))
          (targets (org-with-point-at current
                     (org-edna-finder/next-sibling))))
@@ -552,9 +629,9 @@
 
 (ert-deftest org-edna-finder/next-sibling-wrap-next ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-sibling-two-id t))
+         (current (org-edna-find-test-heading org-edna-test-sibling-two-id))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     `(,org-edna-test-sibling-three-id)))
          (targets (org-with-point-at current
                     (org-edna-finder/next-sibling-wrap))))
@@ -563,9 +640,9 @@
 
 (ert-deftest org-edna-finder/next-sibling-wrap-wrap ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-sibling-three-id t))
+         (current (org-edna-find-test-heading org-edna-test-sibling-three-id))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     `(,org-edna-test-sibling-one-id)))
          (targets (org-with-point-at current
                     (org-edna-finder/next-sibling-wrap))))
@@ -574,9 +651,9 @@
 
 (ert-deftest org-edna-finder/previous-sibling ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find "06aca55e-ce09-46df-80d7-5b52e55d6505" t))
+         (current (org-edna-find-test-heading 
"06aca55e-ce09-46df-80d7-5b52e55d6505"))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     '("72534efa-e932-460b-ae2d-f044a0074815")))
          (targets (org-with-point-at current
                     (org-edna-finder/previous-sibling))))
@@ -585,8 +662,8 @@
 
 (ert-deftest org-edna-finder/first-child ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-parent-id t))
-         (first-child (list (org-id-find org-edna-test-sibling-one-id t)))
+         (current (org-edna-find-test-heading org-edna-test-parent-id))
+         (first-child (list (org-edna-find-test-heading 
org-edna-test-sibling-one-id)))
          (targets (org-with-point-at current
                     (org-edna-finder/first-child))))
     (should (= (length targets) 1))
@@ -594,9 +671,9 @@
 
 (ert-deftest org-edna-finder/children ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-parent-id t))
+         (current (org-edna-find-test-heading org-edna-test-parent-id))
          (children (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     `(,org-edna-test-sibling-one-id
                       ,org-edna-test-sibling-two-id
                       ,org-edna-test-sibling-three-id)))
@@ -607,8 +684,8 @@
 
 (ert-deftest org-edna-finder/parent ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-sibling-one-id t))
-         (parent (list (org-id-find org-edna-test-parent-id t)))
+         (current (org-edna-find-test-heading org-edna-test-sibling-one-id))
+         (parent (list (org-edna-find-test-heading org-edna-test-parent-id)))
          (targets (org-with-point-at current
                     (org-edna-finder/parent))))
     (should (= (length targets) 1))
@@ -616,9 +693,9 @@
 
 (ert-deftest org-edna-relatives/from-top ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-sibling-one-id t))
+         (current (org-edna-find-test-heading org-edna-test-sibling-one-id))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     `(,org-edna-test-sibling-one-id)))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives 'from-top 1))))
@@ -626,9 +703,9 @@
 
 (ert-deftest org-edna-relatives/from-bottom ()
   (let* ((org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find org-edna-test-sibling-one-id t))
+         (current (org-edna-find-test-heading org-edna-test-sibling-one-id))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     `(,org-edna-test-sibling-three-id)))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives 'from-bottom 1))))
@@ -639,9 +716,9 @@
          (target-list `(,org-edna-test-sibling-two-id))
          (arg 'forward-wrap)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -652,9 +729,9 @@
          (target-list `(,org-edna-test-sibling-one-id))
          (arg 'forward-wrap)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -665,9 +742,9 @@
          (target-list `(,org-edna-test-sibling-two-id))
          (arg 'forward-no-wrap)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -678,9 +755,9 @@
          (target-list nil)
          (arg 'forward-no-wrap)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg))))
@@ -692,9 +769,9 @@
          (arg 'backward-wrap)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -706,9 +783,9 @@
          (arg 'backward-wrap)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -720,9 +797,9 @@
          (arg 'backward-no-wrap)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -734,9 +811,9 @@
          (arg 'backward-no-wrap)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -748,9 +825,9 @@
          (arg 'walk-up)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -761,9 +838,9 @@
          (target-list `(,org-edna-test-sibling-one-id))
          (arg 'walk-up-with-self)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -774,9 +851,9 @@
          (target-list `(,org-edna-test-sibling-one-id))
          (arg 'walk-down)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -787,9 +864,9 @@
          (target-list `(,org-edna-test-parent-id))
          (arg 'walk-down-with-self)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -800,9 +877,9 @@
          (target-list `(,org-edna-test-sibling-one-id))
          (arg 'walk-down)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 1))))
@@ -821,9 +898,9 @@
          (arg 'walk-down)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -840,9 +917,9 @@
          (arg 'step-down)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg size))))
@@ -853,9 +930,9 @@
          (target-list `(,org-edna-test-relative-child-with-todo))
          (arg 'step-down)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 'todo-only))))
@@ -867,9 +944,9 @@
                         ,org-edna-test-relative-child-with-done))
          (arg 'step-down)
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg 'todo-and-done-only))))
@@ -886,9 +963,9 @@
          (filter 'no-comment)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg filter size))))
@@ -905,9 +982,9 @@
          (filter 'no-archive)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg filter size))))
@@ -920,9 +997,9 @@
          (filter "+ARCHIVE")
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg filter size))))
@@ -939,9 +1016,9 @@
          (filter "-ARCHIVE")
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg filter size))))
@@ -956,9 +1033,9 @@
          (filter "Child Heading With .*")
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg filter size))))
@@ -976,9 +1053,9 @@
          (sort 'reverse-sort)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets (org-with-point-at current
                     (org-edna-finder/relatives arg sort size))))
@@ -995,9 +1072,9 @@
          (arg 'step-down)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list))
          (targets ))
     (should (equal siblings
@@ -1018,9 +1095,9 @@
          (arg 'step-down)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list)))
     (should (equal siblings
                    (org-with-point-at current
@@ -1040,9 +1117,9 @@
          (arg 'step-down)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list)))
     (should (equal siblings
                    (org-with-point-at current
@@ -1062,9 +1139,9 @@
          (arg 'step-down)
          (size (length target-list))
          (org-agenda-files `(,org-edna-test-file))
-         (current (org-id-find start-marker t))
+         (current (org-edna-find-test-heading start-marker))
          (siblings (mapcar
-                    (lambda (uuid) (org-id-find uuid t))
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
                     target-list)))
     (should (equal siblings
                    (org-with-point-at current
@@ -1073,6 +1150,28 @@
                    (org-with-point-at current
                      (org-edna-finder/relatives arg 'deadline-down size))))))
 
+(ert-deftest org-edna-relatives/sort-timestamp ()
+  (let* ((start-marker org-edna-test-relative-parent-one)
+         (target-list `(,org-edna-test-relative-child-with-todo
+                        ,org-edna-test-relative-child-with-done
+                        ,org-edna-test-relative-commented-child
+                        ,org-edna-test-relative-child-with-children
+                        ,org-edna-test-relative-standard-child
+                        ,org-edna-test-relative-archived-child))
+         (arg 'step-down)
+         (size (length target-list))
+         (org-agenda-files `(,org-edna-test-file))
+         (current (org-edna-find-test-heading start-marker))
+         (siblings (mapcar
+                    (lambda (uuid) (org-edna-find-test-heading uuid))
+                    target-list)))
+    (should (equal siblings
+                   (org-with-point-at current
+                     (org-edna-finder/relatives arg 'timestamp-up size))))
+    (should (equal (nreverse siblings)
+                   (org-with-point-at current
+                     (org-edna-finder/relatives arg 'timestamp-down size))))))
+
 (ert-deftest org-edna-cache/no-entry ()
   (let* ((org-edna-finder-use-cache t)
          (org-edna--finder-cache (make-hash-table :test 'equal)))
@@ -1129,44 +1228,38 @@
         (should (not (org-edna--get-cache-entry 'org-edna-finder/match 
'("test&1"))))))))
 
 
-;; Actions
+;;; Actions
 
 (ert-deftest org-edna-action/todo-test ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (target (org-id-find "0d491588-7da3-43c5-b51a-87fbd34f79f7" t)))
-    (org-with-point-at target
-      (org-edna-action/todo! nil "DONE")
-      (should (string-equal (org-entry-get nil "TODO") "DONE"))
-      (org-edna-action/todo! nil "TODO")
-      (should (string-equal (org-entry-get nil "TODO") "TODO"))
-      (org-edna-action/todo! nil 'DONE)
-      (should (string-equal (org-entry-get nil "TODO") "DONE"))
-      (org-edna-action/todo! nil 'TODO)
-      (should (string-equal (org-entry-get nil "TODO") "TODO")))))
+  (org-edna-with-test-heading "0d491588-7da3-43c5-b51a-87fbd34f79f7"
+    (org-edna-action/todo! nil "DONE")
+    (should (string-equal (org-entry-get nil "TODO") "DONE"))
+    (org-edna-action/todo! nil "TODO")
+    (should (string-equal (org-entry-get nil "TODO") "TODO"))
+    (org-edna-action/todo! nil 'DONE)
+    (should (string-equal (org-entry-get nil "TODO") "DONE"))
+    (org-edna-action/todo! nil 'TODO)
+    (should (string-equal (org-entry-get nil "TODO") "TODO"))))
+
+;; Scheduled
 
 (ert-deftest org-edna-action-scheduled/wkdy ()
-  ;; Override `current-time' so we can get a deterministic value
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (org-agenda-files `(,org-edna-test-file))
-             (target (org-id-find "0d491588-7da3-43c5-b51a-87fbd34f79f7" t)))
-    (org-with-point-at target
-      (org-edna-action/scheduled! nil "Mon")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-17 Mon>"))
-      (org-edna-action/scheduled! nil 'rm)
-      (should (not (org-entry-get nil "SCHEDULED")))
-      (org-edna-action/scheduled! nil "Mon 9:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-17 Mon 09:00>"))
-      (org-edna-action/scheduled! nil 'rm)
-      (should (not (org-entry-get nil "SCHEDULED"))))))
+  (org-edna-with-test-heading "0d491588-7da3-43c5-b51a-87fbd34f79f7"
+    (org-edna-action/scheduled! nil "Mon")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-17 Mon>"))
+    (org-edna-action/scheduled! nil 'rm)
+    (should (not (org-entry-get nil "SCHEDULED")))
+    (org-edna-action/scheduled! nil "Mon 9:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-17 Mon 09:00>"))
+    (org-edna-action/scheduled! nil 'rm)
+    (should (not (org-entry-get nil "SCHEDULED")))))
 
 (ert-deftest org-edna-action-scheduled/cp ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (target (org-id-find "0d491588-7da3-43c5-b51a-87fbd34f79f7" t))
-         (source (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t))
-         (pairs '((cp . rm) (copy . remove) ("cp" . "rm") ("copy" . 
"remove"))))
-    (org-with-point-at target
+  (org-edna-with-test-heading "0d491588-7da3-43c5-b51a-87fbd34f79f7"
+    (let* ((source (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5"))
+           (pairs '((cp . rm) (copy . remove) ("cp" . "rm") ("copy" . 
"remove"))))
       (dolist (pair pairs)
         (org-edna-action/scheduled! source (car pair))
         (should (string-equal (org-entry-get nil "SCHEDULED")
@@ -1175,226 +1268,355 @@
         (should (not (org-entry-get nil "SCHEDULED")))))))
 
 (ert-deftest org-edna-action-scheduled/inc ()
-  ;; Override `current-time' so we can get a deterministic value
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (org-agenda-files `(,org-edna-test-file))
-             (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t)))
-    (org-with-point-at target
-      ;; Time starts at Jan 15, 2000
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>"))
-      ;; Increment 1 minute
-      (org-edna-action/scheduled! nil "+1M")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:01>"))
-      ;; Decrement 1 minute
-      (org-edna-action/scheduled! nil "-1M")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>"))
-      ;; +1 day
-      (org-edna-action/scheduled! nil "+1d")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-16 Sun 00:00>"))
-      ;; +1 hour from current time
-      (org-edna-action/scheduled! nil "++1h")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 01:00>"))
-      ;; Back to Saturday
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>"))
-      ;; -1 day to Friday
-      (org-edna-action/scheduled! nil "-1d")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-14 Fri 00:00>"))
-      ;; Increment two days to the next weekday
-      (org-edna-action/scheduled! nil "+2wkdy")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-17 Mon 00:00>"))
-      ;; Increment one day, expected to land on a weekday
-      (org-edna-action/scheduled! nil "+1wkdy")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-18 Tue 00:00>"))
-      ;; Move forward 8 days, then backward until we find a weekend
-      (org-edna-action/scheduled! nil "+8d -wknd")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-23 Sun 00:00>"))
-      ;; Move forward one week, then forward until we find a weekday
-      ;; (org-edna-action/scheduled! nil "+1w +wkdy")
-      ;; (should (string-equal (org-entry-get nil "SCHEDULED")
-      ;;                       "<2000-01-31 Mon 00:00>"))
-      ;; Back to Saturday for other tests
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>")))))
+  (org-edna-with-test-heading "97e6b0f0-40c4-464f-b760-6e5ca9744eb5"
+    ;; Time starts at Jan 15, 2000
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; Increment 1 minute
+    (org-edna-action/scheduled! nil "+1M")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:01>"))
+    ;; Decrement 1 minute
+    (org-edna-action/scheduled! nil "-1M")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; +1 day
+    (org-edna-action/scheduled! nil "+1d")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-16 Sun 00:00>"))
+    ;; +1 hour from current time
+    (org-edna-action/scheduled! nil "++1h")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 01:00>"))
+    ;; Back to Saturday
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; -1 day to Friday
+    (org-edna-action/scheduled! nil "-1d")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-14 Fri 00:00>"))
+    ;; Increment two days to the next weekday
+    (org-edna-action/scheduled! nil "+2wkdy")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-17 Mon 00:00>"))
+    ;; Increment one day, expected to land on a weekday
+    (org-edna-action/scheduled! nil "+1wkdy")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-18 Tue 00:00>"))
+    ;; Move forward 8 days, then backward until we find a weekend
+    (org-edna-action/scheduled! nil "+8d -wknd")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-23 Sun 00:00>"))
+    ;; Move forward one week, then forward until we find a weekday
+    ;; (org-edna-action/scheduled! nil "+1w +wkdy")
+    ;; (should (string-equal (org-entry-get nil "SCHEDULED")
+    ;;                       "<2000-01-31 Mon 00:00>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))))
 
 (ert-deftest org-edna-action-scheduled/landing ()
   "Test landing arguments to scheduled increment."
-  ;; Override `current-time' so we can get a deterministic value
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (org-agenda-files `(,org-edna-test-file))
-             (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t)))
-    (org-with-point-at target
-      ;; Time starts at Jan 15, 2000
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>"))
-      ;; Move forward 10 days, then backward until we find a weekend
-      (org-edna-action/scheduled! nil "+10d -wknd")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-23 Sun 00:00>"))
-      ;; Move forward one week, then forward until we find a weekday
-      (org-edna-action/scheduled! nil "+1w +wkdy")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-31 Mon 00:00>"))
-      ;; Back to Saturday for other tests
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>")))))
+  (org-edna-with-test-heading "97e6b0f0-40c4-464f-b760-6e5ca9744eb5"
+    ;; Time starts at Jan 15, 2000
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; Move forward 10 days, then backward until we find a weekend
+    (org-edna-action/scheduled! nil "+10d -wknd")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-23 Sun 00:00>"))
+    ;; Move forward one week, then forward until we find a weekday
+    (org-edna-action/scheduled! nil "+1w +wkdy")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-31 Mon 00:00>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))))
 
 (ert-deftest org-edna-action-scheduled/landing-no-hour ()
   "Test landing arguments to scheduled increment, without hour."
-  ;; Override `current-time' so we can get a deterministic value
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (org-agenda-files `(,org-edna-test-file))
-             (target (org-id-find "caf27724-0887-4565-9765-ed2f1edcfb16" t)))
-    (org-with-point-at target
-      ;; Time starts at Jan 1, 2017
-      (org-edna-action/scheduled! nil "2017-01-01 Sun")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2017-01-01 Sun>"))
-      ;; Move forward 10 days, then backward until we find a weekend
-      (org-edna-action/scheduled! nil "+10d -wknd")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2017-01-08 Sun>"))
-      ;; Move forward one week, then forward until we find a weekday
-      (org-edna-action/scheduled! nil "+1w +wkdy")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2017-01-16 Mon>"))
-      ;; Back to Saturday for other tests
-      (org-edna-action/scheduled! nil "2017-01-01 Sun")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2017-01-01 Sun>")))))
+  (org-edna-with-test-heading "caf27724-0887-4565-9765-ed2f1edcfb16"
+    ;; Time starts at Jan 1, 2017
+    (org-edna-action/scheduled! nil "2017-01-01 Sun")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2017-01-01 Sun>"))
+    ;; Move forward 10 days, then backward until we find a weekend
+    (org-edna-action/scheduled! nil "+10d -wknd")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2017-01-08 Sun>"))
+    ;; Move forward one week, then forward until we find a weekday
+    (org-edna-action/scheduled! nil "+1w +wkdy")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2017-01-16 Mon>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/scheduled! nil "2017-01-01 Sun")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2017-01-01 Sun>"))))
 
 (ert-deftest org-edna-action-scheduled/float ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (org-agenda-files `(,org-edna-test-file))
-             (target (org-id-find "97e6b0f0-40c4-464f-b760-6e5ca9744eb5" t)))
-    (org-with-point-at target
-      ;; Time starts at Jan 15, 2000
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>"))
-      ;; The third Tuesday of next month (Feb 15th)
-      (org-edna-action/scheduled! nil "float 3 Tue")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-02-15 Tue 00:00>"))
-      ;; The second Friday of the following May (May 12th)
-      (org-edna-action/scheduled! nil "float 2 5 May")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-05-12 Fri 00:00>"))
-      ;; Move forward to the second Wednesday of the next month (June 14th)
-      (org-edna-action/scheduled! nil "float 2 Wednesday")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-06-14 Wed 00:00>"))
-      ;; Move forward to the first Thursday in the following Jan (Jan 4th, 
2001)
-      (org-edna-action/scheduled! nil "float 1 4 Jan")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2001-01-04 Thu 00:00>"))
-      ;; The fourth Monday in Feb, 2000 (Feb 28th)
-      (org-edna-action/scheduled! nil "float ++4 monday")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-02-28 Mon 00:00>"))
-      ;; The second Monday after Mar 12th, 2000 (Mar 20th)
-      (org-edna-action/scheduled! nil "float 2 monday Mar 12")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-03-20 Mon 00:00>"))
-      ;; Back to Saturday for other tests
-      (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
-      (should (string-equal (org-entry-get nil "SCHEDULED")
-                            "<2000-01-15 Sat 00:00>")))))
+  (org-edna-with-test-heading "97e6b0f0-40c4-464f-b760-6e5ca9744eb5"
+    ;; Time starts at Jan 15, 2000
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; The third Tuesday of next month (Feb 15th)
+    (org-edna-action/scheduled! nil "float 3 Tue")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-02-15 Tue 00:00>"))
+    ;; The second Friday of the following May (May 12th)
+    (org-edna-action/scheduled! nil "float 2 5 May")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-05-12 Fri 00:00>"))
+    ;; Move forward to the second Wednesday of the next month (June 14th)
+    (org-edna-action/scheduled! nil "float 2 Wednesday")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-06-14 Wed 00:00>"))
+    ;; Move forward to the first Thursday in the following Jan (Jan 4th, 2001)
+    (org-edna-action/scheduled! nil "float 1 4 Jan")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2001-01-04 Thu 00:00>"))
+    ;; The fourth Monday in Feb, 2000 (Feb 28th)
+    (org-edna-action/scheduled! nil "float ++4 monday")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-02-28 Mon 00:00>"))
+    ;; The second Monday after Mar 12th, 2000 (Mar 20th)
+    (org-edna-action/scheduled! nil "float 2 monday Mar 12")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-03-20 Mon 00:00>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/scheduled! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "SCHEDULED")
+                          "<2000-01-15 Sat 00:00>"))))
+
+(ert-deftest org-edna-action-deadline/wkdy ()
+  (org-edna-with-test-heading "0d491588-7da3-43c5-b51a-87fbd34f79f7"
+    (org-edna-action/deadline! nil "Mon")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-17 Mon>"))
+    (org-edna-action/deadline! nil 'rm)
+    (should (not (org-entry-get nil "DEADLINE")))
+    (org-edna-action/deadline! nil "Mon 9:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-17 Mon 09:00>"))
+    (org-edna-action/deadline! nil 'rm)
+    (should (not (org-entry-get nil "DEADLINE")))))
+
+(ert-deftest org-edna-action-deadline/cp ()
+  (org-edna-with-test-heading "0d491588-7da3-43c5-b51a-87fbd34f79f7"
+    (let* ((source (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5"))
+           (pairs '((cp . rm) (copy . remove) ("cp" . "rm") ("copy" . 
"remove"))))
+      (dolist (pair pairs)
+        (org-edna-action/deadline! source (car pair))
+        (should (string-equal (org-entry-get nil "DEADLINE")
+                              "<2000-01-15 Sat 00:00>"))
+        (org-edna-action/deadline! source (cdr pair))
+        (should (not (org-entry-get nil "DEADLINE")))))))
+
+(ert-deftest org-edna-action-deadline/inc ()
+  (org-edna-with-test-heading "97e6b0f0-40c4-464f-b760-6e5ca9744eb5"
+    ;; Time starts at Jan 15, 2000
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; Increment 1 minute
+    (org-edna-action/deadline! nil "+1M")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:01>"))
+    ;; Decrement 1 minute
+    (org-edna-action/deadline! nil "-1M")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; +1 day
+    (org-edna-action/deadline! nil "+1d")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-16 Sun 00:00>"))
+    ;; +1 hour from current time
+    (org-edna-action/deadline! nil "++1h")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 01:00>"))
+    ;; Back to Saturday
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; -1 day to Friday
+    (org-edna-action/deadline! nil "-1d")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-14 Fri 00:00>"))
+    ;; Increment two days to the next weekday
+    (org-edna-action/deadline! nil "+2wkdy")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-17 Mon 00:00>"))
+    ;; Increment one day, expected to land on a weekday
+    (org-edna-action/deadline! nil "+1wkdy")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-18 Tue 00:00>"))
+    ;; Move forward 8 days, then backward until we find a weekend
+    (org-edna-action/deadline! nil "+8d -wknd")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-23 Sun 00:00>"))
+    ;; Move forward one week, then forward until we find a weekday
+    ;; (org-edna-action/deadline! nil "+1w +wkdy")
+    ;; (should (string-equal (org-entry-get nil "DEADLINE")
+    ;;                       "<2000-01-31 Mon 00:00>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))))
+
+(ert-deftest org-edna-action-deadline/landing ()
+  "Test landing arguments to deadline increment."
+  (org-edna-with-test-heading "97e6b0f0-40c4-464f-b760-6e5ca9744eb5"
+    ;; Time starts at Jan 15, 2000
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; Move forward 10 days, then backward until we find a weekend
+    (org-edna-action/deadline! nil "+10d -wknd")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-23 Sun 00:00>"))
+    ;; Move forward one week, then forward until we find a weekday
+    (org-edna-action/deadline! nil "+1w +wkdy")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-31 Mon 00:00>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))))
+
+(ert-deftest org-edna-action-deadline/landing-no-hour ()
+  "Test landing arguments to deadline increment, without hour."
+  (org-edna-with-test-heading "caf27724-0887-4565-9765-ed2f1edcfb16"
+    ;; Time starts at Jan 1, 2017
+    (org-edna-action/deadline! nil "2017-01-01 Sun")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2017-01-01 Sun>"))
+    ;; Move forward 10 days, then backward until we find a weekend
+    (org-edna-action/deadline! nil "+10d -wknd")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2017-01-08 Sun>"))
+    ;; Move forward one week, then forward until we find a weekday
+    (org-edna-action/deadline! nil "+1w +wkdy")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2017-01-16 Mon>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/deadline! nil "2017-01-01 Sun")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2017-01-01 Sun>"))))
+
+(ert-deftest org-edna-action-deadline/float ()
+  (org-edna-with-test-heading "97e6b0f0-40c4-464f-b760-6e5ca9744eb5"
+    ;; Time starts at Jan 15, 2000
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))
+    ;; The third Tuesday of next month (Feb 15th)
+    (org-edna-action/deadline! nil "float 3 Tue")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-02-15 Tue 00:00>"))
+    ;; The second Friday of the following May (May 12th)
+    (org-edna-action/deadline! nil "float 2 5 May")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-05-12 Fri 00:00>"))
+    ;; Move forward to the second Wednesday of the next month (June 14th)
+    (org-edna-action/deadline! nil "float 2 Wednesday")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-06-14 Wed 00:00>"))
+    ;; Move forward to the first Thursday in the following Jan (Jan 4th, 2001)
+    (org-edna-action/deadline! nil "float 1 4 Jan")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2001-01-04 Thu 00:00>"))
+    ;; The fourth Monday in Feb, 2000 (Feb 28th)
+    (org-edna-action/deadline! nil "float ++4 monday")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-02-28 Mon 00:00>"))
+    ;; The second Monday after Mar 12th, 2000 (Mar 20th)
+    (org-edna-action/deadline! nil "float 2 monday Mar 12")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-03-20 Mon 00:00>"))
+    ;; Back to Saturday for other tests
+    (org-edna-action/deadline! nil "2000-01-15 Sat 00:00")
+    (should (string-equal (org-entry-get nil "DEADLINE")
+                          "<2000-01-15 Sat 00:00>"))))
 
 (ert-deftest org-edna-action-tag ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one)))
-    (org-with-point-at pom
-      (org-edna-action/tag! nil "tag")
-      (should (equal (org-get-tags) '("tag")))
-      (org-edna-action/tag! nil "")
-      (should (equal (org-get-tags) '(""))))))
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (org-edna-action/tag! nil "tag")
+    (should (equal (org-get-tags) '("tag")))
+    (org-edna-action/tag! nil "")
+    (should (equal (org-get-tags) '("")))))
 
 (ert-deftest org-edna-action-property ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one)))
-    (org-with-point-at pom
-      (org-edna-action/set-property! nil "TEST" "1")
-      (should (equal (org-entry-get nil "TEST") "1"))
-      (org-edna-action/delete-property! nil "TEST")
-      (should-not (org-entry-get nil "TEST")))))
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (org-edna-action/set-property! nil "TEST" "1")
+    (should (equal (org-entry-get nil "TEST") "1"))
+    (org-edna-action/delete-property! nil "TEST")
+    (should-not (org-entry-get nil "TEST"))))
 
 (ert-deftest org-edna-action-property/inc-dec ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one)))
-    (org-with-point-at pom
-      (org-edna-action/set-property! nil "TEST" "1")
-      (should (equal (org-entry-get nil "TEST") "1"))
-      (org-edna-action/set-property! nil "TEST" 'inc)
-      (should (equal (org-entry-get nil "TEST") "2"))
-      (org-edna-action/set-property! nil "TEST" 'dec)
-      (should (equal (org-entry-get nil "TEST") "1"))
-      (org-edna-action/delete-property! nil "TEST")
-      (should-not (org-entry-get nil "TEST"))
-      (should-error (org-edna-action/set-property! nil "TEST" 'inc))
-      (should-error (org-edna-action/set-property! nil "TEST" 'dec))
-      (org-edna-action/set-property! nil "TEST" "a")
-      (should (equal (org-entry-get nil "TEST") "a"))
-      (should-error (org-edna-action/set-property! nil "TEST" 'inc))
-      (should-error (org-edna-action/set-property! nil "TEST" 'dec))
-      (org-edna-action/delete-property! nil "TEST")
-      (should-not (org-entry-get nil "TEST")))))
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (org-edna-action/set-property! nil "TEST" "1")
+    (should (equal (org-entry-get nil "TEST") "1"))
+    (org-edna-action/set-property! nil "TEST" 'inc)
+    (should (equal (org-entry-get nil "TEST") "2"))
+    (org-edna-action/set-property! nil "TEST" 'dec)
+    (should (equal (org-entry-get nil "TEST") "1"))
+    (org-edna-action/delete-property! nil "TEST")
+    (should-not (org-entry-get nil "TEST"))
+    (should-error (org-edna-action/set-property! nil "TEST" 'inc))
+    (should-error (org-edna-action/set-property! nil "TEST" 'dec))
+    (org-edna-action/set-property! nil "TEST" "a")
+    (should (equal (org-entry-get nil "TEST") "a"))
+    (should-error (org-edna-action/set-property! nil "TEST" 'inc))
+    (should-error (org-edna-action/set-property! nil "TEST" 'dec))
+    (org-edna-action/delete-property! nil "TEST")
+    (should-not (org-entry-get nil "TEST"))))
 
 (ert-deftest org-edna-action-property/next-prev ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one)))
-    (org-with-point-at pom
-      (org-edna-action/set-property! nil "TEST" "a")
-      (should (equal (org-entry-get nil "TEST") "a"))
-      (should-error (org-edna-action/set-property! nil "TEST" 'next))
-      (should-error (org-edna-action/set-property! nil "TEST" 'prev))
-      (should-error (org-edna-action/set-property! nil "TEST" 'previous))
-      (org-edna-action/delete-property! nil "TEST")
-      (should-not (org-entry-get nil "TEST"))
-      ;; Test moving forwards
-      (org-edna-action/set-property! nil "COUNTER" "a")
-      (should (equal (org-entry-get nil "COUNTER") "a"))
-      (org-edna-action/set-property! nil "COUNTER" 'next)
-      (should (equal (org-entry-get nil "COUNTER") "b"))
-      ;; Test moving forwards past the last one
-      (org-edna-action/set-property! nil "COUNTER" "d")
-      (should (equal (org-entry-get nil "COUNTER") "d"))
-      (org-edna-action/set-property! nil "COUNTER" 'next)
-      (should (equal (org-entry-get nil "COUNTER") "a"))
-      ;; Test moving backwards past the first one
-      (org-edna-action/set-property! nil "COUNTER" 'prev)
-      (should (equal (org-entry-get nil "COUNTER") "d"))
-      ;; Test moving backwards normally
-      (org-edna-action/set-property! nil "COUNTER" 'previous)
-      (should (equal (org-entry-get nil "COUNTER") "c"))
-      (org-edna-action/delete-property! nil "COUNTER")
-      (should-not (org-entry-get nil "COUNTER")))))
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (org-edna-action/set-property! nil "TEST" "a")
+    (should (equal (org-entry-get nil "TEST") "a"))
+    (should-error (org-edna-action/set-property! nil "TEST" 'next))
+    (should-error (org-edna-action/set-property! nil "TEST" 'prev))
+    (should-error (org-edna-action/set-property! nil "TEST" 'previous))
+    (org-edna-action/delete-property! nil "TEST")
+    (should-not (org-entry-get nil "TEST"))
+    ;; Test moving forwards
+    (org-edna-action/set-property! nil "COUNTER" "a")
+    (should (equal (org-entry-get nil "COUNTER") "a"))
+    (org-edna-action/set-property! nil "COUNTER" 'next)
+    (should (equal (org-entry-get nil "COUNTER") "b"))
+    ;; Test moving forwards past the last one
+    (org-edna-action/set-property! nil "COUNTER" "d")
+    (should (equal (org-entry-get nil "COUNTER") "d"))
+    (org-edna-action/set-property! nil "COUNTER" 'next)
+    (should (equal (org-entry-get nil "COUNTER") "a"))
+    ;; Test moving backwards past the first one
+    (org-edna-action/set-property! nil "COUNTER" 'prev)
+    (should (equal (org-entry-get nil "COUNTER") "d"))
+    ;; Test moving backwards normally
+    (org-edna-action/set-property! nil "COUNTER" 'previous)
+    (should (equal (org-entry-get nil "COUNTER") "c"))
+    (org-edna-action/delete-property! nil "COUNTER")
+    (should-not (org-entry-get nil "COUNTER"))))
 
 (ert-deftest org-edna-action-clock ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one)))
-    (org-with-point-at pom
-      (org-edna-action/clock-in! nil)
-      (should (org-clocking-p))
-      (should (equal org-clock-hd-marker pom))
-      (org-edna-action/clock-out! nil)
-      (should-not (org-clocking-p)))))
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (org-edna-action/clock-in! nil)
+    (should (org-clocking-p))
+    (should (equal org-clock-hd-marker (point-marker)))
+    (org-edna-action/clock-out! nil)
+    (should-not (org-clocking-p))))
 
 (ert-deftest org-edna-action-priority ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one))
-        (org-lowest-priority  ?C)
-        (org-highest-priority ?A)
-        (org-default-priority ?B))
-    (org-with-point-at pom
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (let ((org-lowest-priority  ?C)
+          (org-highest-priority ?A)
+          (org-default-priority ?B))
       (org-edna-action/set-priority! nil "A")
       (should (equal (org-entry-get nil "PRIORITY") "A"))
       (org-edna-action/set-priority! nil 'down)
@@ -1407,45 +1629,49 @@
       (should (equal (org-entry-get nil "PRIORITY") "B")))))
 
 (ert-deftest org-edna-action-effort ()
-  (let ((pom (org-edna-find-test-heading org-edna-test-id-heading-one)))
-    (org-with-point-at pom
-      (org-edna-action/set-effort! nil "0:01")
-      (should (equal (org-entry-get nil "EFFORT") "0:01"))
-      (org-edna-action/set-effort! nil 'increment)
-      (should (equal (org-entry-get nil "EFFORT") "0:02"))
-      (org-entry-delete nil "EFFORT"))))
+  (org-edna-with-test-heading org-edna-test-id-heading-one
+    (org-edna-action/set-effort! nil "0:01")
+    (should (equal (org-entry-get nil "EFFORT") "0:01"))
+    (org-edna-action/set-effort! nil 'increment)
+    (should (equal (org-entry-get nil "EFFORT") "0:02"))
+    (org-entry-delete nil "EFFORT")))
 
 (ert-deftest org-edna-action-archive ()
-  (let ((org-archive-save-context-info '(todo))
-        (pom (org-edna-find-test-heading org-edna-test-archive-heading))
-        ;; Archive it to the same location
-        (org-archive-location "::** Archive")
-        (org-edna-prompt-for-archive nil))
-    (org-with-point-at pom
+  (org-edna-with-test-heading org-edna-test-archive-heading
+    (let* ((org-archive-save-context-info '(todo))
+           ;; Archive it to the same location
+           (org-archive-location "::** Archive")
+           ;; We're non-interactive, so no prompt.
+           (org-edna-prompt-for-archive nil))
       (org-edna-action/archive! nil)
       (should (equal (org-entry-get nil "ARCHIVE_TODO") "TODO"))
       (org-entry-delete nil "ARCHIVE_TODO"))))
 
 (ert-deftest org-edna-action-chain ()
-  (let ((old-pom (org-edna-find-test-heading org-edna-test-id-heading-one))
-        (new-pom (org-edna-find-test-heading org-edna-test-id-heading-two)))
-    (org-entry-put old-pom "TEST" "1")
-    (org-with-point-at new-pom
-      (org-edna-action/chain! old-pom "TEST")
-      (should (equal (org-entry-get nil "TEST") "1")))
-    (org-entry-delete old-pom "TEST")
-    (org-entry-delete new-pom "TEST")))
+  (org-edna-test-setup
+    (let ((old-pom (org-edna-find-test-heading org-edna-test-id-heading-one))
+          (new-pom (org-edna-find-test-heading org-edna-test-id-heading-two)))
+      (org-edna-protect-test-file
+        (org-entry-put old-pom "TEST" "1")
+        (org-with-point-at new-pom
+          (org-edna-action/chain! old-pom "TEST")
+          (should (equal (org-entry-get nil "TEST") "1")))
+        (org-entry-delete old-pom "TEST")
+        (org-entry-delete new-pom "TEST")))))
 
 
-;; Conditions
+;;; Conditions
 
 (defun org-edna-test-condition-form (func-sym pom-true pom-false block-true 
block-false &rest args)
-  (org-with-point-at pom-true
-    (should-not (apply func-sym t args))
-    (should     (equal (apply func-sym nil args) block-true)))
-  (org-with-point-at pom-false
-    (should     (equal (apply func-sym t args) block-false))
-    (should-not (apply func-sym nil args))))
+  (org-edna-test-setup
+    (let* ((block-true (or block-true (org-with-point-at pom-true 
(org-get-heading))))
+           (block-false (or block-false (org-with-point-at pom-false 
(org-get-heading)))))
+      (org-with-point-at pom-true
+        (should-not (apply func-sym t args))
+        (should     (equal (apply func-sym nil args) block-true)))
+      (org-with-point-at pom-false
+        (should     (equal (apply func-sym t args) block-false))
+        (should-not (apply func-sym nil args))))))
 
 (ert-deftest org-edna-condition-done ()
   (let* ((pom-done (org-edna-find-test-heading org-edna-test-id-heading-four))
@@ -1522,30 +1748,444 @@
                                   block-true block-false
                                   string)))
 
+(ert-deftest org-edna-condition/has-tags ()
+  (let* ((pom-true (org-edna-find-test-heading 
"0fa0d4dd-40f2-4251-a558-4c6e2898c2df"))
+         (pom-false (org-edna-find-test-heading org-edna-test-id-heading-one))
+         (block-true (org-with-point-at pom-true (org-get-heading)))
+         (block-false (org-with-point-at pom-false (org-get-heading))))
+    (org-edna-test-condition-form 'org-edna-condition/has-tags?
+                                  pom-true pom-false
+                                  block-true block-false
+                                  "test")))
+
+(ert-deftest org-edna-condition/matches-tags ()
+  (org-edna-test-condition-form
+   'org-edna-condition/matches?
+   (org-edna-find-test-heading "0fa0d4dd-40f2-4251-a558-4c6e2898c2df")
+   (org-edna-find-test-heading org-edna-test-id-heading-one)
+   nil nil
+   "1&test")
+  (org-edna-test-condition-form
+   'org-edna-condition/matches?
+   (org-edna-find-test-heading org-edna-test-id-heading-four)
+   (org-edna-find-test-heading "0fa0d4dd-40f2-4251-a558-4c6e2898c2df")
+   nil nil
+   "TODO==\"DONE\""))
+
 
-;; Consideration
+;;; Consideration
+
+(ert-deftest org-edna-consideration/any ()
+  (let ((blocks-all-blocking `("a" "c" "b"))
+        (blocks-some-blocking `("a" nil "b"))
+        (blocks-no-blocking `(nil nil nil)))
+    (should (string-equal (org-edna-handle-consideration 'any 
blocks-all-blocking) "a"))
+    (should (string-equal (org-edna-handle-consideration 'any 
blocks-some-blocking) "a"))
+    (should (not (org-edna-handle-consideration 'any blocks-no-blocking)))))
 
 (ert-deftest org-edna-consideration/all ()
-  (let ((blocks-blocking `("a" nil "b"))
+  (let ((blocks-all-blocking `("a" "c" "b"))
+        (blocks-some-blocking `(nil "c" nil))
         (blocks-no-blocking `(nil nil nil)))
-    (should (string-equal (org-edna-handle-consideration 'all blocks-blocking) 
"a"))
+    (should (string-equal (org-edna-handle-consideration 'all 
blocks-all-blocking) "a"))
+    (should (not (org-edna-handle-consideration 'all blocks-some-blocking)))
     (should (not (org-edna-handle-consideration 'all blocks-no-blocking)))))
 
 (ert-deftest org-edna-consideration/integer ()
-  (let ((blocks-blocking `("a" "c" "b"))
-        (blocks-no-blocking `("a" nil "b"))
-        (blocks-empty `(nil nil nil)))
-    (should (string-equal (org-edna-handle-consideration 1 blocks-blocking) 
"a"))
-    (should (not (org-edna-handle-consideration 1 blocks-no-blocking)))
-    (should (not (org-edna-handle-consideration 1 blocks-empty)))))
+  (let ((blocks-all-blocking `("a" "c" "b"))
+        (blocks-some-blocking `("a" nil "b"))
+        (blocks-no-blocking `(nil nil nil)))
+    (should (string-equal (org-edna-handle-consideration 2 
blocks-all-blocking) "a"))
+    (should (string-equal (org-edna-handle-consideration 2 
blocks-some-blocking) "a"))
+    (should (not (org-edna-handle-consideration 2 blocks-no-blocking)))))
 
 (ert-deftest org-edna-consideration/float ()
-  (let ((blocks-blocking `("a" "c" "b"))
-        (blocks-no-blocking `("a" nil "b"))
-        (blocks-empty `(nil nil nil)))
-    (should (string-equal (org-edna-handle-consideration 0.25 blocks-blocking) 
"a"))
-    (should (not (org-edna-handle-consideration 0.25 blocks-no-blocking)))
-    (should (not (org-edna-handle-consideration 0.25 blocks-empty)))))
+  (let ((blocks-all-blocking `("a" "c" "b"))
+        (blocks-some-blocking `("a" nil "b"))
+        (blocks-no-blocking `(nil nil nil)))
+    (should (string-equal (org-edna-handle-consideration 0.25 
blocks-all-blocking) "a"))
+    (should (string-equal (org-edna-handle-consideration 0.25 
blocks-some-blocking) "a"))
+    (should (not (org-edna-handle-consideration 0.25 blocks-no-blocking)))))
+
+
+;;; Full Run-through Tests from the Documentation
+
+(defmacro org-edna-doc-test-setup (heading-id &rest body)
+  (declare (indent 1))
+  `(org-edna-with-test-heading ,heading-id
+     (save-restriction
+       ;; Only allow operating on the current tree
+       (org-narrow-to-subtree)
+       ;; Show the entire subtree
+       (outline-show-all)
+       ,@body)))
+
+(ert-deftest org-edna-doc-test/ancestors ()
+  (org-edna-doc-test-setup "24a0c3bb-7e69-4e9e-bb98-5aba2ff17bb1"
+    (pcase-let* ((`(,heading1-pom ,heading2-pom ,heading3-pom ,heading4-pom 
,heading5-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify that we can't change the TODO state to DONE
+      (should (org-edna-test-check-block heading5-pom "Initial state of 
heading 5"))
+      ;; Change the state at 4 to DONE
+      (org-edna-test-mark-done heading4-pom)
+      ;; Verify that ALL ancestors need to be changed
+      (should (org-edna-test-check-block heading5-pom "Heading 5 after parent 
changed"))
+      (org-edna-test-mark-done heading1-pom heading3-pom)
+      ;; Only need 1, 3, and 4 to change 5
+      (should (not (org-edna-test-check-block heading5-pom
+                                            "Heading 5 after all parents 
changed")))
+      ;; Change the state back to TODO on all of them
+      (org-edna-test-mark-todo heading1-pom heading3-pom heading4-pom 
heading5-pom))))
+
+(ert-deftest org-edna-doc-test/ancestors-cache ()
+  (let ((org-edna-finder-use-cache t))
+    (org-edna-doc-test-setup "24a0c3bb-7e69-4e9e-bb98-5aba2ff17bb1"
+      (pcase-let* ((`(,heading1-pom ,heading2-pom ,heading3-pom ,heading4-pom 
,heading5-pom)
+                    (org-edna-test-children-marks)))
+        ;; Verify that we can't change the TODO state to DONE
+        (should (org-edna-test-check-block heading5-pom "Initial state of 
heading 5"))
+        ;; Change the state at 4 to DONE
+        (org-edna-test-mark-done heading4-pom)
+        ;; Verify that ALL ancestors need to be changed
+        (should (org-edna-test-check-block heading5-pom "Heading 5 after 
parent changed"))
+        (org-edna-test-mark-done heading1-pom heading3-pom)
+        ;; Only need 1, 3, and 4 to change 5
+        (should (not (org-edna-test-check-block heading5-pom
+                                              "Heading 5 after all parents 
changed")))
+        ;; Change the state back to TODO on all of them
+        (org-edna-test-mark-todo heading1-pom heading3-pom heading4-pom 
heading5-pom)))))
+
+(ert-deftest org-edna-doc-test/descendants ()
+  (org-edna-doc-test-setup "cc18dc74-00e8-4081-b46f-e36800041fe7"
+    (pcase-let* ((`(,heading1-pom ,heading2-pom ,heading3-pom ,heading4-pom 
,heading5-pom)
+                  (org-edna-test-children-marks)))
+      (should (org-edna-test-check-block heading1-pom "Heading 1 initial 
state"))
+      ;; Change the state at 2 to DONE
+      (org-edna-test-mark-done heading2-pom)
+      ;; Verify that ALL descendants need to be changed
+      (should (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 2"))
+      ;; Try 3
+      (org-edna-test-mark-done heading3-pom)
+      ;; Verify that ALL descendants need to be changed
+      (should (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 3"))
+      ;; Try 4
+      (org-edna-test-mark-done heading4-pom)
+      ;; Verify that ALL descendants need to be changed
+      (should (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 4"))
+      ;; Try 5
+      (org-edna-test-mark-done heading5-pom)
+      ;; Verify that ALL descendants need to be changed
+      (should (not (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 5"))))))
+
+(ert-deftest org-edna-doc-test/descendants-cache ()
+  (let ((org-edna-finder-use-cache t))
+    (org-edna-doc-test-setup "cc18dc74-00e8-4081-b46f-e36800041fe7"
+      (pcase-let* ((`(,heading1-pom ,heading2-pom ,heading3-pom ,heading4-pom 
,heading5-pom)
+                    (org-edna-test-children-marks)))
+        (should (org-edna-test-check-block heading1-pom "Heading 1 initial 
state"))
+        ;; Change the state at 2 to DONE
+        (org-edna-test-mark-done heading2-pom)
+        ;; Verify that ALL descendants need to be changed
+        (should (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 2"))
+        ;; Try 3
+        (org-edna-test-mark-done heading3-pom)
+        ;; Verify that ALL descendants need to be changed
+        (should (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 3"))
+        ;; Try 4
+        (org-edna-test-mark-done heading4-pom)
+        ;; Verify that ALL descendants need to be changed
+        (should (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 4"))
+        ;; Try 5
+        (org-edna-test-mark-done heading5-pom)
+        ;; Verify that ALL descendants need to be changed
+        (should (not (org-edna-test-check-block heading1-pom "Heading 1 after 
changing 5")))))))
+
+(ert-deftest org-edna-doc-test/laundry ()
+  "Test for the \"laundry\" example in the documentation."
+  (org-edna-doc-test-setup "e57ce099-9f37-47f4-a6bb-61a84eb1fbbe"
+    (pcase-let* ((`(,heading1-pom ,heading2-pom ,heading3-pom ,heading4-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify that headings 2, 3, and 4 are all blocked
+      (should (org-edna-test-check-block heading2-pom
+                                         "Initial attempt to change heading 
2"))
+      (should (org-edna-test-check-block heading3-pom
+                                         "Initial attempt to change heading 
3"))
+      (should (org-edna-test-check-block heading4-pom
+                                         "Initial attempt to change heading 
4"))
+      ;; Mark heading 1 as DONE
+      (should (not (org-edna-test-check-block heading1-pom
+                                            "Set heading 1 to DONE")))
+      ;; Only heading 2 should have a scheduled time
+      (should (string-equal (org-entry-get heading2-pom "SCHEDULED")
+                            "<2000-01-15 Sat 01:00>"))
+      (should (not (org-entry-get heading3-pom "SCHEDULED")))
+      (should (not (org-entry-get heading4-pom "SCHEDULED")))
+      ;; The others should still be blocked.
+      (should (org-edna-test-check-block heading3-pom
+                                         "Second attempt to change heading 3"))
+      (should (org-edna-test-check-block heading4-pom
+                                         "Second attempt to change heading 4"))
+      ;; Try changing heading 2
+      (should (not (org-edna-test-check-block heading2-pom
+                                            "Set heading 2 to DONE")))
+      (should (string-equal (org-entry-get heading3-pom "SCHEDULED")
+                            "<2000-01-16 Sun 09:00>"))
+      ;; 4 should still be blocked
+      (should (org-edna-test-check-block heading4-pom
+                                         "Second attempt to change heading 
4")))))
+
+(ert-deftest org-edna-doc-test/laundry-cache ()
+  "Test for the \"laundry\" example in the documentation.
+
+This version enables cache, ensuring that the repeated calls to
+the relative finders all still work while cache is enabled."
+  (let ((org-edna-finder-use-cache t))
+    (org-edna-doc-test-setup "e57ce099-9f37-47f4-a6bb-61a84eb1fbbe"
+      (pcase-let* ((`(,heading1-pom ,heading2-pom ,heading3-pom ,heading4-pom)
+                    (org-edna-test-children-marks)))
+        ;; Verify that headings 2, 3, and 4 are all blocked
+        (should (org-edna-test-check-block heading2-pom
+                                           "Initial attempt to change heading 
2"))
+        (should (org-edna-test-check-block heading3-pom
+                                           "Initial attempt to change heading 
3"))
+        (should (org-edna-test-check-block heading4-pom
+                                           "Initial attempt to change heading 
4"))
+        ;; Mark heading 1 as DONE
+        (should (not (org-edna-test-check-block heading1-pom
+                                              "Set heading 1 to DONE")))
+        ;; Only heading 2 should have a scheduled time
+        (should (string-equal (org-entry-get heading2-pom "SCHEDULED")
+                              "<2000-01-15 Sat 01:00>"))
+        (should (not (org-entry-get heading3-pom "SCHEDULED")))
+        (should (not (org-entry-get heading4-pom "SCHEDULED")))
+        ;; The others should still be blocked.
+        (should (org-edna-test-check-block heading3-pom
+                                           "Second attempt to change heading 
3"))
+        (should (org-edna-test-check-block heading4-pom
+                                           "Second attempt to change heading 
4"))
+        ;; Try changing heading 2
+        (should (not (org-edna-test-check-block heading2-pom
+                                              "Set heading 2 to DONE")))
+        (should (string-equal (org-entry-get heading3-pom "SCHEDULED")
+                              "<2000-01-16 Sun 09:00>"))
+        ;; 4 should still be blocked
+        (should (org-edna-test-check-block heading4-pom
+                                           "Second attempt to change heading 
4"))))))
+
+(ert-deftest org-edna-doc-test/nightly ()
+  (org-edna-doc-test-setup "8b6d9820-d943-4622-85c9-4a346e033453"
+    (pcase-let* ((`(,nightly-pom ,lunch-pom ,door-pom ,dog-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify that Nightly is blocked
+      (should (org-edna-test-check-block nightly-pom "Initial Nightly Check"))
+      ;; Check off Lunch, and verify that nightly is still blocked
+      (org-edna-test-mark-done lunch-pom)
+      (should (org-edna-test-check-block nightly-pom "Nightly after Lunch"))
+      ;; Check off Door, and verify that nightly is still blocked
+      (org-edna-test-mark-done door-pom)
+      (should (org-edna-test-check-block nightly-pom "Nightly after Door"))
+      ;; Check off Dog.  This should trigger the others.
+      (org-edna-test-mark-done dog-pom)
+      (should (org-edna-test-compare-todos lunch-pom "TODO" "Lunch after 
Nightly Trigger"))
+      (should (org-edna-test-compare-todos door-pom "TODO" "Door after Nightly 
Trigger"))
+      (should (org-edna-test-compare-todos dog-pom "TODO" "Dog after Nightly 
Trigger"))
+      (should (string-equal (org-entry-get nightly-pom "DEADLINE")
+                            "<2000-01-16 Sun +1d>")))))
+
+(ert-deftest org-edna-doc-test/nightly-cache ()
+  (let ((org-edna-finder-use-cache t))
+    (org-edna-doc-test-setup "8b6d9820-d943-4622-85c9-4a346e033453"
+      (pcase-let* ((`(,nightly-pom ,lunch-pom ,door-pom ,dog-pom)
+                    (org-edna-test-children-marks)))
+        ;; Verify that Nightly is blocked
+        (should (org-edna-test-check-block nightly-pom "Initial Nightly 
Check"))
+        ;; Check off Lunch, and verify that nightly is still blocked
+        (org-edna-test-mark-done lunch-pom)
+        (should (org-edna-test-check-block nightly-pom "Nightly after Lunch"))
+        ;; Check off Door, and verify that nightly is still blocked
+        (org-edna-test-mark-done door-pom)
+        (should (org-edna-test-check-block nightly-pom "Nightly after Door"))
+        ;; Check off Dog.  This should trigger the others.
+        (org-edna-test-mark-done dog-pom)
+        (should (org-edna-test-compare-todos lunch-pom "TODO" "Lunch after 
Nightly Trigger"))
+        (should (org-edna-test-compare-todos door-pom "TODO" "Door after 
Nightly Trigger"))
+        (should (org-edna-test-compare-todos dog-pom "TODO" "Dog after Nightly 
Trigger"))
+        (should (string-equal (org-entry-get nightly-pom "DEADLINE")
+                              "<2000-01-16 Sun +1d>"))))))
+
+(ert-deftest org-edna-doc-test/daily ()
+  (org-edna-doc-test-setup "630805bb-a864-4cdc-9a6f-0f126e887c66"
+    (pcase-let* ((`(,daily-pom ,lunch-pom ,door-pom ,dog-pom)
+                  (org-edna-test-children-marks)))
+      ;; Check off Lunch.  This should trigger the others.
+      (org-edna-test-mark-done lunch-pom)
+      (should (org-edna-test-compare-todos lunch-pom "TODO" "Lunch after Daily 
Trigger"))
+      (should (org-edna-test-compare-todos door-pom "TODO" "Door after Daily 
Trigger"))
+      (should (org-edna-test-compare-todos dog-pom "TODO" "Dog after Daily 
Trigger"))
+      (should (string-equal (org-entry-get daily-pom "DEADLINE")
+                            "<2000-01-16 Sun +1d>"))
+      ;; Check off Door.  This should trigger the others.
+      (org-edna-test-mark-done door-pom)
+      (should (org-edna-test-compare-todos lunch-pom "TODO" "Lunch after Door 
Trigger"))
+      (should (org-edna-test-compare-todos door-pom "TODO" "Door after Door 
Trigger"))
+      (should (org-edna-test-compare-todos dog-pom "TODO" "Dog after Door 
Trigger"))
+      (should (string-equal (org-entry-get daily-pom "DEADLINE")
+                            "<2000-01-17 Mon +1d>"))
+      ;; Check off Dog.  This should trigger the others.
+      (org-edna-test-mark-done dog-pom)
+      (should (org-edna-test-compare-todos lunch-pom "TODO" "Lunch after Dog 
Trigger"))
+      (should (org-edna-test-compare-todos door-pom "TODO" "Door after Dog 
Trigger"))
+      (should (org-edna-test-compare-todos dog-pom "TODO" "Dog after Dog 
Trigger"))
+      (should (string-equal (org-entry-get daily-pom "DEADLINE")
+                            "<2000-01-18 Tue +1d>")))))
+
+(ert-deftest org-edna-doc-test/weekly ()
+  (org-edna-doc-test-setup "cf529a5e-1b0c-40c3-8f85-fe2fc4df0ffd"
+    (pcase-let* ((`(,weekly-pom ,lunch-pom ,door-pom ,dog-pom)
+                  (org-edna-test-children-marks)))
+      ;; Check off Lunch.  This should trigger the others.
+      (org-edna-test-mark-done lunch-pom)
+      (should (org-edna-test-compare-todos lunch-pom "TODO" "Lunch after 
Weekly Trigger"))
+      (should (org-edna-test-compare-todos door-pom "TODO" "Door after Weekly 
Trigger"))
+      (should (org-edna-test-compare-todos dog-pom "TODO" "Dog after Weekly 
Trigger"))
+      (should (string-equal (org-entry-get weekly-pom "DEADLINE")
+                            "<2000-01-16 Sun +1d>")))))
+
+(ert-deftest org-edna-doc-test/basic-shower ()
+  (org-edna-doc-test-setup "34d67756-927b-4a21-a62d-7989bd138946"
+    (pcase-let* ((`(,shower-pom ,towels-pom) (org-edna-test-children-marks)))
+      ;; Verify towels is blocked
+      (should (org-edna-test-check-block towels-pom "Initial Towels Check"))
+      ;; Check off "Take Shower" and verify that it incremented the property
+      (org-edna-test-mark-done shower-pom)
+      (should (string-equal (org-entry-get shower-pom "COUNT") "1"))
+      ;; Verify towels is blocked
+      (should (org-edna-test-check-block towels-pom "Towels Check, Count=1"))
+      ;; Check off "Take Shower" and verify that it incremented the property
+      (org-edna-test-mark-done shower-pom)
+      (should (string-equal (org-entry-get shower-pom "COUNT") "2"))
+      ;; Verify towels is blocked
+      (should (org-edna-test-check-block towels-pom "Towels Check, Count=2"))
+      ;; Check off "Take Shower" and verify that it incremented the property
+      (org-edna-test-mark-done shower-pom)
+      (should (string-equal (org-entry-get shower-pom "COUNT") "3"))
+      ;; Verify that towels is no longer blocked.
+      (should (not (org-edna-test-check-block towels-pom "Towels Check, 
Count=3")))
+      ;; Verify that the property was reset.
+      (should (string-equal (org-entry-get shower-pom "COUNT") "0")))))
+
+(ert-deftest org-edna-doc-test/snow-shoveling ()
+  (org-edna-doc-test-setup "b1d89bd8-db96-486e-874c-98e2b3a8cbf2"
+    (pcase-let* ((`(,monday-pom ,tuesday-pom ,wednesday-pom ,shovel-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify shovels is blocked
+      (should (org-edna-test-check-block shovel-pom "Initial Shovel Check"))
+
+      ;; Mark Monday as done
+      (org-edna-test-mark-done monday-pom)
+      (should (not (org-edna-test-check-block shovel-pom "Shovel after 
changing Monday")))
+      ;; Reset
+      (org-edna-test-mark-todo monday-pom tuesday-pom wednesday-pom shovel-pom)
+
+      ;; Mark Tuesday as done
+      (org-edna-test-mark-done tuesday-pom)
+      (should (not (org-edna-test-check-block shovel-pom "Shovel after 
changing Tuesday")))
+
+      ;; Reset
+      (org-edna-test-mark-todo monday-pom tuesday-pom wednesday-pom shovel-pom)
+      ;; Mark Wednesday as done
+      (org-edna-test-mark-done wednesday-pom)
+      (should (not (org-edna-test-check-block shovel-pom "Shovel after 
changing Wednesday"))))))
+
+(ert-deftest org-edna-doc-test/consider-fraction ()
+  (org-edna-doc-test-setup "7de5af8b-a226-463f-8360-edd88b99462a"
+    (pcase-let* ((`(,shovel-pom ,room-pom ,vacuum-pom ,lunch-pom ,edna-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify Edna is blocked
+      (should (org-edna-test-check-block edna-pom "Initial Edna Check"))
+
+      ;; Mark Shovel snow as done
+      (org-edna-test-mark-done shovel-pom)
+      ;; Verify Edna is still blocked
+      (should (org-edna-test-check-block edna-pom "Edna Check after Shovel"))
+
+      ;; Mark Vacuum as done
+      (org-edna-test-mark-done vacuum-pom)
+      ;; Verify Edna is still blocked
+      (should (org-edna-test-check-block edna-pom "Edna Check after Vacuum"))
+
+      ;; Mark Room as done
+      (org-edna-test-mark-done room-pom)
+      ;; Verify Edna is no longer blocked
+      (should (not (org-edna-test-check-block edna-pom "Edna Check after 
Room"))))))
+
+(ert-deftest org-edna-doc-test/consider-number ()
+  (org-edna-doc-test-setup "b79279f7-be3c-45ac-96dc-6e962a5873d4"
+    (pcase-let* ((`(,shovel-pom ,room-pom ,vacuum-pom ,lunch-pom ,edna-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify Edna is blocked
+      (should (org-edna-test-check-block edna-pom "Initial Edna Check"))
+
+      ;; Mark Shovel snow as done
+      (org-edna-test-mark-done shovel-pom)
+      ;; Verify Edna is still blocked
+      (should (org-edna-test-check-block edna-pom "Edna Check after Shovel"))
+
+      ;; Mark Vacuum as done
+      (org-edna-test-mark-done vacuum-pom)
+      ;; Verify Edna is still blocked
+      (should (org-edna-test-check-block edna-pom "Edna Check after Vacuum"))
+
+      ;; Mark Room as done
+      (org-edna-test-mark-done room-pom)
+      ;; Verify Edna is no longer blocked
+      (should (not (org-edna-test-check-block edna-pom "Edna Check after 
Room"))))))
+
+(ert-deftest org-edna-doc-test/has-tags ()
+  (org-edna-doc-test-setup "6885e932-2c3e-4f20-ac22-5f5a0e791d67"
+    (pcase-let* ((`(,first-pom ,second-pom ,third-pom)
+                  (org-edna-test-children-marks)))
+      ;; Verify that 3 is blocked
+      (should (org-edna-test-check-block third-pom "Initial Check"))
+
+      ;; Remove the tag from Task 1
+      (org-with-point-at first-pom
+        (org-set-tags-to ""))
+
+      ;; Verify that 3 is still blocked
+      (should (org-edna-test-check-block third-pom "Check after removing 
tag1"))
+
+      ;; Remove the tag from Task 2
+      (org-with-point-at second-pom
+        (org-set-tags-to ""))
+
+      ;; Verify that 3 is no longer blocked
+      (should (not (org-edna-test-check-block third-pom "Check after removing 
tag2"))))))
+
+(ert-deftest org-edna-doc-test/matches ()
+  (org-edna-doc-test-setup "8170bf82-c2ea-49e8-bd79-97a95176783f"
+    (pcase-let* ((`(,first-pom ,second-pom ,third-pom) 
(org-edna-test-children-marks)))
+      ;; Verify that 3 is blocked
+      (should (org-edna-test-check-block third-pom "Initial Check"))
+
+      ;; Set 1 to DONE
+      (org-edna-test-mark-done first-pom)
+
+      ;; Verify that 3 is still blocked
+      (should (org-edna-test-check-block third-pom "Check after First"))
+
+      ;; Set 2 to DONE
+      (org-edna-test-mark-done second-pom)
+
+      ;; Verify that 3 is no longer blocked
+      (should (not (org-edna-test-check-block third-pom "Check after 
Second"))))))
+
+(ert-deftest org-edna-doc-test/chain ()
+  (org-edna-doc-test-setup "1bd282ea-9238-47ea-9b4d-dafba19d278b"
+    (pcase-let* ((`(,first-pom ,second-pom) (org-edna-test-children-marks)))
+      ;; Set 1 to DONE
+      (org-edna-test-mark-done first-pom)
+      (should (string-equal (org-entry-get second-pom "COUNT") "2")))))
 
 (provide 'org-edna-tests)
 
diff --git a/packages/org-edna/org-edna-tests.org 
b/packages/org-edna/org-edna-tests.org
index 5e20614..2819700 100644
--- a/packages/org-edna/org-edna-tests.org
+++ b/packages/org-edna/org-edna-tests.org
@@ -1,4 +1,5 @@
 #+STARTUP: nologdone
+#+STARTUP: indent
 #+PROPERTY: Effort_ALL 0:01 0:02 0:03
 #+PROPERTY: COUNTER_ALL a b c d
 
@@ -21,7 +22,13 @@ along with this program.  If not, see 
<http://www.gnu.org/licenses/>.
 #+END_QUOTE
 * Test Pool
 ** TODO Tagged Heading 1                                            :1:test:
+:PROPERTIES:
+:ID:       0fa0d4dd-40f2-4251-a558-4c6e2898c2df
+:END:
 ** TODO Tagged Heading 2                                            :1:test:
+:PROPERTIES:
+:ID:       30957f69-8c31-4a13-86ff-f0c5026fb65d
+:END:
 ** TODO ID Heading 1
 :PROPERTIES:
 :ID:       0d491588-7da3-43c5-b51a-87fbd34f79f7
@@ -33,25 +40,28 @@ along with this program.  If not, see 
<http://www.gnu.org/licenses/>.
 :LOGGING:  nil
 :END:
 ** TODO ID Heading 3
-SCHEDULED: <2000-01-15 Sat 00:00>
+DEADLINE: <2000-01-15 Sat 00:00> SCHEDULED: <2000-01-15 Sat 00:00>
 :PROPERTIES:
 :ID:       97e6b0f0-40c4-464f-b760-6e5ca9744eb5
 :END:
+<2000-01-15 Sat 00:00>
 ** DONE ID Heading 4
 :PROPERTIES:
 :ID:       7d4d564b-18b2-445c-a0c8-b1b3fb9ad29e
 :END:
 ** Scheduled Headings
 *** TODO Scheduled Heading 1
-SCHEDULED: <2017-01-01 Sun>
+DEADLINE: <2017-01-01 Sun> SCHEDULED: <2017-01-01 Sun>
 :PROPERTIES:
 :ID:       caf27724-0887-4565-9765-ed2f1edcfb16
 :END:
+<2017-01-01 Sun>
 *** TODO Scheduled Heading 2
-SCHEDULED: <2017-01-01 Sun>
+DEADLINE: <2017-01-01 Sun> SCHEDULED: <2017-01-01 Sun>
 :PROPERTIES:
 :ID:       5594d4f1-b1bb-400f-9f3d-e2f9b43e82c3
 :END:
+<2017-01-01 Sun>
 ** Sibling Headings
 :PROPERTIES:
 :ID:       21b8f1f5-14e8-4677-873d-69e0389fdc9e
@@ -95,12 +105,14 @@ DEADLINE: <2017-01-07 Sat> SCHEDULED: <2017-01-02 Mon>
 :ID:       7c542695-8165-4c8b-b44d-4c12fa009548
 :Effort:   0:01
 :END:
+<2017-01-02 Mon>
 *** [#B] Child Heading with Children
 DEADLINE: <2017-01-03 Tue> SCHEDULED: <2017-01-03 Tue>
 :PROPERTIES:
 :ID:       c7a986df-8d89-4509-b086-6db429b5607b
 :Effort:   0:03
 :END:
+<2017-01-03 Tue>
 **** Child Heading One
 :PROPERTIES:
 :ID:       588bbd29-2e07-437f-b74d-f72459b545a1
@@ -115,25 +127,224 @@ DEADLINE: <2017-01-01 Sun> SCHEDULED: <2017-01-06 Fri>
 :ID:       8c0b31a1-af49-473c-92ea-a5c1c3bace33
 :Effort:   0:02
 :END:
+<2017-01-06 Fri>
 *** [#B] COMMENT Commented Child Heading
 DEADLINE: <2017-01-08 Sun> SCHEDULED: <2017-01-04 Wed>
 :PROPERTIES:
 :ID:       0a1b9508-17ce-49c5-8ff3-28a0076374f5
 :Effort:   0:06
 :END:
+<2017-01-04 Wed>
 *** [#A] Archived Child Heading                                   :ARCHIVE:
 DEADLINE: <2017-01-02 Mon> SCHEDULED: <2017-01-01 Sun>
 :PROPERTIES:
 :ID:       a4b6131e-0560-4201-86d5-f32b36363431
 :Effort:   0:05
 :END:
+<2017-01-01 Sun>
 *** DONE [#C] Child Heading with DONE
 DEADLINE: <2017-01-05 Thu> SCHEDULED: <2017-01-05 Thu>
 :PROPERTIES:
 :ID:       4a1d74a2-b032-47da-a823-b32f5cab0aae
 :Effort:   0:08
 :END:
+<2017-01-05 Thu>
 ** Parent Sub Heading #2
 :PROPERTIES:
 :ID:       4fe67f03-2b35-4708-8c38-54d2c4dfab81
 :END:
+* Documentation Tests
+** Ancestors
+:PROPERTIES:
+:ID:       24a0c3bb-7e69-4e9e-bb98-5aba2ff17bb1
+:END:
+*** TODO Heading 1
+**** TODO Heading 2
+**** TODO Heading 3
+***** TODO Heading 4
+****** TODO Heading 5
+:PROPERTIES:
+:BLOCKER:  ancestors
+:END:
+** Descendants
+:PROPERTIES:
+:ID:       cc18dc74-00e8-4081-b46f-e36800041fe7
+:END:
+*** TODO Heading 1
+:PROPERTIES:
+:BLOCKER:  descendants
+:END:
+**** TODO Heading 2
+**** TODO Heading 3
+***** TODO Heading 4
+****** TODO Heading 5
+** Laundry
+:PROPERTIES:
+:ID:       e57ce099-9f37-47f4-a6bb-61a84eb1fbbe
+:END:
+*** TODO Put clothes in washer
+SCHEDULED: <2000-01-15 Sat 00:00>
+:PROPERTIES:
+:TRIGGER: next-sibling scheduled!("++1h")
+:END:
+*** TODO Put clothes in dryer
+:PROPERTIES:
+:TRIGGER: next-sibling scheduled!("Sun 9:00")
+:BLOCKER:  previous-sibling
+:END:
+*** TODO Fold laundry
+:PROPERTIES:
+:TRIGGER: next-sibling scheduled!("++1h")
+:BLOCKER:  previous-sibling
+:END:
+*** TODO Put clothes away
+:PROPERTIES:
+:TRIGGER: next-sibling scheduled!("++1h")
+:BLOCKER:  previous-sibling
+:END:
+** Nightlies - Standard
+:PROPERTIES:
+:ID:       8b6d9820-d943-4622-85c9-4a346e033453
+:END:
+*** TODO Nightly
+DEADLINE: <2000-01-15 Sat +1d>
+:PROPERTIES:
+:ID:       2d94abf9-2d63-46fd-8dc5-cd396555bcfe
+:BLOCKER:  match("nightly")
+:TRIGGER:  match("nightly") todo!(TODO)
+:END:
+*** TODO Prepare Tomorrow's Lunch                                 :nightly:
+:PROPERTIES:
+:TRIGGER:  if match("nightly") then ids(2d94abf9-2d63-46fd-8dc5-cd396555bcfe) 
todo!(DONE) endif
+:END:
+*** TODO Lock Back Door                                           :nightly:
+:PROPERTIES:
+:TRIGGER:  if match("nightly") then ids(2d94abf9-2d63-46fd-8dc5-cd396555bcfe) 
todo!(DONE) endif
+:END:
+*** TODO Feed Dog                                                 :nightly:
+:PROPERTIES:
+:TRIGGER:  if match("nightly") then ids(2d94abf9-2d63-46fd-8dc5-cd396555bcfe) 
todo!(DONE) endif
+:END:
+** Dailies - Consideration
+:PROPERTIES:
+:ID:       630805bb-a864-4cdc-9a6f-0f126e887c66
+:END:
+*** TODO Daily
+DEADLINE: <2000-01-15 Sat +1d>
+:PROPERTIES:
+:ID:       96f7e46c-40c3-4f5b-8f00-81a6e3cb122b
+:TRIGGER:  match("daily") todo!(TODO)
+:END:
+*** TODO Prepare Tomorrow's Lunch                                   :daily:
+:PROPERTIES:
+:TRIGGER:  if consider(all) match("daily") then 
ids(96f7e46c-40c3-4f5b-8f00-81a6e3cb122b) todo!(DONE) endif
+:END:
+*** TODO Lock Back Door                                             :daily:
+:PROPERTIES:
+:TRIGGER:  if consider(3) match("daily") then 
ids(96f7e46c-40c3-4f5b-8f00-81a6e3cb122b) todo!(DONE) endif
+:END:
+*** TODO Feed Dog                                                   :daily:
+:PROPERTIES:
+:TRIGGER:  if consider(0.9) match("daily") then 
ids(96f7e46c-40c3-4f5b-8f00-81a6e3cb122b) todo!(DONE) endif
+:END:
+** Weeklies - Inverted Conditional
+:PROPERTIES:
+:ID:       cf529a5e-1b0c-40c3-8f85-fe2fc4df0ffd
+:END:
+*** TODO Weekly
+DEADLINE: <2000-01-15 Sat +1d>
+:PROPERTIES:
+:ID:       9a0c4b00-64be-4971-a93e-c530cbdd4b2b
+:TRIGGER:  match("weekly") todo!(TODO)
+:END:
+*** TODO Prepare Tomorrow's Lunch                                 :weekly:
+:PROPERTIES:
+:TRIGGER:  if match("weekly") then else 
ids(9a0c4b00-64be-4971-a93e-c530cbdd4b2b) todo!(DONE) endif
+:END:
+*** TODO Lock Back Door                                           :weekly:
+:PROPERTIES:
+:TRIGGER:  if match("weekly") then else 
ids(9a0c4b00-64be-4971-a93e-c530cbdd4b2b) todo!(DONE) endif
+:END:
+*** TODO Feed Dog                                                 :weekly:
+:PROPERTIES:
+:TRIGGER:  if match("weekly") then else 
ids(9a0c4b00-64be-4971-a93e-c530cbdd4b2b) todo!(DONE) endif
+:END:
+** Basic Shower - No Conditional
+:PROPERTIES:
+:ID:       34d67756-927b-4a21-a62d-7989bd138946
+:END:
+*** TODO Take Shower
+:PROPERTIES:
+:COUNT:  0
+:TRIGGER:  self set-property!("COUNT" inc) todo!("TODO")
+:END:
+*** TODO Wash Towels
+:PROPERTIES:
+:BLOCKER:  previous-sibling !has-property?("COUNT" "3")
+:TRIGGER:  previous-sibling set-property!("COUNT" "0")
+:END:
+** Snow Shoveling
+:PROPERTIES:
+:ID:       b1d89bd8-db96-486e-874c-98e2b3a8cbf2
+:END:
+*** TODO Shovel on Monday
+*** TODO Shovel on Tuesday
+*** TODO Shovel on Wednesday
+*** TODO Put shovel away
+:PROPERTIES:
+:BLOCKER: consider(all) rest-of-siblings-wrap
+:END:
+** Work I - Consider Fraction
+:PROPERTIES:
+:ID:       7de5af8b-a226-463f-8360-edd88b99462a
+:END:
+*** TODO Shovel Snow
+*** TODO Clean room
+*** TODO Vacuum
+*** TODO Eat lunch
+*** TODO Work on Edna
+:PROPERTIES:
+:BLOCKER: consider(0.5) rest-of-siblings-wrap
+:END:
+** Work II - Consider Number
+:PROPERTIES:
+:ID:       b79279f7-be3c-45ac-96dc-6e962a5873d4
+:END:
+*** TODO Shovel Snow
+*** TODO Clean room
+*** TODO Vacuum
+*** TODO Eat lunch
+*** TODO Work on Edna
+:PROPERTIES:
+:BLOCKER: consider(2) rest-of-siblings-wrap
+:END:
+** Has Tags
+:PROPERTIES:
+:ID:       6885e932-2c3e-4f20-ac22-5f5a0e791d67
+:END:
+*** Task 1                                                           :tag1:
+*** Task 2                                                      :tag3:tag2:
+*** TODO Task 3
+:PROPERTIES:
+:BLOCKER:  rest-of-siblings-wrap has-tags?("tag1" "tag2")
+:END:
+** Matches
+:PROPERTIES:
+:ID:       8170bf82-c2ea-49e8-bd79-97a95176783f
+:END:
+*** TODO Task 1
+*** TODO Task 2
+*** TODO Task 3
+:PROPERTIES:
+:BLOCKER:  rest-of-siblings-wrap !matches?("TODO==\"DONE\"")
+:END:
+** Chain
+:PROPERTIES:
+:ID:       1bd282ea-9238-47ea-9b4d-dafba19d278b
+:END:
+*** TODO Heading 1
+:PROPERTIES:
+:COUNT:    2
+:TRIGGER:  next-sibling chain!("COUNT")
+:END:
+*** TODO Heading 2
diff --git a/packages/org-edna/org-edna.el b/packages/org-edna/org-edna.el
index e271cb0..486cd88 100644
--- a/packages/org-edna/org-edna.el
+++ b/packages/org-edna/org-edna.el
@@ -7,7 +7,7 @@
 ;; Keywords: convenience, text, org
 ;; URL: https://savannah.nongnu.org/projects/org-edna-el/
 ;; Package-Requires: ((emacs "25.1") (seq "2.19") (org "9.0.5"))
-;; Version: 1.0beta8
+;; Version: 1.0
 
 ;; This file is part of GNU Emacs.
 
@@ -96,7 +96,8 @@ Currently, the following are handled:
 
 Everything else is returned as is."
   (pcase arg
-    ((and (pred symbolp)
+    ((and (pred symbolp) ;; Symbol
+          ;; Name matches `org-uuidgen-p'
           (let (pred org-uuidgen-p) (symbol-name arg)))
      (symbol-name arg))
     (_
@@ -127,10 +128,12 @@ If KEY is an invalid Edna keyword, then return nil."
   (cond
    ;; Just return nil if it's not a symbol
    ((or (not key)
-        (not (symbolp key))))
+        (not (symbolp key)))
+    nil)
    ((memq key '(consideration consider))
-    ;; Function is ignored here
-    (cons 'consideration 'identity))
+    ;; Function is ignored here, but `org-edna-describe-keyword' needs this
+    ;; function.
+    (cons 'consideration 'org-edna-handle-consideration))
    ((string-suffix-p "!" (symbol-name key))
     ;; Action
     (let ((func-sym (intern (format "org-edna-action/%s" key))))
@@ -396,7 +399,7 @@ correspond to internal variables."
                                                             ,target-var
                                                             
,consideration-var))))
       ('consideration
-       `(setq ,consideration-var ,(nth 0 args))))))
+       `(setq ,consideration-var ',(nth 0 args))))))
 
 (defun org-edna--expand-sexp-form (form &optional
                                         use-old-scope
@@ -495,7 +498,7 @@ specific form were generated, the results will be 
regenerated and
 stored in cache.
 
 Minor changes to an Org file, such as setting properties or
-adding unrelated headlines, will be taken into account."
+adding unrelated headings, will be taken into account."
   :group 'org-edna
   :type 'boolean)
 
@@ -504,6 +507,24 @@ adding unrelated headlines, will be taken into account."
   :group 'org-edna
   :type 'number)
 
+(defvar org-edna-finder-cache-enabled-finders
+  '(org-edna-finder/match
+    org-edna-finder/ids
+    org-edna-finder/olp
+    org-edna-finder/file
+    org-edna-finder/org-file)
+  "List of finders for which cache is enabled.
+
+Only edit this list if you've added custom finders.  Many
+finders, specifically relative finders, rely on the context in
+which they're called.  For these finders, cache will not work
+properly.
+
+The default state of this list contains the built-in finders for
+which context is irrelevant.
+
+Each entry is the function symbol for the finder.")
+
 (defun org-edna--add-to-finder-cache (func-sym args)
   (let* ((results (apply func-sym args))
          (input (make-org-edna--finder-input :func-sym func-sym
@@ -547,8 +568,12 @@ following reasons:
      ;; We have an entry created within the allowed interval.
      (t entry))))
 
+(defun org-edna--cache-is-enabled-for-finder (func-sym)
+  (memq func-sym org-edna-finder-cache-enabled-finders))
+
 (defun org-edna--handle-finder (func-sym args)
-  (if (not org-edna-finder-use-cache)
+  (if (or (not org-edna-finder-use-cache)
+          (not (org-edna--cache-is-enabled-for-finder func-sym)))
       ;; Not using cache, so use the function directly.
       (apply func-sym args)
     (let* ((entry (org-edna--get-cache-entry func-sym args)))
@@ -558,6 +583,7 @@ following reasons:
         (org-edna--add-to-finder-cache func-sym args)))))
 
 
+;;; Interactive Functions
 
 (defmacro org-edna-run (change-plist &rest body)
   "Run a TODO state change.
@@ -630,6 +656,7 @@ Remove Edna's workers from `org-trigger-hook' and
   (remove-hook 'org-blocker-hook 'org-edna-blocker-function))
 
 
+;;; Finders
 
 ;; Tag Finder
 (defun org-edna-finder/match (match-spec &optional scope skip)
@@ -641,7 +668,11 @@ MATCH-SPEC may be any valid match string; it is passed 
straight
 into `org-map-entries'.
 
 SCOPE and SKIP are their counterparts in `org-map-entries'.
-SCOPE defaults to agenda, and SKIP defaults to nil.
+SCOPE defaults to agenda, and SKIP defaults to nil.  Because of
+the different defaults in SCOPE, the symbol 'buffer may also be
+used.  This indicates that scope should be the current buffer,
+honoring any restriction (the equivalent of the nil SCOPE in
+`org-map-entries'.)
 
 * TODO Test
   :PROPERTIES:
@@ -650,7 +681,10 @@ SCOPE defaults to agenda, and SKIP defaults to nil.
 
 \"Test\" will block until all entries tagged \"test\" and
 \"mine\" in the agenda files are marked DONE."
+  ;; Our default is agenda...
   (setq scope (or scope 'agenda))
+  ;; ...but theirs is the buffer
+  (when (eq scope 'buffer) (setq scope nil))
   (org-map-entries
    ;; Find all entries in the agenda files that match the given tag.
    (lambda nil (point-marker))
@@ -776,6 +810,14 @@ Return a list of markers for the descendants."
   (when-let* ((entry-tags (org-get-tags-at)))
     (seq-intersection tags entry-tags)))
 
+(defun org-edna--get-timestamp-time (pom &optional inherit)
+  "Get the timestamp time as a time tuple, of a format suitable
+for calling org-schedule with, or if there is no timestamp,
+returns nil."
+  (let ((time (org-entry-get pom "TIMESTAMP" inherit)))
+    (when time
+      (apply 'encode-time (org-parse-time-string time)))))
+
 (defun org-edna-finder/relatives (&rest options)
   "Find some relative of the current heading.
 
@@ -841,7 +883,9 @@ All arguments are symbols, unless noted otherwise.
 - scheduled-up:    Scheduled time, farthest first
 - scheduled-down:  Scheduled time, closest first
 - deadline-up:     Deadline time, farthest first
-- deadline-down:   Deadline time, closest first"
+- deadline-down:   Deadline time, closest first
+- timestamp-up:    Timestamp time, farthest first
+- timestamp-down:  Timestamp time, closest first"
   (let (targets
         sortfun
         reverse-sort
@@ -994,6 +1038,18 @@ All arguments are symbols, unless noted otherwise.
                (lambda (lhs rhs)
                  (let ((time-lhs (org-get-deadline-time lhs))
                        (time-rhs (org-get-deadline-time rhs)))
+                   (time-less-p time-lhs time-rhs)))))
+        ('timestamp-up
+         (setq sortfun
+               (lambda (lhs rhs)
+                 (let ((time-lhs (org-edna--get-timestamp-time lhs))
+                       (time-rhs (org-edna--get-timestamp-time rhs)))
+                   (not (time-less-p time-lhs time-rhs))))))
+        ('timestamp-down
+         (setq sortfun
+               (lambda (lhs rhs)
+                 (let ((time-lhs (org-edna--get-timestamp-time lhs))
+                       (time-rhs (org-edna--get-timestamp-time rhs)))
                    (time-less-p time-lhs time-rhs)))))))
     (setq filterfuns (nreverse filterfuns))
     (when (and targets sortfun)
@@ -1224,6 +1280,7 @@ which ones will and won't work."
     (list (point-min-marker))))
 
 
+;;; Actions
 
 ;; Set TODO state
 (defun org-edna-action/todo! (_last-entry new-state)
@@ -1250,11 +1307,18 @@ N is an integer.  WHAT can be `day', `month', `year', 
`minute',
     (org-timestamp-change n what)
     (buffer-string)))
 
+(defun org-edna--property-for-planning-type (type)
+  (pcase type
+    ('scheduled "SCHEDULED")
+    ('deadline "DEADLINE")
+    ('timestamp "TIMESTAMP")
+    (_ "")))
+
 (defun org-edna--get-planning-info (what)
   "Get the planning info for WHAT.
 
-WHAT is either 'scheduled or 'deadline."
-  (org-entry-get nil (if (eq what 'scheduled) "SCHEDULED" "DEADLINE")))
+WHAT is one of 'scheduled, 'deadline, or 'timestamp."
+  (org-entry-get nil (org-edna--property-for-planning-type what)))
 
 ;; Silence the byte-compiler
 (defvar parse-time-weekdays)
@@ -1850,6 +1914,7 @@ Does nothing if the source heading has no property 
PROPERTY."
     (org-entry-put nil property old-prop)))
 
 
+;;; Conditions
 
 ;; For most conditions, we return true if condition is true and neg is false, 
or
 ;; if condition is false and neg is true:
@@ -1952,57 +2017,104 @@ starting from target's position."
     (when (org-xor condition neg)
       (format "%s %s in %s" (if neg "Did Not Find" "Found") match 
(buffer-name)))))
 
+(defun org-edna-condition/has-tags? (neg &rest tags)
+  "Check if the target heading has tags.
+
+Edna Syntax: has-tags?(\"tag1\" \"tag2\"...)
+
+Block if the target heading has any of the tags tag1, tag2, etc."
+  (let* ((condition (apply 'org-edna-entry-has-tags-p tags)))
+    (when (org-xor condition neg)
+      (org-get-heading))))
+
+(defun org-edna--heading-matches (match-string)
+  "Return non-nil if the current heading matches MATCH-STRING."
+  (let* ((matcher (cdr (org-make-tags-matcher match-string)))
+         (todo (org-entry-get nil "TODO"))
+         (tags (org-get-tags-at))
+         (level (org-reduced-level (org-outline-level))))
+    (funcall matcher todo tags level)))
+
+(defun org-edna-condition/matches? (neg match-string)
+  "Matches a heading against a match string.
+
+Edna Syntax: matches?(\"MATCH-STRING\")
+
+Blocks if the target heading matches MATCH-STRING.
+
+MATCH-STRING is a valid match string as passed to
+`org-map-entries'."
+  (let* ((condition (org-edna--heading-matches match-string)))
+    (when (org-xor condition neg)
+      (org-get-heading))))
+
 
+;;; Consideration
 
 (defun org-edna-handle-consideration (consideration blocks)
   "Handle consideration CONSIDERATION.
 
-Edna Syntax: consider(all) [1]
+Edna Syntax: consider(any) [1]
 Edna Syntax: consider(N)   [2]
 Edna Syntax: consider(P)   [3]
-Edna Syntax: consider(any) [4]
+Edna Syntax: consider(all) [4]
+
+A blocker can be read as:
+\"If ANY heading in TARGETS matches CONDITION, block this heading\"
+
+The consideration is \"ANY\".
+
+Form 1 blocks only if any target matches the condition.  This is
+the default.
 
-Form 1: consider all targets when evaluating conditions.
-Form 2: consider the condition met if only N of the targets pass.
-Form 3: consider the condition met if only P% of the targets pass.
-Form 4: consider the condition met if any target meets it
+Form 2 blocks only if at least N targets meet the condition.  N=1
+is the same as 'any'.
 
-If CONSIDERATION is nil, default to 'all.
+Form 3 blocks only if *at least* fraction P of the targets meet
+the condition.  This should be a decimal value between 0 and 1.
+
+Form 4 blocks only if all targets match the condition.
+
+The default consideration is \"any\".
+
+If CONSIDERATION is nil, default to 'any.
 
 The \"consideration\" keyword is also provided.  It functions the
 same as \"consider\"."
-  ;; BLOCKS is a list of blocking entries; if one isn't blocked, its entry will
-  ;; be nil.
-  (let ((consideration (or consideration 'all))
-        (first-block (seq-find #'identity blocks))
-        (total-blocks (seq-length blocks))
-        (fulfilled (seq-count #'not blocks)))
+  ;; BLOCKS is a list of entries that meets the blocking condition; if one 
isn't
+  ;; blocked, its entry will be nil.
+  (let* ((consideration (or consideration 'any))
+         (first-block (seq-find #'identity blocks))
+         (total-blocks (seq-length blocks))
+         (fulfilled (seq-count #'not blocks))
+         (blocked (- total-blocks fulfilled)))
     (pcase consideration
-      ('all
-       ;; All of them must be fulfilled, so find the first one that isn't.
-       first-block)
       ('any
-       ;; Any of them can be fulfilled, so find the first one that is
+       ;; In order to pass, all of them must be fulfilled, so find the first 
one
+       ;; that isn't.
+       first-block)
+      ('all
+       ;; All of them must be set to block, so if one of them doesn't block, 
the
+       ;; entire entry won't block.
        (if (> fulfilled 0)
            ;; Have one fulfilled
            nil
          ;; None of them are fulfilled
          first-block))
       ((pred integerp)
-       ;; A fixed number of them must be fulfilled, so check how many aren't.
-       (let* ((fulfilled (seq-count #'not blocks)))
-         (if (>= fulfilled consideration)
-             nil
-           first-block)))
+       ;; A minimum number of them must meet the blocking condition, so check
+       ;; how many block.
+       (if (>= blocked consideration)
+           first-block
+         nil))
       ((pred floatp)
-       ;; A certain percentage of them must be fulfilled
-       (let* ((fulfilled (seq-count #'not blocks)))
-         (if (>= (/ (float fulfilled) (float total-blocks)) consideration)
-             nil
-           first-block))))))
+       ;; A certain percentage of them must block for the blocker to block.
+       (let* ((float-blocked (/ (float blocked) (float total-blocks))))
+         (if (>= float-blocked consideration)
+             first-block
+           nil))))))
 
 
-
 ;;; Popout editing
 
 (defvar org-edna-edit-original-marker nil)
@@ -2183,7 +2295,7 @@ SUFFIX is an additional suffix to use when matching 
keywords."
   "Return a list of all allowed Edna keywords for a blocker."
   `(,@(org-edna--collect-finders)
     ,@(org-edna--collect-conditions)
-    "consideration"))
+    "consideration" "consider"))
 
 (defun org-edna-completions-for-trigger ()
   "Return a list of all allowed Edna keywords for a trigger."
@@ -2221,7 +2333,32 @@ PRED, and ACTION."
   (when-let* ((bounds (bounds-of-thing-at-point 'symbol)))
     (list (car bounds) (cdr bounds) 'org-edna-completion-table-function)))
 
+(defun org-edna-describe-keyword (keyword)
+  "Describe the Org Edna keyword KEYWORD.
+
+KEYWORD should be a string for a keyword recognized by edna.
+
+Displays help for KEYWORD in the Help buffer."
+  (interactive
+   (list
+    (completing-read
+     "Keyword: "
+     `(,@(org-edna--collect-finders)
+       ,@(org-edna--collect-actions)
+       ,@(org-edna--collect-conditions)
+       "consideration" "consider")
+     nil ;; No filter predicate
+     t))) ;; require match
+  ;; help-split-fundoc splits the usage info from the rest of the 
documentation.
+  ;; This avoids having another usage line in the keyword documentation that 
has
+  ;; nothing to do with how edna expects the function.
+  (pcase-let* ((`(,_type . ,func) (org-edna--function-for-key (intern 
keyword)))
+               (`(,_usage . ,doc) (help-split-fundoc (documentation func t) 
func)))
+    (with-help-window (help-buffer)
+      (princ doc))))
+
 
+;;; Bug Reports
 
 (declare-function lm-report-bug "lisp-mnt" (topic))
 
diff --git a/packages/org-edna/org-edna.info b/packages/org-edna/org-edna.info
index 4f29ebe..abd1b88 100644
--- a/packages/org-edna/org-edna.info
+++ b/packages/org-edna/org-edna.info
@@ -36,6 +36,7 @@ Basic Features
 
 * Finders::                      How to find targets
 * Actions::                      Next steps
+* Getting Help::                 Getting some help
 
 Finders
 
@@ -84,12 +85,14 @@ Advanced Features
 
 Conditions
 
-* done::
-* headings::
-* todo-state::
-* variable-set::
-* has-property::
-* re-search::                    Search for a regular expression
+* Heading is DONE::
+* File Has Headings::
+* Heading TODO State::
+* Lisp Variable Set::
+* Heading Has Property::
+* Regexp Search::                Search for a regular expression
+* Checking Tags::                Matching against a set of tags
+* Matching Headings::            Matching against a match string
 * Negating Conditions::
 
 
@@ -112,6 +115,7 @@ Contributing
 
 Changelog
 
+* 1.0: 10.
 * 1.0beta8: 10beta8.
 * 1.0beta7: 10beta7.
 * 1.0beta6: 10beta6.
@@ -314,6 +318,7 @@ The most basic features of Edna are *finders* and *actions*.
 
 * Finders::                      How to find targets
 * Actions::                      Next steps
+* Getting Help::                 Getting some help
 
 
 File: org-edna.info,  Node: Finders,  Next: Actions,  Up: Basic Features
@@ -666,6 +671,8 @@ and for similarity to org-depend.
    • scheduled-down: Scheduled time, closest first
    • deadline-up: Deadline time, farthest first
    • deadline-down: Deadline time, closest first
+   • timestamp-up: Timestamp time, farthest first
+   • timestamp-down: Timestamp time, closest first
 
    Many of the other finders are shorthand for argument combinations of
 relative:
@@ -758,7 +765,7 @@ when it reaches the end.
    Identical to the *note rest-of-siblings-wrap:: finder.
 
 
-File: org-edna.info,  Node: Actions,  Prev: Finders,  Up: Basic Features
+File: org-edna.info,  Node: Actions,  Next: Getting Help,  Prev: Finders,  Up: 
Basic Features
 
 Actions
 =======
@@ -863,19 +870,23 @@ following, PLANNING is either scheduled or deadline.
 
    Examples:
 
-   • scheduled!(“Mon 09:00”) -> Set SCHEDULED to the following Monday at
-     9:00
-   • deadline!(“++2h”) -> Set DEADLINE to two hours from now.
-   • deadline!(copy) deadline!(“+1h”) -> Copy the source deadline to the
-     target, then increment it by an hour.
-   • scheduled!(“+1wkdy”) -> Set SCHEDULED to the next weekday
-   • scheduled!(“+1d +wkdy”) -> Same as above
-   • deadline!(“+1m -wkdy”) -> Set SCHEDULED up one month, but move
-     backward to find a weekend
-   • scheduled!(“float 2 Tue Feb”) -> Set SCHEDULED to the second
-     Tuesday in the following February
-   • scheduled!(“float 3 Thu”) -> Set SCHEDULED to the third Thursday in
-     the following month
+scheduled!(“Mon 09:00”)
+     Set SCHEDULED to the following Monday at 9:00
+deadline!(“++2h”)
+     Set DEADLINE to two hours from now.
+deadline!(copy) deadline!(“+1h”)
+     Copy the source deadline to the target, then increment it by an
+     hour.
+scheduled!(“+1wkdy”)
+     Set SCHEDULED to the next weekday
+scheduled!(“+1d +wkdy”)
+     Same as above
+deadline!(“+1m -wkdy”)
+     Set DEADLINE up one month, but move backward to find a weekend
+scheduled!(“float 2 Tue Feb”)
+     Set SCHEDULED to the second Tuesday in the following February
+scheduled!(“float 3 Thu”)
+     Set SCHEDULED to the third Thursday in the following month
 
 
 File: org-edna.info,  Node: TODO State,  Next: Archive,  Prev: 
Scheduled/Deadline,  Up: Actions
@@ -891,6 +902,23 @@ TODO State
 state.  It can also be the empty string, in which case the TODO state is
 removed.
 
+   Example:
+
+     * TODO Heading 1
+       :PROPERTIES:
+       :TRIGGER: next-sibling todo!(DONE)
+       :END:
+     * TODO Heading 2
+
+   In this example, when “Heading 1” is marked as DONE, it will also
+mark “Heading 2” as DONE:
+
+     * DONE Heading 1
+       :PROPERTIES:
+       :TRIGGER: next-sibling todo!(DONE)
+       :END:
+     * DONE Heading 2
+
 
 File: org-edna.info,  Node: Archive,  Next: Chain Property,  Prev: TODO State, 
 Up: Actions
 
@@ -915,6 +943,28 @@ Chain Property
    Copies PROPERTY from the source entry to all targets.  Does nothing
 if the source heading has no property PROPERTY.
 
+   Example:
+
+     * TODO Heading 1
+       :PROPERTIES:
+       :COUNTER: 2
+       :TRIGGER: next-sibling chain!("COUNTER")
+       :END:
+     * TODO Heading 2
+
+   In this example, when “Heading 1” is marked as DONE, it will copy its
+COUNTER property to “Heading 2”:
+
+     * DONE Heading 1
+       :PROPERTIES:
+       :COUNTER: 2
+       :TRIGGER: next-sibling chain!("COUNTER")
+       :END:
+     * TODO Heading 2
+       :PROPERTIES:
+       :COUNTER: 2
+       :END:
+
 
 File: org-edna.info,  Node: Clocking,  Next: Property,  Prev: Chain Property,  
Up: Actions
 
@@ -985,7 +1035,7 @@ already set:
 
      * TODO Test
        :PROPERTIES:
-       :TRIGGER: self set-property("TEST" inc)
+       :TRIGGER: self set-property!("TEST" inc)
        :END:
 
    In the above example, if “Test” is set to DONE, Edna will fail to
@@ -997,10 +1047,11 @@ increment the TEST property, since it doesn’t exist.
 
    Examples:
 
-   • set-property!(“COUNTER” “1”) -> Sets the property COUNTER to 1 on
-     all targets
-   • set-property!(“COUNTER” inc) -> Increments the property COUNTER by
-     1.  Following the previous example, it would be 2.
+set-property!(“COUNTER” “1”)
+     Sets the property COUNTER to 1 on all targets
+set-property!(“COUNTER” inc)
+     Increments the property COUNTER by 1.  Following the previous
+     example, it would be 2.
 
 
 File: org-edna.info,  Node: Priority,  Next: Tag,  Prev: Property,  Up: Actions
@@ -1045,14 +1096,32 @@ File: org-edna.info,  Node: Effort,  Prev: Tag,  Up: 
Actions
 Effort
 ------
 
-   • Syntax: set-effort!(VALUE)
+Modifies the effort of all targets.
 
-   Sets the effort of all targets according to VALUE:
+   • Syntax: set-effort!(“VALUE”)
 
-   • If VALUE is a string, then the effort is set to VALUE
-   • If VALUE is an integer, then set the value to the VALUE’th allowed
-     effort property
-   • If VALUE is the symbol ’increment, increment effort
+     Set the effort of all targets to “VALUE”.
+
+   • Syntax: set-effort!(NUMBER)
+
+     Sets the effort to the NUMBER’th allowed effort property.
+
+   • Syntax: set-effort!(increment)
+
+     Increment the effort value.
+
+
+File: org-edna.info,  Node: Getting Help,  Prev: Actions,  Up: Basic Features
+
+Getting Help
+============
+
+Edna provides help for any keyword with ‘M-x org-edna-describe-keyword’.
+When invoked, a list of keywords (finders, actions, etc.)  known to Edna
+will be provided.  Select any one to get its description.
+
+   This description includes the syntax and an explanation of what the
+keyword does.  Some descriptions also contain examples.
 
 
 File: org-edna.info,  Node: Advanced Features,  Next: Extending Edna,  Prev: 
Basic Features,  Up: Top
@@ -1109,29 +1178,31 @@ means block if any target heading isn’t done.
 
 * Menu:
 
-* done::
-* headings::
-* todo-state::
-* variable-set::
-* has-property::
-* re-search::                    Search for a regular expression
+* Heading is DONE::
+* File Has Headings::
+* Heading TODO State::
+* Lisp Variable Set::
+* Heading Has Property::
+* Regexp Search::                Search for a regular expression
+* Checking Tags::                Matching against a set of tags
+* Matching Headings::            Matching against a match string
 * Negating Conditions::
 
 
-File: org-edna.info,  Node: done,  Next: headings,  Up: Conditions
+File: org-edna.info,  Node: Heading is DONE,  Next: File Has Headings,  Up: 
Conditions
 
-done
-----
+Heading is DONE
+---------------
 
    • Syntax: done?
 
    Blocks the source heading if any target heading is DONE.
 
 
-File: org-edna.info,  Node: headings,  Next: todo-state,  Prev: done,  Up: 
Conditions
+File: org-edna.info,  Node: File Has Headings,  Next: Heading TODO State,  
Prev: Heading is DONE,  Up: Conditions
 
-headings
---------
+File Has Headings
+-----------------
 
    • Syntax: headings?
 
@@ -1143,10 +1214,10 @@ Org heading.  This means that target does not have to 
be a heading.
    The above example blocks if refile.org has any headings.
 
 
-File: org-edna.info,  Node: todo-state,  Next: variable-set,  Prev: headings,  
Up: Conditions
+File: org-edna.info,  Node: Heading TODO State,  Next: Lisp Variable Set,  
Prev: File Has Headings,  Up: Conditions
 
-todo-state
-----------
+Heading TODO State
+------------------
 
    • Syntax: todo-state?(STATE)
 
@@ -1155,10 +1226,10 @@ todo-state
    STATE may be a string or a symbol.
 
 
-File: org-edna.info,  Node: variable-set,  Next: has-property,  Prev: 
todo-state,  Up: Conditions
+File: org-edna.info,  Node: Lisp Variable Set,  Next: Heading Has Property,  
Prev: Heading TODO State,  Up: Conditions
 
-variable-set
-------------
+Lisp Variable Set
+-----------------
 
    • Syntax: variable-set?(VARIABLE VALUE)
 
@@ -1167,24 +1238,45 @@ against VALUE. Block the source heading if VARIABLE = 
VALUE.
 
    VARIABLE should be a symbol, and VALUE is any valid lisp expression.
 
-     self variable-set?(test-variable 12)
+   Examples:
+
+self variable-set?(test-variable 12)
+     Blocks if the variable ‘test-variable’ is set to 12.
+self variable-set?(buffer-file-name “org-edna.org”)
+     Blocks if the variable ‘buffer-file-name’ is set to “org-edna.org”.
 
 
-File: org-edna.info,  Node: has-property,  Next: re-search,  Prev: 
variable-set,  Up: Conditions
+File: org-edna.info,  Node: Heading Has Property,  Next: Regexp Search,  Prev: 
Lisp Variable Set,  Up: Conditions
 
-has-property
-------------
+Heading Has Property
+--------------------
 
    • Syntax: has-property?(“PROPERTY” “VALUE”)
 
    Tests each target for the property PROPERTY, and blocks if it’s set
 to VALUE.
 
+   Example:
+
+     * TODO Take Shower
+       :PROPERTIES:
+       :COUNT:  1
+       :TRIGGER: self set-property!("COUNT" inc) todo!("TODO")
+       :END:
+     * TODO Wash Towels
+       :PROPERTIES:
+       :BLOCKER:  previous-sibling !has-property?("COUNT" "3")
+       :TRIGGER:  previous-sibling set-property!("COUNT" "0")
+       :END:
+
+   In this example, “Wash Towels” can’t be completed until the user has
+showered at least three times.
+
 
-File: org-edna.info,  Node: re-search,  Next: Negating Conditions,  Prev: 
has-property,  Up: Conditions
+File: org-edna.info,  Node: Regexp Search,  Next: Checking Tags,  Prev: 
Heading Has Property,  Up: Conditions
 
-re-search
----------
+Regexp Search
+-------------
 
    • Syntax: re-search?(“REGEXP”)
 
@@ -1192,10 +1284,59 @@ re-search
 in any of the targets.
 
    The targets are expected to be files, although this will work with
-other targets as well.
+other targets as well.  When given a target heading, the heading’s file
+will be searched.
+
+
+File: org-edna.info,  Node: Checking Tags,  Next: Matching Headings,  Prev: 
Regexp Search,  Up: Conditions
+
+Checking Tags
+-------------
+
+   • Syntax: has-tags?(“TAG1” “TAG2” ...)
+
+   Blocks the source heading if any of the target headings have one or
+more of the given tags.
+
+     * TODO Task 1                                                          
:tag1:
+     * TODO Task 2                                                     
:tag3:tag2:
+     * TODO Task 3
+       :PROPERTIES:
+       :BLOCKER:  rest-of-siblings-wrap has-tags?("tag1" "tag2")
+       :END:
+
+   In the above example, Tasks 1 and 2 will block Task 3.  Task 1 will
+block it because it contains “tag1” as one of its tags, and likewise for
+Task 2 and “tag2”.
+
+   Note that marking “Task 1” or “Task 2” as DONE will not unblock “Task
+3”.  If you want to set up such a system, use the *note match:: finder.
+
+
+File: org-edna.info,  Node: Matching Headings,  Next: Negating Conditions,  
Prev: Checking Tags,  Up: Conditions
+
+Matching Headings
+-----------------
+
+   • Syntax: matches?(“MATCH-STRING”)
+
+   Blocks the source heading if any of the target headings match against
+MATCH-STRING.
+
+   MATCH-STRING is a string passed to ‘org-map-entries’.
+
+     * TODO Task 1
+     * TODO Task 2
+     * TODO Task 3
+       :PROPERTIES:
+       :BLOCKER:  rest-of-siblings-wrap !matches?("TODO==\"DONE\"")
+       :END:
+
+   In the above example, Tasks 1 and 2 will block Task 3 until they’re
+marked as DONE.
 
 
-File: org-edna.info,  Node: Negating Conditions,  Prev: re-search,  Up: 
Conditions
+File: org-edna.info,  Node: Negating Conditions,  Prev: Matching Headings,  
Up: Conditions
 
 Negating Conditions
 -------------------
@@ -1213,45 +1354,74 @@ File: org-edna.info,  Node: Consideration,  Next: 
Conditional Forms,  Prev: Cond
 Consideration
 =============
 
-“Consideration” is a special keyword that’s only valid for blockers.
+“Consideration” and “consider” are special keywords that are only valid
+for blockers.
 
-   This says “Allow a task to complete if CONSIDERATION of its targets
-pass the given condition”.
+   A blocker says “If ANY heading in TARGETS meets CONDITION, block this
+task”.
 
-   This keyword can allow specifying only a portion of tasks to
-consider:
+   In order to modify the ANY part of that statement, the ‘consider’
+keyword may be used:
 
-  1. consider(PERCENT)
-  2. consider(NUMBER)
-  3. consider(all) (Default)
-  4. consider(any)
+  1. consider(any)
+  2. consider(all)
+  3. consider(FRACTION)
+  4. consider(NUMBER)
 
-   (1) tells the blocker to only consider some portion of the targets.
-If at least PERCENT of them are in a DONE state, allow the task to be
-set to DONE. PERCENT must be a decimal, and doesn’t need to include a
-%-sign.
+   (1) blocks the current task if any target meets the blocking
+condition.  This is the default case.
 
-   (2) tells the blocker to only consider NUMBER of the targets.
+   (2) blocks the current task only if all targets meet the blocking
+condition.
 
-   (3) tells the blocker to consider all following targets.
+     * Shovel Snow
+     ** TODO Shovel on Monday
+     ** TODO Shovel on Tuesday
+     ** TODO Shovel on Wednesday
+     ** TODO Put shovel away
+        :PROPERTIES:
+        :BLOCKER: consider(all) rest-of-siblings-wrap
+        :END:
 
-   (4) tells the blocker to allow passage if any of the targets pass.
+   The above example blocks “Put shovel away” so long as all of the
+siblings are still marked TODO.
 
-   A consideration must be specified before the conditions to which it
-applies:
+   (3) blocks the current task if at least FRACTION of the targets meet
+the blocking condition.
 
-     consider(0.5) siblings match("find_me") consider(all) !done?
+     * Work
+     ** TODO Shovel Snow
+     ** TODO Clean room
+     ** TODO Vacuum
+     ** TODO Eat lunch
+     ** TODO Work on Edna
+        :PROPERTIES:
+        :BLOCKER: consider(0.5) rest-of-siblings-wrap
+        :END:
+
+   The above example blocks “Work on Edna” so long as at least half of
+the siblings are marked TODO. This means that three of them must be
+completed before development can begin on Edna.
 
-   The above code will allow task completion if at least half the
-siblings are complete, and all tasks tagged “find_me” are complete.
+   (4) blocks the current task if at least NUMBER of the targets meet
+the blocking condition.
 
-     consider(1) ids(ID1 ID2 ID3) consider(2) ids(ID3 ID4 ID5 ID6)
+     * Work
+     ** TODO Shovel Snow
+     ** TODO Clean room
+     ** TODO Vacuum
+     ** TODO Eat lunch
+     ** TODO Work on Edna
+        :PROPERTIES:
+        :BLOCKER: consider(2) rest-of-siblings-wrap
+        :END:
 
-   The above code will allow task completion if at least one of ID1,
-ID2, and ID3 are complete, and at least two of ID3, ID4, ID5, and ID6
-are complete.
+   The above example blocks “Work on Edna” so long as two of the
+siblings are marked TODO. This means that NUMBER=1 is the same as
+specifying ‘any’.
 
-   If no consideration is given, ALL is assumed.
+   A consideration must be specified before the conditions to which it
+applies.
 
    Both “consider” and “consideration” are valid keywords; they both
 mean the same thing.
@@ -1305,18 +1475,18 @@ it won’t trigger the original until the last one is 
marked DONE.
    Occasionally, you may find that you’d rather execute a form if the
 condition *would* block.  There are two options.
 
-   The first is confusing: use ‘consider(any)’.  This will tell Edna to
-pass so long as one of the targets meets the condition.  This is the
-opposite of Edna’s standard operation, which only allows passage if all
-targets meet the condition.
+   The first is to use ‘consider(all)’.  This will tell Edna to block
+only if all of the targets meets the condition, and thus not block if at
+least one of them does not meet the condition.  This is the opposite of
+Edna’s standard operation, which only allows passage if all targets meet
+the condition.
 
      * TODO Prepare Tomorrow's Lunch                                     
:nightly:
        :PROPERTIES:
-       :TRIGGER:  if consider(any) match("nightly") then ids(12345) 
todo!(DONE) endif
+       :TRIGGER:  if consider(all) match("nightly") then ids(12345) 
todo!(DONE) endif
        :END:
 
-   The second is a lot easier to understand: just switch the then and
-else clauses:
+   The second is to switch the then and else clauses:
 
      * TODO Prepare Tomorrow's Lunch                                     
:nightly:
        :PROPERTIES:
@@ -1388,6 +1558,12 @@ predicates with ’?’.
    Thus, one can have an action that files a target, and a finder that
 finds a file.
 
+   We recommend that you don’t name a finder with a special character at
+the end of its name.  As we devise new ideas, we consider using special
+characters for additional categories of keywords.  Thus, to avoid
+complications in the future, it’s best if everyone avoids using
+characters that may become reserved in the future.
+
 
 File: org-edna.info,  Node: Finders 1,  Next: Actions 1,  Prev: Naming 
Conventions,  Up: Extending Edna
 
@@ -1637,6 +1813,7 @@ Changelog
 
 * Menu:
 
+* 1.0: 10.
 * 1.0beta8: 10beta8.
 * 1.0beta7: 10beta7.
 * 1.0beta6: 10beta6.
@@ -1646,7 +1823,23 @@ Changelog
 * 1.0beta2: 10beta2.
 
 
-File: org-edna.info,  Node: 10beta8,  Next: 10beta7,  Up: Changelog
+File: org-edna.info,  Node: 10,  Next: 10beta8,  Up: Changelog
+
+1.0
+===
+
+   • Various bugs fixes
+        • Fixed parsing of consideration
+        • Limited cache to just the finders that don’t depend on current
+          position
+   • Added “buffer” option for match finder
+   • Added timestamp sorting to relatives finder
+   • Inverted meaning of consideration to avoid confusion
+   • Added *note has-tags?: Checking Tags. and *note matches?: Matching
+     Headings. conditions
+
+
+File: org-edna.info,  Node: 10beta8,  Next: 10beta7,  Prev: 10,  Up: Changelog
 
 1.0beta8
 ========
@@ -1760,79 +1953,83 @@ Big release here, with three new features.
 
 Tag Table:
 Node: Top225
-Node: Copying4114
-Node: Introduction4936
-Node: Installation and Setup5884
-Node: Basic Operation6608
-Node: Blockers8459
-Node: Triggers8745
-Node: Syntax9007
-Node: Basic Features9697
-Node: Finders10000
-Node: ancestors11765
-Node: children12359
-Node: descendants12769
-Node: file13291
-Node: first-child14040
-Node: ids14300
-Node: match14961
-Node: next-sibling15599
-Node: next-sibling-wrap15856
-Node: olp16170
-Node: org-file16582
-Node: parent17227
-Node: previous-sibling17425
-Node: previous-sibling-wrap17686
-Node: relatives17965
-Node: rest-of-siblings21586
-Node: rest-of-siblings-wrap21871
-Node: self22220
-Node: siblings22381
-Node: siblings-wrap22618
-Node: Actions22922
-Node: Scheduled/Deadline23664
-Node: TODO State27239
-Node: Archive27607
-Node: Chain Property27927
-Node: Clocking28210
-Node: Property28622
-Node: Priority30809
-Node: Tag31378
-Node: Effort31595
-Node: Advanced Features31984
-Node: Finder Cache32432
-Node: Conditions33471
-Node: done34107
-Node: headings34271
-Node: todo-state34647
-Node: variable-set34903
-Node: has-property35332
-Node: re-search35601
-Node: Negating Conditions35961
-Node: Consideration36348
-Node: Conditional Forms37917
-Node: Setting the Properties40573
-Node: Extending Edna41657
-Node: Naming Conventions42147
-Node: Finders 142608
-Node: Actions 142970
-Node: Conditions 143429
-Node: Contributing44315
-Node: Bugs45181
-Node: Working with EDE45538
-Node: Compiling Edna46622
-Node: Testing Edna47491
-Node: Before Sending Changes48472
-Node: Developing with Bazaar49159
-Node: Documentation49900
-Node: Changelog50356
-Node: 10beta850606
-Node: 10beta750718
-Node: 10beta651012
-Node: 10beta551288
-Node: 10beta451675
-Node: 10beta351928
-Node: 10beta252367
+Node: Copying4346
+Node: Introduction5168
+Node: Installation and Setup6116
+Node: Basic Operation6840
+Node: Blockers8691
+Node: Triggers8977
+Node: Syntax9239
+Node: Basic Features9929
+Node: Finders10283
+Node: ancestors12048
+Node: children12642
+Node: descendants13052
+Node: file13574
+Node: first-child14323
+Node: ids14583
+Node: match15244
+Node: next-sibling15882
+Node: next-sibling-wrap16139
+Node: olp16453
+Node: org-file16865
+Node: parent17510
+Node: previous-sibling17708
+Node: previous-sibling-wrap17969
+Node: relatives18248
+Node: rest-of-siblings21974
+Node: rest-of-siblings-wrap22259
+Node: self22608
+Node: siblings22769
+Node: siblings-wrap23006
+Node: Actions23310
+Node: Scheduled/Deadline24073
+Node: TODO State27587
+Node: Archive28312
+Node: Chain Property28632
+Node: Clocking29385
+Node: Property29797
+Node: Priority31970
+Node: Tag32539
+Node: Effort32756
+Node: Getting Help33140
+Node: Advanced Features33585
+Node: Finder Cache34033
+Node: Conditions35072
+Node: Heading is DONE35878
+Node: File Has Headings36084
+Node: Heading TODO State36506
+Node: Lisp Variable Set36800
+Node: Heading Has Property37468
+Node: Regexp Search38214
+Node: Checking Tags38657
+Node: Matching Headings39559
+Node: Negating Conditions40156
+Node: Consideration40551
+Node: Conditional Forms42735
+Node: Setting the Properties45423
+Node: Extending Edna46507
+Node: Naming Conventions46997
+Node: Finders 147789
+Node: Actions 148151
+Node: Conditions 148610
+Node: Contributing49496
+Node: Bugs50362
+Node: Working with EDE50719
+Node: Compiling Edna51803
+Node: Testing Edna52672
+Node: Before Sending Changes53653
+Node: Developing with Bazaar54340
+Node: Documentation55081
+Node: Changelog55537
+Node: 1055798
+Node: 10beta856300
+Node: 10beta756423
+Node: 10beta656717
+Node: 10beta556993
+Node: 10beta457380
+Node: 10beta357633
+Node: 10beta258072
 
 End Tag Table
 
diff --git a/packages/org-edna/org-edna.org b/packages/org-edna/org-edna.org
index 1f01709..89c32aa 100644
--- a/packages/org-edna/org-edna.org
+++ b/packages/org-edna/org-edna.org
@@ -526,6 +526,8 @@ All arguments are symbols, unless noted otherwise.
 - scheduled-down:  Scheduled time, closest first
 - deadline-up:     Deadline time, farthest first
 - deadline-down:   Deadline time, closest first
+- timestamp-up:    Timestamp time, farthest first
+- timestamp-down:  Timestamp time, closest first
 
 Many of the other finders are shorthand for argument combinations of relative:
 
@@ -692,14 +694,15 @@ PLANNING is either scheduled or deadline.
 
 Examples:
 
-- scheduled!("Mon 09:00") -> Set SCHEDULED to the following Monday at 9:00
-- deadline!("++2h") -> Set DEADLINE to two hours from now.
-- deadline!(copy) deadline!("+1h") -> Copy the source deadline to the target, 
then increment it by an hour.
-- scheduled!("+1wkdy") -> Set SCHEDULED to the next weekday
-- scheduled!("+1d +wkdy") -> Same as above
-- deadline!("+1m -wkdy") -> Set SCHEDULED up one month, but move backward to 
find a weekend
-- scheduled!("float 2 Tue Feb") -> Set SCHEDULED to the second Tuesday in the 
following February
-- scheduled!("float 3 Thu") -> Set SCHEDULED to the third Thursday in the 
following month
+- scheduled!("Mon 09:00") :: Set SCHEDULED to the following Monday at 9:00
+- deadline!("++2h") :: Set DEADLINE to two hours from now.
+- deadline!(copy) deadline!("+1h") :: Copy the source deadline to the target, 
then increment it by an hour.
+- scheduled!("+1wkdy") :: Set SCHEDULED to the next weekday
+- scheduled!("+1d +wkdy") :: Same as above
+- deadline!("+1m -wkdy") :: Set DEADLINE up one month, but move backward to 
find a weekend
+- scheduled!("float 2 Tue Feb") :: Set SCHEDULED to the second Tuesday in the 
following February
+- scheduled!("float 3 Thu") :: Set SCHEDULED to the third Thursday in the 
following month
+
 *** TODO State
 :PROPERTIES:
 :CUSTOM_ID: todo!
@@ -713,6 +716,27 @@ Sets the TODO state of the target heading to NEW-STATE.
 NEW-STATE may either be a string or a symbol denoting the new TODO state.  It
 can also be the empty string, in which case the TODO state is removed.
 
+Example:
+
+#+BEGIN_SRC org
+,* TODO Heading 1
+  :PROPERTIES:
+  :TRIGGER: next-sibling todo!(DONE)
+  :END:
+,* TODO Heading 2
+#+END_SRC
+
+In this example, when "Heading 1" is marked as DONE, it will also mark "Heading
+2" as DONE:
+
+#+BEGIN_SRC org
+,* DONE Heading 1
+  :PROPERTIES:
+  :TRIGGER: next-sibling todo!(DONE)
+  :END:
+,* DONE Heading 2
+#+END_SRC
+
 *** Archive
 :PROPERTIES:
 :CUSTOM_ID: archive!
@@ -737,6 +761,32 @@ nil, Edna will not ask before archiving targets.
 Copies PROPERTY from the source entry to all targets.  Does nothing if the
 source heading has no property PROPERTY.
 
+Example:
+
+#+BEGIN_SRC org
+,* TODO Heading 1
+  :PROPERTIES:
+  :COUNTER: 2
+  :TRIGGER: next-sibling chain!("COUNTER")
+  :END:
+,* TODO Heading 2
+#+END_SRC
+
+In this example, when "Heading 1" is marked as DONE, it will copy its COUNTER
+property to "Heading 2":
+
+#+BEGIN_SRC org
+,* DONE Heading 1
+  :PROPERTIES:
+  :COUNTER: 2
+  :TRIGGER: next-sibling chain!("COUNTER")
+  :END:
+,* TODO Heading 2
+  :PROPERTIES:
+  :COUNTER: 2
+  :END:
+#+END_SRC
+
 *** Clocking
 :PROPERTIES:
 :CUSTOM_ID: clocking
@@ -804,7 +854,7 @@ Additionally, all special forms will fail if the property 
is not already set:
 #+begin_src org
 ,* TODO Test
   :PROPERTIES:
-  :TRIGGER: self set-property("TEST" inc)
+  :TRIGGER: self set-property!("TEST" inc)
   :END:
 #+end_src
 
@@ -817,8 +867,8 @@ Deletes the property PROPERTY from all targets.
 
 Examples:
 
-- set-property!("COUNTER" "1") -> Sets the property COUNTER to 1 on all targets
-- set-property!("COUNTER" inc) -> Increments the property COUNTER by 1.  
Following the previous example, it would be 2.
+- set-property!("COUNTER" "1") :: Sets the property COUNTER to 1 on all targets
+- set-property!("COUNTER" inc) :: Increments the property COUNTER by 1.  
Following the previous example, it would be 2.
 
 *** Priority
 :PROPERTIES:
@@ -861,13 +911,32 @@ e.g. tag1:tag2
 :DESCRIPTION: So much effort!
 :END:
 
-- Syntax: set-effort!(VALUE)
+Modifies the effort of all targets.
+
+- Syntax: set-effort!("VALUE")
+
+  Set the effort of all targets to "VALUE".
+
+- Syntax: set-effort!(NUMBER)
+
+  Sets the effort to the NUMBER'th allowed effort property.
+
+- Syntax: set-effort!(increment)
+
+  Increment the effort value.
 
-Sets the effort of all targets according to VALUE:
+** Getting Help
+:PROPERTIES:
+:CUSTOM_ID: help
+:DESCRIPTION: Getting some help
+:END:
+
+Edna provides help for any keyword with ~M-x org-edna-describe-keyword~.  When
+invoked, a list of keywords (finders, actions, etc.) known to Edna will be
+provided.  Select any one to get its description.
 
-- If VALUE is a string, then the effort is set to VALUE
-- If VALUE is an integer, then set the value to the VALUE'th allowed effort 
property
-- If VALUE is the symbol 'increment, increment effort
+This description includes the syntax and an explanation of what the keyword
+does.  Some descriptions also contain examples.
 
 * Advanced Features
 :PROPERTIES:
@@ -910,7 +979,7 @@ that target, then the source heading is blocked.
 If no condition is specified, ~!done?~ is used by default, which means block if
 any target heading isn't done.
 
-*** done
+*** Heading is DONE
 :PROPERTIES:
 :CUSTOM_ID: done
 :END:
@@ -919,7 +988,7 @@ any target heading isn't done.
 
 Blocks the source heading if any target heading is DONE.
 
-*** headings
+*** File Has Headings
 :PROPERTIES:
 :CUSTOM_ID: headings
 :END:
@@ -935,7 +1004,7 @@ org-file("refile.org") headings?
 
 The above example blocks if refile.org has any headings.
 
-*** todo-state
+*** Heading TODO State
 :PROPERTIES:
 :CUSTOM_ID: todo-state
 :END:
@@ -946,7 +1015,7 @@ Blocks if any target heading has TODO state set to STATE.
 
 STATE may be a string or a symbol.
 
-*** variable-set
+*** Lisp Variable Set
 :PROPERTIES:
 :CUSTOM_ID: variable-set
 :END:
@@ -958,11 +1027,12 @@ against VALUE.  Block the source heading if VARIABLE = 
VALUE.
 
 VARIABLE should be a symbol, and VALUE is any valid lisp expression.
 
-#+BEGIN_EXAMPLE
-self variable-set?(test-variable 12)
-#+END_EXAMPLE
+Examples:
 
-*** has-property
+- self variable-set?(test-variable 12) :: Blocks if the variable 
~test-variable~ is set to 12.
+- self variable-set?(buffer-file-name "org-edna.org") :: Blocks if the 
variable ~buffer-file-name~ is set to "org-edna.org".
+
+*** Heading Has Property
 :PROPERTIES:
 :CUSTOM_ID: has-property
 :END:
@@ -971,7 +1041,25 @@ self variable-set?(test-variable 12)
 
 Tests each target for the property PROPERTY, and blocks if it's set to VALUE.
 
-*** re-search
+Example:
+
+#+begin_src org
+,* TODO Take Shower
+  :PROPERTIES:
+  :COUNT:  1
+  :TRIGGER: self set-property!("COUNT" inc) todo!("TODO")
+  :END:
+,* TODO Wash Towels
+  :PROPERTIES:
+  :BLOCKER:  previous-sibling !has-property?("COUNT" "3")
+  :TRIGGER:  previous-sibling set-property!("COUNT" "0")
+  :END:
+#+end_src
+
+In this example, "Wash Towels" can't be completed until the user has showered 
at
+least three times.
+
+*** Regexp Search
 :PROPERTIES:
 :CUSTOM_ID: re-search
 :DESCRIPTION: Search for a regular expression
@@ -983,7 +1071,57 @@ Blocks the source heading if the regular expression 
REGEXP is present in any
 of the targets.
 
 The targets are expected to be files, although this will work with other 
targets
-as well.
+as well.  When given a target heading, the heading's file will be searched.
+*** Checking Tags
+:PROPERTIES:
+:CUSTOM_ID: has-tags
+:DESCRIPTION: Matching against a set of tags
+:END:
+
+- Syntax: has-tags?("TAG1" "TAG2" ...)
+
+Blocks the source heading if any of the target headings have one or more of the
+given tags.
+
+#+begin_src org
+,* TODO Task 1                                                          :tag1:
+,* TODO Task 2                                                     :tag3:tag2:
+,* TODO Task 3
+  :PROPERTIES:
+  :BLOCKER:  rest-of-siblings-wrap has-tags?("tag1" "tag2")
+  :END:
+#+end_src
+
+In the above example, Tasks 1 and 2 will block Task 3.  Task 1 will block it
+because it contains "tag1" as one of its tags, and likewise for Task 2 and
+"tag2".
+
+Note that marking "Task 1" or "Task 2" as DONE will not unblock "Task 3".  If
+you want to set up such a system, use the [[#match][match]] finder.
+*** Matching Headings
+:PROPERTIES:
+:CUSTOM_ID: matches
+:DESCRIPTION: Matching against a match string
+:END:
+
+- Syntax: matches?("MATCH-STRING")
+
+Blocks the source heading if any of the target headings match against
+MATCH-STRING.
+
+MATCH-STRING is a string passed to ~org-map-entries~.
+
+#+begin_src org
+,* TODO Task 1
+,* TODO Task 2
+,* TODO Task 3
+  :PROPERTIES:
+  :BLOCKER:  rest-of-siblings-wrap !matches?("TODO==\"DONE\"")
+  :END:
+#+end_src
+
+In the above example, Tasks 1 and 2 will block Task 3 until they're marked as
+DONE.
 
 *** Negating Conditions
 :PROPERTIES:
@@ -1002,45 +1140,76 @@ tagged "test" does *not* have the property PROP set to 
"1".
 :DESCRIPTION: Only some of them
 :END:
 
-"Consideration" is a special keyword that's only valid for blockers.
+"Consideration" and "consider" are special keywords that are only valid for
+blockers.
 
-This says "Allow a task to complete if CONSIDERATION of its targets pass the
-given condition".
+A blocker says "If ANY heading in TARGETS meets CONDITION, block this task".
 
-This keyword can allow specifying only a portion of tasks to consider:
+In order to modify the ANY part of that statement, the ~consider~ keyword may 
be
+used:
 
-1. consider(PERCENT)
-2. consider(NUMBER)
-3. consider(all) (Default)
-4. consider(any)
+1. consider(any)
+2. consider(all)
+3. consider(FRACTION)
+4. consider(NUMBER)
 
-(1) tells the blocker to only consider some portion of the targets.  If at 
least
-PERCENT of them are in a DONE state, allow the task to be set to DONE.  PERCENT
-must be a decimal, and doesn't need to include a %-sign.
+(1) blocks the current task if any target meets the blocking condition.  This 
is
+the default case.
 
-(2) tells the blocker to only consider NUMBER of the targets.
+(2) blocks the current task only if all targets meet the blocking condition.
 
-(3) tells the blocker to consider all following targets.
+#+begin_src org
+,* Shovel Snow
+,** TODO Shovel on Monday
+,** TODO Shovel on Tuesday
+,** TODO Shovel on Wednesday
+,** TODO Put shovel away
+   :PROPERTIES:
+   :BLOCKER: consider(all) rest-of-siblings-wrap
+   :END:
+#+end_src
 
-(4) tells the blocker to allow passage if any of the targets pass.
+The above example blocks "Put shovel away" so long as all of the siblings are
+still marked TODO.
 
-A consideration must be specified before the conditions to which it applies:
+(3) blocks the current task if at least FRACTION of the targets meet the
+blocking condition.
 
-#+BEGIN_EXAMPLE
-consider(0.5) siblings match("find_me") consider(all) !done?
-#+END_EXAMPLE
+#+begin_src org
+,* Work
+,** TODO Shovel Snow
+,** TODO Clean room
+,** TODO Vacuum
+,** TODO Eat lunch
+,** TODO Work on Edna
+   :PROPERTIES:
+   :BLOCKER: consider(0.5) rest-of-siblings-wrap
+   :END:
+#+end_src
 
-The above code will allow task completion if at least half the siblings are
-complete, and all tasks tagged "find_me" are complete.
+The above example blocks "Work on Edna" so long as at least half of the 
siblings
+are marked TODO.  This means that three of them must be completed before
+development can begin on Edna.
 
-#+BEGIN_SRC emacs-lisp
-consider(1) ids(ID1 ID2 ID3) consider(2) ids(ID3 ID4 ID5 ID6)
-#+END_SRC
+(4) blocks the current task if at least NUMBER of the targets meet the blocking
+condition.
+
+#+begin_src org
+,* Work
+,** TODO Shovel Snow
+,** TODO Clean room
+,** TODO Vacuum
+,** TODO Eat lunch
+,** TODO Work on Edna
+   :PROPERTIES:
+   :BLOCKER: consider(2) rest-of-siblings-wrap
+   :END:
+#+end_src
 
-The above code will allow task completion if at least one of ID1, ID2, and ID3
-are complete, and at least two of ID3, ID4, ID5, and ID6 are complete.
+The above example blocks "Work on Edna" so long as two of the siblings are
+marked TODO.  This means that NUMBER=1 is the same as specifying ~any~.
 
-If no consideration is given, ALL is assumed.
+A consideration must be specified before the conditions to which it applies.
 
 Both "consider" and "consideration" are valid keywords; they both mean the same
 thing.
@@ -1099,18 +1268,19 @@ trigger the original until the last one is marked DONE.
 Occasionally, you may find that you'd rather execute a form if the condition
 *would* block.  There are two options.
 
-The first is confusing: use ~consider(any)~.  This will tell Edna to pass so
-long as one of the targets meets the condition.  This is the opposite of Edna's
-standard operation, which only allows passage if all targets meet the 
condition.
+The first is to use ~consider(all)~.  This will tell Edna to block only if all
+of the targets meets the condition, and thus not block if at least one of them
+does not meet the condition.  This is the opposite of Edna's standard 
operation,
+which only allows passage if all targets meet the condition.
 
 #+begin_src org
 ,* TODO Prepare Tomorrow's Lunch                                     :nightly:
   :PROPERTIES:
-  :TRIGGER:  if consider(any) match("nightly") then ids(12345) todo!(DONE) 
endif
+  :TRIGGER:  if consider(all) match("nightly") then ids(12345) todo!(DONE) 
endif
   :END:
 #+end_src
 
-The second is a lot easier to understand: just switch the then and else 
clauses:
+The second is to switch the then and else clauses:
 
 #+begin_src org
 ,* TODO Prepare Tomorrow's Lunch                                     :nightly:
@@ -1174,6 +1344,12 @@ Scheme to suffix destructive functions with '!' and 
predicates with '?'.
 Thus, one can have an action that files a target, and a finder that finds a
 file.
 
+We recommend that you don't name a finder with a special character at the end 
of
+its name.  As we devise new ideas, we consider using special characters for
+additional categories of keywords.  Thus, to avoid complications in the future,
+it's best if everyone avoids using characters that may become reserved in the
+future.
+
 ** Finders
 :PROPERTIES:
 :DESCRIPTION: Making a new finder
@@ -1410,6 +1586,16 @@ making any changes:
 :PROPERTIES:
 :DESCRIPTION: List of changes by version
 :END:
+** 1.0
+
+- Various bugs fixes
+  - Fixed parsing of consideration
+  - Limited cache to just the finders that don't depend on current position
+- Added "buffer" option for match finder
+- Added timestamp sorting to relatives finder
+- Inverted meaning of consideration to avoid confusion
+- Added [[#has-tags][has-tags?]] and [[#matches][matches?]] conditions
+
 ** 1.0beta8
 Quick fix for beta7.
 ** 1.0beta7
diff --git a/packages/org-edna/test.mk b/packages/org-edna/test.mk
index 29e31e5..4b6590c 100644
--- a/packages/org-edna/test.mk
+++ b/packages/org-edna/test.mk
@@ -23,6 +23,7 @@ test: compile
        -L "." \
        -l "ert" \
        -l "org-edna-tests.el" \
+       --eval "(setq org-edna-test-inhibit-messages t)" \
        -f ert-run-tests-batch-and-exit
 
 include Makefile



reply via email to

[Prev in Thread] Current Thread [Next in Thread]