aboutsummaryrefslogtreecommitdiff
path: root/tests
Commit message (Collapse)AuthorAgeFilesLines
* 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
* test: card presenters with mocked presentation-promptCraig Jennings2026-05-051-0/+210
| | | | | | | | | | | | | 9 ERT tests covering the per-card-type presentation functions: - present-simple-card: clozes hidden during prompt, return value passthrough, overlays cleaned up after via with-hidden-cloze-text - present-default-answer: drill-answer slot path (overlay-displays the answer), unhide path (reveals body, calls reschedule-fn) - present-card-using-text: replaces body with question, sets drill-answer slot when ANSWER arg provided - present-two-sided-card and present-multi-sided-card: run cleanly on 2-side and 3-side cards
* test: add multicloze hide-n and hide-nth coverageCraig Jennings2026-05-051-0/+163
| | | | | | | | | | | | | 9 ERT tests with org-drill-presentation-prompt mocked to bypass interactive prompts: - hide-n with positive N: hides exactly N cloze overlays, no-op when buffer has no cloze - hide-n with negative N (show-mode): hides total-N pieces, leaving abs(N) visible - force-show-first + force-hide-first → user-visible error - hide-nth: hides only the Nth piece, no-op when out of range, negative N counts from the end
* test: presentation-prompt return values and dispatcherCraig Jennings2026-05-051-0/+125
| | | | | | | | | | | | 7 ERT tests covering the card-prompt return-value contract: - org-drill-presentation-prompt-in-mini-buffer with input-pending-p and read-key-sequence both mocked: quit → nil, edit → 'edit, skip → 'skip, any-other-key → t - explicit PROMPT arg appears in the formatted full-prompt - org-drill-presentation-prompt dispatcher routes by org-drill-presentation-prompt-with-typing (nil → mini-buffer variant, non-nil → in-buffer variant)
* test: add multicloze weighted-presenter dispatch coverageCraig Jennings2026-05-051-0/+143
| | | | | | | | | | | | | | | | | 8 ERT tests covering hide1-firstmore, show1-lastmore, show1-firstless. Each wraps a cond that selects between common and uncommon multicloze presenters based on org-drill-cloze-text-weight and the entry's total-repeats counter. Underlying presenter functions are mocked to no-op stubs that record which one was selected — the branch logic is what's under test, not the (interactive) cloze-prompt itself. Cases covered per function: - nil weight → fall back to non-weighted variant - invalid weight (non-positive int) → error - non-trigger rep → common path (hide-first / show-last / skip-first) - trigger rep → uncommon path (hide-n with appropriate force flags)
* test: add coverage for org-drill-reschedule rating loopCraig Jennings2026-05-051-0/+143
| | | | | | | | | | | | 10 ERT tests covering the rating function (read-key-sequence mocked): - Quality 0/3/5 each returns the integer rating - Quit key returns nil, edit key returns 'edit - Successful rating pushes quality onto session->qualities - Non-cram rating sets a SCHEDULED stamp via smart-reschedule - Cram mode skips the reschedule (no SCHEDULED set) - Failure with >= leech-failure-threshold tags entry :leech: - Failure under threshold doesn't tag :leech:
* test: add navigation, key-binding, push-end, and leitner-rebox coverageCraig Jennings2026-05-051-0/+191
| | | | | | | | | | | | | | 15 ERT tests covering: - org-drill-goto-entry: marker → buffer + position - org-drill-goto-drill-entry-heading: stays put on the drill heading, walks up from a child sub-heading, errors outside any drill entry - org-drill-command-keybinding-to-string: nil for unbound, string for bound commands - org-drill-push-end: appends to non-empty and empty lists - org-drill-leitner-rebox (interactive — read-key-sequence mocked): rating 0 resets to box 1, rating 1 decrements (with floor at 1), rating 2 stays, ratings 3-5 promote, quit-key returns 'quit
* test: add minibuffer prompt, relearn-item, and progress-message coverageCraig Jennings2026-05-051-0/+145
| | | | | | | | | | | 10 ERT tests covering: - org-drill--make-minibuffer-prompt: status char (N/Y/o/!/F), cram-mode shows C, done-entries count, prompt-text passthrough - org-drill-relearn-item: resets DRILL_LAST_INTERVAL to 0, unschedules the entry (days-ahead = 0 path through smart-reschedule) - org-drill-progress-message: emits on multiples of 50, silent otherwise, includes the COLLECTED count
* test: add coverage for org-drill-smart-rescheduleCraig Jennings2026-05-051-0/+119
| | | | | | | | | | | | | | 6 ERT tests covering all four days-ahead branches: - 0 → unschedule (treat as new again) - negative → schedule today (current-time) - positive → schedule N days ahead - nil → use the algorithm-computed next-interval (locks in the numberp guard fix) Plus property side-effects: writes DRILL_LAST_INTERVAL / EASE / TOTAL_REPEATS via store-item-data, and TOTAL_REPEATS increments on each call.
* test: queue popping, fontification, ID creation, strip-all-dataCraig Jennings2026-05-051-0/+219
| | | | | | | | | | | | | | | | | 12 ERT tests covering: - org-drill-pop-next-pending-entry: empty session → nil, failed prioritized over new/old, again-entries fallback, max-item limit gates primary queues but again-entries bypasses - org-drill-card-tag-caller: dispatches per-tag hook fn from alist, unknown tag is silent no-op (falls through to ignore) - org-drill-id-get-create-with-warning: creates ID and flips warned-about-id-creation flag, doesn't re-warn (uses tempfile- backed buffer because org-id-get requires file-visiting) - org-drill-add-cloze-fontification: sets buffer-local cloze-regexp and cloze-keywords from current delimiters - org-drill-strip-all-data: yes-or-no-p gate (no-confirm = no-op, confirm = wipes scheduling props)
* test: add explain-entry-p, end-of-entry-pos, and language card info coverageCraig Jennings2026-05-051-0/+176
| | | | | | | | | | | | | 14 ERT tests covering: - org-drill-explain-entry-p: with/without :explain: tag, no-inherit flag rejects parent's tag - org-drill-end-of-entry-pos: single-heading and multi-heading subtree bounds - org-drill-get-verb-conjugation-info: full property read, tense-only (mood optional), missing-required errors, tense-color highlight face - org-drill-get-noun-info: full property read, missing-required errors, feminine-gender orchid color from alist, unknown-gender red fallback
* test: add list-utility, hide-comments/drawers, and Leitner promotion coverageCraig Jennings2026-05-051-0/+175
| | | | | | | | | | | | | 17 ERT tests covering: - org-drill-swap: distinct indices, same-index no-op, end-to-start - org-drill-shuffle: preserves element multiset, empty list, singleton - org-drill-pop-random: removes-one, nil-on-empty, empties singleton - org-drill-hide-comments: per-line overlay, no-op on comment-free buffer - org-drill-hide-drawers: PROPERTIES drawer, multiple drawers, no-op on drawer-free entry - org-drill-leitner-promote: box-N → box-(N+1), graduation at box 5 (with and without org-drill-leitner-promote-to-drill-p flag)
* test: add entry-status, days-since-creation, and overdue ordering coverageCraig Jennings2026-05-051-0/+212
| | | | | | | | | | | | | | | | | | | | | | Plus a docs fix to org-drill-order-overdue-entries' header comment. 16 ERT tests covering: - org-drill-entry-status: non-drill nil, empty entry nil, virgin :new, future :future, low-quality :failed, due+short-interval :young, due+long-interval :old, very-overdue :overdue, skipped-leech :unscheduled, three-element return shape - org-drill-entry-days-since-creation: with DATE_ADDED, missing without flag (nil), missing with use-last-interval-p flag (overdue+interval) - org-drill-order-overdue-entries: empty stays empty, non-lapsed sorted by DUE desc, lapsed split (by DUE crossing threshold, not AGE) appearing after sorted by AGE desc Fixed misleading header comment at line 2888 — it claimed the lapse split was by AGE, but the code uses DUE (cl-second). This matches the semantic gate in org-drill--entry-lapsed-p, so the code was right and the comment was stale. Updated the comment to state the actual three-step sort.
* test: add session-state predicate coverageCraig Jennings2026-05-051-0/+265
| | | | | | | | | | | | | | | 23 ERT tests covering the queue control flow: - org-drill-entries-pending-p: empty session, current-item slot, again-entries bypassing limits, item-count limit interaction - org-drill-pending-entry-count: empty, sums all queues, current-item marker check - org-drill-maximum-duration-reached-p: nil-duration disables, cram bypasses, fresh session under limit, old session over limit - org-drill-maximum-item-count-reached-p: nil disables, cram bypasses, under/at limit, includes-failed-items-p flag - org-drill--entry-lapsed-p: feature flag gate, threshold respected - org-drill-free-markers: explicit list, t-frees-everything
* test: add overlay coverage for cloze hide/show and entry text replacementCraig Jennings2026-05-051-0/+222
| | | | | | | | | | | | | | | | | 19 ERT tests covering the buffer-overlay machinery behind card presentation: - org-drill-hide-region / unhide-text: bounds, optional display text, no-op on clean buffers, leaves unrelated overlays alone - org-drill-hide-clozed-text / unhide-clozed-text: hides every cloze span with org-drill-cloze-overlay-defaults category, clean round-trip - org-drill-hide-cloze-hints: hides only the ||hint portion when present, no-op when absent (locks in the production fix) - org-drill-replace-entry-text / unreplace-entry-text: covers entry body with placeholder string overlay - org-drill-get-entry-text: returns body text, strips text-properties by default
* test: add tests for due/overdue predicates and scope translationCraig Jennings2026-05-051-0/+257
| | | | | | | | | | | | | | | 27 ERT tests covering the predicates that decide whether a card appears in today's drill session: - org-drill-days-since-last-review / hours-since-last-review with current-time mocked for determinism - org-drill-entry-days-overdue: normal mode (scheduled future/past/now, leech skip), cram mode (recent vs stale review windows) - org-drill-entry-due-p: scheduled in past/future, non-drill, virgin - org-drill-entry-overdue-p: factor-based threshold across last-interval and days-overdue - org-drill-current-scope: file → nil, file-no-restriction → file, symbol passthrough
* test: add tests for cloze regex, hypothetical scheduling, and entry strippingCraig Jennings2026-05-051-0/+185
| | | | | | | | | | | | | | 15 ERT tests covering: - org-drill--compute-cloze-regexp: match default and custom delimiters, hint separator, three-capture-group structure for fontification - org-drill--compute-cloze-keywords: font-lock spec shape - org-drill-hypothetical-next-review-date: virgin-card scheduling, quality-monotonic next-interval, DRILL_CARD_WEIGHT damping - org-drill-hypothetical-next-review-dates: 6-element non-decreasing list driving the rating-prompt preview - org-drill-strip-entry-data: scheduling-property cleanup, no-op on virgin entry
* test: add round-trip tests for item-data save/loadCraig Jennings2026-05-051-0/+177
| | | | | | | | | | | | | | | 11 ERT tests covering org-drill-get-item-data and store-item-data. The user-facing contract: rate a card → state persists across sessions. Three branches tested: virgin item (zero-list sentinel), modern DRILL_* properties (read all six fields, partial-set falls back to defaults), and legacy LEARN_DATA backward compat (precedence over modern, graceful fallthrough on malformed data). Round-trip tests document a deliberate type quirk: rounded fields (interval, meanq, ease) come back as floats because org-drill-round-float returns float; counters (repeats, failures, total-repeats) stay int. Numerically lossless and scheduler-safe.
* test: add unit tests for entry-property accessorsCraig Jennings2026-05-051-0/+214
| | | | | | | | | | | 30 ERT tests covering org-drill-entry-last-quality, entry-failure-count, entry-average-quality, entry-last-interval, entry-repeats-since-fail, entry-total-repeats, entry-ease, entry-leech-p, and entry-new-p. Documents each function's missing-property fallback behavior — three distinct shapes: nil-by-default (last-quality, average-quality, ease), hardcoded-zero-by-default (failure-count, last-interval, repeats-since- fail, total-repeats), or computed from other state (new-p, leech-p).
* test: add direct unit tests for SM2/SM5 scheduler helpersCraig Jennings2026-05-051-0/+222
| | | | | | | | | | | | 35 ERT tests covering org-drill-round-float, org-drill-modify-e-factor, org-drill-modify-of, org-drill-set-optimal-factor, org-drill-initial-optimal-factor-sm5, org-drill-get-optimal-factor-sm5, org-drill-inter-repetition-interval-sm5, org-drill-early-interval-factor, org-drill-random-dispersal-factor, and org-drill--safe-read-learn-data. These helpers were exercised transitively by the existing top-level scheduler tests but had no direct unit coverage. Direct tests give faster feedback when a helper breaks and pin each helper's contract.
* fix: include child subtree in entry-empty-p search bound (upstream #13)Craig Jennings2026-05-051-0/+135
| | | | | | | | | | | | kqr (2019-07-22) reported that drill entries whose answer lives inside a child sub-heading were silently skipped. Their example: a question in the heading text and the answer under `** The Answer`. The function returned t (empty) for such entries, so they never got presented during drill sessions. The cause is `(outline-next-heading)` in `org-drill-entry-empty-p`. That primitive lands on the first heading at any level, including children. So the search range was metadata-end up to the child's heading line, which excluded the child's body. Bodies that lived in child sub-headings never got searched. I switched the bound to `(org-end-of-subtree t t)`, which covers the whole subtree of the current heading and degrades gracefully at the last heading in the buffer. The reporter suggested `outline-forward-same-level`, but that primitive errors at the last sibling, which would be its own regression. `org-end-of-subtree` is the canonical Emacs idiom for this kind of bound and handles end-of-buffer correctly. I added `tests/test-org-drill-entry-empty-p.el` with 6 ERT tests across Normal, Boundary (kqr's exact fixture), and edge categories. The two regression tests fail at HEAD before the fix and pass after. One semantic note worth flagging: any subtree content now counts as non-empty, including bare child headings with no body of their own. The bug report is silent on that case and I expect it to be rare in practice. If anyone reports the new behavior as a regression, the fix would be to filter heading lines out of the graphical-character search.
* fix: preserve default-input-method during key reads (upstream #52, #58)Craig Jennings2026-05-051-0/+108
| | | | | | | | | | Two reports from breadncup (issue #52 in 2023, issue #58 in 2024) said that running an org-drill session silently nulled out their `default-input-method`. The reproduction is exact: every rating prompt clears the user's persistent setting. The cause is `(set-input-method nil)` in `org-drill--read-key-sequence`. When `current-input-method` is nil, calling `set-input-method` with nil clears `default-input-method` as a documented side effect. The unwind-protect on the way back has the symmetric problem, since it passes the captured nil. The fix is to use the primitives that are scoped to current state. `deactivate-input-method` and `activate-input-method` don't touch `default-input-method`, and I wrap each call in a guard so the function is a no-op when no input method is active. The same pattern lives in `org-drill-response-get-buffer-create`, which propagates the caller's input method into the response buffer. When the caller has no input method active, the captured value is nil and `(set-input-method nil)` runs in the new buffer, clearing `default-input-method` again. I applied the same guard there. I added `tests/test-org-drill-read-key-sequence.el` with 6 ERT tests across Normal, Boundary (the bug case), and Error categories. The four regression tests fail at HEAD before the fix and pass after.