Files
felhom-controller/controller/internal/web/app_row_test.go
T
admin 3eacb6c326 Part A: shared app_list_row partial — ONE row grammar on four surfaces (v0.126.0)
- templates/app_row.html: app_list_row/app_list_row_end (layout_start/_end idiom) — icon+name
  (+optional secondary) left, caller action block right; compact 44px row; logoURL→PNG→
  optional FallbackIcon→hidden onerror chain
- applied: Távoli mentés toggle list, Visszaállítás restore-to-verify + .fab lists, dashboard
  Telepített alkalmazások (state edge + data-href preserved); Alkalmazások collapsed headers
  ALIGNED (icon+name left, status dot moved right before chevron; expander untouched)
- funcmap: dict + appHref; OffboxAppRow/AppBackupRow gain Slug
- style.css: .app-row family; stack-card/stack-info/stack-logo/stack-name/stack-desc/
  stack-actions rules retired (dashboard rows now shared); .app-backup-row-header matched
  to the shared row height
- scripts/app_row_dedup_gate.py: row markup single-sourced (red-proven: pasted old
  storage-path-item block → exit 1); render tests per surface (app_row_test.go)
- NO behavior change: toggle/download/expander actions byte-identical
2026-07-13 11:26:48 +02:00

132 lines
5.5 KiB
Go

package web
import (
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)
// v0.126.0 Part A — the shared app_list_row partial (app_row.html) is the ONE row source on
// the list surfaces. Per-surface render tests: the partial's marker classes are present, the
// old hand-rolled structures (storage-path-item rows / stack-card rows) are gone, and the
// caller-provided action blocks (behavior) are untouched.
// COMPANION red-proof (recorded in REPORT): paste one pre-0.126.0 storage-path-item row block
// back into backups_remote.html → scripts/app_row_dedup_gate.py exits 1.
func appRowSplitData() map[string]interface{} {
d := splitTestData()
d["OffboxApps"] = []OffboxAppRow{
{Name: "calibre-web", DisplayName: "Calibre-Web", Slug: "calibre-web", Enabled: true},
{Name: "radarr", DisplayName: "Radarr", Slug: "radarr", Enabled: false},
}
return d
}
func TestAppRow_RemoteToggleList(t *testing.T) {
html := renderBackupPage(t, "backups_remote", appRowSplitData())
if !strings.Contains(html, `class="app-row"`) || !strings.Contains(html, `app-row-name`) {
t.Error("remote toggle list must render through the app_list_row partial")
}
if strings.Contains(html, "storage-path-item") {
t.Error("old storage-path-item row structure survives on the remote page")
}
// Behavior unchanged: the toggle form + both label variants render as before.
if !strings.Contains(html, `action="/backup/offbox/toggle"`) {
t.Error("toggle form missing")
}
if !strings.Contains(html, "Távoli mentés kikapcsolása") || !strings.Contains(html, "Távoli mentés bekapcsolása") {
t.Error("toggle button labels changed")
}
}
func TestAppRow_RestoreLists(t *testing.T) {
html := renderBackupPage(t, "backups_restore", appRowSplitData())
if !strings.Contains(html, `class="app-row"`) {
t.Error("restore-page lists must render through the app_list_row partial")
}
if strings.Contains(html, "storage-path-item") {
t.Error("old storage-path-item row structure survives on the restore page")
}
// Behavior unchanged: restore-to-verify form + .fab download button.
if !strings.Contains(html, `action="/backup/offbox/restore"`) {
t.Error("restore-to-verify form missing")
}
if !strings.Contains(html, `fab-dl-btn`) || !strings.Contains(html, "Letöltés (.fab)") {
t.Error(".fab download action missing")
}
// The disabled app (radarr) must NOT appear in the restore-to-verify list (Enabled filter),
// but MUST appear in the .fab list — the partial must not have changed the filters.
if strings.Count(html, `value="radarr"`) != 0 {
t.Error("restore-to-verify must list only offbox-enabled apps")
}
if !strings.Contains(html, `data-stack="radarr"`) {
t.Error(".fab list must list all apps")
}
}
func TestAppRow_Dashboard(t *testing.T) {
data := map[string]interface{}{
"Page": "dashboard", "Title": "Vezérlőpult",
"Stacks": []stacks.Stack{
{Name: "radarr", Deployed: true, State: stacks.StateRunning,
Meta: stacks.Metadata{Slug: "radarr", DisplayName: "Radarr", Description: "Filmgyűjtemény kezelése"}},
},
"MissingStorage": map[string]string{},
"NetworkWarnings": map[string]string{},
"NetworkStubs": map[string]string{},
"Subdomains": map[string]string{"radarr": "radarr"},
"RunningCount": 1, "StoppedCount": 0, "TotalCount": 1,
"SystemInfo": system.SystemInfo{},
"BackupEnabled": false,
"Domain": "demo-felhom.eu",
}
html := renderBackupPage(t, "dashboard", data)
if !strings.Contains(html, `class="app-row stack-state-run"`) {
t.Error("dashboard rows must render through app_list_row with the state RowClass")
}
if !strings.Contains(html, `data-href="/apps/radarr"`) {
t.Error("dashboard row lost its data-href")
}
if !strings.Contains(html, "Filmgyűjtemény kezelése") {
t.Error("dashboard row lost the description secondary line")
}
if strings.Contains(html, "stack-card") || strings.Contains(html, "stack-info") {
t.Error("old stack-card row structure survives on the dashboard")
}
}
// The Alkalmazások collapsed header is ALIGNED to the row grammar (icon + name left,
// status + chevron right) while keeping its own expander structure.
func TestAppRow_AppsHeaderAligned(t *testing.T) {
d := appRowSplitData()
d["AppBackupRows"] = []AppBackupRow{
{StackName: "calibre-web", DisplayName: "Calibre-Web", Slug: "calibre-web",
Status: "green", Tier3State: "active"},
}
d["Backup"] = &backup.FullBackupStatus{
AppDataInfo: []backup.AppBackupInfo{{StackName: "calibre-web", DisplayName: "Calibre-Web"}},
}
d["Offbox"] = &settings.OffboxTarget{Enabled: true, Host: "nas.local", LastStatus: "ok", EscrowState: "escrowed"}
html := renderBackupPage(t, "backups_apps", d)
hdr := html[strings.Index(html, `class="app-backup-row-header"`):]
hdr = hdr[:strings.Index(hdr, "app-backup-row-detail")]
iIcon := strings.Index(hdr, "app-row-icon")
iName := strings.Index(hdr, "app-backup-row-name")
iDot := strings.Index(hdr, "status-dot")
iChevron := strings.Index(hdr, "expand-icon")
if iIcon < 0 || iName < 0 || iDot < 0 || iChevron < 0 {
t.Fatalf("aligned header pieces missing (icon=%d name=%d dot=%d chevron=%d)", iIcon, iName, iDot, iChevron)
}
if !(iIcon < iName && iName < iDot && iDot < iChevron) {
t.Error("header grammar must be icon, name, ..., status, chevron (left→right)")
}
if !strings.Contains(hdr, `onclick="toggleBackupDetail(this)"`) {
t.Error("expander behavior must be untouched")
}
}