Release / build-and-push (push) Successful in 3m15s
Two independent dead-ends, confirmed on prod's /admin/archived (2026-10): Purge All rendered but clicking it did nothing at all, and per-row Delete never even showed its confirm dialog. Purge All is Alpine-bound (@click="purgeArchivedItems()"), but this page's <body> lacked x-data="admin" — the only admin page without it. Alpine evaluates directives only inside an x-data tree, so the attribute was inert: the admin component (and its method, registered in admin.ts) never instantiated here. The body now carries x-data="admin", matching admin.templ and admin_library.templ. Per-item Delete was a second, subtler dead-end: admin.ts's API helpers guard with `if (!token) return;` BEFORE the confirm dialog, and the reporting session had no localStorage token — SSR pages run on the cookie session, so the web UI works while every Bearer- authenticated admin action (purge, delete, restore, merge, scan-all, cancel-scan) silently no-ops with zero feedback. Those guards now go through requireToken, which toasts "Session expired — sign out and back in, then retry" when the token is absent. The two passive page-load probes (loadWatchStatus, checkActiveScans) deliberately keep the silent return: a toast there would fire on every visit. Pinned by TestAdminArchivedPageHasAlpineScope: renders the page and asserts the body scope and the action buttons coexist in one render. After deploy: hard-refresh prod, click Purge All (confirm appears); if the token toast fires on any admin action, log out and back in once to stash a fresh API token.
48 lines
1.7 KiB
Go
48 lines
1.7 KiB
Go
package templates
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// The archived-items admin page binds its buttons through the page-wide
|
|
// Alpine component: Purge All is @click="purgeArchivedItems()" and the
|
|
// per-row Delete/Restore/Merge buttons rely on listeners main.ts binds at
|
|
// module load. The body tag therefore MUST carry x-data="admin" — the
|
|
// other admin pages have it, and when this page's body lost it the purge
|
|
// button rendered as an inert attribute (Alpine only evaluates directives
|
|
// inside an x-data tree; the click did literally nothing).
|
|
//
|
|
// Regression: /admin/archived rendered <body class="theme-…"> with no
|
|
// Alpine scope, so "Purge All Archived" was dead (prod report 2026-10).
|
|
func TestAdminArchivedPageHasAlpineScope(t *testing.T) {
|
|
archivedAt := time.Date(2026, 10, 28, 4, 38, 0, 0, time.UTC)
|
|
var b strings.Builder
|
|
err := AdminArchived(User{ID: "u1", Timezone: "UTC"}, []ArchivedItem{{
|
|
ID: "m1",
|
|
Title: "Lost Book",
|
|
Author: "An Author",
|
|
LibraryName: "Audiobooks",
|
|
FilePath: "Author/Book/file.m4b",
|
|
MissingScans: 2,
|
|
ArchivedAt: &archivedAt,
|
|
Status: "Archived - pending purge",
|
|
}}, 0).Render(context.Background(), &b)
|
|
if err != nil {
|
|
t.Fatalf("render: %v", err)
|
|
}
|
|
out := b.String()
|
|
if !strings.Contains(out, `<body x-data="admin"`) {
|
|
t.Error("archived page body missing x-data=\"admin\" — the Alpine-bound purge button would be dead")
|
|
}
|
|
// The action buttons the page depends on must be present in the same
|
|
// render, so the pin cannot pass on an empty shell.
|
|
for _, want := range []string{"purgeArchivedItems()", `data-delete-archived="m1"`} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("archived page missing %q", want)
|
|
}
|
|
}
|
|
}
|