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

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

[elpa] master 26d2a0d 11/13: Added has-tags? and matches? conditions


From: Ian Dunn
Subject: [elpa] master 26d2a0d 11/13: Added has-tags? and matches? conditions
Date: Sun, 25 Nov 2018 14:09:49 -0500 (EST)

branch: master
commit 26d2a0d716bbb7a92c288b8b78474e6e279bc9b0
Author: Ian Dunn <address@hidden>
Commit: Ian Dunn <address@hidden>

    Added has-tags? and matches? conditions
    
    * org-edna.el (org-edna-condition/has-tags?): New defun.
      (org-edna--heading-matches): New helper defun.
      (org-edna-condition/matches?): New defun.
    
    * org-edna-tests.el: Refactored to consolidate common forms.
      (org-edna-protect-test-file):
      (org-edna-test-setup):
      (org-edna-with-point-at-test-heading):
      (org-edna-with-test-heading): New helper macros.
      (org-edna-test-children-marks): New helper function.
      (org-edna-doc-test/has-tags):
      (org-edna-doc-test/matches):
      (org-edna-doc-test/chain): New tests.
    
    * org-edna.org (Checking Tags):
      (Matching Headings): Sections for new conditions.
---
 org-edna-tests.el  | 1920 +++++++++++++++++++++++-----------------------------
 org-edna-tests.org |   36 +
 org-edna.el        |   33 +-
 org-edna.info      |  215 +++---
 org-edna.org       |   55 +-
 5 files changed, 1092 insertions(+), 1167 deletions(-)

diff --git a/org-edna-tests.el b/org-edna-tests.el
index 52c901a..b6adf15 100644
--- a/org-edna-tests.el
+++ b/org-edna-tests.el
@@ -69,6 +69,48 @@
   (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.
 
@@ -97,8 +139,11 @@ This avoids org-id digging into its internal database."
   (dolist (pom poms)
     (org-edna-test-change-todo-state pom "TODO")))
 
+(defun org-edna-test-children-marks ()
+  (org-edna-collect-descendants nil))
+
 
