ido-ubiquitous-test.el 18 KB

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