summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/fossil-ui-test.el343
1 files changed, 271 insertions, 72 deletions
diff --git a/test/fossil-ui-test.el b/test/fossil-ui-test.el
index 081a701..80fd5c0 100644
--- a/test/fossil-ui-test.el
+++ b/test/fossil-ui-test.el
@@ -15,12 +15,18 @@
(make-directory (file-name-directory file) t)
(with-temp-file file (insert contents)))
+(defun fossil-ui-test--write-bytes (file bytes)
+ "Write exact unibyte BYTES to FILE."
+ (make-directory (file-name-directory file) t)
+ (fossil-ui--write-bytes file bytes))
+
(defmacro fossil-ui-test--with-checkout (&rest body)
"Create a temporary Fossil checkout and evaluate BODY there."
(declare (indent 0) (debug t))
`(let* ((base (make-temp-file "fossil-ui-test-" t))
(repo (expand-file-name "project.fossil" base))
- (root (file-name-as-directory (expand-file-name "checkout" base))))
+ (root (file-name-as-directory (expand-file-name "checkout" base)))
+ (fossil-ui-stage-directory (expand-file-name "stage/" base)))
(unwind-protect
(progn
(make-directory root)
@@ -35,6 +41,15 @@
,@body)
(delete-directory base t))))
+(defun fossil-ui-test--cat-bytes (root path)
+ "Return committed PATH from ROOT as exact bytes."
+ (with-temp-buffer
+ (set-buffer-multibyte nil)
+ (let ((default-directory root)
+ (coding-system-for-read 'no-conversion))
+ (should (zerop (process-file fossil-ui-program nil t nil "cat" "-r" "current" "--" path))))
+ (buffer-string)))
+
(ert-deftest fossil-ui-parse-change-line-preserves-spaces ()
(should
(equal (fossil-ui--parse-change-line "EDITED \"dir/file name.el\"")
@@ -203,18 +218,224 @@
:key (lambda (change) (plist-get change :path))
:test #'equal)))))
-(ert-deftest fossil-ui-diff-produces-navigable-hunks ()
+(ert-deftest fossil-ui-staged-and-unstaged-inline-sections-are-independent ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "staged version\n")
+ (let ((buffer (save-window-excursion (fossil-ui-status root))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-stage)
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "working version\n")
+ (fossil-ui-refresh)
+ (fossil-ui--goto-path "tracked.txt" t)
+ (fossil-ui-diff)
+ (fossil-ui--goto-path "tracked.txt" nil)
+ (fossil-ui-diff)
+ (should (member '(t "tracked.txt") (plist-get textui-state :expanded-diffs)))
+ (should (member '(nil "tracked.txt") (plist-get textui-state :expanded-diffs)))
+ (goto-char (point-min))
+ (should (text-property-search-forward 'fossil-ui-location '(file t "tracked.txt") #'equal))
+ (should (text-property-search-forward 'fossil-ui-location '(file nil "tracked.txt") #'equal)))
+ (when (buffer-live-p buffer) (kill-buffer buffer))))))
+
+(ert-deftest fossil-ui-inline-hunk-stage-and-stale-diff-guard ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "changed\n")
+ (let ((buffer (save-window-excursion (fossil-ui-status root))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (goto-char (point-min))
+ (let ((hunk (text-property-search-forward 'fossil-ui-hunk-header t #'eq)))
+ (should hunk)
+ (goto-char (prop-match-beginning hunk))
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "changed again\n")
+ (should-error (fossil-ui-stage) :type 'user-error)
+ (fossil-ui-refresh)
+ (goto-char (point-min))
+ (setq hunk (text-property-search-forward 'fossil-ui-hunk-header t #'eq))
+ (goto-char (prop-match-beginning hunk))
+ (fossil-ui-stage)
+ (let* ((entry (car (plist-get (fossil-ui--index root) :entries))))
+ (should (equal (plist-get entry :staged) "changed again\n")))))
+ (when (buffer-live-p buffer) (kill-buffer buffer))))))
+
+(defun fossil-ui-test--select-replacement-lines ()
+ "Activate a region spanning the first removed and added inline lines."
+ (goto-char (point-min))
+ (let* ((removed (or (text-property-search-forward 'fossil-ui-line-kind ?- #'eq)
+ (ert-fail "No removed line")))
+ (begin (prop-match-beginning removed))
+ (added (or (text-property-search-forward 'fossil-ui-line-kind ?+ #'eq)
+ (ert-fail "No added line")))
+ (end (prop-match-end added)))
+ (goto-char (max begin (1- end)))
+ (set-mark begin)
+ (activate-mark)))
+
+(ert-deftest fossil-ui-visual-lines-stage-unstage-and-discard ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "replacement\n")
+ (let ((buffer (save-window-excursion (fossil-ui-status root)))
+ (fossil-ui-confirm-revert nil))
+ (unwind-protect
+ (with-current-buffer buffer
+ (setq-local transient-mark-mode t)
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (fossil-ui-test--select-replacement-lines)
+ (fossil-ui-stage)
+ (should (equal (plist-get (car (plist-get (fossil-ui--index root) :entries)) :staged) "replacement\n"))
+ (fossil-ui--goto-path "tracked.txt" t)
+ (fossil-ui-diff)
+ (fossil-ui-test--select-replacement-lines)
+ (fossil-ui-unstage)
+ (should-not (plist-get (fossil-ui--index root) :entries))
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (fossil-ui-test--select-replacement-lines)
+ (fossil-ui-discard)
+ (should (equal (fossil-ui--text-file (expand-file-name "tracked.txt" root)) "initial\n")))
+ (when (buffer-live-p buffer) (kill-buffer buffer))))))
+
+(ert-deftest fossil-ui-ret-from-inline-line-visits-correct-source-line ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "initial\nsecond line\n")
+ (let ((buffer (save-window-excursion (fossil-ui-status root)))
+ visited)
+ (unwind-protect
+ (with-current-buffer buffer
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (goto-char (point-min))
+ (let ((line (text-property-search-forward 'fossil-ui-line-kind ?+ #'eq)))
+ (should line)
+ (goto-char (prop-match-beginning line))
+ (should (= (fossil-ui--property-at-line 'fossil-ui-target-line) 2))
+ (cl-letf (((symbol-function 'find-file)
+ (lambda (file)
+ (setq visited (find-file-noselect file))
+ (set-buffer visited))))
+ (fossil-ui-visit-file)
+ (should (= (line-number-at-pos) 2)))))
+ (when (buffer-live-p visited) (kill-buffer visited))
+ (when (buffer-live-p buffer) (kill-buffer buffer))))))
+
+(ert-deftest fossil-ui-invalid-utf8-whole-file-stage-cleans-snapshot-on-unstage ()
+ (fossil-ui-test--with-checkout
+ (let ((bytes (concat (string-make-unibyte "age-encryption.org/v1\n") (unibyte-string #xff #x80) "\n")))
+ (fossil-ui-test--write-bytes (expand-file-name "tracked.txt" root) bytes)
+ (let ((buffer (save-window-excursion (fossil-ui-status root))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (let ((change (cl-find "tracked.txt" (plist-get textui-state :changes)
+ :key (lambda (candidate) (plist-get candidate :path)) :test #'equal)))
+ (should (plist-get change :binary)))
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (should (string-search "Binary files differ" (buffer-substring-no-properties (point-min) (point-max))))
+ (goto-char (point-min))
+ (should-not (text-property-search-forward 'fossil-ui-hunk-header t #'eq))
+ (goto-char (point-min))
+ (let ((binary-line (text-property-search-forward 'fossil-ui-diff-key '(nil "tracked.txt") #'equal)))
+ (should binary-line)
+ (goto-char (prop-match-beginning binary-line)))
+ (fossil-ui-stage)
+ (let* ((entry (car (plist-get (fossil-ui--index root) :entries)))
+ (snapshot (plist-get entry :snapshot)))
+ (should (plist-get entry :binary))
+ (should (equal (fossil-ui--read-bytes snapshot) bytes))
+ (should (= (logand (file-modes snapshot) #o777) #o600))
+ (fossil-ui--goto-path "tracked.txt" t)
+ (fossil-ui-unstage)
+ (should-not (file-exists-p snapshot))
+ (should-not (plist-get (fossil-ui--index root) :entries))
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-stage)
+ (let* ((restaged (car (plist-get (fossil-ui--index root) :entries)))
+ (restaged-snapshot (plist-get restaged :snapshot)))
+ (cl-letf (((symbol-function 'yes-or-no-p) (lambda (&rest _) t)))
+ (fossil-ui-clear-stage))
+ (should-not (file-exists-p restaged-snapshot))
+ (should-not (plist-get (fossil-ui--index root) :entries)))))
+ (when (buffer-live-p buffer) (kill-buffer buffer)))))))
+
+(ert-deftest fossil-ui-encoding-glob-allows-invalid-utf8-warning-override ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--call root "settings" "encoding-glob" "*.txt")
+ (fossil-ui-test--write-bytes (expand-file-name "tracked.txt" root) (unibyte-string #xff #xfe))
+ (should (equal (fossil-ui--commit-warning-arguments root '("tracked.txt")) '("--no-warnings")))))
+
+(ert-deftest fossil-ui-configured-invalid-utf8-commit-is-byte-exact ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--call root "settings" "binary-glob" "*.txt")
+ (let* ((staged (concat (string-make-unibyte "age-v1\n") (unibyte-string #xff #xfe) "\n"))
+ (working (concat (string-make-unibyte "age-v2\n") (unibyte-string #x80 #x81) "\n")))
+ (fossil-ui-test--write-bytes (expand-file-name "tracked.txt" root) staged)
+ (let ((buffer (save-window-excursion (fossil-ui-status root))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-stage)
+ (let* ((entry (car (plist-get (fossil-ui--index root) :entries)))
+ (snapshot (plist-get entry :snapshot)))
+ (should (equal (fossil-ui--commit-warning-arguments root '("tracked.txt") (list entry)) '("--no-warnings")))
+ (fossil-ui-test--write-bytes (expand-file-name "tracked.txt" root) working)
+ (fossil-ui--commit-staged root "binary update")
+ (should (equal (fossil-ui-test--cat-bytes root "tracked.txt") staged))
+ (should (equal (fossil-ui--read-bytes (expand-file-name "tracked.txt" root)) working))
+ (should-not (file-exists-p snapshot))
+ (should-not (plist-get (fossil-ui--index root) :entries))))
+ (when (buffer-live-p buffer) (kill-buffer buffer)))))))
+
+(ert-deftest fossil-ui-unconfigured-invalid-utf8-commit-is-rejected ()
+ (fossil-ui-test--with-checkout
+ (let ((bytes (concat (string-make-unibyte "invalid\n") (unibyte-string #xff) "\n")))
+ (fossil-ui-test--write-bytes (expand-file-name "tracked.txt" root) bytes)
+ (let ((buffer (save-window-excursion (fossil-ui-status root))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-stage)
+ (let ((error (should-error (fossil-ui--commit-staged root "must fail") :type 'user-error)))
+ (should (string-match-p "binary-glob" (error-message-string error)))
+ (should (plist-get (fossil-ui--index root) :entries))))
+ (when (buffer-live-p buffer) (kill-buffer buffer)))))))
+
+(ert-deftest fossil-ui-warning-override-requires-every-invalid-file-to-be-configured ()
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--call root "settings" "binary-glob" "tracked.txt")
+ (fossil-ui-test--write-bytes (expand-file-name "tracked.txt" root) (unibyte-string #xff))
+ (fossil-ui-test--write-bytes (expand-file-name "other.bin" root) (unibyte-string #xfe))
+ (let ((error (should-error (fossil-ui--commit-warning-arguments root '("tracked.txt" "other.bin")) :type 'user-error)))
+ (should (string-match-p "other.bin" (error-message-string error))))))
+
+(ert-deftest fossil-ui-inline-diff-starts-collapsed-and-folds-file-and-hunk ()
(fossil-ui-test--with-checkout
(fossil-ui-test--write (expand-file-name "tracked.txt" root)
"first changed line\nsecond line\n")
- (let ((diff (fossil-ui-test--call
- root "diff" "--internal" "--unified" "--" "tracked.txt")))
- (should (string-match-p "^@@ " diff))
- (with-temp-buffer
- (fossil-ui-diff-mode)
- (should (eq (key-binding (kbd "]c")) #'diff-hunk-next))
- (should (eq (key-binding (kbd "[c")) #'diff-hunk-prev))
- (should (eq (key-binding (kbd "TAB")) #'outline-toggle-children))))))
+ (let ((buffer (save-window-excursion (fossil-ui-status root))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (should-not (plist-get textui-state :expanded-diffs))
+ (should-not (text-property-search-forward 'fossil-ui-hunk-header t #'eq))
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (should (member '(nil "tracked.txt") (plist-get textui-state :expanded-diffs)))
+ (goto-char (point-min))
+ (let ((hunk (text-property-search-forward 'fossil-ui-hunk-header t #'eq)))
+ (should hunk)
+ (goto-char (prop-match-beginning hunk))
+ (let ((id (fossil-ui--property-at-line 'fossil-ui-hunk-id)))
+ (should id)
+ (fossil-ui-diff)
+ (should (member id (plist-get textui-state :collapsed-hunks)))
+ (goto-char (point-min))
+ (should-not (text-property-search-forward 'fossil-ui-line-kind ?+ #'eq))
+ (should (text-property-search-forward 'fossil-ui-hunk-header t #'eq)))))
+ (when (buffer-live-p buffer) (kill-buffer buffer))))))
(ert-deftest fossil-ui-delta-render-keeps-hunks-and-emacs-faces ()
(skip-unless (executable-find fossil-ui-delta-program))
@@ -254,38 +475,26 @@
(let ((fossil-ui-delta-arguments arguments))
(should (eq (fossil-ui--effective-delta-arguments) arguments))))))
-(ert-deftest fossil-ui-theme-change-rerenders-open-diff-in-place ()
+(ert-deftest fossil-ui-theme-change-rerenders-inline-diff-in-place ()
(save-window-excursion
- (with-temp-buffer
- (let* ((buffer (current-buffer))
- (raw (mapconcat (lambda (number) (format "line %d" number)) (number-sequence 1 80) "\n"))
- (context (list :root "/tmp/project/" :path "example.el" :diff raw)))
- (fossil-ui-diff-mode)
- (let ((inhibit-read-only t))
- (insert raw))
- (set-buffer-modified-p nil)
- (setq-local fossil-ui-diff-context context)
- (switch-to-buffer buffer)
- (goto-char (point-min))
- (forward-line 40)
- (let ((point-before (point))
- (window-before (selected-window)))
- (set-window-start window-before (save-excursion
- (goto-char (point-min))
- (forward-line 20)
- (point)))
- (let ((start-before (window-start window-before)))
- (cl-letf (((symbol-function 'fossil-ui--display-diff)
- (lambda (_diff &optional _frame)
- (propertize raw 'face 'success))))
- (fossil-ui--theme-enabled 'test-theme))
- (should (eq (selected-window) window-before))
- (should (= (point) point-before))
- (should (= (window-start window-before) start-before))
- (should-not (buffer-modified-p))
- (should (equal fossil-ui-diff-context context))
- (should (equal (buffer-substring-no-properties (point-min) (point-max)) raw))
- (should (eq (get-text-property (point-min) 'face) 'success))))))))
+ (fossil-ui-test--with-checkout
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "theme change\n")
+ (let ((buffer (fossil-ui-status root)))
+ (unwind-protect
+ (with-current-buffer buffer
+ (fossil-ui--goto-path "tracked.txt")
+ (fossil-ui-diff)
+ (let* ((key '(nil "tracked.txt"))
+ (raw (plist-get (alist-get key (plist-get textui-state :inline-diffs) nil nil #'equal) :diff))
+ (point-before (point)))
+ (cl-letf (((symbol-function 'fossil-ui--display-diff)
+ (lambda (_diff &optional _frame) (propertize raw 'face 'success))))
+ (fossil-ui--theme-enabled 'test-theme))
+ (let ((display (plist-get (alist-get key (plist-get textui-state :inline-diffs) nil nil #'equal) :display)))
+ (should (equal (substring-no-properties display) raw))
+ (should (eq (get-text-property 0 'face display) 'success)))
+ (should (= (point) point-before))))
+ (when (buffer-live-p buffer) (kill-buffer buffer)))))))
(ert-deftest fossil-ui-status-opens-rendered-dashboard ()
(fossil-ui-test--with-checkout
@@ -306,46 +515,36 @@
(should-error (fossil-ui--checkout-info directory) :type 'user-error)
(delete-directory directory t))))
-(ert-deftest fossil-ui-preview-block-resizes-and-retains-diff-faces ()
- (let* ((fossil-ui-preview-lines 3)
- (widget
- (widget-convert 'fossil-ui-preview
- :path "tracked.txt"
- :value "@@ -1 +1 @@\n-old\n+new\n context")))
- (dolist (width '(20 80 120))
- (let ((rendered (textui-layout-widget widget width)))
- (should (text-property-not-all 0 (length rendered) 'face nil rendered))
- (dolist (line (split-string rendered "\n"))
- (should (<= (string-width line) width)))
- (with-temp-buffer
- (insert rendered)
- (textui-attach-widget widget (point-min) (point-max))
- (should (equal (buffer-string) rendered))
- (widget-delete widget))))))
+(ert-deftest fossil-ui-display-diff-rejects-structurally-changed-delta-output ()
+ (let ((raw "@@ -1 +1 @@\n-old\n+new\n"))
+ (cl-letf (((symbol-function 'fossil-ui--render-diff)
+ (lambda (_diff &optional _frame) (propertize "changed structure" 'face 'success))))
+ (should (equal (fossil-ui--display-diff raw) raw)))))
-(ert-deftest fossil-ui-preview-toggle-refresh-and-clean-checkout ()
+(ert-deftest fossil-ui-inline-diff-survives-refresh ()
(fossil-ui-test--with-checkout
- (fossil-ui-test--write (expand-file-name "tracked.txt" root) "preview change\n")
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "inline change\n")
(let ((buffer (save-window-excursion (fossil-ui-status root))))
(unwind-protect
(with-current-buffer buffer
(fossil-ui--goto-path "tracked.txt")
- (should (eq (key-binding (kbd "P")) #'fossil-ui-toggle-preview))
- (fossil-ui-toggle-preview)
- (should (equal (plist-get textui-state :preview-path) "tracked.txt"))
- (textui-refresh buffer)
- (should (string-search "+preview change" (buffer-substring-no-properties (point-min) (point-max))))
- (fossil-ui-test--write (expand-file-name "tracked.txt" root) "new preview\n")
- (fossil-ui-refresh)
- (textui-refresh buffer)
- (should (string-search "+new preview" (buffer-substring-no-properties (point-min) (point-max))))
+ (fossil-ui-diff)
+ (goto-char (point-min))
+ (let ((hunk (text-property-search-forward 'fossil-ui-hunk-header t #'eq)))
+ (should hunk)
+ (goto-char (prop-match-beginning hunk)))
+ (let ((location (fossil-ui--property-at-line 'fossil-ui-location)))
+ (fossil-ui-test--write (expand-file-name "tracked.txt" root) "new inline change\n")
+ (fossil-ui-refresh)
+ (should (member '(nil "tracked.txt") (plist-get textui-state :expanded-diffs)))
+ (should (equal (fossil-ui--property-at-line 'fossil-ui-location) location)))
+ (should (string-search "+new inline change" (buffer-substring-no-properties (point-min) (point-max))))
(fossil-ui--goto-path "tracked.txt")
- (fossil-ui-toggle-preview)
- (should-not (plist-get textui-state :preview-path))
- (fossil-ui-toggle-preview)
+ (fossil-ui-diff)
+ (should-not (plist-get textui-state :expanded-diffs))
(fossil-ui-test--write (expand-file-name "tracked.txt" root) "initial\n")
(fossil-ui-refresh)
- (should-not (plist-get textui-state :preview-path)))
+ (should-not (plist-get textui-state :expanded-diffs)))
(when (buffer-live-p buffer)
(kill-buffer buffer))))))