package docs import ( "os" "path/filepath" "testing" "bookhoard/templates" ) func TestPlaceDoc(t *testing.T) { tests := []struct { path string section string group string pinned bool }{ {"index.md", "Getting Started", "", true}, {"user/user-guide.md", "User Guide", "", true}, {"user/dashboard.md", "User Guide", "", false}, {"user/admin-guide.md", "User Guide", "Administration", false}, {"user/devices/kobo-setup.md", "User Guide", "Devices", false}, {"developer/api/api-reference.md", "API Reference", "", true}, {"developer/api/dashboard.md", "API Reference", "", false}, {"developer/api/authentication/login.md", "API Reference", "Authentication", false}, {"developer/api/saved-filters/index.md", "API Reference", "Saved Filters", true}, {"developer/testing.md", "Developer", "", false}, {"developer/development.md", "Developer", "", true}, {"developer/scanner/audio-metadata.md", "Developer", "Scanner", false}, {"contract_pins.md", "Developer", "Reference", false}, {"FRONTEND_INTEGRATION.md", "Developer", "Reference", false}, {"development/calibre-opf-implementation.md", "Developer", "Reference", false}, {"operations/operations.md", "Operations", "", true}, {"operations/troubleshooting.md", "Operations", "", false}, {"contributing/contributing.md", "Contributing", "", true}, {"contributing/development.md", "Contributing", "", false}, } for _, tt := range tests { got := placeDoc(tt.path) if got.section != tt.section || got.group != tt.group { t.Errorf("placeDoc(%q) = (%q, %q), want (%q, %q)", tt.path, got.section, got.group, tt.section, tt.group) } if (got.pin < 0) != tt.pinned { t.Errorf("placeDoc(%q) pin = %d, pinned want %v", tt.path, got.pin, tt.pinned) } } } func TestDocTitle(t *testing.T) { tests := map[string]string{ "index.md": "Overview", "user/user-guide.md": "User Guide", "user/devices/koreader-setup.md": "KOReader Setup", "developer/development.md": "Developer Portal", "developer/api/api-reference.md": "Overview", "developer/api/users/update_profile.md": "Update Profile", "developer/api/book-matching/link_book.md": "Link Book", "operations/troubleshooting.md": "Troubleshooting", "contract_pins.md": "Contract Pins", } for path, want := range tests { if got := docTitle(path); got != want { t.Errorf("docTitle(%q) = %q, want %q", path, got, want) } } } func TestDocURL(t *testing.T) { if got := docURL("index.md"); got != "/docs" { t.Errorf("docURL(index.md) = %q, want /docs", got) } if got := docURL("user/sync-guide.md"); got != "/docs/user/sync-guide" { t.Errorf("docURL = %q, want /docs/user/sync-guide", got) } } // buildTestDocs creates a representative docs tree in a temp dir and returns // a handler over it. func buildTestDocs(t *testing.T) *DocsHandler { t.Helper() dir := t.TempDir() files := []string{ "index.md", "user/user-guide.md", "user/dashboard.md", "user/admin-guide.md", "user/devices/kobo-setup.md", "user/devices/koreader-setup.md", "developer/api/api-reference.md", "developer/api/authentication/login.md", "developer/api/authentication/overview.md", "developer/api/saved-filters/index.md", "developer/testing.md", "operations/troubleshooting.md", "contributing/development.md", "contract_pins.md", } for _, f := range files { path := filepath.Join(dir, f) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte("# Test\n"), 0o644); err != nil { t.Fatal(err) } } return &DocsHandler{docsFS: os.DirFS(dir), docsPath: dir} } func TestBuildNavigation(t *testing.T) { h := buildTestDocs(t) // Current page: a device setup guide → User Guide section and its // Devices group must render expanded; everything else collapsed. nav := h.BuildNavigation("/docs/user/devices/kobo-setup") if len(nav.Sections) == 0 { t.Fatal("no sections built") } wantOrder := []string{"Getting Started", "User Guide", "API Reference", "Developer", "Operations", "Contributing"} for i, want := range wantOrder { if nav.Sections[i].Title != want { t.Fatalf("section %d = %q, want %q", i, nav.Sections[i].Title, want) } } getting := nav.Sections[0] if getting.Collapsed { t.Error("Getting Started should be expanded (root overview pinned)") } if len(getting.Items) != 1 || getting.Items[0].Title != "Overview" || getting.Items[0].URL != "/docs" { t.Errorf("Getting Started items = %+v, want single Overview → /docs", getting.Items) } userGuide := nav.Sections[1] if userGuide.Collapsed { t.Error("User Guide should be expanded: it contains the current page") } if len(userGuide.Items) != 2 || userGuide.Items[0].Title != "User Guide" { t.Errorf("User Guide direct items = %+v, want User Guide pinned first", userGuide.Items) } var devices *templates.NavGroup for i := range userGuide.Groups { if userGuide.Groups[i].Title == "Devices" { devices = &userGuide.Groups[i] } } if devices == nil { t.Fatal("User Guide has no Devices group") } if devices.Collapsed { t.Error("Devices group should be expanded: it contains the current page") } if len(devices.Items) != 2 || devices.Items[0].Title != "Kobo Setup" { t.Errorf("Devices items = %+v, want Kobo Setup + KOReader Setup", devices.Items) } // Everything else starts collapsed. for _, s := range nav.Sections[2:] { if !s.Collapsed { t.Errorf("section %q should be collapsed (current page is elsewhere)", s.Title) } } // API Reference: subdirectory groups with the index page as Overview. api := nav.Sections[2] if len(api.Groups) != 2 { t.Fatalf("API Reference groups = %d, want 2 (Authentication, Saved Filters)", len(api.Groups)) } if api.Groups[0].Title != "Authentication" || len(api.Groups[0].Items) != 2 { t.Errorf("Authentication group = %+v", api.Groups[0]) } saved := api.Groups[1] if saved.Title != "Saved Filters" || len(saved.Items) != 1 || saved.Items[0].Title != "Overview" { t.Errorf("Saved Filters group = %+v, want single Overview item", saved) } if !saved.Collapsed { t.Error("Saved Filters group should be collapsed on an unrelated page") } // Stray root files land in Developer → Reference. dev := nav.Sections[3] if len(dev.Groups) != 1 || dev.Groups[0].Title != "Reference" { t.Errorf("Developer groups = %+v, want Reference", dev.Groups) } } func TestBuildNavigationNothingCurrent(t *testing.T) { h := buildTestDocs(t) nav := h.BuildNavigation("/docs/nonexistent") open := 0 for _, s := range nav.Sections { if !s.Collapsed { open++ } } if open != 1 { t.Errorf("expected exactly one section open on a 404 path, got %d", open) } if nav.Sections[0].Collapsed { t.Error("fallback should open the first section") } }