;;; tp-layer-tests.el --- ERT regression tests for tp-layer.el -*- lexical-binding: t -*- ;;; Commentary: ;; Regression tests for confirmed bugs fixed in the layer-definition ;; module (tp-layer.el). Each section is tagged with the canonical ;; bug id it guards against. ;;; Code: (require 'ert) (require 'tp) (defmacro tp-layer-tests--with-clean (&rest body) "Run BODY with a clean layer/reactive state, resetting afterwards." (declare (indent 0)) `(unwind-protect (progn (tp-layer-reset) ,@body) (tp-layer-reset))) ;; Dynamic variables used by reactive tests ($foo refers to variable foo). (defvar tp-layer-test-b15-color nil) (defvar tp-layer-test-b23-color nil) (defvar tp-layer-test-b26-color nil) ;;; B20: documented parameterized define-tps format must yield props (ert-deftest tp-layer-test-param-group-docstring-format () "The define-tps docstring Format 2 example returns real props." (tp-layer-tests--with-clean (define-tps tp-layer-test-status (color) `((face (:foreground ,color))) '(face (:weight bold))) (should (tp-group-parameterized-p 'tp-layer-test-status)) (should (equal (tp-group-props-with-arg 'tp-layer-test-status "red") '((face (:foreground "red")) (face (:weight bold))))))) (ert-deftest tp-layer-test-param-group-resolves-in-tp-set-path () "tp--resolve-props builds a layered structure from a parameterized group." (tp-layer-tests--with-clean (define-tps tp-layer-test-status (color) `((face (:foreground ,color))) '(face (:weight bold))) (let ((props (tp--resolve-props '(tp-layer-test-status "red")))) (should (equal (plist-get props 'face) '(:foreground "red"))) (should (equal (plist-get props 'tp-layers) '((face (:weight bold)))))))) (ert-deftest tp-layer-test-param-group-layer-reference-specs () "Parameterized groups still accept layer-name and (LAYER ARG) specs." (tp-layer-tests--with-clean (define-tp tp-layer-test-bold () '(face bold)) (define-tp tp-layer-test-fg (c) `(face (:foreground ,c))) (define-tps tp-layer-test-mixed (color) 'tp-layer-test-bold `(tp-layer-test-fg ,color)) (should (equal (tp-group-props-with-arg 'tp-layer-test-mixed "blue") '((face bold) (face (:foreground "blue"))))))) (ert-deftest tp-layer-test-param-group-named-element () "Parameterized groups accept named (\"NAME\" :props PLIST) elements." (tp-layer-tests--with-clean (define-tps tp-layer-test-named (color) `(("fg" :props (face (:foreground ,color))))) (should (equal (tp-group-props-with-arg 'tp-layer-test-named "red") '((face (:foreground "red"))))))) ;;; B21: cyclic layer references signal a clear error, not stack overflow (ert-deftest tp-layer-test-cycle-self-reference () "A layer referencing itself signals an error naming the cycle." (tp-layer-tests--with-clean (tp--set-layer-props 'tp-layer-test-cyc '(tp-layer-test-cyc t face bold)) (let ((err (should-error (tp-layer-props 'tp-layer-test-cyc)))) (should (string-match-p "cyclic layer reference" (error-message-string err))) (should (string-match-p "tp-layer-test-cyc -> tp-layer-test-cyc" (error-message-string err)))))) (ert-deftest tp-layer-test-cycle-mutual-reference () "Two layers referencing each other signal an error naming both." (tp-layer-tests--with-clean (tp--set-layer-props 'tp-layer-test-ca '(tp-layer-test-cb t face bold)) (tp--set-layer-props 'tp-layer-test-cb '(tp-layer-test-ca t face italic)) (let ((err (should-error (tp-layer-props 'tp-layer-test-ca)))) (should (string-match-p "tp-layer-test-ca -> tp-layer-test-cb -> tp-layer-test-ca" (error-message-string err)))))) (ert-deftest tp-layer-test-cycle-diamond-is-not-a-cycle () "Re-using the same layer along different branches is not a cycle." (tp-layer-tests--with-clean (define-tp tp-layer-test-base () '(face bold)) (tp--set-layer-props 'tp-layer-test-left '(tp-layer-test-base t help-echo "l")) (tp--set-layer-props 'tp-layer-test-right '(tp-layer-test-base t mouse-face highlight)) (tp--set-layer-props 'tp-layer-test-top '(tp-layer-test-left t tp-layer-test-right t)) (let ((props (tp-layer-props 'tp-layer-test-top))) (should (equal (plist-get props 'help-echo) "l")) (should (eq (plist-get props 'mouse-face) 'highlight))))) ;;; B22: extra body forms in define-tp simple format are an error (ert-deftest tp-layer-test-extra-body-forms-error () "define-tp with two simple body forms errors instead of dropping one." (should-error (eval '(define-tp tp-layer-test-extra () '(face bold) '(display "x")) t))) (ert-deftest tp-layer-test-single-body-form-still-works () "define-tp with exactly one simple body form still defines the layer." (tp-layer-tests--with-clean (eval '(define-tp tp-layer-test-single () '(face bold)) t) (should (equal (tp-layer-props 'tp-layer-test-single) '(face bold))))) (ert-deftest tp-layer-test-keyword-format-unaffected-by-arity-check () "The reactive keyword format still accepts multiple keyword pairs." (tp-layer-tests--with-clean (eval '(define-tp tp-layer-test-kw () :props '(face bold) :transform #'upcase) t) (should (equal (plist-get (tp-layer-props 'tp-layer-test-kw) 'face) 'bold)) (should (eq (cdr (assoc 'tp-layer-test-kw tp-layer-transforms)) #'upcase)))) ;;; B23: $-symbols in parameterized bodies resolve instead of leaking (ert-deftest tp-layer-test-param-layer-resolves-reactive-symbols () "$-syms in a parameterized body resolve to current variable values." (tp-layer-tests--with-clean (setq tp-layer-test-b23-color "green") (define-tp tp-layer-test-preact (x) `(face (:foreground $tp-layer-test-b23-color) help-echo ,x)) (should (equal (tp-layer-props-with-arg 'tp-layer-test-preact "hi") '(face (:foreground "green") help-echo "hi"))) ;; And through the tp-set resolution pipeline as well. (should (equal (tp--resolve-props '(tp-layer-test-preact "hi")) '(face (:foreground "green") help-echo "hi"))))) (ert-deftest tp-layer-test-param-layer-reactive-syms-not-registered () "Resolved $-syms in parameterized bodies create no reactive deps." (tp-layer-tests--with-clean (setq tp-layer-test-b23-color "green") (define-tp tp-layer-test-preact (x) `(face (:foreground $tp-layer-test-b23-color) help-echo ,x)) (tp-layer-props-with-arg 'tp-layer-test-preact "hi") (should-not (tp--layer-has-reactive-deps-p 'tp-layer-test-preact)))) ;;; B24: accessors return copies, not internal storage (ert-deftest tp-layer-test-props-mutation-does-not-corrupt-static-layer () "Mutating the plist returned for a define-tp layer leaves it intact." (tp-layer-tests--with-clean (define-tp tp-layer-test-st () '(face bold)) (let ((props (tp-layer-props 'tp-layer-test-st))) (setcar (cdr props) 'MUTATED)) (should (equal (tp-layer-props 'tp-layer-test-st) '(face bold))))) (ert-deftest tp-layer-test-props-mutation-does-not-corrupt-old-format () "Mutating the plist returned for an old-format layer leaves it intact." (tp-layer-tests--with-clean (tp--set-layer-props 'tp-layer-test-old '(face bold)) (let ((props (tp-layer-props 'tp-layer-test-old))) (setcar (cdr props) 'MUTATED)) (should (equal (tp-layer-props 'tp-layer-test-old) '(face bold))))) (ert-deftest tp-layer-test-props-deep-mutation-does-not-corrupt () "Mutating nested structure of the returned plist leaves storage intact." (tp-layer-tests--with-clean (tp--set-layer-props 'tp-layer-test-deep '(face (:weight bold))) (let ((props (tp-layer-props 'tp-layer-test-deep))) (setcar (plist-get props 'face) 'MUTATED)) (should (equal (tp-layer-props 'tp-layer-test-deep) '(face (:weight bold)))))) (ert-deftest tp-layer-test-group-props-mutation-does-not-corrupt () "Mutating plists returned by tp-group-props leaves layers intact." (tp-layer-tests--with-clean (define-tp tp-layer-test-gm () '(face bold)) (define-tps tp-layer-test-gmg () 'tp-layer-test-gm) (let ((props-list (tp-group-props 'tp-layer-test-gmg))) (setcar (cdar props-list) 'MUTATED)) (should (equal (tp-group-props 'tp-layer-test-gmg) '((face bold)))))) ;;; B25: :transform in define-tps group elements is registered (ert-deftest tp-layer-test-group-element-transform-registered () "A format-4 group element's :transform lands in tp-layer-transforms." (tp-layer-tests--with-clean (define-tps tp-layer-test-tg () '("a" :props (face (:foreground $tp-layer-test-b26-color)) :data ((tp-layer-test-b26-color . "red")) :transform upcase)) (should (eq (cdr (assoc 'tp-layer-test-tg-a tp-layer-transforms)) 'upcase)))) (ert-deftest tp-layer-test-group-element-transform-removed-on-redefine () "Redefining a group element without :transform unregisters the old one." (tp-layer-tests--with-clean (define-tps tp-layer-test-tg () '("a" :props (face bold) :transform upcase)) (should (assoc 'tp-layer-test-tg-a tp-layer-transforms)) (define-tps tp-layer-test-tg () '("a" . (face bold))) (should-not (assoc 'tp-layer-test-tg-a tp-layer-transforms)))) ;;; B26: group redefinition / undefinition cleans up generated layers (ert-deftest tp-layer-test-group-redefine-removes-orphans () "Shrinking a group on redefinition undefines the dropped layers." (tp-layer-tests--with-clean (define-tps tp-layer-test-rg () '(face bold) '(face italic) '(face underline)) (should (assoc 'tp-layer-test-rg-1 tp-layer-alist)) (should (assoc 'tp-layer-test-rg-2 tp-layer-alist)) (define-tps tp-layer-test-rg () '(face bold)) (should (assoc 'tp-layer-test-rg-0 tp-layer-alist)) (should-not (assoc 'tp-layer-test-rg-1 tp-layer-alist)) (should-not (assoc 'tp-layer-test-rg-2 tp-layer-alist)) (should (equal (tp-group-props 'tp-layer-test-rg) '((face bold)))))) (ert-deftest tp-layer-test-undefine-group-removes-generated-layers () "tp-undefine-group also undefines layers generated by the group." (tp-layer-tests--with-clean (define-tps tp-layer-test-ug () '(face bold) '("named" . (face italic))) (tp-undefine-group 'tp-layer-test-ug) (should-not (assoc 'tp-layer-test-ug tp-layer-groups)) (should-not (assoc 'tp-layer-test-ug-0 tp-layer-alist)) (should-not (assoc 'tp-layer-test-ug-named tp-layer-alist)))) (ert-deftest tp-layer-test-undefine-group-keeps-referenced-layers () "Layers merely referenced by a group survive its undefinition." (tp-layer-tests--with-clean (define-tp tp-layer-test-keep () '(face bold)) (define-tps tp-layer-test-ug2 () 'tp-layer-test-keep '(face italic)) (tp-undefine-group 'tp-layer-test-ug2) (should (assoc 'tp-layer-test-keep tp-layer-alist)) (should-not (assoc 'tp-layer-test-ug2-0 tp-layer-alist)))) (ert-deftest tp-layer-test-undefine-group-cleans-reactive-deps () "Undefining a group unregisters reactive deps of generated layers." (tp-layer-tests--with-clean (define-tps tp-layer-test-ug3 () '("r" :props (face (:foreground $tp-layer-test-b26-color)) :data ((tp-layer-test-b26-color . "red")))) (should (tp--layer-has-reactive-deps-p 'tp-layer-test-ug3-r)) (tp-undefine-group 'tp-layer-test-ug3) (should-not (tp--layer-has-reactive-deps-p 'tp-layer-test-ug3-r)) (should-not (assoc 'tp-layer-test-ug3-r tp-layer-alist)))) (ert-deftest tp-layer-test-group-redefine-to-parameterized-cleans-up () "Redefining a plain group as parameterized undefines its old layers." (tp-layer-tests--with-clean (define-tps tp-layer-test-pg () '(face bold)) (should (assoc 'tp-layer-test-pg-0 tp-layer-alist)) (define-tps tp-layer-test-pg (color) `((face (:foreground ,color)))) (should-not (assoc 'tp-layer-test-pg-0 tp-layer-alist)) (should (tp-group-parameterized-p 'tp-layer-test-pg)))) ;;; B27: unknown keywords in group elements are an error, not a misparse (ert-deftest tp-layer-test-group-element-unknown-keyword-errors () "An unknown keyword in a format-4 group element signals an error." (tp-layer-tests--with-clean (let ((err (should-error (eval '(define-tps tp-layer-test-bad () '("a" :props (face bold) :bogus (:props (face italic)))) t)))) (should (string-match-p "Unknown keyword" (error-message-string err)))))) ;;; B15: anonymous reactive layers are interned, not minted per call (ert-deftest tp-layer-test-anonymous-layer-interned () "Equal reactive plists reuse a single anonymous layer entry." (tp-layer-tests--with-clean (setq tp-layer-test-b15-color "red") (let* ((s1 (tp-set (copy-sequence "hi") '(face (:foreground $tp-layer-test-b15-color)))) (s2 (tp-set (copy-sequence "hi") '(face (:foreground $tp-layer-test-b15-color)))) (n1 (get-text-property 0 'tp-name s1)) (n2 (get-text-property 0 'tp-name s2))) (should n1) (should (eq n1 n2)) ;; Exactly one anonymous registry entry for the shared spec. (should (= (length tp-layer-alist) 1))))) (ert-deftest tp-layer-test-anonymous-layer-distinct-specs-distinct () "Different reactive plists still get different anonymous layers." (tp-layer-tests--with-clean (setq tp-layer-test-b15-color "red") (let* ((s1 (tp-set (copy-sequence "hi") '(face (:foreground $tp-layer-test-b15-color)))) (s2 (tp-set (copy-sequence "hi") '(face (:background $tp-layer-test-b15-color)))) (n1 (get-text-property 0 'tp-name s1)) (n2 (get-text-property 0 'tp-name s2))) (should n1) (should n2) (should-not (eq n1 n2))))) (ert-deftest tp-layer-test-anonymous-layer-reuse-keeps-reactivity () "Reactive updates still reach buffer text using a reused anonymous layer." (tp-layer-tests--with-clean (with-temp-buffer (setq tp-layer-test-b15-color "red") (insert "Hello World") (tp-set 1 3 '(face (:foreground $tp-layer-test-b15-color))) (tp-set 7 9 '(face (:foreground $tp-layer-test-b15-color))) (should (eq (get-text-property 1 'tp-name) (get-text-property 7 'tp-name))) (setq tp-layer-test-b15-color "blue") (should (equal (plist-get (get-text-property 1 'face) :foreground) "blue")) (should (equal (plist-get (get-text-property 7 'face) :foreground) "blue"))))) (ert-deftest tp-layer-test-anonymous-registry-cleared-on-reset () "tp-layer-reset clears the anonymous-layer intern registry." (tp-layer-tests--with-clean (setq tp-layer-test-b15-color "red") (tp-set (copy-sequence "hi") '(face (:foreground $tp-layer-test-b15-color))) (should tp--anonymous-layer-registry) (tp-layer-reset) (should-not tp--anonymous-layer-registry))) ;;; 0.3.0 A4: multi-argument parameterized layers (ert-deftest tp-layer-test-multi-arg-define-and-props-with-args () "define-tp accepts multi-symbol arglists; props-with-args expands them." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) (should (tp-layer-parameterized-p 'tp-layer-test-fgbg)) (should (equal (tp-layer-arglist 'tp-layer-test-fgbg) '(fg bg))) (should (equal (tp-layer-props-with-args 'tp-layer-test-fgbg '("red" "blue")) '(face (:foreground "red" :background "blue")))) (should (equal (tp-layer-props-with-args 'tp-layer-test-fgbg '("red" "blue") t) '(face (:foreground "red" :background "blue") tp-name tp-layer-test-fgbg))))) (ert-deftest tp-layer-test-props-with-arg-is-thin-wrapper () "tp-layer-props-with-arg keeps its single-argument contract." (tp-layer-tests--with-clean (define-tp tp-layer-test-fg1 (c) `(face (:foreground ,c))) (should (equal (tp-layer-props-with-arg 'tp-layer-test-fg1 "red") '(face (:foreground "red")))) (should (equal (tp-layer-props-with-arg 'tp-layer-test-fg1 "red") (tp-layer-props-with-args 'tp-layer-test-fg1 '("red")))))) (ert-deftest tp-layer-test-props-with-args-non-parameterized-nil () "props-with-args and tp-layer-arglist return nil for other layers." (tp-layer-tests--with-clean (define-tp tp-layer-test-np () '(face bold)) (should-not (tp-layer-props-with-args 'tp-layer-test-np '(1))) (should-not (tp-layer-arglist 'tp-layer-test-np)) (should-not (tp-layer-props-with-args 'tp-layer-test-missing '(1))))) (ert-deftest tp-layer-test-multi-arg-tp-set-flat-string-form () "The flat (tp-set STRING \\='LAYER ARG1 ARG2) form binds all params." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) (let ((s (tp-set "hello" 'tp-layer-test-fgbg "red" "blue"))) (should (equal (get-text-property 0 'face s) '(:foreground "red" :background "blue")))))) (ert-deftest tp-layer-test-multi-arg-tp-set-flat-with-extra-props () "Extra props after multi args survive, with no stray nil pair." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) (let ((s (tp-set "hello" 'tp-layer-test-fgbg "red" "blue" 'help-echo "tip"))) (should (equal (plist-get (get-text-property 0 'face s) :foreground) "red")) (should (equal (get-text-property 0 'help-echo s) "tip")) ;; The odd-length flat spec is padded with nil by key merging; ;; resolution must strip it instead of setting a nil property. (should (equal (text-properties-at 0 s) '(face (:foreground "red" :background "blue") help-echo "tip")))))) (ert-deftest tp-layer-test-multi-arg-tp-set-region-list-form () "The region form (tp-set START END \\='(LAYER ARG1 ARG2)) works (1-based)." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) (with-temp-buffer (insert "hello") (tp-set 1 4 '(tp-layer-test-fgbg "red" "blue")) (should (equal (get-text-property 1 'face) '(:foreground "red" :background "blue"))) (should-not (get-text-property 4 'face))))) (ert-deftest tp-layer-test-multi-arg-tp-set-wrapped-args-plist-form () "The plist spec (LAYER (ARG1 ARG2) EXTRA...) passes args as one list." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) ;; Layer at the head of the plist. (let ((s (copy-sequence "hello"))) (tp-set 0 5 '(tp-layer-test-fgbg ("red" "blue") help-echo "tip") s) (should (equal (get-text-property 0 'face s) '(:foreground "red" :background "blue"))) (should (equal (get-text-property 0 'help-echo s) "tip"))) ;; Layer at a non-head plist position. (let ((s (copy-sequence "hello"))) (tp-set 0 5 '(help-echo "tip" tp-layer-test-fgbg ("red" "blue")) s) (should (equal (plist-get (get-text-property 0 'face s) :background) "blue")) (should (equal (get-text-property 0 'help-echo s) "tip"))))) (ert-deftest tp-layer-test-multi-arg-normalize-layer-spec () "Normalized parameterized specs retain args in managed metadata." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) (let* ((entry (tp--normalize-layer-spec '(tp-layer-test-fgbg "red" "blue"))) (meta (plist-get entry 'tp-meta))) (should (equal (plist-get entry 'face) '(:foreground "red" :background "blue"))) (should (eq (plist-get entry 'tp-name) 'tp-layer-test-fgbg)) (should (equal (plist-get meta :args) '("red" "blue"))) (should (equal (plist-get meta :arglist) '(fg bg))) (should (integerp (plist-get meta :definition-version)))))) (ert-deftest tp-layer-test-multi-arg-tp-put-layer () "tp-put-layer accepts multi-argument parameterized layer specs." (tp-layer-tests--with-clean (define-tp tp-layer-test-fgbg (fg bg) `(face (:foreground ,fg :background ,bg))) (let ((s (copy-sequence "hi"))) (tp-put-layer s '(tp-layer-test-fgbg "red" "blue") 0) (should (equal (get-text-property 0 'face s) '(:foreground "red" :background "blue"))) (should (eq (get-text-property 0 'tp-name s) 'tp-layer-test-fgbg))))) (ert-deftest tp-layer-test-multi-arg-cycle-detection () "Cycle detection still fires through the multi-argument path." (tp-layer-tests--with-clean (define-tp tp-layer-test-mcyc (a b) `(tp-layer-test-mcyc (,a ,b))) (let ((err (should-error (tp-layer-props-with-args 'tp-layer-test-mcyc '(1 2))))) (should (string-match-p "cyclic layer reference" (error-message-string err)))))) (ert-deftest tp-layer-test-multi-arg-props-are-copies () "props-with-args returns fresh copies; mutation cannot corrupt storage." (tp-layer-tests--with-clean ;; The (:weight bold) subform is a shared constant in the ;; backquoted body; without copy-on-return, mutating the returned ;; plist would corrupt every later expansion. (define-tp tp-layer-test-mcopy (a b) `(face (:weight bold) help-echo ,(format "%s-%s" a b))) (let ((props (tp-layer-props-with-args 'tp-layer-test-mcopy '("x" "y")))) (setcar (plist-get props 'face) 'MUTATED)) (should (equal (tp-layer-props-with-args 'tp-layer-test-mcopy '("x" "y")) '(face (:weight bold) help-echo "x-y"))))) (ert-deftest tp-layer-test-multi-arg-group () "define-tps accepts multi-symbol arglists usable through tp-set specs." (tp-layer-tests--with-clean (define-tps tp-layer-test-mgrp (fg w) `((face (:foreground ,fg))) `((face (:weight ,w)))) (should (tp-group-parameterized-p 'tp-layer-test-mgrp)) (should (equal (tp--group-arglist 'tp-layer-test-mgrp) '(fg w))) (should (equal (tp--group-props-with-args 'tp-layer-test-mgrp '("red" bold)) '((face (:foreground "red")) (face (:weight bold))))) ;; Flat (GROUP ARG1 ARG2) spec through the tp-set pipeline. (let ((props (tp--resolve-props '(tp-layer-test-mgrp "red" bold)))) (should (equal (plist-get props 'face) '(:foreground "red"))) (should (equal (plist-get props 'tp-layers) '((face (:weight bold)))))) ;; Single-argument groups keep working through the wrapper. (define-tps tp-layer-test-sgrp (color) `((face (:foreground ,color)))) (should (equal (tp-group-props-with-arg 'tp-layer-test-sgrp "red") '((face (:foreground "red"))))))) ;;; 0.3.0 A5: tp-describe-layer and its data collector (ert-deftest tp-layer-test-describe-data-unified () "Describe data for a define-tp layer reports the unified format." (tp-layer-tests--with-clean (define-tp tp-layer-test-du () '(face bold)) (let ((data (tp--describe-layer-data 'tp-layer-test-du))) (should (eq (plist-get data :name) 'tp-layer-test-du)) (should (eq (plist-get data :format) 'unified)) (should (equal (plist-get data :body) '(quote (face bold)))) (should (equal (plist-get data :props) '(face bold tp-name tp-layer-test-du))) (should-not (plist-get data :arglist)) (should-not (plist-get data :reactive-deps)) (should-not (plist-get data :transform)) (should-not (plist-get data :group))))) (ert-deftest tp-layer-test-describe-data-flat () "Describe data for an old-format layer reports the flat format." (tp-layer-tests--with-clean (tp--set-layer-props 'tp-layer-test-df '(face italic)) (let ((data (tp--describe-layer-data 'tp-layer-test-df))) (should (eq (plist-get data :format) 'flat)) (should (equal (plist-get data :body) '(face italic))) (should (equal (plist-get data :props) '(face italic tp-name tp-layer-test-df)))))) (ert-deftest tp-layer-test-describe-data-parameterized () "Describe data for a parameterized layer reports arglist and a note." (tp-layer-tests--with-clean (define-tp tp-layer-test-dp (a b) `(face (:foreground ,a :background ,b))) (let ((data (tp--describe-layer-data 'tp-layer-test-dp))) (should (eq (plist-get data :format) 'parameterized)) (should (equal (plist-get data :arglist) '(a b))) ;; Expanded props need arguments, so a placeholder note is used. (should (stringp (plist-get data :props))) (should (string-match-p "tp-layer-props-with-args" (plist-get data :props)))))) (ert-deftest tp-layer-test-describe-data-reactive () "Describe data for a reactive layer reports format and dependencies." (tp-layer-tests--with-clean (setq tp-layer-test-b15-color "red") (define-tp tp-layer-test-dr () '(face (:foreground $tp-layer-test-b15-color))) (let ((data (tp--describe-layer-data 'tp-layer-test-dr))) (should (eq (plist-get data :format) 'reactive)) (should (equal (plist-get data :reactive-deps) '(tp-layer-test-b15-color)))))) (ert-deftest tp-layer-test-describe-data-group-and-transform () "Describe data reports the owning group and transform presence." (tp-layer-tests--with-clean (define-tps tp-layer-test-dg () '("a" :props (face bold) :transform upcase)) (let ((data (tp--describe-layer-data 'tp-layer-test-dg-a))) (should (eq (plist-get data :group) 'tp-layer-test-dg)) (should (plist-get data :transform))))) (ert-deftest tp-layer-test-describe-data-unknown-layer-nil () "Describe data returns nil for names not in tp-layer-alist." (tp-layer-tests--with-clean (should-not (tp--describe-layer-data 'tp-layer-test-nonexistent)))) (ert-deftest tp-layer-test-describe-layer-command () "tp-describe-layer is a command and renders a help buffer." (should (commandp 'tp-describe-layer)) (tp-layer-tests--with-clean (define-tp tp-layer-test-dc () '(face bold)) (save-window-excursion (tp-describe-layer 'tp-layer-test-dc) (with-current-buffer (help-buffer) (should (string-match-p "tp-layer-test-dc is a tp layer" (buffer-string))) (should (string-match-p "Storage format: unified" (buffer-string))))) (should-error (tp-describe-layer 'tp-layer-test-missing) :type 'user-error))) ;;; ARG-1: wrong-arity parameterized-layer calls signal clear errors (defmacro tp-layer-tests--with-colors (&rest body) "Run BODY with the two-parameter test layer tp-lt-colors defined." (declare (indent 0)) `(tp-layer-tests--with-clean (define-tp tp-lt-colors (fg bg) `(face (:foreground ,fg :background ,bg))) ,@body)) (ert-deftest tp-layer-test-props-with-args-missing-arg-errors () "tp-layer-props-with-args signals on fewer args than parameters. Since Emacs 27 `cl-progv' silently binds missing parameters to nil, so the old docstring's promised unbound-variable error could never fire; the arity is now checked explicitly (ARG-1)." (tp-layer-tests--with-colors (let ((err (should-error (tp-layer-props-with-args 'tp-lt-colors '("red"))))) ;; Parens are literal in Emacs regexps. (should (string-match-p "takes 2 argument(s), got 1" (cadr err)))) ;; Correct arity still works. (should (equal (tp-layer-props-with-args 'tp-lt-colors '("red" "blue")) '(face (:foreground "red" :background "blue")))) ;; Extra values are still ignored, per the documented contract. (should (equal (tp-layer-props-with-args 'tp-lt-colors '("red" "blue" "green")) '(face (:foreground "red" :background "blue")))))) (ert-deftest tp-layer-test-tp-set-flat-missing-arg-errors () "The flat tp-set form with too few layer args signals, not nil-binds. Before ARG-1, (tp-set \"s\" \\='(layer \"red\")) on a two-parameter layer silently produced (:foreground \"red\" :background nil)." (tp-layer-tests--with-colors (should-error (tp-set "s" '(tp-lt-colors "red"))))) (ert-deftest tp-layer-test-tp-set-flat-excess-arg-errors () "Flat-form excess positional args signal instead of corrupting props. Before ARG-1, the excess string fell into extra-props and was applied as a text-property KEY with value nil." (tp-layer-tests--with-colors (let ((err (should-error (tp-set "gg" '(tp-lt-colors "red" "blue" "green"))))) (should (string-match-p "excess argument" (cadr err)))) ;; Correct-arity flat form is unchanged. (should (equal (text-properties-at 0 (tp-set "ok" '(tp-lt-colors "red" "blue"))) '(face (:foreground "red" :background "blue")))) ;; Legitimate extra PROPS after the args still work. (should (equal (plist-get (text-properties-at 0 (tp-set "ok" '(tp-lt-colors "red" "blue" help-echo "tip"))) 'help-echo) "tip")) ;; The wrapped-args form with extra props is untouched as well. (should (equal (plist-get (text-properties-at 0 (tp-set "ok" '(tp-lt-colors ("red" "blue") help-echo "tip"))) 'help-echo) "tip")))) (ert-deftest tp-layer-test-stack-path-wrong-arity-clear-error () "The stack path signals a clear arity error, not \"Odd length ...\". Before ARG-1, (tp-push-layer s \\='(layer \"red\")) fell through tp--normalize-layer-spec's named-inline branch, producing the odd plist (\"red\" tp-name layer) and the cryptic error \"Odd length text property list\"." (tp-layer-tests--with-colors (let ((err (should-error (tp-push-layer (copy-sequence "st") '(tp-lt-colors "red"))))) (should (string-match-p "expects 2 args, got 1" (cadr err)))) (let ((err (should-error (tp--normalize-layer-spec '(tp-lt-colors "red"))))) (should (string-match-p "expects 2 args, got 1" (cadr err)))) ;; Correct arity through the stack path keeps the rendered facade ;; while authoritative metadata lives in stack storage. (let ((s (copy-sequence "st"))) (tp-push-layer s '(tp-lt-colors "red" "blue")) (should (equal (get-text-property 0 'face s) '(:foreground "red" :background "blue"))) (should (eq (get-text-property 0 'tp-name s) 'tp-lt-colors)) (let* ((entry (car (get-text-property 0 'tp-layers s))) (meta (plist-get entry 'tp-meta))) (should (equal (plist-get meta :args) '("red" "blue"))))))) ;;; API-SYM-01: public tp-group-props-with-args mirrors the layer pair (ert-deftest tp-layer-test-group-props-with-args-public () "The public plural group accessor matches the private path." (tp-layer-tests--with-clean (define-tps tp-layer-test-pgrp (fg w) `((face (:foreground ,fg))) `((face (:weight ,w)))) (should (equal (tp-group-props-with-args 'tp-layer-test-pgrp '("red" bold)) '((face (:foreground "red")) (face (:weight bold))))) (should (equal (tp-group-props-with-args 'tp-layer-test-pgrp '("red" bold)) (tp--group-props-with-args 'tp-layer-test-pgrp '("red" bold)))) (should (equal (tp-group-props-with-args 'tp-layer-test-pgrp '("red" bold) t) (tp--group-props-with-args 'tp-layer-test-pgrp '("red" bold) t))) ;; Non-parameterized or undefined groups return nil, like the ;; layer counterpart. (should-not (tp-group-props-with-args 'tp-layer-test-nope '("x"))))) ;;; API-NAME-02: prefix-conforming tp-define-* aliases (ert-deftest tp-layer-test-define-layer-alias () "tp-define-layer is a working macro alias of define-tp." (tp-layer-tests--with-clean (tp-define-layer tp-layer-test-alias-l () '(face bold)) (should (equal (tp-layer-props 'tp-layer-test-alias-l) '(face bold))) ;; Parameterized definitions work through the alias too. (tp-define-layer tp-layer-test-alias-p (color) `(face (:foreground ,color))) (should (equal (tp-layer-props-with-arg 'tp-layer-test-alias-p "red") '(face (:foreground "red")))))) (ert-deftest tp-layer-test-define-group-alias () "tp-define-group is a working macro alias of define-tps." (tp-layer-tests--with-clean (tp-define-layer tp-layer-test-alias-m () '(face italic)) (tp-define-group tp-layer-test-alias-g () 'tp-layer-test-alias-m '(face bold)) (should (assoc 'tp-layer-test-alias-g tp-layer-groups)) (should (equal (tp-group-props 'tp-layer-test-alias-g) '((face italic) (face bold)))))) (provide 'tp-layer-tests) ;;; tp-layer-tests.el ends here