From 1d9d252e8b9e1385337cd0af087a7007f8e62da8 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 3 May 2026 17:20:54 -0500 Subject: feat(dev-fkeys): add F6 test runner menu (Phase 2a) I extended `dev-fkeys.el` with the F6 dispatcher half of the spec. F6 prompts via `completing-read` between two candidates: "All tests" delegates to `projectile-test-project`, and "Current file's tests" detects the buffer's language by extension, derives the runner command, and pipes through `compile' from the projectile root. C-F6 is the fast path straight to "Current file's tests". Per-language coverage: - Elisp source files map to `make test-name TEST=^test--`. Elisp test files run with `make test-file FILE=` so a per-helper file like `test-foo--bar.el' runs only its own tests. - Python source files map to `pytest tests/test_.py'. Python test files run with `pytest '. - Go runs the package containing the file: `go test ./'. Source and test files use the same command since Go test scope is per-package. Limit: this runs every `_test.go' in the package, not just the buffer's file. Phase 2b can refine via test-name discovery. - TypeScript and JavaScript are detected but punted for v1. The runner-command builder returns nil and the orchestrator signals a user-error rather than guessing. The F6 binding moved from the Phase 1 stopgap (`projectile-test-project') to `cj/f6-test-runner'. C-F6 is newly bound to `cj/f6-current-file-tests'. M-F6 stays unbound, reserved for Phase 2b's "Run a test..." menu entry. TDD: 68 new tests across 7 files. Production code split into small testable internals (`cj/--f6-language-detect', `cj/--f6-buffer-is-test-file-p', `cj/--f6-source-stem', `cj/--f6-test-runner-cmd-for', `cj/--f6-current-file-tests-impl') plus two thin interactive wrappers. Smoke tests confirm bindings register on load. I also updated the module commentary with the Phase 2b plan, the capture-then-filter approach for tree-sitter discovery, and a pointer to Emacs bug #79687. The bug is the predicate-syntax mismatch that breaks `:match' / `:equal' / `:pred' queries on Emacs 30.2 with libtree-sitter 0.26. The fix lives on Emacs master (commit b0143530), targets Emacs 31, and has not been backported to the emacs-30 branch as of today. Phase 2b will use queries without predicates and filter results in Elisp, sidestepping the issue. Mike Olson's `treesit-predicate-rewrite.el' applies the same idea to font-lock if you want it before Phase 2b lands. --- tests/test-dev-fkeys--f6-language-detect.el | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/test-dev-fkeys--f6-language-detect.el (limited to 'tests/test-dev-fkeys--f6-language-detect.el') diff --git a/tests/test-dev-fkeys--f6-language-detect.el b/tests/test-dev-fkeys--f6-language-detect.el new file mode 100644 index 000000000..93dd6d8be --- /dev/null +++ b/tests/test-dev-fkeys--f6-language-detect.el @@ -0,0 +1,80 @@ +;;; test-dev-fkeys--f6-language-detect.el --- Tests for cj/--f6-language-detect -*- lexical-binding: t -*- + +;;; Commentary: +;; Tests for the language classifier used by the F6 test runner. Detection +;; runs purely off the file extension. The classifier returns one of +;; \\='elisp / \\='python / \\='go / \\='typescript / \\='javascript / \\='unknown. + +;;; Code: + +(require 'ert) +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'dev-fkeys) + +;;; Normal Cases + +(ert-deftest test-dev-fkeys-f6-language-detect-elisp () + "Normal: .el classifies as elisp." + (should (eq (cj/--f6-language-detect "foo.el") 'elisp))) + +(ert-deftest test-dev-fkeys-f6-language-detect-python () + "Normal: .py classifies as python." + (should (eq (cj/--f6-language-detect "foo.py") 'python))) + +(ert-deftest test-dev-fkeys-f6-language-detect-go () + "Normal: .go classifies as go." + (should (eq (cj/--f6-language-detect "foo.go") 'go))) + +(ert-deftest test-dev-fkeys-f6-language-detect-typescript () + "Normal: .ts and .tsx classify as typescript." + (should (eq (cj/--f6-language-detect "foo.ts") 'typescript)) + (should (eq (cj/--f6-language-detect "foo.tsx") 'typescript))) + +(ert-deftest test-dev-fkeys-f6-language-detect-javascript () + "Normal: .js and .jsx classify as javascript." + (should (eq (cj/--f6-language-detect "foo.js") 'javascript)) + (should (eq (cj/--f6-language-detect "foo.jsx") 'javascript))) + +;;; Boundary Cases + +(ert-deftest test-dev-fkeys-f6-language-detect-with-directory-path () + "Boundary: classifier ignores the path, looks only at extension." + (should (eq (cj/--f6-language-detect "/abs/path/to/foo.el") 'elisp)) + (should (eq (cj/--f6-language-detect "rel/dir/foo.py") 'python))) + +(ert-deftest test-dev-fkeys-f6-language-detect-test-file-name () + "Boundary: a filename that is itself a test file still classifies by extension. +The test-file-vs-source distinction is a separate helper." + (should (eq (cj/--f6-language-detect "tests/test-foo.el") 'elisp)) + (should (eq (cj/--f6-language-detect "tests/test_foo.py") 'python)) + (should (eq (cj/--f6-language-detect "pkg/foo_test.go") 'go))) + +(ert-deftest test-dev-fkeys-f6-language-detect-uppercase-extension () + "Boundary: extension match is case-insensitive (.PY, .EL, etc.)." + (should (eq (cj/--f6-language-detect "foo.EL") 'elisp)) + (should (eq (cj/--f6-language-detect "foo.PY") 'python))) + +(ert-deftest test-dev-fkeys-f6-language-detect-no-extension () + "Boundary: a filename with no extension classifies as unknown." + (should (eq (cj/--f6-language-detect "Makefile") 'unknown)) + (should (eq (cj/--f6-language-detect "README") 'unknown))) + +(ert-deftest test-dev-fkeys-f6-language-detect-unsupported-extension () + "Boundary: an extension we do not classify returns unknown." + (should (eq (cj/--f6-language-detect "foo.rs") 'unknown)) + (should (eq (cj/--f6-language-detect "foo.rb") 'unknown)) + (should (eq (cj/--f6-language-detect "foo.txt") 'unknown))) + +;;; Error Cases + +(ert-deftest test-dev-fkeys-f6-language-detect-nil-filename () + "Error: nil filename returns unknown without erroring (defensive against +buffers that aren't backed by files)." + (should (eq (cj/--f6-language-detect nil) 'unknown))) + +(ert-deftest test-dev-fkeys-f6-language-detect-empty-filename () + "Error: empty string returns unknown." + (should (eq (cj/--f6-language-detect "") 'unknown))) + +(provide 'test-dev-fkeys--f6-language-detect) +;;; test-dev-fkeys--f6-language-detect.el ends here -- cgit v1.2.3