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))))

Reply via email to