From 6fa688a65f9b185220ce3ecbefce7103b4168f7d Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 14 May 2026 01:34:18 -0500 Subject: test(org-roam-config): cover tag predicate, find-node, finalize hook, insert-immediate Sibling tests covered the slug helper, link-description, demote-subtree, format-roam-node, and the copy-todo-to-today hook. This batch covers the tag/find/insert family: - `cj/org-roam-filter-by-tag`: returns a predicate; keeps matching nodes, rejects non-matching. - `cj/org-roam-list-notes-by-tag`: returns file paths for matches, empty list when no matches. - `cj/org-roam-add-node-to-agenda-files-finalize-hook`: removes itself, adds the captured buffer's file to `org-agenda-files` when not aborted, skips on abort. - `cj/org-roam-find-node`: calls `org-roam-node-find` with a filter predicate + templates. - `cj/org-roam-find-node-topic` / `-recipe`: delegate with the right tag + key (+ subdir). - `cj/org-roam-node-insert-immediate`: appends `:immediate-finish t` to the first capture template before calling `org-roam-node-insert`. Top-level `defvar`s for `org-note-abort`, `org-agenda-files`, `org-roam-capture-templates`, and `org-capture-after-finalize-hook` make the let-bindings reach the dynamic vars under lexical scope. --- tests/test-org-roam-config-tag-and-find.el | 167 +++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/test-org-roam-config-tag-and-find.el diff --git a/tests/test-org-roam-config-tag-and-find.el b/tests/test-org-roam-config-tag-and-find.el new file mode 100644 index 00000000..f98d8af0 --- /dev/null +++ b/tests/test-org-roam-config-tag-and-find.el @@ -0,0 +1,167 @@ +;;; test-org-roam-config-tag-and-find.el --- Tests for org-roam tag predicate + find wrappers -*- lexical-binding: t; -*- + +;;; Commentary: +;; Sibling tests cover the slug helper, the link-description helper, +;; the demote-subtree pure function, the format-roam-node helper, and +;; the copy-todo-to-today hook. This file fills in the tag-filter +;; predicate, the list-notes-by-tag wrapper, the find-node command + +;; its convenience entries, the insert-immediate wrapper, and the +;; capture-finalize hook. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'org-roam-config) + +;; Top-level defvars so `let' bindings reach the dynamic variable under +;; lexical scope (the hooks reference these globals). +(defvar org-note-abort nil + "Dynamic stand-in for `org-note-abort' (org-capture isn't loaded here).") +(defvar org-roam-capture-templates nil + "Dynamic stand-in for `org-roam-capture-templates'.") +(defvar org-agenda-files nil + "Dynamic stand-in for `org-agenda-files' (org-agenda not loaded here).") +(defvar org-capture-after-finalize-hook nil + "Dynamic stand-in for `org-capture-after-finalize-hook'.") + +;;; cj/org-roam-filter-by-tag + +(ert-deftest test-org-roam-filter-by-tag-returns-predicate () + "Normal: filter returns a function." + (should (functionp (cj/org-roam-filter-by-tag "Topic")))) + +(ert-deftest test-org-roam-filter-by-tag-keeps-nodes-with-matching-tag () + "Normal: predicate returns non-nil for a node whose tags contain TAG-NAME." + (cl-letf (((symbol-function 'org-roam-node-tags) + (lambda (n) (plist-get n :tags)))) + (let ((pred (cj/org-roam-filter-by-tag "Topic"))) + (should (funcall pred (list :tags '("Topic" "Tech"))))))) + +(ert-deftest test-org-roam-filter-by-tag-rejects-nodes-without-tag () + "Boundary: predicate returns nil when the tag isn't in the node's tag list." + (cl-letf (((symbol-function 'org-roam-node-tags) + (lambda (n) (plist-get n :tags)))) + (let ((pred (cj/org-roam-filter-by-tag "Topic"))) + (should-not (funcall pred (list :tags '("Recipe"))))))) + +;;; cj/org-roam-list-notes-by-tag + +(ert-deftest test-org-roam-list-notes-by-tag-returns-file-paths-for-matches () + "Normal: filters the node list by tag and returns each node's file path." + (cl-letf (((symbol-function 'org-roam-node-list) + (lambda () (list (list :tags '("Topic") :file "/p/a.org") + (list :tags '("Recipe") :file "/p/b.org") + (list :tags '("Topic" "Tech") :file "/p/c.org")))) + ((symbol-function 'org-roam-node-tags) + (lambda (n) (plist-get n :tags))) + ((symbol-function 'org-roam-node-file) + (lambda (n) (plist-get n :file)))) + (should (equal (cj/org-roam-list-notes-by-tag "Topic") + '("/p/a.org" "/p/c.org"))))) + +(ert-deftest test-org-roam-list-notes-by-tag-empty-when-no-matches () + "Boundary: a tag with no matches returns an empty list." + (cl-letf (((symbol-function 'org-roam-node-list) + (lambda () (list (list :tags '("Recipe") :file "/p/a.org")))) + ((symbol-function 'org-roam-node-tags) + (lambda (n) (plist-get n :tags))) + ((symbol-function 'org-roam-node-file) + (lambda (n) (plist-get n :file)))) + (should (null (cj/org-roam-list-notes-by-tag "Topic"))))) + +;;; cj/org-roam-add-node-to-agenda-files-finalize-hook + +(ert-deftest test-org-roam-finalize-hook-removes-itself () + "Normal: the hook removes itself after running, regardless of abort." + (let ((org-note-abort nil) + (org-agenda-files nil) + (org-capture-after-finalize-hook + (list #'cj/org-roam-add-node-to-agenda-files-finalize-hook))) + (cl-letf (((symbol-function 'org-capture-get) + (lambda (_) (current-buffer)))) + (with-temp-buffer + (setq buffer-file-name "/tmp/captured.org") + (cj/org-roam-add-node-to-agenda-files-finalize-hook) + (setq buffer-file-name nil))) + (should-not (memq #'cj/org-roam-add-node-to-agenda-files-finalize-hook + org-capture-after-finalize-hook)))) + +(ert-deftest test-org-roam-finalize-hook-adds-file-when-not-aborted () + "Normal: when the capture wasn't aborted, the new file lands in +`org-agenda-files'." + (let ((org-note-abort nil) + (org-agenda-files nil) + (org-capture-after-finalize-hook + (list #'cj/org-roam-add-node-to-agenda-files-finalize-hook))) + (cl-letf (((symbol-function 'org-capture-get) + (lambda (_) (current-buffer)))) + (with-temp-buffer + (setq buffer-file-name "/tmp/captured-a.org") + (cj/org-roam-add-node-to-agenda-files-finalize-hook) + (setq buffer-file-name nil))) + (should (member "/tmp/captured-a.org" org-agenda-files)))) + +(ert-deftest test-org-roam-finalize-hook-skips-on-abort () + "Boundary: when the capture was aborted, the file is NOT added." + (let ((org-note-abort t) + (org-agenda-files nil) + (org-capture-after-finalize-hook + (list #'cj/org-roam-add-node-to-agenda-files-finalize-hook))) + (cj/org-roam-add-node-to-agenda-files-finalize-hook) + (should-not org-agenda-files))) + +;;; cj/org-roam-find-node + the two convenience wrappers + +(ert-deftest test-org-roam-find-node-passes-templates-and-tag-filter () + "Normal: find-node calls org-roam-node-find with a filter + templates." + (let ((called-with nil)) + (cl-letf (((symbol-function 'org-roam-node-find) + (lambda (&rest args) (setq called-with args)))) + (cj/org-roam-find-node "Topic" "t" "/tmp/template.org")) + (should called-with) + ;; Third positional arg is the filter predicate. + (should (functionp (nth 2 called-with))) + ;; Templates keyword arg present. + (should (memq :templates called-with)))) + +(ert-deftest test-org-roam-find-node-topic-delegates-to-find-node () + "Normal: `find-node-topic' calls `find-node' with Topic tag + template-key 't'." + (let ((args nil)) + (cl-letf (((symbol-function 'cj/org-roam-find-node) + (lambda (&rest a) (setq args a)))) + (cj/org-roam-find-node-topic)) + (should (equal (car args) "Topic")) + (should (equal (cadr args) "t")))) + +(ert-deftest test-org-roam-find-node-recipe-delegates-to-find-node () + "Normal: `find-node-recipe' uses Recipe tag, 'r' key, recipes/ subdir." + (let ((args nil)) + (cl-letf (((symbol-function 'cj/org-roam-find-node) + (lambda (&rest a) (setq args a)))) + (cj/org-roam-find-node-recipe)) + (should (equal (car args) "Recipe")) + (should (equal (cadr args) "r")) + ;; The 4th arg is the subdir + (should (equal (nth 3 args) "recipes/")))) + +;;; cj/org-roam-node-insert-immediate + +(ert-deftest test-org-roam-node-insert-immediate-rebinds-templates-and-calls-insert () + "Normal: the wrapper appends :immediate-finish t to the first capture +template and calls org-roam-node-insert." + (let ((seen-templates nil) + (called nil)) + (cl-letf (((symbol-function 'org-roam-node-insert) + (lambda (&rest _) + (setq called t + seen-templates org-roam-capture-templates)))) + (let ((org-roam-capture-templates '(("d" "default" plain "%?")))) + (cj/org-roam-node-insert-immediate nil))) + (should called) + (should (member :immediate-finish (car seen-templates))))) + +(provide 'test-org-roam-config-tag-and-find) +;;; test-org-roam-config-tag-and-find.el ends here -- cgit v1.2.3