From 3fa47b1a3b7dd8d3a4f00f117e6f97caf55a3c5d Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 23 Apr 2026 01:07:52 -0500 Subject: feat(coverage): add format-report helper for the report buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure helper that renders intersect records into the text shown in the coverage report buffer. Takes the list of per-file plists from cj/--coverage-intersect and a scope label, returns the formatted string. Output has three sections depending on what's present: - "Uncovered lines" — one line per uncovered line, formatted as ":: uncovered" so compilation-mode's default regex picks them up for next-error navigation. - "Not tracked" — files changed in the diff but absent from the coverage data (READMEs, test files, config). - "Fully covered" — tracked files where every changed line is covered. Files with empty :changed-lines (deletion-only hunks) are omitted. Summary counts cover only tracked files, so an all-README change shows "0 of 0" rather than a misleading percentage over nothing. Tests cover Normal (partial, fully covered, mixed sections), Boundary (empty records, 100% coverage with no uncovered section, only-not-tracked case, deletion-only exclusion), and the output format that next-error relies on. --- tests/test-coverage-core--format-report.el | 124 +++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/test-coverage-core--format-report.el (limited to 'tests/test-coverage-core--format-report.el') diff --git a/tests/test-coverage-core--format-report.el b/tests/test-coverage-core--format-report.el new file mode 100644 index 00000000..24d34be0 --- /dev/null +++ b/tests/test-coverage-core--format-report.el @@ -0,0 +1,124 @@ +;;; test-coverage-core--format-report.el --- Tests for cj/--coverage-format-report -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for `cj/--coverage-format-report', the pure helper that +;; renders intersect records into the text shown in the report buffer. +;; +;; Input: list of plists from `cj/--coverage-intersect': +;; (:path PATH +;; :changed-lines LIST +;; :covered-lines LIST +;; :uncovered-lines LIST +;; :tracked BOOL) +;; +;; Output: a string suitable for insertion into a compilation-mode +;; buffer. Uncovered-line entries use the format ":: +;; uncovered" so `compilation-error-regexp-alist' picks them up for +;; `next-error' navigation. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'coverage-core) + +(defun test-coverage-format-report--record (path changed covered uncovered tracked) + "Build an intersect record plist for use in tests." + (list :path path + :changed-lines changed + :covered-lines covered + :uncovered-lines uncovered + :tracked tracked)) + +;;; Normal cases + +(ert-deftest test-coverage-format-report-partial-coverage () + "Normal: one file with 2 of 3 changed lines covered." + (let* ((records (list (test-coverage-format-report--record + "modules/foo.el" '(10 11 12) '(10 11) '(12) t))) + (output (cj/--coverage-format-report records "Staged"))) + (should (string-match-p "Staged" output)) + ;; Summary line shows the counts + (should (string-match-p "2 of 3" output)) + (should (string-match-p "66" output)) ; 66.7% + ;; Uncovered line uses compilation-friendly format + (should (string-match-p "modules/foo\\.el:12: uncovered" output)) + ;; Covered lines aren't listed individually + (should-not (string-match-p "modules/foo\\.el:10: uncovered" output)) + (should-not (string-match-p "modules/foo\\.el:11: uncovered" output)))) + +(ert-deftest test-coverage-format-report-fully-covered () + "Normal: a fully-covered file lists in the \"Fully covered\" section." + (let* ((records (list (test-coverage-format-report--record + "modules/baz.el" '(1 2 3) '(1 2 3) nil t))) + (output (cj/--coverage-format-report records "Working tree"))) + (should (string-match-p "Fully covered" output)) + (should (string-match-p "modules/baz\\.el" output)) + (should (string-match-p "3/3" output)) + ;; No uncovered entries for this file + (should-not (string-match-p ":[0-9]+: uncovered" output)))) + +(ert-deftest test-coverage-format-report-mixed-records () + "Normal: a mix of covered, partial, and not-tracked files." + (let* ((records (list + (test-coverage-format-report--record + "modules/foo.el" '(10 11) '(10) '(11) t) + (test-coverage-format-report--record + "README.md" '(5 6 7) nil nil nil) + (test-coverage-format-report--record + "modules/baz.el" '(1 2) '(1 2) nil t))) + (output (cj/--coverage-format-report records "Branch vs main"))) + (should (string-match-p "Uncovered lines" output)) + (should (string-match-p "Not tracked" output)) + (should (string-match-p "Fully covered" output)) + (should (string-match-p "modules/foo\\.el:11: uncovered" output)) + (should (string-match-p "README\\.md" output)) + (should (string-match-p "modules/baz\\.el" output)))) + +;;; Boundary cases + +(ert-deftest test-coverage-format-report-empty () + "Boundary: no records produces a clear \"nothing to report\" message." + (let ((output (cj/--coverage-format-report nil "Staged"))) + (should (string-match-p "No changes" output)) + (should (string-match-p "Staged" output)))) + +(ert-deftest test-coverage-format-report-100-percent () + "Boundary: 100% coverage shows a success line." + (let* ((records (list (test-coverage-format-report--record + "modules/foo.el" '(1 2 3) '(1 2 3) nil t))) + (output (cj/--coverage-format-report records "Working tree"))) + (should (string-match-p "3 of 3" output)) + (should (string-match-p "100" output)) + ;; No "Uncovered lines" section when there are none + (should-not (string-match-p "Uncovered lines" output)))) + +(ert-deftest test-coverage-format-report-only-not-tracked () + "Boundary: every changed file is not-tracked (README-only edits)." + (let* ((records (list + (test-coverage-format-report--record + "README.md" '(1 2) nil nil nil) + (test-coverage-format-report--record + "CHANGELOG.md" '(5) nil nil nil))) + (output (cj/--coverage-format-report records "Staged"))) + (should (string-match-p "Not tracked" output)) + ;; Summary denominator excludes not-tracked lines + (should (string-match-p "0 of 0" output)) + (should-not (string-match-p "Uncovered lines" output)))) + +(ert-deftest test-coverage-format-report-deletion-only-skipped () + "Boundary: deletion-only records (empty changed-lines) are excluded from display." + (let* ((records (list + (test-coverage-format-report--record + "modules/foo.el" '(10 11) '(10 11) nil t) + (test-coverage-format-report--record + "gone.el" nil nil nil t))) + (output (cj/--coverage-format-report records "Working tree"))) + ;; The deletion-only file shouldn't appear anywhere. + (should-not (string-match-p "gone\\.el" output)) + ;; Summary still shows the normal-case counts. + (should (string-match-p "2 of 2" output)))) + +(provide 'test-coverage-core--format-report) +;;; test-coverage-core--format-report.el ends here -- cgit v1.2.3