-;;;; Parser Tests
+;;; Parser Tests
 
 (ert-deftest org-edna-parse-form-no-arguments ()
   (let* ((input-string "test-string")
@@ -469,48 +514,48 @@ This avoids org-id digging into its internal database."
     (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-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:"))))
+  (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)))
@@ -1183,564 +1228,450 @@ This avoids org-id digging into its internal 
database."
         (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))
-         (inhibit-message org-edna-test-inhibit-messages)
-         (target (org-edna-find-test-heading 
"0d491588-7da3-43c5-b51a-87fbd34f79f7")))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"0d491588-7da3-43c5-b51a-87fbd34f79f7")))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-         (inhibit-message org-edna-test-inhibit-messages)
-         (target (org-edna-find-test-heading 
"0d491588-7da3-43c5-b51a-87fbd34f79f7"))
-         (source (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5"))
-         (pairs '((cp . rm) (copy . remove) ("cp" . "rm") ("copy" . 
"remove"))))
-    (unwind-protect
-        (org-with-point-at target
-          (dolist (pair pairs)
-            (org-edna-action/scheduled! source (car pair))
-            (should (string-equal (org-entry-get nil "SCHEDULED")
-                                  "<2000-01-15 Sat 00:00>"))
-            (org-edna-action/scheduled! source (cdr pair))
-            (should (not (org-entry-get nil "SCHEDULED")))))
-      (org-edna-test-restore-test-file))))
+  (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")
+                              "<2000-01-15 Sat 00:00>"))
+        (org-edna-action/scheduled! source (cdr pair))
+        (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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5")))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5")))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"caf27724-0887-4565-9765-ed2f1edcfb16")))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5")))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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 ()
-  ;; 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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"0d491588-7da3-43c5-b51a-87fbd34f79f7")))
-    (unwind-protect
-        (org-with-point-at target
-          (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"))))
-      (org-edna-test-restore-test-file))))
+  (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 ()
-  (let* ((org-agenda-files `(,org-edna-test-file))
-         (inhibit-message org-edna-test-inhibit-messages)
-         (target (org-edna-find-test-heading 
"0d491588-7da3-43c5-b51a-87fbd34f79f7"))
-         (source (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5"))
-         (pairs '((cp . rm) (copy . remove) ("cp" . "rm") ("copy" . 
"remove"))))
-    (unwind-protect
-        (org-with-point-at target
-          (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")))))
-      (org-edna-test-restore-test-file))))
+  (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 ()
-  ;; 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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5")))
-    (unwind-protect
-        (org-with-point-at target
-          ;; 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>")))
-      (org-edna-test-restore-test-file))))
+  (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."
-  ;; 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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5")))
-    (unwind-protect
-        (org-with-point-at target
-          ;; 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>")))
-      (org-edna-test-restore-test-file))))
+  (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."
-  ;; 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))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (target (org-edna-find-test-heading 
"caf27724-0887-4565-9765-ed2f1edcfb16")))
-    (unwind-protect
-        (org-with-point-at target
-          ;; 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>")))
-      (org-edna-test-restore-test-file))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (inhibit-message org-edna-test-inhibit-messages)
-             (org-agenda-files `(,org-edna-test-file))
-             (target (org-edna-find-test-heading 
"97e6b0f0-40c4-464f-b760-6e5ca9744eb5")))
-    (unwind-protect
-        (org-with-point-at target
-          ;; 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>")))
-      (org-edna-test-restore-test-file))))
+  (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))
-        (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-        (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-        (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-        (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-        (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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))
-        (inhibit-message org-edna-test-inhibit-messages)
-        (org-lowest-priority  ?C)
-        (org-highest-priority ?A)
-        (org-default-priority ?B))
-    (unwind-protect
-        (org-with-point-at pom
-          (org-edna-action/set-priority! nil "A")
-          (should (equal (org-entry-get nil "PRIORITY") "A"))
-          (org-edna-action/set-priority! nil 'down)
-          (should (equal (org-entry-get nil "PRIORITY") "B"))
-          (org-edna-action/set-priority! nil 'up)
-          (should (equal (org-entry-get nil "PRIORITY") "A"))
-          (org-edna-action/set-priority! nil ?C)
-          (should (equal (org-entry-get nil "PRIORITY") "C"))
-          (org-edna-action/set-priority! nil 'remove)
-          (should (equal (org-entry-get nil "PRIORITY") "B")))
-      (org-edna-test-restore-test-file))))
+  (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)
+      (should (equal (org-entry-get nil "PRIORITY") "B"))
+      (org-edna-action/set-priority! nil 'up)
+      (should (equal (org-entry-get nil "PRIORITY") "A"))
+      (org-edna-action/set-priority! nil ?C)
+      (should (equal (org-entry-get nil "PRIORITY") "C"))
+      (org-edna-action/set-priority! nil 'remove)
+      (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))
-        (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (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-test-restore-test-file))))
+  (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* ((inhibit-message org-edna-test-inhibit-messages)
-         (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))
-    (unwind-protect
-        (org-with-point-at pom
-          (org-edna-action/archive! nil)
-          (should (equal (org-entry-get nil "ARCHIVE_TODO") "TODO"))
-          (org-entry-delete nil "ARCHIVE_TODO"))
-      (org-edna-test-restore-test-file))))
+  (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 ((inhibit-message org-edna-test-inhibit-messages)
-        (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)))
-    (unwind-protect
-        (progn
-          (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-restore-test-file))))
+  (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)
-  (let* ((inhibit-message org-edna-test-inhibit-messages))
-    (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))
@@ -1817,8 +1748,32 @@ This avoids org-id digging into its internal database."
                                   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"))
@@ -1855,587 +1810,382 @@ This avoids org-id digging into its internal 
database."
 
 ;;; 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 ()
