test-code-action-quick.el
1 ;;; test-code-action-quick.el --- Automated tests for code-action-quick -*- lexical-binding: t -*- 2 3 ;;; Commentary: 4 ;; Run with: emacs -batch -l test-code-action-quick.el -f caq-test-run-all 5 ;; Or interactively: M-x caq-test-run-all 6 7 ;;; Code: 8 9 (require 'ert) 10 11 ;; Load the package 12 (let ((root-dir (expand-file-name ".." (file-name-directory load-file-name)))) 13 (add-to-list 'load-path root-dir) 14 (load (expand-file-name "code-action-quick.el" root-dir))) 15 16 (require 'code-action-quick) 17 18 ;;; ============================================================================ 19 ;;; Test Configuration 20 ;;; ============================================================================ 21 22 (defvar caq-test-fixtures-dir 23 (expand-file-name "fixtures" (file-name-directory load-file-name)) 24 "Directory containing test fixtures.") 25 26 (defvar caq-test-timeout 30 27 "Timeout in seconds for LSP operations.") 28 29 ;;; ============================================================================ 30 ;;; Unit Tests (no LSP required) 31 ;;; ============================================================================ 32 33 (ert-deftest caq-test-kind-priority () 34 "Test that kind priority works correctly." 35 (should (= 0 (caq--kind-priority "quickfix"))) 36 (should (= 1 (caq--kind-priority "refactor.rewrite"))) 37 (should (= 999 (caq--kind-priority "refactor.extract"))) 38 (should (= 999 (caq--kind-priority "source.organizeImports"))) 39 (should (= 999 (caq--kind-priority nil)))) 40 41 (ert-deftest caq-test-kind-allowed () 42 "Test that kind filtering works." 43 (should (caq--kind-allowed-p "quickfix")) 44 (should (caq--kind-allowed-p "refactor.rewrite")) 45 (should-not (caq--kind-allowed-p "refactor.extract")) 46 (should-not (caq--kind-allowed-p "refactor.inline")) 47 (should-not (caq--kind-allowed-p nil))) 48 49 (ert-deftest caq-test-kind-subkind-matching () 50 "Test that sub-kinds are matched correctly." 51 (should (caq--kind-allowed-p "quickfix.something")) 52 (should (caq--kind-allowed-p "refactor.rewrite.something")) 53 (should-not (caq--kind-allowed-p "refactor.extract"))) 54 55 (ert-deftest caq-test-client-detection-no-lsp () 56 "Test client detection when no LSP is active." 57 (with-temp-buffer 58 (setq-local caq--detected-client nil) 59 (should (null (caq--detect-lsp-client))))) 60 61 (ert-deftest caq-test-normalize-eglot-action () 62 "Test normalizing an eglot action." 63 (let ((action '(:title "Import HashMap" :kind "quickfix" :isPreferred t))) 64 (let ((normalized (caq--normalize-eglot-action action))) 65 (should (equal "Import HashMap" (caq-action-title normalized))) 66 (should (equal "quickfix" (caq-action-kind normalized))) 67 (should (eq t (caq-action-preferred normalized))) 68 (should (= 0 (caq-action-priority normalized))) 69 (should (eq 'eglot (caq-action-client normalized)))))) 70 71 (ert-deftest caq-test-action-sorting () 72 "Test that actions are sorted correctly." 73 (let* ((action1 (make-caq-action :title "B" :kind "refactor.rewrite" :preferred nil :priority 1)) 74 (action2 (make-caq-action :title "A" :kind "quickfix" :preferred nil :priority 0)) 75 (action3 (make-caq-action :title "C" :kind "quickfix" :preferred t :priority 0)) 76 (actions (list action1 action2 action3)) 77 (sorted (sort (copy-sequence actions) 78 (lambda (a b) 79 (let ((pref-a (caq-action-preferred a)) 80 (pref-b (caq-action-preferred b)) 81 (pri-a (caq-action-priority a)) 82 (pri-b (caq-action-priority b))) 83 (cond 84 ((and pref-a (not pref-b)) t) 85 ((and pref-b (not pref-a)) nil) 86 ((< pri-a pri-b) t) 87 ((> pri-a pri-b) nil) 88 (t (string< (caq-action-title a) (caq-action-title b))))))))) 89 (should (equal "C" (caq-action-title (nth 0 sorted)))) 90 (should (equal "A" (caq-action-title (nth 1 sorted)))) 91 (should (equal "B" (caq-action-title (nth 2 sorted)))))) 92 93 (ert-deftest caq-test-collect-positions () 94 "Test position collection for lookback." 95 (with-temp-buffer 96 (insert "line 1\nline 2\nline 3") 97 (goto-char (point-max)) 98 (let ((caq-lookback-lines 1)) 99 (let ((positions (caq--collect-positions))) 100 (should (member (point) positions)) 101 (should (member (line-beginning-position) positions)))))) 102 103 ;;; ============================================================================ 104 ;;; Edge Case Tests 105 ;;; ============================================================================ 106 107 (ert-deftest caq-test-kind-nil-handling () 108 "Test handling of nil kinds." 109 (should-not (caq--kind-allowed-p nil)) 110 (should (= 999 (caq--kind-priority nil)))) 111 112 (ert-deftest caq-test-kind-empty-string () 113 "Test handling of empty string kind." 114 (should-not (caq--kind-allowed-p "")) 115 (should (= 999 (caq--kind-priority "")))) 116 117 (ert-deftest caq-test-kind-deep-nesting () 118 "Test deeply nested sub-kinds." 119 (should (caq--kind-allowed-p "quickfix.a.b.c")) 120 (should (caq--kind-allowed-p "refactor.rewrite.x.y.z")) 121 (should-not (caq--kind-allowed-p "refactor.a.b.c"))) 122 123 (ert-deftest caq-test-kind-partial-match-not-allowed () 124 "Test that partial matches are rejected." 125 ;; 'quick' should not match 'quickfix' 126 (should-not (caq--kind-allowed-p "quick")) 127 ;; 'quickfixer' should not match 'quickfix' 128 (should-not (caq--kind-allowed-p "quickfixer")) 129 ;; 'refactor.rewritable' should not match 'refactor.rewrite' 130 (should-not (caq--kind-allowed-p "refactor.rewritable"))) 131 132 (ert-deftest caq-test-collect-positions-empty-buffer () 133 "Test position collection in empty buffer." 134 (with-temp-buffer 135 (let ((caq-lookback-lines 2)) 136 (let ((positions (caq--collect-positions))) 137 (should (member (point) positions)) 138 ;; Should have at least one position 139 (should (>= (length positions) 1)))))) 140 141 (ert-deftest caq-test-collect-positions-single-line () 142 "Test position collection with single line." 143 (with-temp-buffer 144 (insert "single line content") 145 (goto-char (point-max)) 146 (let ((caq-lookback-lines 5)) ; More lines than exist 147 (let ((positions (caq--collect-positions))) 148 (should (member (point) positions)) 149 (should (member (line-beginning-position) positions)))))) 150 151 (ert-deftest caq-test-collect-positions-at-buffer-start () 152 "Test position collection at beginning of buffer." 153 (with-temp-buffer 154 (insert "line 1\nline 2\nline 3") 155 (goto-char (point-min)) 156 (let ((caq-lookback-lines 2)) 157 (let ((positions (caq--collect-positions))) 158 (should (member (point) positions)))))) 159 160 (ert-deftest caq-test-collect-positions-zero-lookback () 161 "Test position collection with zero lookback." 162 (with-temp-buffer 163 (insert "line 1\nline 2\nline 3") 164 (goto-char (point-max)) 165 (let ((caq-lookback-lines 0)) 166 (let ((positions (caq--collect-positions))) 167 ;; Should still include current line positions 168 (should (member (point) positions)))))) 169 170 (ert-deftest caq-test-normalize-eglot-action-minimal () 171 "Test normalizing eglot action with minimal fields." 172 (let ((action '(:title "Minimal"))) 173 (let ((normalized (caq--normalize-eglot-action action))) 174 (should (equal "Minimal" (caq-action-title normalized))) 175 (should (null (caq-action-kind normalized))) 176 (should (null (caq-action-preferred normalized))) 177 (should (= 999 (caq-action-priority normalized)))))) 178 179 (ert-deftest caq-test-normalize-eglot-action-preferred-json-false () 180 "Test that :json-false is treated as not preferred." 181 (let ((action '(:title "Test" :kind "quickfix" :isPreferred :json-false))) 182 (let ((normalized (caq--normalize-eglot-action action))) 183 (should-not (caq-action-preferred normalized))))) 184 185 (ert-deftest caq-test-client-cache-isolation () 186 "Test that client cache is buffer-local." 187 (let ((buf1 (generate-new-buffer "*test-buf-1*")) 188 (buf2 (generate-new-buffer "*test-buf-2*"))) 189 (unwind-protect 190 (progn 191 ;; Set cache in buf1 192 (with-current-buffer buf1 193 (setq-local caq--detected-client 'eglot)) 194 ;; buf2 should have independent cache 195 (with-current-buffer buf2 196 (should (null caq--detected-client))) 197 ;; buf1 should retain its value 198 (with-current-buffer buf1 199 (should (eq 'eglot caq--detected-client)))) 200 (kill-buffer buf1) 201 (kill-buffer buf2)))) 202 203 (ert-deftest caq-test-action-struct-accessors () 204 "Test that all struct accessors work." 205 (let ((action (make-caq-action 206 :title "Test Title" 207 :kind "quickfix" 208 :preferred t 209 :priority 0 210 :client 'eglot 211 :raw '(:some "data")))) 212 (should (equal "Test Title" (caq-action-title action))) 213 (should (equal "quickfix" (caq-action-kind action))) 214 (should (eq t (caq-action-preferred action))) 215 (should (= 0 (caq-action-priority action))) 216 (should (eq 'eglot (caq-action-client action))) 217 (should (equal '(:some "data") (caq-action-raw action))))) 218 219 (ert-deftest caq-test-custom-allowed-kinds () 220 "Test customizing allowed action kinds." 221 (let ((caq-allowed-action-kinds '("source.organizeImports"))) 222 ;; Now only source.organizeImports should be allowed 223 (should (caq--kind-allowed-p "source.organizeImports")) 224 (should-not (caq--kind-allowed-p "quickfix")) 225 (should-not (caq--kind-allowed-p "refactor.rewrite")))) 226 227 (ert-deftest caq-test-empty-allowed-kinds () 228 "Test with empty allowed kinds list." 229 (let ((caq-allowed-action-kinds '())) 230 (should-not (caq--kind-allowed-p "quickfix")) 231 (should-not (caq--kind-allowed-p "refactor.rewrite")) 232 (should-not (caq--kind-allowed-p "anything")))) 233 234 (ert-deftest caq-test-sorting-all-same-priority () 235 "Test sorting when all actions have same priority." 236 (let* ((action1 (make-caq-action :title "Zebra" :kind "quickfix" :preferred nil :priority 0)) 237 (action2 (make-caq-action :title "Apple" :kind "quickfix" :preferred nil :priority 0)) 238 (action3 (make-caq-action :title "Mango" :kind "quickfix" :preferred nil :priority 0)) 239 (actions (list action1 action2 action3)) 240 (sorted (sort (copy-sequence actions) 241 (lambda (a b) 242 (let ((pref-a (caq-action-preferred a)) 243 (pref-b (caq-action-preferred b)) 244 (pri-a (caq-action-priority a)) 245 (pri-b (caq-action-priority b))) 246 (cond 247 ((and pref-a (not pref-b)) t) 248 ((and pref-b (not pref-a)) nil) 249 ((< pri-a pri-b) t) 250 ((> pri-a pri-b) nil) 251 (t (string< (caq-action-title a) (caq-action-title b))))))))) 252 ;; Should be alphabetical 253 (should (equal "Apple" (caq-action-title (nth 0 sorted)))) 254 (should (equal "Mango" (caq-action-title (nth 1 sorted)))) 255 (should (equal "Zebra" (caq-action-title (nth 2 sorted)))))) 256 257 (ert-deftest caq-test-sorting-preferred-always-first () 258 "Test that preferred actions always come first." 259 (let* ((action1 (make-caq-action :title "Low priority preferred" :kind "refactor.rewrite" :preferred t :priority 1)) 260 (action2 (make-caq-action :title "High priority not preferred" :kind "quickfix" :preferred nil :priority 0)) 261 (actions (list action1 action2)) 262 (sorted (sort (copy-sequence actions) 263 (lambda (a b) 264 (let ((pref-a (caq-action-preferred a)) 265 (pref-b (caq-action-preferred b)) 266 (pri-a (caq-action-priority a)) 267 (pri-b (caq-action-priority b))) 268 (cond 269 ((and pref-a (not pref-b)) t) 270 ((and pref-b (not pref-a)) nil) 271 ((< pri-a pri-b) t) 272 ((> pri-a pri-b) nil) 273 (t (string< (caq-action-title a) (caq-action-title b))))))))) 274 ;; Preferred should come first despite lower kind priority 275 (should (equal "Low priority preferred" (caq-action-title (nth 0 sorted)))))) 276 277 ;;; ============================================================================ 278 ;;; Feature Availability Tests 279 ;;; ============================================================================ 280 281 (ert-deftest caq-test-eglot-not-loaded () 282 "Test behavior when eglot is not loaded." 283 (with-temp-buffer 284 (setq-local caq--detected-client nil) 285 ;; When eglot functions don't exist, should return nil 286 (cl-letf (((symbol-function 'fboundp) 287 (lambda (sym) (not (memq sym '(eglot-current-server)))))) 288 ;; This won't fully work since we can't mock fboundp completely 289 ;; but we can test that detection handles missing functions 290 (should (or (null (caq--detect-lsp-client)) 291 (caq--detect-lsp-client)))))) 292 293 (ert-deftest caq-test-no-lsp-active () 294 "Test that nil is returned when no LSP is active." 295 (with-temp-buffer 296 (setq-local caq--detected-client nil) 297 ;; In a temp buffer with no LSP, detection should return nil 298 (let ((result (caq--detect-lsp-client))) 299 (should (null result))))) 300 301 ;;; ============================================================================ 302 ;;; Integration Tests (requires eglot + rust-analyzer) 303 ;;; ============================================================================ 304 305 (defvar caq-test-results nil 306 "Accumulated test results.") 307 308 (defun caq-test--wait-for-lsp (timeout) 309 "Wait up to TIMEOUT seconds for LSP to be ready." 310 (let ((start (current-time)) 311 (ready nil)) 312 (while (and (not ready) 313 (< (float-time (time-subtract (current-time) start)) timeout)) 314 (setq ready (caq--detect-lsp-client)) 315 (unless ready 316 (sleep-for 0.5))) 317 ready)) 318 319 (defun caq-test--run-fixture (fixture-name expected-kind expected-title-pattern) 320 "Test a fixture and return result plist." 321 (let* ((fixture-dir (expand-file-name fixture-name caq-test-fixtures-dir)) 322 (main-file (expand-file-name "src/main.rs" fixture-dir))) 323 (if (not (file-exists-p main-file)) 324 (list :fixture fixture-name :status 'error :message "File not found") 325 (condition-case err 326 (caq-test--run-fixture-impl fixture-name main-file expected-kind expected-title-pattern) 327 (error 328 (list :fixture fixture-name :status 'error :message (error-message-string err))))))) 329 330 (defun caq-test--run-fixture-impl (fixture-name main-file expected-kind expected-title-pattern) 331 "Implementation of fixture test runner." 332 (let ((buf (find-file-noselect main-file)) 333 (result nil)) 334 (unwind-protect 335 (with-current-buffer buf 336 (when (fboundp 'eglot-ensure) 337 (eglot-ensure)) 338 (if (not (caq-test--wait-for-lsp caq-test-timeout)) 339 (setq result (list :fixture fixture-name :status 'error :message "LSP timeout")) 340 (let ((actions (caq--collect-all-actions))) 341 (if (null actions) 342 (setq result (list :fixture fixture-name :status 'fail :message "No actions found")) 343 (setq result (caq-test--match-action fixture-name actions expected-kind expected-title-pattern)))))) 344 (kill-buffer buf)) 345 result)) 346 347 (defun caq-test--match-action (fixture-name actions expected-kind expected-title-pattern) 348 "Check if ACTIONS contains expected action, return result plist." 349 (let ((found nil)) 350 (dolist (a actions) 351 (when (and (not found) 352 (or (null expected-kind) 353 (equal expected-kind (caq-action-kind a)) 354 (string-prefix-p (concat expected-kind ".") (or (caq-action-kind a) ""))) 355 (or (null expected-title-pattern) 356 (string-match-p expected-title-pattern (caq-action-title a)))) 357 (setq found a))) 358 (if found 359 (list :fixture fixture-name 360 :status 'pass 361 :action-title (caq-action-title found) 362 :action-kind (caq-action-kind found)) 363 (list :fixture fixture-name 364 :status 'fail 365 :message "Expected action not found" 366 :available (mapcar (lambda (a) 367 (format "[%s] %s" (caq-action-kind a) (caq-action-title a))) 368 actions))))) 369 370 (defun caq-test-run-integration () 371 "Run integration tests on all fixtures." 372 (interactive) 373 (setq caq-test-results nil) 374 (let ((fixtures 375 '(("missing-import" "quickfix" "Import.*HashMap") 376 ("unused-import" "quickfix" "Remove unused") 377 ("missing-impl" "quickfix" "Implement missing") 378 ("missing-module" "quickfix" "Create module") 379 ("glob-import" "refactor.rewrite" "Expand glob") 380 ("merge-imports" "refactor.rewrite" "Merge imports")))) 381 (dolist (fixture fixtures) 382 (message "Testing fixture: %s" (car fixture)) 383 (let ((result (apply #'caq-test--run-fixture fixture))) 384 (push result caq-test-results) 385 (message " Result: %s" (plist-get result :status)))) 386 (caq-test--print-results))) 387 388 (defun caq-test--print-results () 389 "Print test results summary." 390 (let ((pass 0) (fail 0) (err 0)) 391 (message "\n========== TEST RESULTS ==========") 392 (dolist (r (reverse caq-test-results)) 393 (let ((status (plist-get r :status)) 394 (fixture (plist-get r :fixture))) 395 (pcase status 396 ('pass 397 (cl-incf pass) 398 (message "PASS %s: %s" fixture (plist-get r :action-title))) 399 ('fail 400 (cl-incf fail) 401 (message "FAIL %s: %s" fixture (plist-get r :message)) 402 (when (plist-get r :available) 403 (message " Available: %S" (plist-get r :available)))) 404 ('error 405 (cl-incf err) 406 (message "ERROR %s: %s" fixture (plist-get r :message)))))) 407 (message "==================================") 408 (message "PASS: %d FAIL: %d ERROR: %d" pass fail err) 409 (list :pass pass :fail fail :error err))) 410 411 ;;; ============================================================================ 412 ;;; Minor Mode Indicator Tests 413 ;;; ============================================================================ 414 415 (ert-deftest caq-test-set-indicator-with-actions () 416 "Test that set-indicator creates correct propertized string." 417 (with-temp-buffer 418 (let* ((action (make-caq-action 419 :title "Import foo" 420 :kind "quickfix" 421 :preferred t 422 :priority 0 423 :raw nil 424 :client 'eglot)) 425 (caq-indicator-format " 💡%s") 426 (caq-indicator-max-length 30)) 427 (caq--set-indicator (list action)) 428 (should caq--mode-line-indicator) 429 (should (string-match-p "💡Import foo" caq--mode-line-indicator)) 430 (should (eq (get-text-property 0 'face caq--mode-line-indicator) 431 'caq-indicator-face)) 432 (should (get-text-property 0 'local-map caq--mode-line-indicator)) 433 (should (string-match-p "mouse-1" (get-text-property 0 'help-echo caq--mode-line-indicator)))))) 434 435 (ert-deftest caq-test-set-indicator-no-actions () 436 "Test that set-indicator clears indicator when no actions." 437 (with-temp-buffer 438 (setq caq--mode-line-indicator "previous value") 439 (caq--set-indicator nil) 440 (should-not caq--mode-line-indicator))) 441 442 (ert-deftest caq-test-indicator-truncation () 443 "Test that long action titles are truncated." 444 (with-temp-buffer 445 (let* ((action (make-caq-action 446 :title "This is a very long action title that should be truncated" 447 :kind "quickfix" 448 :preferred nil 449 :priority 0 450 :raw nil 451 :client 'eglot)) 452 (caq-indicator-format " 💡%s") 453 (caq-indicator-max-length 20)) 454 (caq--set-indicator (list action)) 455 (should caq--mode-line-indicator) 456 ;; Should be truncated with ellipsis 457 (should (string-match-p "…" caq--mode-line-indicator)) 458 ;; Total length should respect max 459 (should (<= (length (format caq-indicator-format "")) 460 (+ (length caq--mode-line-indicator) 1)))))) 461 462 (ert-deftest caq-test-clear-indicator () 463 "Test that clear-indicator works." 464 (with-temp-buffer 465 (setq caq--mode-line-indicator "some indicator") 466 (caq--clear-indicator) 467 (should-not caq--mode-line-indicator))) 468 469 (ert-deftest caq-test-minor-mode-enable-disable () 470 "Test that minor mode enable/disable works." 471 (with-temp-buffer 472 ;; Enable 473 (code-action-quick-mode 1) 474 (should code-action-quick-mode) 475 (should (memq #'caq--schedule-indicator-update post-command-hook)) 476 ;; Disable 477 (code-action-quick-mode -1) 478 (should-not code-action-quick-mode) 479 (should-not (memq #'caq--schedule-indicator-update post-command-hook)) 480 (should-not caq--indicator-timer))) 481 482 (ert-deftest caq-test-handle-indicator-actions () 483 "Test the action handling callback." 484 (with-temp-buffer 485 ;; Mock lsp-mode for normalization 486 (provide 'lsp-mode) 487 (setq-local lsp-mode t) 488 (setq-local caq--detected-client nil) 489 490 ;; Define mock lsp functions 491 (cl-letf (((symbol-function 'lsp-workspaces) (lambda () '(mock))) 492 ((symbol-function 'lsp:code-action-title) (lambda (a) (plist-get a :title))) 493 ((symbol-function 'lsp:code-action-kind) (lambda (a) (plist-get a :kind))) 494 ((symbol-function 'lsp:code-action-is-preferred) (lambda (a) (plist-get a :isPreferred)))) 495 ;; Test with quickfix action (allowed) 496 (caq--handle-indicator-actions 497 (list (list :title "Import bar" :kind "quickfix" :isPreferred t))) 498 (should caq--mode-line-indicator) 499 (should (string-match-p "Import bar" caq--mode-line-indicator)) 500 501 ;; Test with only rejected action 502 (caq--handle-indicator-actions 503 (list (list :title "Organize" :kind "source.organizeImports"))) 504 (should-not caq--mode-line-indicator)))) 505 506 ;;; ============================================================================ 507 ;;; Lookback Tests 508 ;;; ============================================================================ 509 510 (ert-deftest caq-test-indicator-uses-lookback () 511 "Test that indicator respects lookback when cursor moves. 512 Reproducer: when moving right or to next line from a position with actions, 513 the actions should still be found via lookback." 514 (with-temp-buffer 515 (insert "line 1 with error\n") 516 (insert "line 2 continues here") 517 ;; Setup: pretend we have actions anywhere on line 1 518 (let* ((caq-lookback-lines 1) 519 (line1-end (save-excursion (goto-char (point-min)) (end-of-line) (point)))) 520 ;; Mock the LSP functions to return actions for positions on line 1 521 (cl-letf (((symbol-function 'caq--detect-lsp-client) (lambda () 'eglot)) 522 ((symbol-function 'caq--get-actions-eglot) 523 (lambda (beg _end) 524 ;; Return action only for positions on line 1 525 (when (<= beg line1-end) 526 '((:title "Fix error" :kind "quickfix" :isPreferred t)))))) 527 ;; Test 1: On line 1, should find action 528 (goto-char (point-min)) 529 (let ((result (caq--collect-all-actions))) 530 (should (= 1 (length (plist-get result :allowed))))) 531 ;; Test 2: Move to end of line 1 - should still find action 532 (end-of-line) 533 (let ((result (caq--collect-all-actions))) 534 (should (= 1 (length (plist-get result :allowed))))) 535 ;; Test 3: On line 2 - should find via lookback to line 1 536 (goto-char (point-max)) 537 (let ((result (caq--collect-all-actions))) 538 (should (= 1 (length (plist-get result :allowed))))))))) 539 540 (ert-deftest caq-test-indicator-fetch-uses-lookback () 541 "Test that caq--fetch-actions-for-indicator uses lookback positions." 542 (with-temp-buffer 543 (insert "line 1 with error\nline 2") 544 (let* ((caq-lookback-lines 1) 545 (line1-end (save-excursion (goto-char (point-min)) (end-of-line) (point))) 546 (action-positions nil)) 547 ;; Mock to track which positions are queried 548 (cl-letf (((symbol-function 'caq--detect-lsp-client) (lambda () 'eglot)) 549 ((symbol-function 'caq--get-actions-eglot) 550 (lambda (beg _end) 551 (push beg action-positions) 552 (when (<= beg line1-end) 553 '((:title "Fix" :kind "quickfix")))))) 554 ;; Position on line 2 555 (goto-char (point-max)) 556 ;; Fetch for indicator 557 (caq--fetch-actions-for-indicator) 558 559 ;; Verify lookback positions were checked (including line 1) 560 (should (cl-some (lambda (p) (<= p line1-end)) action-positions)) 561 ;; Indicator should show the action 562 (should caq--mode-line-indicator) 563 (should (string-match-p "Fix" caq--mode-line-indicator)))))) 564 565 ;;; ============================================================================ 566 ;;; Run all tests 567 ;;; ============================================================================ 568 569 (defun caq-test-run-all () 570 "Run all tests (unit + integration if LSP available)." 571 (interactive) 572 (message "Running unit tests...") 573 (ert-run-tests-batch "^caq-test-") 574 (message "\nUnit tests complete.") 575 (when (and (fboundp 'eglot-ensure) 576 (executable-find "rust-analyzer")) 577 (message "\nRunning integration tests (requires rust-analyzer)...") 578 (caq-test-run-integration))) 579 580 (defun caq-test-run-unit () 581 "Run only unit tests (no LSP required)." 582 (interactive) 583 (ert-run-tests-interactively "^caq-test-")) 584 585 (provide 'test-code-action-quick) 586 ;;; test-code-action-quick.el ends here