diff options
| author | Craig Jennings <c@cjennings.net> | 2025-11-13 11:03:00 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-11-13 11:03:00 -0600 |
| commit | 87e74a3a6ccf5b05b760e9f8beec9a78886ab076 (patch) | |
| tree | 2179ba106bef7b6dc2f3ad72bfe567205213e609 | |
| parent | 2442235f1aa86dcfba1909ddeba0cf37b46922a3 (diff) | |
| download | org-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-- | Makefile | 268 | ||||
| -rw-r--r-- | org-drill.el | 145 | ||||
| -rw-r--r-- | test/Makefile | 6 | ||||
| -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.org | 636 |
6 files changed, 940 insertions, 115 deletions
@@ -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 |
