Files
2026-03-15 10:31:41 -07:00

200 lines
5.9 KiB
Go

package docs
import (
"strings"
"testing"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/x/ansi"
)
func sampleDocs() []docEntry {
return []docEntry{
{
Name: "Getting Started",
Content: "# Getting Started\n\n- first step\n- second step",
Description: "Getting Started",
},
{
Name: "Operators",
Content: "# Operators\n\nTeam workflows",
Description: "Team workflows",
},
{
Name: "Networking",
Content: "# Networking\n\nTransport details",
Description: "Transport details",
},
}
}
func TestRenderMarkdownWithGlowRendersBulletLists(t *testing.T) {
rendered, err := renderMarkdownWithGlow(48, "# Title\n\n- first item")
if err != nil {
t.Fatalf("renderMarkdownWithGlow(): %v", err)
}
plain := ansi.Strip(rendered)
if !strings.Contains(plain, "Title") {
t.Fatalf("expected heading text, got %q", plain)
}
if !strings.Contains(plain, "• first item") {
t.Fatalf("expected glow-style bullet rendering, got %q", plain)
}
}
func TestSummarizeMarkdownPrefersFirstMeaningfulLine(t *testing.T) {
summary := summarizeMarkdown("# Title\n\n- Bullet\n\nParagraph")
if summary != "Title" {
t.Fatalf("expected first heading summary, got %q", summary)
}
}
func TestSummarizeMarkdownStripsMarkdownAndUnicodeSymbols(t *testing.T) {
summary := summarizeMarkdown("⚠️ **IMPORTANT:** This content was NOT created by the Sliver authors.")
if summary != "IMPORTANT: This content was NOT created by the Sliver authors." {
t.Fatalf("expected sanitized browser summary, got %q", summary)
}
}
func TestDocsModelDefaultsToGettingStarted(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
if model.currentDocName != "Getting Started" {
t.Fatalf("expected Getting Started to be selected, got %q", model.currentDocName)
}
plain := ansi.Strip(model.viewer.View())
if !strings.Contains(plain, "first step") {
t.Fatalf("expected initial viewer content to render selected doc, got %q", plain)
}
}
func TestDocsModelSlashFocusesBrowserAndStartsFiltering(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
model.focus = docsFocusViewer
updated, cmd := model.Update(tea.KeyPressMsg{Text: "/", Code: '/'})
if cmd == nil {
t.Fatal("expected slash to focus the search input")
}
updatedModel := updated.(*docsModel)
if updatedModel.focus != docsFocusBrowser {
t.Fatalf("expected slash to move focus to browser, got %v", updatedModel.focus)
}
if updatedModel.filterState != docsFilterStateFiltering {
t.Fatalf("expected slash to enter filtering state, got %v", updatedModel.filterState)
}
}
func TestDocsModelFilteringUpdatesVisibleEntries(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
model.startFiltering()
model.browserFilter.SetValue("oper")
model.updateFilteredEntries(false)
visible := model.visibleEntries()
if len(visible) != 1 || visible[0].Name != "Operators" {
t.Fatalf("expected Operators to be the only filtered result, got %+v", visible)
}
if model.currentDocName != "Operators" {
t.Fatalf("expected filtering to sync selection to Operators, got %q", model.currentDocName)
}
}
func TestDocsModelEnterMovesFocusToViewer(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
updated, _ := model.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
updatedModel := updated.(*docsModel)
if updatedModel.focus != docsFocusViewer {
t.Fatalf("expected enter to focus viewer, got %v", updatedModel.focus)
}
}
func TestDocsModelEscClearsAppliedFilter(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
model.startFiltering()
model.browserFilter.SetValue("net")
model.finishFiltering(false)
updated, _ := model.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
updatedModel := updated.(*docsModel)
if updatedModel.filterState != docsFilterStateUnfiltered {
t.Fatalf("expected esc to clear the filter, got %v", updatedModel.filterState)
}
if len(updatedModel.visibleEntries()) != len(sampleDocs()) {
t.Fatalf("expected filter clear to restore all docs, got %d visible", len(updatedModel.visibleEntries()))
}
}
func TestDocsModelBrowserBodyPinsHelpToBottom(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 28)
body := ansi.Strip(model.renderBrowserBody(model.browserWidth, model.browserHeight))
lines := strings.Split(body, "\n")
if len(lines) != model.browserHeight {
t.Fatalf("expected browser body height %d, got %d lines", model.browserHeight, len(lines))
}
if !strings.Contains(lines[len(lines)-1], "q quit") {
t.Fatalf("expected help controls on the last browser line, got %q", lines[len(lines)-1])
}
}
func TestDocsWindowPollSchedulesResizeAndKeepsPolling(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(100, 28)
updated, cmd := model.Update(docsWindowPollMsg{width: 120, height: 34})
if cmd == nil {
t.Fatal("expected window poll to schedule resize handling")
}
updatedModel := updated.(*docsModel)
if updatedModel.width != 100 || updatedModel.height != 28 {
t.Fatalf("expected poll tick to leave dimensions unchanged until WindowSizeMsg, got %dx%d", updatedModel.width, updatedModel.height)
}
msg := cmd()
batch, ok := msg.(tea.BatchMsg)
if !ok {
t.Fatalf("expected poll tick to return tea.BatchMsg, got %T", msg)
}
if len(batch) != 2 {
t.Fatalf("expected poll tick batch size 2, got %d", len(batch))
}
var sawWindowSize bool
var sawPollTick bool
for _, subcmd := range batch {
if subcmd == nil {
continue
}
submsg := subcmd()
switch msg := submsg.(type) {
case tea.WindowSizeMsg:
sawWindowSize = true
if msg.Width != 120 || msg.Height != 34 {
t.Fatalf("expected resize 120x34, got %dx%d", msg.Width, msg.Height)
}
case docsWindowPollMsg:
sawPollTick = true
}
}
if !sawWindowSize {
t.Fatal("expected poll tick to emit WindowSizeMsg")
}
if !sawPollTick {
t.Fatal("expected poll tick to schedule another poll")
}
}