ido-ubiquitous-test.el 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. ;;; ido-ubiquitous-test.el --- -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2015 Ryan C. Thompson
  3. ;; Filename: ido-ubiquitous-test.el
  4. ;; Author: Ryan C. Thompson
  5. ;; Created: Tue Oct 6 20:52:45 2015 (-0700)
  6. ;; This file is NOT part of GNU Emacs.
  7. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  8. ;;
  9. ;; This program is free software: you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or (at
  12. ;; your option) any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful, but
  15. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. ;; General Public License for more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  21. ;;
  22. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  23. ;;
  24. ;;; Code:
  25. ;; This is a series of macros to facilitate the testing of completion
  26. ;; non-interactively by simulating input.
  27. (require 'ido-ubiquitous)
  28. (require 'ert)
  29. (require 'cl-macs)
  30. (defmacro keyboard-quit-to-error (&rest body)
  31. "Evaluate BODY but signal an error on `keyboard-quit'."
  32. `(condition-case nil
  33. (progn ,@body)
  34. (quit
  35. (error "Caught `keyboard-quit'"))))
  36. (defmacro with-simulated-input (keys &rest body)
  37. "Eval body with KEYS as simulated input.
  38. This macro is intended for testing normally interactive functions
  39. by simulating input. If BODY tries to read more input events than
  40. KEYS provides, `keyboard-quit' is invoked (by means of appending
  41. multple C-g keys to KEYS). This is to ensure that BODY will never
  42. block waiting for input, since this macro is intended for
  43. noninteractive use. As such, BODY should not invoke
  44. `keyboard-quit' under normal operation, and KEYS should not
  45. include C-g, or this macro will interpret it as reading past the
  46. end of input."
  47. ;; It would be better to detect end-of-input by overriding
  48. ;; `read-event' to throw an error, since theoretically C-g could be
  49. ;; rebound to something other than `keyboard-quit'. But apparently
  50. ;; some functions read input directly in C code, and redefining
  51. ;; `read-event' has no effect on those. So the suboptimal solution
  52. ;; is to rely on C-g.
  53. (declare (indent 1))
  54. `(let* ((key-sequence (listify-key-sequence (kbd ,keys)))
  55. (C-g-key-sequence
  56. (listify-key-sequence
  57. ;; We *really* want to trigger `keyboard-quit' if we reach
  58. ;; the end of the input.
  59. (kbd "C-g C-g C-g C-g C-g C-g C-g")))
  60. (unread-command-events
  61. (append key-sequence C-g-key-sequence)))
  62. (when (member (car C-g-key-sequence) key-sequence)
  63. (error "KEYS must include C-g"))
  64. (condition-case nil
  65. ,@body
  66. (quit
  67. (error "Reached end of simulated input while evaluating body")))))
  68. (defmacro with-mode (mode arg &rest body)
  69. "Eval (MODE ARG), then body, then restore previous status of MODE.
  70. This will only work on modes that respect the normal conventions
  71. for activation and deactivation."
  72. (declare (indent 2))
  73. (let* ((orig-status (eval mode))
  74. (restore-arg (if orig-status 1 0)))
  75. `(unwind-protect
  76. (progn
  77. (,mode ,arg)
  78. ,@body)
  79. (,mode ,restore-arg))))
  80. (defmacro with-ido-ubiquitous-standard-env (&rest body)
  81. "Execute BODY with standard ido-ubiquitous settings.\n\nAll ido-ubiquitous and ido-cr+ options will be let-bound to their\ndefault values, and `ido-ubiquitous-mode' will be enabled."
  82. (declare (indent 0))
  83. (let*
  84. ((ido-ubiquitous-options
  85. '(ido-ubiquitous-allow-on-functional-collection
  86. ido-ubiquitous-command-overrides
  87. ido-ubiquitous-debug-mode
  88. ido-ubiquitous-default-state
  89. ido-ubiquitous-function-overrides
  90. ido-cr+-fallback-function
  91. ido-cr+-max-items
  92. ido-cr+-replace-completely))
  93. (idu-bindings
  94. (loop for var in ido-ubiquitous-options collect
  95. (list var
  96. (list 'quote
  97. (default-value var))))))
  98. `(with-mode ido-ubiquitous-mode 1
  99. (let ,idu-bindings ,@body))))
  100. (defun collection-as-function (collection)
  101. "Return a function equivalent to COLLECTION.
  102. The returned function will work equivalently to COLLECTION when
  103. passed to `all-completions' and `try-completion'."
  104. (completion-table-dynamic (lambda (string) (all-completions string collection))))
  105. (cl-defmacro should-with-tag (form &key tag)
  106. "Equivalent to `(should FORM)' but with a tag on the output.
  107. This is useful if the same `should' form will be called multiple
  108. times in different contexts. Each test can pass a different tag
  109. so it's clear in the ERT output which context is causing the
  110. failure."
  111. `(if ,tag
  112. (should (and ,tag ,form))
  113. (should ,form)))
  114. (defun plist-delete (plist property)
  115. "Delete PROPERTY from PLIST.
  116. This is in contrast to merely setting it to 0."
  117. (let (p)
  118. (while plist
  119. (if (not (eq property (car plist)))
  120. (setq p (plist-put p (car plist) (nth 1 plist))))
  121. (setq plist (cddr plist)))
  122. p))
  123. (cl-defmacro should-error-with-tag (form &rest other-keys &key tag &allow-other-keys)
  124. "Equivalent to `(should FORM)' but with a tag on the output.
  125. See `should-with-tag'."
  126. (setq other-keys (plist-delete other-keys :tag))
  127. `(if ,tag
  128. (should-error (and ,tag ,form) ,@other-keys)
  129. (should-error ,form ,@other-keys)))
  130. (defun test-ido-ubiquitous-expected-mode (override &optional tag)
  131. "Test whether observed ido-ubiquitous behavior matches OVERRIDE."
  132. (declare (indent 1))
  133. (if (eq override 'disable)
  134. (progn
  135. (should-with-tag
  136. ;; Verify that we get standard completion
  137. (string=
  138. "g"
  139. (with-simulated-input "g RET"
  140. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  141. :tag tag)
  142. (should-with-tag
  143. (string=
  144. "green"
  145. (with-simulated-input "g RET"
  146. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  147. :tag tag)
  148. ;; Standard completion should refuse to finish with incomplete
  149. ;; input if match is required
  150. (should-error-with-tag
  151. (with-simulated-input "b RET"
  152. (completing-read "Prompt: " '("brown" "blue" "yellow" "green") nil t))
  153. :type 'error
  154. :tag tag))
  155. ;; Common tests whenever ido-ubiquitous is enabled in any way
  156. (should-with-tag
  157. ;; Verify that ido completion is active
  158. (string=
  159. "green"
  160. (with-simulated-input "g RET"
  161. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  162. :tag tag)
  163. ;; Verify that C-j is working correctly
  164. (should-with-tag
  165. (string=
  166. "g"
  167. (with-simulated-input "g C-j"
  168. (completing-read "Prompt: " '("blue" "yellow" "green"))))
  169. :tag tag)
  170. (let ((collection '("brown" "blue" "yellow" "green")))
  171. (should-with-tag
  172. (member
  173. (with-simulated-input "b RET"
  174. (completing-read "Prompt: " collection))
  175. (all-completions "b" collection))
  176. :tag tag))
  177. (case override
  178. (enable
  179. ;; Test for new style
  180. (should-with-tag
  181. (string=
  182. "blue"
  183. (with-simulated-input "RET"
  184. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  185. :tag tag)
  186. (should-with-tag
  187. (string=
  188. ""
  189. (with-simulated-input "C-j"
  190. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  191. :tag tag))
  192. (enable-old
  193. (should-with-tag
  194. (string=
  195. ""
  196. (with-simulated-input "RET"
  197. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  198. :tag tag)
  199. (should-with-tag
  200. (string=
  201. "blue"
  202. (with-simulated-input "C-j"
  203. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  204. :tag tag)
  205. ;; Verify that doing other stuff reverts RET and C-j to standard
  206. ;; meanings
  207. (should-with-tag
  208. (string=
  209. "blue"
  210. (with-simulated-input "g DEL RET"
  211. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  212. :tag tag)
  213. (should-with-tag
  214. (string=
  215. "blue"
  216. (with-simulated-input "<right> <left> RET"
  217. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  218. :tag tag)
  219. (should-with-tag
  220. (string=
  221. ""
  222. (with-simulated-input "g DEL C-j"
  223. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  224. :tag tag)
  225. (should-with-tag
  226. (string=
  227. ""
  228. (with-simulated-input "<right> <left> C-j"
  229. (completing-read "Prompt: " '("blue" "yellow" "green") nil t)))
  230. :tag tag))
  231. (otherwise (error "Unknown override %S" override)))))
  232. (defun test-ido-ubiquitous-expected-mode-on-functional-collection (override &optional tag)
  233. "Test whether observed ido-ubiquitous behavior on functional collection matches OVERRIDE."
  234. (declare (indent 1))
  235. (cl-letf* ((original-completing-read (symbol-function #'completing-read))
  236. ((symbol-function #'completing-read)
  237. (lambda (prompt collection &rest args)
  238. (apply original-completing-read prompt
  239. (collection-as-function collection)
  240. args))))
  241. (test-ido-ubiquitous-expected-mode override tag)))
  242. (ert-deftest ido-ubiquitous-test-simple ()
  243. "Test that basic ido-ubiquitous functionality is working."
  244. (with-ido-ubiquitous-standard-env
  245. (ido-ubiquitous-mode 1)
  246. (test-ido-ubiquitous-expected-mode 'enable
  247. :simple)
  248. (ido-ubiquitous-mode 0)
  249. (test-ido-ubiquitous-expected-mode 'disable
  250. :simple)))
  251. (ert-deftest ido-ubiquitous-test-oldstyle ()
  252. (with-ido-ubiquitous-standard-env
  253. (let ((ido-ubiquitous-default-state 'enable-old))
  254. (test-ido-ubiquitous-expected-mode 'enable-old
  255. :simple-oldstyle))))
  256. (ert-deftest ido-ubiquitous-test-maxitems ()
  257. (with-ido-ubiquitous-standard-env
  258. (let ((ido-cr+-max-items -1))
  259. (test-ido-ubiquitous-expected-mode 'disable
  260. :maxitems))))
  261. (ert-deftest ido-ubiquitous-test-override ()
  262. (with-ido-ubiquitous-standard-env
  263. (ido-ubiquitous-with-override 'enable
  264. (test-ido-ubiquitous-expected-mode 'enable
  265. :override-enable))
  266. (ido-ubiquitous-with-override 'enable-old
  267. (test-ido-ubiquitous-expected-mode 'enable-old
  268. :override-enable-old))
  269. (ido-ubiquitous-with-override 'disable
  270. (test-ido-ubiquitous-expected-mode 'disable
  271. :override-disable))))
  272. (ert-deftest ido-ubiquitous-test-functional-collection ()
  273. (with-ido-ubiquitous-standard-env
  274. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  275. :colfunc)
  276. (ido-ubiquitous-with-override 'enable
  277. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  278. :override-enable-colfunc))
  279. (ido-ubiquitous-with-override 'enable-old
  280. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  281. :override-enable-old-colfunc))))
  282. (ert-deftest ido-ubiquitous-require-match ()
  283. "Test whether require-match works.
  284. (Require match seems to be broken in ido at the moment)"
  285. :expected-result :failed
  286. (should-error
  287. (with-simulated-input "b C-j"
  288. (ido-completing-read "Prompt: " '("blue" "brown" "yellow" "green") nil t))))
  289. ;; Functions to define overrides on for testing
  290. (defun idu-no-override-testfunc ()
  291. (test-ido-ubiquitous-expected-mode 'enable
  292. :func-override-none)
  293. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  294. :func-override-none-colfunc))
  295. (defun idu-enabled-testfunc (&rest args)
  296. (test-ido-ubiquitous-expected-mode 'enable
  297. :func-override-enable)
  298. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  299. :func-override-enable-colfunc))
  300. (defun idu-disabled-testfunc (&rest args)
  301. (test-ido-ubiquitous-expected-mode 'disable
  302. :func-override-disable)
  303. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  304. :func-override-disable-colfunc))
  305. (defun idu-enabled-oldstyle-testfunc (&rest args)
  306. (test-ido-ubiquitous-expected-mode 'enable-old
  307. :func-override-enable-old)
  308. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  309. :func-override-enable-old-colfunc))
  310. ;; commands to define overrides on for testing
  311. (defun idu-no-override-testcmd (&rest args)
  312. (interactive
  313. (list
  314. (test-ido-ubiquitous-expected-mode 'enable
  315. :cmd-override-none)
  316. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  317. :cmd-override-non-colfunc)))
  318. (test-ido-ubiquitous-expected-mode 'enable
  319. :cmd-override-none)
  320. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  321. :cmd-override-non-colfunc))
  322. (defun idu-enabled-testcmd (&rest args)
  323. (interactive
  324. (list
  325. (test-ido-ubiquitous-expected-mode 'enable
  326. :cmd-override-enable)
  327. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  328. :cmd-override-enable-colfunc)))
  329. (test-ido-ubiquitous-expected-mode 'enable
  330. :cmd-override-enable)
  331. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  332. :cmd-override-enable-colfunc))
  333. (defun idu-disabled-testcmd (&rest args)
  334. (interactive
  335. (list
  336. (test-ido-ubiquitous-expected-mode 'disable
  337. :cmd-override-disable)
  338. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  339. :cmd-override-disable-colfunc)))
  340. (test-ido-ubiquitous-expected-mode 'disable
  341. :cmd-override-disable)
  342. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  343. :cmd-override-disable-colfunc))
  344. (defun idu-enabled-oldstyle-testcmd (&rest args)
  345. (interactive
  346. (list
  347. (test-ido-ubiquitous-expected-mode 'enable-old
  348. :cmd-override-enable-old)
  349. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  350. :cmd-override-enable-old-colfunc)))
  351. (test-ido-ubiquitous-expected-mode 'enable-old
  352. :cmd-override-enable-old)
  353. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  354. :cmd-override-enable-old-colfunc))
  355. (ert-deftest ido-ubiquitous-test-command-and-function-overrides ()
  356. (let ((orig-func-overrides ido-ubiquitous-function-overrides)
  357. (orig-cmd-overrides ido-ubiquitous-command-overrides))
  358. (unwind-protect
  359. (progn
  360. (customize-set-variable
  361. 'ido-ubiquitous-function-overrides
  362. (append ido-ubiquitous-function-overrides
  363. '((enable exact "idu-enabled-testfunc")
  364. (disable exact "idu-disabled-testfunc")
  365. (enable-old exact "idu-enabled-oldstyle-testfunc"))))
  366. (loop for func in
  367. '(idu-no-override-testfunc
  368. idu-enabled-testfunc
  369. idu-disabled-testfunc
  370. idu-enabled-oldstyle-testfunc)
  371. do (funcall func))
  372. (customize-set-variable
  373. 'ido-ubiquitous-command-overrides
  374. (append ido-ubiquitous-command-overrides
  375. '((enable exact "idu-enabled-testcmd")
  376. (disable exact "idu-disabled-testcmd")
  377. (enable-old exact "idu-enabled-oldstyle-testcmd"))))
  378. (loop for cmd in
  379. '(idu-no-override-testcmd
  380. idu-enabled-testcmd
  381. idu-disabled-testcmd
  382. idu-enabled-oldstyle-testcmd)
  383. do (call-interactively cmd)))
  384. (customize-set-variable 'ido-ubiquitous-function-overrides orig-func-overrides)
  385. (customize-set-variable 'ido-ubiquitous-command-overrides orig-cmd-overrides))))
  386. (ert-deftest ido-ubiquitous-test-fallback ()
  387. (with-ido-ubiquitous-standard-env
  388. (should
  389. ;; C-b/f not at beginning/end of input should not fall back
  390. (string=
  391. "green"
  392. (with-simulated-input "g C-b C-f RET"
  393. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  394. (should
  395. ;; C-f at end of input should fall back
  396. (string=
  397. "g"
  398. (with-simulated-input "g C-f RET"
  399. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  400. (should
  401. ;; Repeated C-b should not fall back
  402. (string=
  403. "green"
  404. (with-simulated-input "g C-b C-b C-b C-b RET"
  405. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  406. (should
  407. ;; C-b at beginning of line should fall back (if previous action
  408. ;; was not also C-b)
  409. (string=
  410. "g"
  411. (with-simulated-input "g C-b x DEL C-b RET"
  412. (completing-read "Prompt: " '("blue" "yellow" "green")))))))
  413. (provide 'ido-ubiquitous-test)
  414. ;;; ido-ubiquitous-test.el ends here