;;; test-duet-pane.el --- Tests for the commander mode and pane layout -*- lexical-binding: t; -*- ;; Copyright (C) 2026 Craig Jennings ;; Author: Craig Jennings ;; 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 . ;;; 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 "")))) (should (eq 'duet-edit (lookup-key duet-mode-map (kbd "")))) (should (eq 'duet-copy (lookup-key duet-mode-map (kbd "")))) (should (eq 'duet-move (lookup-key duet-mode-map (kbd "")))) (should (eq 'duet-mkdir (lookup-key duet-mode-map (kbd "")))) (should (eq 'duet-delete (lookup-key duet-mode-map (kbd "")))) (should (eq 'duet-quit (lookup-key duet-mode-map (kbd ""))))) (ert-deftest test-duet-pane-q-quits-the-commander () "q quits the whole commander (both panes), not just dired's current window." (should (eq 'duet-quit (lookup-key duet-mode-map (kbd "q"))))) ;;; 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 ""))))) (with-temp-buffer (should-not (eq 'duet-copy (key-binding (kbd "")))))) ;;; Not-yet-implemented action commands (ert-deftest test-duet-pane-unimplemented-actions-error () "The viewer actions announce themselves until the viewer phase lands. The transfer actions (copy/move/mkdir/delete) are wired to the engine and are covered in test-duet-transfer-exec.el." (dolist (cmd '(duet-view duet-edit)) (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