diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-02 01:01:38 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-02 01:01:38 -0400 |
| commit | 25e74fcb57ae467a39ecabe41568a986db08ebe4 (patch) | |
| tree | 38cf1aa6201e4d2fc59078f088bdda3e60d145ed /tests/test-org-spec-links.el | |
| parent | 6252ec01350bd211e043bb84b31498cc00a11046 (diff) | |
| download | dotemacs-25e74fcb57ae467a39ecabe41568a986db08ebe4.tar.gz dotemacs-25e74fcb57ae467a39ecabe41568a986db08ebe4.zip | |
feat(org): resolve org-id links into project spec docs
The docs-lifecycle convention gives every formal spec under a project's docs/specs/ an :ID: and links cross-project with [[id:...]], but org-id-locations only indexes agenda files and visited files, so a fresh spec's id never resolved on click. org-spec-links.el enumerates every project's docs/specs/*.org into org-id-extra-files once org-id loads (a literal file list; org-id doesn't glob), and cj/org-id-refresh-spec-locations re-scans and updates org-id-locations for immediacy after new specs land. Verified live against a known cross-project spec id.
Diffstat (limited to 'tests/test-org-spec-links.el')
| -rw-r--r-- | tests/test-org-spec-links.el | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/tests/test-org-spec-links.el b/tests/test-org-spec-links.el new file mode 100644 index 00000000..6359689f --- /dev/null +++ b/tests/test-org-spec-links.el @@ -0,0 +1,79 @@ +;;; test-org-spec-links.el --- docs/specs org-id resolution -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the docs/specs spec-file scanner behind org-id link +;; resolution (org-spec-links.el). Projects are directories carrying +;; .ai/protocols.org; each contributes its docs/specs/*.org files. The +;; scanner takes explicit base dirs so the tests run against a sandbox. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) + +(require 'org-spec-links) + +;; org-id may never load in batch; declare its var special so the +;; let-binding below is dynamic, not a silent lexical shadow. +(defvar org-id-extra-files) + +(defun test-org-spec-links--make-sandbox () + "Build a sandbox: two projects, one with specs, one without; one non-project." + (let ((base (make-temp-file "spec-links-" t))) + ;; p1: a project with two spec files + (make-directory (expand-file-name "p1/.ai" base) t) + (write-region "" nil (expand-file-name "p1/.ai/protocols.org" base)) + (make-directory (expand-file-name "p1/docs/specs" base) t) + (write-region "" nil (expand-file-name "p1/docs/specs/a-spec.org" base)) + (write-region "" nil (expand-file-name "p1/docs/specs/b-spec.org" base)) + ;; p2: a project with no docs/specs + (make-directory (expand-file-name "p2/.ai" base) t) + (write-region "" nil (expand-file-name "p2/.ai/protocols.org" base)) + ;; p3: not a project (no protocols.org) but has docs/specs + (make-directory (expand-file-name "p3/docs/specs" base) t) + (write-region "" nil (expand-file-name "p3/docs/specs/c-spec.org" base)) + base)) + +(ert-deftest test-org-spec-links-scanner-finds-project-specs () + "Normal: spec files from protocols-carrying projects only, absolute paths." + (let ((base (test-org-spec-links--make-sandbox))) + (unwind-protect + (let ((files (cj/--org-spec-files (list base)))) + (should (= (length files) 2)) + (should (cl-every #'file-name-absolute-p files)) + (should (seq-find (lambda (f) (string-suffix-p "p1/docs/specs/a-spec.org" f)) files)) + (should (seq-find (lambda (f) (string-suffix-p "p1/docs/specs/b-spec.org" f)) files)) + (should-not (seq-find (lambda (f) (string-match-p "p3" f)) files))) + (delete-directory base t)))) + +(ert-deftest test-org-spec-links-scanner-empty-base () + "Boundary: a base dir with no projects yields nil." + (let ((base (make-temp-file "spec-links-empty-" t))) + (unwind-protect + (should-not (cj/--org-spec-files (list base))) + (delete-directory base t)))) + +(ert-deftest test-org-spec-links-scanner-missing-base () + "Error: a non-existent base dir is skipped without signaling." + (should-not (cj/--org-spec-files '("/nonexistent/base/dir")))) + +(ert-deftest test-org-spec-links-refresh-sets-extra-files () + "Normal: the refresh command points org-id-extra-files at the scan result." + (let* ((base (test-org-spec-links--make-sandbox)) + (org-id-extra-files nil) + ;; the default scan appends user-emacs-directory; point it into + ;; the sandbox so the real config's specs don't leak in + (user-emacs-directory (expand-file-name "p2/" base))) + (unwind-protect + (cl-letf (((symbol-function 'cj/--org-spec-project-base-dirs) + (lambda () (list base))) + ;; org-id may be absent in batch; the update step is mocked. + ((symbol-function 'org-id-update-id-locations) + (lambda (&rest _) nil))) + (cj/org-id-refresh-spec-locations) + (should (= (length org-id-extra-files) 2))) + (delete-directory base t)))) + +(provide 'test-org-spec-links) +;;; test-org-spec-links.el ends here |
