ido-ubiquitous-test.el 19 KB

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