;; Test service composition defcomps — replaces Python string concatenation ;; in test/sxc/pages/__init__.py. ;; Service filter nav links (defcomp ~components/test-service-nav (&key services active-service) (<> (~shared:layout/nav-link :href "/" :label "all" :is-selected (if (not active-service) "true" nil) :select-colours "aria-selected:bg-sky-200 aria-selected:text-sky-900") (map (lambda (svc) (~shared:layout/nav-link :href (str "/?service=" svc) :label svc :is-selected (if (= active-service svc) "true" nil) :select-colours "aria-selected:bg-sky-200 aria-selected:text-sky-900")) services))) ;; Test header menu row (defcomp ~components/test-header-row (&key services active-service) (~shared:layout/menu-row-sx :id "test-row" :level 1 :colour "sky" :link-href "/" :link-label "Tests" :icon "fa fa-flask" :nav (~components/test-service-nav :services services :active-service active-service) :child-id "test-header-child")) ;; Layout: full page header stack (reads root header values from env free variables) (defcomp ~components/test-layout-full (&key services active-service) (<> (~root-header-auto) (~shared:layout/header-child-sx :inner (~components/test-header-row :services services :active-service active-service)))) ;; Map test dicts to test-row components (defcomp ~components/test-rows (&key tests) (<> (map (lambda (t) (~test-row :nodeid (get t "nodeid") :outcome (get t "outcome") :duration (str (get t "duration")) :longrepr (or (get t "longrepr") ""))) tests))) ;; Grouped test rows with service headers (defcomp ~components/test-grouped-rows (&key sections) (<> (map (lambda (sec) (<> (~test-service-header :service (get sec "service") :total (str (get sec "total")) :passed (str (get sec "passed")) :failed (str (get sec "failed"))) (~components/test-rows :tests (get sec "tests")))) sections))) ;; Results partial: conditional rendering based on running/result state (defcomp ~components/test-results-partial (&key status summary-data tests sections has-failures) (let* ((state (get summary-data "state"))) (<> (~test-summary :status (get summary-data "status") :passed (get summary-data "passed") :failed (get summary-data "failed") :errors (get summary-data "errors") :skipped (get summary-data "skipped") :total (get summary-data "total") :duration (get summary-data "duration") :last-run (get summary-data "last_run") :running (get summary-data "running") :csrf (get summary-data "csrf") :active-filter (get summary-data "active_filter")) (cond ((= state "running") (~test-running-indicator)) ((= state "no-results") (~test-no-results)) ((= state "empty-filtered") (~test-no-results)) (true (~test-results-table :rows (~components/test-grouped-rows :sections sections) :has-failures has-failures)))))) ;; Wrap results in a div with optional HTMX polling (defcomp ~components/test-results-wrap (&key running inner) (div :id "test-results" :class "space-y-6 p-4" :sx-get (when running "/results") :sx-trigger (when running "every 2s") :sx-swap (when running "outerHTML") inner)) ;; Test detail section wrapper (defcomp ~components/test-detail-section (&key test) (section :id "main-panel" :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport" (~test-detail :nodeid (get test "nodeid") :outcome (get test "outcome") :duration (str (get test "duration")) :longrepr (or (get test "longrepr") "")))) ;; Detail page header stack (reads root header values from env free variables) (defcomp ~components/test-detail-layout-full (&key services test-nodeid test-label) (<> (~root-header-auto) (~shared:layout/header-child-sx :inner (<> (~components/test-header-row :services services) (~shared:layout/header-child-sx :id "test-header-child" :inner (~shared:layout/menu-row-sx :id "test-detail-row" :level 2 :colour "sky" :link-href (str "/test/" test-nodeid) :link-label test-label))))))