-  (let* ((start-heading (org-edna-find-test-heading 
"24a0c3bb-7e69-4e9e-bb98-5aba2ff17bb1"))
-         (org-todo-keywords '((sequence "TODO" "|" "DONE")))
-         ;; Only block based on Edna
-         (org-blocker-hook 'org-edna-blocker-function)
-         (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (org-with-point-at start-heading
-          (save-restriction
-            ;; Only allow operating on the current tree
-            (org-narrow-to-subtree)
-            ;; Show the entire subtree
-            (outline-show-all)
-            (let* ((heading1-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading2-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading3-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading4-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading5-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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-change-todo-state heading4-pom "DONE")
-              ;; 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))))
-      (org-edna-test-restore-test-file))))
+  (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* ((start-heading (org-edna-find-test-heading 
"24a0c3bb-7e69-4e9e-bb98-5aba2ff17bb1"))
-         (org-todo-keywords '((sequence "TODO" "|" "DONE")))
-         ;; Only block based on Edna
-         (org-blocker-hook 'org-edna-blocker-function)
-         ;; Enable cache
-         (org-edna-finder-use-cache t)
-         (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (org-with-point-at start-heading
-          (save-restriction
-            ;; Only allow operating on the current tree
-            (org-narrow-to-subtree)
-            ;; Show the entire subtree
-            (outline-show-all)
-            (let* ((heading1-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading2-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading3-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading4-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading5-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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-change-todo-state heading4-pom "DONE")
-              ;; 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))))
-      (org-edna-test-restore-test-file))))
+  (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 ()
-  (let* ((start-heading (org-edna-find-test-heading 
"cc18dc74-00e8-4081-b46f-e36800041fe7"))
-         (org-todo-keywords '((sequence "TODO" "|" "DONE")))
-         ;; Only block based on Edna
-         (org-blocker-hook 'org-edna-blocker-function)
-         (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (org-with-point-at start-heading
-          (save-restriction
-            ;; Only allow operating on the current tree
-            (org-narrow-to-subtree)
-            ;; Show the entire subtree
-            (outline-show-all)
-            (let* ((heading1-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading2-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading3-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading4-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading5-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              (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"))))))
-      (org-edna-test-restore-test-file))))
+  (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* ((start-heading (org-edna-find-test-heading 
"cc18dc74-00e8-4081-b46f-e36800041fe7"))
-         (org-todo-keywords '((sequence "TODO" "|" "DONE")))
-         ;; Only block based on Edna
-         (org-blocker-hook 'org-edna-blocker-function)
-         ;; Enable cache
-         (org-edna-finder-use-cache t)
-         (inhibit-message org-edna-test-inhibit-messages))
-    (unwind-protect
-        (org-with-point-at start-heading
-          (save-restriction
-            ;; Only allow operating on the current tree
-            (org-narrow-to-subtree)
-            ;; Show the entire subtree
-            (outline-show-all)
-            (let* ((heading1-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading2-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading3-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading4-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading5-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              (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"))))))
-      (org-edna-test-restore-test-file))))
+  (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."
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"e57ce099-9f37-47f4-a6bb-61a84eb1fbbe"))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((heading1-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading2-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading3-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading4-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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."
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"e57ce099-9f37-47f4-a6bb-61a84eb1fbbe"))
-             (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)
-             ;; Enable cache
-             (org-edna-finder-use-cache t)
-             (inhibit-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((heading1-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading2-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading3-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (heading4-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"8b6d9820-d943-4622-85c9-4a346e033453"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((nightly-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (lunch-pom   (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (door-pom    (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (dog-pom     (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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>")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"8b6d9820-d943-4622-85c9-4a346e033453"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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)
-             ;; Enable cache
-             (org-edna-finder-use-cache t)
-             (inhibit-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((nightly-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (lunch-pom   (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (door-pom    (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (dog-pom     (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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>")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"630805bb-a864-4cdc-9a6f-0f126e887c66"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((daily-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (lunch-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (door-pom  (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (dog-pom   (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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>")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"cf529a5e-1b0c-40c3-8f85-fe2fc4df0ffd"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((weekly-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (lunch-pom  (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (door-pom   (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (dog-pom    (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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>")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"34d67756-927b-4a21-a62d-7989bd138946"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((shower-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (towels-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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")))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"b1d89bd8-db96-486e-874c-98e2b3a8cbf2"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((monday-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (tuesday-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (wednesday-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (shovel-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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"))))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"7de5af8b-a226-463f-8360-edd88b99462a"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((shovel-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (room-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (vacuum-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (lunch-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (edna-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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"))))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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 ()
-  (cl-letf* (((symbol-function 'current-time) (lambda () org-edna-test-time))
-             (start-heading (org-edna-find-test-heading 
"b79279f7-be3c-45ac-96dc-6e962a5873d4"))
-             ;; Only use the test file in the agenda
-             (org-agenda-files `(,org-edna-test-file))
-             (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-message org-edna-test-inhibit-messages))
-    (org-with-point-at start-heading
-      (save-restriction
-        ;; Only allow operating on the current tree
-        (org-narrow-to-subtree)
-        ;; Show the entire subtree
-        (outline-show-all)
-        (unwind-protect
-            (let* ((shovel-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (room-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (vacuum-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (lunch-pom (progn (org-next-visible-heading 1) 
(point-marker)))
-                   (edna-pom (progn (org-next-visible-heading 1) 
(point-marker))))
-              ;; 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"))))
-          ;; Change the test file back to its original state.
-          (org-edna-test-restore-test-file))))))
+  (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/org-edna-tests.org b/org-edna-tests.org
index 35d5455..2819700 100644
--- a/org-edna-tests.org
+++ b/org-edna-tests.org
@@ -22,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
@@ -312,3 +318,33 @@ DEADLINE: <2000-01-15 Sat +1d>
 :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/org-edna.el b/org-edna.el
index 929d8ff..f804dba 100644
--- a/org-edna.el
+++ b/org-edna.el
@@ -498,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)
 
@@ -2017,6 +2017,37 @@ 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
 
diff --git a/org-edna.info b/org-edna.info
index 691f110..abd1b88 100644
--- a/org-edna.info
+++ b/org-edna.info
@@ -91,6 +91,8 @@ Conditions
 * 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::
 
 
@@ -880,7 +882,7 @@ scheduled!(“+1wkdy”)
 scheduled!(“+1d +wkdy”)
      Same as above
 deadline!(“+1m -wkdy”)
-     Set SCHEDULED up one month, but move backward to find a weekend
+     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”)
@@ -1182,6 +1184,8 @@ means block if any target heading isn’t done.
 * 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::
 
 
@@ -1269,7 +1273,7 @@ to VALUE.
 showered at least three times.
 
 
-File: org-edna.info,  Node: Regexp Search,  Next: Negating Conditions,  Prev: 
Heading Has Property,  Up: Conditions
+File: org-edna.info,  Node: Regexp Search,  Next: Checking Tags,  Prev: 
Heading Has Property,  Up: Conditions
 
 Regexp Search
 -------------
@@ -1280,10 +1284,59 @@ Regexp 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: Negating Conditions,  Prev: Regexp Search,  Up: 
Conditions
+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: Matching Headings,  
Up: Conditions
 
 Negating Conditions
 -------------------
@@ -1782,6 +1835,8 @@ File: org-edna.info,  Node: 10,  Next: 10beta8,  Up: 
Changelog
    • 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
@@ -1898,81 +1953,83 @@ Big release here, with three new features.
 
 Tag Table:
 Node: Top225
-Node: Copying4217
-Node: Introduction5039
-Node: Installation and Setup5987
-Node: Basic Operation6711
-Node: Blockers8562
-Node: Triggers8848
-Node: Syntax9110
-Node: Basic Features9800
-Node: Finders10154
-Node: ancestors11919
-Node: children12513
-Node: descendants12923
-Node: file13445
-Node: first-child14194
-Node: ids14454
-Node: match15115
-Node: next-sibling15753
-Node: next-sibling-wrap16010
-Node: olp16324
-Node: org-file16736
-Node: parent17381
-Node: previous-sibling17579
-Node: previous-sibling-wrap17840
-Node: relatives18119
-Node: rest-of-siblings21845
-Node: rest-of-siblings-wrap22130
-Node: self22479
-Node: siblings22640
-Node: siblings-wrap22877
-Node: Actions23181
-Node: Scheduled/Deadline23944
-Node: TODO State27459
-Node: Archive28184
-Node: Chain Property28504
-Node: Clocking29257
-Node: Property29669
-Node: Priority31842
-Node: Tag32411
-Node: Effort32628
-Node: Getting Help33012
-Node: Advanced Features33457
-Node: Finder Cache33905
-Node: Conditions34944
-Node: Heading is DONE35621
-Node: File Has Headings35827
-Node: Heading TODO State36249
-Node: Lisp Variable Set36543
-Node: Heading Has Property37211
-Node: Regexp Search37957
-Node: Negating Conditions38337
-Node: Consideration38728
-Node: Conditional Forms40912
-Node: Setting the Properties43600
-Node: Extending Edna44684
-Node: Naming Conventions45174
-Node: Finders 145966
-Node: Actions 146328
-Node: Conditions 146787
-Node: Contributing47673
-Node: Bugs48539
-Node: Working with EDE48896
-Node: Compiling Edna49980
-Node: Testing Edna50849
-Node: Before Sending Changes51830
-Node: Developing with Bazaar52517
-Node: Documentation53258
-Node: Changelog53714
-Node: 1053975
-Node: 10beta854377
-Node: 10beta754500
-Node: 10beta654794
-Node: 10beta555070
-Node: 10beta455457
-Node: 10beta355710
-Node: 10beta256149
+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/org-edna.org b/org-edna.org
index 2188f53..89c32aa 100644
--- a/org-edna.org
+++ b/org-edna.org
@@ -699,7 +699,7 @@ Examples:
 - 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
+- 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
 
@@ -1071,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:
@@ -1544,6 +1594,7 @@ making any changes:
 - 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.



reply via email to

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