summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--init.el2
-rw-r--r--modules/jumper.el252
-rw-r--r--tests/test-jumper.el352
3 files changed, 529 insertions, 77 deletions
diff --git a/init.el b/init.el
index a06d25d6..1af73885 100644
--- a/init.el
+++ b/init.el
@@ -145,7 +145,7 @@
(require 'browser-config)
;;(require 'wip)
(require 'lorem-optimum)
-;;(require 'jumper)
+(require 'jumper)
;; ---------------------------------- Wrap Up ----------------------------------
diff --git a/modules/jumper.el b/modules/jumper.el
index 7a3991d0..67d930aa 100644
--- a/modules/jumper.el
+++ b/modules/jumper.el
@@ -10,24 +10,76 @@
;; Jumper provides a simple way to store and jump between locations
;; in your codebase without needing to remember register assignments.
+;;
+;; PURPOSE:
+;;
+;; When working on large codebases, you often need to jump between
+;; multiple related locations: a function definition, its tests, its
+;; callers, configuration files, etc. Emacs registers are perfect for
+;; this, but require you to remember which register you assigned to
+;; which location. Jumper automates register management, letting you
+;; focus on your work instead of bookkeeping.
+;;
+;; WORKFLOW:
+;;
+;; 1. Navigate to an important location in your code
+;; 2. Press M-SPC SPC to store it (automatically assigned to register 0)
+;; 3. Continue working, storing more locations as needed (registers 1-9)
+;; 4. Press M-SPC j to jump back to any stored location
+;; 5. Select from the list using completion (shows file, line, context)
+;; 6. Press M-SPC d to remove locations you no longer need
+;;
+;; RECOMMENDED USAGE:
+;;
+;; Store locations temporarily while working on a feature:
+;; - Store the main function you're implementing
+;; - Store the test file where you're writing tests
+;; - Store the caller that needs updating
+;; - Store the documentation that needs changes
+;; - Jump between them freely as you work
+;; - Clear them when done with the feature
+;;
+;; SPECIAL BEHAVIORS:
+;;
+;; - Duplicate prevention: Storing the same location twice shows a message
+;; instead of wasting a register slot.
+;;
+;; - Single location toggle: When only one location is stored, M-SPC j
+;; toggles between that location and your current position. Perfect for
+;; rapid back-and-forth between two related files.
+;;
+;; - Last location tracking: The last position before each jump is saved
+;; in register 'z', allowing quick "undo" of navigation.
+;;
+;; - Smart selection: With multiple locations, completing-read shows
+;; helpful context: "[0] filename.el:42 - function definition..."
+;;
+;; KEYBINDINGS:
+;;
+;; M-SPC SPC Store current location in next available register
+;; M-SPC j Jump to a stored location (with completion)
+;; M-SPC d Delete a stored location from the list
+;;
+;; CONFIGURATION:
+;;
+;; You can customize the prefix key and maximum locations:
+;;
+;; (setq jumper-prefix-key "C-c j") ; Change prefix key
+;; (setq jumper-max-locations 20) ; Store up to 20 locations
+;;
+;; Note: Changing jumper-max-locations requires restarting Emacs or
+;; manually reinitializing jumper--registers.
;;; Code:
-(defgroup jumper nil
- "Quick navigation between stored locations."
- :group 'convenience)
+(require 'cl-lib)
-(defcustom jumper-prefix-key "M-SPC"
+(defvar jumper-prefix-key "M-SPC"
"Prefix key for jumper commands.
+Note that using M-SPC will override the default binding to just-one-space.")
-Note that using M-SPC will override the default binding to just-one-space."
- :type 'string
- :group 'jumper)
-
-(defcustom jumper-max-locations 10
- "Maximum number of locations to store."
- :type 'integer
- :group 'jumper)
+(defvar jumper-max-locations 10
+ "Maximum number of locations to store.")
;; Internal variables
(defvar jumper--registers (make-vector jumper-max-locations nil)
@@ -50,12 +102,10 @@ Note that using M-SPC will override the default binding to just-one-space."
"Check if current location is already stored."
(let ((key (jumper--location-key))
(found nil))
- (dotimes (i
- jumper--next-index found)
+ (dotimes (i jumper--next-index found)
(let* ((reg (aref jumper--registers i))
- (pos (get-register reg))
- (marker (and pos (registerv-data pos))))
- (when marker
+ (marker (get-register reg)))
+ (when (and marker (markerp marker))
(save-current-buffer
(set-buffer (marker-buffer marker))
(save-excursion
@@ -70,9 +120,8 @@ Note that using M-SPC will override the default binding to just-one-space."
(defun jumper--format-location (index)
"Format location at INDEX for display."
(let* ((reg (aref jumper--registers index))
- (pos (get-register reg))
- (marker (and pos (registerv-data pos))))
- (when marker
+ (marker (get-register reg)))
+ (when (and marker (markerp marker))
(save-current-buffer
(set-buffer (marker-buffer marker))
(save-excursion
@@ -86,49 +135,83 @@ Note that using M-SPC will override the default binding to just-one-space."
(min (+ (line-beginning-position) 40)
(line-end-position)))))))))
+(defun jumper--do-store-location ()
+ "Store current location in the next free register.
+Returns: \\='already-exists if location is already stored,
+ \\='no-space if all registers are full,
+ register character if successfully stored."
+ (cond
+ ((jumper--location-exists-p) 'already-exists)
+ ((not (jumper--register-available-p)) 'no-space)
+ (t
+ (let ((reg (+ ?0 jumper--next-index)))
+ (point-to-register reg)
+ (aset jumper--registers jumper--next-index reg)
+ (setq jumper--next-index (1+ jumper--next-index))
+ reg))))
+
(defun jumper-store-location ()
"Store current location in the next free register."
(interactive)
- (if (jumper--location-exists-p)
- (message "Location already stored")
- (if (jumper--register-available-p)
- (let ((reg (+ ?0 jumper--next-index)))
- (point-to-register reg)
- (aset jumper--registers jumper--next-index reg)
- (setq jumper--next-index (1+ jumper--next-index))
- (message "Location stored in register %c" reg))
- (message "Sorry - all jump locations are filled!"))))
+ (pcase (jumper--do-store-location)
+ ('already-exists (message "Location already stored"))
+ ('no-space (message "Sorry - all jump locations are filled!"))
+ (reg (message "Location stored in register %c" reg))))
+
+(defun jumper--do-jump-to-location (target-idx)
+ "Jump to location at TARGET-IDX.
+TARGET-IDX: -1 for last location, 0-9 for stored locations, nil for toggle.
+Returns: \\='no-locations if no locations stored,
+ \\='already-there if at the only location (toggle case),
+ \\='jumped if successfully jumped."
+ (cond
+ ((= jumper--next-index 0) 'no-locations)
+ ;; Toggle behavior when target-idx is nil and only 1 location
+ ((and (null target-idx) (= jumper--next-index 1))
+ (if (jumper--location-exists-p)
+ 'already-there
+ (let ((reg (aref jumper--registers 0)))
+ (point-to-register jumper--last-location-register)
+ (jump-to-register reg)
+ 'jumped)))
+ ;; Jump to specific target
+ (t
+ (if (= target-idx -1)
+ ;; Jumping to last location - don't overwrite it
+ (jump-to-register jumper--last-location-register)
+ ;; Jumping to stored location - save current for "last"
+ (progn
+ (point-to-register jumper--last-location-register)
+ (jump-to-register (aref jumper--registers target-idx))))
+ 'jumped)))
(defun jumper-jump-to-location ()
"Jump to a stored location."
(interactive)
- (if (= jumper--next-index 0)
- (message "No locations stored")
- (if (= jumper--next-index 1)
- ;; Special case for one location - toggle behavior
- (let ((reg (aref jumper--registers 0)))
- (if (jumper--location-exists-p)
- (message "You're already at the stored location")
- (point-to-register jumper--last-location-register)
- (jump-to-register reg)
- (message "Jumped to location")))
- ;; Multiple locations - use completing-read
- (let* ((locations
- (cl-loop for i from 0 below jumper--next-index
- for fmt = (jumper--format-location i)
- when fmt collect (cons fmt i)))
- ;; Add last location if available
- (last-pos (get-register jumper--last-location-register))
- (locations (if last-pos
- (cons (cons "[z] Last location" -1) locations)
- locations))
- (choice (completing-read "Jump to: " locations nil t))
- (idx (cdr (assoc choice locations))))
- (point-to-register jumper--last-location-register)
- (if (= idx -1)
- (jump-to-register jumper--last-location-register)
- (jump-to-register (aref jumper--registers idx)))
- (message "Jumped to location")))))
+ (cond
+ ;; No locations
+ ((= jumper--next-index 0)
+ (message "No locations stored"))
+ ;; Single location - toggle
+ ((= jumper--next-index 1)
+ (pcase (jumper--do-jump-to-location nil)
+ ('already-there (message "You're already at the stored location"))
+ ('jumped (message "Jumped to location"))))
+ ;; Multiple locations - prompt user
+ (t
+ (let* ((locations
+ (cl-loop for i from 0 below jumper--next-index
+ for fmt = (jumper--format-location i)
+ when fmt collect (cons fmt i)))
+ ;; Add last location if available
+ (last-pos (get-register jumper--last-location-register))
+ (locations (if last-pos
+ (cons (cons "[z] Last location" -1) locations)
+ locations))
+ (choice (completing-read "Jump to: " locations nil t))
+ (idx (cdr (assoc choice locations))))
+ (jumper--do-jump-to-location idx)
+ (message "Jumped to location")))))
(defun jumper--reorder-registers (removed-idx)
"Reorder registers after removing the one at REMOVED-IDX."
@@ -139,30 +222,39 @@ Note that using M-SPC will override the default binding to just-one-space."
(aset jumper--registers i next-reg))))
(setq jumper--next-index (1- jumper--next-index)))
+(defun jumper--do-remove-location (index)
+ "Remove location at INDEX.
+Returns: \\='no-locations if no locations stored,
+ \\='cancelled if index is -1,
+ t if successfully removed."
+ (cond
+ ((= jumper--next-index 0) 'no-locations)
+ ((= index -1) 'cancelled)
+ (t
+ (jumper--reorder-registers index)
+ t)))
+
(defun jumper-remove-location ()
"Remove a stored location."
(interactive)
(if (= jumper--next-index 0)
- (message "No locations stored")
- (let* ((locations
- (cl-loop for i from 0 below jumper--next-index
- for fmt = (jumper--format-location i)
- when fmt collect (cons fmt i)))
- (locations (cons (cons "Cancel" -1) locations))
- (choice (completing-read "Remove location: " locations nil t))
- (idx (cdr (assoc choice locations))))
- (if (= idx -1)
- (message "Operation cancelled")
- (jumper--reorder-registers idx)
- (message "Location removed")))))
-
-(defvar jumper-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "SPC") #'jumper-store-location)
- (define-key map (kbd "j") #'jumper-jump-to-location)
- (define-key map (kbd "d") #'jumper-remove-location)
- map)
- "Keymap for jumper commands.")
+ (message "No locations stored")
+ (let* ((locations
+ (cl-loop for i from 0 below jumper--next-index
+ for fmt = (jumper--format-location i)
+ when fmt collect (cons fmt i)))
+ (locations (cons (cons "Cancel" -1) locations))
+ (choice (completing-read "Remove location: " locations nil t))
+ (idx (cdr (assoc choice locations))))
+ (pcase (jumper--do-remove-location idx)
+ ('cancelled (message "Operation cancelled"))
+ ('t (message "Location removed"))))))
+
+(defvar-keymap jumper-map
+ :doc "Keymap for jumper commands"
+ "SPC" #'jumper-store-location
+ "j" #'jumper-jump-to-location
+ "d" #'jumper-remove-location)
(defun jumper-setup-keys ()
"Setup default keybindings for jumper."
@@ -172,5 +264,13 @@ Note that using M-SPC will override the default binding to just-one-space."
;; Call jumper-setup-keys when the package is loaded
(jumper-setup-keys)
+;; which-key integration
+(with-eval-after-load 'which-key
+ (which-key-add-key-based-replacements
+ "M-SPC" "jumper menu"
+ "M-SPC SPC" "store location"
+ "M-SPC j" "jump to location"
+ "M-SPC d" "remove location"))
+
(provide 'jumper)
;;; jumper.el ends here.
diff --git a/tests/test-jumper.el b/tests/test-jumper.el
new file mode 100644
index 00000000..fa65d3f4
--- /dev/null
+++ b/tests/test-jumper.el
@@ -0,0 +1,352 @@
+;;; test-jumper.el --- Tests for jumper.el -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for jumper.el - location navigation using registers.
+;;
+;; Testing approach:
+;; - Tests focus on internal `jumper--do-*` functions (pure business logic)
+;; - Interactive wrappers are thin UI layers and tested minimally
+;; - Each test is isolated with setup/teardown to reset global state
+;; - Tests verify return values, not user messages
+
+;;; Code:
+
+(require 'ert)
+(require 'testutil-general)
+
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Load the module
+(require 'jumper)
+
+;;; Test Utilities
+
+(defvar test-jumper--original-registers nil
+ "Backup of jumper registers before test.")
+
+(defvar test-jumper--original-index nil
+ "Backup of jumper index before test.")
+
+(defun test-jumper-setup ()
+ "Reset jumper state before each test."
+ ;; Backup current state
+ (setq test-jumper--original-registers jumper--registers)
+ (setq test-jumper--original-index jumper--next-index)
+ ;; Reset to clean state
+ (setq jumper--registers (make-vector jumper-max-locations nil))
+ (setq jumper--next-index 0))
+
+(defun test-jumper-teardown ()
+ "Restore jumper state after each test."
+ (setq jumper--registers test-jumper--original-registers)
+ (setq jumper--next-index test-jumper--original-index))
+
+;;; Normal Cases - Store Location
+
+(ert-deftest test-jumper-store-first-location ()
+ "Should store first location and return register character."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test content")
+ (goto-char (point-min))
+ (let ((result (jumper--do-store-location)))
+ (should (= result ?0))
+ (should (= jumper--next-index 1))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-store-multiple-locations ()
+ "Should store multiple locations in sequence."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2\nline 3")
+ (goto-char (point-min))
+ (should (= (jumper--do-store-location) ?0))
+ (forward-line 1)
+ (should (= (jumper--do-store-location) ?1))
+ (forward-line 1)
+ (should (= (jumper--do-store-location) ?2))
+ (should (= jumper--next-index 3)))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-store-duplicate-location ()
+ "Should detect and reject duplicate locations."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test content")
+ (goto-char (point-min))
+ (should (= (jumper--do-store-location) ?0))
+ (should (eq (jumper--do-store-location) 'already-exists))
+ (should (= jumper--next-index 1)))
+ (test-jumper-teardown))
+
+;;; Normal Cases - Jump to Location
+
+(ert-deftest test-jumper-jump-to-stored-location ()
+ "Should jump to a previously stored location."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2\nline 3")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (goto-char (point-max))
+ (let ((result (jumper--do-jump-to-location 0)))
+ (should (eq result 'jumped))
+ (should (= (point) (point-min)))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-jump-toggle-with-single-location ()
+ "Should toggle between current and stored location."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2\nline 3")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ ;; Move away
+ (goto-char (point-max))
+ ;; Toggle should jump back
+ (let ((result (jumper--do-jump-to-location nil)))
+ (should (eq result 'jumped))
+ (should (= (point) (point-min)))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-jump-already-at-location ()
+ "Should detect when already at the only stored location."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ ;; Try to toggle while at the location
+ (let ((result (jumper--do-jump-to-location nil)))
+ (should (eq result 'already-there))))
+ (test-jumper-teardown))
+
+;;; Normal Cases - Remove Location
+
+(ert-deftest test-jumper-remove-location ()
+ "Should remove a stored location."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test content")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (let ((result (jumper--do-remove-location 0)))
+ (should (eq result t))
+ (should (= jumper--next-index 0))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-remove-reorders-registers ()
+ "Should reorder registers after removal from middle."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2\nline 3")
+ (goto-char (point-min))
+ (jumper--do-store-location) ; Register 0
+ (forward-line 1)
+ (jumper--do-store-location) ; Register 1
+ (forward-line 1)
+ (jumper--do-store-location) ; Register 2
+ ;; Remove middle (index 1)
+ (jumper--do-remove-location 1)
+ (should (= jumper--next-index 2))
+ ;; What was at index 2 should now be at index 1
+ (should (= (aref jumper--registers 1) ?2)))
+ (test-jumper-teardown))
+
+;;; Boundary Cases - Store Location
+
+(ert-deftest test-jumper-store-at-capacity ()
+ "Should successfully store location at maximum capacity."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test content")
+ (goto-char (point-min))
+ ;; Fill to capacity
+ (dotimes (i jumper-max-locations)
+ (forward-char 1)
+ (should (= (jumper--do-store-location) (+ ?0 i))))
+ (should (= jumper--next-index jumper-max-locations)))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-store-when-full ()
+ "Should return 'no-space when all registers are full."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "01234567890123456789")
+ (goto-char (point-min))
+ ;; Fill to capacity
+ (dotimes (i jumper-max-locations)
+ (forward-char 1)
+ (jumper--do-store-location))
+ ;; Try to store one more
+ (forward-char 1)
+ (should (eq (jumper--do-store-location) 'no-space))
+ (should (= jumper--next-index jumper-max-locations)))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-store-in-different-buffers ()
+ "Should store locations across different buffers."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "buffer 1")
+ (goto-char (point-min))
+ (should (= (jumper--do-store-location) ?0))
+ (with-temp-buffer
+ (insert "buffer 2")
+ (goto-char (point-min))
+ (should (= (jumper--do-store-location) ?1))
+ (should (= jumper--next-index 2))))
+ (test-jumper-teardown))
+
+;;; Boundary Cases - Jump to Location
+
+(ert-deftest test-jumper-jump-with-no-locations ()
+ "Should return 'no-locations when nothing is stored."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test")
+ (let ((result (jumper--do-jump-to-location 0)))
+ (should (eq result 'no-locations))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-jump-to-first-location ()
+ "Should jump to location at index 0."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (forward-line 1)
+ (jumper--do-store-location)
+ (goto-char (point-max))
+ (jumper--do-jump-to-location 0)
+ (should (= (point) (point-min))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-jump-to-last-location ()
+ "Should jump to last location (register 'z)."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2\nline 3")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (let ((line2-pos (line-beginning-position 2)))
+ (goto-char line2-pos)
+ ;; Jump to location 0 (this stores current location in 'z)
+ (jumper--do-jump-to-location 0)
+ (should (= (point) (point-min)))
+ ;; Jump to last location should go back to line 2
+ (let ((result (jumper--do-jump-to-location -1)))
+ (should (eq result 'jumped))
+ (should (= (point) line2-pos)))))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-jump-to-max-index ()
+ "Should jump to location at maximum index."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "0123456789012345678")
+ (goto-char (point-min))
+ ;; Store at all positions
+ (dotimes (i jumper-max-locations)
+ (forward-char 1)
+ (jumper--do-store-location))
+ (goto-char (point-min))
+ ;; Jump to last one (index 9, which is at position 10)
+ (jumper--do-jump-to-location (1- jumper-max-locations))
+ (should (= (point) (1+ jumper-max-locations))))
+ (test-jumper-teardown))
+
+;;; Boundary Cases - Remove Location
+
+(ert-deftest test-jumper-remove-first-location ()
+ "Should remove location at index 0."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (forward-line 1)
+ (jumper--do-store-location)
+ (jumper--do-remove-location 0)
+ (should (= jumper--next-index 1))
+ ;; What was at index 1 should now be at index 0
+ (should (= (aref jumper--registers 0) ?1)))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-remove-last-location ()
+ "Should remove location at last index."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "line 1\nline 2\nline 3")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (forward-line 1)
+ (jumper--do-store-location)
+ (forward-line 1)
+ (jumper--do-store-location)
+ (jumper--do-remove-location 2)
+ (should (= jumper--next-index 2)))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-remove-with-cancel ()
+ "Should return 'cancelled when index is -1."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (let ((result (jumper--do-remove-location -1)))
+ (should (eq result 'cancelled))
+ (should (= jumper--next-index 1))))
+ (test-jumper-teardown))
+
+;;; Error Cases
+
+(ert-deftest test-jumper-remove-when-empty ()
+ "Should return 'no-locations when removing from empty list."
+ (test-jumper-setup)
+ (let ((result (jumper--do-remove-location 0)))
+ (should (eq result 'no-locations)))
+ (test-jumper-teardown))
+
+;;; Helper Function Tests
+
+(ert-deftest test-jumper-location-key-format ()
+ "Should generate unique location keys."
+ (with-temp-buffer
+ (insert "line 1\nline 2")
+ (goto-char (point-min))
+ (let ((key1 (jumper--location-key)))
+ (forward-line 1)
+ (let ((key2 (jumper--location-key)))
+ (should-not (string= key1 key2))
+ ;; Keys should contain buffer name and position info
+ (should (string-match-p ":" key1))
+ (should (string-match-p ":" key2))))))
+
+(ert-deftest test-jumper-register-available-p ()
+ "Should correctly report register availability."
+ (test-jumper-setup)
+ (should (jumper--register-available-p))
+ ;; Fill to capacity
+ (setq jumper--next-index jumper-max-locations)
+ (should-not (jumper--register-available-p))
+ (test-jumper-teardown))
+
+(ert-deftest test-jumper-format-location ()
+ "Should format location for display."
+ (test-jumper-setup)
+ (with-temp-buffer
+ (insert "test line with some content")
+ (goto-char (point-min))
+ (jumper--do-store-location)
+ (let ((formatted (jumper--format-location 0)))
+ (should formatted)
+ (should (string-match-p "\\[0\\]" formatted))
+ (should (string-match-p "test line" formatted))))
+ (test-jumper-teardown))
+
+(provide 'test-jumper)
+;;; test-jumper.el ends here