;;; compat-tests.el --- Tests for Compat -*- lexical-binding: t; no-byte-compile: t; -*-
;; Copyright (C) 2021-2024 Free Software Foundation, Inc.
;; 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 compatibility functions from compat.el.
;;
;; Note that all functions are covered by tests. When new functions are
;; added to Compat, they must come with test coverage!
;; Functions are marked with a link to the test suite. The link type
;; `compat-tests' must be registered first by evaluating the following
;; code. If you intend to work on the test suite you can add this code to
;; your init.el.
;;
;; (require 'ol)
;; (org-link-set-parameters
;; "compat-tests"
;; :follow
;; (lambda (link _)
;; (org-link-open-from-string
;; (format "[[file:compat-tests.el::ert-deftest compat-%s ()]]" link))))
;;
;; You can then jump to the links with the command
;; `org-open-at-point-global', ideally bound to a convenient key.
;; The tests are written in a simple, explicit style. Please inspect the
;; tests in order to find out the supported calling conventions. In
;; particular, note the use of `compat-call' to call functions, where the
;; calling convention or behavior changed between Emacs versions.
;; The functions tested here are guaranteed to work on the Emacs versions
;; tested by continuous integration. This includes 24.4, 24.5, 25.1, 25.2,
;; 25.3, 26.1, 26.2, 26.3, 27.1, 27.2, 28.1, 28.2, 29.1 and the current
;; Emacs master branch.
;;; Code:
(require 'compat)
(require 'ert-x)
(require 'subr-x)
(require 'time-date)
(require 'image)
(require 'text-property-search nil t)
;; Setup tramp mock
(require 'tramp)
(add-to-list
'tramp-methods
'("mock"
(tramp-login-program "sh")
(tramp-login-args (("-i")))
(tramp-direct-async ("-c"))
(tramp-remote-shell "/bin/sh")
(tramp-remote-shell-args ("-c"))
(tramp-connection-timeout 10)))
(add-to-list
'tramp-default-host-alist
`("\\`mock\\'" nil ,(system-name)))
(defmacro should-equal (a b)
`(should (equal ,a ,b)))
(ert-deftest compat-loaded-features ()
(let ((version 0))
(while (< version 31)
(should-equal (> version emacs-major-version)
(featurep (intern (format "compat-%s" version))))
(setq version (1+ version)))))
(ert-deftest compat-function ()
(let ((sym (compat-function plist-put)) list)
(should sym)
(should (symbolp sym))
(setq list (funcall sym list "first" 1 #'string=))
(should-equal (compat-call plist-get list "first" #'string=) 1)))
(defconst compat-tests--version (package-get-version))
(ert-deftest compat-package-get-version ()
(should (stringp compat-tests--version))
(should-equal 30 (car (version-to-list compat-tests--version))))
(ert-deftest compat-buffer-match-p ()
(let ((b "*compat-test-buffer*")
(child-mode (make-symbol "child"))
(parent-mode (make-symbol "parent")))
(put child-mode 'derived-mode-parent parent-mode)
(with-current-buffer (get-buffer-create b)
(setq major-mode child-mode))
(should (buffer-match-p t b))
(should-not (buffer-match-p nil b))
(should (buffer-match-p "compat" b))
(should (buffer-match-p #'always b))
(should-not (buffer-match-p #'ignore b))
(should (buffer-match-p `(derived-mode . ,parent-mode) b))
(should-not (buffer-match-p '(derived-mode . text-mode) b))
(should (buffer-match-p `(major-mode . ,child-mode) b))
(should-not (buffer-match-p '(major-mode . prog-mode) b))
(should (buffer-match-p '(not (major-mode . prog-mode)) b))
(should (buffer-match-p `(and (major-mode . ,child-mode) "compat" t) b))
(should (buffer-match-p `(or (major-mode . prog-mode) "foo" t) b))))
(ert-deftest compat-match-buffers ()
(let ((b1 (get-buffer-create "*compat-buffer1*"))
(b2 (get-buffer-create "*compat-buffer2*"))
(b3 (get-buffer-create "*compat-buffer3*"))
(m1 (make-symbol "mode1"))
(m2 (make-symbol "mode2"))
(m3 (make-symbol "mode3")))
(with-current-buffer b1 (setq major-mode m1))
(with-current-buffer b2 (setq major-mode m2))
(with-current-buffer b3 (setq major-mode m3))
(should-equal (list b2 b1)
(match-buffers `(or (major-mode . ,m1) (major-mode . ,m2))
(list b1 b2 b3)))))
(ert-deftest compat-thing-at-mouse ()
(save-window-excursion
(with-temp-buffer
(let ((event `(mouse-1 (,(selected-window) 1 (0 . 0) 0))))
(set-window-buffer nil (current-buffer))
(insert "https://emacs.org/")
(goto-char (point-min))
(should-equal "https://emacs.org/" (thing-at-mouse event 'url))
(should-equal '(1 . 19) (bounds-of-thing-at-mouse event 'url))
(should-not (region-active-p))
(mark-thing-at-mouse event 'url)
(should-equal (point) 19)
(should-equal '((1 . 19)) (region-bounds))
(let ((mouse-select-region-move-to-beginning t))
(mark-thing-at-mouse event 'url))
(should-equal (point) 1)
(should-equal '((1 . 19)) (region-bounds))))))
(ert-deftest compat-dolist-with-progress-reporter ()
(let (y)
(should-equal
(dolist-with-progress-reporter (x '(1 2 3) y) "Reporter"
(push x y))
'(3 2 1)))
(let (y)
(should-equal
(dolist-with-progress-reporter
(x '(1 2 3) y) (make-progress-reporter "Reporter")
(push x y))
'(3 2 1))))
(ert-deftest compat-minibuffer-history-value ()
(let ((minibuffer-history-variable 'file-name-history)
(file-name-history '("a" "b" "c")))
(should-equal (minibuffer-history-value) '("a" "b" "c")))
(let ((file-name-history '("x" "y" "z")))
(should-equal
(catch 'compat-tests--exit
(minibuffer-with-setup-hook
(lambda ()
(message "%S" minibuffer-history-variable)
(throw 'compat-tests--exit (minibuffer-history-value)))
(let ((executing-kbd-macro t))
(completing-read "Prompt: " #'completion-file-name-table
nil nil nil 'file-name-history))))
'("x" "y" "z"))))
(ert-deftest compat-with-minibuffer-selected-window ()
(let (ran)
(should-not (minibuffer-selected-window))
(should-not (with-minibuffer-selected-window
(setq ran t)))
(should-not ran)
(unwind-protect
(progn
(advice-add #'minibuffer-selected-window :override #'selected-window)
(should-equal 'result (with-minibuffer-selected-window
(setq ran t)
'result))
(should ran))
(advice-remove #'minibuffer-selected-window #'selected-window))))
(ert-deftest compat-fixnump ()
(should (fixnump 0))
(should (fixnump most-negative-fixnum))
(should (fixnump most-positive-fixnum)))
(ert-deftest compat-bignump ()
(should-not (bignump 0))
(should-not (bignump most-negative-fixnum))
(should-not (bignump most-positive-fixnum))
(should-equal (bignump (1+ most-positive-fixnum)) (> emacs-major-version 26))
(should-equal (bignump (1- most-negative-fixnum)) (> emacs-major-version 26)))
(ert-deftest compat-buttonize ()
(let ((b (buttonize "button" 'c 'd 'h)))
(should-equal b "button")
(should-equal 'c (get-text-property 0 'action b))
(should-equal 'c (get-text-property 5 'action b))
(should-equal 'd (get-text-property 0 'button-data b))
(should-equal 'd (get-text-property 5 'button-data b))
(should-equal 'h (get-text-property 0 'help-echo b))
(should-equal 'h (get-text-property 5 'help-echo b))))
(ert-deftest compat-obsolete-button-buttonize ()
(let ((b (with-no-warnings (button-buttonize "button" 'c 'd))))
(should-equal b "button")
(should-equal 'c (get-text-property 0 'action b))
(should-equal 'c (get-text-property 5 'action b))
(should-equal 'd (get-text-property 0 'button-data b))
(should-equal 'd (get-text-property 5 'button-data b))))
(ert-deftest compat-buttonize-region ()
(with-temp-buffer
(insert "