branch: elpa/aidermacs commit 1b4357383bc71f6ba699506ce2ba9eda2ddeac4d Author: Kang Tu <tni...@gmail.com> Commit: GitHub <nore...@github.com>
Feat: Write unit tests (#46) * test: Update buffer name test to use magit-toplevel * test: Add comprehensive test cases for aider.el functions * test: Update test comments to use English * fix: Remove unnecessary tilde in aider buffer name generation * feat: Add aider-write-test function for generating tests with transient menu integration * refactor: Reorder menu items and move test-related functions * refactor: Rename and enhance unit test generation function with more specific prompts * docs: Add aider-write-unit-test function to TDD documentation * style(keyboard): change Write Unit Test shortcut from 'w' to 'U' * feat: Add command sending function without auto-adding current file * feat: Add `aider-general-question` function and transient menu item * refactor: Reorder menu items in aider.el for better organization * feat: Add function context to aider-ask-question when inside a function * docs: Update commit message for "Ask Question" menu item * feat: Enhance aider-ask-question with function context in initial prompt * style(format): fix indentation in aider-ask-with-context --- README.org | 1 + aider.el | 66 +++++++++++++++++++++++----- test_aider.el | 135 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 172 insertions(+), 30 deletions(-) diff --git a/README.org b/README.org index b6e608c356..e04ee88fde 100644 --- a/README.org +++ b/README.org @@ -40,6 +40,7 @@ - (`aider-explain-symbol-under-point`): Ask Aider to explain the symbol under cursor, given the line as context. *** Support for Test Driven Development: + - (`aider-write-unit-test`): Generate comprehensive unit tests for the current function or file. The generated tests will include normal cases, edge cases, and error handling scenarios. - (`aider-fix-failing-test-under-cursor`): Place cursor on a failing test function and ask Aider to analyze and fix the code to make tests pass. *** And More: diff --git a/aider.el b/aider.el index 603c391010..fb5866a351 100644 --- a/aider.el +++ b/aider.el @@ -123,12 +123,13 @@ Affects the system message too.") ("t" "Architect Discuss and Change" aider-architect-discussion) ("c" "Code Change" aider-code-change) ("r" "Refactor Function or Region" aider-function-or-region-refactor) + ("U" "Write Unit Test" aider-write-unit-test) ("T" "Fix Failing Test Under Cursor" aider-fix-failing-test-under-cursor) ("m" "Show Last Commit with Magit" aider-magit-show-last-commit) ("u" "Undo Last Change" aider-undo-last-change) ] ["Discussion" - ("q" "Ask Question" aider-ask-question) + ("q" "Ask Question given Context" aider-ask-question) ("y" "Go Ahead" aider-go-ahead) ("e" "Explain Function or Region" aider-function-or-region-explain) ("p" "Explain Symbol Under Point" aider-explain-symbol-under-point) @@ -136,6 +137,7 @@ Affects the system message too.") ] ["Other" ("g" "General Command" aider-general-command) + ("Q" "Ask General Question" aider-general-question) ("h" "Help" aider-help) ] ]) @@ -149,7 +151,7 @@ If not in a git repository, an error is raised." (let ((git-repo-path (magit-toplevel))) (if (string-match-p "fatal" git-repo-path) (error "Not in a git repository") - (format "*aider:~%s*" git-repo-path)))) + (format "*aider:%s*" git-repo-path)))) (defun aider--inherit-source-highlighting (source-buffer) "Inherit syntax highlighting settings from SOURCE-BUFFER." @@ -325,7 +327,6 @@ COMMAND should be a string representing the command to send." (interactive) (let ((command (aider-read-string "Enter command to send to aider: "))) ;; Use the shared helper function to send the command - (aider-add-current-file) (aider--send-command command t))) ;; New function to get command from user and send it prefixed with "/code " @@ -340,14 +341,27 @@ COMMAND should be a string representing the command to send." ;;;###autoload (defun aider-ask-question () "Prompt the user for a command and send it to the corresponding aider comint buffer prefixed with \"/ask \". -If a region is active, append the region text to the question." +If a region is active, append the region text to the question. +If cursor is inside a function, include the function name as context." + (interactive) + (let* ((function-name (which-function)) + (initial-input (when function-name + (format "About function '%s': " function-name))) + (question (aider-read-string "Enter question to ask: " initial-input)) + (region-text (and (region-active-p) + (buffer-substring-no-properties (region-beginning) (region-end)))) + (command (if region-text + (format "/ask %s: %s" question region-text) + (format "/ask %s" question)))) + (aider-add-current-file) + (aider--send-command command t))) + +;;;###autoload +(defun aider-general-question () + "Prompt the user for a general question and send it to the corresponding aider comint buffer prefixed with \"/ask \"." (interactive) - (let ((question (aider-read-string "Enter question to ask: ")) - (region-text (and (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end))))) - (let ((command (if region-text - (format "/ask %s: %s" question region-text) - (format "/ask %s" question)))) - (aider-add-current-file) + (let ((question (aider-read-string "Enter general question to ask: "))) + (let ((command (format "/ask %s" question))) (aider--send-command command t)))) ;; New function to get command from user and send it prefixed with "/help " @@ -541,6 +555,38 @@ If there are more than 40 files, refuse to add and show warning message." ;;; functions for test fixing +;;;###autoload +(defun aider-write-unit-test () + "Generate unit test code for current buffer. +Do nothing if current buffer is not visiting a file. +If current buffer filename contains 'test', do nothing. +If cursor is on a function, generate unit test for that function. +Otherwise, generate unit tests for the entire file." + (interactive) + (if (not buffer-file-name) + (message "Current buffer is not visiting a file.") + (if (string-match-p "test" (file-name-nondirectory buffer-file-name)) + (message "Current buffer appears to be a test file.") + (let* ((function-name (which-function)) + (initial-input + (if function-name + (format "Please write unit test code for function '%s'. Include test cases for: +1. Normal input/output scenarios +2. Edge cases and boundary conditions +3. Error handling and invalid inputs +Make the test comprehensive but maintainable. Follow standard unit testing practices." + function-name) + (format "Please write unit test code for file '%s'. For each function include test cases for: +1. Normal input/output scenarios +2. Edge cases and boundary conditions +3. Error handling and invalid inputs +Make the tests comprehensive but maintainable. Follow standard unit testing practices." + (file-name-nondirectory buffer-file-name)))) + (user-command (aider-read-string "Unit test generation instruction: " initial-input)) + (command (format "/architect %s" user-command))) + (aider-add-current-file) + (aider--send-command command t))))) + ;;;###autoload (defun aider-fix-failing-test-under-cursor () "Report the current test failure to aider and ask it to fix the code. diff --git a/test_aider.el b/test_aider.el index db4060bc2f..b7e1be181d 100644 --- a/test_aider.el +++ b/test_aider.el @@ -1,23 +1,118 @@ - (require 'ert) -(ert-deftest aider-buffer-name-from-git-repo-path-test () - "Test the aider-buffer-name-from-git-repo-path function." - (should (equal (aider-buffer-name-from-git-repo-path "/Users/username/git/repo" "/Users/username") - "*aider:~/git/repo*")) - (should (equal (aider-buffer-name-from-git-repo-path "/home/username/git/repo" "/home/username") - "*aider:~/git/repo*")) - (should (equal (aider-buffer-name-from-git-repo-path "/Users/username/git/repo/subdir" "/Users/username") - "*aider:~/git/repo/subdir*")) - (should (equal (aider-buffer-name-from-git-repo-path "/home/username/git/repo/subdir" "/home/username") - "*aider:~/git/repo/subdir*"))) - -(ert-deftest test-aider-region-refactor-generate-command () +(ert-deftest aider-buffer-name-test () + "Test the aider-buffer-name function." + (cl-letf (((symbol-function 'magit-toplevel) + (lambda () "/path/to/git/repo"))) + (should (equal (aider-buffer-name) "*aider:/path/to/git/repo*"))) + + ;; Test error case when not in a git repo + (cl-letf (((symbol-function 'magit-toplevel) + (lambda () "fatal: not a git repository"))) + (should-error (aider-buffer-name) :type 'error))) + +(ert-deftest aider--process-message-if-multi-line-test () + "Test the aider--process-message-if-multi-line function." + ;; Test single line text + (should (equal (aider--process-message-if-multi-line "single line") + "single line")) + ;; Test multi-line text + (should (equal (aider--process-message-if-multi-line "line 1\nline 2") + "{aider\nline 1\nline 2\naider}")) + ;; Test empty string + (should (equal (aider--process-message-if-multi-line "") + "")) + ;; Test newline only + (should (equal (aider--process-message-if-multi-line "\n") + "{aider\n\n\naider}"))) + +(ert-deftest aider--get-add-command-prefix-test () + "Test the aider--get-add-command-prefix function." + ;; Test when read-only mode is off + (let ((aider--add-file-read-only nil)) + (should (equal (aider--get-add-command-prefix) "/add"))) + ;; Test when read-only mode is on + (let ((aider--add-file-read-only t)) + (should (equal (aider--get-add-command-prefix) "/read-only")))) + +(ert-deftest aider-region-refactor-generate-command-test () "Test the aider-region-refactor-generate-command function." - (should (equal (aider-region-refactor-generate-command "some code" - "my-function" "refactor this") - "/architect \"in function my-function, for the following code block, refactor this: some code\"\n")) - (should (equal (aider-region-refactor-generate-command "some code" nil - "make it more functional") - "/architect \"for the following code block, make it more functional: some code\"\n")) - ) + ;; Test with function name + (should (equal (aider-region-refactor-generate-command + "code block" + "test_func" + "make it better") + "/architect \"in function test_func, for the following code block, make it better: code block\"\n")) + + ;; Test without function name + (should (equal (aider-region-refactor-generate-command + "code block" + nil + "make it better") + "/architect \"for the following code block, make it better: code block\"\n")) + + ;; Test multi-line code block + (should (equal (aider-region-refactor-generate-command + "line 1\nline 2" + "test_func" + "make it better") + "/architect \"in function test_func, for the following code block, make it better: line 1\nline 2\"\n")) + + ;; Test empty code block + (should (equal (aider-region-refactor-generate-command + "" + "test_func" + "make it better") + "/architect \"in function test_func, for the following code block, make it better: \"\n"))) + +(ert-deftest aider-font-lock-keywords-test () + "Test the aider-font-lock-keywords variable." + ;; Ensure font-lock-keywords contains correct patterns and faces + (should (equal (car (car aider-font-lock-keywords)) + "^\x2500+\n?")) + (should (equal (nth 2 (car aider-font-lock-keywords)) + '(face aider-command-separator))) + (should (equal (car (cadr aider-font-lock-keywords)) + "^\x2500+")) + (should (equal (nth 2 (cadr aider-font-lock-keywords)) + '(face nil display (space :width 2))))) + +(ert-deftest aider-write-unit-test-behavior () + "Test the behavior of aider-write-unit-test function." + ;; Test when buffer is not visiting a file + (with-temp-buffer + (should (progn (aider-write-unit-test) + (equal (current-message) "Current buffer is not visiting a file.")))) + + ;; Test when buffer is a test file + (with-temp-buffer + (let ((buffer-file-name "test_something.py")) + (should (progn (aider-write-test) + (equal (current-message) "Current buffer appears to be a test file."))))) + + ;; Test when buffer is a regular file with no function under cursor + (with-temp-buffer + (let ((buffer-file-name "regular.py") + (aider-read-string-result "modified prompt")) + (cl-letf (((symbol-function 'which-function) (lambda () nil)) + ((symbol-function 'aider-read-string) + (lambda (prompt initial) aider-read-string-result)) + ((symbol-function 'aider-add-current-file) (lambda () t)) + ((symbol-function 'aider--send-command) (lambda (cmd switch) t))) + (aider-write-test) + (should (not (equal (current-message) "Current buffer is not visiting a file."))) + (should (not (equal (current-message) "Current buffer appears to be a test file."))))))) + +(ert-deftest aider-faces-test () + "Test that aider faces are properly defined." + ;; Test aider-command-separator face + (should (face-differs-from-default-p 'aider-command-separator)) + ;; Test aider-command-text face + (should (face-differs-from-default-p 'aider-command-text))) + +;; Helper function to test if a face differs from the default face +(defun face-differs-from-default-p (face) + "Check if FACE has different properties from default face." + (let ((face-props (face-all-attributes face nil)) + (default-props (face-all-attributes 'default nil))) + (not (equal face-props default-props))))