summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-custom-line-paragraph-duplicate-line-or-region.el12
-rw-r--r--tests/test-dwim-shell-security.el341
-rw-r--r--tests/test-fs--mode-to-permissions.el36
-rw-r--r--tests/test-fs-filter-by-extension.el68
-rw-r--r--tests/test-fs-format-file-info.el40
-rw-r--r--tests/test-fs-get-file-info.el75
-rw-r--r--tests/test-fs-list-directory-recursive-extra.el106
-rw-r--r--tests/test-fs-list-directory-recursive.el71
-rw-r--r--tests/test-fs-validate-path.el45
-rw-r--r--tests/test-integration-transcription.el5
-rw-r--r--tests/test-lorem-optimum-benchmark.el12
-rw-r--r--tests/test-org-agenda-build-list.el294
-rw-r--r--tests/test-org-contacts-capture-finalize.el65
-rw-r--r--tests/test-org-gcal-mock.el112
-rw-r--r--tests/test-testutil-filesystem-directory-entries.el317
-rw-r--r--tests/test-transcription-audio-file.el5
-rw-r--r--tests/test-transcription-counter.el5
-rw-r--r--tests/test-transcription-duration.el5
-rw-r--r--tests/test-transcription-log-cleanup.el5
-rw-r--r--tests/test-transcription-paths.el5
-rw-r--r--tests/test-undead-buffers.el249
21 files changed, 412 insertions, 1461 deletions
diff --git a/tests/test-custom-line-paragraph-duplicate-line-or-region.el b/tests/test-custom-line-paragraph-duplicate-line-or-region.el
index 22f19c16..bd82e00f 100644
--- a/tests/test-custom-line-paragraph-duplicate-line-or-region.el
+++ b/tests/test-custom-line-paragraph-duplicate-line-or-region.el
@@ -435,18 +435,6 @@
(kill-buffer (current-buffer)))
(test-duplicate-line-or-region-teardown)))
-(ert-deftest test-duplicate-line-or-region-comment-without-syntax ()
- "Should error when comment requested but no comment syntax defined."
- (test-duplicate-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- ;; Fundamental mode has no comment syntax
- (fundamental-mode)
- (insert "line")
- (goto-char (point-min))
- ;; Should error when trying to comment without syntax
- (should-error (cj/duplicate-line-or-region t)))
- (test-duplicate-line-or-region-teardown)))
(ert-deftest test-duplicate-line-or-region-special-characters ()
"Should handle control characters."
diff --git a/tests/test-dwim-shell-security.el b/tests/test-dwim-shell-security.el
deleted file mode 100644
index 0151a7c7..00000000
--- a/tests/test-dwim-shell-security.el
+++ /dev/null
@@ -1,341 +0,0 @@
-;;; test-dwim-shell-security.el --- ERT tests for dwim-shell-config security functions -*- lexical-binding: t; -*-
-
-;; Author: Claude Code and cjennings
-;; Keywords: tests, dwim-shell, security
-
-;;; Commentary:
-;; ERT tests for security-related dwim-shell-config.el functions.
-;; Tests are organized into normal, boundary, and error cases.
-;;
-;; These tests verify that password-protected operations:
-;; - Do not expose passwords in process lists or command output
-;; - Use temporary files with restrictive permissions (mode 600)
-;; - Clean up temporary files after use (even on error)
-;; - Properly handle edge cases and errors
-
-;;; Code:
-
-(require 'ert)
-(require 'dwim-shell-config)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-dwim-shell-security-setup ()
- "Set up test environment for dwim-shell-security tests."
- (cj/create-test-base-dir)
- ;; Create test PDF file
- (setq test-pdf-file (expand-file-name "test.pdf" cj/test-base-dir))
- ;; Create minimal valid PDF (this is a minimal PDF structure)
- (with-temp-file test-pdf-file
- (insert "%PDF-1.4\n")
- (insert "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")
- (insert "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n")
- (insert "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n")
- (insert "xref\n0 4\n")
- (insert "0000000000 65535 f\n")
- (insert "0000000009 00000 n\n")
- (insert "0000000058 00000 n\n")
- (insert "0000000115 00000 n\n")
- (insert "trailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n203\n%%EOF\n"))
- ;; Create test files for archive operations
- (setq test-file-1 (expand-file-name "file1.txt" cj/test-base-dir))
- (setq test-file-2 (expand-file-name "file2.txt" cj/test-base-dir))
- (with-temp-file test-file-1 (insert "Test content 1"))
- (with-temp-file test-file-2 (insert "Test content 2")))
-
-(defun test-dwim-shell-security-teardown ()
- "Clean up test environment after dwim-shell-security tests."
- ;; Clean up test directory
- (cj/delete-test-base-dir))
-
-;;; Helper Functions
-
-(defun test-dwim-check-temp-file-cleanup (pattern)
- "Check that no temporary files matching PATTERN remain after operation."
- (let ((temp-files (directory-files temporary-file-directory nil pattern)))
- (should (null temp-files))))
-
-(defun test-dwim-check-file-permissions (file expected-mode)
- "Check that FILE has EXPECTED-MODE permissions."
- (when (file-exists-p file)
- (should (equal (file-modes file) expected-mode))))
-
-;;; Normal Cases - PDF Password Protect
-
-(ert-deftest test-dwim-pdf-password-protect-creates-temp-file-normal ()
- "Normal: PDF password protect creates temporary file with secure permissions."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let* ((captured-temp-file nil)
- (original-make-temp-file (symbol-function 'make-temp-file)))
- ;; Wrap make-temp-file to capture the temp file path
- (cl-letf (((symbol-function 'make-temp-file)
- (lambda (&rest args)
- (setq captured-temp-file (apply original-make-temp-file args))
- captured-temp-file))
- ;; Mock read-passwd to avoid interactive prompts
- ((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ;; Mock dwim-shell-command-on-marked-files to check behavior
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest _args)
- ;; Verify temp file exists with correct permissions during execution
- (should (file-exists-p captured-temp-file))
- (test-dwim-check-file-permissions captured-temp-file #o600)
- ;; Verify password is in temp file, not in command
- (should (string-match-p captured-temp-file command))
- (should-not (string-match-p "test-password" command)))))
- (cj/dwim-shell-commands-pdf-password-protect)
- ;; Verify temp file is cleaned up after operation
- (should-not (file-exists-p captured-temp-file))))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-pdf-password-protect-no-password-in-command-normal ()
- "Normal: Password does not appear in shell command string."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let ((test-password "SuperSecret123!"))
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) test-password))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest _args)
- ;; Password should NOT appear in command
- (should-not (string-match-p test-password command))
- ;; Command should reference password file
- (should (string-match-p "--password-file=" command)))))
- (cj/dwim-shell-commands-pdf-password-protect)))
- (test-dwim-shell-security-teardown)))
-
-;;; Normal Cases - PDF Password Unprotect
-
-(ert-deftest test-dwim-pdf-password-unprotect-creates-temp-file-normal ()
- "Normal: PDF password unprotect creates temporary file with secure permissions."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let* ((captured-temp-file nil)
- (original-make-temp-file (symbol-function 'make-temp-file)))
- (cl-letf (((symbol-function 'make-temp-file)
- (lambda (&rest args)
- (setq captured-temp-file (apply original-make-temp-file args))
- captured-temp-file))
- ((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest _args)
- (should (file-exists-p captured-temp-file))
- (test-dwim-check-file-permissions captured-temp-file #o600)
- (should (string-match-p captured-temp-file command))
- (should-not (string-match-p "test-password" command)))))
- (cj/dwim-shell-commands-pdf-password-unprotect)
- (should-not (file-exists-p captured-temp-file))))
- (test-dwim-shell-security-teardown)))
-
-;;; Normal Cases - Create Encrypted Archive
-
-(ert-deftest test-dwim-create-encrypted-zip-uses-7z-normal ()
- "Normal: Create encrypted archive uses 7z, not zip."
- (skip-unless (executable-find "7z"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ((symbol-function 'read-string)
- (lambda (_prompt &optional _default) "test-archive"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest args)
- ;; Should use 7z, not zip
- (should (string-match-p "7z a" command))
- (should-not (string-match-p "zip -" command))
- ;; Should use AES encryption
- (should (string-match-p "-mhe=on" command))
- ;; Verify utils parameter is 7z
- (should (equal (plist-get args :utils) "7z")))))
- (cj/dwim-shell-commands-create-encrypted-zip))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-create-encrypted-zip-no-password-in-command-normal ()
- "Normal: Password does not appear in shell command string for archive creation."
- (skip-unless (executable-find "7z"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let ((test-password "VerySecret456!"))
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) test-password))
- ((symbol-function 'read-string)
- (lambda (_prompt &optional _default) "test-archive"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest _args)
- ;; Password should NOT appear directly in command
- (should-not (string-match-p test-password command))
- ;; Should use cat to read from temp file
- (should (string-match-p "cat" command)))))
- (cj/dwim-shell-commands-create-encrypted-zip)))
- (test-dwim-shell-security-teardown)))
-
-;;; Normal Cases - Remove Archive Encryption
-
-(ert-deftest test-dwim-remove-zip-encryption-uses-7z-normal ()
- "Normal: Remove archive encryption uses 7z for both extract and create."
- (skip-unless (executable-find "7z"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest args)
- ;; Should use 7z for both extract and archive
- (should (string-match-p "7z x" command))
- (should (string-match-p "7z a" command))
- ;; Verify utils parameter is 7z
- (should (equal (plist-get args :utils) "7z")))))
- (cj/dwim-shell-commands-remove-zip-encryption))
- (test-dwim-shell-security-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-dwim-pdf-password-empty-password-boundary ()
- "Boundary: Empty password is accepted (though qpdf may reject it)."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let ((command-executed nil))
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) ""))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description _command &rest _args)
- (setq command-executed t))))
- (cj/dwim-shell-commands-pdf-password-protect)
- ;; Function should accept empty password (tool may reject later)
- (should command-executed)))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-pdf-password-special-characters-boundary ()
- "Boundary: Password with special characters is properly handled."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let ((special-password "p@$$w0rd!#%^&*()"))
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) special-password))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description command &rest _args)
- ;; Special characters should not appear in command
- (should-not (string-match-p (regexp-quote special-password) command)))))
- (cj/dwim-shell-commands-pdf-password-protect)))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-archive-very-long-password-boundary ()
- "Boundary: Very long password (1000+ chars) is properly handled."
- (skip-unless (executable-find "7z"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let ((long-password (make-string 1000 ?x))
- (captured-temp-file nil))
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) long-password))
- ((symbol-function 'read-string)
- (lambda (_prompt &optional _default) "test"))
- ((symbol-function 'make-temp-file)
- (lambda (&rest args)
- (setq captured-temp-file (apply (symbol-function 'make-temp-file) args))
- captured-temp-file))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description _command &rest _args)
- ;; Verify password was written to temp file
- (with-temp-buffer
- (insert-file-contents captured-temp-file)
- (should (equal (buffer-string) long-password))))))
- (cj/dwim-shell-commands-create-encrypted-zip)))
- (test-dwim-shell-security-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-dwim-pdf-password-temp-file-cleanup-on-error-error ()
- "Error: Temporary file is cleaned up even when command fails."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let* ((captured-temp-file nil)
- (original-make-temp-file (symbol-function 'make-temp-file)))
- (cl-letf (((symbol-function 'make-temp-file)
- (lambda (&rest args)
- (setq captured-temp-file (apply original-make-temp-file args))
- captured-temp-file))
- ((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description _command &rest _args)
- ;; Simulate command failure
- (error "Command failed"))))
- ;; Should error, but still clean up temp file
- (should-error (cj/dwim-shell-commands-pdf-password-protect))
- ;; Temp file should be cleaned up despite error
- (should-not (file-exists-p captured-temp-file))))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-archive-temp-file-cleanup-on-error-error ()
- "Error: Archive temp file cleaned up even when 7z command fails."
- (skip-unless (executable-find "7z"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (let* ((captured-temp-file nil)
- (original-make-temp-file (symbol-function 'make-temp-file)))
- (cl-letf (((symbol-function 'make-temp-file)
- (lambda (&rest args)
- (setq captured-temp-file (apply original-make-temp-file args))
- captured-temp-file))
- ((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ((symbol-function 'read-string)
- (lambda (_prompt &optional _default) "test"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (_description _command &rest _args)
- (error "7z command failed"))))
- (should-error (cj/dwim-shell-commands-create-encrypted-zip))
- (should-not (file-exists-p captured-temp-file))))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-pdf-password-temp-file-write-error-error ()
- "Error: Error when unable to write to temporary file."
- (skip-unless (executable-find "qpdf"))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) "test-password"))
- ;; Mock make-temp-file to return a path that can't be written
- ((symbol-function 'make-temp-file)
- (lambda (&rest _args) "/nonexistent/path/temp-file")))
- ;; Should error when trying to write to non-existent path
- (should-error (cj/dwim-shell-commands-pdf-password-protect)))
- (test-dwim-shell-security-teardown)))
-
-(ert-deftest test-dwim-multiple-temp-file-cleanup-error ()
- "Error: Multiple operations don't leave temp files behind."
- (skip-unless (and (executable-find "qpdf") (executable-find "7z")))
- (test-dwim-shell-security-setup)
- (unwind-protect
- (progn
- ;; Track temp files before operations
- (let ((initial-temp-files (directory-files temporary-file-directory nil "^qpdf-pass-\\|^7z-pass-")))
- (cl-letf (((symbol-function 'read-passwd)
- (lambda (_prompt) "password"))
- ((symbol-function 'read-string)
- (lambda (_prompt &optional _default) "archive"))
- ((symbol-function 'dwim-shell-command-on-marked-files)
- (lambda (&rest _args) nil)))
- ;; Run multiple operations
- (cj/dwim-shell-commands-pdf-password-protect)
- (cj/dwim-shell-commands-pdf-password-unprotect)
- (cj/dwim-shell-commands-create-encrypted-zip)
- (cj/dwim-shell-commands-remove-zip-encryption))
- ;; Check no new temp files remain
- (let ((final-temp-files (directory-files temporary-file-directory nil "^qpdf-pass-\\|^7z-pass-")))
- (should (equal (length final-temp-files) (length initial-temp-files))))))
- (test-dwim-shell-security-teardown)))
-
-(provide 'test-dwim-shell-security)
-;;; test-dwim-shell-security.el ends here
diff --git a/tests/test-fs--mode-to-permissions.el b/tests/test-fs--mode-to-permissions.el
deleted file mode 100644
index 3d27ac08..00000000
--- a/tests/test-fs--mode-to-permissions.el
+++ /dev/null
@@ -1,36 +0,0 @@
-;;; test-tool-library-fs--mode-to-permissions.el --- ERT tests for cj/fs--mode-to-permissions -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; ERT tests for the cj/fs--mode-to-permissions function from tool-filesystem-library.el.
-;; Place this file in ~/.emacs.d/tests/ and load it to run tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'tool-filesystem-library)
-
-(ert-deftest test-cj/fs--mode-to-permissions-normal-directory ()
- "Normal: directory permissions string."
- (should (string-prefix-p "d"
- (cj/fs--mode-to-permissions #o40755))))
-
-(ert-deftest test-cj/fs--mode-to-permissions-normal-regular-file ()
- "Normal: regular file permissions string."
- (should (string-prefix-p "-"
- (cj/fs--mode-to-permissions #o100644))))
-
-(ert-deftest test-cj/fs--mode-to-permissions-boundary-zero ()
- "Boundary: no permissions."
- (should (string= "----------"
- (cj/fs--mode-to-permissions 0))))
-
-(ert-deftest test-cj/fs--mode-to-permissions-boundary-full ()
- "Boundary: full permissions string."
- (should (string= "-rwxrwxrwx"
- (cj/fs--mode-to-permissions #o777))))
-
-(provide 'test-tool-library-fs--mode-to-permissions)
-;;; test-tool-library-fs--mode-to-permissions.el ends here
diff --git a/tests/test-fs-filter-by-extension.el b/tests/test-fs-filter-by-extension.el
deleted file mode 100644
index 254cf47c..00000000
--- a/tests/test-fs-filter-by-extension.el
+++ /dev/null
@@ -1,68 +0,0 @@
-;;; test-tool-library-fs-filter-by-extension.el --- ERT tests for cj/fs-filter-by-extension -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; ERT tests for the cj/fs-filter-by-extension function from tool-filesystem-library.el.
-;; Place this file in ~/.emacs.d/tests/ and load it to run tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'f)
-(require 'tool-filesystem-library)
-
-(defvar cj/fs-test--temp-dir nil "Temporary test directory for fs-filter-by-extension tests.")
-
-(defun cj/fs-test--setup ()
- "Set up temp directory for fs-filter-by-extension tests."
- (setq cj/fs-test--temp-dir (make-temp-file "fs-lib-test" t))
- ;; Create files
- (with-temp-buffer (insert "Org file") (write-file (f-join cj/fs-test--temp-dir "file1.org")))
- (with-temp-buffer (insert "Txt file") (write-file (f-join cj/fs-test--temp-dir "file2.txt")))
- (make-directory (f-join cj/fs-test--temp-dir "subdir") t))
-
-(defun cj/fs-test--teardown ()
- "Clean up temp directory for fs-filter-by-extension tests."
- (when (and cj/fs-test--temp-dir (file-directory-p cj/fs-test--temp-dir))
- (delete-directory cj/fs-test--temp-dir t))
- (setq cj/fs-test--temp-dir nil))
-
-(ert-deftest test-cj/fs-filter-by-extension-normal-match ()
- "Normal: match single extension in list."
- (cj/fs-test--setup)
- (unwind-protect
- (let* ((infos (mapcar #'cj/fs-get-file-info (cj/fs-directory-entries cj/fs-test--temp-dir)))
- (filtered (cj/fs-filter-by-extension infos "org")))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.org")) filtered))
- (should-not (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.txt")) filtered)))
- (cj/fs-test--teardown)))
-
-(ert-deftest test-cj/fs-filter-by-extension-normal-no-filter ()
- "Normal: no extension filter returns full list."
- (cj/fs-test--setup)
- (unwind-protect
- (let* ((infos (mapcar #'cj/fs-get-file-info (cj/fs-directory-entries cj/fs-test--temp-dir)))
- (filtered (cj/fs-filter-by-extension infos nil)))
- (should (= (length filtered) (length infos))))
- (cj/fs-test--teardown)))
-
-(ert-deftest test-cj/fs-filter-by-extension-error-empty-list ()
- "Error: empty file info list handled."
- (should (equal (cj/fs-filter-by-extension nil "org") nil)))
-
-(ert-deftest test-cj/fs-filter-by-extension-boundary-mixed-files ()
- "Boundary: mixed extensions and directories handled."
- (cj/fs-test--setup)
- (unwind-protect
- (let* ((entries (cj/fs-directory-entries cj/fs-test--temp-dir))
- (infos (mapcar #'cj/fs-get-file-info entries))
- (filtered (cj/fs-filter-by-extension infos "org")))
- (should (cl-some (lambda (fi) (plist-get fi :directory)) filtered))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.org")) filtered))
- (should-not (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.txt")) filtered)))
- (cj/fs-test--teardown)))
-
-(provide 'test-tool-library-fs-filter-by-extension)
-;;; test-tool-library-fs-filter-by-extension.el ends here
diff --git a/tests/test-fs-format-file-info.el b/tests/test-fs-format-file-info.el
deleted file mode 100644
index b5a82f4b..00000000
--- a/tests/test-fs-format-file-info.el
+++ /dev/null
@@ -1,40 +0,0 @@
-;;; test-tool-library-fs-format-file-info.el --- ERT tests for cj/fs-format-file-info -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; ERT tests for the cj/fs-format-file-info function from tool-filesystem-library.el.
-;; Place this file in ~/.emacs.d/tests/ and load it to run tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'f)
-(require 'tool-filesystem-library)
-
-(ert-deftest test-cj/fs-format-file-info-normal-typical ()
- "Normal: format typical file info plist."
- (let ((info (list :permissions "-rw-r--r--"
- :executable nil
- :size 1024
- :last-modified (current-time)
- :path "~/test-file.txt")))
- (should (string-match-p "test-file.txt" (cj/fs-format-file-info info "~")))))
-
-(ert-deftest test-cj/fs-format-file-info-error-missing-keys ()
- "Error: format with missing keys handled."
- (let ((info (list)))
- (should (cj/fs-format-file-info info "~"))))
-
-(ert-deftest test-cj/fs-format-file-info-boundary-zero-size ()
- "Boundary: format with zero size."
- (let ((info (list :permissions "-rw-r--r--"
- :executable nil
- :size 0
- :last-modified (current-time)
- :path "~/empty-file.txt")))
- (should (string-match-p "empty-file.txt" (cj/fs-format-file-info info "~")))))
-
-(provide 'test-tool-library-fs-format-file-info)
-;;; test-tool-library-fs-format-file-info.el ends here
diff --git a/tests/test-fs-get-file-info.el b/tests/test-fs-get-file-info.el
deleted file mode 100644
index 9e7e337c..00000000
--- a/tests/test-fs-get-file-info.el
+++ /dev/null
@@ -1,75 +0,0 @@
-;;; test-tool-library-fs-get-file-info.el --- ERT tests for cj/fs-get-file-info -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; ERT tests for the cj/fs-get-file-info function from tool-filesystem-library.el.
-;; Place this file in ~/.emacs.d/tests/ and load it to run tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'f)
-(require 'tool-filesystem-library)
-
-(defvar cj/fs-test--temp-dir nil "Temporary test directory for fs-get-file-info tests.")
-
-(defun cj/fs-test--setup ()
- "Setup temporary directory for fs-get-file-info tests."
- (setq cj/fs-test--temp-dir (make-temp-file "fs-lib-test" t))
- ;; Create test files and directories
- (make-directory (f-join cj/fs-test--temp-dir "subdir") t)
- (with-temp-buffer (insert "Test content") (write-file (f-join cj/fs-test--temp-dir "test-file.txt")))
- (make-directory (f-join cj/fs-test--temp-dir "subdir") t)
- (with-temp-buffer (insert "Nested test") (write-file (f-join cj/fs-test--temp-dir "subdir/nested-file.txt"))))
-
-(defun cj/fs-test--teardown ()
- "Clean up temporary directory for fs-get-file-info tests."
- (when (and cj/fs-test--temp-dir (file-directory-p cj/fs-test--temp-dir))
- (delete-directory cj/fs-test--temp-dir t))
- (setq cj/fs-test--temp-dir nil))
-
-(ert-deftest test-cj/fs-get-file-info-normal-regular-file ()
- "Normal: info for regular file."
- (cj/fs-test--setup)
- (unwind-protect
- (let ((info (cj/fs-get-file-info (f-join cj/fs-test--temp-dir "test-file.txt"))))
- (should (plist-get info :success))
- (should (string-suffix-p "test-file.txt" (plist-get info :path)))
- (should (not (plist-get info :directory))))
- (cj/fs-test--teardown)))
-
-(ert-deftest test-cj/fs-get-file-info-normal-directory ()
- "Normal: info for directory."
- (cj/fs-test--setup)
- (unwind-protect
- (let ((info (cj/fs-get-file-info (f-join cj/fs-test--temp-dir "subdir"))))
- (should (plist-get info :success))
- (should (string-suffix-p "subdir" (plist-get info :path)))
- (should (plist-get info :directory)))
- (cj/fs-test--teardown)))
-
-(ert-deftest test-cj/fs-get-file-info-error-nonexistent ()
- "Error: non-existent file returns :success nil plist."
- (let ((info (cj/fs-get-file-info "/tmp/nonexistent-file-1234567890")))
- (should (not (plist-get info :success)))
- (should (stringp (plist-get info :error)))))
-
-(ert-deftest test-cj/fs-get-file-info-error-permission-denied ()
- "Error: permission denied file returns :success nil plist."
- (cj/fs-test--setup)
- (let ((file (f-join cj/fs-test--temp-dir "protected-file")))
- (unwind-protect
- (progn
- (with-temp-buffer (insert "secret") (write-file file))
- (set-file-modes file #o000)
- (let ((info (cj/fs-get-file-info file)))
- (should (not (plist-get info :success)))
- (should (stringp (plist-get info :error)))))
- (set-file-modes file #o644)
- (delete-file file)
- (cj/fs-test--teardown))))
-
-(provide 'test-tool-library-fs-get-file-info)
-;;; test-tool-library-fs-get-file-info.el ends here
diff --git a/tests/test-fs-list-directory-recursive-extra.el b/tests/test-fs-list-directory-recursive-extra.el
deleted file mode 100644
index 53ce3c8d..00000000
--- a/tests/test-fs-list-directory-recursive-extra.el
+++ /dev/null
@@ -1,106 +0,0 @@
-;;; test-tool-library-fs-list-directory-recursive-extra.el --- Additional ERT tests for cj/fs-list-directory-recursive -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; Additional tests to verify combined filters, boundary cases,
-;; symlink protection, and permission issue handling in
-;; cj/fs-list-directory-recursive.
-
-;;; Code:
-
-(require 'ert)
-(require 'f)
-(require 'tool-filesystem-library)
-
-(defvar cj/fs-extra-test--temp-dir nil "Temporary temp directory for extra fs-list-directory-recursive tests.")
-
-(defun cj/fs-extra-test--setup ()
- "Set up temp directory for extra fs-list-directory-recursive tests."
- (setq cj/fs-extra-test--temp-dir (make-temp-file "fs-lib-test" t))
- ;; Create directory structure
- (make-directory (f-join cj/fs-extra-test--temp-dir "subdir") t)
- (make-directory (f-join cj/fs-extra-test--temp-dir "subdir2") t)
- ;; Files at root level
- (with-temp-buffer (insert "Root org file") (write-file (f-join cj/fs-extra-test--temp-dir "file1.org")))
- (with-temp-buffer (insert "Root txt file") (write-file (f-join cj/fs-extra-test--temp-dir "file2.txt")))
- ;; Files in subdirectories
- (with-temp-buffer (insert "Subdir txt file") (write-file (f-join cj/fs-extra-test--temp-dir "subdir" "file3.txt")))
- (with-temp-buffer (insert "Subdir2 org file") (write-file (f-join cj/fs-extra-test--temp-dir "subdir2" "file4.org")))
- ;; Symlink to subdir2 inside subdir (potential for loops)
- (let ((target (f-join cj/fs-extra-test--temp-dir "subdir2"))
- (link (f-join cj/fs-extra-test--temp-dir "subdir" "link-to-subdir2")))
- (ignore-errors (delete-file link))
- (make-symbolic-link target link))
-
- ;; Create protected directory inside subdir to test permission issues
- (let ((protected-dir (f-join cj/fs-extra-test--temp-dir "subdir" "protected-dir")))
- (make-directory protected-dir t)
- ;; Remove read & execute permissions
- (set-file-modes protected-dir #o000)))
-
-(defun cj/fs-extra-test--teardown ()
- "Clean up temp directory for extra tests."
- (when (and cj/fs-extra-test--temp-dir (file-directory-p cj/fs-extra-test--temp-dir))
- ;; Reset permissions to allow deletion
- (let ((protected-dir (f-join cj/fs-extra-test--temp-dir "subdir" "protected-dir")))
- (when (file-exists-p protected-dir)
- (set-file-modes protected-dir #o755)))
- (delete-directory cj/fs-extra-test--temp-dir t))
- (setq cj/fs-extra-test--temp-dir nil))
-
-(ert-deftest test-cj/fs-list-directory-recursive-normal-combined-filter-maxdepth ()
- "Normal: recursive listing combining extension filter and max depth."
- (cj/fs-extra-test--setup)
- (unwind-protect
- (let* ((filter-fn (lambda (fi)
- (string-suffix-p ".org" (f-filename (plist-get fi :path)))))
- ;; max-depth 1 means root directory only, no recursion into subdirs
- (files (cj/fs-list-directory-recursive cj/fs-extra-test--temp-dir filter-fn 1)))
- ;; Should find only root level org files, not ones nested
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.org")) files))
- (should-not (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file4.org")) files)))
- (cj/fs-extra-test--teardown)))
-
-(ert-deftest test-cj/fs-list-directory-recursive-boundary-max-depth-zero ()
- "Boundary: max depth zero lists no files (no recursion)."
- (cj/fs-extra-test--setup)
- (unwind-protect
- (let ((files (cj/fs-list-directory-recursive cj/fs-extra-test--temp-dir nil 0)))
- ;; Should be empty as depth 0 means no entries processed
- (should (equal files nil)))
- (cj/fs-extra-test--teardown)))
-
-(ert-deftest test-cj/fs-list-directory-recursive-error-negative-max-depth ()
- "Error: negative max depth results in error."
- (cj/fs-extra-test--setup)
- (unwind-protect
- (should-error (cj/fs-list-directory-recursive cj/fs-extra-test--temp-dir nil -1))
- (cj/fs-extra-test--teardown)))
-
-(ert-deftest test-cj/fs-list-directory-recursive-boundary-symlink-no-infinite-loop ()
- "Boundary: symlinked directories do not cause infinite recursion."
- (cj/fs-extra-test--setup)
- (unwind-protect
- (let ((files (cj/fs-list-directory-recursive cj/fs-extra-test--temp-dir nil 5)))
- ;; There should be files from subdirs, but no infinite loop crashes
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file4.org")) files))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.org")) files)))
- (cj/fs-extra-test--teardown)))
-
-(ert-deftest test-cj/fs-list-directory-recursive-normal-permission-issue-handling ()
- "Normal: files in directories with permission issues are handled gracefully."
- (cj/fs-extra-test--setup)
- (unwind-protect
- (let ((caught-warning nil))
- (cl-letf (((symbol-function 'message)
- (lambda (&rest args)
- (when (string-match "Warning:" (apply #'format args))
- (setq caught-warning t)))))
- (cj/fs-list-directory-recursive cj/fs-extra-test--temp-dir nil 5)
- (should caught-warning)))
- (cj/fs-extra-test--teardown)))
-
-(provide 'test-tool-library-fs-list-directory-recursive-extra)
-;;; test-tool-library-fs-list-directory-recursive-extra.el ends here
diff --git a/tests/test-fs-list-directory-recursive.el b/tests/test-fs-list-directory-recursive.el
deleted file mode 100644
index 25dd1439..00000000
--- a/tests/test-fs-list-directory-recursive.el
+++ /dev/null
@@ -1,71 +0,0 @@
-;;; test-tool-library-fs-list-directory-recursive.el --- ERT tests for cj/fs-list-directory-recursive -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; ERT tests for the cj/fs-list-directory-recursive function from tool-filesystem-library.el.
-;; Place this file in ~/.emacs.d/tests/ and load it to run tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'f)
-(require 'tool-filesystem-library)
-
-(defvar cj/fs-test--temp-dir nil "Temporary temp directory for fs-list-directory-recursive tests.")
-
-(defun cj/fs-test--setup ()
- "Set up temp directory for fs-list-directory-recursive tests."
- (setq cj/fs-test--temp-dir (make-temp-file "fs-lib-test" t))
- ;; Create test directory structure
- (make-directory (f-join cj/fs-test--temp-dir "subdir") t)
- (make-directory (f-join cj/fs-test--temp-dir "subdir2") t)
- (with-temp-buffer (insert "Test file 1") (write-file (f-join cj/fs-test--temp-dir "file1.org")))
- (with-temp-buffer (insert "Test file 2") (write-file (f-join cj/fs-test--temp-dir "subdir" "file2.txt")))
- (with-temp-buffer (insert "Test file 3") (write-file (f-join cj/fs-test--temp-dir "subdir2" "file3.org")))
- (make-directory (f-join cj/fs-test--temp-dir ".hiddendir") t)
- (with-temp-buffer (insert "Secret") (write-file (f-join cj/fs-test--temp-dir ".hiddendir" "secret.txt"))))
-
-(defun cj/fs-test--teardown ()
- "Clean up temp directory for fs-list-directory-recursive tests."
- (when (and cj/fs-test--temp-dir (file-directory-p cj/fs-test--temp-dir))
- (delete-directory cj/fs-test--temp-dir t))
- (setq cj/fs-test--temp-dir nil))
-
-(ert-deftest test-cj/fs-list-directory-recursive-normal-recursive-filter ()
- "Normal: recursive listing with filter."
- (cj/fs-test--setup)
- (unwind-protect
- (let* ((filter-fn (lambda (fi) (string-suffix-p ".org" (f-filename (plist-get fi :path)))))
- (files (cj/fs-list-directory-recursive cj/fs-test--temp-dir filter-fn)))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.org")) files))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file3.org")) files))
- (should-not (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.txt")) files)))
- (cj/fs-test--teardown)))
-
-(ert-deftest test-cj/fs-list-directory-recursive-normal-max-depth ()
- "Normal: recursive listing with max depth limit."
- (cj/fs-test--setup)
- (unwind-protect
- (let* ((filter-fn (lambda (_) t))
- (files (cj/fs-list-directory-recursive cj/fs-test--temp-dir filter-fn 1)))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.org")) files))
- (should-not (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file3.org")) files)))
- (cj/fs-test--teardown)))
-
-(ert-deftest test-cj/fs-list-directory-recursive-error-non-directory ()
- "Error: non-directory input."
- (should-error (cj/fs-list-directory-recursive "/etc/hosts")))
-
-(ert-deftest test-cj/fs-list-directory-recursive-boundary-empty-dir ()
- "Boundary: recursive listing in empty directory."
- (make-temp-file "empty-dir" t)
- (let ((empty (make-temp-file "empty-dir" t)))
- (unwind-protect
- (progn
- (should (equal (cj/fs-list-directory-recursive empty) nil))
- (delete-directory empty)))))
-
-(provide 'test-tool-library-fs-list-directory-recursive)
-;;; test-tool-library-fs-list-directory-recursive.el ends here
diff --git a/tests/test-fs-validate-path.el b/tests/test-fs-validate-path.el
deleted file mode 100644
index 011789e0..00000000
--- a/tests/test-fs-validate-path.el
+++ /dev/null
@@ -1,45 +0,0 @@
-;;; test-tool-library-cj/fs-validate-path.el --- ERT tests for cj/fs-validate-path -*- lexical-binding: t; -*-
-
-;; Author: gptel-tool-writer and cjennings
-;; Keywords: tests, filesystem, tools
-
-;;; Commentary:
-;; ERT tests for the cj/fs-validate-path function from tool-filesystem-library.el.
-;; Place this file in ~/.emacs.d/tests/ and load it to run tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'f)
-(require 'tool-filesystem-library)
-
-(ert-deftest test-cj/fs-validate-path-normal-home ()
- "Normal: validate home directory path."
- (should (string-prefix-p (expand-file-name "~")
- (cj/fs-validate-path "~"))))
-
-(ert-deftest test-cj/fs-validate-path-normal-temp ()
- "Normal: validate temp directory path."
- (let ((temp (expand-file-name temporary-file-directory)))
- (should (string-prefix-p temp (cj/fs-validate-path temp)))))
-
-(ert-deftest test-cj/fs-validate-path-error-outside ()
- "Error: path outside allowed directories."
- (should-error (cj/fs-validate-path "/etc/passwd")))
-
-(ert-deftest test-cj/fs-validate-path-error-nonexistent ()
- "Error: non-existent path."
- (should-error (cj/fs-validate-path (format "/tmp/nonexistent-%d" (random 100000)))))
-
-(ert-deftest test-cj/fs-validate-path-error-unreadable ()
- "Error: unreadable path."
- (let ((file (make-temp-file "test-unreadable")))
- (unwind-protect
- (progn
- (set-file-modes file 0)
- (should-error (cj/fs-validate-path file)))
- (set-file-modes file #o644)
- (delete-file file))))
-
-(provide 'test-tool-library-cj/fs-validate-path)
-;;; test-tool-library-cj/fs-validate-path.el ends here
diff --git a/tests/test-integration-transcription.el b/tests/test-integration-transcription.el
index 96b617bc..d014d00e 100644
--- a/tests/test-integration-transcription.el
+++ b/tests/test-integration-transcription.el
@@ -8,6 +8,11 @@
;;; Code:
(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
(require 'transcription-config)
;; ----------------------------- Test Helpers ----------------------------------
diff --git a/tests/test-lorem-optimum-benchmark.el b/tests/test-lorem-optimum-benchmark.el
index 79e872ff..57d5ae5f 100644
--- a/tests/test-lorem-optimum-benchmark.el
+++ b/tests/test-lorem-optimum-benchmark.el
@@ -149,17 +149,7 @@ Needs lorem-optimum performance optimization before re-enabling."
(let ((time (benchmark-time
(lambda () (cj/markov-generate chain 100)))))
(benchmark-report "Generate 100 words" time)
- (should (< time 20.0))))) ; Should be < 20ms
-
-(ert-deftest benchmark-generate-1000-words ()
- "Benchmark generating 1000 words."
- (let* ((text (generate-test-text 10000))
- (chain (cj/markov-chain-create)))
- (cj/markov-learn chain text)
- (let ((time (benchmark-time
- (lambda () (cj/markov-generate chain 1000)))))
- (benchmark-report "Generate 1000 words" time)
- (should (< time 100.0))))) ; Should be < 100ms
+ (should (< time 30.0))))) ; Should be < 30ms
;;; Tokenization Performance Tests
diff --git a/tests/test-org-agenda-build-list.el b/tests/test-org-agenda-build-list.el
new file mode 100644
index 00000000..6b424200
--- /dev/null
+++ b/tests/test-org-agenda-build-list.el
@@ -0,0 +1,294 @@
+;;; test-org-agenda-build-list.el --- Tests for cj/build-org-agenda-list -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for cj/build-org-agenda-list caching logic.
+;; Tests cache behavior, TTL expiration, force rebuild, and async build flag.
+
+;;; Code:
+
+(require 'ert)
+
+;; Add modules to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Stub dependencies before loading the module
+(defvar inbox-file "/tmp/test-inbox.org")
+(defvar schedule-file "/tmp/test-schedule.org")
+(defvar gcal-file "/tmp/test-gcal.org")
+(defvar projects-dir "/tmp/test-projects/")
+
+;; Now load the actual production module
+(require 'org-agenda-config)
+
+;;; Setup and Teardown
+
+(defun test-org-agenda-setup ()
+ "Reset cache and state before each test."
+ (setq cj/org-agenda-files-cache nil)
+ (setq cj/org-agenda-files-cache-time nil)
+ (setq cj/org-agenda-files-building nil)
+ (setq org-agenda-files nil))
+
+(defun test-org-agenda-teardown ()
+ "Clean up after each test."
+ (setq cj/org-agenda-files-cache nil)
+ (setq cj/org-agenda-files-cache-time nil)
+ (setq cj/org-agenda-files-building nil)
+ (setq org-agenda-files nil))
+
+;;; Normal Cases
+
+(ert-deftest test-org-agenda-build-list-normal-first-call-builds-cache ()
+ "Test that first call builds cache from scratch.
+
+When cache is empty, function should:
+1. Scan directory for todo.org files
+2. Build agenda files list
+3. Populate cache
+4. Set cache timestamp"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs) '("/tmp/project/todo.org"))))
+
+ ;; Before call: cache empty
+ (should (null cj/org-agenda-files-cache))
+ (should (null cj/org-agenda-files-cache-time))
+
+ ;; Build agenda files
+ (cj/build-org-agenda-list)
+
+ ;; After call: cache populated
+ (should cj/org-agenda-files-cache)
+ (should cj/org-agenda-files-cache-time)
+ (should org-agenda-files)
+
+ ;; Cache matches org-agenda-files
+ (should (equal cj/org-agenda-files-cache org-agenda-files))
+
+ ;; Contains base files (inbox, schedule, gcal) plus project files
+ (should (>= (length org-agenda-files) 3)))
+ (test-org-agenda-teardown)))
+
+(ert-deftest test-org-agenda-build-list-normal-second-call-uses-cache ()
+ "Test that second call uses cache instead of rebuilding.
+
+When cache is valid (not expired):
+1. Should NOT scan directories again
+2. Should restore files from cache
+3. Should NOT update cache timestamp"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (let ((scan-count 0))
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs)
+ (setq scan-count (1+ scan-count))
+ '("/tmp/project/todo.org"))))
+
+ ;; First call: builds cache
+ (cj/build-org-agenda-list)
+ (should (= scan-count 1)) ; 1 directory scanned
+
+ (let ((cached-time cj/org-agenda-files-cache-time)
+ (cached-files cj/org-agenda-files-cache))
+
+ ;; Second call: uses cache
+ (cj/build-org-agenda-list)
+
+ ;; Scan count unchanged (cache hit)
+ (should (= scan-count 1))
+
+ ;; Cache unchanged
+ (should (equal cj/org-agenda-files-cache-time cached-time))
+ (should (equal cj/org-agenda-files-cache cached-files)))))
+ (test-org-agenda-teardown)))
+
+(ert-deftest test-org-agenda-build-list-normal-force-rebuild-bypasses-cache ()
+ "Test that force-rebuild parameter bypasses cache.
+
+When force-rebuild is non-nil:
+1. Should ignore valid cache
+2. Should rebuild from scratch
+3. Should update cache with new data"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (let ((scan-count 0))
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs)
+ (setq scan-count (1+ scan-count))
+ (if (> scan-count 1)
+ '("/tmp/project/todo.org" "/tmp/project2/todo.org") ; New file on rebuild
+ '("/tmp/project/todo.org")))))
+
+ ;; First call: builds cache
+ (cj/build-org-agenda-list)
+ (let ((initial-count (length org-agenda-files)))
+
+ ;; Force rebuild
+ (cj/build-org-agenda-list 'force)
+
+ ;; Scanned again
+ (should (= scan-count 2))
+
+ ;; New files include additional project
+ (should (> (length org-agenda-files) initial-count)))))
+ (test-org-agenda-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-org-agenda-build-list-boundary-cache-expires-after-ttl ()
+ "Test that cache expires after TTL period.
+
+When cache timestamp exceeds TTL:
+1. Should rebuild files list
+2. Should update cache timestamp
+3. Should rescan directory"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (let ((scan-count 0))
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs)
+ (setq scan-count (1+ scan-count))
+ '("/tmp/project/todo.org"))))
+
+ ;; First call: builds cache
+ (cj/build-org-agenda-list)
+ (should (= scan-count 1))
+
+ ;; Simulate cache expiration (set time to 2 hours ago)
+ (setq cj/org-agenda-files-cache-time
+ (- (float-time) (* 2 3600)))
+
+ ;; Second call: cache expired, rebuild
+ (cj/build-org-agenda-list)
+
+ ;; Scanned again (cache was expired)
+ (should (= scan-count 2))
+
+ ;; Cache timestamp updated to current time
+ (should (< (- (float-time) cj/org-agenda-files-cache-time) 1))))
+ (test-org-agenda-teardown)))
+
+(ert-deftest test-org-agenda-build-list-boundary-empty-directory-creates-minimal-list ()
+ "Test behavior when directory contains no todo.org files.
+
+When directory scan returns empty:
+1. Should still create base files (inbox, schedule)
+2. Should not fail or error
+3. Should cache the minimal result"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs) nil))) ; No files found
+
+ (cj/build-org-agenda-list)
+
+ ;; Should have base files only (inbox, schedule, gcal)
+ (should (= (length org-agenda-files) 3))
+
+ ;; Cache should contain base files
+ (should cj/org-agenda-files-cache)
+ (should (= (length cj/org-agenda-files-cache) 3)))
+ (test-org-agenda-teardown)))
+
+(ert-deftest test-org-agenda-build-list-boundary-building-flag-set-during-build ()
+ "Test that building flag is set during build and cleared after.
+
+During build:
+1. Flag should be set to prevent concurrent builds
+2. Flag should clear even if build fails
+3. Flag state should be consistent"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (let ((flag-during-build nil))
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs)
+ ;; Capture flag state during directory scan
+ (setq flag-during-build cj/org-agenda-files-building)
+ '("/tmp/project/todo.org"))))
+
+ ;; Before build
+ (should (null cj/org-agenda-files-building))
+
+ ;; Build
+ (cj/build-org-agenda-list)
+
+ ;; Flag was set during build
+ (should flag-during-build)
+
+ ;; Flag cleared after build
+ (should (null cj/org-agenda-files-building))))
+ (test-org-agenda-teardown)))
+
+(ert-deftest test-org-agenda-build-list-boundary-building-flag-clears-on-error ()
+ "Test that building flag clears even if build errors.
+
+When build encounters error:
+1. Flag should still be cleared (unwind-protect)
+2. Prevents permanently locked state
+3. Next build can proceed"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs)
+ (error "Simulated scan failure"))))
+
+ ;; Build will error
+ (should-error (cj/build-org-agenda-list))
+
+ ;; Flag cleared despite error (unwind-protect)
+ (should (null cj/org-agenda-files-building)))
+ (test-org-agenda-teardown)))
+
+;;; Error Cases
+
+(ert-deftest test-org-agenda-build-list-error-nil-cache-with-old-timestamp ()
+ "Test handling of inconsistent state (nil cache but timestamp set).
+
+When cache is nil but timestamp exists:
+1. Should recognize cache as invalid
+2. Should rebuild files list
+3. Should set both cache and timestamp"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs) '("/tmp/project/todo.org"))))
+
+ ;; Set inconsistent state
+ (setq cj/org-agenda-files-cache nil)
+ (setq cj/org-agenda-files-cache-time (float-time))
+
+ ;; Build should recognize invalid state
+ (cj/build-org-agenda-list)
+
+ ;; Cache now populated
+ (should cj/org-agenda-files-cache)
+ (should cj/org-agenda-files-cache-time)
+ (should org-agenda-files))
+ (test-org-agenda-teardown)))
+
+(ert-deftest test-org-agenda-build-list-error-directory-scan-failure-propagates ()
+ "Test that directory scan failures propagate as errors.
+
+When directory-files-recursively errors:
+1. Error should propagate to caller
+2. Cache should not be corrupted
+3. Building flag should clear"
+ (test-org-agenda-setup)
+ (unwind-protect
+ (cl-letf (((symbol-function 'directory-files-recursively)
+ (lambda (_dir _pattern &optional _include-dirs)
+ (error "Permission denied"))))
+
+ ;; Should propagate error
+ (should-error (cj/build-org-agenda-list))
+
+ ;; Cache not corrupted (still nil)
+ (should (null cj/org-agenda-files-cache))
+
+ ;; Building flag cleared
+ (should (null cj/org-agenda-files-building)))
+ (test-org-agenda-teardown)))
+
+(provide 'test-org-agenda-build-list)
+;;; test-org-agenda-build-list.el ends here
diff --git a/tests/test-org-contacts-capture-finalize.el b/tests/test-org-contacts-capture-finalize.el
index d379a912..6793defe 100644
--- a/tests/test-org-contacts-capture-finalize.el
+++ b/tests/test-org-contacts-capture-finalize.el
@@ -23,58 +23,19 @@
(require 'ert)
(require 'org)
-;; Define the function to test (copied from org-contacts-config.el)
-(defun cj/org-contacts-finalize-birthday-timestamp ()
- "Add yearly repeating timestamp after properties drawer if BIRTHDAY is set.
-This function is called during `org-capture' finalization to automatically
-insert a plain timestamp for birthdays, enabling them to appear in org-agenda
-without requiring org-contacts to be loaded in the async subprocess."
- (when (string= (plist-get org-capture-plist :key) "C")
- (save-excursion
- (goto-char (point-min))
- ;; Find the properties drawer
- (when (re-search-forward "^:PROPERTIES:" nil t)
- (let ((drawer-start (point))
- (drawer-end (save-excursion
- (when (re-search-forward "^:END:" nil t)
- (point)))))
- (when drawer-end
- ;; Get BIRTHDAY property value
- (goto-char drawer-start)
- (when (re-search-forward "^:BIRTHDAY:[ \t]*\\(.+\\)$" drawer-end t)
- (let ((birthday-value (string-trim (match-string 1))))
- ;; Only process non-empty birthdays
- (when (and birthday-value
- (not (string-blank-p birthday-value)))
- ;; Parse birthday and create timestamp
- (let* ((parsed (cond
- ;; Format: YYYY-MM-DD
- ((string-match "^\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)$" birthday-value)
- (list (string-to-number (match-string 1 birthday-value))
- (string-to-number (match-string 2 birthday-value))
- (string-to-number (match-string 3 birthday-value))))
- ;; Format: MM-DD
- ((string-match "^\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)$" birthday-value)
- (list nil
- (string-to-number (match-string 1 birthday-value))
- (string-to-number (match-string 2 birthday-value))))
- (t nil)))
- (year (when parsed (or (nth 0 parsed) (nth 5 (decode-time)))))
- (month (when parsed (nth 1 parsed)))
- (day (when parsed (nth 2 parsed))))
- (when (and year month day)
- ;; Create timestamp
- (let* ((time (encode-time 0 0 0 day month year))
- (dow (format-time-string "%a" time))
- (date-str (format "%04d-%02d-%02d" year month day))
- (timestamp (format "<%s %s +1y>" date-str dow)))
- ;; Insert after :END: if not already present
- (goto-char drawer-end)
- (let ((heading-end (save-excursion (outline-next-heading) (point))))
- (unless (re-search-forward "<[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}[^>]*\\+1y>" heading-end t)
- (goto-char drawer-end)
- (end-of-line)
- (insert "\n" timestamp)))))))))))))
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Stub dependencies before loading the module
+(defvar contacts-file "/tmp/test-contacts.org"
+ "Stub contacts file for testing.")
+
+;; Declare org-capture-plist for dynamic scoping in tests
+(defvar org-capture-plist nil
+ "Plist that org-capture uses during capture. Declared for testing.")
+
+;; Load the actual module
+(require 'org-contacts-config)
;;; Tests for birthday timestamp finalization
diff --git a/tests/test-org-gcal-mock.el b/tests/test-org-gcal-mock.el
deleted file mode 100644
index 4b063867..00000000
--- a/tests/test-org-gcal-mock.el
+++ /dev/null
@@ -1,112 +0,0 @@
-;;; test-org-gcal-mock.el --- Mock test for org-gcal sync -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Mock test to capture what org-gcal sends to Google Calendar API
-;; This helps debug bidirectional sync issues without hitting the real API
-
-;;; Code:
-
-(require 'ert)
-(require 'org)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Load org-gcal (this will require auth, but we'll mock the requests)
-(require 'org-gcal-config nil t)
-
-;; Variables to capture requests
-(defvar test-org-gcal-captured-requests nil
- "List of captured HTTP requests.")
-
-(defvar test-org-gcal-captured-url nil
- "Last captured URL.")
-
-(defvar test-org-gcal-captured-type nil
- "Last captured HTTP method (GET/POST/PUT/PATCH).")
-
-(defvar test-org-gcal-captured-data nil
- "Last captured request body/data.")
-
-(defvar test-org-gcal-captured-headers nil
- "Last captured request headers.")
-
-;;; Mock request-deferred to capture what org-gcal sends
-
-(defun test-org-gcal-mock-request-deferred (url &rest args)
- "Mock request-deferred to capture requests instead of sending them.
-URL is the API endpoint. ARGS contains :type, :data, :headers, etc."
- (let* ((type (plist-get args :type))
- (data (plist-get args :data))
- (headers (plist-get args :headers)))
- ;; Capture the request
- (setq test-org-gcal-captured-url url)
- (setq test-org-gcal-captured-type type)
- (setq test-org-gcal-captured-data data)
- (setq test-org-gcal-captured-headers headers)
- (push (list :url url
- :type type
- :data data
- :headers headers)
- test-org-gcal-captured-requests)
-
- ;; Print for debugging
- (message "MOCK REQUEST: %s %s" type url)
- (when data
- (message "MOCK DATA: %S" data))
-
- ;; Return a mock deferred object that succeeds immediately
- (require 'deferred)
- (deferred:succeed
- ;; Mock response with a fake event
- (list :data '(:id "test-event-id-123"
- :etag "test-etag-456"
- :summary "Test Event"
- :start (:dateTime "2025-10-28T14:00:00-05:00")
- :end (:dateTime "2025-10-28T15:00:00-05:00"))
- :status-code 200))))
-
-(ert-deftest test-org-gcal-capture-post-request ()
- "Test capturing what org-gcal sends when posting an event."
- ;; Reset captured requests
- (setq test-org-gcal-captured-requests nil)
- (setq test-org-gcal-captured-url nil)
- (setq test-org-gcal-captured-type nil)
- (setq test-org-gcal-captured-data nil)
-
- ;; Mock request-deferred
- (cl-letf (((symbol-function 'request-deferred) #'test-org-gcal-mock-request-deferred))
-
- ;; Create a test org buffer with an event
- (with-temp-buffer
- (org-mode)
- (insert "* TEST: Mock Sync Test Event\n")
- (insert "<2025-10-28 Tue 14:00-15:00>\n")
- (insert "\n")
- (insert "Test event for mocking.\n")
-
- ;; Go to the headline
- (goto-char (point-min))
- (org-back-to-heading)
-
- ;; Try to post (this should be captured by our mock)
- (condition-case err
- (org-gcal-post-at-point)
- (error
- (message "Error during post: %S" err)))))
-
- ;; Check what was captured
- (should test-org-gcal-captured-requests)
- (let ((request (car test-org-gcal-captured-requests)))
- (message "Captured URL: %s" (plist-get request :url))
- (message "Captured Type: %s" (plist-get request :type))
- (message "Captured Data: %S" (plist-get request :data))
-
- ;; Verify it's trying to POST/PATCH
- (should (member (plist-get request :type) '("POST" "PATCH" "PUT")))
-
- ;; Verify URL contains calendar API
- (should (string-match-p "googleapis.com/calendar" (plist-get request :url)))))
-
-(provide 'test-org-gcal-mock)
-;;; test-org-gcal-mock.el ends here
diff --git a/tests/test-testutil-filesystem-directory-entries.el b/tests/test-testutil-filesystem-directory-entries.el
deleted file mode 100644
index 7ddbf426..00000000
--- a/tests/test-testutil-filesystem-directory-entries.el
+++ /dev/null
@@ -1,317 +0,0 @@
-;;; test-testutil-filesystem-directory-entries.el --- -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; ERT tests for testutil-filesystem.el
-;; Tests cj/list-directory-recursive and it's helper function cj/get--directory-entries.
-;;
-;;; Code:
-
-(require 'ert)
-(require 'f)
-
-;; load test directory
-(add-to-list 'load-path (concat user-emacs-directory "tests/"))
-(require 'testutil-general) ;; helper functions
-(require 'testutil-filesystem) ;; file under test
-
-(defun cj/test--setup ()
- "Create the test base directory using `cj/create-test-base-dir'."
- (cj/create-test-base-dir))
-
-(defun cj/test--teardown ()
- "Remove the test base directory using `cj/delete-test-base-dir'."
- (cj/delete-test-base-dir))
-
-;;; ---------------------- CJ/GET--DIRECTORY-ENTRIES TESTS ----------------------
-;;;; Normal Case Tests
-
-(ert-deftest test-normal-one-file ()
- "Test a single file at the base directory."
- (cj/test--setup)
- (unwind-protect
- (progn
- (cj/create-directory-or-file-ensuring-parents "file.txt" "Test file")
- (let
- ;; get paths to all files
- ((entries (cj/get--directory-entries cj/test-base-dir)))
- ;; check for files of different types and in subdirectories
- (should (cl-some (lambda (e) (string= (f-filename e) "file.txt")) entries))))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-includes-subdirectories-but-no-contents ()
- "Test that we do include subdirectories themselves."
- (cj/test--setup)
- (unwind-protect
- (progn
- ;; create yoru test assets
- (cj/create-directory-or-file-ensuring-parents "file1.org" "Test file 1" t)
- (cj/create-directory-or-file-ensuring-parents "subdir/file2.org" "Nested file")
- ;; get paths to all files
- (let ((entries (cj/get--directory-entries cj/test-base-dir)))
- (should (cl-some (lambda (e) (and (file-directory-p e)
- (string= (f-filename e) "subdir"))) entries))
- (should-not (cl-some (lambda (e) (string= (f-filename e) "file2.org")) entries))))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-excludes-hidden-by-default ()
- "Test that hidden files (i.e.,begin with a dot) are excluded by default.
-Asserts no subdirectories or hidden files or visible files in hidden subdirectories are returned."
- (cj/test--setup)
- (unwind-protect
- (progn
- ;; create your test assets
- (cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
- ;; get paths to all files
- (let ((entries (cj/get--directory-entries cj/test-base-dir)))
- ;; should not see hidden file
- (should-not (cl-some (lambda (e) (string= (f-filename e) ".hiddenfile")) entries))))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-includes-hidden-with-flag ()
- "Non-nil means hidden files are included."
- (cj/test--setup)
- (unwind-protect
- (progn
- ;; create your test assets
- (cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
- ;; get paths to all files passing in t to reveal hidden files
- (let ((entries (cj/get--directory-entries cj/test-base-dir t)))
- ;; should not see hidden file
- (should (cl-some (lambda (e) (string= (f-filename e) ".hiddenfile")) entries))))
- (cj/test--teardown)))
-
-;;
-;;;; Boundary Cases
-
-(ert-deftest test-boundary-empty-directory ()
- "Test an empty directory returns empty list."
- (cj/test--setup)
- (unwind-protect
- (let ((entries (cj/get--directory-entries cj/test-base-dir)))
- (should (equal entries nil)))
- (cj/test--teardown)))
-
-(ert-deftest test-boundary-files-with-unusual-names ()
- "Test files with unusual names."
- (cj/test--setup)
- (unwind-protect
- (progn
- (cj/create-directory-or-file-ensuring-parents "file with spaces.org" "content")
- (cj/create-directory-or-file-ensuring-parents "unicode-ß₄©.org" "content") ;; Direct Unicode chars
- ;; Or use proper escape sequences:
- ;; (cj/create-directory-or-file-ensuring-parents "unicode-\u00DF\u2074\u00A9.org" "content")
- (let ((entries (cj/get--directory-entries cj/test-base-dir)))
- (should (cl-some (lambda (e) (string= (f-filename e) "file with spaces.org")) entries))
- (should (cl-some (lambda (e) (string= (f-filename e) "unicode-ß₄©.org")) entries))))
- (cj/test--teardown)))
-
-;;;; Error Cases
-
-(ert-deftest test-error-nonexistent-directory ()
- "Test calling on nonexistent directory returns nil or error handled."
- (should-error (cj/get--directory-entries "/path/does/not/exist")))
- ;
-(ert-deftest test-error-not-a-directory-path ()
- "Test calling on a file path signals error."
- (cj/test--setup)
- (unwind-protect
- (let ((filepath (cj/create-directory-or-file-ensuring-parents "file.txt" "data")))
- (should-error (cj/get--directory-entries filepath)))
- (cj/test--teardown)))
-
-(ert-deftest test-error-permission-denied ()
- "Test directory with no permission signals error or returns nil."
- (cj/test--setup)
- (unwind-protect
- (let ((dir (expand-file-name "noperm" cj/test-base-dir)))
- (cj/create-directory-or-file-ensuring-parents "noperm/file2.org" "Nested file")
- (let ((original-mode (file-modes dir))) ; Save original permissions
- (set-file-modes dir #o000) ; Remove all permissions
- (unwind-protect
- (should-error (cj/get--directory-entries dir))
- (set-file-modes dir original-mode)))) ; Restore permissions - extra paren here
- (cj/test--teardown)))
-
-;;; --------------------- CJ/LIST-DIRECTORY-RECURSIVE TESTS ---------------------
-;;;; Normal Cases
-
-(ert-deftest test-normal-single-file-at-root ()
- "Test the normal base case: one single file at the root."
- (cj/test--setup)
- (unwind-protect
- (progn
- (cj/create-directory-or-file-ensuring-parents "file.txt" "Content")
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file.txt")) file-infos))))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-multiple-files-at-root ()
- "Test finding multiple files at the root directory."
- (cj/test--setup)
- (unwind-protect
- (cj/create-directory-or-file-ensuring-parents "file1.txt" "Content in File 1")
- (cj/create-directory-or-file-ensuring-parents "file2.org" "Content in File 2")
- (cj/create-directory-or-file-ensuring-parents "file3.md" "Content in File 3")
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.txt")) file-infos))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.org")) file-infos))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file3.md")) file-infos)))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-multiple-files-in-subdirectories ()
- "Test finding multiple files at the root directory."
- (cj/test--setup)
- (unwind-protect
- (cj/create-directory-or-file-ensuring-parents "one/file1.txt" "Content in File 1")
- (cj/create-directory-or-file-ensuring-parents "two/file2.org" "Content in File 2")
- (cj/create-directory-or-file-ensuring-parents "three/file3.md" "Content in File 3")
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file1.txt")) file-infos))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file2.org")) file-infos))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "file3.md")) file-infos)))
- (cj/test--teardown)))
-
-(ert-deftest test-recursive-excludes-hidden-by-default ()
- "Test that hidden files are excluded by default in recursive listing.
-Verify that files beginning with a dot, hidden directories, and files
-within hidden directories are all excluded when include-hidden is nil."
- (cj/test--setup)
- (unwind-protect
- (progn
- ;; Create test assets including hidden files at various levels
- (cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
- (cj/create-directory-or-file-ensuring-parents ".hiddendir/visible-in-hidden.txt" "File in hidden dir")
- (cj/create-directory-or-file-ensuring-parents "visible/normal.txt" "Normal file")
- (cj/create-directory-or-file-ensuring-parents "visible/.hidden-in-visible.txt" "Hidden in visible dir")
-
- ;; Get all files recursively (default excludes hidden)
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
- ;; Should not see .hiddenfile at root
- (should-not (cl-some (lambda (fi)
- (string= (f-filename (plist-get fi :path)) ".hiddenfile"))
- file-infos))
- ;; Should not see .hiddendir directory
- (should-not (cl-some (lambda (fi)
- (string= (f-filename (plist-get fi :path)) ".hiddendir"))
- file-infos))
- ;; Should not see files inside hidden directory
- (should-not (cl-some (lambda (fi)
- (string= (f-filename (plist-get fi :path)) "visible-in-hidden.txt"))
- file-infos))
- ;; Should not see hidden file in visible directory
- (should-not (cl-some (lambda (fi)
- (string= (f-filename (plist-get fi :path)) ".hidden-in-visible.txt"))
- file-infos))
- ;; Should see normal visible file
- (should (cl-some (lambda (fi)
- (string= (f-filename (plist-get fi :path)) "normal.txt"))
- file-infos))))
- (cj/test--teardown)))
-
-(ert-deftest test-recursive-includes-hidden-with-flag ()
- "Non-nil means hidden files are included.
-Verifies that files beginning with a dot, hidden directories, and files
-within hidden directories are all included when include-hidden is t."
- (cj/test--setup)
- (unwind-protect
- (progn
- ;; Create test assets including hidden files at various levels
- (cj/create-directory-or-file-ensuring-parents ".hiddenfile" "Hidden content")
- (cj/create-directory-or-file-ensuring-parents ".hiddendir/visible-in-hidden.txt" "File in hidden dir")
- (cj/create-directory-or-file-ensuring-parents "visible/normal.txt" "Normal file")
- (cj/create-directory-or-file-ensuring-parents "visible/.hidden-in-visible.txt" "Hidden in visible dir")
-
- ;; Get all files recursively with include-hidden = t
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir t)))
- ;; Should see .hiddenfile at root
- (should (cl-some (lambda (fi)
- (string= (f-filename (plist-get fi :path)) ".hiddenfile")) file-infos))
- ;; Should see .hiddendir directory
- (should (cl-some (lambda (fi) (and (plist-get fi :directory)
- (string= (f-filename (plist-get fi :path)) ".hiddendir"))) file-infos))
- ;; Should see files inside hidden directory
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "visible-in-hidden.txt")) file-infos))
- ;; Should see hidden file in visible directory
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) ".hidden-in-visible.txt")) file-infos))
- ;; Should still see normal visible file
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "normal.txt")) file-infos))))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-deeply-nested-structure ()
- "Tests with deeply nested directory trees."
- (cj/test--setup)
- (unwind-protect
- (progn
- (cj/create-directory-or-file-ensuring-parents
- "one/two/three/four/five/six/seven/eight/nine/ten/eleven/twelve/13.txt" "thirteen")
- (cj/create-directory-or-file-ensuring-parents
- "1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/thirty.txt" "30")
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
- ;; validate the files
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "13.txt")) file-infos))
- (should (cl-some (lambda (fi) (string= (f-filename (plist-get fi :path)) "thirty.txt")) file-infos))))
- (cj/test--teardown)))
-
-(ert-deftest test-normal-only-directory-entries ()
- "Tests with deeply nested directory trees without files."
- (cj/test--setup)
- (unwind-protect
- (progn
- (cj/create-directory-or-file-ensuring-parents
- "one/two/three/four/five/six/seven/eight/nine/ten/eleven/twelve/thirteen/")
- (cj/create-directory-or-file-ensuring-parents
- "1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/")
- (let ((file-infos (cj/list-directory-recursive cj/test-base-dir)))
- ;; validate the directories
- (should (cl-some (lambda (fi)
- (and (string= (f-filename (plist-get fi :path)) "thirteen")
- (plist-get fi :directory)
- (file-directory-p (plist-get fi :path))))
- file-infos))
-
- (should (cl-some (lambda (fi)
- (and (string= (f-filename (plist-get fi :path)) "30")
- (plist-get fi :directory)
- (file-directory-p (plist-get fi :path))))
- file-infos))))
- (cj/test--teardown)))
-
-;; 5. =test-normal-filter-by-extension= - Filter predicate correctly filters .org files
-
-
-;; 6. =test-normal-filter-by-size= - Filter predicate filters files > 1KB
-;; 7. =test-normal-filter-excludes-directories= - Filter can exclude directories themselves
-;; 8. =test-normal-max-depth-one= - Respects max-depth=1 (only immediate children)
-;; 9. =test-normal-max-depth-three= - Respects max-depth=3 limit
-;; 11. =test-normal-executable-files= - Correctly identifies executable files
-;; 12. =test-normal-file-info-plist-structure= - Verifies correct plist keys/values returned
-
-;;;; Boundary Cases
-;; 1. =test-boundary-empty-directory= - Empty directory returns empty list
-;; 2. =test-boundary-single-empty-subdirectory= - Directory with only empty subdirectory
-;; 3. =test-boundary-unicode-filenames= - Files with unicode characters (emoji, Chinese, etc.)
-;; 4. =test-boundary-spaces-in-names= - Files/dirs with spaces in names
-;; 5. =test-boundary-special-characters= - Files with special chars (@#$%^&*()_+)
-;; 6. =test-boundary-very-long-filename= - File with 255 character name
-;; 8. =test-boundary-many-files= - Directory with 1000+ files
-;; 9. =test-boundary-max-depth-zero= - max-depth=0 (unlimited) works correctly
-;; 10. =test-boundary-symlinks= - How it handles symbolic links
-;; 11. =test-boundary-filter-returns-all-nil= - Filter that rejects everything
-;; 12. =test-boundary-filter-returns-all-true= - Filter that accepts everything
-
-;;;; Error Cases
-;; 1. =test-error-nonexistent-path= - Path that doesn't exist
-;; 2. =test-error-file-not-directory= - PATH is a file, not directory
-;; 3. =test-error-permission-denied= - Directory without read permissions
-;; 4. =test-error-permission-denied-subdirectory= - Subdirectory without permissions
-;; 5. =test-error-invalid-max-depth= - Negative max-depth value
-;; 6. =test-error-filter-predicate-errors= - Filter function that throws error
-;; 7. =test-error-circular-symlinks= - Circular symbolic link reference
-;; 8. =test-error-path-outside-home= - Attempt to access system directories (if restricted)
-;; 9. =test-error-nil-path= - PATH is nil
-;; 10. =test-error-empty-string-path= - PATH is empty string
-
-(provide 'test-testutil-filesystem-directory-entries)
-;;; test-testutil-filesystem-directory-entries.el ends here.
diff --git a/tests/test-transcription-audio-file.el b/tests/test-transcription-audio-file.el
index f40d9ca6..ac4ff452 100644
--- a/tests/test-transcription-audio-file.el
+++ b/tests/test-transcription-audio-file.el
@@ -7,6 +7,11 @@
;;; Code:
(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
(require 'transcription-config)
;; ----------------------------- Normal Cases ----------------------------------
diff --git a/tests/test-transcription-counter.el b/tests/test-transcription-counter.el
index fae353ba..dd4df7dc 100644
--- a/tests/test-transcription-counter.el
+++ b/tests/test-transcription-counter.el
@@ -7,6 +7,11 @@
;;; Code:
(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
(require 'transcription-config)
;; ----------------------------- Normal Cases ----------------------------------
diff --git a/tests/test-transcription-duration.el b/tests/test-transcription-duration.el
index 370c439b..4f4e9a75 100644
--- a/tests/test-transcription-duration.el
+++ b/tests/test-transcription-duration.el
@@ -7,6 +7,11 @@
;;; Code:
(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
(require 'transcription-config)
;; ----------------------------- Normal Cases ----------------------------------
diff --git a/tests/test-transcription-log-cleanup.el b/tests/test-transcription-log-cleanup.el
index 82c902d8..251e5ef9 100644
--- a/tests/test-transcription-log-cleanup.el
+++ b/tests/test-transcription-log-cleanup.el
@@ -7,6 +7,11 @@
;;; Code:
(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
(require 'transcription-config)
;; ----------------------------- Normal Cases ----------------------------------
diff --git a/tests/test-transcription-paths.el b/tests/test-transcription-paths.el
index 5ee80e67..69dc27e7 100644
--- a/tests/test-transcription-paths.el
+++ b/tests/test-transcription-paths.el
@@ -7,6 +7,11 @@
;;; Code:
(require 'ert)
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
(require 'transcription-config)
;; ----------------------------- Normal Cases ----------------------------------
diff --git a/tests/test-undead-buffers.el b/tests/test-undead-buffers.el
index 38187525..d08649b7 100644
--- a/tests/test-undead-buffers.el
+++ b/tests/test-undead-buffers.el
@@ -2,14 +2,14 @@
;;; Commentary:
;; ERT tests for undead-buffers.el.
-;; Exercises kill vs bury decisions driven by cj/buffer-bury-alive-list
+;; Exercises kill vs bury decisions driven by cj/undead-buffer-list
;; and window-management helpers.
;; Coverage:
;; - cj/kill-buffer-or-bury-alive: kills non-listed buffers; buries listed; C-u adds to list
;; - cj/kill-buffer-and-window: deletes selected window, then kill/bury buffer as appropriate
;; - cj/kill-other-window: deletes the other window, then kill/bury that buffer
;; - cj/kill-all-other-buffers-and-windows: keeps only current window/buffer
-;; Tests isolate state with temporary buffers/windows and restore cj/buffer-bury-alive-list.
+;; Tests isolate state with temporary buffers/windows and restore cj/undead-buffer-list.
;; Note: bury-buffer does not delete windows; tests assert buffer liveness, not window removal.
;;; Code:
@@ -19,200 +19,99 @@
(require 'undead-buffers)
(ert-deftest undead-buffers/kill-or-bury-when-not-in-list-kills ()
- "cj/kill-buffer-or-bury-alive should kill a buffer not in `cj/buffer-bury-alive-list'."
+ "cj/kill-buffer-or-bury-alive should kill a buffer not in `cj/undead-buffer-list'."
(let* ((buf (generate-new-buffer "test-not-in-list"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (unwind-protect
- (progn
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive (buffer-name buf))
- (should-not (buffer-live-p buf)))
- (setq cj/buffer-bury-alive-list orig)
- (when (buffer-live-p buf) (kill-buffer buf)))))
+ (orig (copy-sequence cj/undead-buffer-list)))
+ (unwind-protect
+ (progn
+ (should (buffer-live-p buf))
+ (cj/kill-buffer-or-bury-alive (buffer-name buf))
+ (should-not (buffer-live-p buf)))
+ (setq cj/undead-buffer-list orig)
+ (when (buffer-live-p buf) (kill-buffer buf)))))
(ert-deftest undead-buffers/kill-or-bury-when-in-list-buries ()
"cj/kill-buffer-or-bury-alive should bury (not kill) a buffer in the list."
(let* ((name "*dashboard*") ; an element already in the default list
- (buf (generate-new-buffer name))
- (orig (copy-sequence cj/buffer-bury-alive-list))
- win-was)
- (unwind-protect
- (progn
- (add-to-list 'cj/buffer-bury-alive-list name)
- ;; show it in a temporary window so we can detect bury
- (setq win-was (display-buffer buf))
- (cj/kill-buffer-or-bury-alive name)
- ;; bury should leave it alive
- (should (buffer-live-p buf))
- ;; note: Emacs’s `bury-buffer` does not delete windows by default,
- ;; so we no longer assert that no window shows it.
- )
- ;; cleanup
- (setq cj/buffer-bury-alive-list orig)
- (delete-windows-on buf)
- (kill-buffer buf))))
+ (buf (generate-new-buffer name))
+ (orig (copy-sequence cj/undead-buffer-list))
+ win-was)
+ (unwind-protect
+ (progn
+ (add-to-list 'cj/undead-buffer-list name)
+ ;; show it in a temporary window so we can detect bury
+ (setq win-was (display-buffer buf))
+ (cj/kill-buffer-or-bury-alive name)
+ ;; bury should leave it alive
+ (should (buffer-live-p buf))
+ ;; note: Emacs's `bury-buffer` does not delete windows by default,
+ ;; so we no longer assert that no window shows it.
+ )
+ ;; cleanup
+ (setq cj/undead-buffer-list orig)
+ (delete-windows-on buf)
+ (kill-buffer buf))))
(ert-deftest undead-buffers/kill-or-bury-adds-to-list-with-prefix ()
"Calling `cj/kill-buffer-or-bury-alive' with a prefix arg should add the buffer to the list."
(let* ((buf (generate-new-buffer "test-add-prefix"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (unwind-protect
- (progn
- (let ((current-prefix-arg '(4)))
- (cj/kill-buffer-or-bury-alive (buffer-name buf)))
- (should (member (buffer-name buf) cj/buffer-bury-alive-list)))
- (setq cj/buffer-bury-alive-list orig)
- (kill-buffer buf))))
+ (orig (copy-sequence cj/undead-buffer-list)))
+ (unwind-protect
+ (progn
+ (let ((current-prefix-arg '(4)))
+ (cj/kill-buffer-or-bury-alive (buffer-name buf)))
+ (should (member (buffer-name buf) cj/undead-buffer-list)))
+ (setq cj/undead-buffer-list orig)
+ (kill-buffer buf))))
(ert-deftest undead-buffers/kill-buffer-and-window-removes-window ()
"cj/kill-buffer-and-window should delete the current window and kill/bury its buffer."
(let* ((buf (generate-new-buffer "test-kill-and-win"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (split-window) ; now two windows
- (let ((win (next-window)))
- (set-window-buffer win buf)
- (select-window win)
- (cj/kill-buffer-and-window)
- (should-not (window-live-p win))
- (unless (member (buffer-name buf) orig)
- (should-not (buffer-live-p buf))))
- (setq cj/buffer-bury-alive-list orig)))
+ (orig (copy-sequence cj/undead-buffer-list)))
+ (split-window) ; now two windows
+ (let ((win (next-window)))
+ (set-window-buffer win buf)
+ (select-window win)
+ (cj/kill-buffer-and-window)
+ (should-not (window-live-p win))
+ (unless (member (buffer-name buf) orig)
+ (should-not (buffer-live-p buf))))
+ (setq cj/undead-buffer-list orig)))
(ert-deftest undead-buffers/kill-other-window-deletes-that-window ()
"cj/kill-other-window should delete the *other* window and kill/bury its buffer."
(let* ((buf1 (current-buffer))
- (buf2 (generate-new-buffer "test-other-window"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (split-window)
- (let* ((win1 (selected-window))
- (win2 (next-window win1)))
- (set-window-buffer win2 buf2)
- ;; stay on the original window
- (select-window win1)
- (cj/kill-other-window)
- (should-not (window-live-p win2))
- (unless (member (buffer-name buf2) orig)
- (should-not (buffer-live-p buf2))))
- (setq cj/buffer-bury-alive-list orig)))
+ (buf2 (generate-new-buffer "test-other-window"))
+ (orig (copy-sequence cj/undead-buffer-list)))
+ (split-window)
+ (let* ((win1 (selected-window))
+ (win2 (next-window win1)))
+ (set-window-buffer win2 buf2)
+ ;; stay on the original window
+ (select-window win1)
+ (cj/kill-other-window)
+ (should-not (window-live-p win2))
+ (unless (member (buffer-name buf2) orig)
+ (should-not (buffer-live-p buf2))))
+ (setq cj/undead-buffer-list orig)))
(ert-deftest undead-buffers/kill-all-other-buffers-and-windows-keeps-only-current ()
"cj/kill-all-other-buffers-and-windows should delete other windows and kill/bury all other buffers."
(let* ((main (current-buffer))
- (extra (generate-new-buffer "test-all-others"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (split-window)
- (set-window-buffer (next-window) extra)
- (cj/kill-all-other-buffers-and-windows)
- (should (one-window-p))
- ;; main buffer still exists
- (should (buffer-live-p main))
- ;; extra buffer either buried or killed
- (unless (member (buffer-name extra) orig)
- (should-not (buffer-live-p extra)))
- ;; cleanup
- (setq cj/buffer-bury-alive-list orig)
- (when (buffer-live-p extra) (kill-buffer extra))))
-
-;; --------------------------------- ERT Tests ---------------------------------
-;; Run these tests with M-x ert RET t RET
-
-(require 'ert)
-(require 'cl-lib)
-
-(ert-deftest undead-buffers/kill-or-bury-when-not-in-list-kills ()
- "cj/kill-buffer-or-bury-alive should kill a buffer not in `cj/buffer-bury-alive-list'."
- (let* ((buf (generate-new-buffer "test-not-in-list"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (unwind-protect
- (progn
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive (buffer-name buf))
- (should-not (buffer-live-p buf)))
- (setq cj/buffer-bury-alive-list orig)
- (when (buffer-live-p buf) (kill-buffer buf)))))
-
-(ert-deftest undead-buffers/kill-or-bury-when-in-list-buries ()
- "cj/kill-buffer-or-bury-alive should bury (not kill) a buffer in the list."
- (let* ((name "*dashboard*") ; an element already in the default list
- (buf (generate-new-buffer name))
- (orig (copy-sequence cj/buffer-bury-alive-list))
- win-was)
- (unwind-protect
- (progn
- (add-to-list 'cj/buffer-bury-alive-list name)
- ;; show it in a temporary window so we can detect bury
- (setq win-was (display-buffer buf))
- (cj/kill-buffer-or-bury-alive name)
- ;; bury should leave it alive
- (should (buffer-live-p buf))
- ;; note: Emacs’s `bury-buffer` does not delete windows by default,
- ;; so we no longer assert that no window shows it.
- )
- ;; cleanup
- (setq cj/buffer-bury-alive-list orig)
- (delete-windows-on buf)
- (kill-buffer buf))))
-
-(ert-deftest undead-buffers/kill-or-bury-adds-to-list-with-prefix ()
- "Calling `cj/kill-buffer-or-bury-alive' with a prefix arg should add the buffer to the list."
- (let* ((buf (generate-new-buffer "test-add-prefix"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (unwind-protect
- (progn
- (let ((current-prefix-arg '(4)))
- (cj/kill-buffer-or-bury-alive (buffer-name buf)))
- (should (member (buffer-name buf) cj/buffer-bury-alive-list)))
- (setq cj/buffer-bury-alive-list orig)
- (kill-buffer buf))))
-
-(ert-deftest undead-buffers/kill-buffer-and-window-removes-window ()
- "cj/kill-buffer-and-window should delete the current window and kill/bury its buffer."
- (let* ((buf (generate-new-buffer "test-kill-and-win"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (split-window) ; now two windows
- (let ((win (next-window)))
- (set-window-buffer win buf)
- (select-window win)
- (cj/kill-buffer-and-window)
- (should-not (window-live-p win))
- (unless (member (buffer-name buf) orig)
- (should-not (buffer-live-p buf))))
- (setq cj/buffer-bury-alive-list orig)))
-
-(ert-deftest undead-buffers/kill-other-window-deletes-that-window ()
- "cj/kill-other-window should delete the *other* window and kill/bury its buffer."
- (let* ((buf1 (current-buffer))
- (buf2 (generate-new-buffer "test-other-window"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (split-window)
- (let* ((win1 (selected-window))
- (win2 (next-window win1)))
- (set-window-buffer win2 buf2)
- ;; stay on the original window
- (select-window win1)
- (cj/kill-other-window)
- (should-not (window-live-p win2))
- (unless (member (buffer-name buf2) orig)
- (should-not (buffer-live-p buf2))))
- (setq cj/buffer-bury-alive-list orig)))
-
-(ert-deftest undead-buffers/kill-all-other-buffers-and-windows-keeps-only-current ()
- "cj/kill-all-other-buffers-and-windows should delete other windows and kill/bury all other buffers."
- (let* ((main (current-buffer))
- (extra (generate-new-buffer "test-all-others"))
- (orig (copy-sequence cj/buffer-bury-alive-list)))
- (split-window)
- (set-window-buffer (next-window) extra)
- (cj/kill-all-other-buffers-and-windows)
- (should (one-window-p))
- ;; main buffer still exists
- (should (buffer-live-p main))
- ;; extra buffer either buried or killed
- (unless (member (buffer-name extra) orig)
- (should-not (buffer-live-p extra)))
- ;; cleanup
- (setq cj/buffer-bury-alive-list orig)
- (when (buffer-live-p extra) (kill-buffer extra))))
+ (extra (generate-new-buffer "test-all-others"))
+ (orig (copy-sequence cj/undead-buffer-list)))
+ (split-window)
+ (set-window-buffer (next-window) extra)
+ (cj/kill-all-other-buffers-and-windows)
+ (should (one-window-p))
+ ;; main buffer still exists
+ (should (buffer-live-p main))
+ ;; extra buffer either buried or killed
+ (unless (member (buffer-name extra) orig)
+ (should-not (buffer-live-p extra)))
+ ;; cleanup
+ (setq cj/undead-buffer-list orig)
+ (when (buffer-live-p extra) (kill-buffer extra))))
(provide 'test-undead-buffers)
;;; test-undead-buffers.el ends here.