aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/dev-fkeys.el64
-rw-r--r--tests/test-dev-fkeys--projectile-advice-install.el86
2 files changed, 124 insertions, 26 deletions
diff --git a/modules/dev-fkeys.el b/modules/dev-fkeys.el
index 0b973470..c9a5fc13 100644
--- a/modules/dev-fkeys.el
+++ b/modules/dev-fkeys.el
@@ -179,11 +179,6 @@ a single Compile entry that calls plain `compile'."
;; buffer-local compilation finish hook so overlapping compiles cannot
;; overwrite or consume one another's revert metadata.
-(defvar cj/--projectile-revert-state nil
- "Legacy dynamic state used by direct tests of `cj/--projectile-revert-on-fail'.
-The around advice no longer stores live compile metadata here; it closes
-over the plist returned by `cj/--projectile-capture-cmd' instead.")
-
(defun cj/--projectile-capture-cmd (map-symbol)
"Capture the cached cmd at the project root in MAP-SYMBOL.
MAP-SYMBOL is the symbol of a projectile cmd-map (e.g.
@@ -210,18 +205,6 @@ cache alone."
(not (equal prior current)))
(puthash root prior (symbol-value map))))))
-(defun cj/--projectile-revert-on-fail (_buf status)
- "Compilation-finish hook: revert projectile cache on failed-and-modified.
-Always self-removes from `compilation-finish-functions' and clears
-`cj/--projectile-revert-state'. Reverts the cmd-map entry only when the
-compile failed AND the cmd was modified from the captured prior value
-AND that prior was non-nil. The unchanged-and-failed case (test fails
-because of a real bug) leaves the cache alone."
- (remove-hook 'compilation-finish-functions #'cj/--projectile-revert-on-fail)
- (let ((state cj/--projectile-revert-state))
- (setq cj/--projectile-revert-state nil)
- (cj/--projectile-revert-state-on-fail state status)))
-
(defun cj/--projectile-make-revert-on-fail-hook (state)
"Return a one-shot buffer-local finish hook for projectile revert STATE."
(let (hook)
@@ -497,15 +480,44 @@ message."
;; ---------- Projectile advice ----------
-(advice-add 'projectile-compile-project :around
- (apply-partially #'cj/--projectile-around-revert
- 'projectile-compile-cmd-map))
-(advice-add 'projectile-test-project :around
- (apply-partially #'cj/--projectile-around-revert
- 'projectile-test-cmd-map))
-(advice-add 'projectile-run-project :around
- (apply-partially #'cj/--projectile-around-revert
- 'projectile-run-cmd-map))
+(defun cj/--projectile-compile-around-revert (orig-fn &rest args)
+ "Around advice for `projectile-compile-project' command-cache revert."
+ (apply #'cj/--projectile-around-revert
+ 'projectile-compile-cmd-map orig-fn args))
+
+(defun cj/--projectile-test-around-revert (orig-fn &rest args)
+ "Around advice for `projectile-test-project' command-cache revert."
+ (apply #'cj/--projectile-around-revert
+ 'projectile-test-cmd-map orig-fn args))
+
+(defun cj/--projectile-run-around-revert (orig-fn &rest args)
+ "Around advice for `projectile-run-project' command-cache revert."
+ (apply #'cj/--projectile-around-revert
+ 'projectile-run-cmd-map orig-fn args))
+
+(defconst cj/--projectile-revert-advice-specs
+ '((projectile-compile-project . cj/--projectile-compile-around-revert)
+ (projectile-test-project . cj/--projectile-test-around-revert)
+ (projectile-run-project . cj/--projectile-run-around-revert))
+ "Projectile command runners and their command-cache revert advice.")
+
+(defun cj/--projectile-install-revert-advice ()
+ "Install Projectile command-cache revert advice when Projectile is available."
+ (dolist (spec cj/--projectile-revert-advice-specs)
+ (let ((target (car spec))
+ (advice (cdr spec)))
+ (when (and (fboundp target)
+ (not (advice-member-p advice target)))
+ (advice-add target :around advice)))))
+
+(defun cj/--projectile-register-revert-advice ()
+ "Install Projectile revert advice now, or after Projectile loads."
+ (if (featurep 'projectile)
+ (cj/--projectile-install-revert-advice)
+ (eval-after-load 'projectile
+ (list 'cj/--projectile-install-revert-advice))))
+
+(cj/--projectile-register-revert-advice)
;; ---------- Bindings ----------
diff --git a/tests/test-dev-fkeys--projectile-advice-install.el b/tests/test-dev-fkeys--projectile-advice-install.el
new file mode 100644
index 00000000..bfa9b691
--- /dev/null
+++ b/tests/test-dev-fkeys--projectile-advice-install.el
@@ -0,0 +1,86 @@
+;;; test-dev-fkeys--projectile-advice-install.el --- Tests for Projectile advice setup -*- lexical-binding: t -*-
+
+;;; Commentary:
+;; Smoke tests for the load-order contract between dev-fkeys.el and Projectile.
+;; Requiring dev-fkeys should not force Projectile to load, but the command-cache
+;; revert advice should be installed once Projectile is available.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'dev-fkeys)
+
+(ert-deftest test-dev-fkeys-projectile-advice-register-defers-until-projectile-loads ()
+ "When Projectile is not loaded, registration should use `eval-after-load'."
+ (let (registered-feature registered-form install-called)
+ (cl-letf (((symbol-function 'featurep)
+ (lambda (feature) (and (not (eq feature 'projectile))
+ (featurep feature))))
+ ((symbol-function 'eval-after-load)
+ (lambda (feature form)
+ (setq registered-feature feature
+ registered-form form)))
+ ((symbol-function 'cj/--projectile-install-revert-advice)
+ (lambda () (setq install-called t))))
+ (cj/--projectile-register-revert-advice))
+ (should (eq registered-feature 'projectile))
+ (should (equal registered-form '(cj/--projectile-install-revert-advice)))
+ (should-not install-called)))
+
+(ert-deftest test-dev-fkeys-projectile-advice-register-installs-when-projectile-loaded ()
+ "When Projectile is already loaded, registration should install immediately."
+ (let (install-called eval-after-load-called)
+ (cl-letf (((symbol-function 'featurep)
+ (lambda (feature) (eq feature 'projectile)))
+ ((symbol-function 'eval-after-load)
+ (lambda (&rest _args) (setq eval-after-load-called t)))
+ ((symbol-function 'cj/--projectile-install-revert-advice)
+ (lambda () (setq install-called t))))
+ (cj/--projectile-register-revert-advice))
+ (should install-called)
+ (should-not eval-after-load-called)))
+
+(ert-deftest test-dev-fkeys-projectile-advice-install-skips-unbound-projectile-functions ()
+ "The installer should not advise Projectile functions that are not bound."
+ (let (advised)
+ (cl-letf (((symbol-function 'fboundp)
+ (lambda (symbol)
+ (and (not (memq symbol '(projectile-compile-project
+ projectile-test-project
+ projectile-run-project)))
+ (fboundp symbol))))
+ ((symbol-function 'advice-add)
+ (lambda (symbol &rest _args)
+ (push symbol advised))))
+ (cj/--projectile-install-revert-advice))
+ (should-not advised)))
+
+(ert-deftest test-dev-fkeys-projectile-advice-install-advises-bound-projectile-functions ()
+ "The installer should advise each available Projectile command runner."
+ (let (advised)
+ (cl-letf (((symbol-function 'fboundp)
+ (lambda (symbol)
+ (or (memq symbol '(projectile-compile-project
+ projectile-test-project
+ projectile-run-project))
+ (fboundp symbol))))
+ ((symbol-function 'advice-member-p)
+ (lambda (&rest _args) nil))
+ ((symbol-function 'advice-add)
+ (lambda (symbol _where function &rest _args)
+ (push (list symbol function) advised))))
+ (cj/--projectile-install-revert-advice))
+ (should (member '(projectile-compile-project
+ cj/--projectile-compile-around-revert)
+ advised))
+ (should (member '(projectile-test-project
+ cj/--projectile-test-around-revert)
+ advised))
+ (should (member '(projectile-run-project
+ cj/--projectile-run-around-revert)
+ advised))))
+
+(provide 'test-dev-fkeys--projectile-advice-install)
+;;; test-dev-fkeys--projectile-advice-install.el ends here