fix: preserve mixed updates beside independent scroll regions
This commit is contained in:
parent
7c0ddbe933
commit
e7066b27f4
@ -7601,11 +7601,11 @@ line count; its outside siblings are shifted by the one scoped replacement."
|
||||
(setq candidate-id (gethash candidate-id parents))))))
|
||||
proof))
|
||||
|
||||
(defun ebox-incremental--two-owner-allocation-proof
|
||||
(defun ebox-incremental--two-owner-geometry-proof
|
||||
(buffer old-state candidate-state geometry-prepared)
|
||||
"Return two disjoint geometry proofs containing one allocation closure.
|
||||
This narrow fallback runs only after the ordinary combined span proof misses.
|
||||
It never partitions larger dirty sets, preserving their existing coalescing."
|
||||
"Combine exact geometry proofs for two changes.
|
||||
Changes sharing one safe owner reuse its grouped proof; separate owners retain
|
||||
their disjoint span or allocation proofs. Larger dirty sets keep coalescing."
|
||||
(let ((dirty-set (plist-get geometry-prepared :dirty-set))
|
||||
(root-id (ebox--buffer-root-node-id buffer))
|
||||
proofs owner-proofs valid allocation-p)
|
||||
@ -7684,36 +7684,42 @@ It never partitions larger dirty sets, preserving their existing coalescing."
|
||||
(copy-sequence local-owner-proofs)))
|
||||
(setq allocation-p (or allocation-p allocation-proof)))
|
||||
(setq valid nil)))))
|
||||
(when (and valid allocation-p
|
||||
(= (length proofs) 2)
|
||||
(ebox-incremental--owner-proofs-disjoint-p
|
||||
buffer owner-proofs))
|
||||
(when (and valid (= (length proofs) 2))
|
||||
(setq proofs (nreverse proofs))
|
||||
(let ((owner-ids
|
||||
(delete-dups
|
||||
(mapcar (lambda (proof) (plist-get proof :owner-id))
|
||||
owner-proofs))))
|
||||
(list :owner-id (car owner-ids)
|
||||
:owner-ids owner-ids
|
||||
:owner-proofs owner-proofs
|
||||
:dirty-set (copy-sequence dirty-set)
|
||||
:variable-content-p
|
||||
(cl-some (lambda (proof)
|
||||
(plist-get proof :variable-content-p))
|
||||
proofs)
|
||||
:ancestor-slot-signature
|
||||
(cl-mapcan
|
||||
(lambda (proof)
|
||||
(copy-sequence
|
||||
(plist-get proof :ancestor-slot-signature)))
|
||||
proofs)
|
||||
:layout-snapshot-detail-generation
|
||||
(plist-get old-state :layout-snapshot-detail-generation)
|
||||
:static-cascade-p
|
||||
(cl-every (lambda (proof)
|
||||
(plist-get proof :static-cascade-p))
|
||||
proofs)
|
||||
:contains-allocation-closure-p t)))))
|
||||
(if (= (length owner-ids) 1)
|
||||
(when-let* ((grouped
|
||||
(ebox-incremental--grouped-owner-span-proof
|
||||
buffer old-state geometry-prepared candidate-state
|
||||
(list (ebox--patch-op 'span-patch (car owner-ids))))))
|
||||
(dolist (owner-proof (plist-get grouped :owner-proofs))
|
||||
(plist-put owner-proof :retain-external-owner-suffix-p t))
|
||||
grouped)
|
||||
(when (ebox-incremental--owner-proofs-disjoint-p buffer owner-proofs)
|
||||
(list :owner-id (car owner-ids)
|
||||
:owner-ids owner-ids
|
||||
:owner-proofs owner-proofs
|
||||
:dirty-set (copy-sequence dirty-set)
|
||||
:variable-content-p
|
||||
(cl-some (lambda (proof)
|
||||
(plist-get proof :variable-content-p))
|
||||
proofs)
|
||||
:ancestor-slot-signature
|
||||
(cl-mapcan
|
||||
(lambda (proof)
|
||||
(copy-sequence
|
||||
(plist-get proof :ancestor-slot-signature)))
|
||||
proofs)
|
||||
:layout-snapshot-detail-generation
|
||||
(plist-get old-state :layout-snapshot-detail-generation)
|
||||
:static-cascade-p
|
||||
(cl-every (lambda (proof)
|
||||
(plist-get proof :static-cascade-p))
|
||||
proofs)
|
||||
:contains-allocation-closure-p allocation-p)))))))
|
||||
|
||||
(defun ebox-incremental--mixed-owner-proof
|
||||
(buffer old-state candidate-state prepared owner-plan)
|
||||
@ -7763,7 +7769,7 @@ suppresses the ordinary fallback when any fact is missing."
|
||||
;; two local proofs already close exactly.
|
||||
(geometry-two-owner-proof
|
||||
(and (= (length geometry-dirty) 2)
|
||||
(ebox-incremental--two-owner-allocation-proof
|
||||
(ebox-incremental--two-owner-geometry-proof
|
||||
buffer old-state candidate-state geometry-prepared)))
|
||||
(geometry-context-proof
|
||||
(and (not geometry-two-owner-proof)
|
||||
@ -7903,12 +7909,13 @@ suppresses the ordinary fallback when any fact is missing."
|
||||
:retain-external-owner-suffix-p t)))
|
||||
(when (and geometry-proof paint-valid-p disjoint-p
|
||||
allocation-disjoint-p cascade-safe-p
|
||||
;; The single-owner span proof can retain unrelated scroll
|
||||
;; subtrees. Its geometry partition does not prove that this
|
||||
;; mixed transaction's paint owners leave scroll caches intact.
|
||||
(null (plist-get old-state :scroll-region-ids))
|
||||
(null (ebox-incremental--hash-keys
|
||||
(plist-get prepared :scroll-state-table)))
|
||||
;; Both the rendered geometry and inherited paint must leave
|
||||
;; every retained scroll producer outside their dependency scope.
|
||||
(cl-every
|
||||
(lambda (owner-id)
|
||||
(ebox-incremental--owner-disjoint-from-scroll-p
|
||||
buffer old-state prepared candidate-state owner-id))
|
||||
(append geometry-owner-ids paint-owner-ids))
|
||||
(not (plist-get prepared :scroll-state-transaction))
|
||||
(not (plist-get prepared :scroll-patch-fast-p))
|
||||
root-id
|
||||
@ -8484,18 +8491,25 @@ closure may still plan one for a later owner-scoped surface projection."
|
||||
(ebox-incremental--commit-report prepared report candidate-state)))
|
||||
|
||||
(defun ebox-incremental--paint-projection-p
|
||||
(old-state owner-plan dirty-set)
|
||||
"Return non-nil when OWNER-PLAN can reuse OLD-STATE spatial topology."
|
||||
(buffer old-state prepared candidate-state owner-plan)
|
||||
"Return non-nil when OWNER-PLAN can reuse OLD-STATE spatial topology.
|
||||
BUFFER, PREPARED and CANDIDATE-STATE prove independence from retained scroll."
|
||||
(and owner-plan
|
||||
(null (plist-get old-state :scroll-region-ids))
|
||||
(not (plist-get prepared :scroll-state-transaction))
|
||||
(not (plist-get prepared :scroll-patch-fast-p))
|
||||
(cl-every (lambda (op) (eq (plist-get op :op) 'paint-patch))
|
||||
owner-plan)
|
||||
(cl-every
|
||||
(lambda (op)
|
||||
(ebox-incremental--owner-disjoint-from-scroll-p
|
||||
buffer old-state prepared candidate-state (plist-get op :owner-id)))
|
||||
owner-plan)
|
||||
(cl-every
|
||||
(lambda (dirty)
|
||||
(cl-every (lambda (key)
|
||||
(memq key ebox--paint-style-signature-keys))
|
||||
(plist-get dirty :changed-keys)))
|
||||
dirty-set)))
|
||||
(plist-get prepared :dirty-set))))
|
||||
|
||||
(defun ebox-incremental--hash-key-set-equal-p (left right)
|
||||
"Return non-nil when hash tables LEFT and RIGHT have the same keys."
|
||||
@ -9288,7 +9302,7 @@ the surface renderer and role/overflow validators remain the final proof."
|
||||
(plist-get slot :start-pixel)))
|
||||
slots))))))
|
||||
|
||||
(defun ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(defun ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(buffer old-state prepared candidate-state owner-id)
|
||||
"Return non-nil when OWNER-ID cannot change any retained scroll content.
|
||||
The active scroll set and each scroll box identity must survive preparation.
|
||||
@ -9313,8 +9327,10 @@ regenerate its cached lines; only separate branches may keep those lines."
|
||||
(eq next-box (gethash scroll-id (plist-get candidate-state :node-table)))
|
||||
(eq next-box (gethash region (plist-get candidate-state :region-box-table)))
|
||||
(not (equal scroll-id owner-id))
|
||||
(not (ebox--runtime-ancestor-id-p buffer scroll-id owner-id))
|
||||
(not (ebox--runtime-ancestor-id-p buffer owner-id scroll-id))
|
||||
(let ((ebox-incremental--buffer-render-state-override
|
||||
(cons buffer old-state)))
|
||||
(and (not (ebox--runtime-ancestor-id-p buffer scroll-id owner-id))
|
||||
(not (ebox--runtime-ancestor-id-p buffer owner-id scroll-id))))
|
||||
(let ((ebox-incremental--buffer-render-state-override
|
||||
(cons buffer candidate-state)))
|
||||
(and (not (ebox--runtime-ancestor-id-p buffer scroll-id owner-id))
|
||||
@ -9450,7 +9466,7 @@ metadata; this predicate only authorizes the local attempt."
|
||||
local-typography-p)
|
||||
(ebox-incremental--cascade-local-owner-proof-p
|
||||
old-state candidate-state (list dirty))))
|
||||
(ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(ebox-incremental--owner-disjoint-from-scroll-p
|
||||
buffer old-state prepared candidate-state owner-id)
|
||||
;; Admit the new scroll coexistence case only for an exact
|
||||
;; content extent. Length or width changes can alter an
|
||||
@ -9729,7 +9745,8 @@ role, and outside-complement compatibility before publication."
|
||||
new-node))
|
||||
(ebox-incremental--same-root-surface-identity-p
|
||||
old-state candidate-state)
|
||||
(null (plist-get old-state :scroll-region-ids))
|
||||
(ebox-incremental--owner-disjoint-from-scroll-p
|
||||
buffer old-state prepared candidate-state owner-id)
|
||||
(not (ebox--node-visible-overflow-p old-node))
|
||||
(not (ebox--node-visible-overflow-p new-node))
|
||||
ancestor-slot-signature
|
||||
@ -9741,7 +9758,8 @@ role, and outside-complement compatibility before publication."
|
||||
:snapshot snapshot
|
||||
:allocated-width allocated-width
|
||||
:variable-content-p
|
||||
(and allocated-width
|
||||
(and (null (plist-get old-state :scroll-region-ids))
|
||||
allocated-width
|
||||
(cl-some
|
||||
(lambda (entry)
|
||||
(memq :content
|
||||
@ -10331,7 +10349,7 @@ role, and outside-complement compatibility before publication."
|
||||
(paint-p
|
||||
(and (not native-program-p)
|
||||
(ebox-incremental--paint-projection-p
|
||||
old-state owner-plan (plist-get prepared :dirty-set))))
|
||||
buffer old-state prepared candidate-state owner-plan)))
|
||||
(span-proof
|
||||
(unless (or native-program-p structural-dirty-p paint-p mixed-proof)
|
||||
(ebox-incremental--span-patch-projection-proof
|
||||
@ -10512,7 +10530,7 @@ whether NEXT-ROOT shares untouched published nodes."
|
||||
(plist-get prepared :dirty-set)))))
|
||||
(paint-p
|
||||
(ebox-incremental--paint-projection-p
|
||||
old-state owner-plan (plist-get prepared :dirty-set)))
|
||||
buffer old-state prepared candidate-state owner-plan))
|
||||
(span-proof
|
||||
(unless (or paint-p mixed-proof skip-span-proof-p)
|
||||
(ebox-incremental--span-patch-projection-proof
|
||||
|
||||
@ -86,12 +86,11 @@
|
||||
|
||||
(defun ebox-surface--mixed-range-splice-p (state)
|
||||
"Return non-nil when STATE's mixed proof contains one Range splice."
|
||||
(cl-some
|
||||
(lambda (owner-proof)
|
||||
(plist-get owner-proof :range-splice-p))
|
||||
(plist-get
|
||||
(plist-get (plist-get state :mixed-owner-proof) :geometry-proof)
|
||||
:owner-proofs)))
|
||||
(let ((proof (plist-get (plist-get state :mixed-owner-proof) :geometry-proof)))
|
||||
(or (plist-get proof :range-splice-p)
|
||||
(cl-some (lambda (owner-proof)
|
||||
(plist-get owner-proof :range-splice-p))
|
||||
(plist-get proof :owner-proofs)))))
|
||||
|
||||
(declare-function ebox--render-layout "ebox-layout" (node))
|
||||
(declare-function ebox--maplines "ebox" (function string))
|
||||
@ -1813,7 +1812,12 @@ the projection roots a second time."
|
||||
(region-objects
|
||||
(or (and scroll-fast-p
|
||||
(plist-get state :region-surface-object-table))
|
||||
(ebox-surface--region-object-table state node-objects))))
|
||||
(ebox-surface--region-object-table state node-objects)))
|
||||
(property-contributions
|
||||
(plist-get state :paint-property-contributions)))
|
||||
;; Paint contributions belong to this one publication. Keep them out of
|
||||
;; the transferred client state so later paint cannot replay old layers.
|
||||
(cl-remf state :paint-property-contributions)
|
||||
(plist-put state :surface-node-object-table node-objects)
|
||||
(plist-put state :region-surface-object-table region-objects)
|
||||
(unless (and scroll-fast-p
|
||||
@ -1827,7 +1831,8 @@ the projection roots a second time."
|
||||
(or (and native-retained-p
|
||||
(ebox-surface--native-patch-result
|
||||
context projection state output node-objects region-objects))
|
||||
(and (memq projection-kind
|
||||
(and (null property-contributions)
|
||||
(memq projection-kind
|
||||
'(span-patch owner-scoped mixed-owner-reflow))
|
||||
(ebox-surface--span-coordinate-result
|
||||
context projection state output previous-owned-ranges))
|
||||
@ -1840,10 +1845,9 @@ the projection roots a second time."
|
||||
scroll-fast-p))
|
||||
(plan (car prepared))
|
||||
(rendered (cadr prepared))
|
||||
(owned-ranges (nth 2 prepared))
|
||||
(property-contributions
|
||||
(plist-get state :paint-property-contributions)))
|
||||
(or (and (memq projection-kind
|
||||
(owned-ranges (nth 2 prepared)))
|
||||
(or (and (null property-contributions)
|
||||
(memq projection-kind
|
||||
'(span-patch owner-scoped mixed-owner-reflow))
|
||||
(ebox-surface--coordinate-commit
|
||||
context state rendered owned-ranges (nth 3 prepared)
|
||||
@ -3395,11 +3399,14 @@ old allocated width while the owner changes its natural content length."
|
||||
(cons buffer state)))
|
||||
(ebox--flex-item-slot-sized-node
|
||||
buffer owner-id old-snapshot changed-keys)))
|
||||
;; Variable content is explicitly shaped into a retained slot.
|
||||
;; Typography-only changes must render at their natural size so
|
||||
;; Grouped owners and variable content have a proven retained
|
||||
;; allocation. Rendering there does not grant permission for
|
||||
;; the final footprint to vary.
|
||||
;; Ungrouped typography changes render at their natural size so
|
||||
;; the final footprint check observes any intrinsic-width change
|
||||
;; instead of hiding it behind the old allocation.
|
||||
(if (and node allocated-width variable-content-p)
|
||||
(if (and node allocated-width
|
||||
(or variable-content-p role-owned-lines-p))
|
||||
(plist-put (copy-sequence node) :width allocated-width)
|
||||
node))))
|
||||
(when (and old-snapshot spans node render-node)
|
||||
@ -3876,7 +3883,12 @@ owners are then recomposed from the committed semantic fragment ledger, never
|
||||
by peeling an already-composed face suffix. Any missing address/baseline
|
||||
causes the caller to use the ordinary full projection."
|
||||
(let* ((proof (plist-get state :mixed-owner-proof))
|
||||
(geometry-proof (and proof (plist-get proof :geometry-proof)))
|
||||
;; Range splices may change the TP object set, so the retained
|
||||
;; content result cannot carry their paint layers. Preserve the
|
||||
;; ordinary renderer before producing a cleaned paint baseline.
|
||||
(geometry-proof (and proof
|
||||
(not (ebox-surface--mixed-range-splice-p state))
|
||||
(plist-get proof :geometry-proof)))
|
||||
(geometry-kind (and proof (plist-get proof :geometry-kind)))
|
||||
(paint-owner-ids (and proof (plist-get proof :paint-owner-ids)))
|
||||
(output
|
||||
@ -4005,7 +4017,9 @@ causes the caller to use the ordinary full projection."
|
||||
output fragments state))
|
||||
(plist-put state :mixed-owner-fragment-data fragments)
|
||||
(plist-put state :mixed-owner-content-p t)
|
||||
output)))))))
|
||||
(apply #'concat
|
||||
(mapcar (lambda (fragment) (plist-get fragment :text))
|
||||
fragments)))))))))
|
||||
|
||||
(defun ebox-surface--scroll-patch-output (_buffer state)
|
||||
"Return a staged visible scroll window without rerunning layout when safe.
|
||||
@ -5344,6 +5358,7 @@ but cannot introduce or remove a viewport expression or node identity."
|
||||
(copy-sequence previous-state)
|
||||
(ebox--new-buffer-render-state root))
|
||||
(copy-sequence state-overrides))))
|
||||
(cl-remf state :paint-property-contributions)
|
||||
(plist-put state :root-node root)
|
||||
(when (plist-get state :native-sync-confirmed-p)
|
||||
(ebox-native-commit-attach-confirmed-base previous-state state))
|
||||
@ -5622,7 +5637,14 @@ published runtime and must be consumed without clearing those shared nodes."
|
||||
(with-current-buffer buffer
|
||||
(save-restriction
|
||||
(widen)
|
||||
(buffer-substring (point-min) (point-max)))))))
|
||||
(buffer-substring (point-min) (point-max)))))))
|
||||
;; Retaining scroll pixels still requires the producer's
|
||||
;; reactive scroll subscription in this paint-only evaluation.
|
||||
(when (and signals
|
||||
(hash-table-p (plist-get state :scroll-state-table))
|
||||
(> (hash-table-count
|
||||
(plist-get state :scroll-state-table)) 0))
|
||||
(tp-signal-read (ebox-surface--signals-scroll signals)))
|
||||
(setq state (ebox-surface--finish-runtime-state state))
|
||||
(ebox-surface--refresh-paint-snapshots state dirty-set)
|
||||
(cl-remf state :paint-dirty-set)
|
||||
|
||||
@ -153,31 +153,43 @@
|
||||
(prepared (list :scroll-state-table table))
|
||||
(candidate (copy-sequence state)))
|
||||
(puthash region scroll table)
|
||||
(should (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(should (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate owner))
|
||||
(should-not (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate root))
|
||||
(should-not (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate scroll-id))
|
||||
(remhash region table)
|
||||
(should-not (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate owner))
|
||||
(puthash 'different-region scroll table)
|
||||
(should-not (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate owner))
|
||||
(clrhash table)
|
||||
(puthash region scroll table)
|
||||
(let ((regions (copy-hash-table (plist-get state :region-box-table))))
|
||||
(remhash region regions)
|
||||
(plist-put candidate :region-box-table regions)
|
||||
(should-not (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate owner)))
|
||||
(plist-put candidate :region-box-table (plist-get state :region-box-table))
|
||||
(let ((parents (copy-hash-table (plist-get state :parent-table))))
|
||||
(puthash scroll-id owner parents)
|
||||
(plist-put candidate :parent-table parents)
|
||||
(should-not (ebox-incremental--span-owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate owner))))))
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) state prepared candidate owner)))
|
||||
(plist-put candidate :parent-table (plist-get state :parent-table))
|
||||
(let* ((parents (copy-hash-table (plist-get state :parent-table)))
|
||||
(overlapping-old-state (copy-sequence state))
|
||||
(ebox-incremental--buffer-render-state-override
|
||||
(cons (current-buffer) candidate)))
|
||||
;; The caller may already expose candidate ancestry. Old-state
|
||||
;; ancestry must still reject an owner that used to be inside scroll.
|
||||
(puthash owner scroll-id parents)
|
||||
(plist-put overlapping-old-state :parent-table parents)
|
||||
(should-not (ebox-incremental--owner-disjoint-from-scroll-p
|
||||
(current-buffer) overlapping-old-state prepared
|
||||
candidate owner))))))
|
||||
|
||||
(ert-deftest ebox-commit-content-shift-preserves-later-scroll-targets ()
|
||||
"Longer and shorter local text retain the later scroll widget's targets."
|
||||
@ -221,6 +233,224 @@
|
||||
(should (= 1 (ebox--scroll-region-by scroll-id 1)))
|
||||
(should (= -1 (ebox--scroll-region-by scroll-id -1)))))))
|
||||
|
||||
(defun ebox-commit-test--scroll-family-root (left right color)
|
||||
"Return independent fixed-width LEFT, RIGHT and COLOR owners beside scroll."
|
||||
(ebox-test-column :key 'family-root :width '(120)
|
||||
(ebox-test-box :key 'left :source-identity 'left :width '(80)
|
||||
(ebox-test-text left))
|
||||
(ebox-test-box :key 'right :source-identity 'right :width '(80)
|
||||
(ebox-test-text right))
|
||||
(ebox-test-box :key 'paint :source-identity 'paint :color color
|
||||
(ebox-test-text "paint"))
|
||||
(ebox-commit-test--scroll-sibling-root "untouched")))
|
||||
|
||||
(defun ebox-commit-test--scroll-family-candidate (buffer color &optional content)
|
||||
"Return BUFFER candidate changing COLOR and, when CONTENT, both text owners."
|
||||
(let ((candidate (ebox-candidate-begin buffer)))
|
||||
(when content
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'left
|
||||
(ebox-test-box :key 'left :source-identity 'left :width '(80)
|
||||
(ebox-test-text "left-new")))
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'right
|
||||
(ebox-test-box :key 'right :source-identity 'right :width '(80)
|
||||
(ebox-test-text "right-new"))))
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'paint
|
||||
(if color
|
||||
(ebox-test-box :key 'paint :source-identity 'paint :color color
|
||||
(ebox-test-text "paint"))
|
||||
(ebox-test-box :key 'paint :source-identity 'paint
|
||||
(ebox-test-text "paint"))))
|
||||
candidate))
|
||||
|
||||
(defun ebox-commit-test--assert-scroll-family-output (left right color)
|
||||
"Assert current buffer contains LEFT, RIGHT, and the painted COLOR."
|
||||
(should (string-match-p left (buffer-string)))
|
||||
(should (string-match-p right (buffer-string)))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(search-forward "paint")
|
||||
(should (equal color
|
||||
(ebox-commit-test--face-value
|
||||
(get-text-property (1- (point)) 'face) :foreground)))))
|
||||
|
||||
(ert-deftest ebox-commit-paint-retains-disjoint-scroll ()
|
||||
"Pure sibling paint retains scroll caches and stays painted after scrolling."
|
||||
(with-temp-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-root "left-old" "right-old" "#123456"))
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll-id (car (plist-get state :scroll-region-ids)))
|
||||
(scroll (gethash scroll-id ebox--scroll-global-state))
|
||||
(raw (plist-get scroll :content-lines))
|
||||
(rendered (plist-get scroll :rendered-content-lines))
|
||||
(region-count (hash-table-count ebox--region-box-table))
|
||||
(root-render (symbol-function 'ebox-surface--render-candidate))
|
||||
(root-renders 0)
|
||||
report)
|
||||
(should scroll-id)
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (candidate-state)
|
||||
(cl-incf root-renders)
|
||||
(funcall root-render candidate-state))))
|
||||
(setq report
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-candidate
|
||||
(current-buffer) "#654321"))))
|
||||
(should (eq (plist-get report :projection-kind) 'paint))
|
||||
(should (zerop root-renders))
|
||||
(should-not (plist-get report :tp-full-root))
|
||||
(should (= 1 (tp-signal-subscriber-count
|
||||
(ebox-surface--signals-scroll
|
||||
ebox-surface--context-signals))))
|
||||
(let ((next-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(should (eq raw (plist-get next-scroll :content-lines)))
|
||||
(should (eq rendered (plist-get next-scroll :rendered-content-lines)))
|
||||
(should (eq (plist-get next-scroll :box)
|
||||
(gethash scroll-id ebox--region-box-table)))
|
||||
(should (= region-count (hash-table-count ebox--region-box-table))))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-old" "right-old" "#654321")
|
||||
(should (= 1 (ebox--scroll-region-by scroll-id 1)))
|
||||
(should (string-match-p "line-c" (buffer-string)))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-old" "right-old" "#654321")
|
||||
(should (= -1 (ebox--scroll-region-by scroll-id -1)))
|
||||
(should (string-match-p "line-a" (buffer-string)))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-old" "right-old" "#654321"))))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-owners-retain-disjoint-scroll-and-rollback ()
|
||||
"Two text owners plus paint retain exact output, scroll caches and rollback."
|
||||
(with-temp-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-root "left-old" "right-old" "#123456"))
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll-id (car (plist-get state :scroll-region-ids)))
|
||||
(scroll (gethash scroll-id ebox--scroll-global-state))
|
||||
(box (gethash scroll-id ebox--region-box-table))
|
||||
(raw (plist-get scroll :content-lines))
|
||||
(rendered (plist-get scroll :rendered-content-lines))
|
||||
(region-count (hash-table-count ebox--region-box-table))
|
||||
(before (buffer-string))
|
||||
(footprint (ebox--rendered-span-footprint-signature before))
|
||||
(expected (replace-regexp-in-string
|
||||
"right-old" "right-new"
|
||||
(replace-regexp-in-string "left-old" "left-new"
|
||||
(substring-no-properties before))))
|
||||
(root-render (symbol-function 'ebox-surface--render-candidate))
|
||||
(root-renders 0)
|
||||
report)
|
||||
(should scroll-id)
|
||||
(cl-letf (((symbol-function 'accept-change-group)
|
||||
(lambda (_) (error "Reject mixed sibling publication"))))
|
||||
(should
|
||||
(equal
|
||||
(should-error
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-candidate
|
||||
(current-buffer) "#654321" t)))
|
||||
'(error "Reject mixed sibling publication"))))
|
||||
(should (eq state (ebox--buffer-render-state (current-buffer))))
|
||||
(should (equal-including-properties before (buffer-string)))
|
||||
(should (eq scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(should (eq box (gethash scroll-id ebox--region-box-table)))
|
||||
(should (eq raw (plist-get scroll :content-lines)))
|
||||
(should (eq rendered (plist-get scroll :rendered-content-lines)))
|
||||
(should (= region-count (hash-table-count ebox--region-box-table)))
|
||||
(should (= 1 (ebox--scroll-region-by scroll-id 1)))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-old" "right-old" "#123456")
|
||||
(should (= -1 (ebox--scroll-region-by scroll-id -1)))
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (candidate-state)
|
||||
(cl-incf root-renders)
|
||||
(funcall root-render candidate-state))))
|
||||
(setq report
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-candidate
|
||||
(current-buffer) "#654321" t))))
|
||||
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||
(should (zerop root-renders))
|
||||
(should-not (plist-get report :tp-full-root))
|
||||
(should (equal expected (buffer-substring-no-properties (point-min) (point-max))))
|
||||
(should (equal footprint
|
||||
(ebox--rendered-span-footprint-signature (buffer-string))))
|
||||
(let ((next-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(should (eq raw (plist-get next-scroll :content-lines)))
|
||||
(should (eq rendered (plist-get next-scroll :rendered-content-lines)))
|
||||
(should (eq (plist-get next-scroll :box)
|
||||
(gethash scroll-id ebox--region-box-table)))
|
||||
(should (= region-count (hash-table-count ebox--region-box-table))))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-new" "right-new" "#654321")
|
||||
(should (= 1 (ebox--scroll-region-by scroll-id 1)))
|
||||
(should (string-match-p "line-c" (buffer-string)))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-new" "right-new" "#654321")
|
||||
(should (= -1 (ebox--scroll-region-by scroll-id -1)))
|
||||
(should (string-match-p "line-a" (buffer-string)))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-new" "right-new" "#654321"))))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-paint-followups-replace-and-remove-prior-color ()
|
||||
"Mixed paint must not reappear after pure paint, removal, rollback or scroll."
|
||||
(with-temp-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-root "left-old" "right-old" "#123456"))
|
||||
(let* ((initial (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll-id (car (plist-get initial :scroll-region-ids)))
|
||||
(scroll (gethash scroll-id ebox--scroll-global-state))
|
||||
(raw (plist-get scroll :content-lines))
|
||||
(rendered (plist-get scroll :rendered-content-lines))
|
||||
(paint-id (plist-get (ebox--host-ref-node (current-buffer) 'paint) :node-id)))
|
||||
(dolist (stage '(("#0000FF" t mixed-owner-reflow)
|
||||
("#00FF00" nil paint)
|
||||
(nil nil paint)))
|
||||
(let ((color (car stage))
|
||||
(content-p (cadr stage)))
|
||||
(ert-info ((format "paint transition: %S" stage))
|
||||
(when (equal color "#00FF00")
|
||||
(let ((before (buffer-string))
|
||||
(state (ebox--buffer-render-state (current-buffer)))
|
||||
(old-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(cl-letf (((symbol-function 'accept-change-group)
|
||||
(lambda (_) (error "Reject followup paint"))))
|
||||
(should
|
||||
(equal
|
||||
(should-error
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-candidate (current-buffer) color)))
|
||||
'(error "Reject followup paint"))))
|
||||
(should (eq state (ebox--buffer-render-state (current-buffer))))
|
||||
(should (eq old-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(should (equal-including-properties before (buffer-string)))))
|
||||
(let ((report
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-candidate
|
||||
(current-buffer) color content-p))))
|
||||
(should (eq (plist-get report :projection-kind) (nth 2 stage))))
|
||||
(should (= paint-id (plist-get (ebox--host-ref-node (current-buffer) 'paint)
|
||||
:node-id)))
|
||||
(dolist (delta '(0 1 -1))
|
||||
(unless (zerop delta)
|
||||
(should (= delta (ebox--scroll-region-by scroll-id delta))))
|
||||
(ebox-commit-test--assert-scroll-family-output "left-new" "right-new" color)
|
||||
(let* ((contents (buffer-string))
|
||||
(start (string-match "paint" contents)))
|
||||
(dotimes (offset (length "paint"))
|
||||
(should (equal (get-text-property (+ start offset) 'face contents)
|
||||
(and color (list :foreground color)))))))
|
||||
(should (string-match-p "line-a" (buffer-string)))
|
||||
(let ((next-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(should (eq raw (plist-get next-scroll :content-lines)))
|
||||
(should (eq rendered (plist-get next-scroll :rendered-content-lines)))
|
||||
(should (eq (plist-get next-scroll :box)
|
||||
(gethash scroll-id ebox--region-box-table))))))))))
|
||||
|
||||
(ert-deftest ebox-commit-disjoint-scroll-validates-rendered-font-extent ()
|
||||
"Equal raw widths do not authorize changed rendered glyph widths."
|
||||
(let ((measure (symbol-function 'ebox--string-pixel-width)))
|
||||
@ -2158,15 +2388,21 @@ remain retained identities."
|
||||
:foreground))))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(defun ebox-commit-test--fixed-basis-selection-root (row-1 row-2)
|
||||
"Return a stretched fixed-basis panel containing two selectable rows."
|
||||
(let ((panel
|
||||
(ebox-test-box
|
||||
:key 'selection-panel :width 'stretch :min-width 0 :min-height 24
|
||||
:flex-grow 2 :flex-shrink 1 :flex-basis '(340)
|
||||
(defun ebox-commit-test--fixed-basis-selection-root (row-1 row-2 &optional scroll)
|
||||
"Return a fixed-basis panel with two rows, inside a scroll box when SCROLL."
|
||||
(let* ((rows
|
||||
(ebox-test-column
|
||||
(ebox-test-box :key 'row-1 :source-identity 'row-1 (ebox-test-text row-1))
|
||||
(ebox-test-box :key 'row-2 :source-identity 'row-2 (ebox-test-text row-2))))))
|
||||
(ebox-test-box :key 'row-2 :source-identity 'row-2 (ebox-test-text row-2))))
|
||||
(panel
|
||||
(ebox-test-box
|
||||
:key 'selection-panel :width 'stretch :min-width 0 :min-height 24
|
||||
:flex-grow 2 :flex-shrink 1 :flex-basis '(340)
|
||||
(if scroll
|
||||
(ebox-test-box :key 'scroll :id "scroll" :height 2 :width '(80)
|
||||
:overflow 'scroll
|
||||
(ebox-test-column rows (ebox-test-text "line-c\nline-d")))
|
||||
rows))))
|
||||
(ebox-test-flex
|
||||
:key 'fixed-basis-selection-root
|
||||
:width '(900) :height 24
|
||||
@ -2188,6 +2424,125 @@ remain retained identities."
|
||||
(ebox-test-box :key 'row-2 :source-identity 'row-2 (ebox-test-text row-2)))
|
||||
candidate))
|
||||
|
||||
(ert-deftest ebox-commit-grouped-owners-retain-disjoint-scroll ()
|
||||
"A stable slot groups two text edits, with optional paint, beside scroll."
|
||||
(dolist (paint-p '(nil t))
|
||||
(ert-info ((format "grouped geometry with external paint: %S" paint-p))
|
||||
(with-temp-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-test-column :width '(900)
|
||||
(if paint-p
|
||||
(ebox-test-box :width '(200) :height 2
|
||||
(ebox-test-column :key 'header :source-identity 'header
|
||||
(ebox-test-text "[x] row 1" :key 'row-1 :source-identity 'row-1)
|
||||
(ebox-test-text "[ ] row 2" :key 'row-2 :source-identity 'row-2)))
|
||||
(ebox-commit-test--fixed-basis-selection-root "[x] row 1" "[ ] row 2"))
|
||||
(ebox-test-box :key 'paint :source-identity 'paint :color "#123456"
|
||||
(ebox-test-text "paint"))
|
||||
(ebox-commit-test--scroll-sibling-root "untouched")))
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll-id (car (plist-get state :scroll-region-ids)))
|
||||
(scroll (gethash scroll-id ebox--scroll-global-state))
|
||||
(raw (plist-get scroll :content-lines))
|
||||
(rendered (plist-get scroll :rendered-content-lines))
|
||||
(region-count (hash-table-count ebox--region-box-table))
|
||||
(owner-id
|
||||
(if paint-p
|
||||
(plist-get (ebox--host-ref-node (current-buffer) 'header) :node-id)
|
||||
(ebox-incremental--nearest-fixed-basis-flex-item-owner-id
|
||||
(current-buffer)
|
||||
(plist-get (ebox--host-ref-node (current-buffer) 'row-1) :node-id))))
|
||||
(before (buffer-substring-no-properties (point-min) (point-max)))
|
||||
(footprint (ebox--rendered-span-footprint-signature (buffer-string)))
|
||||
(expected (replace-regexp-in-string
|
||||
(regexp-quote "[ ] row 2") "[x] row 2"
|
||||
(replace-regexp-in-string
|
||||
(regexp-quote "[x] row 1") "[ ] row 1" before)))
|
||||
(root-render (symbol-function 'ebox-surface--render-candidate))
|
||||
(root-renders 0)
|
||||
report)
|
||||
(should scroll-id)
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (candidate-state)
|
||||
(cl-incf root-renders)
|
||||
(funcall root-render candidate-state))))
|
||||
(let ((candidate
|
||||
(if paint-p
|
||||
(ebox-candidate-begin (current-buffer))
|
||||
(ebox-commit-test--fixed-basis-selection-candidate
|
||||
(current-buffer) "[ ] row 1" "[x] row 2"))))
|
||||
(when paint-p
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'row-1
|
||||
(ebox-test-text "[ ] row 1" :key 'row-1 :source-identity 'row-1))
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'row-2
|
||||
(ebox-test-text "[x] row 2" :key 'row-2 :source-identity 'row-2))
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'paint
|
||||
(ebox-test-box :key 'paint :source-identity 'paint :color "#654321"
|
||||
(ebox-test-text "paint"))))
|
||||
(setq report (ebox-commit (current-buffer) candidate))))
|
||||
(if paint-p
|
||||
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||
(should (memq (plist-get report :projection-kind) '(span-patch owner-scoped))))
|
||||
(should (equal (plist-get report :owner-ids)
|
||||
(append (list owner-id)
|
||||
(when paint-p
|
||||
(list (plist-get
|
||||
(ebox--host-ref-node (current-buffer) 'paint)
|
||||
:node-id))))))
|
||||
(should (zerop root-renders))
|
||||
(should-not (plist-get report :tp-full-root))
|
||||
(should (equal expected (buffer-substring-no-properties (point-min) (point-max))))
|
||||
(should (equal footprint
|
||||
(ebox--rendered-span-footprint-signature (buffer-string))))
|
||||
(let ((next-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||
(should (eq raw (plist-get next-scroll :content-lines)))
|
||||
(should (eq rendered (plist-get next-scroll :rendered-content-lines)))
|
||||
(should (eq (plist-get next-scroll :box)
|
||||
(gethash scroll-id ebox--region-box-table)))
|
||||
(should (= region-count (hash-table-count ebox--region-box-table))))
|
||||
(should (= 1 (ebox--scroll-region-by scroll-id 1)))
|
||||
(should (string-match-p "line-c" (buffer-string)))
|
||||
(should (string-match-p (regexp-quote "[x] row 2") (buffer-string)))
|
||||
(should (= -1 (ebox--scroll-region-by scroll-id -1)))
|
||||
(should (string-match-p "line-a" (buffer-string)))
|
||||
(should (equal expected (buffer-substring-no-properties (point-min) (point-max))))
|
||||
(ebox-commit-test--assert-scroll-family-output
|
||||
(regexp-quote "[ ] row 1") (regexp-quote "[x] row 2")
|
||||
(if paint-p "#654321" "#123456")))))))
|
||||
|
||||
(ert-deftest ebox-commit-group-containing-scroll-refreshes-cache ()
|
||||
"A group containing scrolling descendants must rebuild their cached text."
|
||||
(with-temp-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-commit-test--fixed-basis-selection-root "[x] row 1" "[ ] row 2" t))
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll-id (car (plist-get state :scroll-region-ids)))
|
||||
(raw (plist-get (gethash scroll-id ebox--scroll-global-state)
|
||||
:content-lines))
|
||||
(report
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--fixed-basis-selection-candidate
|
||||
(current-buffer) "[ ] row 1" "[x] row 2"))))
|
||||
(should scroll-id)
|
||||
(should-not (memq (plist-get report :projection-kind)
|
||||
'(span-patch owner-scoped)))
|
||||
(should-not (eq raw (plist-get (gethash scroll-id ebox--scroll-global-state)
|
||||
:content-lines)))
|
||||
(should (= 1 (ebox--scroll-region-by scroll-id 1)))
|
||||
(should (string-match-p "line-c" (buffer-string)))
|
||||
(should (string-match-p (regexp-quote "[x] row 2") (buffer-string)))
|
||||
(should (= -1 (ebox--scroll-region-by scroll-id -1)))
|
||||
(should (string-match-p (regexp-quote "[ ] row 1") (buffer-string)))
|
||||
(should (string-match-p (regexp-quote "[x] row 2") (buffer-string)))
|
||||
(should-not (string-match-p (regexp-quote "[x] row 1") (buffer-string)))
|
||||
(should-not (string-match-p (regexp-quote "[ ] row 2") (buffer-string))))))
|
||||
|
||||
(ert-deftest ebox-commit-fixed-basis-selection-round-trip-stays-local ()
|
||||
"Continuous row-1 -> row-2 -> row-1 publication keeps local TP scope."
|
||||
(let ((planner-render-count 0)
|
||||
@ -2389,6 +2744,78 @@ remain retained identities."
|
||||
(ebox-commit-test--buffer-string buffer))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-commit-range-splice-paint-preserves-kept-identities-and-faces ()
|
||||
"Range object replacement falls back with current paint and kept peers."
|
||||
(with-temp-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-test-column :width '(120)
|
||||
(ebox-test-box :key 'keep :source-identity 'keep :height 1
|
||||
(ebox-test-text "keep"))
|
||||
(ebox-test-box :width '(80) :height 1
|
||||
(ebox-test-column :key 'rows :source-identity 'rows
|
||||
(ebox-test-child-range
|
||||
'items
|
||||
(ebox-test-box :key 'old :source-identity 'old :height 1
|
||||
(ebox-test-text "old")))))
|
||||
(ebox-test-box :key 'paint :source-identity 'paint :color "#123456"
|
||||
(ebox-test-text "paint"))))
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(keep-id (plist-get (ebox--host-ref-node (current-buffer) 'keep) :node-id))
|
||||
(old-id (plist-get (ebox--host-ref-node (current-buffer) 'old) :node-id))
|
||||
(paint-id (plist-get (ebox--host-ref-node (current-buffer) 'paint) :node-id))
|
||||
(objects (plist-get state :surface-node-object-table))
|
||||
(keep-object (gethash keep-id objects))
|
||||
(paint-object (gethash paint-id objects))
|
||||
(candidate (ebox-candidate-begin (current-buffer)))
|
||||
(root-render (symbol-function 'ebox-surface--render-candidate))
|
||||
(root-renders 0)
|
||||
report)
|
||||
(ebox-candidate-replace-range-ref
|
||||
candidate 'items
|
||||
(ebox-test-box :key 'new :source-identity 'new :height 1
|
||||
(ebox-test-text "new")))
|
||||
(ebox-candidate-replace-host-ref
|
||||
candidate 'paint
|
||||
(ebox-test-box :key 'paint :source-identity 'paint :color "#0000FF"
|
||||
(ebox-test-text "paint")))
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (candidate-state)
|
||||
(cl-incf root-renders)
|
||||
(should (ebox-surface--mixed-range-splice-p candidate-state))
|
||||
(funcall root-render candidate-state))))
|
||||
(setq report (ebox-commit (current-buffer) candidate)))
|
||||
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||
(should (= 1 root-renders))
|
||||
(should-not (plist-get report :tp-retained-content))
|
||||
(should (> (plist-get report :created-objects) 0))
|
||||
(should (> (plist-get report :removed-objects) 0))
|
||||
(should-not (ebox--host-ref-node (current-buffer) 'old))
|
||||
(should-not (= old-id (plist-get (ebox--host-ref-node (current-buffer) 'new)
|
||||
:node-id)))
|
||||
(ebox-commit-test--assert-scroll-family-output "keep" "new" "#0000FF")
|
||||
(dolist (color '("#0000FF" "#00FF00"))
|
||||
(when (equal color "#00FF00")
|
||||
(let ((paint-report
|
||||
(ebox-commit
|
||||
(current-buffer)
|
||||
(ebox-commit-test--scroll-family-candidate (current-buffer) color))))
|
||||
(should (eq (plist-get paint-report :projection-kind) 'paint))))
|
||||
(ebox-commit-test--assert-scroll-family-output "keep" "new" color)
|
||||
(let* ((next (ebox--buffer-render-state (current-buffer)))
|
||||
(next-objects (plist-get next :surface-node-object-table))
|
||||
(contents (buffer-string))
|
||||
(start (string-match "paint" contents)))
|
||||
(should (= keep-id (plist-get (ebox--host-ref-node (current-buffer) 'keep)
|
||||
:node-id)))
|
||||
(should (= paint-id (plist-get (ebox--host-ref-node (current-buffer) 'paint)
|
||||
:node-id)))
|
||||
(should (eq keep-object (gethash keep-id next-objects)))
|
||||
(should (eq paint-object (gethash paint-id next-objects)))
|
||||
(dotimes (offset (length "paint"))
|
||||
(should (equal (get-text-property (+ start offset) 'face contents)
|
||||
(list :foreground color)))))))))
|
||||
|
||||
|
||||
(defun ebox-commit-test--allocation-closure-root
|
||||
(toast paint-a paint-b &optional width footer-overflow root-overflow)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user