Add canonical query semantics, managed metadata and transactions, overlay-aware lookup, reproducible benchmarks, and synchronized API documentation.
1305 lines
55 KiB
EmacsLisp
1305 lines
55 KiB
EmacsLisp
;;; tp-stack-tests.el --- ERT regression tests for tp-stack.el -*- lexical-binding: t -*-
|
|
|
|
;;; Commentary:
|
|
|
|
;; Regression tests for confirmed bugs fixed in the layer-stack module
|
|
;; (tp-stack.el). Each section is tagged with the canonical bug id it
|
|
;; guards against.
|
|
|
|
;;; Code:
|
|
|
|
(require 'ert)
|
|
(require 'tp)
|
|
|
|
(defmacro tp-stack-tests--with-env (&rest body)
|
|
"Run BODY in a temp buffer with a clean tp layer state.
|
|
Layer registries are reset before BODY and again afterwards so
|
|
definitions cannot leak between tests."
|
|
(declare (indent 0))
|
|
`(unwind-protect
|
|
(with-temp-buffer
|
|
(tp-layer-reset)
|
|
,@body)
|
|
(tp-layer-reset)))
|
|
|
|
(defun tp-stack-tests--has-prop-p (pos prop &optional object)
|
|
"Return non-nil if PROP is present (even with value nil) at POS of OBJECT."
|
|
(and (plist-member (text-properties-at pos object) prop) t))
|
|
|
|
;;; B28: region ops must not mutate text outside [START, END)
|
|
|
|
(ert-deftest tp-stack-test-delete-layer-subregion-keeps-outside ()
|
|
"Deleting a layer on a sub-region leaves the rest of the stack alone."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdefghij")
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(tp-push-layer 1 11 'layer1)
|
|
(tp-push-layer 1 11 'layer2)
|
|
(tp-delete-layer 3 6 'layer2)
|
|
;; Inside [3, 6): layer2 gone, layer1 now on top.
|
|
(should (eq (get-text-property 3 'tp-name) 'layer1))
|
|
(should (eq (get-text-property 5 'tp-name) 'layer1))
|
|
(should-not (tp-layer-exists-p 3 6 'layer2))
|
|
;; Outside the region: the full 2-layer stack survives.
|
|
(should (eq (get-text-property 1 'tp-name) 'layer2))
|
|
(should (eq (get-text-property 2 'tp-name) 'layer2))
|
|
(should (eq (get-text-property 6 'tp-name) 'layer2))
|
|
(should (eq (get-text-property 10 'tp-name) 'layer2))
|
|
(should (tp-layer-exists-p 1 3 'layer1))
|
|
(should (tp-layer-exists-p 6 11 'layer1))))
|
|
|
|
(ert-deftest tp-stack-test-push-layer-subregion-keeps-outside ()
|
|
"Pushing onto a sub-region does not smear over the whole interval."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdefghij")
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(tp-push-layer 1 4 'layer1)
|
|
(tp-push-layer 3 8 'layer2)
|
|
;; [1, 3): still only layer1.
|
|
(should (eq (get-text-property 1 'tp-name) 'layer1))
|
|
(should (eq (get-text-property 2 'tp-name) 'layer1))
|
|
(should-not (tp-layer-exists-p 1 3 'layer2))
|
|
;; [3, 4): layer2 stacked over layer1.
|
|
(should (eq (get-text-property 3 'tp-name) 'layer2))
|
|
(should (tp-layer-exists-p 3 4 'layer1))
|
|
;; [4, 8): only layer2.
|
|
(should (eq (get-text-property 5 'tp-name) 'layer2))
|
|
(should-not (tp-layer-exists-p 4 8 'layer1))
|
|
;; [8, 11): untouched bare text.
|
|
(should (null (text-properties-at 8)))
|
|
(should (null (text-properties-at 10)))))
|
|
|
|
(ert-deftest tp-stack-test-push-layer-subregion-string ()
|
|
"Region-form push on a string only affects the requested sub-range."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-put-layer 2 5 'layer1 0 str)
|
|
(should (null (text-properties-at 0 str)))
|
|
(should (null (text-properties-at 1 str)))
|
|
(should (eq (get-text-property 2 'tp-name str) 'layer1))
|
|
(should (eq (get-text-property 4 'tp-name str) 'layer1))
|
|
(should (null (text-properties-at 5 str))))))
|
|
|
|
;;; B29: tp-put-layer must be region-local, not whole-object
|
|
|
|
(ert-deftest tp-stack-test-put-layer-bare-region-distant-props ()
|
|
"Putting a layer on a bare region ignores properties elsewhere."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdefghij")
|
|
(define-tp layer1 () '(face bold))
|
|
(put-text-property 8 10 'help-echo "far")
|
|
(tp-push-layer 1 4 'layer1)
|
|
;; The layer covers exactly [1, 4).
|
|
(should (eq (get-text-property 1 'tp-name) 'layer1))
|
|
(should (eq (get-text-property 3 'tp-name) 'layer1))
|
|
(should (null (text-properties-at 4)))
|
|
(should (null (text-properties-at 7)))
|
|
;; The distant properties are untouched.
|
|
(should (equal (get-text-property 8 'help-echo) "far"))
|
|
(should (null (get-text-property 8 'tp-name)))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-same-result-with-or-without-distant-props ()
|
|
"Distant properties do not change the public managed stack."
|
|
(tp-stack-tests--with-env
|
|
(define-tp layer1 () '(face bold))
|
|
(let (props-bare props-distant)
|
|
(with-temp-buffer
|
|
(insert "abcdefghij")
|
|
(tp-push-layer 1 4 'layer1)
|
|
(setq props-bare (tp-layer-stack-at 1)))
|
|
(with-temp-buffer
|
|
(insert "abcdefghij")
|
|
(put-text-property 8 10 'help-echo "far")
|
|
(tp-push-layer 1 4 'layer1)
|
|
(setq props-distant (tp-layer-stack-at 1)))
|
|
(should (equal props-bare props-distant)))))
|
|
|
|
;;; B30: inline plists with ordinary (non-keyword) properties
|
|
|
|
(ert-deftest tp-stack-test-put-layer-inline-plist-plain ()
|
|
"An inline plist like (face bold) is a valid layer spec."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(tp-put-layer 1 6 '(face bold) 0)
|
|
(should (eq (get-text-property 1 'face) 'bold))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-inline-plist-nested ()
|
|
"An inline plist with a nested value list is a valid layer spec."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(tp-put-layer 1 6 '(face (:foreground "red")) 0)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "red")))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-inline-plist-multi-pair ()
|
|
"A multi-pair inline plist is applied as one layer."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(tp-put-layer 1 6 '(face bold help-echo "tip") 0)
|
|
(should (eq (get-text-property 1 'face) 'bold))
|
|
(should (equal (get-text-property 1 'help-echo) "tip"))
|
|
(should (= (tp-layer-count 1 6) 1))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-named-inline-still-works ()
|
|
"A named inline layer (NAME PROP VAL ...) keeps its old meaning."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(tp-put-layer 1 6 '(mylayer face bold) 0)
|
|
(should (eq (get-text-property 1 'tp-name) 'mylayer))
|
|
(should (eq (get-text-property 1 'face) 'bold))))
|
|
|
|
;;; B31: list of layer names
|
|
|
|
(ert-deftest tp-stack-test-put-layer-list-of-names ()
|
|
"A list of defined layer names pushes each as its own layer."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer-a () '(face bold))
|
|
(define-tp layer-b () '(help-echo "b"))
|
|
(tp-put-layer 1 5 '(layer-a layer-b) 0)
|
|
(should (= (tp-layer-count 1 5) 2))
|
|
(should (eq (tp-layer-top 1 5) 'layer-a))
|
|
(should (tp-layer-exists-p 1 5 'layer-a))
|
|
(should (tp-layer-exists-p 1 5 'layer-b))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-mixed-list ()
|
|
"A list mixing a layer name and an inline plist works."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer-a () '(face bold))
|
|
(tp-put-layer 1 5 '(layer-a (help-echo "inline")) 0)
|
|
(should (= (tp-layer-count 1 5) 2))
|
|
(should (eq (tp-layer-top 1 5) 'layer-a))))
|
|
|
|
;;; B32: parameterized groups
|
|
|
|
(ert-deftest tp-stack-test-put-layer-parameterized-group ()
|
|
"A (GROUP-NAME ARG) spec resolves a parameterized group."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp pcolor (c) `(face (:foreground ,c)))
|
|
(define-tps pgroup (c) `(pcolor ,c))
|
|
(tp-put-layer 1 6 '(pgroup "red") 0)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "red")))
|
|
(should (eq (get-text-property 1 'tp-name) 'pcolor))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-parameterized-group-without-arg-errors ()
|
|
"A bare parameterized group name signals instead of silently no-oping."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp pcolor (c) `(face (:foreground ,c)))
|
|
(define-tps pgroup (c) `(pcolor ,c))
|
|
(should-error (tp-put-layer 1 6 'pgroup 0))))
|
|
|
|
;;; B33: tp-region-layer-props string positions
|
|
|
|
(ert-deftest tp-stack-test-region-layer-props-string-subrange ()
|
|
"String sub-range queries return absolute in-bounds string positions."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer str 'layer1)
|
|
(let ((result (tp-region-layer-props 2 5 'layer1 str)))
|
|
(should (= (length result) 1))
|
|
(should (= (nth 0 (car result)) 2))
|
|
(should (= (nth 1 (car result)) 5))
|
|
(should (<= (nth 1 (car result)) (length str)))
|
|
(should (eq (plist-get (nth 2 (car result)) 'tp-name) 'layer1))))))
|
|
|
|
(ert-deftest tp-stack-test-region-layer-props-buffer-subrange ()
|
|
"Buffer queries return 1-based positions clipped to the region."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer 1 7 'layer1)
|
|
(let ((result (tp-region-layer-props 2 4 'layer1)))
|
|
(should (equal (list (nth 0 (car result)) (nth 1 (car result)))
|
|
'(2 4))))))
|
|
|
|
;;; string/buffer path convergence for region-form mutators
|
|
|
|
(ert-deftest tp-stack-test-delete-layer-string-region-form ()
|
|
"Region-form delete on a string works and stays inside the range."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(tp-push-layer str 'layer1)
|
|
(tp-push-layer str 'layer2)
|
|
(tp-delete-layer 2 5 'layer2 str)
|
|
(should (eq (get-text-property 2 'tp-name str) 'layer1))
|
|
(should (eq (get-text-property 4 'tp-name str) 'layer1))
|
|
;; Outside [2, 5) both layers survive.
|
|
(should (eq (get-text-property 0 'tp-name str) 'layer2))
|
|
(should (eq (get-text-property 5 'tp-name str) 'layer2))
|
|
(should (tp-layer-exists-p 0 2 'layer1 str))
|
|
(should (tp-layer-exists-p 5 6 'layer1 str)))))
|
|
|
|
;;; B34: explicit nil values survive merge/flatten precedence
|
|
|
|
(ert-deftest tp-stack-test-merge-layers-explicit-nil-wins ()
|
|
"An explicitly-nil value in a higher-precedence layer is kept."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face nil help-echo "u"))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(tp-merge-layers 1 6 'merged '(upper lower))
|
|
(should (tp-stack-tests--has-prop-p 1 'face))
|
|
(should (null (get-text-property 1 'face)))
|
|
(should (equal (get-text-property 1 'help-echo) "u"))
|
|
(should (eq (get-text-property 1 'tp-name) 'merged))))
|
|
|
|
(ert-deftest tp-stack-test-flatten-layers-explicit-nil-wins ()
|
|
"Flattening keeps an explicit nil from a higher layer over lower values."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold help-echo "low"))
|
|
(define-tp upper () '(face nil))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(tp-flatten-layers 1 6 'flat)
|
|
(should (tp-stack-tests--has-prop-p 1 'face))
|
|
(should (null (get-text-property 1 'face)))
|
|
(should (equal (get-text-property 1 'help-echo) "low"))
|
|
(should (eq (get-text-property 1 'tp-name) 'flat))))
|
|
|
|
;;; B35: a single managed layer has non-nil authoritative storage
|
|
|
|
(ert-deftest tp-stack-test-single-layer-has-authoritative-storage ()
|
|
"Pushing one layer stores one metadata entry, never tp-layers nil."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer 1 6 'layer1)
|
|
(should (= (length (get-text-property 1 'tp-layers)) 1))
|
|
(should (plist-get (car (get-text-property 1 'tp-layers)) 'tp-meta))
|
|
(should-not (plist-member (tp-layer-stack-at 1) 'tp-meta))
|
|
(should (eq (get-text-property 1 'face) 'bold))))
|
|
|
|
(ert-deftest tp-stack-test-delete-to-single-layer-keeps-metadata-storage ()
|
|
"Deleting down to one layer keeps one authoritative metadata entry."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(tp-push-layer 1 6 'layer1)
|
|
(tp-push-layer 1 6 'layer2)
|
|
;; With two layers the below-stack is a real, non-nil list.
|
|
(should (get-text-property 1 'tp-layers))
|
|
(tp-delete-layer 1 6 'layer2)
|
|
(should (= (length (get-text-property 1 'tp-layers)) 1))
|
|
(should (eq (get-text-property 1 'tp-name) 'layer1))))
|
|
|
|
(ert-deftest tp-stack-test-pop-to-single-layer-keeps-metadata-storage ()
|
|
"Popping down to one layer keeps one authoritative metadata entry."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(tp-push-layer str 'layer1)
|
|
(tp-push-layer str 'layer2)
|
|
(tp-pop-layer str)
|
|
(should (= (length (get-text-property 0 'tp-layers str)) 1))
|
|
(should (eq (get-text-property 0 'tp-name str) 'layer1)))))
|
|
|
|
(ert-deftest tp-stack-test-absent-tp-layers-tolerated-by-stack-ops ()
|
|
"Stacks without a tp-layers property still work with every operation."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(tp-push-layer 1 6 'layer1) ; single layer, no tp-layers prop
|
|
(should (= (tp-layer-count 1 6) 1))
|
|
(should (equal (tp-layer-list 1 6) '(layer1)))
|
|
(should (tp-layer-exists-p 1 6 'layer1))
|
|
(should (eq (tp-layer-top 1 6) 'layer1))
|
|
(tp-push-layer 1 6 'layer2) ; stacking on top still works
|
|
(should (= (tp-layer-count 1 6) 2))
|
|
(should (eq (tp-layer-top 1 6) 'layer2))
|
|
(should (tp-layer-exists-p 1 6 'layer1))))
|
|
|
|
;;; B36: tp-layer-top respects the whole region
|
|
|
|
(ert-deftest tp-stack-test-layer-top-mid-region-layer ()
|
|
"A layer starting after bare text is still found by tp-layer-top."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer 3 6 'layer1)
|
|
(should (eq (tp-layer-top 1 6) 'layer1))))
|
|
|
|
(ert-deftest tp-stack-test-layer-top-respects-end ()
|
|
"tp-layer-top does not report layers that lie beyond END."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer 4 6 'layer1)
|
|
(should (null (tp-layer-top 1 3)))))
|
|
|
|
(ert-deftest tp-stack-test-layer-top-first-named-run-wins ()
|
|
"The first run with a named top layer determines the result."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer-a () '(face bold))
|
|
(define-tp layer-b () '(face italic))
|
|
(tp-push-layer 1 3 'layer-a)
|
|
(tp-push-layer 3 6 'layer-b)
|
|
(should (eq (tp-layer-top 1 6) 'layer-a))
|
|
(should (eq (tp-layer-top 3 6) 'layer-b))))
|
|
|
|
;;; Shared argument normalizer: both calling conventions still work
|
|
|
|
(ert-deftest tp-stack-test-normalizer-string-forms ()
|
|
"Whole-string forms of the routed mutators behave as before."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp layer1 () '(face bold))
|
|
(define-tp layer2 () '(face italic))
|
|
(define-tp layer3 () '(face underline))
|
|
(tp-push-layer str 'layer1)
|
|
(tp-push-layer str 'layer2)
|
|
(tp-push-layer str 'layer3)
|
|
(should (eq (tp-layer-top 0 6 str) 'layer3))
|
|
(tp-rotate-layer str)
|
|
(should (eq (tp-layer-top 0 6 str) 'layer2))
|
|
(tp-pin-layer str 'layer1)
|
|
(should (eq (tp-layer-top 0 6 str) 'layer1))
|
|
(tp-pop-layer str)
|
|
(should (eq (tp-layer-top 0 6 str) 'layer2))
|
|
(tp-delete-layer str 'layer3)
|
|
(should (equal (tp-layer-list 0 6 str) '(layer2)))
|
|
(should (eq (tp-push-layer str 'layer1) str)))))
|
|
|
|
(ert-deftest tp-stack-test-normalizer-invalid-first-arg-signals ()
|
|
"A non-string, non-number first argument signals a clear error."
|
|
(tp-stack-tests--with-env
|
|
(define-tp layer1 () '(face bold))
|
|
(should-error (tp-push-layer nil 'layer1))
|
|
(should-error (tp-delete-layer 'not-a-position 5 'layer1))))
|
|
|
|
;;; 0.3.0 S1: layer visibility (tp-hide-layer / tp-show-layer)
|
|
|
|
(ert-deftest tp-stack-test-hide-top-reveals-next-visible ()
|
|
"Hiding the top layer renders the next visible layer's properties."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face italic))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(should (= (tp-hide-layer 1 6 'upper) 1))
|
|
;; The text now renders the lower layer.
|
|
(should (eq (get-text-property 1 'face) 'bold))
|
|
(should (eq (get-text-property 1 'tp-name) 'lower))
|
|
;; The hidden layer is still in the stack for the queries.
|
|
(should (= (tp-layer-count 1 6) 2))
|
|
(should (equal (tp-layer-list 1 6) '(upper lower)))
|
|
(should (tp-layer-exists-p 1 6 'upper))))
|
|
|
|
(ert-deftest tp-stack-test-show-restores-hidden-top ()
|
|
"Showing a hidden top layer restores its properties onto the text."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face italic))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(tp-hide-layer 1 6 'upper)
|
|
(should (= (tp-show-layer 1 6 'upper) 1))
|
|
(should (eq (get-text-property 1 'face) 'italic))
|
|
(should (eq (get-text-property 1 'tp-name) 'upper))
|
|
;; No bookkeeping flag leaks into the rendered properties.
|
|
(should-not (tp-stack-tests--has-prop-p 1 'tp-hidden))))
|
|
|
|
(ert-deftest tp-stack-test-hide-all-layers-contract ()
|
|
"With every layer hidden only the tp-layers bookkeeping remains."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face italic))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(should (= (tp-hide-layer 1 6 'upper) 1))
|
|
(should (= (tp-hide-layer 1 6 'lower) 1))
|
|
;; No layer props render, not even tp-name.
|
|
(should (null (get-text-property 1 'face)))
|
|
(should (null (get-text-property 1 'tp-name)))
|
|
(should (tp-stack-tests--has-prop-p 1 'tp-layers))
|
|
;; The whole stack stays queryable.
|
|
(should (= (tp-layer-count 1 6) 2))
|
|
(should (equal (tp-layer-list 1 6) '(upper lower)))
|
|
;; Showing one layer again renders it.
|
|
(should (= (tp-show-layer 1 6 'lower) 1))
|
|
(should (eq (get-text-property 1 'face) 'bold))
|
|
(should (eq (get-text-property 1 'tp-name) 'lower))))
|
|
|
|
(ert-deftest tp-stack-test-hide-missing-name-is-silent-noop ()
|
|
"Hiding or showing a non-existent layer returns 0 without signaling."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer 1 6 'layer1)
|
|
(let ((before (text-properties-at 1)))
|
|
(should (= (tp-hide-layer 1 6 'nope) 0))
|
|
(should (= (tp-show-layer 1 6 'nope) 0))
|
|
(should (equal (text-properties-at 1) before)))))
|
|
|
|
(ert-deftest tp-stack-test-hide-already-hidden-returns-zero ()
|
|
"Hiding an already-hidden layer (or showing a visible one) counts 0."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face italic))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(should (= (tp-show-layer 1 6 'upper) 0)) ; visible already
|
|
(should (= (tp-hide-layer 1 6 'upper) 1))
|
|
(should (= (tp-hide-layer 1 6 'upper) 0)) ; hidden already
|
|
(should (eq (get-text-property 1 'face) 'bold))))
|
|
|
|
(ert-deftest tp-stack-test-hide-string-forms ()
|
|
"Whole-string and region-on-string forms of hide/show work 0-based."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face italic))
|
|
(tp-push-layer str 'lower)
|
|
(tp-push-layer str 'upper)
|
|
(should (= (tp-hide-layer str 'upper) 1))
|
|
(should (eq (get-text-property 0 'tp-name str) 'lower))
|
|
(should (= (tp-show-layer 0 6 'upper str) 1))
|
|
(should (eq (get-text-property 0 'tp-name str) 'upper))
|
|
;; Region form only touches [2, 5).
|
|
(should (= (tp-hide-layer 2 5 'upper str) 1))
|
|
(should (eq (get-text-property 0 'tp-name str) 'upper))
|
|
(should (eq (get-text-property 2 'tp-name str) 'lower))
|
|
(should (eq (get-text-property 5 'tp-name str) 'upper)))))
|
|
|
|
(ert-deftest tp-stack-test-show-layer-above-visible-top ()
|
|
"Showing a hidden layer above the visible top makes it render again."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc)
|
|
(tp-hide-layer 1 6 'lc)
|
|
(tp-hide-layer 1 6 'lb)
|
|
(should (eq (get-text-property 1 'tp-name) 'la))
|
|
;; lc sits above the visible top (la); showing it wins again.
|
|
(should (= (tp-show-layer 1 6 'lc) 1))
|
|
(should (eq (get-text-property 1 'tp-name) 'lc))
|
|
(should (eq (get-text-property 1 'face) 'underline))))
|
|
|
|
(ert-deftest tp-stack-test-hidden-layer-can-be-raised ()
|
|
"A hidden layer can be moved in the stack and shown later."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-hide-layer 1 6 'la) ; hide the bottom layer
|
|
(should (= (tp-raise-layer 1 6 'la 1) 1))
|
|
;; la is now on top but hidden, so lb still renders.
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lb)))
|
|
(should (eq (get-text-property 1 'tp-name) 'lb))
|
|
(should (= (tp-show-layer 1 6 'la) 1))
|
|
(should (eq (get-text-property 1 'tp-name) 'la))
|
|
(should (eq (get-text-property 1 'face) 'bold))))
|
|
|
|
(ert-deftest tp-stack-test-hide-show-roundtrip-restores-storage ()
|
|
"A hide/show roundtrip restores the exact metadata-backed storage."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp layer1 () '(face bold))
|
|
(tp-push-layer 1 6 'layer1)
|
|
(let ((before (text-properties-at 1)))
|
|
(tp-hide-layer 1 6 'layer1)
|
|
;; All layers hidden: only bookkeeping remains.
|
|
(should (null (get-text-property 1 'tp-name)))
|
|
(tp-show-layer 1 6 'layer1)
|
|
(should (equal (text-properties-at 1) before))
|
|
(should (= (length (get-text-property 1 'tp-layers)) 1)))))
|
|
|
|
(ert-deftest tp-stack-test-hidden-direct-edit-signals-before-stack-write ()
|
|
"A hidden-range direct edit raises a conflict instead of being discarded."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp tp-st-a10-layer () '(face bold))
|
|
(tp-push-layer 1 6 'tp-st-a10-layer)
|
|
(tp-hide-layer 1 6 'tp-st-a10-layer)
|
|
(put-text-property 1 6 'help-echo "external")
|
|
(let ((before (text-properties-at 1)))
|
|
(should-error (tp-show-layer 1 6 'tp-st-a10-layer)
|
|
:type 'tp-layer-conflict)
|
|
(should (equal (text-properties-at 1) before))
|
|
(should (equal (get-text-property 1 'help-echo) "external")))))
|
|
|
|
(ert-deftest tp-stack-test-flatten-drops-tp-hidden-flag ()
|
|
"Flattening a stack with a hidden layer never leaks the tp-hidden flag.
|
|
HID-2: the hidden layer's props are discarded entirely, so the
|
|
flattened result renders the visible layer's face, not the hidden
|
|
one's."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp lower () '(face bold))
|
|
(define-tp upper () '(face italic))
|
|
(tp-push-layer 1 6 'lower)
|
|
(tp-push-layer 1 6 'upper)
|
|
(tp-hide-layer 1 6 'upper)
|
|
(should (= (tp-flatten-layers 1 6 'flat) 1))
|
|
(should (eq (get-text-property 1 'tp-name) 'flat))
|
|
(should-not (tp-stack-tests--has-prop-p 1 'tp-hidden))
|
|
(should-not (tp-stack-tests--has-prop-p 1 'tp-layers))
|
|
;; The visible layer's face renders; the hidden italic is gone.
|
|
(should (eq (get-text-property 1 'face) 'bold))))
|
|
|
|
;;; HID-2: flatten/merge must not render hidden layers' properties
|
|
|
|
(ert-deftest tp-stack-test-flatten-discards-hidden-layer-props ()
|
|
"Flatten discards a hidden layer's props instead of rendering them.
|
|
Probe scenario A: red (hidden, with help-echo) over green over blue
|
|
\(with mouse-face); the flattened result must show green and keep
|
|
blue's mouse-face, with no trace of the hidden red layer."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp tp-st-h2-red () '(face (:foreground "red") help-echo "red"))
|
|
(define-tp tp-st-h2-green () '(face (:foreground "green")))
|
|
(define-tp tp-st-h2-blue () '(face (:foreground "blue")
|
|
mouse-face highlight))
|
|
(tp-push-layer 1 6 'tp-st-h2-blue)
|
|
(tp-push-layer 1 6 'tp-st-h2-green)
|
|
(tp-push-layer 1 6 'tp-st-h2-red) ; top->bottom: red green blue
|
|
(tp-hide-layer 1 6 'tp-st-h2-red)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "green")))
|
|
(should (= (tp-flatten-layers 1 6 'flat) 1))
|
|
(should (equal (get-text-property 1 'face) '(:foreground "green")))
|
|
(should-not (tp-stack-tests--has-prop-p 1 'help-echo))
|
|
(should (eq (get-text-property 1 'mouse-face) 'highlight))))
|
|
|
|
(ert-deftest tp-stack-test-flatten-all-hidden-yields-bare-text ()
|
|
"Flattening a run whose every layer is hidden clears all properties.
|
|
Consistent with the all-hidden rendering of `tp-hide-layer'; the run
|
|
still counts as modified in the returned count."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp tp-st-h2a-one () '(face bold))
|
|
(define-tp tp-st-h2a-two () '(face italic))
|
|
(tp-push-layer 1 6 'tp-st-h2a-one)
|
|
(tp-push-layer 1 6 'tp-st-h2a-two)
|
|
(tp-hide-layer 1 6 'tp-st-h2a-one)
|
|
(tp-hide-layer 1 6 'tp-st-h2a-two)
|
|
(should (= (tp-flatten-layers 1 6 'flat) 1))
|
|
(should (null (text-properties-at 1)))))
|
|
|
|
(ert-deftest tp-stack-test-merge-excludes-hidden-layer-props ()
|
|
"Merging a hidden layer with a visible one excludes the hidden props.
|
|
Probe scenario B: merging hidden red with visible green removes both
|
|
from the stack but the merged layer renders green - a merge must
|
|
never un-hide what `tp-hide-layer' hid."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp tp-st-h2b-red () '(face (:foreground "red")))
|
|
(define-tp tp-st-h2b-green () '(face (:foreground "green")))
|
|
(define-tp tp-st-h2b-blue () '(face (:foreground "blue")))
|
|
(tp-push-layer 1 6 'tp-st-h2b-blue)
|
|
(tp-push-layer 1 6 'tp-st-h2b-green)
|
|
(tp-push-layer 1 6 'tp-st-h2b-red)
|
|
(tp-hide-layer 1 6 'tp-st-h2b-red)
|
|
(should (= (tp-merge-layers 1 6 'merged '(tp-st-h2b-red tp-st-h2b-green))
|
|
1))
|
|
(should (equal (get-text-property 1 'face) '(:foreground "green")))
|
|
(should (eq (get-text-property 1 'tp-name) 'merged))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1))
|
|
'(merged tp-st-h2b-blue)))))
|
|
|
|
(ert-deftest tp-stack-test-merge-all-hidden-stays-hidden ()
|
|
"Merging only hidden layers produces a hidden merged layer.
|
|
The merged layer keeps the hidden layers' merged props (data is
|
|
preserved) but carries tp-hidden itself, so nothing starts rendering;
|
|
`tp-show-layer' can reveal it later."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp tp-st-h2c-red () '(face (:foreground "red")))
|
|
(define-tp tp-st-h2c-green () '(face (:foreground "green")))
|
|
(define-tp tp-st-h2c-blue () '(face (:foreground "blue")))
|
|
(tp-push-layer 1 6 'tp-st-h2c-blue)
|
|
(tp-push-layer 1 6 'tp-st-h2c-green)
|
|
(tp-push-layer 1 6 'tp-st-h2c-red)
|
|
(tp-hide-layer 1 6 'tp-st-h2c-red)
|
|
(tp-hide-layer 1 6 'tp-st-h2c-green)
|
|
(should (= (tp-merge-layers 1 6 'merged
|
|
'(tp-st-h2c-red tp-st-h2c-green))
|
|
1))
|
|
;; The merged layer does not render: blue stays visible.
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))
|
|
;; It is present, hidden, and carries the merged (red-wins) props.
|
|
(let ((entry (assq 'merged (tp-layer-stack-at 1))))
|
|
(should entry)
|
|
(should (eq (plist-get (cdr entry) 'tp-hidden) t))
|
|
(should (equal (plist-get (cdr entry) 'face) '(:foreground "red"))))
|
|
;; Showing the merged layer renders it.
|
|
(tp-show-layer 1 6 'merged)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "red")))))
|
|
|
|
;;; HID2-RET: merge/flatten return modified-run counts
|
|
|
|
(ert-deftest tp-stack-test-merge-and-flatten-return-counts ()
|
|
"tp-merge-layers / tp-flatten-layers return modified-run counts.
|
|
Counting matches `tp-delete-layer': one per rewritten run, 0 when
|
|
nothing matched."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdefghij")
|
|
(define-tp tp-st-ret-a () '(face bold))
|
|
(define-tp tp-st-ret-b () '(face italic))
|
|
;; Two separate runs with different stacks.
|
|
(tp-push-layer 1 4 'tp-st-ret-a)
|
|
(tp-push-layer 1 4 'tp-st-ret-b)
|
|
(tp-push-layer 5 8 'tp-st-ret-a)
|
|
;; Merge matches both layers in run 1, only one in run 2: both
|
|
;; runs are rewritten.
|
|
(should (= (tp-merge-layers 1 8 'm '(tp-st-ret-a tp-st-ret-b)) 2))
|
|
;; Nothing matches on bare text.
|
|
(should (= (tp-merge-layers 8 11 'm2 '(tp-st-ret-a)) 0))
|
|
;; Flatten counts every run that had layers ([1,4) and [5,8) are
|
|
;; separated by bare text); bare text does not count.
|
|
(should (= (tp-flatten-layers 1 8 'flat) 2))
|
|
(should (= (tp-flatten-layers 8 11 'flat2) 0))))
|
|
|
|
;;; 0.3.0 S2: tp-lower-layer and extended tp-rotate-layer
|
|
|
|
(ert-deftest tp-stack-test-lower-layer-moves-down ()
|
|
"Lowering by 1 swaps the layer with the one below it."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc) ; top->bottom: lc lb la
|
|
(should (= (tp-lower-layer 1 6 'lc 1) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lb lc la)))
|
|
(should (eq (get-text-property 1 'tp-name) 'lb))))
|
|
|
|
(ert-deftest tp-stack-test-lower-layer-mirrors-raise ()
|
|
"Lowering then raising by the same N restores the stack order."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc)
|
|
(let ((before (mapcar #'car (tp-layer-stack-at 1))))
|
|
(tp-lower-layer 1 6 'lc 2)
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lb la lc)))
|
|
(tp-raise-layer 1 6 'lc 2)
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) before)))))
|
|
|
|
(ert-deftest tp-stack-test-lower-layer-clamps-and-negates ()
|
|
"Lowering clamps at the bottom; a negative N raises instead."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc)
|
|
(should (= (tp-lower-layer 1 6 'lc 99) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lb la lc)))
|
|
(should (= (tp-lower-layer 1 6 'lc -2) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))))
|
|
|
|
(ert-deftest tp-stack-test-lower-layer-defaults-and-index ()
|
|
"N defaults to 1 and integer indexes address the stack (0 = top)."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer str 'la)
|
|
(tp-push-layer str 'lb) ; top->bottom: lb la
|
|
(should (= (tp-lower-layer str 0) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(la lb)))
|
|
(should (eq (get-text-property 0 'tp-name str) 'la)))))
|
|
|
|
(ert-deftest tp-stack-test-lower-layer-missing-returns-zero ()
|
|
"Lowering a non-existent layer is a silent no-op returning 0."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(tp-push-layer 1 6 'la)
|
|
(let ((before (text-properties-at 1)))
|
|
(should (= (tp-lower-layer 1 6 'nope 1) 0))
|
|
(should (equal (text-properties-at 1) before)))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-default-unchanged ()
|
|
"With no new arguments rotate still moves the top layer to bottom."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc) ; top->bottom: lc lb la
|
|
(should (= (tp-rotate-layer 1 6) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lb la lc)))
|
|
(should (eq (get-text-property 1 'tp-name) 'lb))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-up-inverts-down ()
|
|
"Rotating up moves the bottom layer to the top; up undoes down."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc)
|
|
(should (= (tp-rotate-layer 1 6 nil 'up) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lc lb)))
|
|
(should (= (tp-rotate-layer 1 6 nil 'down) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-count-and-wraparound ()
|
|
"COUNT rotates several steps; a full cycle restores the order."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc)
|
|
(should (= (tp-rotate-layer 1 6 nil 'down 2) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lc lb)))
|
|
(should (= (tp-rotate-layer 1 6 nil 'up 2) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))
|
|
(should (= (tp-rotate-layer 1 6 nil 'down 3) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-string-form-direction ()
|
|
"String form accepts DIRECTION and COUNT right after the string."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer str 'la)
|
|
(tp-push-layer str 'lb) ; top->bottom: lb la
|
|
(should (= (tp-rotate-layer str 'up) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(la lb)))
|
|
(should (= (tp-rotate-layer str 'down 1) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(lb la))))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-edge-arguments ()
|
|
"Invalid DIRECTION signals; COUNT below 1 and bare text return 0."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(tp-push-layer 1 4 'la)
|
|
(should-error (tp-rotate-layer 1 4 nil 'sideways))
|
|
(should (= (tp-rotate-layer 1 4 nil 'down 0) 0))
|
|
(should (= (tp-rotate-layer 4 6) 0))
|
|
(should (eq (get-text-property 1 'tp-name) 'la))))
|
|
|
|
;;; API-ARG-01: canonical (START END DIRECTION COUNT OBJECT) rotate order
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-canonical-order ()
|
|
"The canonical order needs no nil OBJECT placeholder."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(define-tp lc () '(face underline))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-push-layer 1 6 'lc) ; top->bottom: lc lb la
|
|
(should (= (tp-rotate-layer 1 6 'up) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lc lb)))
|
|
(should (= (tp-rotate-layer 1 6 'down) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))
|
|
;; COUNT rides fourth in the canonical order.
|
|
(should (= (tp-rotate-layer 1 6 'down 2) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lc lb)))
|
|
(should (= (tp-rotate-layer 1 6 'up 2) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lc lb la)))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-canonical-order-object-last ()
|
|
"OBJECT rides last in the canonical order (buffer and string)."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb) ; top->bottom: lb la
|
|
(let ((buf (current-buffer)))
|
|
(with-temp-buffer ; a different current buffer
|
|
(should (= (tp-rotate-layer 1 6 'up 1 buf) 1))))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lb)))
|
|
;; nil COUNT in the canonical order still defaults to 1.
|
|
(let ((buf (current-buffer)))
|
|
(with-temp-buffer
|
|
(should (= (tp-rotate-layer 1 6 'down nil buf) 1))))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(lb la)))
|
|
;; A string OBJECT in the canonical order's last slot.
|
|
(let ((str (copy-sequence "xyz")))
|
|
(tp-push-layer str 'la)
|
|
(tp-push-layer str 'lb)
|
|
(should (= (tp-rotate-layer 0 3 'up 1 str) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(la lb))))))
|
|
|
|
(ert-deftest tp-stack-test-rotate-layer-legacy-order-still-works ()
|
|
"The legacy (START END OBJECT DIRECTION COUNT) order keeps working.
|
|
A non-up/down third argument - nil, a buffer or a string - still
|
|
selects the legacy order."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer str 'la)
|
|
(tp-push-layer str 'lb) ; top->bottom: lb la
|
|
(should (= (tp-rotate-layer 0 6 str 'up 1) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 0 str)) '(la lb))))
|
|
(insert "abcdef")
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(should (= (tp-rotate-layer 1 6 nil 'up 1) 1))
|
|
(should (equal (mapcar #'car (tp-layer-stack-at 1)) '(la lb)))
|
|
;; Canonical-order direction errors still signal.
|
|
(should-error (tp-rotate-layer 1 6 'sideways))))
|
|
|
|
;;; 0.3.0 S3: tp-layer-stack-at
|
|
|
|
(ert-deftest tp-stack-test-layer-stack-at-shape ()
|
|
"The stack at a position is (NAME . PROPS) conses, top first."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(should (equal (tp-layer-stack-at 1)
|
|
'((lb . (face italic))
|
|
(la . (face bold)))))))
|
|
|
|
(ert-deftest tp-stack-test-layer-stack-at-hidden-marker ()
|
|
"Hidden layers carry a tp-hidden t entry in their PROPS."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(tp-hide-layer 1 6 'lb)
|
|
(let ((stack (tp-layer-stack-at 1)))
|
|
(should (equal (mapcar #'car stack) '(lb la)))
|
|
(should (eq (plist-get (cdr (nth 0 stack)) 'tp-hidden) t))
|
|
(should-not (plist-member (cdr (nth 1 stack)) 'tp-hidden)))))
|
|
|
|
(ert-deftest tp-stack-test-layer-stack-at-string-positions ()
|
|
"String positions are 0-based; outside the layer the stack is nil."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp la () '(face bold))
|
|
(tp-put-layer 2 5 'la 0 str)
|
|
(should (null (tp-layer-stack-at 0 str)))
|
|
(should (equal (tp-layer-stack-at 2 str) '((la . (face bold)))))
|
|
(should (null (tp-layer-stack-at 5 str))))))
|
|
|
|
(ert-deftest tp-stack-test-layer-stack-at-unnamed-and-bare ()
|
|
"Unnamed layers report a nil NAME; bare text reports nil."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(tp-push-layer 1 4 '(face bold))
|
|
(should (equal (tp-layer-stack-at 1) '((nil . (face bold)))))
|
|
(should (null (tp-layer-stack-at 5)))))
|
|
|
|
;;; 0.3.0 S4: modified-interval counts and NOERROR
|
|
|
|
(ert-deftest tp-stack-test-delete-layer-returns-run-count ()
|
|
"Delete returns how many property runs matched; 0 when none did."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdefghij")
|
|
(define-tp la () '(face bold))
|
|
(tp-push-layer 1 4 'la)
|
|
(tp-push-layer 6 9 'la)
|
|
(should (= (tp-delete-layer 1 9 'nope) 0))
|
|
(should (= (tp-delete-layer 1 9 'la) 2))
|
|
(should-not (tp-layer-exists-p 1 9 'la))))
|
|
|
|
(ert-deftest tp-stack-test-pop-layer-returns-run-count ()
|
|
"Pop returns the number of runs that had a layer to pop."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp la () '(face bold))
|
|
(tp-put-layer 0 3 'la 0 str)
|
|
(should (= (tp-pop-layer 0 6 str) 1))
|
|
(should (= (tp-pop-layer 0 6 str) 0)))))
|
|
|
|
(ert-deftest tp-stack-test-movement-ops-return-run-counts ()
|
|
"Move, raise, pin and switch return matched-run counts."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(define-tp lb () '(face italic))
|
|
(tp-push-layer 1 6 'la)
|
|
(tp-push-layer 1 6 'lb)
|
|
(should (= (tp-raise-layer 1 6 'nope 1) 0))
|
|
(should (= (tp-raise-layer 1 6 'la 1) 1))
|
|
(should (= (tp-pin-layer 1 6 'lb) 1))
|
|
(should (= (tp-move-layer 1 6 'la 0) 1))
|
|
(should (= (tp-move-layer 1 6 'nope 0) 0))
|
|
(should (= (tp-switch-layer 1 6 'la 'lb) 1))
|
|
(should (= (tp-switch-layer 1 6 'la 'nope) 0))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-noerror ()
|
|
"With NOERROR an unresolvable LAYER returns nil and writes nothing."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp la () '(face bold))
|
|
(should-error (tp-put-layer 1 6 'undefined-x 0))
|
|
(should (null (tp-put-layer 1 6 'undefined-x 0 nil t)))
|
|
(should (null (text-properties-at 1)))
|
|
;; A resolvable layer with NOERROR still applies normally.
|
|
(should (tp-put-layer 1 6 'la 0 nil t))
|
|
(should (eq (get-text-property 1 'tp-name) 'la))))
|
|
|
|
(ert-deftest tp-stack-test-push-layer-noerror-both-forms ()
|
|
"NOERROR works for push in region and string forms."
|
|
(tp-stack-tests--with-env
|
|
(let ((str (copy-sequence "abcdef")))
|
|
(define-tp la () '(face bold))
|
|
(should-error (tp-push-layer str 'undefined-x))
|
|
(should (null (tp-push-layer str 'undefined-x t)))
|
|
(should (null (tp-put-layer str 'undefined-x 0 t)))
|
|
(should (null (text-properties-at 0 str)))
|
|
;; The string form still returns the string on success.
|
|
(should (eq (tp-push-layer str 'la t) str))
|
|
(should (eq (get-text-property 0 'tp-name str) 'la)))
|
|
(insert "abcdef")
|
|
(should (null (tp-push-layer 1 6 'undefined-x nil t)))
|
|
(should (null (text-properties-at 1)))))
|
|
|
|
(ert-deftest tp-stack-test-noerror-does-not-catch-layer-body-errors ()
|
|
"NOERROR suppresses unresolved names, not errors from a resolved body."
|
|
(tp-stack-tests--with-env
|
|
(insert "abcdef")
|
|
(define-tp tp-st-a06-boom (_value)
|
|
(error "tp-a06 body failure"))
|
|
(should-error
|
|
(tp-push-layer 1 6 '(tp-st-a06-boom 1) nil t)
|
|
:type 'error)
|
|
(should-not (text-properties-at 1))))
|
|
|
|
;;; Multi-argument parameterized specs through tp-put-layer
|
|
|
|
(ert-deftest tp-stack-test-put-layer-multiarg-layer-flat ()
|
|
"tp-put-layer accepts flat (LAYER ARG1 ARG2) for a 2-arity layer."
|
|
(tp-layer-reset)
|
|
(define-tp tp-st-colors (fg bg)
|
|
`(face (:foreground ,fg :background ,bg)))
|
|
(with-temp-buffer
|
|
(insert "Hello")
|
|
(tp-put-layer 1 5 '(tp-st-colors "red" "blue") 0)
|
|
(should (equal (tp-at 1 'face)
|
|
'(:foreground "red" :background "blue")))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-multiarg-layer-wrapped ()
|
|
"tp-put-layer accepts wrapped (LAYER (ARG1 ARG2)) for a 2-arity layer."
|
|
(tp-layer-reset)
|
|
(define-tp tp-st-colors2 (fg bg)
|
|
`(face (:foreground ,fg :background ,bg)))
|
|
(with-temp-buffer
|
|
(insert "Hello")
|
|
(tp-put-layer 1 5 '(tp-st-colors2 ("green" "black")) 0)
|
|
(should (equal (tp-at 1 'face)
|
|
'(:foreground "green" :background "black")))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-multiarg-layer-symbol-args ()
|
|
"Multi-arg specs are not misread as a list of layer names.
|
|
Arguments that are themselves defined layer names used to be
|
|
intercepted by the list-of-specs branch."
|
|
(tp-layer-reset)
|
|
(define-tp tp-st-a () '(help-echo "a"))
|
|
(define-tp tp-st-b () '(help-echo "b"))
|
|
(define-tp tp-st-pair (x y)
|
|
`(display (,x . ,y)))
|
|
(with-temp-buffer
|
|
(insert "Hello")
|
|
(tp-put-layer 1 5 '(tp-st-pair tp-st-a tp-st-b) 0)
|
|
(should (equal (tp-at 1 'display) '(tp-st-a . tp-st-b)))
|
|
(should (null (tp-at 1 'help-echo)))))
|
|
|
|
(ert-deftest tp-stack-test-put-layer-multiarg-group ()
|
|
"tp-put-layer accepts (GROUP ARG1 ARG2) for a 2-arity group."
|
|
(tp-layer-reset)
|
|
(define-tps tp-st-duo (fg bg)
|
|
`(face (:foreground ,fg))
|
|
`(face (:background ,bg)))
|
|
(with-temp-buffer
|
|
(insert "Hello")
|
|
(tp-put-layer 1 5 '(tp-st-duo "red" "blue") 0)
|
|
(should (equal (tp-at 1 'face) '(:foreground "red")))
|
|
(should (= (tp-layer-count 1 5) 2))))
|
|
|
|
(ert-deftest tp-stack-test-remove-multiarg-layer-by-name ()
|
|
"tp-remove removes a multi-arg parameterized layer's props by name.
|
|
Applied via `tp-put-layer' so the region carries the layer's
|
|
`tp-name' (the `tp-set' plist forms do not stamp `tp-name' for
|
|
parameterized layers, so name-based removal cannot see those).
|
|
The key-extraction path must bind all parameters (dummy args),
|
|
not just the first."
|
|
(tp-layer-reset)
|
|
(define-tp tp-st-colors3 (fg bg)
|
|
`(face (:foreground ,fg :background ,bg)))
|
|
(with-temp-buffer
|
|
(insert "Hello")
|
|
(tp-put-layer 1 5 '(tp-st-colors3 "red" "blue") 0)
|
|
(put-text-property 1 5 'help-echo "tip")
|
|
(should (tp-at 1 'face))
|
|
(should (eq (tp-at 1 'tp-name) 'tp-st-colors3))
|
|
(tp-remove 1 5 'tp-st-colors3)
|
|
(should (null (tp-at 1 'face)))
|
|
(should (equal (tp-at 1 'help-echo) "tip"))))
|
|
|
|
;;; HID-1/XM-01: reactive updates must write through to tp-layers storage
|
|
|
|
(defvar tp-st-xm01-a-color nil)
|
|
(defvar tp-st-xm01-b-color nil)
|
|
(defvar tp-st-xm01-c-color nil)
|
|
(defvar tp-st-xm01-rt-color nil)
|
|
(defvar tp-st-xm01-t-text nil)
|
|
(defvar tp-st-xm01-x-color nil)
|
|
|
|
(ert-deftest tp-stack-test-reactive-update-reaches-hidden-layer ()
|
|
"XM-01 A3: an update received while a layer is hidden renders after show.
|
|
The hidden layer has no direct `tp-name', so the update must find and
|
|
refresh its entry inside `tp-layers' stack storage."
|
|
(tp-stack-tests--with-env
|
|
(setq tp-st-xm01-a-color "red")
|
|
(define-tp tp-st-xm01-lay-a ()
|
|
:props '(face (:foreground $tp-st-xm01-a-color)))
|
|
(insert "AAAAAA")
|
|
(tp-push-layer 1 7 'tp-st-xm01-lay-a)
|
|
(tp-hide-layer 1 7 'tp-st-xm01-lay-a)
|
|
(setq tp-st-xm01-a-color "blue")
|
|
(tp-show-layer 1 7 'tp-st-xm01-lay-a)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))))
|
|
|
|
(ert-deftest tp-stack-test-stack-op-never-reverts-reactive-update ()
|
|
"XM-01 B1-B4: stack ops rebuild from CURRENT values, never stale ones.
|
|
With another layer hidden the storage switches to full-stack mode
|
|
where `tp-layers' is authoritative; a reactive update must refresh
|
|
the stored snapshot so a no-op stack operation cannot revert the
|
|
rendered value, and re-setting the SAME value (a watcher no-op) never
|
|
needs to repair anything."
|
|
(tp-stack-tests--with-env
|
|
(setq tp-st-xm01-b-color "red")
|
|
(define-tp tp-st-xm01-lay-b ()
|
|
:props '(face (:foreground $tp-st-xm01-b-color)))
|
|
(define-tp tp-st-xm01-lay-bg () '(face (:background "gray")))
|
|
(insert "BBBBBB")
|
|
(tp-push-layer 1 7 'tp-st-xm01-lay-bg)
|
|
(tp-push-layer 1 7 'tp-st-xm01-lay-b)
|
|
(tp-hide-layer 1 7 'tp-st-xm01-lay-bg) ; -> full-stack storage mode
|
|
(setq tp-st-xm01-b-color "blue")
|
|
;; B1: the visible reactive top renders the new value...
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))
|
|
;; ...and the stored stack snapshot agrees (write-through).
|
|
(let ((entry (assq 'tp-st-xm01-lay-b (tp-layer-stack-at 1))))
|
|
(should (equal (plist-get (cdr entry) 'face) '(:foreground "blue"))))
|
|
;; B2: a no-op stack operation must not revert the update.
|
|
(tp-move-layer 1 7 'tp-st-xm01-lay-b 0)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))
|
|
;; B3: re-setting the same value is a watcher no-op; the buffer is
|
|
;; already correct (before the fix it stayed stuck on the old value).
|
|
(setq tp-st-xm01-b-color "blue")
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))
|
|
;; B4: a third value still updates normally.
|
|
(setq tp-st-xm01-b-color "green")
|
|
(should (equal (get-text-property 1 'face) '(:foreground "green")))))
|
|
|
|
(ert-deftest tp-stack-test-hidden-top-round-trip-keeps-reactive-value ()
|
|
"XM-01/HID-1: show+hide of an UNRELATED layer keeps the reactive value.
|
|
Static top hidden, reactive layer rendered below: after a variable
|
|
change, a show/hide round trip of the top rebuilds from storage and
|
|
must not revert the reactive layer to a stale snapshot."
|
|
(tp-stack-tests--with-env
|
|
(setq tp-st-xm01-rt-color "red")
|
|
(define-tp tp-st-xm01-lay-rt ()
|
|
:props '(face (:foreground $tp-st-xm01-rt-color)))
|
|
(define-tp tp-st-xm01-lay-cover () '(face (:background "yellow")))
|
|
(insert "Hello")
|
|
(tp-push-layer 1 6 'tp-st-xm01-lay-rt)
|
|
(tp-push-layer 1 6 'tp-st-xm01-lay-cover)
|
|
(tp-hide-layer 1 6 'tp-st-xm01-lay-cover)
|
|
(setq tp-st-xm01-rt-color "blue")
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))
|
|
(tp-show-layer 1 6 'tp-st-xm01-lay-cover)
|
|
(tp-hide-layer 1 6 'tp-st-xm01-lay-cover)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))))
|
|
|
|
(ert-deftest tp-stack-test-pop-reveals-current-reactive-value ()
|
|
"XM-01 C2: a below-top reactive layer revealed by tp-pop-layer is current.
|
|
The buried layer's entry lives inside `tp-layers'; the update must
|
|
refresh it there so the reveal renders current values."
|
|
(tp-stack-tests--with-env
|
|
(setq tp-st-xm01-c-color "red")
|
|
(define-tp tp-st-xm01-lay-c ()
|
|
:props '(face (:foreground $tp-st-xm01-c-color)))
|
|
(define-tp tp-st-xm01-lay-top () '(face (:foreground "black")))
|
|
(insert "DDDDDD")
|
|
(tp-push-layer 1 7 'tp-st-xm01-lay-c)
|
|
(tp-push-layer 1 7 'tp-st-xm01-lay-top)
|
|
(setq tp-st-xm01-c-color "blue")
|
|
(tp-pop-layer 1 7)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))))
|
|
|
|
(ert-deftest tp-stack-test-reactive-tp-text-reaches-hidden-layer ()
|
|
"XM-01 T1: a reactive tp-text update reaches a hidden layer's text.
|
|
Text content is physical - hide/show toggles properties, never text -
|
|
so the model value replaces the text while the layer is hidden, and
|
|
`tp-show-layer' then renders current props over current text."
|
|
(tp-stack-tests--with-env
|
|
(setq tp-st-xm01-t-text "AAA")
|
|
(define-tp tp-st-xm01-lay-t ()
|
|
:props '(tp-text $tp-st-xm01-t-text face (:foreground "purple")))
|
|
(insert "AAA")
|
|
(tp-push-layer 1 4 'tp-st-xm01-lay-t)
|
|
(tp-hide-layer 1 4 'tp-st-xm01-lay-t)
|
|
(setq tp-st-xm01-t-text "ZZZ")
|
|
(tp-show-layer 1 4 'tp-st-xm01-lay-t)
|
|
(should (equal (buffer-substring-no-properties (point-min) (point-max))
|
|
"ZZZ"))
|
|
(should (equal (get-text-property 1 'tp-text) "ZZZ"))
|
|
(should (equal (get-text-property 1 'face) '(:foreground "purple")))))
|
|
|
|
(ert-deftest tp-stack-test-mixed-visible-hidden-regions-stay-in-sync ()
|
|
"XM-01 X1: visible and hidden regions of one layer both end up current.
|
|
Before the fix one buffer could render two different values of the
|
|
same variable at once (split-brain)."
|
|
(tp-stack-tests--with-env
|
|
(setq tp-st-xm01-x-color "red")
|
|
(define-tp tp-st-xm01-lay-x ()
|
|
:props '(face (:foreground $tp-st-xm01-x-color)))
|
|
(insert "XXXXXXXXXX")
|
|
(tp-push-layer 1 5 'tp-st-xm01-lay-x)
|
|
(tp-push-layer 6 11 'tp-st-xm01-lay-x)
|
|
(tp-hide-layer 6 11 'tp-st-xm01-lay-x)
|
|
(setq tp-st-xm01-x-color "blue")
|
|
(tp-show-layer 6 11 'tp-st-xm01-lay-x)
|
|
(should (equal (get-text-property 1 'face) '(:foreground "blue")))
|
|
(should (equal (get-text-property 6 'face) '(:foreground "blue")))))
|
|
|
|
;;; REG-1: every stack write must register its buffer in the reactive registry
|
|
|
|
(defvar tp-st-reg1-color nil)
|
|
|
|
(ert-deftest tp-stack-test-push-layer-registers-reactive-buffer ()
|
|
"tp-push-layer in a second buffer keeps reactive updates flowing there.
|
|
Once the registry knows a layer from a `tp-set' in one buffer, a
|
|
stack-path application in another buffer must register too; before
|
|
the REG-1 fix the second buffer was silently and permanently skipped
|
|
by every later update."
|
|
(setq tp-st-reg1-color "red")
|
|
(unwind-protect
|
|
(progn
|
|
(tp-layer-reset)
|
|
(define-tp tp-st-reg1-layer ()
|
|
:props '(face (:foreground $tp-st-reg1-color)))
|
|
(let ((a (generate-new-buffer " *tp-reg1-a*"))
|
|
(b (generate-new-buffer " *tp-reg1-b*")))
|
|
(unwind-protect
|
|
(progn
|
|
(with-current-buffer a (insert "hello"))
|
|
(with-current-buffer b (insert "hello"))
|
|
(tp-set 1 6 'tp-st-reg1-layer a) ; registers A
|
|
(with-current-buffer b
|
|
(tp-push-layer 1 6 'tp-st-reg1-layer))
|
|
;; The registry must know BOTH buffers.
|
|
(let ((bufs (tp-reactive-layer-buffers 'tp-st-reg1-layer)))
|
|
(should (memq a bufs))
|
|
(should (memq b bufs)))
|
|
(setq tp-st-reg1-color "blue")
|
|
(should (equal (with-current-buffer a
|
|
(get-text-property 1 'face))
|
|
'(:foreground "blue")))
|
|
(should (equal (with-current-buffer b
|
|
(get-text-property 1 'face))
|
|
'(:foreground "blue")))
|
|
;; And the registration is permanent, not a one-shot fluke.
|
|
(setq tp-st-reg1-color "green")
|
|
(should (equal (with-current-buffer b
|
|
(get-text-property 1 'face))
|
|
'(:foreground "green"))))
|
|
(kill-buffer a)
|
|
(kill-buffer b))))
|
|
(tp-layer-reset)
|
|
(setq tp-st-reg1-color nil)))
|
|
|
|
(ert-deftest tp-stack-test-stack-write-registers-buried-and-hidden-layers ()
|
|
"Stack writes register every named layer of the new stack, not just the top.
|
|
A buried layer (under a fresh push) and a hidden layer arrive in the
|
|
buffer via string insertion - a path that never registers - and the
|
|
next stack write on the region must register them (REG-1; GC-1's
|
|
liveness depends on this)."
|
|
(unwind-protect
|
|
(progn
|
|
(tp-layer-reset)
|
|
(define-tp tp-st-reg1-buried () '(face bold))
|
|
(define-tp tp-st-reg1-top () '(face italic))
|
|
(define-tp tp-st-reg1-hidden () '(face underline))
|
|
(let ((buf (generate-new-buffer " *tp-reg1-c*")))
|
|
(unwind-protect
|
|
(with-current-buffer buf
|
|
;; Propertized string insertion bypasses registration.
|
|
(insert (let ((s (copy-sequence "hello")))
|
|
(tp-push-layer s 'tp-st-reg1-buried)
|
|
s))
|
|
(insert (let ((s (copy-sequence " world")))
|
|
(tp-push-layer s 'tp-st-reg1-hidden)
|
|
s))
|
|
(should (eq (tp-reactive-layer-buffers 'tp-st-reg1-buried)
|
|
'unknown))
|
|
;; Pushing a new top rewrites the stack: the buried
|
|
;; layer below it must be registered as well.
|
|
(tp-push-layer 1 6 'tp-st-reg1-top)
|
|
(should (memq buf (tp-reactive-layer-buffers
|
|
'tp-st-reg1-buried)))
|
|
(should (memq buf (tp-reactive-layer-buffers
|
|
'tp-st-reg1-top)))
|
|
;; Hiding rewrites the stack: the now-hidden layer must
|
|
;; stay registered even though it loses its direct
|
|
;; tp-name.
|
|
(tp-hide-layer 7 12 'tp-st-reg1-hidden)
|
|
(should (memq buf (tp-reactive-layer-buffers
|
|
'tp-st-reg1-hidden))))
|
|
(kill-buffer buf))))
|
|
(tp-layer-reset)))
|
|
|
|
;;; Stage 2 canonical layer-operation ranges
|
|
|
|
(ert-deftest tp-stack-test-parser-resolves-canonical-native-object ()
|
|
"Layer argument parsing resolves nil to a concrete buffer object."
|
|
(with-temp-buffer
|
|
(insert "hello")
|
|
(pcase-let ((`(,start ,end ,object ,layer)
|
|
(tp--parse-layer-args 2 (list 5 'example nil) 1)))
|
|
(should (= start 2))
|
|
(should (= end 5))
|
|
(should (eq object (current-buffer)))
|
|
(should (eq layer 'example)))))
|
|
|
|
(provide 'tp-stack-tests)
|
|
;;; tp-stack-tests.el ends here
|