(import test) (include "entity.scm") (import (downstroke entity)) (test-begin "entity") ;; Test: entity-ref retrieves values from entity alists (test-group "entity-ref" (let ((entity '((#:type . player) (#:x . 100) (#:y . 200) (#:width . 16) (#:height . 16)))) (test "retrieves type" 'player (entity-ref entity #:type)) (test "retrieves x" 100 (entity-ref entity #:x)) (test "retrieves y" 200 (entity-ref entity #:y)) (test "retrieves width" 16 (entity-ref entity #:width)) (test "retrieves height" 16 (entity-ref entity #:height))) ;; Test with default value (let ((entity '((#:type . player)))) (test "returns default for missing key" 99 (entity-ref entity #:x 99)) (test "returns #f as default if not specified" #f (entity-ref entity #:missing-key)))) ;; Test: entity-ref with procedure as default (test-group "entity-ref-with-procedure-default" (let ((entity '((#:type . player)))) (test "calls procedure default when key missing" 42 (entity-ref entity #:x (lambda () 42))))) ;; Test: make-player-entity creates valid player entity (test-group "make-entity" (let ((player (make-entity 50 75 16 16))) (test-assert "returns a list" (list? player)) (test "has correct x" 50 (entity-ref player #:x)) (test "has correct y" 75 (entity-ref player #:y)) (test "has correct width" 16 (entity-ref player #:width)) (test "has correct height" 16 (entity-ref player #:height)))) ;; Test: entity-type extracts type from entity (test-group "entity-type" (let ((player '((#:type . player) (#:x . 100))) (enemy '((#:type . enemy) (#:x . 200)))) (test "extracts player type" 'player (entity-type player)) (test "extracts enemy type" 'enemy (entity-type enemy))) (let ((no-type '((#:x . 100) (#:y . 200)))) (test "returns #f for entity without type" #f (entity-type no-type)))) ;; Test: complex entity with multiple properties (test-group "complex-entity" (let ((entity '((#:type . enemy) (#:x . 100) (#:y . 200) (#:width . 16) (#:height . 16) (#:health . 50) (#:speed . 2.5) (#:ai-state . patrol)))) (test "retrieves numeric property" 50 (entity-ref entity #:health)) (test "retrieves float property" 2.5 (entity-ref entity #:speed)) (test "retrieves symbol property" 'patrol (entity-ref entity #:ai-state)))) ;; Test: entity-set updates entity properties (test-group "entity-set" (test-group "existing key is replaced" (let ((e (entity-set '((#:x . 10) (#:y . 20)) #:x 15))) (test "value updated" 15 (entity-ref e #:x)) (test "other key untouched" 20 (entity-ref e #:y)) ;; alist length stays at 2 (one pair removed, one added) — not 3. (test "no duplicate key: list length unchanged" 2 (length e)))) (test-group "new key is added" (let ((e (entity-set '((#:x . 10)) #:vx 3))) (test "new key present" 3 (entity-ref e #:vx)) (test "existing key untouched" 10 (entity-ref e #:x)) (test "list grows by one pair" 2 (length e))))) (test-group "entity-set-many" (test-group "Set multiple entities with cons" (let ((e (entity-set-many '((#:x . 10) (#:y . 20)) '((#:x . 15) (#:y . 25))))) (test "value x updated" 15 (entity-ref e #:x)) (test "value y updated" 25 (entity-ref e #:y))))) ;; Test: entity-update applies transformations (test-group "entity-update" (test-group "transform existing value" (let ((e (entity-update '((#:x . 10) (#:y . 20)) #:x (lambda (v) (+ v 5))))) (test "#:x is 15" 15 (entity-ref e #:x)) (test "#:y is 20" 20 (entity-ref e #:y)))) (test-group "missing key uses default" (let ((e (entity-update '((#:x . 10)) #:health (lambda (v) (+ v 1)) 0))) (test "#:health is 1" 1 (entity-ref e #:health)))) (test-group "missing key without default" (let ((e (entity-update '((#:x . 10)) #:z (lambda (v) v)))) (test "#:z is #f" #f (entity-ref e #:z)))) (test-group "no duplicate keys" (let ((e (entity-update '((#:x . 10) (#:y . 20)) #:x (lambda (v) (* v 2))))) (test "length is 2" 2 (length e))))) (test-group "entity-skips-pipeline?" (test-assert "absent skip list" (not (entity-skips-pipeline? '((#:type . a)) 'gravity))) (test-assert "empty skip list" (not (entity-skips-pipeline? '((#:skip-pipelines . ())) 'gravity))) (test-assert "member" (entity-skips-pipeline? '((#:skip-pipelines . (gravity velocity-x))) 'gravity)) (test-assert "not member" (not (entity-skips-pipeline? '((#:skip-pipelines . (gravity))) 'velocity-x)))) (define-pipeline (fixture-pipeline fixture-skip) (scene_ ent _dt) (entity-set ent #:x 42)) (test-group "define-pipeline" (let ((e '((#:type . t) (#:x . 0)))) (test "runs body" 42 (entity-ref (fixture-pipeline #f e 0) #:x))) (let ((e '((#:type . t) (#:x . 0) (#:skip-pipelines . (fixture-skip))))) (test "skipped" 0 (entity-ref (fixture-pipeline #f e 0) #:x)))) (define-pipeline (guarded-pipeline guarded-skip) (scene_ ent _dt) guard: (entity-ref ent #:active? #f) (entity-set ent #:x 99)) (test-group "define-pipeline with guard:" (let ((e '((#:type . t) (#:x . 0) (#:active? . #t)))) (test "runs body when guard passes" 99 (entity-ref (guarded-pipeline #f e 0) #:x))) (let ((e '((#:type . t) (#:x . 0)))) (test "returns entity unchanged when guard fails" 0 (entity-ref (guarded-pipeline #f e 0) #:x))) (let ((e '((#:type . t) (#:x . 0) (#:active? . #t) (#:skip-pipelines . (guarded-skip))))) (test "skip-pipelines takes precedence over guard" 0 (entity-ref (guarded-pipeline #f e 0) #:x)))) (test-end "entity") (test-exit)