aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-duet-pane.el165
-rw-r--r--tests/test-duet-smoke.el9
2 files changed, 168 insertions, 6 deletions
diff --git a/tests/test-duet-pane.el b/tests/test-duet-pane.el
new file mode 100644
index 0000000..181309b
--- /dev/null
+++ b/tests/test-duet-pane.el
@@ -0,0 +1,165 @@
+;;; test-duet-pane.el --- Tests for the commander mode and pane layout -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Craig Jennings
+
+;; Author: Craig Jennings <c@cjennings.net>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for the batch-safe core of the pane layer: the pure sibling-pane
+;; selector (the dirvish#36 fix without needing a live frame), the duet-mode
+;; F-key map, and minor-mode keymap precedence. The two-window resolution is
+;; also exercised against a real split frame. The live two-pane launch and the
+;; F-key feel in a running daemon are Manual tests.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+
+;;; Pure sibling-pane selection
+
+(ert-deftest test-duet-pane-sibling-returns-other-of-two ()
+ "Given two panes, the sibling is the one that is not the active window."
+ (should (eq 'b (duet--sibling-pane 'a '(a b))))
+ (should (eq 'a (duet--sibling-pane 'b '(a b)))))
+
+(ert-deftest test-duet-pane-sibling-errors-unless-exactly-two ()
+ "Zero, one, or three commander panes is a clear error, not a guess."
+ (should-error (duet--sibling-pane 'a '(a)) :type 'user-error)
+ (should-error (duet--sibling-pane 'a '(a b c)) :type 'user-error))
+
+(ert-deftest test-duet-pane-sibling-errors-when-window-not-a-pane ()
+ "Asking for the sibling of a window that is not one of the panes errors."
+ (should-error (duet--sibling-pane 'x '(a b)) :type 'user-error))
+
+;;; F-key map
+
+(ert-deftest test-duet-pane-fkeys-bound ()
+ "The mc/Norton F-keys map to their DUET commands in `duet-mode-map'."
+ (should (eq 'duet-view (lookup-key duet-mode-map (kbd "<f3>"))))
+ (should (eq 'duet-edit (lookup-key duet-mode-map (kbd "<f4>"))))
+ (should (eq 'duet-copy (lookup-key duet-mode-map (kbd "<f5>"))))
+ (should (eq 'duet-move (lookup-key duet-mode-map (kbd "<f6>"))))
+ (should (eq 'duet-mkdir (lookup-key duet-mode-map (kbd "<f7>"))))
+ (should (eq 'duet-delete (lookup-key duet-mode-map (kbd "<f8>"))))
+ (should (eq 'duet-quit (lookup-key duet-mode-map (kbd "<f10>")))))
+
+;;; Minor-mode precedence
+
+(ert-deftest test-duet-pane-mode-is-buffer-local ()
+ "`duet-mode' is a buffer-local minor mode."
+ (with-temp-buffer
+ (duet-mode 1)
+ (should (bound-and-true-p duet-mode)))
+ (with-temp-buffer
+ (should-not (bound-and-true-p duet-mode))))
+
+(ert-deftest test-duet-pane-fkey-precedence-inside-vs-outside ()
+ "An F-key resolves to a DUET command inside a pane, but not outside one."
+ (with-temp-buffer
+ (duet-mode 1)
+ (should (eq 'duet-copy (key-binding (kbd "<f5>")))))
+ (with-temp-buffer
+ (should-not (eq 'duet-copy (key-binding (kbd "<f5>"))))))
+
+;;; Not-yet-implemented action commands
+
+(ert-deftest test-duet-pane-unimplemented-actions-error ()
+ "The transfer/file actions announce themselves until their phase lands."
+ (dolist (cmd '(duet-view duet-edit duet-copy duet-move duet-mkdir duet-delete))
+ (should-error (funcall cmd) :type 'user-error)))
+
+;;; Launch, quit, and two-window resolution against a real frame
+
+(ert-deftest test-duet-pane-other-pane-resolves-sibling-window ()
+ "With two commander panes, `duet--other-pane' returns the sibling window."
+ (let ((buf-a (generate-new-buffer " *duet-a*"))
+ (buf-b (generate-new-buffer " *duet-b*")))
+ (unwind-protect
+ (progn
+ (delete-other-windows)
+ (set-window-buffer (selected-window) buf-a)
+ (with-current-buffer buf-a (duet-mode 1))
+ (let ((win-b (split-window-right)))
+ (set-window-buffer win-b buf-b)
+ (with-current-buffer buf-b (duet-mode 1))
+ (should (= 2 (length (duet--commander-windows))))
+ (let ((other (duet--other-pane (selected-window))))
+ (should (window-live-p other))
+ (should (eq buf-b (window-buffer other))))))
+ (delete-other-windows)
+ (kill-buffer buf-a)
+ (kill-buffer buf-b))))
+
+(ert-deftest test-duet-launch-creates-two-commander-panes ()
+ "Launching DUET lays out two side-by-side commander panes."
+ (let ((duet--saved-window-configuration nil))
+ (unwind-protect
+ (progn
+ (delete-other-windows)
+ (duet "/tmp" "/tmp")
+ (should (= 2 (length (duet--commander-windows))))
+ (should (window-live-p (duet--other-pane (selected-window)))))
+ (delete-other-windows))))
+
+(ert-deftest test-duet-quit-restores-prior-layout ()
+ "`duet-quit' restores the single-window layout DUET launched from."
+ (delete-other-windows)
+ (duet "/tmp" "/tmp")
+ (should (= 2 (length (duet--commander-windows))))
+ (duet-quit)
+ (should (= 1 (length (window-list)))))
+
+(ert-deftest test-duet-pane-directory-reports-pane-dir ()
+ "`duet--pane-directory' reports the directory a pane is showing."
+ (let ((duet--saved-window-configuration nil))
+ (unwind-protect
+ (progn
+ (delete-other-windows)
+ (duet "/tmp" "/tmp")
+ (should (equal (expand-file-name "/tmp/")
+ (duet--pane-directory (selected-window)))))
+ (delete-other-windows))))
+
+(ert-deftest test-duet-quit-without-launch-messages ()
+ "Quitting with no saved layout messages rather than erroring."
+ (let ((duet--saved-window-configuration nil))
+ (duet-quit)
+ (should-not duet--saved-window-configuration)))
+
+(ert-deftest test-duet-other-pane-defaults-to-selected-window ()
+ "Called with no argument, `duet--other-pane' resolves from the selected window."
+ (let ((duet--saved-window-configuration nil))
+ (unwind-protect
+ (progn
+ (delete-other-windows)
+ (duet "/tmp" "/tmp")
+ (should (window-live-p (duet--other-pane))))
+ (delete-other-windows))))
+
+(ert-deftest test-duet-launch-defaults-to-current-directory ()
+ "With no directory arguments, both panes open on the current directory."
+ (let ((duet--saved-window-configuration nil)
+ (default-directory "/tmp/"))
+ (unwind-protect
+ (progn
+ (delete-other-windows)
+ (duet)
+ (should (= 2 (length (duet--commander-windows)))))
+ (delete-other-windows))))
+
+(provide 'test-duet-pane)
+;;; test-duet-pane.el ends here
diff --git a/tests/test-duet-smoke.el b/tests/test-duet-smoke.el
index 4f88a0e..07d75ea 100644
--- a/tests/test-duet-smoke.el
+++ b/tests/test-duet-smoke.el
@@ -28,13 +28,10 @@
(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
(ert-deftest test-duet-smoke-feature-loaded ()
- "The package source loads and defines its entry command."
+ "The package source loads and defines its entry command and commander mode."
(should (featurep 'duet))
- (should (commandp 'duet)))
-
-(ert-deftest test-duet-smoke-entry-not-yet-implemented ()
- "The entry command errors until the pane layout lands (Phase 4)."
- (should-error (duet)))
+ (should (commandp 'duet))
+ (should (fboundp 'duet-mode)))
(provide 'test-duet-smoke)
;;; test-duet-smoke.el ends here