ido-ubiquitous-test.el 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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. (require 'ido-completing-read+)
  26. (require 'ido-ubiquitous)
  27. (require 'ert)
  28. (require 'cl-macs)
  29. ;; This is a series of macros to facilitate the testing of completion
  30. ;; non-interactively by simulating input.
  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-enable)
  249. (ido-ubiquitous-mode 0)
  250. (test-ido-ubiquitous-expected-mode 'disable
  251. :simple-disable)))
  252. (ert-deftest ido-ubiquitous-test-oldstyle ()
  253. "Test whether old-style completion works as expected."
  254. (with-ido-ubiquitous-standard-env
  255. (let ((ido-ubiquitous-default-state 'enable-old))
  256. (test-ido-ubiquitous-expected-mode 'enable-old
  257. :simple-oldstyle))))
  258. (ert-deftest ido-ubiquitous-test-maxitems ()
  259. "Test whether the large-collection fallback works."
  260. (with-ido-ubiquitous-standard-env
  261. (let ((ido-cr+-max-items -1))
  262. (test-ido-ubiquitous-expected-mode 'disable
  263. :maxitems))))
  264. (ert-deftest ido-ubiquitous-test-override ()
  265. "Test whether ido-ubiquitous overrides work."
  266. (with-ido-ubiquitous-standard-env
  267. (ido-ubiquitous-with-override 'enable
  268. (test-ido-ubiquitous-expected-mode 'enable
  269. :override-enable))
  270. (ido-ubiquitous-with-override 'enable-old
  271. (test-ido-ubiquitous-expected-mode 'enable-old
  272. :override-enable-old))
  273. (ido-ubiquitous-with-override 'disable
  274. (test-ido-ubiquitous-expected-mode 'disable
  275. :override-disable))))
  276. (ert-deftest ido-ubiquitous-test-functional-collection ()
  277. "Test whether ido-ubiquitous overrides work when collection is a function."
  278. (with-ido-ubiquitous-standard-env
  279. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  280. :colfunc)
  281. (ido-ubiquitous-with-override 'enable
  282. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  283. :override-enable-colfunc))
  284. (ido-ubiquitous-with-override 'enable-old
  285. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  286. :override-enable-old-colfunc))))
  287. (ert-deftest ido-cr+-require-match ()
  288. "Test whether require-match works."
  289. (should-error
  290. (with-simulated-input "b C-j C-j C-j"
  291. (ido-completing-read+
  292. "Prompt: "
  293. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  294. (should-error
  295. (with-simulated-input "b C-j b C-j C-j"
  296. (ido-completing-read+
  297. "Prompt: "
  298. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  299. (should
  300. (string=
  301. "blueberry"
  302. (with-simulated-input "b C-j b C-j e C-j C-j"
  303. (ido-completing-read+
  304. "Prompt: "
  305. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  306. (should-error
  307. (with-simulated-input "b l u e g C-j"
  308. (ido-completing-read+
  309. "Prompt: "
  310. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  311. (should
  312. (string=
  313. "bluegrass"
  314. (with-simulated-input "b l u e g C-j C-j"
  315. (ido-completing-read+
  316. "Prompt: "
  317. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  318. ;; Finally, test for the expected wrong behavior without ido-cr+. If
  319. ;; ido.el ever fixes this bug, it will cause this test to fail as a
  320. ;; signal that the workaround can be phased out.
  321. (should
  322. (string=
  323. "b"
  324. (with-simulated-input "b C-j"
  325. (ido-completing-read
  326. "Prompt: "
  327. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))))
  328. ;; Functions to define overrides on for testing
  329. (defun idu-no-override-testfunc ()
  330. (test-ido-ubiquitous-expected-mode 'enable
  331. :func-override-none)
  332. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  333. :func-override-none-colfunc))
  334. (defun idu-enabled-testfunc (&rest args)
  335. (test-ido-ubiquitous-expected-mode 'enable
  336. :func-override-enable)
  337. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  338. :func-override-enable-colfunc))
  339. (defun idu-disabled-testfunc (&rest args)
  340. (test-ido-ubiquitous-expected-mode 'disable
  341. :func-override-disable)
  342. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  343. :func-override-disable-colfunc))
  344. (defun idu-enabled-oldstyle-testfunc (&rest args)
  345. (test-ido-ubiquitous-expected-mode 'enable-old
  346. :func-override-enable-old)
  347. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  348. :func-override-enable-old-colfunc))
  349. ;; commands to define overrides on for testing
  350. (defun idu-no-override-testcmd (&rest args)
  351. (interactive
  352. (list
  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. (test-ido-ubiquitous-expected-mode 'enable
  358. :cmd-override-none)
  359. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  360. :cmd-override-non-colfunc))
  361. (defun idu-enabled-testcmd (&rest args)
  362. (interactive
  363. (list
  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. (test-ido-ubiquitous-expected-mode 'enable
  369. :cmd-override-enable)
  370. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  371. :cmd-override-enable-colfunc))
  372. (defun idu-disabled-testcmd (&rest args)
  373. (interactive
  374. (list
  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. (test-ido-ubiquitous-expected-mode 'disable
  380. :cmd-override-disable)
  381. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  382. :cmd-override-disable-colfunc))
  383. (defun idu-enabled-oldstyle-testcmd (&rest args)
  384. (interactive
  385. (list
  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. (test-ido-ubiquitous-expected-mode 'enable-old
  391. :cmd-override-enable-old)
  392. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  393. :cmd-override-enable-old-colfunc))
  394. (ert-deftest ido-ubiquitous-test-command-and-function-overrides ()
  395. "Test whether command- and function-specific overrides work."
  396. (let ((orig-func-overrides ido-ubiquitous-function-overrides)
  397. (orig-cmd-overrides ido-ubiquitous-command-overrides))
  398. (unwind-protect
  399. (progn
  400. (customize-set-variable
  401. 'ido-ubiquitous-function-overrides
  402. (append ido-ubiquitous-function-overrides
  403. '((enable exact "idu-enabled-testfunc")
  404. (disable exact "idu-disabled-testfunc")
  405. (enable-old exact "idu-enabled-oldstyle-testfunc"))))
  406. (loop for func in
  407. '(idu-no-override-testfunc
  408. idu-enabled-testfunc
  409. idu-disabled-testfunc
  410. idu-enabled-oldstyle-testfunc)
  411. do (funcall func))
  412. (customize-set-variable
  413. 'ido-ubiquitous-command-overrides
  414. (append ido-ubiquitous-command-overrides
  415. '((enable exact "idu-enabled-testcmd")
  416. (disable exact "idu-disabled-testcmd")
  417. (enable-old exact "idu-enabled-oldstyle-testcmd"))))
  418. (loop for cmd in
  419. '(idu-no-override-testcmd
  420. idu-enabled-testcmd
  421. idu-disabled-testcmd
  422. idu-enabled-oldstyle-testcmd)
  423. do (call-interactively cmd)))
  424. (customize-set-variable 'ido-ubiquitous-function-overrides orig-func-overrides)
  425. (customize-set-variable 'ido-ubiquitous-command-overrides orig-cmd-overrides))))
  426. (ert-deftest ido-ubiquitous-test-fallback ()
  427. "Test whether manually invoking fallback works."
  428. (with-ido-ubiquitous-standard-env
  429. (should
  430. ;; C-b/f not at beginning/end of input should not fall back
  431. (string=
  432. "green"
  433. (with-simulated-input "g C-b C-f RET"
  434. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  435. (should
  436. ;; C-f at end of input should fall back
  437. (string=
  438. "g"
  439. (with-simulated-input "g C-f RET"
  440. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  441. (should
  442. ;; Repeated C-b should not fall back
  443. (string=
  444. "green"
  445. (with-simulated-input "g C-b C-b C-b C-b RET"
  446. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  447. (should
  448. ;; C-b at beginning of line should fall back (if previous action
  449. ;; was not also C-b)
  450. (string=
  451. "g"
  452. (with-simulated-input "g C-b x DEL C-b RET"
  453. (completing-read "Prompt: " '("blue" "yellow" "green")))))))
  454. (defun ido-ubiquitous-run-all-tests ()
  455. (interactive)
  456. (ert "^ido-\\(ubiquitous\\|cr\\+\\)-"))
  457. (provide 'ido-ubiquitous-test)
  458. ;;; ido-ubiquitous-test.el ends here