diff options
| -rw-r--r-- | init.el | 2 | ||||
| -rw-r--r-- | modules/user-constants.el | 35 | ||||
| -rw-r--r-- | tests/test-user-constants.el | 81 |
3 files changed, 102 insertions, 16 deletions
@@ -21,6 +21,8 @@ (require 'system-lib) ;; low-level system utility functions (require 'config-utilities) ;; enable for extra Emacs config debug helpers (require 'user-constants) ;; paths for files referenced in this config +(unless noninteractive + (cj/initialize-user-directories-and-files)) ;; create configured dirs/files on real startup (require 'host-environment) ;; convenience functions re: host environment (require 'keyboard-compat) ;; terminal/GUI keyboard compatibility (require 'system-defaults) ;; native comp; log; unicode, backup, exec path diff --git a/modules/user-constants.el b/modules/user-constants.el index 293bc806..02a500d6 100644 --- a/modules/user-constants.el +++ b/modules/user-constants.el @@ -5,15 +5,16 @@ ;; Layer: 1 (Foundation). ;; Category: F. ;; Load shape: eager. -;; Eager reason: defines the path constants referenced across the config and -;; creates the required directories/files before other modules load. -;; Top-level side effects: file writes — creates configured directories and -;; stub files via `cj/initialize-user-directories-and-files' at load. +;; Eager reason: defines the path constants referenced across the config; other +;; modules read them at their own load time. +;; Top-level side effects: none — only path definitions. Filesystem creation +;; lives in `cj/initialize-user-directories-and-files', which init.el calls on +;; real startup (not at module load), so a bare require is side-effect-free. ;; Runtime requires: none. -;; Direct test load: conditional (touches the filesystem on load). +;; Direct test load: yes. ;; ;; This module defines important file and directory paths used throughout the -;; Emacs configuration, and ensures they exist during startup. +;; Emacs configuration, and provides a command to create them on startup. ;; ;; WHY THIS EXISTS: ;; 1. Centralizes all path definitions for easy reference and maintenance @@ -22,7 +23,8 @@ ;; ;; The module first defines constants and variables for directories and files, ;; then provides functions that verify their existence, creating them if needed. -;; This happens automatically when the module loads. +;; init.el calls `cj/initialize-user-directories-and-files' after requiring this +;; module so the paths exist before the modules that depend on them load. ;; ;; The paths are designed with a hierarchical structure, allowing child paths ;; to reference their parents (e.g., roam-dir is inside org-dir) for better @@ -170,12 +172,6 @@ Stored in .emacs.d/data/ so each machine syncs independently from Proton Calenda "The location of the org file containing DeepSat Calendar information. Stored in .emacs.d/data/ so each machine syncs independently from Google Calendar.") -;; Ensure calendar data files exist so org-agenda-list doesn't hang -;; prompting for missing files (calendar-sync populates them on first sync) -(dolist (f (list gcal-file pcal-file dcal-file)) - (unless (file-exists-p f) - (make-empty-file f t))) - (defvar reference-file (expand-file-name "reference.org" org-dir) "The location of the org file containing reference information.") @@ -241,7 +237,12 @@ and portable across different machines." video-recordings-dir audio-recordings-dir org-dir)) - (mapc 'cj/verify-or-create-file (list schedule-file + ;; gcal/pcal/dcal exist so org-agenda-list doesn't hang on missing files + ;; (calendar-sync populates them on first sync). + (mapc 'cj/verify-or-create-file (list gcal-file + pcal-file + dcal-file + schedule-file inbox-file article-archive reading-notes-file @@ -249,8 +250,10 @@ and portable across different machines." webclipped-file reference-file))) -;; Initialize directories and files when this module is loaded -(cj/initialize-user-directories-and-files) +;; Creation is deferred to startup: init.el calls +;; `cj/initialize-user-directories-and-files' after requiring this module, so a +;; bare `(require 'user-constants)' (tests, byte-compile, batch) stays +;; side-effect-free. (provide 'user-constants) ;;; user-constants.el ends here diff --git a/tests/test-user-constants.el b/tests/test-user-constants.el new file mode 100644 index 00000000..5246e7ea --- /dev/null +++ b/tests/test-user-constants.el @@ -0,0 +1,81 @@ +;;; test-user-constants.el --- Tests for user-constants path init -*- lexical-binding: t; -*- + +;;; Commentary: + +;; user-constants defines the config's path constants and creates the +;; configured directories/files. After the split, loading the module is +;; side-effect-free: creation happens only when +;; cj/initialize-user-directories-and-files is called (from init.el on real +;; startup). These tests sandbox user-emacs-directory and user-home-dir so the +;; path defconsts resolve into temp dirs, then check that loading creates +;; nothing, the initializer creates what it should, and the verify-or-create +;; helpers report required vs optional failures differently. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +;; Declare special so the macro's let-bindings are dynamic — the module +;; `defvar's user-home-dir at load, which errors if the var is lexically bound. +;; user-emacs-directory is already special (built-in). +(defvar user-home-dir) + +(defconst test-user-constants--repo-root + (file-name-directory + (directory-file-name + (file-name-directory (or load-file-name buffer-file-name)))) + "Repository root, derived from this file's location under tests/.") + +(defun test-user-constants--load () + "Load user-constants.el fresh from the repo." + (load (expand-file-name "modules/user-constants.el" + test-user-constants--repo-root) + nil t)) + +(defmacro test-user-constants--with-sandbox (&rest body) + "Load user-constants with paths redirected to temp dirs, run BODY, restore. +The path defconsts are recomputed against temp `user-home-dir' / +`user-emacs-directory' so BODY can create into the sandbox. Afterwards the +module is reloaded against the real paths so the globals are not left pointing +at deleted temp directories." + (declare (indent 0)) + `(let ((home (file-name-as-directory (make-temp-file "uc-home-" t))) + (emacs (file-name-as-directory (make-temp-file "uc-emacs-" t)))) + (unwind-protect + (let ((user-home-dir home) + (user-emacs-directory emacs)) + (test-user-constants--load) + ,@body) + (test-user-constants--load) + (ignore-errors (delete-directory home t)) + (ignore-errors (delete-directory emacs t))))) + +;;; Loading is side-effect-free + +(ert-deftest test-user-constants-loading-creates-no-files () + "Normal: loading the module creates no directories or files. +The whole point of the split — a bare require must not touch the filesystem." + (test-user-constants--with-sandbox + (should-not (file-exists-p (expand-file-name "sync" home))) + (should-not (file-exists-p (expand-file-name "org" sync-dir))) + (should-not (file-exists-p gcal-file)) + (should-not (file-exists-p schedule-file)))) + +;;; The initializer creates the configured paths + +(ert-deftest test-user-constants-initialize-creates-dirs-and-files () + "Normal: the initializer creates the backbone dirs and the configured files." + (test-user-constants--with-sandbox + (cj/initialize-user-directories-and-files) + (should (file-directory-p sync-dir)) + (should (file-directory-p org-dir)) + (should (file-directory-p roam-dir)) + (should (file-exists-p gcal-file)) + (should (file-exists-p pcal-file)) + (should (file-exists-p dcal-file)) + (should (file-exists-p schedule-file)) + (should (file-exists-p inbox-file)))) + +(provide 'test-user-constants) +;;; test-user-constants.el ends here |
