diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..7e714fd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,37 @@ +name: tests + +on: + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + emacs_version: + - "29.1" + - "30.1" + + steps: + - uses: actions/checkout@v6 + + - name: Set up Emacs + uses: purcell/setup-emacs@master + with: + version: ${{ matrix.emacs_version }} + + - name: Install ripgrep + run: sudo apt-get update && sudo apt-get install -y ripgrep + + - name: Show tool versions + run: | + emacs --version + rg --version + + - name: Run all tests + run: emacs --batch -Q --eval "(setq load-prefer-newer t)" -L . -l tests/run-tests.el diff --git a/grove-core.el b/grove-core.el index f29b71d..4e1fe35 100644 --- a/grove-core.el +++ b/grove-core.el @@ -178,11 +178,21 @@ Returns (:title TITLE :tags TAGS :links LINKS :mtime MTIME)." :links (nreverse links) :mtime mtime))) +(defun grove--cacheable-note-p (file) + "Return non-nil when FILE should be included in the note cache." + (let ((name (file-name-nondirectory file))) + (and (string-suffix-p ".org" name) + (not (string-prefix-p ".#" name)) + (not (and (string-prefix-p "#" name) + (string-suffix-p "#" name))) + (not (string-suffix-p "~" name))))) + (defun grove--refresh-cache () "Refresh the vault cache by scanning `grove-directory'. Only re-parses files whose mtime has changed." (grove--ensure-directory) - (let ((files (directory-files-recursively grove-directory "\\.org\\'")) + (let ((files (seq-filter #'grove--cacheable-note-p + (directory-files-recursively grove-directory "\\.org\\'"))) (seen (make-hash-table :test #'equal))) ;; Update or add entries (dolist (file files) diff --git a/tests/grove-review-test.el b/tests/grove-review-test.el index d388225..35da819 100644 --- a/tests/grove-review-test.el +++ b/tests/grove-review-test.el @@ -35,6 +35,46 @@ '("Project: Alpha"))) (delete-file file)))) +(ert-deftest grove-refresh-cache-ignores-emacs-lockfiles () + (let* ((grove-directory (make-temp-file "grove-vault" t)) + (note (expand-file-name "test-2.org" grove-directory)) + (lockfile (expand-file-name ".#test-2.org" grove-directory)) + (grove--cache (make-hash-table :test #'equal))) + (unwind-protect + (progn + (with-temp-file note + (insert "#+title: Real note\n")) + ;; Emacs lockfiles are symlinks whose names can still match the + ;; recursive .org scan, they should never be parsed as notes. + (make-symbolic-link note lockfile) + (delete-file note) + (grove--refresh-cache) + (should-not (gethash lockfile grove--cache)) + (should (= (hash-table-count grove--cache) 0))) + (delete-directory grove-directory t)))) + +(ert-deftest grove-refresh-cache-ignores-emacs-autosave-and-backup-files () + (let* ((grove-directory (make-temp-file "grove-vault" t)) + (autosave (expand-file-name "#draft.org#" grove-directory)) + (backup (expand-file-name "draft.org~" grove-directory)) + (note (expand-file-name "draft.org" grove-directory)) + (grove--cache (make-hash-table :test #'equal))) + (unwind-protect + (progn + (with-temp-file autosave + (insert "#+title: Autosave\n")) + (with-temp-file backup + (insert "#+title: Backup\n")) + (with-temp-file note + (insert "#+title: Real draft\n")) + (grove--refresh-cache) + (should-not (gethash autosave grove--cache)) + (should-not (gethash backup grove--cache)) + (should (= (hash-table-count grove--cache) 1)) + (should (equal (plist-get (gethash note grove--cache) :title) + "Real draft"))) + (delete-directory grove-directory t)))) + (ert-deftest grove-link-fontify-allows-colons-in-note-titles () (with-temp-buffer (insert "[[Project: Alpha]] [[https://example.com]]") diff --git a/tests/run-tests.el b/tests/run-tests.el new file mode 100644 index 0000000..7af3e35 --- /dev/null +++ b/tests/run-tests.el @@ -0,0 +1,20 @@ +;;; run-tests.el --- Load and run all Grove tests -*- lexical-binding: t -*- + +;; Copyright 2026 Guilherme Thomazi Bonicontro + +;;; Commentary: + +;; Loads every *-test.el file in this directory, then runs the full ERT suite. + +;;; Code: + +(require 'ert) + +(let* ((this-file (or load-file-name buffer-file-name)) + (tests-directory (file-name-directory this-file))) + (dolist (file (directory-files tests-directory t "-test\\.el\\'")) + (load file nil 'nomessage))) + +(ert-run-tests-batch-and-exit) + +;;; run-tests.el ends here