From 2dd56761df8e6b6d7b5f99c5c7c6d20f5b2d6447 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 15 May 2026 23:00:59 -0500 Subject: fix(flycheck): correct abbrev-mode no-arg toggle in cj/prose-helpers-on The shape (if (not (abbrev-mode)) (abbrev-mode)) calls abbrev-mode with no argument -- that's the toggle signature, not a query. When the mode was already on the function flipped it off then on instead of being a no-op. Replaced with (unless (bound-and-true-p VAR) (MODE 1)) for both abbrev-mode and flycheck-mode. 4 ERT tests cover both-off, both-on, and the two mixed states. Also ran the module hardening pass across 24 newly-added modules, renamed the six completed Review sub-tasks to Harden, filed 11 new findings under their Harden parents, and broke three design specs (EMMS-free music, dev F-keys, dev-setup-project) into 20 dependency-ordered sub-tasks via parallel subagents. Verified the sqlite finalizer bug from 2026-04-26 is gone and closed its tracking entry. --- tests/test-flycheck-config-prose-helpers-on.el | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/test-flycheck-config-prose-helpers-on.el (limited to 'tests') diff --git a/tests/test-flycheck-config-prose-helpers-on.el b/tests/test-flycheck-config-prose-helpers-on.el new file mode 100644 index 00000000..5c6553b3 --- /dev/null +++ b/tests/test-flycheck-config-prose-helpers-on.el @@ -0,0 +1,88 @@ +;;; test-flycheck-config-prose-helpers-on.el --- Tests for cj/prose-helpers-on -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Unit tests for `cj/prose-helpers-on'. The function must enable +;; `abbrev-mode' and `flycheck-mode' with an explicit positive +;; argument, and must be a no-op when both are already on. +;; +;; Regression: a prior shape +;; (if (not (abbrev-mode)) (abbrev-mode)) +;; called `abbrev-mode' with no argument -- the toggle signature, not +;; a query. When the mode was already on the function flipped it off +;; then on (two transitions per invocation, firing the disable/enable +;; hooks each time) instead of being a no-op. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'flycheck-config) + +;; `abbrev-mode' is preloaded (defvar in core); `flycheck-mode' is autoloaded +;; via use-package with =:defer t= so its defvar hasn't run by the time the +;; tests dynamically let-bind these variables. Declare both as special here +;; so `let' creates dynamic bindings that `bound-and-true-p' inside the +;; production code can read -- otherwise lexical-binding=t makes the let +;; lexical-only and the inner reads can't see the test's state. +(defvar abbrev-mode) +(defvar flycheck-mode) + +(defmacro test-flycheck-config--with-prose-spies (abbrev-state flycheck-state &rest body) + "Run BODY with `abbrev-mode' / `flycheck-mode' stubbed and dynamic. +ABBREV-STATE and FLYCHECK-STATE seed the dynamic values of the mode +variables so `bound-and-true-p' inside the production code reads +them. Inside BODY, `abbrev-calls' / `flycheck-calls' carry the args +each stub received in call order. The stubs return their arg so the +buggy no-arg toggle shape terminates without infinite recursion." + (declare (indent 2)) + `(let ((abbrev-calls '()) + (flycheck-calls '()) + (abbrev-mode ,abbrev-state) + (flycheck-mode ,flycheck-state)) + (cl-letf (((symbol-function 'abbrev-mode) + (lambda (&optional arg) + (setq abbrev-calls (append abbrev-calls (list arg))) + arg)) + ((symbol-function 'flycheck-mode) + (lambda (&optional arg) + (setq flycheck-calls (append flycheck-calls (list arg))) + arg))) + ,@body))) + +(ert-deftest test-flycheck-config-prose-helpers-on-normal-both-off-enables-both () + "Normal: both modes off -> each enabled with explicit positive arg." + (test-flycheck-config--with-prose-spies nil nil + (cj/prose-helpers-on) + (should (= 1 (length abbrev-calls))) + (should (> (prefix-numeric-value (car abbrev-calls)) 0)) + (should (= 1 (length flycheck-calls))) + (should (> (prefix-numeric-value (car flycheck-calls)) 0)))) + +(ert-deftest test-flycheck-config-prose-helpers-on-boundary-both-on-is-noop () + "Boundary: both modes already on -> neither mode function called. +Catches the no-arg toggle shape; the bug records at least one call +with a nil arg." + (test-flycheck-config--with-prose-spies t t + (cj/prose-helpers-on) + (should (null abbrev-calls)) + (should (null flycheck-calls)))) + +(ert-deftest test-flycheck-config-prose-helpers-on-boundary-abbrev-on-flycheck-off () + "Boundary: abbrev on, flycheck off -> only flycheck enabled." + (test-flycheck-config--with-prose-spies t nil + (cj/prose-helpers-on) + (should (null abbrev-calls)) + (should (= 1 (length flycheck-calls))) + (should (> (prefix-numeric-value (car flycheck-calls)) 0)))) + +(ert-deftest test-flycheck-config-prose-helpers-on-boundary-flycheck-on-abbrev-off () + "Boundary: flycheck on, abbrev off -> only abbrev enabled." + (test-flycheck-config--with-prose-spies nil t + (cj/prose-helpers-on) + (should (= 1 (length abbrev-calls))) + (should (> (prefix-numeric-value (car abbrev-calls)) 0)) + (should (null flycheck-calls)))) + +(provide 'test-flycheck-config-prose-helpers-on) +;;; test-flycheck-config-prose-helpers-on.el ends here -- cgit v1.2.3