├── README.md └── tdd.el /README.md: -------------------------------------------------------------------------------- 1 | A simple global minor mode for Emacs. It runs `recompile` (or a 2 | customisable function) after saving a buffer, and indicates success or 3 | failure in the mode line. The idea is to use `M-x compile` to run 4 | tests, and use this mode for test-driven development. 5 | 6 | - Write a test and save the file 7 | - Watch the test fail as the status line indicator turns red 8 | - Write code and save the file until the status line turns green 9 | - Repeat 10 | -------------------------------------------------------------------------------- /tdd.el: -------------------------------------------------------------------------------- 1 | ;;; tdd.el --- run tests on save and indicate success in the mode line 2 | 3 | ;; Copyright (C) 2014 Jorgen Schaefer 4 | 5 | ;; Author: Jorgen Schaefer 6 | ;; URL: https://github.com/jorgenschaefer/emacs-tdd 7 | ;; Version: 1.0 8 | ;; Keywords: tools, processes 9 | 10 | ;; This program is free software; you can redistribute it and/or 11 | ;; modify it under the terms of the GNU General Public License 12 | ;; as published by the Free Software Foundation; either version 3 13 | ;; of the License, or (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; After enabling `tdd-mode', any command to save a file will run 26 | ;; `recompile' (or a customisable function) in the background. The 27 | ;; mode line shows the status of the last compilation process. 28 | 29 | ;; This is meant to be used with test-driven development: 30 | 31 | ;; - Write a test and save the file 32 | ;; - Watch the test fail as the status line indicator turns red 33 | ;; - Write code and save the file until the status line turns green 34 | ;; - Repeat 35 | 36 | ;;; Code: 37 | 38 | (require 'compile) 39 | 40 | (defgroup tdd nil 41 | "Test-Driven Development Indicator." 42 | :prefix "tdd-" 43 | :group 'productivity) 44 | 45 | (defvar tdd-mode-line-map (let ((map (make-sparse-keymap))) 46 | (define-key map [mode-line mouse-1] 47 | 'tdd-display-buffer) 48 | map) 49 | "Keymap used on the mode line indicator.") 50 | 51 | (defcustom tdd-test-function #'recompile 52 | "Test function to run. 53 | 54 | It will be run without arguments, whenever a buffer is saved. It 55 | should run in compilation major mode, because checking for 56 | success or failure depends the mode hooks. 57 | 58 | The default is (recompile)" 59 | :type 'function 60 | :group 'tdd) 61 | 62 | (defcustom tdd-success-symbol "✔" 63 | "Mode line symbol to show when tests passed." 64 | :type 'string 65 | :group 'tdd) 66 | 67 | (defcustom tdd-success-face 'compilation-mode-line-exit 68 | "Face to use for `tdd-success-symbol'." 69 | :type 'face 70 | :group 'tdd) 71 | 72 | (defcustom tdd-failure-symbol "✖" 73 | "Mode line symbol to show when tests failed." 74 | :type 'string 75 | :group 'tdd) 76 | 77 | (defcustom tdd-failure-face 'compilation-mode-line-fail 78 | "Face to use for `tdd-failure-symbol'." 79 | :type 'face 80 | :group 'tdd) 81 | 82 | (defcustom tdd-waiting-symbol "✱" 83 | "Mode line symbol to show when tests are running." 84 | :type 'string 85 | :group 'tdd) 86 | 87 | (defcustom tdd-waiting-face 'compilation-mode-line-run 88 | "Face to use for `tdd-waiting-symbol'." 89 | :type 'face 90 | :group 'tdd) 91 | 92 | (defvar tdd-mode-line-format "" 93 | "The mode line entry for the TDD indicator.") 94 | (put 'tdd-mode-line-format 'risky-local-variable 95 | 'do-show-properties-in-mode-line) 96 | 97 | (defvar tdd-compilation-in-progress nil 98 | "Non-nil if we already started a compilation process. 99 | 100 | Sadly, `get-buffer-process' does not work for preventing 101 | duplicate compilation runs.") 102 | 103 | ;;;###autoload 104 | (define-minor-mode tdd-mode 105 | "Test-driven development global minor mode. 106 | 107 | Runs `tdd-test-function' every time a buffer is saved, and 108 | adjusts a mode line indicator depending on the success or failure 109 | of that compilation command." 110 | :global t 111 | (cond 112 | (tdd-mode 113 | (tdd-add-mode-line-format) 114 | (tdd-success) 115 | (add-hook 'compilation-finish-functions 'tdd-compilation-finish) 116 | (add-hook 'compilation-start-hook 'tdd-compilation-start) 117 | (add-hook 'after-save-hook 'tdd-after-save)) 118 | (t 119 | (tdd-remove-mode-line-format) 120 | (setq tdd-mode-line-format "") 121 | (remove-hook 'compilation-finish-functions 'tdd-compilation-finish) 122 | (remove-hook 'compilation-start-hook 'tdd-compilation-start) 123 | (remove-hook 'after-save-hook 'tdd-after-save)))) 124 | 125 | (defun tdd-success () 126 | "Set the TDD indicator to green." 127 | (interactive) 128 | (setq tdd-mode-line-format 129 | (propertize tdd-success-symbol 130 | 'face tdd-success-face 131 | 'keymap tdd-mode-line-map 132 | 'mouse-face 'mode-line-highlight 133 | 'help-echo (concat "Tests succeeded\n" 134 | "mouse-1: Switch to test buffer")))) 135 | 136 | (defun tdd-failure () 137 | "Set the TDD indicator to red." 138 | (interactive) 139 | (setq tdd-mode-line-format 140 | (propertize tdd-failure-symbol 141 | 'face tdd-failure-face 142 | 'keymap tdd-mode-line-map 143 | 'mouse-face 'mode-line-highlight 144 | 'help-echo (concat "Tests running\n" 145 | "mouse-1: Switch to test buffer")))) 146 | 147 | (defun tdd-waiting () 148 | "Set the TDD indicator to mark an ongoing compilation run." 149 | (interactive) 150 | (setq tdd-mode-line-format 151 | (propertize tdd-waiting-symbol 152 | 'face tdd-waiting-face 153 | 'keymap tdd-mode-line-map 154 | 'mouse-face 'mode-line-highlight 155 | 'help-echo (concat "Tests failed\n" 156 | "mouse-1: Switch to test buffer")))) 157 | 158 | (defun tdd-display-buffer () 159 | "Display the compilation buffer." 160 | (interactive) 161 | (let ((compilation-buffer (get-buffer 162 | (compilation-buffer-name "compilation" 163 | nil nil)))) 164 | (when compilation-buffer 165 | (display-buffer compilation-buffer)))) 166 | 167 | (defun tdd-add-mode-line-format () 168 | "Add `tdd-mode-line-format' to `mode-line-format'." 169 | (let ((global-mode-line (default-value 'mode-line-format))) 170 | (when (not (memq 'tdd-mode-line-format global-mode-line)) 171 | (setq-default mode-line-format 172 | (cons (car global-mode-line) 173 | (cons 'tdd-mode-line-format 174 | (cdr global-mode-line))))))) 175 | 176 | (defun tdd-remove-mode-line-format () 177 | "Add `tdd-mode-line-format' to `mode-line-format'." 178 | (let ((global-mode-line (default-value 'mode-line-format))) 179 | (when (memq 'tdd-mode-line-format global-mode-line) 180 | (setq-default mode-line-format 181 | (delq 'tdd-mode-line-format 182 | global-mode-line))))) 183 | 184 | (defun tdd-after-save () 185 | "Function run in `after-save-hook' to start the compilation." 186 | (when (not tdd-compilation-in-progress) 187 | (setq tdd-compilation-in-progress t) 188 | (let ((compilation-ask-about-save nil) 189 | (compilation-save-buffers-predicate (lambda () nil))) 190 | (save-window-excursion 191 | (funcall tdd-test-function))))) 192 | 193 | (defun tdd-compilation-start (proc) 194 | "Function run from `compilation-start-hook'." 195 | (setq tdd-compilation-in-progress t) 196 | (tdd-waiting)) 197 | 198 | (defun tdd-compilation-finish (buf msg) 199 | "Function run from `compilation-finish-functions'." 200 | (setq tdd-compilation-in-progress nil) 201 | (if (string-match "exited abnormally" msg) 202 | (tdd-failure) 203 | (tdd-success))) 204 | 205 | (provide 'tdd) 206 | ;;; tdd.el ends here 207 | --------------------------------------------------------------------------------