tp/tests/tp-tests.el
Kinneyzhang 0d35358e05 refactor(tp)!: implement retained reactive runtime
Replace the legacy managed layer renderer with one independent retained/reactive text runtime. TP now owns exact dependencies, stable objects, marker-backed mounts, property contribution composition, atomic publication, rollback, and direct text-property facades without ECSS or Ebox dependencies.\n\nBREAKING CHANGE: remove tp-render, tp-stack, scan-driven managed layers, inline runtime metadata, TP-owned CSS cascade APIs, and dollar-variable declarations.\n\nVerified: 290/290 ERT, shuffled 290/290 (seed 20260806), 8/8 doctests, WERROR compile-all, checkdoc, package-lint, diff-check, and isolated TP-only load.
2026-08-07 00:39:50 +08:00

139 lines
5.6 KiB
EmacsLisp

;;; tp-tests.el --- Public TP facade tests -*- lexical-binding: t; -*-
;; Copyright (C) 2024-2026 Geekinney
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; Focused end-to-end tests for the stateless public property facade. Retained
;; and reactive behavior is covered by tp-surface-tests and tp-binding-tests.
;;; Code:
(require 'ert)
(require 'tp)
(defmacro tp-test-with-temp-buffer (&rest body)
"Run BODY in a temporary buffer with isolated declaration recipes."
(declare (indent 0) (debug t))
`(unwind-protect
(with-temp-buffer
(tp-layer-reset)
,@body)
(tp-layer-reset)))
(ert-deftest tp-test-set-get-and-member-preserve-presence ()
"Set/get APIs preserve explicit nil separately from absence."
(tp-test-with-temp-buffer
(insert "hello")
(should (equal (tp-set 1 6 '(face bold help-echo nil)) '(1 . 6)))
(should (eq (tp-at 2 'face) 'bold))
(should (equal (tp-member 2 'help-echo) '(help-echo nil)))
(should-not (tp-member 2 'mouse-face))
(should (equal (tp-get 1 6 'help-echo) '((1 6 nil))))))
(ert-deftest tp-test-set-string-whole-object-is-nondestructive ()
"Whole-string mutation returns a copy and leaves its input untouched."
(let* ((source (copy-sequence "hello"))
(result (tp-set source 'face 'bold 'help-echo "tip")))
(should-not (eq source result))
(should-not (text-properties-at 0 source))
(should (eq (get-text-property 0 'face result) 'bold))
(should (equal (get-text-property 0 'help-echo result) "tip"))))
(ert-deftest tp-test-set-string-range-mutates-in-place ()
"Explicit string ranges retain the historical in-place contract."
(let ((text (copy-sequence "hello")))
(should (eq (tp-set 1 4 '(face italic) text) text))
(should-not (get-text-property 0 'face text))
(should (eq (get-text-property 1 'face text) 'italic))
(should-not (get-text-property 4 'face text))))
(ert-deftest tp-test-reset-replaces-only-the-requested-range ()
"Reset removes prior properties inside its range and nowhere else."
(tp-test-with-temp-buffer
(insert "abcdef")
(put-text-property 1 7 'help-echo "host")
(tp-reset 2 5 '(face bold))
(should (equal (get-text-property 1 'help-echo) "host"))
(should-not (get-text-property 2 'help-echo))
(should (eq (get-text-property 2 'face) 'bold))
(should (equal (get-text-property 5 'help-echo) "host"))))
(ert-deftest tp-test-add-composes-face-and-nested-plists ()
"Add uses the shared native property merge policy."
(let* ((source (propertize "x" 'face 'bold
'display '(:width 1 :height 2)))
(result (tp-add source
'face 'italic
'display '(:width 3))))
(should (equal (get-text-property 0 'face result) '(italic bold)))
(should
(equal (get-text-property 0 'display result)
'(:width 3 :height 2)))))
(ert-deftest tp-test-remove-top-level-and-nested-properties ()
"Remove handles top-level, sub-property, and nested paths."
(let* ((source (propertize
"x" 'face '(:foreground "red"
:underline (:style wave :color "blue"))
'help-echo "tip"))
(no-help (tp-remove source 'help-echo))
(no-underline-style
(tp-remove source 'face :underline '(:style))))
(should-not (get-text-property 0 'help-echo no-help))
(should
(equal (get-text-property 0 'face no-underline-style)
'(:foreground "red" :underline (:color "blue"))))))
(ert-deftest tp-test-clear-defaults-to-target-bounds ()
"Clear removes every property while preserving text."
(let ((text (propertize "hello" 'face 'bold)))
(tp-clear nil nil text)
(should (equal text "hello"))
(should-not (text-properties-at 0 text))))
(ert-deftest tp-test-native-recipe-application-is-one-shot ()
"Applying a named recipe creates no object, binding, anchor, or mount."
(tp-test-with-temp-buffer
(define-tp tp-test-warning (color)
`(face (:foreground ,color) help-echo "warning"))
(insert "warning")
(let ((before (tp-reactive-counters)))
(tp-set 1 8 '(tp-test-warning "orange"))
(should (equal (get-text-property 1 'face)
'(:foreground "orange")))
(should (equal (tp-reactive-counters) before)))))
(ert-deftest tp-test-match-and-regexp-use-the-same-direct-core ()
"Literal and regexp application share recipe projection semantics."
(tp-test-with-temp-buffer
(define-tp tp-test-hit () '(face bold))
(insert "one two one")
(should (equal (tp-match-set "one" 'tp-test-hit)
'((1 . 4) (9 . 12))))
(should (equal (tp-regexp-add "t.o" '(help-echo "two"))
'((5 . 8))))
(should (eq (get-text-property 1 'face) 'bold))
(should (equal (get-text-property 5 'help-echo) "two"))))
(ert-deftest tp-test-property-navigation-keeps-string-buffer-parity ()
"Forward and backward search report equivalent string/buffer matches."
(let ((text (tp-set "abcd" 'face 'bold)))
(with-temp-buffer
(insert text)
(goto-char (point-min))
(let ((buffer-match (tp-forward 'face 'bold)))
(should (= (prop-match-beginning buffer-match) 1))
(should (= (prop-match-end buffer-match) 5))
(should (eq (prop-match-value buffer-match) 'bold)))
(should (equal (tp-forward 'face 'bold text) '((0 4 bold))))
(goto-char (point-max))
(let ((buffer-match (tp-backward 'face 'bold)))
(should (= (prop-match-beginning buffer-match) 1))
(should (= (prop-match-end buffer-match) 5))
(should (eq (prop-match-value buffer-match) 'bold))))))
(provide 'tp-tests)
;;; tp-tests.el ends here