aboutsummaryrefslogtreecommitdiff
path: root/tests
Commit message (Collapse)AuthorAgeFilesLines
* fix: guard sm2 cl-assert error tests behind Emacs-30 skipCraig Jennings2026-05-271-6/+23
| | | | | | The three error-case tests in test-org-drill-determine-next-interval-sm2.el asserted their cl-assert preconditions with a bare should-error :type 'cl-assertion-failed. On Emacs 29, ERT installs a signal-hook that keeps should-error from matching the condition, so the tests failed the 29.4 job outright. Under undercover's edebug instrumentation the same assert dropped into a blocking debugger in batch mode, which hung the coverage job until the 6-hour timeout on every push. The sm5 and simple8 files already wrap their equivalent error tests in a skip-unless guard for this quirk. I copied that helper into sm2 so all three scheduler files handle Emacs 29 the same way.
* feat: undo last rating, customizable keys, and configurable text limitCraig Jennings2026-05-273-0/+181
| | | | | | | | | | | | | | A batch of self-contained user-facing improvements, squashed from the feat/org-drill-solo-features branch. I added an undo for the last rating (issue #2 follow-up). The rating prompt now takes an undo key (org-drill--undo-key, default u): it restores the previous card's scheduling snapshot, drops the recorded quality, and re-queues that card, then returns to the current prompt. Each rating snapshots the scheduling properties and SCHEDULED line onto a per-session stack capped at org-drill-undo-limit (default 3). org-drill-reschedule loops on the rating read so undo doesn't rate the current card. I made the five session-control keys (quit, edit, help, skip, tags) defcustoms so they can be rebound from customize-group (issue #35), keeping their defaults. The 0-5 rating keys stay as-is, since they're tied to the quality scale rather than being variables. I lifted the hardcoded 100-line entry-text limit in org-drill-get-entry-text into the org-drill-entry-text-max-lines defcustom, defaulting to 100. I also deleted a commented-out old org-entry-empty-p that the real definition had already replaced. Existing tests stay green and each change added its own, including snapshot/restore and prompt-loop tests for undo.
* refactor: dedupe presenters, group defcustoms, and fill in docstringsCraig Jennings2026-05-264-0/+125
| | | | | | | | | | | | | | A cleanup pass over org-drill internals, squashed from the refactor/wave3-cleanup branch. No behavior change. Each step kept the existing tests green and added its own. I shared two duplicated helpers across the language card getters: org-drill--read-property-string and org-drill--face-from-alist. I factored the cloze body-scan out of the two multicloze presenters into org-drill--cloze-body-bounds, org-drill--count-cloze-matches, and org-drill--hide-cloze-by-index, so each presenter just picks which indices to hide. I pulled the presenter resolution and the four-way result classification out of org-drill-entry-f into org-drill--resolve-presenter and org-drill--classify-presentation-result, untangling the pivot of every drill iteration. I split the 37 defcustoms (and the three cloze faces) into four customize sub-groups (display, algorithm, session, leech) so customize-group org-drill is navigable. There's no leitner group because the Leitner settings are defvars. I documented the 22 defuns that had no docstring, rewrote the corrupted org-drill-presentation-prompt-in-mini-buffer docstring, and switched eleven docstrings to the imperative "Return" (issue #2).
* fix: scope cloze fontification to drill buffers via org-drill-modeCraig Jennings2026-05-263-20/+157
| | | | | | | | org-drill-add-cloze-fontification ran on org-font-lock-set-keywords-hook, which fires in every org buffer, and pushed the cloze rule into org's global org-font-lock-extra-keywords. The cloze regexp is built from the [ and ] delimiters, so an org priority cookie like [#A] matched the cloze pattern and got fontified as a cloze in every org buffer, colliding with org's headline fontification and stripping the heading's org-level-N face. I replaced the global install with org-drill-mode, a buffer-local minor mode that adds the cloze keywords only to its own buffer via font-lock-add-keywords. org-drill-auto-enable-mode (default on) turns the mode on from org-mode-hook in buffers that hold drill cards, so existing drill files keep their cloze highlighting while plain org buffers stay clean. Highlighting still respects org-drill-use-visible-cloze-face-p. The cloze regexp itself is unchanged, so the single-line cloze constraint from #38 is preserved.
* chore: coverage, autoload fix, and internal cleanup for org-drillCraig Jennings2026-05-2610-4/+277
| | | | | | | | | | | | | | | | A batch of test-coverage and hardening work, squashed from the test-work branch. Tests: deduplicated a colliding leitner-capture test name so make test-name loads again. Added SM2 assert-failure cases, the six basic multicloze variant delegations, the three English-side spanish-verb branches, and org-drill-current-scope branch coverage. Fix: the entry-point commands (org-drill itself, cram-tree, tree, directory, resume, relearn-item, strip-all-data, merge-buffers) carried no autoload cookies, so M-x failed from a fresh install until something pulled the file in. They're autoloaded now. Perf: org-drill-shuffle was quadratic because it indexed a list with elt on every swap. It runs a linear Fisher-Yates pass over a vector now, and it checks its argument is a list. Feat: added org-drill-version, a constant plus an interactive command, so a bug reporter doesn't have to open the file header. Refactor: extracted org-drill--format-tense-mood, shared by the two verb-conjugation presenters that each carried a copy. Docs: explained the SM8 magic numbers in the simple8 helpers as empirical fits rather than tunable knobs.
* test: fix date-sensitive days-since-creation assertionCraig Jennings2026-05-111-7/+13
| | | | | | test-org-drill-entry-days-since-creation-with-date-added pinned DATE_ADDED to a hardcoded 2026-04-01 and asserted 33-35 days, which only holds when the wall clock is near 2026-05-05. The with-fixed-now mock that was supposed to make it deterministic rebinds `current-time`. But the DATE_ADDED branch of org-drill-entry-days-since-creation goes through `org-time-stamp-to-now`, which reads the real clock. The rebind never reaches it. CI went red once the calendar moved past the +35-day window. I rewrote the test to derive DATE_ADDED 34 calendar days before today (via decode-time / encode-time, so month rollover and DST are handled) and assert the function returns exactly 34. No mock needed. Both sides read the same real clock.
* build: migrate from Cask to EaskCraig Jennings2026-05-101-1/+1
| | | | | | | | | | Cask's upstream has slowed. Eask is the actively maintained successor. Eask's `package-file` directive doesn't auto-install the deps from the .el header's Package-Requires, so the Eask file mirrors emacs/seq/org/persist explicitly. `eask install-deps` also doesn't pull transitive deps, so dash, m-buffer, and shut-up needed their own `depends-on` lines for undercover and elisp-lint to activate. The Makefile swaps are mechanical: $(CASK) → $(EASK), `cask install` → `eask install-deps --dev`, `cask build` → `eask compile`. The URL in org-drill.el's header pointed at the abandoned upstream's GitLab issues page. Eask cross-validates that against website-url, so I updated it to the GitHub mirror — where users file issues now.
* test: cover the active-input-method branch of response-get-buffer-createCraig Jennings2026-05-051-0/+12
| | | | | | | | I added a small case for `org-drill-response-get-buffer-create' that sets an input method then asserts the helper propagates it into the new response buffer. Coverage stayed near 94.7% (instrumentation noise can move it ±0.2%).
* test: cover present-simple-card-with-typed-answerCraig Jennings2026-05-051-0/+18
| | | | | | | | | I added a test for `org-drill-present-simple-card-with-typed-answer' that mocks `prompt-for-string' and the surrounding hide-* helpers, then asserts the presenter forwards the session and returns the prompt's result. Coverage moved from 94.4% to 94.8%.
* test: cover org-drill-presentation-prompt-in-bufferCraig Jennings2026-05-051-0/+61
| | | | | | | | | | I extended the presentation-prompt tests with cases for the in-buffer variant: default prompt assembled when none is supplied, explicit prompt fed straight through, drill-answer cleared on entry, and the session's exit-kind flowing back to the caller after recursive-edit returns. Recursive-edit, display-buffer, and the timer are all mocked. Coverage moved from 93.2% to 94.4%.
* test: cover org-drill-test-display dev helperCraig Jennings2026-05-051-0/+30
| | | | | | | | | | I added a test for the developer helper `org-drill-test-display' that mocks `org-drill-entry-f' to confirm the dispatcher fires and that the zysygy tag is toggled off again on exit. I also added a small case for `org-drill-test-display-rescheduler' that verifies it runs `org-drill-display-answer-hook' and waits on read-key-sequence. Coverage moved from 92.7% to 93.2%.
* test: cover all navigation arrow keys in --read-rating-keyCraig Jennings2026-05-051-0/+9
| | | | | | | | | I extended the existing rating-key test with a dolist that fires every remaining navigation key (up, left, right, prior, next) — previously only `down' was covered. Each key advances the loop without ending it, then a numeric input terminates. Coverage moved from 92.3% to 92.7%.
* test: cover org-drill-add-cloze-fontification flag branchCraig Jennings2026-05-051-0/+19
| | | | | | | | | I added a couple of cases for `org-drill-add-cloze-fontification': when `org-drill-use-visible-cloze-face-p' is t, a fontification spec gets pushed onto `org-font-lock-extra-keywords'; with the flag nil, the list is left alone. Coverage moved from 92.1% to 92.3%.
* test: cover SM5 random-noise, --read-key-sequence input-method dance, ↵Craig Jennings2026-05-051-0/+79
| | | | | | | | | | | | | goto-heading error I added small-branch tests for the SM5 dispersal-factor multiplier when `org-drill-add-random-noise-to-intervals-p' is t, `org-drill--read-key-sequence' deactivating/reactivating an active input method (and skipping the dance when none is active), and the error branch in `org-drill-goto-drill-entry-heading' when no parent heading carries the drill tag. Coverage moved from 92.1% to 92.1%.
* test: cover --pick-next-marker and resume happy-pathsCraig Jennings2026-05-052-0/+96
| | | | | | | | | | | | | I extended `tests/test-org-drill-route-rating-result.el' with four `org-drill--pick-next-marker' cases (no resume → pop, resume with live drill marker → keep current-item and clear resume-p, resume with nil or non-drill current-item → fall through to fresh pop). I also extended the resume regression file with the three happy-path branches of `org-drill-resume': pending entries → resume, finished with pending count → y-or-n-p offers a new session, finished with nothing → print 'finished'. Coverage moved from 91.7% to 92.1%.
* test: cover --route-rating-result quit/edit/skip/next branchesCraig Jennings2026-05-051-0/+71
| | | | | | | | | | | I added tests for `org-drill--route-rating-result' covering all four return values: nil → 'quit (end-pos becomes :quit), 'edit → 'edit (end-pos becomes a marker), 'skip → 'skip (current-item cleared), quality 0 → 'next (pushes to again-entries) and quality 5 → 'next (pushes to done-entries). Also a case where again-entries is non-empty so the shuffle branch runs. Coverage moved from 91.6% to 91.7%.
* test: cover the leitner main entry orchestratorCraig Jennings2026-05-051-0/+106
| | | | | | | | | | I added tests for `org-drill-leitner' with mocked `leitner-entry' returning t (full loop completes, summary printed), 'quit (pcase quit branch returns t), 'edit (pcase edit branch jumps to marker), and a case where the boxed queue is short of `org-drill-maximum-items-per-session' so `leitner-start-box' runs to top it up. Coverage moved from 89.8% to 91.6% — the suite is now over 90%.
* test: cover the org-drill main entry and hypothetical-next-review-date dispatchCraig Jennings2026-05-052-0/+129
| | | | | | | | | | | I added tests for the public `org-drill' command that mock `org-drill-entries' so the orchestrator runs in batch: empty buffer → 'no pending' message, populated buffer → entry loop runs, cram=t flag flips the session's cram-mode slot, resume-p skips entry collection. I also extended the cloze + scheduling helpers file with sm2 and simple8 coverage for `org-drill-hypothetical-next-review-date'. Coverage moved from 89.0% to 89.8%.
* test: cover replace-entry-text multi mode, multicloze weight errors, ↵Craig Jennings2026-05-051-0/+93
| | | | | | | | | | | | | copy-to-buffer fallback I added tests for `org-drill-replace-entry-text' with the multi-p flag (list of replacements creates multiple overlays) and the simple single-overlay case, the multicloze weight-validation error branches in `-firstmore' and `-firstless', and the `org-drill-copy-entry-to-other-buffer' recovery path that appends to the end of DEST when the source's outline path doesn't exist there. Coverage moved from 88.6% to 89.0%.
* test: cover leech-warning, minibuffer timer, cloze length flag, simple8 noiseCraig Jennings2026-05-051-0/+115
| | | | | | | | | | | I added tests for `org-drill--maybe-prepend-leech-warning' (three branches: not a leech, method not warn, leech with warn), the presentation minibuffer-timer function (emits MM:SS prompt, cancels after 10 calls, switches to '++:++' after an hour), the `org-drill-cloze-length-matches-hidden-text-p' branch (display becomes a dotted string), and the simple8 random-noise dispersal-factor branch. Coverage moved from 87.8% to 88.6%.
* test: cover marker end-pos, variable-pitch restore, young/overdue queue branchesCraig Jennings2026-05-053-0/+104
| | | | | | | | | | | I extended three existing test files with cases that hit branches the suite was missing: `org-drill--show-end-message' with a live-marker end-pos (jumps to the marker), `org-drill--restore-display' for the variable-pitch-on / variable-pitch-off / text-scale paths, and `org-drill-pop-next-pending-entry' for the young-mature and overdue branches in the queue priority cond. Coverage moved from 87.0% to 87.8%.
* test: cover leitner-start-box and --read-rating-key input shapesCraig Jennings2026-05-052-0/+198
| | | | | | | | | | I added tests for `org-drill-leitner-start-box' (move N entries from unboxed into box 1, respect the count arg, zero is a noop) and for `org-drill--read-rating-key' (string input, arrow vector, wheel-event vector, help key showing help block, tags key triggering set-tags, typed-answer rendering in the prompt). Coverage moved from 85.7% to 87.0%.
* test: cover merge-buffers, all-leitner-capture, leitner-vs-drill summaryCraig Jennings2026-05-051-0/+142
| | | | | | | | | I added tests for `org-drill-merge-buffers' (yes/no confirmation, defaulting dest to current buffer, full migrate pipeline), `org-drill-all-leitner-capture' (populate and reverse boxed/unboxed queues), and the `org-drill-leitner-vs-drill-entries' summary message. Coverage moved from 82.4% to 85.7%.
* test: cover --setup-display, --restore-display, --migrate-from-source, ↵Craig Jennings2026-05-052-0/+214
| | | | | | | | | | | scope=directory I added unit tests for the display-state helpers (capture text scale, variable-pitch, modeline; restore them on session exit), the directory branch of `org-drill-current-scope', and `--migrate-from-source''s three-branch cond (matching ID, no ID, ignore-new-items). Coverage moved from 81.8% to 82.4%.
* test: cover map-leitner-capture, response-mode, timer, sm2/simple8 dispatchCraig Jennings2026-05-053-0/+210
| | | | | | | | | I added direct tests for `org-drill-map-leitner-capture' (route by DRILL_LEITNER_BOX), the response-mode exit-kind handlers (quit/edit/skip/ tags/rtn), `org-drill-presentation-timer-cancel', and the sm2/simple8 branches of `org-drill-smart-reschedule''s algorithm dispatcher. Coverage moved from 80.6% to 81.8%.
* test: cover orchestration helpers extracted during refactorCraig Jennings2026-05-051-0/+245
| | | | | | | | | | | | I added 16 ERT tests for the helpers carved out of org-drill and org-drill-merge-buffers in the recent refactor pass: prepare-fresh-session, queues-empty-p, collect-entries, show-resume-hint, show-end-message, build-dest-id-table, copy-scheduling-to-marker, and strip-unmatched-dest-entries. The clean-completion test for show-end-message binds org-drill-save-buffers-after-drill-sessions-p to nil so the dispatcher doesn't trip save-some-buffers' interactive prompt under batch ERT.
* ci: drop Emacs 28.2 from matrix; skip cl-assert tests on Emacs 29Craig Jennings2026-05-052-30/+25
| | | | | | | | | | | | | | | | | | | | | | | Two pragmatic changes after watching CI fail repeatedly: CI matrix: dropped 28.2. Emacs 28 ships Org 9.5; Cask is supposed to pull our declared org>=9.6 over the built-in but doesn't reliably in this CI setup, and several test categories use APIs/behaviors (cl-letf on signal-hook-function, eieio idioms, modern org-fold-*) that don't quite work on 28. Practical floor is now Emacs 29 (ships Org 9.6 built-in). Matrix is 29.4 + snapshot. Scheduler error tests: added skip-unless (>= emacs-major-version 30) to the test-scheduler--should-cl-assert helper in both simple8 and sm5 test files. ERT 29 installs an aggressive signal-hook-function around the entire ert-deftest body that intercepts every signal before any inner condition-case runs; shadowing the hook locally doesn't help (verified across four attempts). The eight cl-assert-precondition tests now run on Emacs 30+ where ERT's hook leaves inner condition-case alone, and skip on 29.x. All other tests still run on 29.4. Locally green. Pushing to verify CI.
* test: shadow signal-hook-function so cl-assertion-failed can be caughtCraig Jennings2026-05-052-17/+28
| | | | | | | | | | | | | | | | | | | | Attempt 4 at making the eight scheduler error tests pass on Emacs 29.4 in CI. The earlier approaches kept failing because ERT 29.4 installs ert--should-signal-hook as signal-hook-function around the entire ert-deftest body — not just inside should forms. That hook fires on every signal before any inner condition-case can catch it, which is why even a bare (condition-case ... (cl-assertion-failed nil)) at the top of the test body didn't work. The new helper rebinds signal-hook-function to nil inside its own let-scope, so condition-case catches the cl-assertion-failed signal normally. The ert-fail on the no-error path runs outside that shadowing scope, so it still routes through ERT's failure handling. Locally green; pushing to test 29.4 in CI.
* test: catch cl-assertion-failed by name without ERT should-wrappingCraig Jennings2026-05-052-20/+24
| | | | | | | | | | | | | | | | The previous fix wrapped condition-case in (should (eq 'caught ...)), but ERT in Emacs 29.4 installs signal-hook-function around should forms — that hook fires on every signal, intercepting them before the inner condition-case can catch. CI on 29.4 still failed. This iteration drops should entirely. Each test body becomes a plain condition-case at the top level: run the form, and if it returns normally, ert-fail. Catch cl-assertion-failed by name rather than via the error parent — its parent-class registration is inconsistent across Emacs versions, but the symbol-name match through condition-case always works. Locally green; let's see what 29.4 does with it.
* test: replace should-error with manual condition-case in scheduler error testsCraig Jennings2026-05-052-24/+40
| | | | | | | | | | | | | | | | | | | The :type 'cl-assertion-failed' fix didn't help — Emacs 29.4 in CI still marks the eight scheduler error tests as failures even though the cl-assertion-failed signal clearly fires (visible in the test-failure backtrace). Whatever ERT's should-error is doing in 29.4, it isn't accepting the signal as a pass. Replacing should-error with a manual condition-case wrapped in should sidesteps the fragility — we just verify SOMETHING was signalled, which is all the test ever needed. Extracted as a test-scheduler--should-cl-assert helper macro in each file (the two test files don't share infrastructure right now). Locally green; expected to clear the 29.4 CI failure.
* test: pin should-error type to cl-assertion-failed in scheduler error testsCraig Jennings2026-05-052-8/+16
| | | | | | | | | | | | | | | | | | CI on Emacs 29.4 failed eight scheduler :error: tests (5 in Simple8, 3 in SM5). All eight wrap a function call that violates a cl-assert precondition and use bare (should-error ...) to catch the resulting cl-assertion-failed signal. The same tests pass locally (Emacs 30.2) and in CI on Emacs snapshot. Hypothesis: in 29.4 cl-assertion-failed isn't registered with error as a parent class, so the default should-error filter (which catches type 'error') doesn't match. Adding an explicit :type 'cl-assertion-failed' tells should-error exactly what condition to expect, avoiding the inheritance-class question entirely. Locally still green; expected to clear the CI failure on the next push.
* build: bump Org dep to 9.6 to match unguarded org-fold-* callsCraig Jennings2026-05-051-0/+44
| | | | | | | | | | | | | | | | | org-drill calls org-fold-show-entry and org-fold-show-subtree from seven sites without fboundp guards. Both functions arrived in Org 9.6. But the package declared org 9.3 (Package-Requires) / org 9.2 (Cask), so users on older Org would silently void-function at runtime instead of getting a clear install-time mismatch error. Bumped both declarations to org 9.6. Wrapping each of the seven call sites with fboundp would be the alternative, but Org 9.6 was released October 2022 — three-and-a-half years ago — and we already have a follow-up TODO to drop the legacy time-to-inactive fallback that this version bump unblocks. Two tests verify the declared dep and that the org-fold APIs are actually bound on the running Org version.
* fix: keep collection scan alive when one entry errors (upstream #53)Craig Jennings2026-05-052-0/+103
| | | | | | | | | | | | | | | | | | | | | | User reported that running org-drill on a buffer with a new (no-ID) entry threw 'Wrong Type Argument: hash-table-p, nil' and stopped the scan — every subsequent entry was silently skipped, so the user had to re-run org-drill once per item (10 items meant 10 invocations). The exact source of the hash-table error is environment-dependent (Emacs version, Org version, lazy org-id-locations init, Doom overrides), so this fix targets the user-visible failure mode instead of the underlying triggering condition. Wrapped the per-entry body of org-drill-map-entry-function in condition-case. An error on one entry now logs a 'skipping' message and the scan continues to the next entry. The session collects all the well-formed items, and the user can re-run drill once total to process them — no more once-per-item. Two regression tests: one verifies the resilience behavior directly (fail entry 1, scan continues to entry 2), the other documents the ID-creation-with-uninitialized-locations scenario as a smoke check.
* fix: keep cloze regex within a single line (upstream #38)Craig Jennings2026-05-051-0/+57
| | | | | | | | | | | | The inner match was [[:cntrl:][:graph:][:space:]]+?, which silently includes newline. A stray [ could match all the way to a ] several lines later, covering org headings in between with the visible-cloze face. Reporter saw lines 4 and 5 of test.org lose their org-level-N face and use default instead. Switched the inner class to [^\n]+?. Clozes now stay within a single line, which matches the design intent and stops the face bleed. Three new tests cover the regression.
* fix: skip LaTeX preview on TTY frames (upstream #44)Craig Jennings2026-05-051-0/+49
| | | | | | | | | | | | | | | | | Issue #44 (2021): running org-drill in a TTY emacsclient (the reporter mentioned tmux) raised "Window system frame should be used" because LaTeX preview helpers (org-latex-preview, org--latex-preview-region) require a window system and weren't guarded. Wrapped both call sites with (when (display-graphic-p) ...). - org-drill--show-latex-fragments: now a silent no-op on TTY - present-default-answer's clear-and-preview block: same guard LaTeX previews are inherently graphical. The right behavior on TTY is to skip the preview rather than crash the session — TTY users still see the underlying source text just fine.
* fix: clear stale end-pos on resume so final-report fires (upstream #33)Craig Jennings2026-05-051-0/+57
| | | | | | | | | | | | | | | | When a user interrupted a drill session to edit or capture, the session's end-pos slot got set to a marker (or :quit). The end-of- org-drill cond branched on end-pos: if set, show resume message and skip org-drill-final-report. That worked for the first interruption. But on org-drill-resume, the session was reused with end-pos still carrying the prior marker. Even when the resumed session completed normally, the same cond branch fired again — silently skipping final-report. Clear end-pos at the top of org-drill when resume-p is non-nil, per Markus's proposed patch on the upstream issue. The resumed session can now reach the final-report branch.
* fix: restore display state in the buffer setup ran inCraig Jennings2026-05-051-0/+53
| | | | | | | | | | | | | | | | | | org-drill--setup-display saved buffer-local state (mode-line, variable-pitch-mode) into global defvars and called setq-local on the current buffer. org-drill--restore-display read those globals and ran setq-local against whatever buffer happened to be current at restore time. If the user switched buffers mid-session, the restore wrote to the wrong buffer — leaving the original drill buffer's mode-line still hidden and trampling the destination buffer's mode-line with whatever was saved from elsewhere. Captured the buffer at setup in org-drill--saved-display-buffer. Restore now wraps mode-line and variable-pitch restoration in with-current-buffer against that saved buffer. Text-scale stays global (the underlying face attribute is process-wide).
* fix: recover from corrupted persist file at package load (upstream #45)Craig Jennings2026-05-051-0/+51
| | | | | | | | | | | | | Issue #45 (2021): persist-load raised End of file during parsing at persist.el:413 in some configurations, likely from a corrupted persist data file. Pre-fix, this propagated up through the top-level (persist-defvar org-drill-sm5-optimal-factor-matrix ...) form at file-load time and broke the entire package's load. Wrapped the persist-defvar form in condition-case. On failure, the matrix falls back to a fresh nil binding via plain defvar, and a message tells the user what happened. org-drill continues to load normally.
* fix: guard org-drill-again and org-drill-resume against nil last-sessionCraig Jennings2026-05-051-0/+39
| | | | | | | | | | | | Both functions bound session to org-drill-last-session and immediately called setf / org-drill-entries-pending-p on it without checking for nil. First-time invocation (or after Emacs restart with no active session) threw an obscure eieio-oset / nil-slot type error instead of a clear message. Added (unless session (user-error ...)) at the top of each function. A user running M-x org-drill-resume cold now sees a sensible message telling them to run org-drill first.
* fix: hide-drawers ignores drawers with no :END:Craig Jennings2026-05-051-0/+66
| | | | | | | | | | | | | drawer-end was captured as (save-excursion (re-search-forward ':END:' end t) (point)) which always returns a number — (point) is always defined. The subsequent (when drawer-end ...) guard was dead, so a malformed drawer (typo in :END:, mid-edit truncation) ended up with a junk overlay covering whatever range point happened to land in. Captured the search result itself and gate on it. Malformed drawers are now skipped silently; well-formed drawers still get their normal overlay.
* fix: default DRILL_LEITNER_BOX to 0 in leitner-reboxCraig Jennings2026-05-051-0/+43
| | | | | | | | | | | When the property is absent, org-entry-get returns nil and string-to-number errors with wrong-type-argument. Reachable when a user removes the property mid-session, or when a Leitner-tagged entry is rebox'd before its DRILL_LEITNER_BOX has been set. Wrapped the org-entry-get with (or ... "0"). Box 0 makes the rating semantics still sensible: a downgrade stays at 0, a promotion goes to 1.
* fix: drop dead translate_number entry from card-type alist (upstream #43)Craig Jennings2026-05-051-0/+47
| | | | | | | | | | | | | | | | | | | The card-type alist mapped translate_number to a function that no longer exists in the file. Cards with DRILL_CARD_TYPE: translate_number crashed with void-function during drill instead of being skipped. Reporter (issue #43, 2021) said they had old decks using the documented translate_number type and were getting the crash on restore. The function was apparently removed at some point without clearing the alist entry. Removed the alist entry so entry-f's no-presentation-fn branch fires and returns skip after messaging the user. Legacy decks now degrade gracefully instead of crashing the session. Tests in tests/test-org-drill-translate-number-regression.el lock the behavior in (entry-f returns skip on translate_number, alist no longer carries the entry).
* test: org-drill-entry-f per-card dispatcherCraig Jennings2026-05-051-0/+102
| | | | | | | | | | | 5 ERT tests for the per-entry dispatcher: - unknown DRILL_CARD_TYPE returns 'skip and doesn't call answer-fn - presenter returns nil (quit) → entry-f returns nil - presenter returns 'edit → propagates unchanged - presenter returns 'skip → propagates unchanged - presenter returns t (successful) → default answer presenter runs and the complete-func (reschedule) is invoked with the session
* test: org-drill-entries main loop queue-routing logicCraig Jennings2026-05-051-0/+130
| | | | | | | | | | | | | | 5 ERT tests for the session loop body: - quit return (nil) sets end-pos = :quit and exits the loop - edit return ('edit) sets end-pos to a marker and exits - passing rating (>failure-quality) routes marker to done-entries - failing rating (<= failure-quality) skips done-entries - skip return clears current-item without queueing Tests use a tempfile-backed buffer because pop-next-pending-entry calls org-drill-entry-p on each marker, which requires real org buffer state.
* test: replace-multi, map-entry-function, sm2/simple8 schedulersCraig Jennings2026-05-051-0/+167
| | | | | | | | | | | | | 9 ERT tests filling small gaps in coverage: - replace-entry-text-multi: N replacements → N overlays, each showing the matching string via display prop - map-entry-function: virgin entry → new-entries, future-scheduled entry → dormant-entry-count (not new), non-drill skipped - smart-reschedule with org-drill-spaced-repetition-algorithm bound to sm2 and simple8 (default tests covered sm5) - smart-reschedule with DRILL_CARD_WEIGHT - entries-pending-p: overdue queue alone keeps session pending
* test: prompt-for-string, leitner-capture, and resume coverageCraig Jennings2026-05-051-0/+143
| | | | | | | | | | | 7 ERT tests covering the last batch of testable smaller helpers: - presentation-prompt-for-string: stores typed answer in session->drill-answer, uses default prompt when arg is nil - map-leitner-capture: unboxed entry goes to unboxed list, box-3 entry goes to boxed list, box>5 (graduated) skipped, non-drill entry silently skipped - org-drill-resume: with pending entries, calls org-drill resume-p=t
* test: final-report message format and warning-branch zero-guardCraig Jennings2026-05-051-0/+162
| | | | | | | | | | | | 6 ERT tests covering org-drill-final-report: - Reviewed-count from done-entries appears in the message - Pending-queue line lists per-queue counts (1 new, 2 young, etc.) - 100% pass rate doesn't trigger the WARNING branch - Below forgetting-index pass rate triggers the warning prompt - Per-quality counts produce correct percentages (1/4 = 25%) - Warning-branch with zero dormant+due survives (locks in the zero-divisor guard fix)
* test: explain-text helpers and SM-or-Leitner dispatchCraig Jennings2026-05-051-0/+161
| | | | | | | | | | | | | 9 ERT tests covering: - get-explain-text: no :explain: parent returns existing-text, parent with :explain: tag adds its body, recursion stops at top-level outline depth - explain-answer-presenter: creates after-string overlay with Explanation: prefix, replaces prior overlay on second call - explain-cleaner: removes the overlay, no-op on missing overlay - sm-or-leitner: runs SM (via org-drill-again) when pending entries exceed leitner-completed, falls through to Leitner otherwise
* test: Spanish verb presenter and top-level drill commandsCraig Jennings2026-05-051-0/+151
| | | | | | | | | | | | | | 10 ERT tests: - present-spanish-verb (six-way cl-random branch): 0=present-translate, 2=past-translate, 4=future-perfect (mocked cl-random + presentation prompt) - org-drill-cram: passes cram=t to org-drill, accepts scope arg - org-drill-cram-tree: delegates to cram with scope=tree - org-drill-tree: passes scope=tree to org-drill - org-drill-directory: passes scope=directory - org-drill-again: resumes (resume-p=t) when prior session has pending entries, starts fresh otherwise
* test: language card presenters (verb conjugation, noun declension)Craig Jennings2026-05-051-0/+159
| | | | | | | | | | | | | 7 ERT tests covering the language-card presenters and answer-show helpers: - present-verb-conjugation: runs cleanly with all required VERB_ properties, formats tense+mood string when both set - show-answer-verb-conjugation: calls reschedule-fn - present-noun-declension: runs cleanly, includes definite/indefinite suffix when DECLINE_DEFINITE is set, skips suffix when neither extra property is present - show-answer-noun-declension: calls reschedule-fn