package web import ( "html/template" "strings" "testing" "bookmarkmanager/backend/internal/store" ) // The card carries two guarantees a browser can break that Go cannot see, so // they are asserted on the rendered markup: // // - the monogram is unconditional. A cover that 404s or a request the phone // drops leaves an with no bytes, and the letter underneath it is the // only thing between that and the browser's broken-image glyph in a 93px // slot. Rendering it only when Cover is empty covers the wrong failure. // - the chapter field does not refuse its own value. It is pre-filled from // LastChapterNum, which the API accepts as any float, so a step that // quantises the field makes a series read to 1200.25 unsavable without // first editing a number the reader did not want to change. func TestCardRenderKeepsCoverFallbackAndAcceptsFractionalChapter(t *testing.T) { tmpl, err := template.ParseFS(templateFS, "templates/*.html") if err != nil { t.Fatalf("ParseFS: %v", err) } b := store.Bookmark{ Key: "asura:x", Site: "asura", Title: "Chronicles", Status: store.StatusReading, Cover: "https://bookmarks.test/covers/abc", LastChapter: "Chapter 1200.25", LastChapterNum: 1200.25, } var out strings.Builder if err := tmpl.ExecuteTemplate(&out, "card", b); err != nil { t.Fatalf("ExecuteTemplate: %v", err) } got := out.String() if !strings.Contains(got, `class="monogram"`) { t.Error("a card with a cover rendered no monogram: a failed image has no fallback") } if !strings.Contains(got, `step="any"`) { t.Error(`chapter input is not step="any": a fractional pre-filled value is unsavable`) } if !strings.Contains(got, `value="1200.25"`) { t.Errorf("chapter input is not pre-filled with the stored progress:\n%s", got) } } // Every failing request must land somewhere visible. A card's own writes report // into its .error-inline; a tab switch and a credential rotation have no card, // and #notice is the only slot filter.js can fall back to — without it in the // shell they fail silently. The tab's own active-state move is guarded on the // response, or a failed switch underlines a bucket the list is not showing. func TestAppShellCarriesFailureNoticeAndGuardsTabState(t *testing.T) { tmpl, err := template.ParseFS(templateFS, "templates/*.html") if err != nil { t.Fatalf("ParseFS: %v", err) } var out strings.Builder view := listView{Lib: store.KindManga, Tab: "all"} if err := tmpl.ExecuteTemplate(&out, "app", view); err != nil { t.Fatalf("ExecuteTemplate: %v", err) } got := out.String() if !strings.Contains(got, `id="notice"`) { t.Error("no #notice in the shell: a failure with no card to sit in reports nowhere") } if strings.Contains(got, `hx-on::after-request="setActiveTab(this)"`) { t.Error("a tab moves its active state unconditionally: a failed switch underlines the wrong bucket") } }