(defpackage :cl-tty-framebuffer-test (:use :cl :fiveam :cl-tty.rendering :cl-tty.backend)) (in-package :cl-tty-framebuffer-test) (def-suite framebuffer-suite :description "Framebuffer rendering pipeline tests") (in-suite framebuffer-suite) (test make-framebuffer-creates-correct-size (let ((fb (make-framebuffer 80 24))) (is (= 24 (framebuffer-height fb))) (is (= 80 (framebuffer-width fb))))) (test cell-defaults-are-space (let ((cell (aref (make-framebuffer 10 10) 0 0))) (is (eql #\space (cell-char cell))) (is (null (cell-fg cell))) (is (null (cell-bg cell))))) (test draw-text-on-fb-sets-cells (let ((fb (make-framebuffer-backend))) (draw-text fb 2 3 "abc" :red nil) (let ((cells (fb-framebuffer fb))) (is (eql #\a (cell-char (aref cells 3 2)))) (is (eql #\b (cell-char (aref cells 3 3)))) (is (eql #\c (cell-char (aref cells 3 4)))) (is (eql :red (cell-fg (aref cells 3 2))))))) (test draw-text-clips-at-bounds (let ((fb (make-framebuffer-backend :width 10 :height 5))) (draw-text fb 8 2 "hello" nil nil) (let ((cells (fb-framebuffer fb))) (is (eql #\h (cell-char (aref cells 2 8)))) (is (eql #\e (cell-char (aref cells 2 9)))) (is (eql #\space (cell-char (aref cells 2 0))) "out of bounds text is ignored")))) (test diff-identical-fbs-returns-empty (let ((fb1 (make-framebuffer 80 24)) (fb2 (make-framebuffer 80 24))) (is (null (diff-framebuffers fb1 fb2))))) (test diff-changed-fb-returns-changes (let* ((fb1 (make-framebuffer 10 10)) (fb2 (make-framebuffer 10 10))) (setf (aref fb2 5 5) (make-cell :char #\X :fg :red)) (let ((changes (diff-framebuffers fb1 fb2))) (is (= 1 (length changes))) (destructuring-bind (x y cell) (first changes) (is (= 5 x)) (is (= 5 y)) (is (eql #\X (cell-char cell))))))) (test with-scissor-clips-drawing (let ((fb (make-framebuffer-backend :width 20 :height 10))) (with-scissor (fb 5 5 3 3) (draw-text fb 6 6 "ABC" nil nil) (draw-text fb 1 1 "OUTSIDE" nil nil)) (let ((cells (fb-framebuffer fb))) (is (eql #\A (cell-char (aref cells 6 6))) "inside scissor draws") (is (eql #\space (cell-char (aref cells 1 1))) "outside scissor is clipped")))) (test flush-fb-copies-to-backend (let* ((real-be (make-simple-backend :output-stream (make-string-output-stream))) (fb (make-framebuffer-backend))) (draw-text fb 0 0 "X" :red nil) (let ((changed (flush-framebuffer (make-framebuffer 80 24) (fb-framebuffer fb) real-be))) (is (>= changed 1))))) ;; ── Frame inspection ────────────────────────────────────────── (test fb-cell-link-url-returns-nil-for-blank-cell (let ((fb (make-framebuffer 10 10))) (is (null (fb-cell-link-url fb 5 5))))) (test fb-cell-link-url-finds-link-url (let ((fb (make-framebuffer-backend))) (draw-text fb 0 0 "click" nil nil :link-url "https://example.com") (is (equal "https://example.com" (fb-cell-link-url (fb-framebuffer fb) 0 0))) (is (null (fb-cell-link-url (fb-framebuffer fb) 5 5))))) (test fb-cell-link-url-out-of-bounds-returns-nil (let ((fb (make-framebuffer 5 5))) (is (null (fb-cell-link-url fb 10 10))))) (test extract-text-single-row (let ((fb (make-framebuffer-backend))) (draw-text fb 0 0 "hello" nil nil) (let ((cells (fb-framebuffer fb))) (is (equal "hello" (extract-text cells 0 0 4 0)))))) (test extract-text-multi-row (let ((fb (make-framebuffer-backend))) (draw-text fb 0 0 "abc" nil nil) (draw-text fb 0 1 "def" nil nil) (let* ((cells (fb-framebuffer fb)) (text (extract-text cells 0 0 2 1))) (is (equal "abc def" text)))))