ido-ubiquitous-test.el 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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-lib)
  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 not include C-g"))
  65. (condition-case nil
  66. (progn ,@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. (cl-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. ;; "C-j" should be allowed to return an empty string even if
  290. ;; require-match is non-nil, as long as default is nil
  291. (should
  292. (string=
  293. ""
  294. (with-simulated-input "C-j"
  295. (ido-completing-read+
  296. "Prompt: "
  297. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  298. ;; "C-j" should NOT be allowed to return an empty string if
  299. ;; require-match and default are both non-nil.
  300. (should-error
  301. (with-simulated-input "C-j"
  302. (ido-completing-read+
  303. "Prompt: "
  304. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t nil nil "yellow")))
  305. (should-error
  306. (with-simulated-input "b C-j C-j C-j"
  307. (ido-completing-read+
  308. "Prompt: "
  309. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  310. (should-error
  311. (with-simulated-input "b C-j b C-j C-j"
  312. (ido-completing-read+
  313. "Prompt: "
  314. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  315. (should
  316. (string=
  317. "blueberry"
  318. (with-simulated-input "b C-j b C-j e C-j C-j"
  319. (ido-completing-read+
  320. "Prompt: "
  321. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  322. (should-error
  323. (with-simulated-input "b l u e g C-j"
  324. (ido-completing-read+
  325. "Prompt: "
  326. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))
  327. (should
  328. (string=
  329. "bluegrass"
  330. (with-simulated-input "b l u e g C-j C-j"
  331. (ido-completing-read+
  332. "Prompt: "
  333. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  334. ;; Finally, a few tests for the expected wrong behavior without
  335. ;; ido-cr+. If ido.el ever fixes this bug, it will cause this test
  336. ;; to fail as a signal that the workaround can be phased out.
  337. (should
  338. (string=
  339. ""
  340. (with-simulated-input "C-j"
  341. (ido-completing-read
  342. "Prompt: "
  343. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t))))
  344. (should
  345. (string=
  346. "b"
  347. (with-simulated-input "b C-j"
  348. (ido-completing-read
  349. "Prompt: "
  350. '("bluebird" "blues" "bluegrass" "blueberry" "yellow ""green") nil t)))))
  351. ;; Functions to define overrides on for testing
  352. (defun idu-no-override-testfunc ()
  353. (test-ido-ubiquitous-expected-mode 'enable
  354. :func-override-none)
  355. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  356. :func-override-none-colfunc))
  357. (defun idu-enabled-testfunc (&rest args)
  358. (test-ido-ubiquitous-expected-mode 'enable
  359. :func-override-enable)
  360. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  361. :func-override-enable-colfunc))
  362. (defun idu-disabled-testfunc (&rest args)
  363. (test-ido-ubiquitous-expected-mode 'disable
  364. :func-override-disable)
  365. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  366. :func-override-disable-colfunc))
  367. (defun idu-enabled-oldstyle-testfunc (&rest args)
  368. (test-ido-ubiquitous-expected-mode 'enable-old
  369. :func-override-enable-old)
  370. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  371. :func-override-enable-old-colfunc))
  372. ;; commands to define overrides on for testing
  373. (defun idu-no-override-testcmd (&rest args)
  374. (interactive
  375. (list
  376. (test-ido-ubiquitous-expected-mode 'enable
  377. :cmd-override-none)
  378. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  379. :cmd-override-non-colfunc)))
  380. (test-ido-ubiquitous-expected-mode 'enable
  381. :cmd-override-none)
  382. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  383. :cmd-override-non-colfunc))
  384. (defun idu-enabled-testcmd (&rest args)
  385. (interactive
  386. (list
  387. (test-ido-ubiquitous-expected-mode 'enable
  388. :cmd-override-enable)
  389. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  390. :cmd-override-enable-colfunc)))
  391. (test-ido-ubiquitous-expected-mode 'enable
  392. :cmd-override-enable)
  393. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable
  394. :cmd-override-enable-colfunc))
  395. (defun idu-disabled-testcmd (&rest args)
  396. (interactive
  397. (list
  398. (test-ido-ubiquitous-expected-mode 'disable
  399. :cmd-override-disable)
  400. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  401. :cmd-override-disable-colfunc)))
  402. (test-ido-ubiquitous-expected-mode 'disable
  403. :cmd-override-disable)
  404. (test-ido-ubiquitous-expected-mode-on-functional-collection 'disable
  405. :cmd-override-disable-colfunc))
  406. (defun idu-enabled-oldstyle-testcmd (&rest args)
  407. (interactive
  408. (list
  409. (test-ido-ubiquitous-expected-mode 'enable-old
  410. :cmd-override-enable-old)
  411. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  412. :cmd-override-enable-old-colfunc)))
  413. (test-ido-ubiquitous-expected-mode 'enable-old
  414. :cmd-override-enable-old)
  415. (test-ido-ubiquitous-expected-mode-on-functional-collection 'enable-old
  416. :cmd-override-enable-old-colfunc))
  417. (ert-deftest ido-ubiquitous-test-command-and-function-overrides ()
  418. "Test whether command- and function-specific overrides work."
  419. (let ((orig-func-overrides ido-ubiquitous-function-overrides)
  420. (orig-cmd-overrides ido-ubiquitous-command-overrides))
  421. (unwind-protect
  422. (progn
  423. (customize-set-variable
  424. 'ido-ubiquitous-function-overrides
  425. (append ido-ubiquitous-function-overrides
  426. '((enable exact "idu-enabled-testfunc")
  427. (disable exact "idu-disabled-testfunc")
  428. (enable-old exact "idu-enabled-oldstyle-testfunc"))))
  429. (cl-loop for func in
  430. '(idu-no-override-testfunc
  431. idu-enabled-testfunc
  432. idu-disabled-testfunc
  433. idu-enabled-oldstyle-testfunc)
  434. do (funcall func))
  435. (customize-set-variable
  436. 'ido-ubiquitous-command-overrides
  437. (append ido-ubiquitous-command-overrides
  438. '((enable exact "idu-enabled-testcmd")
  439. (disable exact "idu-disabled-testcmd")
  440. (enable-old exact "idu-enabled-oldstyle-testcmd"))))
  441. (cl-loop for cmd in
  442. '(idu-no-override-testcmd
  443. idu-enabled-testcmd
  444. idu-disabled-testcmd
  445. idu-enabled-oldstyle-testcmd)
  446. do (call-interactively cmd)))
  447. (customize-set-variable 'ido-ubiquitous-function-overrides orig-func-overrides)
  448. (customize-set-variable 'ido-ubiquitous-command-overrides orig-cmd-overrides))))
  449. (ert-deftest ido-ubiquitous-test-fallback ()
  450. "Test whether manually invoking fallback works."
  451. (with-ido-ubiquitous-standard-env
  452. (should
  453. ;; C-b/f not at beginning/end of input should not fall back
  454. (string=
  455. "green"
  456. (with-simulated-input "g C-b C-f RET"
  457. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  458. (should
  459. ;; C-f at end of input should fall back
  460. (string=
  461. "g"
  462. (with-simulated-input "g C-f RET"
  463. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  464. (should
  465. ;; Repeated C-b should not fall back
  466. (string=
  467. "green"
  468. (with-simulated-input "g C-b C-b C-b C-b RET"
  469. (completing-read "Prompt: " '("blue" "yellow" "green")))))
  470. (should
  471. ;; C-b at beginning of line should fall back (if previous action
  472. ;; was not also C-b)
  473. (string=
  474. "g"
  475. (with-simulated-input "g C-b x DEL C-b RET"
  476. (completing-read "Prompt: " '("blue" "yellow" "green")))))))
  477. (defun ido-ubiquitous-run-all-tests ()
  478. (interactive)
  479. (ert "^ido-\\(ubiquitous\\|cr\\+\\)-"))
  480. (provide 'ido-ubiquitous-test)
  481. ;;; ido-ubiquitous-test.el ends here