aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-13 11:03:00 -0600
committerCraig Jennings <c@cjennings.net>2025-11-13 11:03:00 -0600
commit87e74a3a6ccf5b05b760e9f8beec9a78886ab076 (patch)
tree2179ba106bef7b6dc2f3ad72bfe567205213e609
parent2442235f1aa86dcfba1909ddeba0cf37b46922a3 (diff)
downloadorg-drill-87e74a3a6ccf5b05b760e9f8beec9a78886ab076.tar.gz
org-drill-87e74a3a6ccf5b05b760e9f8beec9a78886ab076.zip
refactor: Improve test infrastructure and fix all compiler warnings
This commit modernizes the test infrastructure and eliminates all compilation warnings to prepare for comprehensive test coverage. Test Infrastructure Improvements: - Reorganize tests from test/ to tests/ directory (standard convention) - Modernize Makefile with patterns from chime.el - Add convenient test targets (test-file, test-name) - Support unit and integration test separation - Better help documentation with examples - Auto-detect Cask installation location - Add comprehensive test-strategy.org document - 6-week implementation plan - Critical function prioritization - Integration test scenarios - Coverage goals (80% target) Compiler Warning Fixes (org-drill.el): - Replace obsolete org-mode functions: - org-show-subtree → org-fold-show-subtree - org-show-entry → org-fold-show-entry - org-get-tags-at → org-get-tags - org-remove-latex-fragment-image-overlays → org-clear-latex-preview - org-toggle-latex-fragment → org-latex-preview - org-bracket-link-regexp → org-link-bracket-re - Fix ~31 unescaped single quotes in docstrings (use \=') - Remove unused lexical variables (cb, drawer-name, session, orig-fun) - Remove obsolete outline-view-change-hook binding - Wrap org 8.x compatibility shim in with-no-warnings - Fix long docstring at line 1085 Test Status: - All 3 existing tests passing - Zero compilation warnings (was ~40 warnings) - Ready for Phase 1 implementation (foundation tests)
-rw-r--r--Makefile268
-rw-r--r--org-drill.el145
-rw-r--r--test/Makefile6
-rw-r--r--tests/one-two-three.org (renamed from test/one-two-three.org)0
-rw-r--r--tests/org-drill-test.el (renamed from test/org-drill-test.el)0
-rw-r--r--tests/test-strategy.org636
6 files changed, 940 insertions, 115 deletions
diff --git a/Makefile b/Makefile
index f70f3a2..4ed36a4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,63 +1,259 @@
+# Makefile for org-drill
+#
+# Usage:
+# make help - Show this help message
+# make test - Run all tests (unit + robot)
+# make test-unit - Run ERT unit tests only
+# make test-file FILE=org-drill-test.el - Run specific test file
+# make test-name TEST=load-test - Run tests matching pattern
+# make robot - Run basic robot tests
+# make robot-all - Run all robot tests
+# make docker-test - Run tests in Docker (multiple Emacs versions)
+# make install - Install dependencies via Cask
+# make clean - Remove generated files
+
+# Emacs binary to use (override with: make EMACS=emacs29 test)
EMACS ?= emacs
-CASK ?= cask
+# Check for Cask in PATH or common installation location
+CASK ?= $(shell command -v cask 2>/dev/null || echo "$(HOME)/.cask/bin/cask")
+# Include local overrides if present
-include makefile-local
ifdef EMACS
EMACS_ENV=EMACS=$(EMACS)
endif
-all: robot-and-test
+# Test directories and files
+TEST_DIR = tests
+UNIT_TESTS = $(filter-out $(TEST_DIR)/%-integration-test.el, $(wildcard $(TEST_DIR)/*-test.el))
+INTEGRATION_TESTS = $(wildcard $(TEST_DIR)/*-integration-test.el)
+ALL_TESTS = $(UNIT_TESTS) $(INTEGRATION_TESTS)
-install:
- $(EMACS_ENV) $(CASK) install
+# Emacs batch flags
+EMACS_BATCH = $(EMACS) --batch --no-site-file --no-site-lisp
+
+# Docker configuration
+DOCKER_TAG=26
-test: install just-test
+.PHONY: help test test-all test-unit test-integration test-file test-name install build clean clean-elc
+.PHONY: robot robot-all robot-basic robot-leitner robot-all-card robot-spanish robot-explainer
+.PHONY: docker-test test-cp test-git
-all-test: all-robot-test test
+# Default target
+help:
+ @echo "org-drill Test Targets:"
+ @echo ""
+ @echo "Primary Targets:"
+ @echo " make help - Show this help message"
+ @echo " make test - Run all tests (unit + robot)"
+ @echo " make test-unit - Run unit tests only ($(words $(UNIT_TESTS)) files)"
+ @echo " make test-integration - Run integration tests only ($(words $(INTEGRATION_TESTS)) files)"
+ @echo " make robot - Run basic robot tests"
+ @echo " make robot-all - Run all robot tests"
+ @echo ""
+ @echo "Selective Testing:"
+ @echo " make test-file FILE=<filename> - Run specific test file"
+ @echo " make test-name TEST=<pattern> - Run tests matching pattern"
+ @echo ""
+ @echo "Advanced Targets:"
+ @echo " make install - Install dependencies via Cask"
+ @echo " make build - Build package via Cask"
+ @echo " make docker-test - Run tests in Docker (multiple Emacs versions)"
+ @echo " make clean - Remove generated files"
+ @echo " make clean-elc - Remove compiled .elc files"
+ @echo ""
+ @echo "Examples:"
+ @echo " make test-file FILE=org-drill-test.el"
+ @echo " make test-name TEST=load-test"
+ @echo " make test-name TEST='find-*'"
+ @echo " make EMACS=emacs29 test # Use specific Emacs version"
+ @echo ""
+ @echo "Robot Test Targets:"
+ @echo " make robot-basic - Run basic robot test"
+ @echo " make robot-leitner - Run Leitner algorithm test"
+ @echo " make robot-all-card - Run all-card robot test"
+ @echo " make robot-spanish - Run Spanish vocabulary test"
+ @echo " make robot-explainer - Run explainer robot test"
+
+# Default: run all tests
+all: robot test-unit
+
+# Install dependencies
+install:
+ @if ! command -v $(CASK) >/dev/null 2>&1; then \
+ echo "[✗] Cask not found. Please install Cask first:"; \
+ echo " https://github.com/cask/cask"; \
+ echo " Or: curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go | python"; \
+ exit 1; \
+ fi
+ @echo "[i] Installing dependencies via Cask..."
+ @$(EMACS_ENV) $(CASK) install
+ @echo "[✓] Dependencies installed"
+# Build package
build:
$(EMACS_ENV) $(CASK) build
-robot-and-test: basic-robot-test just-test
+# Run all tests
+test: robot test-unit
-just-test:
- $(EMACS_ENV) $(CASK) emacs --batch -q \
- --directory=. \
- --load assess-discover.el \
- --eval '(assess-discover-run-and-exit-batch t)'
+# Full test suite
+test-all: robot-all test-unit test-integration
-DOCKER_TAG=26
-test-cp:
- docker run -it --rm --name docker-cp -v $(PWD):/usr/src/app -w /usr/src/app --entrypoint=/bin/bash silex/emacs:$(DOCKER_TAG)-dev ./test-by-cp
+# Run unit tests only
+test-unit: install
+ @echo "[i] Running unit tests ($(words $(UNIT_TESTS)) files)..."
+ @failed=0; \
+ for test in $(UNIT_TESTS); do \
+ echo " Testing $$test..."; \
+ $(EMACS_ENV) $(CASK) emacs --batch -q \
+ -l ert \
+ -l assess \
+ -l org-drill.el \
+ -l $$test \
+ -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \
+ done; \
+ if [ $$failed -eq 0 ]; then \
+ echo "[✓] All unit tests passed"; \
+ else \
+ echo "[✗] $$failed unit test file(s) failed"; \
+ exit 1; \
+ fi
-test-git:
- docker run -it --rm --name docker-git -v $(PWD):/usr/src/app -w /usr/src/app --entrypoint=/bin/bash silex/emacs:$(DOCKER_TAG)-dev ./test-from-git
+# Run integration tests only
+test-integration: install
+ @echo "[i] Running integration tests ($(words $(INTEGRATION_TESTS)) files)..."
+ @if [ $(words $(INTEGRATION_TESTS)) -eq 0 ]; then \
+ echo "[i] No integration tests found"; \
+ else \
+ failed=0; \
+ for test in $(INTEGRATION_TESTS); do \
+ echo " Testing $$test..."; \
+ $(EMACS_ENV) $(CASK) emacs --batch -q \
+ -l ert \
+ -l assess \
+ -l org-drill.el \
+ -l $$test \
+ -f ert-run-tests-batch-and-exit || failed=$$((failed + 1)); \
+ done; \
+ if [ $$failed -eq 0 ]; then \
+ echo "[✓] All integration tests passed"; \
+ else \
+ echo "[✗] $$failed integration test file(s) failed"; \
+ exit 1; \
+ fi; \
+ fi
-docker-test:
- $(MAKE) test-git DOCKER_TAG=27.2
- $(MAKE) test-cp DOCKER_TAG=27.2
- $(MAKE) test-git DOCKER_TAG=26.3
- $(MAKE) test-cp DOCKER_TAG=26.3
+# Run specific test file
+# Usage: make test-file FILE=org-drill-test.el
+test-file: install
+ifndef FILE
+ @echo "[✗] Error: FILE parameter required"
+ @echo "Usage: make test-file FILE=org-drill-test.el"
+ @exit 1
+endif
+ @echo "[i] Running tests in $(FILE)..."
+ @$(EMACS_ENV) $(CASK) emacs --batch -q \
+ -l ert \
+ -l assess \
+ -l org-drill.el \
+ -l $(TEST_DIR)/$(FILE) \
+ -f ert-run-tests-batch-and-exit
+ @echo "[✓] Tests in $(FILE) complete"
-clean-elc:
- $(CASK) clean-elc
+# Run specific test by name/pattern
+# Usage: make test-name TEST=load-test
+# make test-name TEST="find-*"
+test-name: install
+ifndef TEST
+ @echo "[✗] Error: TEST parameter required"
+ @echo "Usage: make test-name TEST=load-test"
+ @echo " make test-name TEST='find-*'"
+ @exit 1
+endif
+ @echo "[i] Running tests matching pattern: $(TEST)..."
+ @$(EMACS_ENV) $(CASK) emacs --batch -q \
+ -l ert \
+ -l assess \
+ -l org-drill.el \
+ $(foreach test,$(ALL_TESTS),-l $(test)) \
+ --eval '(ert-run-tests-batch-and-exit "$(TEST)")'
+ @echo "[✓] Tests matching '$(TEST)' complete"
+
+#
+# Robot Tests (Automated UI Tests)
+#
+
+# Basic robot test (default for quick testing)
+robot: robot-basic
+
+# All robot tests
+robot-all: robot-basic robot-leitner robot-all-card robot-spanish
-all-robot-test: basic-robot-test leitner-robot-test all-card-robot-test spanish-robot-test
+# Individual robot tests
+robot-basic: clean-elc
+ @echo "[i] Running basic robot test..."
+ @$(EMACS_ENV) ./robot/basic-run.sh $(SMALL)
+ @echo "[✓] Basic robot test complete"
-basic-robot-test: clean-elc
- $(EMACS_ENV) ./robot/basic-run.sh $(SMALL)
+robot-leitner: clean-elc
+ @echo "[i] Running Leitner robot test..."
+ @$(EMACS_ENV) ./robot/leitner-run.sh $(SMALL)
+ @echo "[✓] Leitner robot test complete"
-leitner-robot-test: clean-elc
- $(EMACS_ENV) ./robot/leitner-run.sh $(SMALL)
+robot-all-card: clean-elc
+ @echo "[i] Running all-card robot test..."
+ @$(EMACS_ENV) ./robot/all-card-run.sh $(SMALL)
+ @echo "[✓] All-card robot test complete"
-all-card-robot-test: clean-elc
- $(EMACS_ENV) ./robot/all-card-run.sh $(SMALL)
+robot-spanish: clean-elc
+ @echo "[i] Running Spanish robot test..."
+ @$(EMACS_ENV) ./robot/spanish-run.sh $(SMALL)
+ @echo "[✓] Spanish robot test complete"
-spanish-robot-test: clean-elc
- $(EMACS_ENV) ./robot/spanish-run.sh $(SMALL)
+robot-explainer: clean-elc
+ @echo "[i] Running explainer robot test..."
+ @$(EMACS_ENV) ./robot/explainer-run.sh $(SMALL)
+ @echo "[✓] Explainer robot test complete"
+
+#
+# Docker Testing (Multiple Emacs Versions)
+#
+
+test-cp:
+ docker run -it --rm --name docker-cp \
+ -v $(PWD):/usr/src/app \
+ -w /usr/src/app \
+ --entrypoint=/bin/bash \
+ silex/emacs:$(DOCKER_TAG)-dev ./test-by-cp
-explainer-robot-test: clean-elc
- $(EMACS_ENV) ./robot/explainer-run.sh $(SMALL)
+test-git:
+ docker run -it --rm --name docker-git \
+ -v $(PWD):/usr/src/app \
+ -w /usr/src/app \
+ --entrypoint=/bin/bash \
+ silex/emacs:$(DOCKER_TAG)-dev ./test-from-git
+
+docker-test:
+ @echo "[i] Running Docker tests across multiple Emacs versions..."
+ @$(MAKE) test-git DOCKER_TAG=27.2
+ @$(MAKE) test-cp DOCKER_TAG=27.2
+ @$(MAKE) test-git DOCKER_TAG=26.3
+ @$(MAKE) test-cp DOCKER_TAG=26.3
+ @echo "[✓] Docker tests complete"
+
+#
+# Cleanup
+#
+
+clean-elc:
+ @echo "[i] Cleaning compiled .elc files..."
+ @find . -name "*.elc" -delete
+ @echo "[✓] Clean .elc complete"
-.PHONY: test
+clean:
+ @echo "[i] Cleaning generated files..."
+ @find . -name "*.elc" -delete
+ @find $(TEST_DIR) -name "*-test-*" -type f -delete
+ @echo "[✓] Clean complete"
diff --git a/org-drill.el b/org-drill.el
index cbbf7a2..94a8264 100644
--- a/org-drill.el
+++ b/org-drill.el
@@ -114,7 +114,7 @@ really sensible."
10
"The maximum percentage of items that can be forgotten before a warning.
-What percentage of items do you consider it is 'acceptable' to
+What percentage of items do you consider it is \\='acceptable\\=' to
forget each drill session? The default is 10%. A warning message
is displayed at the end of the session if the percentage forgotten
climbs above this number."
@@ -126,13 +126,13 @@ climbs above this number."
"Threshold before a item is defined as a leech.
If an item is forgotten more than this many times, it is tagged
-as a 'leech' item."
+as a \\='leech\\=' item."
:group 'org-drill
:type '(choice integer (const nil)))
(defcustom org-drill-leech-method
'skip
- "How should 'leech items' be handled during drill sessions?
+ "How should \\='leech items\\=' be handled during drill sessions?
Possible values:
- nil :: Leech items are treated the same as normal items.
- skip :: Leech items are not included in drill sessions.
@@ -168,7 +168,7 @@ Possible values:
"If non-nil, conceal headings during a drill session.
You may want to enable this behaviour if item headings or tags
-contain information that could 'give away' the answer."
+contain information that could \\='give away\\=' the answer."
:group 'org-drill
:type 'boolean)
@@ -311,7 +311,7 @@ that argument is a function of no arguments, which when called,
prompts the user to rate their recall and performs rescheduling
of the drill item. ANSWER-FN is called with the point on the
active item's heading, just prior to displaying the item's
-'answer'. It can therefore be used to modify the appearance of
+\\='answer\\='. It can therefore be used to modify the appearance of
the answer. ANSWER-FN must call its argument before returning.
When supplied, DRILL-EMPTY-P is a boolean value, default nil.
@@ -394,7 +394,7 @@ Available choices are:
- SM5 :: the SM5 algorithm, used in SuperMemo 5.0
- Simple8 :: a modified version of the SM8 algorithm. SM8 is used in
SuperMemo 98. The version implemented here is simplified in that while it
- 'learns' the difficulty of each item using quality grades and number of
+ \\='learns\\=' the difficulty of each item using quality grades and number of
failures, it does not modify the matrix of values that
governs how fast the inter-repetition intervals increase. A method for
adjusting intervals when items are reviewed early or late has been taken
@@ -447,8 +447,8 @@ is used."
(defcustom org-drill-cloze-text-weight
4
- "For card types 'hide1_firstmore', 'show1_lastmore' and 'show1_firstless',
-this number determines how often the 'less favoured' situation
+ "For card types \\='hide1_firstmore\\=', \\='show1_lastmore\\=' and \\='show1_firstless\\=',
+this number determines how often the \\='less favoured\\=' situation
should arise. It will occur 1 in every N trials, where N is the
value of the variable.
@@ -487,7 +487,7 @@ they were reviewed at least this many hours ago."
(defcustom org-drill-days-before-old
10
"When an item's inter-repetition interval rises above this value in days,
-it is no longer considered a 'young' (recently learned) item."
+it is no longer considered a \\='young\\=' (recently learned) item."
:group 'org-drill
:type 'integer)
@@ -546,7 +546,7 @@ Typical values are 16-24 for comfortable reading."
(integer :tag "Font size in points")))
(defcustom org-drill-use-variable-pitch
- t
+ nil
"If non-nil, use variable-pitch font during drill sessions.
This can make text more readable for long-form content."
:group 'org-drill
@@ -658,9 +658,9 @@ This variable is not functionally important, but is used for
"DRILL_EASE" "DRILL_LAST_QUALITY" "DRILL_LAST_REVIEWED"))
(defvar org-drill--lapse-very-overdue-entries-p nil
- "If non-nil, entries more than 90 days overdue are regarded as 'lapsed'.
+ "If non-nil, entries more than 90 days overdue are regarded as \\='lapsed\\='.
This means that when the item is eventually re-tested it will be
-treated as 'failed' (quality 2) for rescheduling purposes,
+treated as \\='failed\\=' (quality 2) for rescheduling purposes,
regardless of whether the test was successful.")
@@ -701,20 +701,21 @@ regardless of whether the test was successful.")
(when (version< org-version "9.2")
(advice-add 'org-get-tags :around #'org-drill-get-tags-advice))
-(defun org-drill-get-tags-advice (orig-fun &rest args)
+(defun org-drill-get-tags-advice (_orig-fun &rest args)
;; the two arg call obsoletes get-local-tags
(if (= 2 (length args))
;; and we don't want any byte compile errors
(if (fboundp 'org-get-local-tags) (org-get-local-tags))
;; the non-arg version doesn't return inherited tags, but
- ;; get-tags-at does.
- (org-get-tags-at)))
+ ;; org-get-tags does.
+ (org-get-tags)))
-(when (= 8 (car (version-to-list org-version)))
- ;; Shut up package-lint
- (defalias 'org-drill-defun 'defun)
- (org-drill-defun org-toggle-latex-fragment (&rest args)
- (apply 'org-preview-latex-fragment args)))
+(with-no-warnings
+ (when (= 8 (car (version-to-list org-version)))
+ ;; Shut up package-lint - compatibility shim for org 8.x (package requires org >= 9.3)
+ (defalias 'org-drill-defun 'defun)
+ (org-drill-defun org-latex-preview (&rest args)
+ (apply 'org-preview-latex-fragment args))))
;;;; Utilities ================================================================
(defmacro org-drill-pop-random (place)
@@ -835,7 +836,7 @@ in hours rather than days."
(* 60 60))))))
(defun org-drill-entry-p (&optional marker)
- "Is MARKER, or the point, in a 'drill item'? This will return nil if
+ "Is MARKER, or the point, in a \\='drill item\\='? This will return nil if
the point is inside a subheading of a drill item -- to handle that
situation use `org-part-of-drill-entry-p'."
(save-excursion
@@ -849,7 +850,7 @@ situation use `org-part-of-drill-entry-p'."
(goto-char marker))
(defun org-drill-part-of-drill-entry-p ()
- "Is the current entry either the main heading of a 'drill item',
+ "Is the current entry either the main heading of a \\='drill item\\=',
or a subheading within a drill item?"
(or (org-drill-entry-p)
;; Does this heading INHERIT the drill tag
@@ -867,7 +868,7 @@ drill entry."
(error "Cannot find a parent heading that is marked as a drill entry"))))
(defun org-drill-entry-leech-p ()
- "Is the current entry a 'leech item'?"
+ "Is the current entry a \\='leech item\\='?"
(and (org-drill-entry-p)
(member "leech" (org-get-tags nil t))))
@@ -920,7 +921,7 @@ drill entry."
(defun org-drill-entry-overdue-p (session &optional days-overdue last-interval)
"Returns true if entry that is scheduled DAYS-OVERDUE dasy in the past,
and whose last inter-repetition interval was LAST-INTERVAL, should be
-considered 'overdue'. If the arguments are not given they are extracted
+considered \\='overdue\\='. If the arguments are not given they are extracted
from the entry at point."
(unless days-overdue
(setq days-overdue (org-drill-entry-days-overdue session)))
@@ -1087,10 +1088,11 @@ in the matrix."
"Arguments:
- LAST-INTERVAL -- the number of days since the item was last reviewed.
- REPEATS -- the number of times the item has been successfully reviewed
-- EF -- the 'easiness factor'
+- EF -- the \\='easiness factor\\='
- QUALITY -- 0 to 5
-Returns a list: (INTERVAL REPEATS EF FAILURES MEAN TOTAL-REPEATS OFMATRIX), where:
+Returns a list:
+ (INTERVAL REPEATS EF FAILURES MEAN TOTAL-REPEATS OFMATRIX), where:
- INTERVAL is the number of days until the item should next be reviewed
- REPEATS is incremented by 1.
- EF is modified based on the recall quality for the item.
@@ -1270,7 +1272,7 @@ to a mean item quality of QUALITY."
"Arguments:
- LAST-INTERVAL -- the number of days since the item was last reviewed.
- REPEATS -- the number of times the item has been successfully reviewed
-- EASE -- the 'easiness factor'
+- EASE -- the \\='easiness factor\\='
- QUALITY -- 0 to 5
- DELTA-DAYS -- how many days overdue was the item when it was reviewed.
0 = reviewed on the scheduled day. +N = N days overdue.
@@ -1541,7 +1543,7 @@ of QUALITY."
;; (let ((drill-entry-level (org-current-level))
;; (drill-sections nil)
;; (drill-heading nil))
-;; (org-show-subtree)
+;; (org-fold-show-subtree)
;; (save-excursion
;; (org-map-entries
;; (lambda ()
@@ -1564,7 +1566,7 @@ Returns a list containing the position of each immediate subheading of
the current topic."
(let ((drill-entry-level (org-current-level))
(drill-sections nil))
- (org-show-subtree)
+ (org-fold-show-subtree)
(save-excursion
(org-map-entries
(lambda ()
@@ -1768,8 +1770,7 @@ Consider reformulating the item to make it easier to remember.\n"
(defun org-drill-response-get-buffer-create ()
"Create a response buffer."
(let ((local-current-input-method
- current-input-method)
- (cb (current-buffer)))
+ current-input-method))
(with-current-buffer
(get-buffer-create "*Org-Drill*")
(erase-buffer)
@@ -1852,7 +1853,7 @@ If non-nil, returns (BEG . END) where beginning and end of the match are."
(org-in-regexp regexp nlines)))
(defun org-drill-hide-region (beg end &optional text)
- "Hide the buffer region between BEG and END with an 'invisible text'
+ "Hide the buffer region between BEG and END with an \\='invisible text\\='
visual overlay, or with the string TEXT if it is supplied."
(let ((ovl (make-overlay beg end)))
(overlay-put ovl 'category
@@ -1886,7 +1887,6 @@ This is more reliable than `org-cycle-hide-drawers' for drill display."
(let ((end (save-excursion (org-end-of-subtree t t))))
(while (re-search-forward org-drawer-regexp end t)
(let* ((drawer-start (match-beginning 0))
- (drawer-name (match-string 1))
(drawer-end (save-excursion
(re-search-forward "^[ \t]*:END:[ \t]*$" end t)
(point))))
@@ -1949,13 +1949,13 @@ Saves current settings and applies drill-specific display preferences."
;; - the contents of SRC blocks
(unless (save-match-data
(or (org-drill-pos-in-regexp (match-beginning 0)
- org-bracket-link-regexp 1)
+ org-link-bracket-re 1)
(org-in-src-block-p)
(org-inside-LaTeX-fragment-p)))
(org-drill-hide-matched-cloze-text)))))
(defun org-drill-hide-matched-cloze-text ()
- "Hide the current match with a 'cloze' visual overlay."
+ "Hide the current match with a \\='cloze\\=' visual overlay."
(let ((ovl (make-overlay (match-beginning 0) (match-end 0)))
(hint-sep-pos (string-match-p (regexp-quote org-drill-hint-separator)
(match-string 0))))
@@ -1992,7 +1992,7 @@ Saves current settings and applies drill-specific display preferences."
(while (re-search-forward org-drill-cloze-regexp nil t)
(unless (or (save-match-data
(org-drill-pos-in-regexp (match-beginning 0)
- org-bracket-link-regexp 1))
+ org-link-bracket-re 1))
(null (match-beginning 2))) ; hint subexpression matched
(org-drill-hide-region (match-beginning 2) (match-end 2))))))
@@ -2151,7 +2151,7 @@ RESCHEDULE-FN is the function to reschedule."
(ignore-errors
(org-display-inline-images t))
(org-drill-hide-drawers)
- (org-remove-latex-fragment-image-overlays)
+ (org-clear-latex-preview)
(save-excursion
(org-mark-subtree)
(let ((beg (region-beginning))
@@ -2176,8 +2176,8 @@ RESCHEDULE-FN is the function to reschedule."
(defun org-drill--show-latex-fragments ()
"Show latex fragment."
- (org-remove-latex-fragment-image-overlays)
- (org-toggle-latex-fragment '(16)))
+ (org-clear-latex-preview)
+ (org-latex-preview '(16)))
(defun org-drill-present-two-sided-card (session)
(org-drill-with-hidden-comments
@@ -2188,7 +2188,7 @@ RESCHEDULE-FN is the function to reschedule."
(save-excursion
(goto-char (nth (cl-random (min 2 (length drill-sections)))
drill-sections))
- (org-show-subtree)))
+ (org-fold-show-subtree)))
(org-drill--show-latex-fragments)
(ignore-errors
(org-display-inline-images t))
@@ -2204,7 +2204,7 @@ RESCHEDULE-FN is the function to reschedule."
(when drill-sections
(save-excursion
(goto-char (nth (cl-random (length drill-sections)) drill-sections))
- (org-show-subtree)))
+ (org-fold-show-subtree)))
(org-drill--show-latex-fragments)
(ignore-errors
(org-display-inline-images t))
@@ -2246,7 +2246,7 @@ items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
(while (re-search-forward org-drill-cloze-regexp item-end t)
(let ((in-regexp? (save-match-data
(org-drill-pos-in-regexp (match-beginning 0)
- org-bracket-link-regexp 1))))
+ org-link-bracket-re 1))))
(unless (or in-regexp?
(org-inside-LaTeX-fragment-p))
(cl-incf match-count)))))
@@ -2277,7 +2277,7 @@ items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
(while (re-search-forward org-drill-cloze-regexp item-end t)
(unless (save-match-data
(or (org-drill-pos-in-regexp (match-beginning 0)
- org-bracket-link-regexp 1)
+ org-link-bracket-re 1)
(org-inside-LaTeX-fragment-p)))
(cl-incf cnt)
(if (memq cnt match-nums)
@@ -2286,7 +2286,7 @@ items if FORCE-SHOW-FIRST or FORCE-SHOW-LAST is non-nil)."
;; do (re-search-forward org-drill-cloze-regexp
;; item-end t pos-to-hide)
;; while (org-drill-pos-in-regexp (match-beginning 0)
- ;; org-bracket-link-regexp 1))
+ ;; org-link-bracket-re 1))
;; (org-drill-hide-matched-cloze-text)))))
(org-drill--show-latex-fragments)
(ignore-errors
@@ -2316,7 +2316,7 @@ the second to last, etc."
(while (re-search-forward org-drill-cloze-regexp item-end t)
(let ((in-regexp? (save-match-data
(org-drill-pos-in-regexp (match-beginning 0)
- org-bracket-link-regexp 1))))
+ org-link-bracket-re 1))))
(unless (or in-regexp?
(org-inside-LaTeX-fragment-p))
(cl-incf match-count)))))
@@ -2336,7 +2336,7 @@ the second to last, etc."
;; org link, or if it occurs inside a LaTeX math
;; fragment
(or (org-drill-pos-in-regexp (match-beginning 0)
- org-bracket-link-regexp 1)
+ org-link-bracket-re 1)
(org-inside-LaTeX-fragment-p)))
(cl-incf cnt)
(if (= cnt to-hide)
@@ -2372,7 +2372,7 @@ chosen at random."
cloze deletion. Uncommonly, hide one of the other pieces of text,
chosen at random.
-The definitions of 'commonly' and 'uncommonly' are determined by
+The definitions of \\='commonly\\=' and \\='uncommonly\\=' are determined by
the value of `org-drill-cloze-text-weight'."
;; The 'firstmore' and 'lastmore' functions used to randomly choose whether
;; to hide the 'favoured' piece of text. However even when the chance of
@@ -2398,11 +2398,11 @@ the value of `org-drill-cloze-text-weight'."
(defun org-drill-present-multicloze-show1-lastmore (session)
"Commonly, hides all pieces except the last. Uncommonly, shows
-any random piece. The effect is similar to 'show1cloze' except
+any random piece. The effect is similar to \\='show1cloze\\=' except
that the last item is much less likely to be the item that is
visible.
-The definitions of 'commonly' and 'uncommonly' are determined by
+The definitions of \\='commonly\\=' and \\='uncommonly\\=' are determined by
the value of `org-drill-cloze-text-weight'."
(cond
((null org-drill-cloze-text-weight)
@@ -2423,11 +2423,11 @@ the value of `org-drill-cloze-text-weight'."
(defun org-drill-present-multicloze-show1-firstless (session)
"Commonly, hides all pieces except one, where the shown piece
is guaranteed NOT to be the first piece. Uncommonly, shows any
-random piece. The effect is similar to 'show1cloze' except that
+random piece. The effect is similar to \\='show1cloze\\=' except that
the first item is much less likely to be the item that is
visible.
-The definitions of 'commonly' and 'uncommonly' are determined by
+The definitions of \\='commonly\\=' and \\='uncommonly\\=' are determined by
the value of `org-drill-cloze-text-weight'."
(cond
((null org-drill-cloze-text-weight)
@@ -2473,7 +2473,7 @@ If ANSWER is supplied, set the session slot `drill-answer' to its value."
(defun org-drill-entry (session)
"Present the current topic for interactive review, as in `org-drill'.
Review will occur regardless of whether the topic is due for review or whether
-it meets the definition of a 'review topic' used by `org-drill'.
+it meets the definition of a \\='review topic\\=' used by `org-drill'.
Returns a quality rating from 0 to 5, or nil if the user quit, or the symbol
EDIT if the user chose to exit the drill and edit the current item. Choosing
@@ -2496,15 +2496,14 @@ See `org-drill' for more details."
;; (org-back-to-heading))
(let ((card-type (org-entry-get (point) "DRILL_CARD_TYPE" t))
(answer-fn 'org-drill-present-default-answer)
- (cont nil)
- ;; fontification functions in `outline-view-change-hook' can cause big
- ;; slowdowns, so we temporarily bind this variable to nil here.
- (outline-view-change-hook nil))
+ (cont nil))
+ ;; fontification functions in `outline-view-change-hook' (obsolete in Emacs 29.1)
+ ;; can cause big slowdowns, so we no longer bind it since we require modern Emacs
(setf (oref session drill-answer) nil)
(org-save-outline-visibility t
(save-restriction
(org-narrow-to-subtree)
- (org-show-subtree)
+ (org-fold-show-subtree)
(org-drill-hide-drawers)
(let ((presentation-fn
@@ -2637,7 +2636,7 @@ maximum number of items."
(defun org-drill-entries (session &optional resuming-p)
"Returns nil, t, or a list of markers representing entries that were
-'failed' and need to be presented again before the session ends.
+\\='failed\\=' and need to be presented again before the session ends.
RESUMING-P is true if we are resuming a suspended drill session."
(cl-block org-drill-entries
@@ -2667,7 +2666,7 @@ RESUMING-P is true if we are resuming a suspended drill session."
(sit-for 0.3)
nil)
(t
- (org-show-entry)
+ (org-fold-show-entry)
(let ((result (org-drill-entry session)))
(cond
((null result)
@@ -2784,7 +2783,7 @@ order to make items appear more frequently over time."
(defun org-drill-free-markers (session markers)
"MARKERS is a list of markers, all of which will be freed (set to
-point nowhere). Alternatively, MARKERS can be 't', in which case
+point nowhere). Alternatively, MARKERS can be \\='t\\=', in which case
all the markers used by Org-Drill will be freed."
(dolist (m (if (eql t markers)
(append (oref session done-entries)
@@ -2955,9 +2954,9 @@ STATUS is one of the following values:
;;;###autoload
(defun org-drill (&optional scope drill-match resume-p cram)
- "Begin an interactive 'drill session'. The user is asked to
+ "Begin an interactive \\='drill session\\='. The user is asked to
review a series of topics (headers). Each topic is initially
-presented as a 'question', often with part of the topic content
+presented as a \\='question\\=', often with part of the topic content
hidden. The user attempts to recall the hidden information or
answer the question, then presses a key to reveal the answer. The
user then rates his or her recall or performance on that
@@ -3067,7 +3066,7 @@ work correctly with older versions of org mode. Your org mode version (%s) appea
(when (markerp (oref session end-pos))
(org-drill-goto-entry (oref session end-pos))
(org-reveal)
- (org-show-entry))
+ (org-fold-show-entry))
(let ((keystr (org-drill-command-keybinding-to-string 'org-drill-resume)))
(message
"You can continue the drill session with the command `org-drill-resume'.%s"
@@ -3086,7 +3085,7 @@ work correctly with older versions of org mode. Your org mode version (%s) appea
;;;###autoload
(defun org-drill-cram (&optional scope drill-match)
- "Run an interactive drill session in 'cram mode'. In cram mode,
+ "Run an interactive drill session in \\='cram mode\\='. In cram mode,
all drill items are considered to be due for review, unless they
have been reviewed within the last `org-drill-cram-hours'
hours."
@@ -3094,7 +3093,7 @@ hours."
(org-drill scope drill-match nil t))
(defun org-drill-cram-tree ()
- "Run an interactive drill session in 'cram mode' using subtree at point.
+ "Run an interactive drill session in \\='cram mode\\=' using subtree at point.
See also, `org-drill-cram' and `org-drill-tree'."
(interactive)
@@ -3156,7 +3155,7 @@ need reviewing. Start a new drill session? "
(defun org-drill-relearn-item ()
"Make the current item due for revision, and set its last interval to 0.
Makes the item behave as if it has been failed, without actually recording a
-failure. This command can be used to 'reset' repetitions for an item."
+failure. This command can be used to \\='reset\\=' repetitions for an item."
(interactive)
(org-drill-smart-reschedule 4 0))
@@ -3224,7 +3223,7 @@ values as `org-drill-scope'."
(defun org-drill-copy-entry-to-other-buffer (dest &optional path)
"Copy the subtree at point to the buffer DEST. The copy will receive
-the tag 'imported'."
+the tag \\='imported\\='."
(cl-block org-drill-copy-entry-to-other-buffer
(save-excursion
(let ((m nil))
@@ -3421,7 +3420,7 @@ the name of the tense.")
(list infinitive inf-hint translation tense mood)))
(defun org-drill-present-verb-conjugation (session)
- "Present a drill entry whose card type is 'conjugate'."
+ "Present a drill entry whose card type is \\='conjugate\\='."
(cl-flet ((tense-and-mood-to-string
(tense mood)
(cond
@@ -3448,7 +3447,7 @@ and conjugate for the %s.\n\n"
(tense-and-mood-to-string tense mood))))))))
(defun org-drill-show-answer-verb-conjugation (session reschedule-fn)
- "Show the answer for a drill item whose card type is 'conjugate'.
+ "Show the answer for a drill item whose card type is \\='conjugate\\='.
RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
returns its return value."
(cl-destructuring-bind (infinitive _inf-hint translation tense mood)
@@ -3513,7 +3512,7 @@ returns its return value."
(list noun noun-root noun-gender noun-hint translation)))
(defun org-drill-present-noun-declension (session)
- "Present a drill entry whose card type is 'decline_noun'."
+ "Present a drill entry whose card type is \\='decline_noun\\='."
(cl-destructuring-bind (noun _noun-root noun-gender noun-hint translation)
(org-drill-get-noun-info)
(let* ((props (org-entry-properties (point)))
@@ -3550,7 +3549,7 @@ and list its declensions%s.\n\n"
""))))))))
(defun org-drill-show-answer-noun-declension (session reschedule-fn)
- "Show the answer for a drill item whose card type is 'decline_noun'.
+ "Show the answer for a drill item whose card type is \\='decline_noun\\='.
RESCHEDULE-FN must be a function that calls `org-drill-reschedule' and
returns its return value."
(cl-destructuring-bind (noun _noun-root noun-gender _noun-hint translation)
@@ -3744,7 +3743,7 @@ Returns a list of strings."
(`(edit ,loc)
(org-drill-goto-entry loc)
(org-reveal)
- (org-show-entry))
+ (org-fold-show-entry))
(`,_
(message "Finished Leitner Learning: %s complete today, %s in process, %s to start"
org-drill-leitner-completed
@@ -3938,7 +3937,7 @@ shuffling is done in place."
#'org-drill-test-display-rescheduler))
(org-toggle-tag "zysygy")))
-(defun org-drill-test-display-rescheduler (session)
+(defun org-drill-test-display-rescheduler (_session)
(run-hooks 'org-drill-display-answer-hook)
;; Normally, the rescheduler waits for input at this point
(read-key-sequence "Press anything to continue"))
diff --git a/test/Makefile b/test/Makefile
deleted file mode 100644
index 8406646..0000000
--- a/test/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-## what ever we called, don't do it here
-default:
- $(MAKE) -C ..
-
-$(MAKECMDGOALS):
- $(MAKE) -C .. $(MAKECMDGOALS)
diff --git a/test/one-two-three.org b/tests/one-two-three.org
index c1120b6..c1120b6 100644
--- a/test/one-two-three.org
+++ b/tests/one-two-three.org
diff --git a/test/org-drill-test.el b/tests/org-drill-test.el
index 4765a99..4765a99 100644
--- a/test/org-drill-test.el
+++ b/tests/org-drill-test.el
diff --git a/tests/test-strategy.org b/tests/test-strategy.org
new file mode 100644
index 0000000..0fb2a20
--- /dev/null
+++ b/tests/test-strategy.org
@@ -0,0 +1,636 @@
+#+TITLE: org-drill Test Strategy
+#+AUTHOR: Test Implementation Plan
+#+DATE: 2025-11-13
+
+* Overview
+
+This document outlines the testing strategy for org-drill, an Emacs package implementing flashcard and spaced repetition functionality. The strategy follows best practices from quality-engineer.org, emphasizing:
+
+- Test isolation and independence
+- Clear naming conventions
+- Comprehensive coverage (normal, boundary, error cases)
+- Balance between unit and integration tests
+- Maintainable, readable test code
+
+* Current Status
+
+** Test Infrastructure
+- [X] Makefile with test targets configured
+- [X] Cask dependency management working
+- [X] Tests directory structure established (tests/)
+- [X] Existing test file moved to tests/ directory
+- [X] All compilation warnings fixed (0 warnings)
+
+** Existing Tests
+- tests/org-drill-test.el (3 tests, all passing)
+ - test-org-drill-entry-p functionality
+ - test-org-drill-map-entries with tags
+
+** Test Coverage Status
+- Unit tests: ~3 tests covering basic entry detection
+- Integration tests: 0 tests
+- Coverage: Minimal (~1% of codebase)
+
+** Next Steps
+1. [ ] Create test files for critical path functions (see Implementation Plan)
+2. [ ] Write integration tests for drill session workflow
+3. [ ] Add comprehensive card type tests
+4. [ ] Implement spaced repetition algorithm tests
+5. [ ] Add boundary and error case coverage
+
+* Architecture Overview
+
+** Core Components
+
+org-drill has several interconnected systems:
+
+*** Entry Management
+- Entry identification (drill tags, inheritance)
+- Entry filtering (due dates, overdue, new vs mature)
+- Entry state tracking (last reviewed, repetitions, etc.)
+
+*** Scheduling Algorithms
+- SM2 (SuperMemo 2)
+- SM5 (SuperMemo 5)
+- Simple8 (modified SM8)
+- Interval calculation based on quality ratings
+
+*** Session Management
+- Session state (entries pending, done, failed)
+- Progress tracking (counts, percentages)
+- Scope management (file, tree, directory, agenda)
+- Cram mode vs normal mode
+
+*** Card Type System
+- Simple cards (question/answer)
+- Two-sided and multi-sided cards
+- Cloze deletion variants (hide1, show1, hide/show with weights)
+- Language learning cards (conjugation, declension)
+- Spanish verb conjugation
+
+*** User Interface
+- Card presentation (hiding/showing text)
+- Response collection (quality ratings 0-5)
+- Answer display
+- Progress reporting
+
+* Test Categories
+
+** Unit Tests
+Test individual functions in isolation with no external dependencies.
+
+*** Naming Convention
+Pattern: =test-org-drill-<function>-<category>-<scenario>-<expected>.el=
+
+Examples:
+- =test-org-drill-entry-p-normal-valid-tag-returns-true.el=
+- =test-org-drill-entry-p-boundary-inherited-tag-returns-true.el=
+- =test-org-drill-entry-overdue-p-error-nil-interval-returns-nil.el=
+
+*** Test Structure
+Each test file should contain:
+- Setup/teardown using testutil functions
+- Normal cases (expected usage)
+- Boundary cases (edge values, empty/nil, single elements)
+- Error cases (invalid inputs, missing data)
+
+** Integration Tests
+Test multiple components working together in realistic workflows.
+
+*** Naming Convention
+Pattern: =test-integration-<area>-<scenario>-<outcome>.el=
+
+Examples:
+- =test-integration-drill-session-complete-workflow-reschedules-entries.el=
+- =test-integration-spaced-repetition-quality-ratings-affect-intervals.el=
+- =test-integration-card-types-cloze-hides-and-reveals-text.el=
+
+*** Integration Test Characteristics
+- Test workflows spanning multiple functions
+- Use real org-mode buffers and data structures
+- May involve file I/O, property manipulation, state changes
+- More setup required, slower than unit tests
+- Higher value for catching real-world bugs
+
+* Critical Functions by Priority
+
+Functions prioritized by criticality to org-drill operation:
+
+** Priority 1: Core Drill Loop (Cannot function without these)
+
+*** org-drill-entry-p
+*Criticality:* CRITICAL - Entry point for identifying drill items
+- Tests if a heading is a drill entry
+- Used by all drill operations
+- *Test file:* =test-org-drill-entry-p.el=
+
+*** org-drill-entries
+*Criticality:* CRITICAL - Main drill session loop
+- Orchestrates the entire drill session
+- Manages entry queue and state transitions
+- *Test file:* =test-org-drill-entries.el=
+- *Integration test:* =test-integration-drill-session-complete-workflow.el=
+
+*** org-drill-entry
+*Criticality:* CRITICAL - Presents individual drill items
+- Shows question, collects response, handles answer
+- Core user interaction point
+- *Test file:* =test-org-drill-entry.el=
+
+** Priority 2: Scheduling & Intervals (Core algorithm correctness)
+
+*** org-drill-determine-next-interval-sm2
+*Criticality:* HIGH - Primary scheduling algorithm
+- Calculates next review interval based on SM2
+- Quality ratings → interval calculations
+- *Test file:* =test-org-drill-determine-next-interval-sm2.el=
+- Must cover all quality values (0-5), boundary intervals
+
+*** org-drill-determine-next-interval-sm5
+*Criticality:* HIGH - Advanced scheduling option
+- More sophisticated than SM2
+- Uses optimal factor matrix
+- *Test file:* =test-org-drill-determine-next-interval-sm5.el=
+
+*** org-drill-reschedule
+*Criticality:* HIGH - Applies scheduling decisions
+- Updates entry properties with new intervals
+- Persists scheduling state
+- *Test file:* =test-org-drill-reschedule.el=
+
+*** org-drill-entry-days-overdue
+*Criticality:* HIGH - Determines entry priority
+- Calculates overdueness for scheduling
+- Affects entry selection order
+- *Test file:* =test-org-drill-entry-days-overdue.el=
+
+** Priority 3: Entry Selection & Filtering (Correct entry set)
+
+*** org-drill-entry-overdue-p
+*Criticality:* MEDIUM - Filters entries for review
+- Determines if entry is due for review
+- *Test file:* =test-org-drill-entry-overdue-p.el=
+
+*** org-drill-entry-due-p
+*Criticality:* MEDIUM - Core filtering logic
+- Checks if entry meets review criteria
+- Different behavior for cram vs normal mode
+- *Test file:* =test-org-drill-entry-due-p.el=
+
+*** org-drill-entry-leech-p
+*Criticality:* MEDIUM - Special case handling
+- Identifies problematic items
+- Affects leech handling behavior
+- *Test file:* =test-org-drill-entry-leech-p.el=
+
+*** org-drill-map-entries
+*Criticality:* MEDIUM - Entry collection
+- Finds and filters drill entries in scope
+- Handles file/tree/agenda scopes
+- *Test file:* =test-org-drill-map-entries.el=
+- *Integration test:* =test-integration-entry-collection-scope-filters.el=
+
+** Priority 4: Card Presentation (User experience)
+
+*** Card Type Tests
+Card type functions are HYBRID tests (both unit and integration aspects):
+- *Unit aspect:* Individual presentation logic (hiding text, formatting)
+- *Integration aspect:* Interaction with answer handling and user response
+
+*Naming convention for card types:*
+Pattern: =test-card-type-<card-type>-<category>-<scenario>.el=
+
+Examples:
+- =test-card-type-simple-normal-shows-question-hides-answer.el=
+- =test-card-type-twosided-normal-alternates-sides.el=
+- =test-card-type-hide1cloze-boundary-single-cloze-hides-correctly.el=
+- =test-card-type-multicloze-error-no-cloze-markup-fails-gracefully.el=
+
+*Card types to test:*
+1. =org-drill-present-simple-card= - Basic Q&A (PRIORITY: HIGH)
+2. =org-drill-present-two-sided-card= - Bidirectional cards (PRIORITY: MEDIUM)
+3. =org-drill-present-multi-sided-card= - Multiple sides (PRIORITY: MEDIUM)
+4. =org-drill-present-multicloze-hide1= - Hide one cloze (PRIORITY: HIGH)
+5. =org-drill-present-multicloze-show1= - Show one cloze (PRIORITY: MEDIUM)
+6. =org-drill-present-multicloze-hide1-firstmore= - Weighted hiding (PRIORITY: LOW)
+7. =org-drill-present-verb-conjugation= - Language learning (PRIORITY: LOW)
+8. =org-drill-present-noun-declension= - Language learning (PRIORITY: LOW)
+
+** Priority 5: Session State & Reporting (Polish)
+
+*** org-drill-session class methods
+*Criticality:* MEDIUM - Session state management
+- Track progress, counts, statistics
+- *Integration test:* =test-integration-session-state-tracking.el=
+
+*** org-drill-report
+*Criticality:* LOW - User feedback
+- Display session results
+- Less critical to core functionality
+
+* Integration Test Scenarios
+
+** High Priority Integration Tests
+
+*** Complete Drill Session Workflow
+*File:* =test-integration-drill-session-complete-workflow.el=
+
+*Scenario:* User runs org-drill, reviews items, session completes successfully
+
+*Components integrated:*
+- org-drill (entry point)
+- org-drill-entries (session loop)
+- org-drill-entry (individual drill)
+- Card presentation functions
+- org-drill-reschedule (update intervals)
+- Property persistence (DRILL_LAST_REVIEWED, etc.)
+
+*Validates:*
+- Entries are selected correctly based on due dates
+- Cards present appropriately for their type
+- User responses trigger correct rescheduling
+- Entry properties are updated and persisted
+- Session statistics are accurate
+
+*** Spaced Repetition Algorithm Integration
+*File:* =test-integration-spaced-repetition-quality-affects-intervals.el=
+
+*Scenario:* Different quality ratings produce expected interval changes
+
+*Components integrated:*
+- org-drill-entry (collects quality rating)
+- org-drill-determine-next-interval-* (calculates interval)
+- org-drill-reschedule (applies new interval)
+- Property reading/writing
+
+*Validates:*
+- Quality 5 → longer intervals (easy items)
+- Quality 0-2 → reset or short intervals (failed items)
+- Intervals increase appropriately with successful repetitions
+- Algorithm choice (SM2/SM5/Simple8) affects results correctly
+- Lapsed items handled appropriately
+
+*** Leech Detection and Handling
+*File:* =test-integration-leech-detection-and-handling.el=
+
+*Scenario:* Items that fail repeatedly are tagged and handled as leeches
+
+*Components integrated:*
+- org-drill-entry (tracks failures)
+- Failure count increment
+- Leech tagging (add "leech" tag)
+- org-drill-entry-leech-p (detection)
+- Leech method handling (skip/warn/nil)
+
+*Validates:*
+- Failure threshold triggers leech tagging
+- Leech items are skipped when leech-method is 'skip
+- Warning displayed when leech-method is 'warn
+- Leech tag persists across sessions
+
+** Medium Priority Integration Tests
+
+*** Card Type Presentation Chain
+*File:* =test-integration-card-types-presentation-and-answer.el=
+
+*Scenario:* Different card types present correctly and collect answers
+
+*Components integrated:*
+- org-drill-entry-f (card orchestration)
+- Card type presentation functions
+- org-drill-present-default-answer (answer display)
+- Overlay management (hiding/showing text)
+
+*Validates:*
+- Each card type hides appropriate content
+- Answer reveal shows correct information
+- User can navigate through answer display
+- Overlays are cleaned up properly
+
+*** Multi-File and Scope Handling
+*File:* =test-integration-scope-handling-files-trees-agenda.el=
+
+*Scenario:* Drill sessions work across different scopes
+
+*Components integrated:*
+- org-drill (scope parameter handling)
+- org-drill-map-entries (scope-aware filtering)
+- org-agenda integration (agenda scope)
+- File finding and buffer management
+
+*Validates:*
+- File scope drills only current file
+- Tree scope drills only current subtree
+- Directory scope finds all .org files
+- Agenda scope uses org-agenda-files
+
+*** Cram Mode vs Normal Mode
+*File:* =test-integration-cram-mode-behavior.el=
+
+*Scenario:* Cram mode treats entries differently than normal mode
+
+*Components integrated:*
+- org-drill-cram (cram mode entry)
+- Entry filtering (all items vs due items)
+- Scheduling (cram doesn't update long-term schedule)
+- org-drill-cram-hours (recent review filtering)
+
+*Validates:*
+- Cram mode includes all entries regardless of due date
+- Recent items (within cram-hours) are excluded
+- Cram mode doesn't update normal scheduling data
+- Normal mode only includes due entries
+
+** Lower Priority Integration Tests
+
+*** Session Interruption and Resume
+*File:* =test-integration-session-resume-after-interruption.el=
+
+*Scenario:* User can pause and resume drill sessions
+
+*Validates:*
+- Session state is preserved
+- Failed items are remembered
+- Resume continues from correct point
+
+*** Session Time and Count Limits
+*File:* =test-integration-session-limits-time-and-count.el=
+
+*Scenario:* Sessions respect maximum duration and item counts
+
+*Validates:*
+- Session stops at maximum items
+- Session stops at maximum duration
+- Limits are configurable
+
+* Implementation Plan
+
+** Phase 1: Foundation (Week 1)
+Goal: Test critical path functions to ensure basic operation
+
+*** Step 1.1: Entry Detection Tests
+- [ ] Create =test-org-drill-entry-p.el=
+ - Normal: Valid drill tag
+ - Boundary: Inherited tag, nested entries
+ - Error: No heading, no tags
+
+- [ ] Create =test-org-drill-part-of-drill-entry-p.el=
+ - Normal: Main heading and subheading
+ - Boundary: Deeply nested
+ - Error: Outside drill entry
+
+*** Step 1.2: Basic Scheduling Tests
+- [ ] Create =test-org-drill-determine-next-interval-sm2.el=
+ - Normal: Quality 3-5 (successful recall)
+ - Boundary: First repetition, very high repetition count
+ - Error: Quality 0-2 (failed items), invalid quality
+
+- [ ] Create =test-org-drill-reschedule.el=
+ - Normal: Update with new interval
+ - Boundary: Nil intervals, zero intervals
+ - Error: Invalid entry, missing properties
+
+*** Step 1.3: First Integration Test
+- [ ] Create =test-integration-drill-session-simple-workflow.el=
+ - Single entry, simple card type
+ - User rates quality 4
+ - Verify interval updated correctly
+
+** Phase 2: Card Types (Week 2)
+Goal: Ensure all card types work correctly
+
+*** Step 2.1: Simple Card Types
+- [ ] Create =test-card-type-simple-normal-presentation.el=
+- [ ] Create =test-card-type-twosided-normal-alternates.el=
+
+*** Step 2.2: Cloze Card Types
+- [ ] Create =test-card-type-hide1cloze-normal-single-hidden.el=
+- [ ] Create =test-card-type-show1cloze-normal-single-shown.el=
+- [ ] Create =test-card-type-multicloze-boundary-multiple-clozes.el=
+
+*** Step 2.3: Card Type Integration
+- [ ] Create =test-integration-card-types-all-types-work.el=
+ - Test each card type in actual drill session
+ - Verify presentation and answer handling
+
+** Phase 3: Advanced Scheduling (Week 3)
+Goal: Test all scheduling algorithms thoroughly
+
+*** Step 3.1: SM5 Algorithm
+- [ ] Create =test-org-drill-determine-next-interval-sm5.el=
+- [ ] Test optimal factor matrix behavior
+
+*** Step 3.2: Simple8 Algorithm
+- [ ] Create =test-org-drill-determine-next-interval-simple8.el=
+- [ ] Test early/late review adjustments
+
+*** Step 3.3: Overdue and Due Logic
+- [ ] Create =test-org-drill-entry-days-overdue.el=
+- [ ] Create =test-org-drill-entry-overdue-p.el=
+- [ ] Create =test-org-drill-entry-due-p.el=
+
+*** Step 3.4: Algorithm Integration
+- [ ] Create =test-integration-spaced-repetition-algorithms.el=
+ - Compare SM2, SM5, Simple8 behaviors
+ - Verify algorithm selection works
+
+** Phase 4: Session Management (Week 4)
+Goal: Test session orchestration and state
+
+*** Step 4.1: Session State Tests
+- [ ] Create =test-org-drill-session-class.el=
+ - Test session initialization
+ - Test state tracking (done, failed, pending)
+
+*** Step 4.2: Entry Collection
+- [ ] Create =test-org-drill-map-entries.el=
+ - Test file scope
+ - Test tree scope
+ - Test tag filtering
+
+*** Step 4.3: Session Integration
+- [ ] Create =test-integration-drill-session-complete-workflow.el=
+- [ ] Create =test-integration-session-state-tracking.el=
+
+** Phase 5: Special Cases (Week 5)
+Goal: Test edge cases and special behaviors
+
+*** Step 5.1: Leech Handling
+- [ ] Create =test-org-drill-entry-leech-p.el=
+- [ ] Create =test-integration-leech-detection-and-handling.el=
+
+*** Step 5.2: Cram Mode
+- [ ] Create =test-org-drill-cram.el=
+- [ ] Create =test-integration-cram-mode-behavior.el=
+
+*** Step 5.3: Session Limits
+- [ ] Create =test-integration-session-limits-time-and-count.el=
+
+** Phase 6: Polish (Week 6)
+Goal: Add remaining coverage and documentation
+
+*** Step 6.1: Boundary Cases
+- [ ] Review all test files for boundary case coverage
+- [ ] Add missing boundary tests
+
+*** Step 6.2: Error Cases
+- [ ] Review all test files for error case coverage
+- [ ] Add missing error tests
+
+*** Step 6.3: Documentation
+- [ ] Update this document with final coverage statistics
+- [ ] Document any untested areas and rationale
+- [ ] Add test maintenance guide
+
+* Test Naming Quick Reference
+
+** Unit Test Naming
+Pattern: =test-org-drill-<function>-<category>-<scenario>-<expected>.el=
+
+Categories:
+- =normal= - Expected usage patterns
+- =boundary= - Edge values, empty/nil, limits
+- =error= - Invalid inputs, failures
+
+Example structure within file:
+#+begin_src elisp
+;;; Normal Cases
+(ert-deftest test-org-drill-entry-p-normal-valid-tag-returns-true () ...)
+(ert-deftest test-org-drill-entry-p-normal-no-tag-returns-nil () ...)
+
+;;; Boundary Cases
+(ert-deftest test-org-drill-entry-p-boundary-inherited-tag-returns-true () ...)
+(ert-deftest test-org-drill-entry-p-boundary-deeply-nested-returns-true () ...)
+
+;;; Error Cases
+(ert-deftest test-org-drill-entry-p-error-not-at-heading-returns-nil () ...)
+#+end_src
+
+** Integration Test Naming
+Pattern: =test-integration-<area>-<scenario>-<outcome>.el=
+
+Areas:
+- =drill-session= - Complete drill workflows
+- =spaced-repetition= - Algorithm behavior
+- =card-types= - Card presentation
+- =leech= - Leech detection and handling
+- =cram= - Cram mode behavior
+- =scope= - File/tree/agenda scope
+- =session-state= - State tracking
+
+Example structure within file:
+#+begin_src elisp
+;;; Setup
+(defun test-integration-setup-drill-buffer () ...)
+
+;;; Normal Workflow Tests
+(ert-deftest test-integration-drill-session-single-entry-completes () ...)
+(ert-deftest test-integration-drill-session-multiple-entries-tracked () ...)
+
+;;; Edge Case Tests
+(ert-deftest test-integration-drill-session-all-failed-tracks-correctly () ...)
+#+end_src
+
+** Card Type Test Naming
+Pattern: =test-card-type-<card-type>-<category>-<scenario>.el=
+
+Card types:
+- =simple= - Basic Q&A
+- =twosided= - Bidirectional
+- =multisided= - Multiple faces
+- =hide1cloze= - Hide one cloze
+- =show1cloze= - Show one cloze
+- =multicloze= - Multiple cloze handling
+- =conjugate= - Verb conjugation
+- =declension= - Noun declension
+
+* Coverage Goals
+
+** Target Coverage by Component
+
+*** Entry Management: 90%
+- Entry detection functions are critical
+- Must handle all tag inheritance cases
+- Edge cases around heading detection
+
+*** Scheduling Algorithms: 95%
+- Mathematical correctness is essential
+- All quality ratings must be tested
+- Boundary intervals (0, 1, max) critical
+
+*** Card Types: 80%
+- Basic types (simple, cloze) need high coverage
+- Specialized types (conjugation) less critical
+- Focus on correct text hiding/showing
+
+*** Session Management: 85%
+- Core loop must be robust
+- State tracking is important
+- Scope handling needs coverage
+
+*** UI/Presentation: 60%
+- Interactive functions harder to test
+- Focus on testable helper functions
+- Integration tests for user workflows
+
+** Overall Target: 80% Coverage
+- Focus on critical path first
+- Add coverage incrementally
+- Balance effort vs value
+
+* Maintenance Guidelines
+
+** Updating This Document
+- Update "Current Status" section as tests are implemented
+- Check off items in Implementation Plan as completed
+- Document any deviations from the plan with rationale
+- Add new test ideas to the appropriate section
+
+** Test Maintenance
+- Run full test suite before committing: =make test=
+- Update tests when functionality changes
+- Remove obsolete tests
+- Refactor tests alongside production code
+
+** Adding New Tests
+1. Determine if unit or integration test
+2. Follow naming convention for category
+3. Place in appropriate file (create if needed)
+4. Use existing test utilities where possible
+5. Add to this document's tracking sections
+
+* References
+
+- quality-engineer.org: Comprehensive testing guidelines
+- Makefile: Test runner configuration
+- tests/org-drill-test.el: Existing test examples
+- testutil-*.el files: Test utility functions (if created)
+
+* Notes
+
+** Test Philosophy for org-drill
+- Spaced repetition correctness is paramount (test algorithms thoroughly)
+- User data integrity matters (test property updates carefully)
+- Card presentation affects learning (test hiding/showing accurately)
+- Session state must be reliable (test state transitions)
+
+** Card Types: Unit or Integration?
+Card type tests are HYBRID:
+- *Unit aspects:* Text hiding, formatting, overlay management
+- *Integration aspects:* Answer handling, user response, state transitions
+
+*Recommendation:* Write as unit tests first (fast, focused), then add integration tests for workflows that span card presentation + answer + rescheduling.
+
+** Testing Interactive Functions
+Many org-drill functions are interactive (=defun ... (interactive)=):
+- Extract testable logic into internal functions (=org-drill--internal=)
+- Test internal functions with explicit parameters
+- Keep interactive wrappers thin (just user input handling)
+- Integration tests can exercise full interactive workflows
+
+** Testing with Real Org Buffers
+Some tests need real org-mode buffers:
+- Use =with-temp-buffer= and =(org-mode)= for temporary buffers
+- Create test data as org-mode text, not mocked functions
+- Test with realistic org structure (headings, properties, tags)
+- Clean up buffers in teardown