| Commit message (Collapse) | Author | Age | Files | Lines |
| ... | |
| | |
|
| |
|
|
|
|
|
|
| |
`selection-framework.el` had two `keymap-global-set "C-s"` calls at module load. The first bound `C-s` to `consult-line`, then a later block rebound the same key to `cj/consult-line-or-repeat`. The second binding always won, so the first was dead configuration and made the file harder to reason about.
I removed the intermediate `consult-line` binding. The final `cj/consult-line-or-repeat` binding stays. Behavior is unchanged.
I added `tests/test-selection-framework-keybindings.el` with one smoke test: load the module with `use-package`, `consult-line`, and `vertico-repeat` stubbed, then assert `C-s` resolves to `cj/consult-line-or-repeat`. That locks in the cleanup so a future re-add of the dead binding would fail the test.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The legacy `cj/--projectile-revert-on-fail` wrapper and `cj/--projectile-revert-state` global were removed when the closure-based revert refactor landed (commit 2f8d898). The corresponding tests in `test-dev-fkeys--projectile-revert-on-fail.el` and the around-revert / capture-cmd files still referenced the legacy symbols, so 7 tests had been failing on `main` since that commit.
I re-pointed each test at `cj/--projectile-revert-state-on-fail`, the pure decision helper that the closure-based hook delegates to. Each test now passes the captured `state` plist as an explicit argument instead of binding the old global. Test names updated to match the new target.
I dropped two tests that no longer have a target. `revert-on-fail-clears-state` was specific to the wrapper clearing the global on completion, and there is no global to clear now. `revert-on-fail-removes-itself` was specific to the wrapper removing itself from `compilation-finish-functions`. The closure-based hook removes itself differently and is covered by the buffer-local hook tests in `test-dev-fkeys--projectile-around-revert.el`.
The around-revert and capture-cmd tests also lost their `cj/--projectile-revert-state nil` let-bindings since that variable no longer exists.
21 projectile-related tests pass together.
|
| |
|
|
|
|
|
|
|
|
| |
`dev-fkeys.el` was wiring its three Projectile cache-revert advices via top-level `advice-add` calls using `apply-partially #'cj/--projectile-around-revert <map-symbol>`. That had three problems. The advice values were anonymous closures, so `advice-member-p` couldn't find them and a re-load would silently double-install. The implicit dependency on Projectile was load-ordered by accident. If `dev-fkeys.el` happened to require before Projectile loaded, the advice still attached to unbound symbols. And a fresh batch require of `dev-fkeys.el` for tests would always force the advice attempt regardless of whether Projectile was around.
I gave each Projectile target a named advice wrapper (`cj/--projectile-compile-around-revert`, `cj/--projectile-test-around-revert`, `cj/--projectile-run-around-revert`) and put the (target . advice) pairs in a `cj/--projectile-revert-advice-specs` defconst. `cj/--projectile-install-revert-advice` walks the specs, checks `fboundp` plus `advice-member-p`, and only adds advice that's missing. The installer is idempotent on reload, and the named wrappers make it easy to tear down later by symbol name.
`cj/--projectile-register-revert-advice` is the entry point at module load time. It installs immediately when Projectile is already a `featurep`, otherwise it schedules the installer through `eval-after-load 'projectile`. Either way the advice is in place once Projectile is available, and `dev-fkeys.el` no longer relies on a particular load order.
Tests in the new `tests/test-dev-fkeys--projectile-advice-install.el` cover four cases. Registration defers via `eval-after-load` when Projectile isn't a feature yet. Registration installs immediately when it is. Install skips unbound Projectile functions. Install advises each bound Projectile command runner with the matching named wrapper. 23 projectile-related tests pass together.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The projectile compile/test/run cache-revert protection in `dev-fkeys.el` used a single global variable, `cj/--projectile-revert-state`. Two overlapping compiles could clobber each other's state. The second compile's capture would overwrite the first's. So when the first compile finished and ran the global finish-hook, it'd act on the wrong project's state, or revert nothing because the keys had drifted.
I moved the state into a closure. `cj/--projectile-capture-cmd` now returns the state plist instead of mutating the global. `cj/--projectile-around-revert` captures the state into a local, calls the projectile cmd-runner, and installs a one-shot buffer-local finish hook on the returned compilation buffer. The hook closes over its own state plist, so two compiles can finish in any order and each one acts on the right project.
I extracted three small helpers along the way. `cj/--projectile-revert-state-on-fail` is the pure decision (revert when failed AND modified AND prior was non-nil). `cj/--projectile-make-revert-on-fail-hook` builds the closure-based one-shot hook. `cj/--projectile-compilation-buffer` normalizes a buffer-or-process result from projectile into a buffer.
The legacy `cj/--projectile-revert-on-fail` function still reads the global `cj/--projectile-revert-state`. It stays around for the existing direct tests, but its core logic now delegates to the extracted state-on-fail helper. No production caller adds it to `compilation-finish-functions` anymore.
I added one regression test in `test-dev-fkeys--projectile-around-revert.el`: two projectile invocations on different projects, finishes triggered out of order, each compile reverts its own project's cache and leaves the other alone. The capture and around-advice tests were rewritten to match the new return-style API and to assert hooks land buffer-locally rather than globally. 19 projectile-related tests pass together.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The custom modeline's VC `:eval` form was calling `vc-backend`, `vc-working-revision`, `vc-git--symbolic-ref`, and `vc-state` on every redisplay. Mode-line eval runs every keystroke. For a large git repo or a TRAMP buffer over SSH, the round-trip cost shows up as visible input lag.
I split the inline form into helpers and added a buffer-local cache. `cj/modeline-vc-info` returns the cached plist when its TTL hasn't expired and the cache key still matches. The TTL defaults to 5 seconds via `cj/modeline-vc-cache-ttl`. Save and revert hooks invalidate the cache so the user sees state changes promptly. The render path (`cj/modeline-vc-render`) is now a separate function so it can be tested without touching VC at all.
Remote files are skipped by default. `cj/modeline-vc-show-remote` opts back in for cases where TRAMP VC is fast enough to be worth it.
Measured on this repo: uncached reads were about 2.4 ms each, cached reads were about 0.0025 ms each, and remote-skipped reads pay only the cheap `file-remote-p` check.
I added five tests in `tests/test-modeline-config-vc-cache.el`: cache reuse within TTL (backend called once for two reads), refresh after TTL expiry (called twice), remote-file bypass (no backend call, nil result), cache clear (buffer-locals reset to nil), and render output (branch text + face metadata preserved).
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
| |
`coverage-core.el` was running git through `shell-command-to-string`, which has two practical problems for central tooling: shell parsing surfaces (especially the `$(git merge-base ...)` substitution), and silent failure modes when git exits non-zero (the bad output just becomes empty parse results).
I extracted three small helpers. `cj/--coverage-git-string` runs git via `process-file` against a temp buffer and signals `user-error` on non-zero exit, with the argv, status, and trimmed output included. `cj/--coverage-git-merge-base` does its own `git merge-base HEAD <base>` invocation. `cj/--coverage-git-diff` is the diff wrapper that always appends `--unified=0`.
`cj/--coverage-changed-lines` now uses `pcase` over the scope symbol and composes the helpers. Branch-vs-main and branch-vs-parent compute the merge-base in a separate call before running `git diff <merge-base>..HEAD`, with no shell substitution involved.
One behavior change is worth flagging. A git failure used to disappear into an empty hash table. It now signals a `user-error` with the failing command, exit status, and git's stderr output.
Tests: I added two argv-boundary cases (working-tree and branch-vs-parent both assert the exact argv list seen) plus a non-zero-exit case that asserts the user-error path. The existing `test-coverage-core--command.el` smoke test gets its `shell-command-to-string` stub upgraded to a `process-file` stub.
|
| |
|
|
|
|
|
|
| |
The fallback compile command in `cj/c-compile-command` was building paths from `(buffer-name)`. That broke for renamed buffers, uniquified names like `foo.c<2>`, and files outside `default-directory`. The buffer name is a display label, not a path, so `gcc -o name name` would compile (or fail to compile) the wrong target whenever the two diverged.
I extracted `cj/c--single-file-compile-command` that takes the source path explicitly, shell-quotes both source and output paths, and signals a clear `user-error` for non-file buffers. The fallback now passes `buffer-file-name` instead of `(buffer-name)`.
Tests for this helper landed in commit f619cbf alongside other prog-c coverage work.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
I added 7 new tests across 3 files, filling coverage gaps in `prog-c.el`. Two functions were untested (`cj/c-mode-settings`, `cj/c-mode-keybindings`) and `cj/c-compile-command` only had its single-file fallback covered.
`cj/c-compile-command` now has the Makefile and CMake branches tested, plus a Boundary case for a Makefile path with spaces being shell-quoted in the `cd` target. I added these to the existing `test-prog-c-compile-command.el` since the helper and dispatcher already lived there.
`cj/c-mode-settings` gets three tests. One covers the buffer-local invariants (`indent-tabs-mode` nil, `c-basic-offset` 4, `tab-width` 4, `fill-column` 80, `comment-auto-fill-only-comments` t). The other two cover the LSP branch: `lsp-deferred` runs when the function is fbound and `executable-find` returns a clangd path, and skips when clangd is missing.
`cj/c-mode-keybindings` gets one test asserting S-F5 binds to `cj/disabled` and S-F6 binds to `gdb` in the buffer's local keymap. No realistic Boundary or Error cases for installing two static bindings, so the single Normal case carries it.
I stubbed `auto-fill-mode`, `electric-pair-mode`, `lsp-deferred`, `executable-find`, and `locate-dominating-file` at the boundaries via `cl-letf`. Buffer-local state was exercised real in `with-temp-buffer`.
12 prog-c tests pass together: 5 existing plus 7 new.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
`tests/testutil-general.el` hard-coded `~/.temp-emacs-tests/` as the test root. That worked locally but blew up under sandboxed `make` runs and CI environments that can't write outside the repo or `/tmp`. A clean sandbox `make test` run reported 32 failing test files purely from the home-directory write attempt, even though the same suite passed when run with normal write permission.
I rewrote `cj/test-base-dir` to honor `CJ_EMACS_TEST_DIR` if set, otherwise create a unique directory under `temporary-file-directory` via `make-temp-file`. So sandbox and CI paths just work, and a stable local debug root is still one env-var away.
I also tightened the path-containment checks. The old `(string-prefix-p base fullpath)` was a textual hack. Relative paths and weird trailing slashes could fool it. I extracted `cj/test--assert-inside-base` using `file-in-directory-p`, which is the proper API. While I was there, I added `cj/test--safe-base-dir-p` so `cj/delete-test-base-dir` refuses to recursively wipe `/`, `~/`, `temporary-file-directory`, `user-emacs-directory`, `default-directory`, or any path of length under six characters. That guards against an env-var typo or a misaligned `let` binding accidentally deleting something important.
I updated the Makefile's `clean-tests` target to nuke the new `$TMPDIR/cj-emacs-tests-*` pattern plus an explicit `CJ_EMACS_TEST_DIR` (if set) and the legacy `~/.temp-emacs-tests` directory.
I added `tests/test-testutil-general.el` with five tests: default base lives under `temporary-file-directory`, env override resolves correctly, parent-escape paths are rejected, broad roots are refused for deletion, and a specific selected root is cleaned cleanly.
|
| |
|
|
|
|
|
|
|
|
| |
`mail-config.el` had three related issues. SMTP transport debug was hard-coded to t, which is sensitive since mail bodies and headers land in debug buffers. The use-package `:config` was also setting `sendmail-program` and `mu4e-get-mail-command` directly from `executable-find` results. So a host without msmtp or mbsync silently got `nil` or `(concat nil " -a")` instead of a clear failure mode.
I added `cj/smtpmail-debug-enabled` (default nil) plus `cj/set-smtpmail-debug` and `cj/toggle-smtpmail-debug` for temporary troubleshooting, mirroring the pattern from `auth-config.el`.
I extracted `cj/mail--executable-or-warn` so a missing program emits a one-time `display-warning` and returns nil. `cj/mail-configure-smtpmail` and `cj/mail--mbsync-command` both use it. Missing msmtp now leaves `sendmail-program` nil with a warning. Missing mbsync produces a nil sync command instead of the broken `(concat nil " -a")` string. I also wrapped the mbsync executable path in `shell-quote-argument` so unusual install paths don't fall apart on the `" -a"` concat.
I added `tests/test-mail-config-transport.el` with seven tests across Normal / Boundary / Error: debug-default-off, toggle wiring, msmtp present and missing, mbsync present, mbsync path with spaces, and mbsync missing. The `test-mail-config--with-executables` macro stubs `executable-find` from an alist so each test names its own environment.
|
| |
|
|
|
|
|
|
|
|
| |
`cj/--f6-test-runner-cmd-for` was building shell command strings with raw paths and stems via `format`. For ordinary names (`tests/test_foo.py`, `pkg/foo`) that worked fine. But a path with spaces or a stem with shell metacharacters would break or misbehave once the string hit `compile`. A Python test file under `dir with spaces/` would get tokenized as separate arguments.
I added `cj/--f6-shell-quote-argument` that escapes only when the argument doesn't match `cj/--f6-shell-safe-argument-regexp` (alphanumerics, slash, dot, dash, plus a small handful of safe punctuation). Ordinary paths skip the quoter and stay readable. Risky paths route through `shell-quote-argument`.
I wrapped the four interpolations in the test-runner builder: the elisp `FILE=` basename, the elisp `TEST=^test-stem-` regex, both pytest paths, and the Go `./rel-dir`. The Go branch also handles an empty rel-dir explicitly so the result stays `go test ./` instead of constructing `./` via format with an empty string.
I added three boundary tests: a Python path with spaces, an elisp stem with `;`, and a Go directory with spaces. Existing tests for ordinary paths continue to pass since the safe regex covers them.
|
| |
|
|
|
|
|
|
| |
`cj/reset-auth-cache`'s error path read "Failed to clear gpg-agent cache". A user seeing that warning could reasonably think nothing happened. But at that point the Emacs-side caches (auth-source + EPA file handler) have already been cleared. Only the gpg-agent cache failed.
I rewrote the message as "Emacs caches cleared, but failed to clear gpg-agent cache" so the user sees both the partial success and the remaining problem.
The error-path test from the previous commit asserts a substring match on "Failed to clear gpg-agent cache", so it still passes after the rewording.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
I added 13 new tests across 5 files, covering the auth-config functions that lacked tests. Categories follow Normal / Boundary / Error where applicable.
`cj/toggle-auth-source-debug` flips state once and back through two toggles. `cj/oauth2-auto--plstore-read-fixed` gets three tests: cache miss reads then caches, cache hit skips `plstore-open` entirely, and the `unwind-protect` runs `plstore-close` even when `plstore-get` signals.
`cj/reset-auth-cache` covers no-prefix (skip `shell-command`), with-prefix success, and with-prefix shell failure (Emacs caches still clear). `cj/kill-gpg-agent` covers shell exit 0 and non-zero. `cj/clear-oauth2-auto-cache` covers bound-with-entries, bound-but-empty, and unbound. The unbound test restores the binding via `unwind-protect` so other tests in the same Emacs session don't void-variable.
I stubbed every boundary via `cl-letf` (`plstore-open`, `plstore-get`, `plstore-close`, `oauth2-auto--compute-id`, `auth-source-forget-all-cached`, `epa-file-clear-cache`, `shell-command`, `message`, `call-process`) and didn't stub any internal helpers.
15 auth-config tests pass together: 2 existing plus 13 new.
|
| |
|
|
|
|
|
|
|
|
| |
`auth-config.el` was setting `auth-source-debug` to t at startup. That meant every credential lookup printed verbose context to *Messages*. The flag was useful while debugging GPG flow but not appropriate for steady state, since the same config handles Slack, AI, REST, mail, and transcription credentials.
I added a `cj/auth-source-debug-enabled` defcustom (default nil) and wired the use-package block to read its value. For temporary troubleshooting I added two commands: `cj/set-auth-source-debug` (prompted on / off via `y-or-n-p`) and `cj/toggle-auth-source-debug` (M-x convenience).
I also scanned the nearby auth callers. The visible failure messages name hosts and logins but don't print secret values directly. So this change closes the practical exposure path without losing useful diagnostics.
I added `tests/test-auth-config-debug.el` covering the disabled-by-default invariant and the setter wiring through both public variables.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The "d" drill capture template had a malformed source link with only one closing bracket and a literal `n` where a newline should have been:
Source: [[%:link][%:description]
nCaptured On: %U
So new drill captures wrote a broken link and `nCaptured On:` instead of a clean `Captured On:` line on the next row.
I closed the link with the missing `]`, and removed the stray `n` so the line break before "Captured On" is just a real newline. The "f" (PDF drill) template was already correct, so I left it alone.
I added `tests/test-org-capture-config-drill-template.el`. It loads the module, picks up the "d" template, and asserts both the closed link and the `\nCaptured On: %U` form, plus negative assertions against the broken pre-fix shapes.
|
| |
|
|
|
|
|
|
| |
`cj/--move-buffer-and-file` was building the destination as `(concat dir "/" (buffer-name))`. If the buffer had been renamed via `M-x rename-buffer`, or uniquified by Emacs with a `<2>` suffix when a second buffer visited the same filename, the move wrote a file with the wrong name on disk.
I derived the destination basename from `buffer-file-name` instead, in both the internal helper and the interactive wrapper. The wrapper's overwrite-prompt now also formats the real target filename rather than the buffer name.
I added two regression tests: one for a renamed buffer visiting `original.txt`, and one for a `<2>` uniquified buffer with a trailing-slash target directory.
|
| |
|
|
|
|
|
|
| |
The module was binding `cj/system-command-map` under `C-; !`, then a few lines later overwriting the same prefix with `cj/system-command-menu`. The second bind won, so every documented subkey, like `C-; ! r` for reboot and `C-; ! l` for lock, was unreachable.
I kept the prefix map and folded the completing-read menu into it at `C-; ! !`. So `C-; !` still opens the prefix, the menu is one extra `!` away, and the single-letter shortcuts work again. I also added which-key labels for every documented subkey so the popup actually says what each one does.
I added `tests/test-system-commands-keymap.el`. It asserts the prefix stays mounted and that every binding (`!`, `L`, `r`, `s`, `S`, `l`, `E`, `e`) resolves to the right command.
|
| |
|
|
|
|
|
|
| |
The line read `(setq-default vc-follow-symlinks)` with no value. That left the variable at nil, so the comment "don't ask to follow symlinks if target is version controlled" was a lie. Opening any version-controlled symlink still prompted.
I checked the Emacs docs first. The value `t` is the one that follows the link without asking, so that's what I set.
I added `tests/test-system-defaults-vc-follow-symlinks.el` as a regression test. It loads the module with the unrelated side effects stubbed and asserts `vc-follow-symlinks` ends up as `t`.
|
| |
|
|
|
|
|
|
| |
`(concat user-home-dir ".elpa-mirrors/")` was producing `/home/cjennings.elpa-mirrors/` because `getenv HOME` doesn't return a trailing slash on Linux. The local mirrors were silently dropping out of `package-archives` because `file-accessible-directory-p` couldn't find the bogus path.
I replaced the `concat` calls for `elpa-mirror-location` and `localrepo-location` with `expand-file-name`, which handles the slash for us. I also lifted the four per-archive subdirs into their own constants (`elpa-mirror-gnu-location`, `nongnu`, `melpa`, `stable-melpa`) so the archive registration block stops splicing `concat` strings inline.
I added `tests/test-early-init-paths.el`. It loads `early-init.el` against a temp HOME with the package side effects stubbed and asserts each constant and each `package-archives` entry resolves to the right path.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Without this, a one-off typo at projectile's compile/test/run prompt poisons the per-project cache: every subsequent invocation pre-fills the broken value. I hit it during the Phase 2a live-test, where projectile's "All tests" prompt was replaying `go test ../.` and there was no clean way to get the prior known-good back.
Three pieces of machinery, all in `dev-fkeys.el`:
`cj/--projectile-capture-cmd' captures the current cached cmd at the project root before each invocation, stashing a plist with :map / :root / :prior in `cj/--projectile-revert-state'.
`cj/--projectile-revert-on-fail' is a `compilation-finish-functions' hook that reads that state. If the compile failed AND the cmd was modified from the captured prior value AND the prior was non-nil, it puts the prior back in projectile's cmd-map. Test-fails-because-of-real-bug (cmd unchanged through the run) leaves the cache alone. The hook self-removes on first invocation regardless of outcome and clears the state.
`cj/--projectile-around-revert' is the around-advice that wires the two together. I added the advice to all three projectile cmd-runners — `projectile-compile-project', `projectile-test-project', `projectile-run-project' — so the auto-revert applies whether the user invoked via F4 / F6 or directly via `M-x'.
Plus the manual escape-hatch: `cj/projectile-reset-cmds' clears compile/test/run cache for the current project. Bound to `C-; P' under the personal keymap. Use when projectile's auto-derived default was wrong from the start and you want to start fresh — the next F4 / F6 invocation re-derives projectile's project-type default.
TDD: 18 new tests across 4 files, one per helper. The around-advice tests build the capture/install/orig-fn flow against stub cmd-maps and verify state captured, hook installed, orig-fn invoked. The revert hook tests cover failure-and-modified (revert), success (leave alone), failure-but-unchanged (leave alone), nil prior (leave alone), nil state (no-op), and self-removal. The reset-cmds tests cover the all-three-maps clear, no-cached-entry no-op, and no-project user-error.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
I had four call sites passing literal nil to `projectile-compile-project' / `projectile-run-project' / `projectile-test-project'. The literal nil ignored whatever prefix arg the user gave. So `C-u F4 → Compile' or `C-u F6 → All tests' didn't actually force projectile's re-prompt — the prefix arg got swallowed at our wrapper layer.
Switched all four to `current-prefix-arg':
- `cj/--f4-dispatch' — `compile-only' and `run-only' actions.
- `cj/f4-compile-only' — the C-F4 fast path's compiled-project branch.
- `cj/f6-test-runner' — the "All tests" menu entry.
Use case: when projectile's cached cmd is wrong (typo, stale, or whatever), `C-u' on any of these forces projectile to re-prompt instead of replaying the bad cmd silently. The compile-and-run and clean-rebuild paths still pass nil to their chained projectile calls because those run inside an async `compilation-finish-functions' hook, where `current-prefix-arg' has already reverted to nil. Refining those would need to capture the prefix at entry and thread it through; left for later.
TDD: 4 new tests (one per call site) bind `current-prefix-arg' to t and verify projectile receives t. Each test failed against the literal-nil version and passes against `current-prefix-arg'.
|
| |
|
|
|
|
| |
I commented out the global `compilation-finish-functions' hook that closed the *compilation* window 1.5 seconds after a successful compile. With the F6 test runner now landing test output in *compilation*, I want the buffer to stay open afterward so I can read the results, not have it slide out from under me.
The block stays in the file as a commented-out reference so I can flip it back on later if I want. A prog-mode-only variant is noted in the comment for the day I want the auto-close back for non-prog compiles (org-export, etc.) — that needs advice on `compile' to capture the originating buffer's `major-mode' at compile-start, since the hook fires after `compilation-mode' has already taken over the current buffer. Skipped for now per the simpler path.
|
| |
|
|
|
|
|
|
|
|
| |
I shipped Phase 2a with `cj/--f6-test-runner-cmd-for' building `make test-file FILE=<rel-path>' for elisp test files (e.g., FILE=tests/test-foo.el). The project Makefile prepends `tests/' to FILE itself, so the full invocation expands to `tests/tests/test-foo.el' and emacs reports "Cannot open load file". The bug surfaced on a live-test in step 7 of the Phase 2a smoke plan.
Fix: pass `(file-name-nondirectory rel-path)' so the Makefile gets just `test-foo.el' and re-prepends `tests/' itself.
Two unit tests in `test-dev-fkeys--f6-test-runner-cmd-for.el' had encoded the wrong expectation (the rel-path form). Two orchestrator tests in `test-dev-fkeys--f6-current-file-tests-impl.el' inherited the same wrong assertion via integration. Updated all four to assert the basename form.
Verified: full suite green, including the 4 updated tests. Live re-test on `tests/test-dev-fkeys--f6-language-detect.el' should now produce the working `make test-file FILE=test-dev-fkeys--f6-language-detect.el'.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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-<stem>-`. Elisp test files run with `make test-file FILE=<rel-path>` so a per-helper file like `test-foo--bar.el' runs only its own tests.
- Python source files map to `pytest tests/test_<stem>.py'. Python test files run with `pytest <rel-path>'.
- Go runs the package containing the file: `go test ./<rel-dir>'. 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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I added a new module `modules/dev-fkeys.el` that owns the dev F-key block. F4 prompts via `completing-read` with a candidate set filtered by project type (compiled / interpreted / unknown). C-F4 is the compile-only fast path. M-F4 is clean + rebuild. It runs a heuristic clean command derived from the project markers (go.mod, Cargo.toml, Eask, Makefile, CMakeLists.txt) and chains `projectile-compile-project` on success. S-F4 stays on `recompile` and now lives globally instead of duplicated across prog-general.el and prog-c.el. F6 is bound globally to `projectile-test-project` as a Phase 1 stopgap. Phase 2 replaces it with the polyglot test runner spec'd in todo.org.
Project-type detection runs against the projectile root and falls back to `unknown` when no marker matches. Interpreted markers are checked first so a Python or Node project with a Makefile for tasks classifies as interpreted instead of compiled. Compile + Run sequencing uses a one-shot `compilation-finish-functions` hook that self-removes on first invocation and only fires the follow-up when the status string starts with `finished`.
Cleanup in the same commit:
- Dropped F4/F5/F6 from `prog-general.el`'s prog-mode-hook. They are now global.
- Dropped F6→format bindings from prog-c.el / prog-python.el / prog-shell.el. C-; f was already bound in each, so this is pure removal.
- Dropped the duplicate S-F4 from prog-c.el. The global binding covers it.
- Updated the keybinding header in prog-general.el and the workflow comments in prog-c.el / prog-shell.el.
- Wired `(require 'dev-fkeys)` in init.el alongside coverage-core.
TDD: 73 tests across 11 files, one per helper. Production code is split into small testable internals (`cj/--detect-project-type`, `cj/--f4-candidates`, `cj/--f4-derive-clean-cmd`, `cj/--f4-make-once-hook`, `cj/--f4-dispatch`, `cj/--f4-compile-and-run-impl`, `cj/--f4-clean-rebuild-impl`, `cj/--f4-project-root`) plus three thin interactive wrappers. Smoke tests confirm bindings register on load.
Known limitation: if another `compilation-finish-functions` hook fires between my add-hook and the compile finishing, the chain can fire on the wrong compile. The hook self-removes on first invocation regardless of which compile it sees. Documented in the impl docstring. Acceptable for v1.
Phase 2 will replace F6 with the polyglot test runner (tree-sitter queries for Python/Go/TS, sexp scan for Elisp, buffer-local last-test memory).
|
| |
|
|
|
|
|
|
|
|
| |
The boundary test for `calendar-sync--expand-weekly` with a 5-element UNTIL built its byday string from `'("SU" "MO" "TU" "WE" "TH" "FR" "SA")`, a 0-indexed Sunday-first array. The production code uses Monday=1, Sunday=7 throughout: `calendar-sync--date-weekday` returns it that way and `calendar-sync--weekday-to-number` expects it. When start-date landed on a Sunday (start-weekday=7), `(nth 7 array)` overran the 7-element list and returned nil. Then byday=(nil), and inside expand-weekly `(mod (- nil current-weekday) 7)` raised "wrong-type-argument number-or-marker-p nil".
The mismatch made the test fail every Saturday (when "tomorrow" is Sunday) and pass the other six days. The flake had been blamed on stale `.elc` in earlier triage, but `make clean && make test` reproduced the failure on Saturdays.
I switched the lookup to `(nth (1- start-weekday) '("MO" "TU" "WE" "TH" "FR" "SA" "SU"))`, the same convention as every other weekday-mapping in the codebase. I verified across all 7 weekdays via a faked `current-time`: each produces exactly 1 occurrence as expected.
Production code is internally consistent. No production change needed.
|
| |
|
|
|
|
|
|
|
|
| |
The region branch's `(while (< (point) end) (join-line 1))` ran one iteration too many. After the final in-region join, point sat just before the end marker, so the loop fired once more. That extra `join-line 1` consumed the next line's preceding newline and replaced it with a space. Then `(goto-char end)` + `(newline)` reinserted a newline at the original end position, before the inserted space, so the space ended up stranded at BOL of the next line.
I replaced the position-based loop with `count-lines` + `dotimes` to do exactly the right number of joins. I also swapped the trailing `(newline)` for `(forward-line 1)`. The bullet-list use case now lands directly on the next existing line with no blank gap.
The trailing-newline change ripples to `cj/join-paragraph` (which delegates here), so paragraphs now also stop adding a trailing newline when the input lacks one. `require-final-newline` handles file-end discipline on save anyway.
I added 3 new tests that fail against the old loop and pass against the fix. I also updated 11 existing tests whose assertions baked in the old trailing-newline behavior. While in there I wrapped the `cj/custom-keymap` defvar stub in `eval-and-compile` in both test files. The bare defvar wasn't evaluated at byte-compile time, so the `require` of `custom-line-paragraph` would hit a void symbol when the validate-el hook ran.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two small tightening passes on the formatter wiring tests just shipped.
Drops `(ignore-errors ...)` from each test file's
`(require 'prog-PKG)' call. Soft use-package warnings (e.g. lsp-pyright
not being installed) still emit messages without aborting the load. A
hard load failure (syntax error, missing required dep) would now
surface as a test error rather than being silently swallowed.
Adds a `(should (featurep PKG))' assertion per language so the test
output makes "package loaded" visible alongside the fboundp and
binding checks. For webdev the assertion is `(featurep 'prog-webdev)'
since the formatter command is defined directly in prog-webdev.el
(it shells out to the prettier CLI, no separate package to load).
17 tests total now (up from 13), all passing.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Four test files plus a shared testutil that locks in the formatter
bindings on C-; f across the four languages. Each test file checks:
- the format command is fboundp after the relevant package loads
- the C-; f binding resolves to that command (in the relevant
mode-map, or in the buffer-local map for hook-based wiring)
- the underlying executable is on PATH (skipped via ert-skip if
not installed)
No production change. The bindings were already at C-; f via two
mechanisms. Use-package :bind handles python and shell. The other
two install via local-set-key inside a hook. This regression net
catches silent breakage if any of those wirings get reshaped later.
The shared tests/testutil-format-wiring.el carries
format-test--ensure-packages-init, which calls package-initialize
once per batch run, since make test runs Emacs with --no-site-file
--no-site-lisp. Without it, use-package can't find blacken / shfmt
/ go-mode in elpa/. Also format-test--skip-unless-executable wraps
ert-skip with a clear "not on PATH" message so missing tools fail
informatively.
Per-language wiring inventory (no changes, just locked in):
- Python: blacken-buffer in python-ts-mode-map (use-package :bind)
- Shell: shfmt-buffer in sh-mode-map and bash-ts-mode-map
(use-package :bind, gated on :if executable-find)
- Go: gofmt via cj/go-mode-keybindings hook
- TS/JS/Web: cj/webdev-format-buffer via cj/webdev-keybindings hook
13 tests across 4 files, all passing.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Each :vc-installed package whose source repo is also cloned under
~/code now carries a commented :load-path line directly under the
:vc form. Uncomment the :load-path and comment the :vc to flip into
local development without rewriting the use-package block.
Covered: gloss, org-drill, wttrin (emacs-wttrin), chime.
Skipped: org-msg. The previous local clone at ~/code/org-msg is no
longer present; if it gets re-cloned later, add the same hint there.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
org-gcal
The three packages that still loaded from local checkouts now install
via :vc:
- chime → git@cjennings.net:chime.git (was :load-path "~/code/chime")
- wttrin → git@cjennings.net:emacs-wttrin.git (was :load-path
"/home/cjennings/code/emacs-wttrin")
- org-msg → https://github.com/jeremy-compostella/org-msg (was
:load-path "/home/cjennings/code/org-msg"; switching to upstream
rather than a fork since the previous fork wasn't carrying any
active changes)
For the two cjennings.net repos this matches the org-drill (be3e227)
and gloss (2e12131) shape: primary on cjennings.net, post-receive
hook mirroring to GitHub. The previously-commented :vc URLs in chime
and wttrin pointed at GitHub directly, which would have lost the
cjennings-first convention if uncommented later.
Also drops :ensure nil on chime (only relevant under package.el, not
:vc) and removes modules/archived/org-gcal-config.el. Nothing in
init.el or any module references org-gcal, so the file is genuinely
unused.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Adds modules/gloss-config.el with a use-package form that installs
gloss from the cjennings.net bare repo. The bare's post-receive hook
mirrors to GitHub, so the package shows up in both places.
Eager-loaded so gloss-prefix-map exists at startup. :config calls
gloss-install-prefix to bind C-h g.
Lands in the "Modules In Test" section of init.el for v1. Can move
out after the first-week shakedown shows the package is steady.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two test files covering the extracted timestamp-validation helpers.
cj/--validate-timestamps-in-buffer (8 tests): empty buffer no-op,
buffer with no timestamps, buffer with all valid timestamps,
DEADLINE flagged with "DEADLINE" property, SCHEDULED flagged with
"SCHEDULED", inline-timestamp flagged with "inline timestamp",
multiple invalid collected in document order, mixed valid+invalid
returning only the invalid one. Tests use real org parsing and
mock org-time-string-to-absolute at the boundary so an arbitrary
timestamp can be marked invalid for a given test.
cj/--format-validation-report-section (4 tests): no-entries says
"No invalid timestamps found", single-entry produces the file: link
+ Property/Type + Invalid timestamp lines, multiple-entry preserves
input order, every section ends with a trailing blank line.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two bugs in cj/validate-org-agenda-timestamps surfaced while extracting
testable helpers.
1. The DEADLINE / SCHEDULED / TIMESTAMP property lookup used
(intern (downcase prop)) as the key, producing 'deadline,
'scheduled, 'timestamp. org-element-property expects keywords
(:deadline, :scheduled, :timestamp) and returns nil for plain
symbols. The property-check branch had never reported anything
since the function was written. Only inline-regex matches inside
headline contents have ever been flagged. Fixed by building the
keyword form: (intern (concat ":" (downcase prop))).
2. Once #1 is fixed, every property timestamp would also match the
inline-timestamp regex during the contents scan (since the
DEADLINE: / SCHEDULED: / TIMESTAMP lines fall inside
contents-begin/end on a parsed headline), producing duplicate
reports. Added a per-headline list of property timestamp strings
and a member check before pushing an inline match.
The function is also restructured into three pieces to make it
testable:
- cj/--validate-timestamps-in-buffer FILE — pure-ish: walks the
current buffer, returns a list of (FILE POS HEAD PROP TS) tuples.
- cj/--format-validation-report-section FILE INVALID — pure: returns
the per-file org-formatted string.
- cj/validate-org-agenda-timestamps (interactive) — orchestrates
both helpers across org-agenda-files into a report buffer.
The interactive entry-point's behaviour is unchanged from the user's
side except that DEADLINE / SCHEDULED / TIMESTAMP property timestamps
are now actually checked.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Six tests against real temp directories. Mocks only the actual
compile invocations (native-compile-async, byte-recompile-directory)
so the deletion side runs end-to-end against real files.
Covers: native dispatch returns 'native and calls native-compile-async,
byte dispatch returns 'byte and calls byte-recompile-directory,
recursive deletion of every .elc/.eln (including in subdirs), removal
of the eln cache dir on the native path, removal of the elc cache dir
on the byte path, and the missing-cache-dir no-op.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Splits the delete-then-recompile work out of cj/recompile-emacs-home
so it takes DIR and an explicit NATIVE-P flag instead of probing
boundp inside the work loop. Returns 'native or 'byte to surface
which path actually ran.
The interactive wrapper still asks `yes-or-no-p' against
user-emacs-directory and probes `(boundp 'native-compile-async)' once
to decide the dispatch and the prompt's wording. The cancellation
message is unchanged.
|
| |
|
|
|
|
|
| |
Four tests against a temporarily fbound test target: runs the symbol
once and propagates its return value, raises user-error on a nil
symbol, raises user-error naming the symbol when it isn't fboundp,
and verifies the with-timer announce/done messages fire.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Splits the timer dispatch out of cj/benchmark-this-method so it can
be tested with a known method symbol instead of going through
read-string + completing-read. The interactive wrapper still prompts
for both inputs, and now catches the user-error from the internal so
the user-facing behaviour on invalid input is unchanged (the message
goes to the echo area).
Returns the funcall's value to the caller, which is observable
through the timer.
|
| |
|
|
|
|
|
|
| |
Five tests against real temp directories: mixed .el/.elc/.eln content,
recursive descent through subdirectories, no-compiled-files no-op,
empty directory, and the suffix-vs-substring boundary that ensures a
file like "looks.elc.bak" isn't deleted (string-suffix-p, not
string-match).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Splits the file-walking work out of cj/delete-emacs-home-compiled-files
so it takes a directory parameter and returns a count. The interactive
wrapper still hardcodes user-emacs-directory and prints the same
status messages, just with the count interpolated.
The split is scope-aligned with adding tests for the file-walking
behaviour. The original function couldn't be tested without spawning
files inside user-emacs-directory itself, which would pollute the
running config.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Four new test files for the pure-and-near-pure helpers in
org-noter-config.el. The interactive heavyweights (cj/org-noter-start,
cj/org-noter-insert-note-dwim, cj/org-noter--toggle-notes-window,
cj/org-noter--find-notes-file, cj/org-noter--create-notes-file) are
out of scope for this pass — they need org-noter loaded and a real
session, or a substantial internal/wrapper split before they can be
tested without spinning up real PDFs and EPUBs.
- preferred-split: 5 tests walking the 1.4 width-to-height threshold
from both sides plus a square-frame boundary.
- title-to-slug: 7 tests covering multi-word, single-word, mixed
punctuation, leading/trailing junk, collapsed runs, digits, and
empty input.
- generate-notes-template: 5 tests with org-id-uuid mocked, asserting
the rendered template carries the UUID, the dual ROAM_REFS /
NOTER_DOCUMENT pointers, the title-and-category lines, the
ReadingNotes filetag, and the trailing Notes heading.
- predicates: 13 tests covering cj/org-noter--in-document-p,
cj/org-noter--in-notes-file-p, cj/org-noter--session-active-p,
cj/org-noter--get-document-path, and cj/org-noter--extract-document-title.
Mocks derived-mode-p, org-entry-get, and buffer-file-name at the
boundary so the suite doesn't require pdf-tools or nov.
The require chain in each test file is user-constants → keybindings →
org-noter-config because cj/org-noter-notes-directory captures
roam-dir from user-constants and the prefix-map binding at the bottom
of org-noter-config references cj/custom-keymap.
30 new tests, all passing. Full suite green.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Four new test files extending the existing coverage of
cj/emacs-build--format-build-time. The interactive heavyweights
(cj/recompile-emacs-home, cj/delete-emacs-home-compiled-files,
cj/benchmark-this-method, cj/validate-org-agenda-timestamps) are out
of scope for this pass — each needs an internal/wrapper split first
before tests can exercise the logic without UI.
- with-timer macro: 4 tests asserting it returns the FORMS' value,
evaluates the body exactly once, emits both announce and done
messages, and returns the last form when given multiple.
- cj/compile-this-elisp-buffer: 6 tests dispatching across native-async,
native-sync, and byte-compile fallbacks, plus the not-elisp /
no-buffer-file-name error paths and the sync-native error catch.
- cj/emacs-build--summary-string: 5 tests asserting the shape of the
multi-line report (Version, System, Build date, Capabilities section,
yes/no flag rendering) without locking exact wording.
- info-commands smoke: 5 tests exercising cj/info-emacs-build,
cj/info-loaded-packages, cj/info-loaded-features, cj/reload-init-file,
and cj/org-alert-list-timers via boundary-mocked pop-to-buffer and
load-file, asserting buffer creation, content shape, or echo-area
message as appropriate.
20 new tests, all passing. Full suite green.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
predicates
Four new test files extending the existing test-host-environment.el
(which already covered the two battery helpers).
- platform-predicates: env-linux-p, env-bsd-p, env-macos-p, env-windows-p
walked across every supported system-type value. 8 tests.
- display-predicates: env-x-p, env-x11-p, env-wayland-p, env-terminal-p,
env-gui-p exercised under every relevant combination of window-system,
WAYLAND_DISPLAY, and display-graphic-p. 13 tests.
- env-laptop-p: composition over the helpers, with Linux dispatch
isolated from non-Linux dispatch via system-type binding. 8 tests
including env-desktop-p as the inverse. battery-status-function is
forward-declared in this test file (initialized to nil) so cl-letf's
symbol-value place can read the prior value without hitting
void-variable.
- detect-system-timezone: the four-method priority chain. Mocks
cj/match-localtime-to-zoneinfo and getenv at the boundary; uses
cl-letf on file-exists-p / insert-file-contents to exercise the
/etc/timezone fall-through without touching real system files.
5 tests.
34 new tests for host-environment, all passing. Full suite green.
|
| |
|
|
|
|
|
|
|
| |
The numbered list in the docstring had file-comparison and TZ env var
swapped relative to what the code does. The code tries
cj/match-localtime-to-zoneinfo first, then falls back to TZ. Updated
the docstring so the numbering matches the actual `or' chain.
Surfaced while writing tests for the priority chain.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two test files for keybindings.el. cj/jump-open-var gets full N/B/E
coverage (6 tests): existing-file happy path, plus error paths for
unbound symbol, nil value, non-string value, empty string, and missing
file. The smoke file for the auto-generated cj/jump-to-NAME commands
asserts that each spec entry has an fbound command, that the command
is bound in cj/jump-map at the spec's key, that calling each command
invokes cj/jump-open-var with the spec's var, and that cj/jump-map is
mounted under cj/custom-keymap at "j".
The test fixture variable is declared at top level. If it were
let-bound inside a test under lexical-binding, the let would create a
lexical binding that shadows the dynamic one. The production code's
symbol-value would then miss what setq writes. find-file is mocked at
the boundary so the existing-file test doesn't actually open a buffer.
10 tests pass. No production change in keybindings.el.
|
| |
|
|
| |
The cjennings.net bare for org-drill has a GitHub mirror as of today's earlier remote migration. Update the :vc URL in modules/org-drill-config.el to point at the primary instead of the mirror so a fresh install clones from the source-of-truth.
|
| |
|
|
| |
The body had `(find-name-dired . escaped-pattern)`, a dotted pair instead of a function call. The reader accepts it, but the form crashes the moment the `f` alias runs. find-name-dired takes (DIR PATTERN), so the right shape passes default-directory and the escaped pattern.
|
| |
|
|
| |
Captures the v1 design for the gloss Emacs package: layered five-module split, Wiktionary REST as the online source, side-buffer picker for ambiguous terms, libxml HTML strip, mtime-based cache invalidation. The implementation is a separate repo, but the design work happened in this tree, so the doc lives alongside the other design archives here.
|