Ревью: oob-обновление панели действий при свопе источника
Кнопка «Применить» (завязана на HasLinks) лежит вне #source-block и не обновлялась при htmx-свопе выбора источника — в краевом случае (пустой предпросмотр из-за коллизии) рассинхронивалась с превью до полной перезагрузки. Выделил панель в партиал review_action_bar с id=action-bar; reviewBlockAction теперь рендерит review_source_swap — свежий #source-block плюс oob-копию панели (hx-swap-oob), так кнопка синхронно отражает HasLinks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/layout"
|
||||
"git.vakhrushev.me/av/jellybit/internal/recognize"
|
||||
"git.vakhrushev.me/av/jellybit/internal/store"
|
||||
"git.vakhrushev.me/av/jellybit/internal/worker"
|
||||
)
|
||||
@@ -272,6 +274,70 @@ func TestActionErrorDownloadSurface(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// reviewDataWithSource собирает ReviewData ревью с одним нейронка-источником;
|
||||
// withLinks управляет наличием превью раскладки (HasLinks).
|
||||
func reviewDataWithSource(withLinks bool) *worker.ReviewData {
|
||||
dl := dlState(store.StateReview)
|
||||
plan := recognize.Plan{Type: "movie", Title: "Fargo", Year: 1996,
|
||||
Files: []recognize.PlanFile{{Src: "fargo.mkv"}}}
|
||||
rd := &worker.ReviewData{
|
||||
Download: dl,
|
||||
Recognition: &store.Recognition{},
|
||||
Plan: plan,
|
||||
Sources: []worker.SourceOption{
|
||||
{Kind: worker.SourceNeural, Provider: "none", Title: "Fargo", Year: 1996, Active: true},
|
||||
},
|
||||
}
|
||||
if withLinks {
|
||||
rd.Preview = []layout.Link{{Src: "fargo.mkv", Dst: "/movies/Fargo (1996)/fargo.mkv"}}
|
||||
}
|
||||
return rd
|
||||
}
|
||||
|
||||
// TestSourceSwapUpdatesActionBarOOB: своп выбора источника отдаёт свежий
|
||||
// #source-block и oob-обновление панели действий (#action-bar с hx-swap-oob),
|
||||
// чтобы кнопка «Применить» синхронно отражала актуальный HasLinks.
|
||||
func TestSourceSwapUpdatesActionBarOOB(t *testing.T) {
|
||||
t.Run("с превью → есть кнопка Применить", func(t *testing.T) {
|
||||
rd := reviewDataWithSource(true)
|
||||
rv := actionReviewer{stubReviewer: stubReviewer{data: rd}}
|
||||
h := testRouterAction(t, stubReader{one: &rd.Download}, rv, stubCommander{}, stubLive{})
|
||||
|
||||
rr := post(t, h, "/ui/downloads/"+testULID+"/nobase", nil, true)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("nobase (htmx) = %d, want 200", rr.Code)
|
||||
}
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, `id="source-block"`) {
|
||||
t.Errorf("ответ без свежего #source-block: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, `id="action-bar"`) || !strings.Contains(body, `hx-swap-oob="true"`) {
|
||||
t.Errorf("ответ без oob-панели действий: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, "/apply") {
|
||||
t.Errorf("HasLinks=true, но в панели нет кнопки «Применить»: %s", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("без превью → кнопки Применить нет", func(t *testing.T) {
|
||||
rd := reviewDataWithSource(false)
|
||||
rv := actionReviewer{stubReviewer: stubReviewer{data: rd}}
|
||||
h := testRouterAction(t, stubReader{one: &rd.Download}, rv, stubCommander{}, stubLive{})
|
||||
|
||||
rr := post(t, h, "/ui/downloads/"+testULID+"/nobase", nil, true)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("nobase (htmx) = %d, want 200", rr.Code)
|
||||
}
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, `id="action-bar"`) || !strings.Contains(body, `hx-swap-oob="true"`) {
|
||||
t.Errorf("ответ без oob-панели действий: %s", body)
|
||||
}
|
||||
if strings.Contains(body, "/apply") {
|
||||
t.Errorf("HasLinks=false, но в панели осталась кнопка «Применить»: %s", body)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestRetryListShowsProgress: retry из списка → карточка downloading с
|
||||
// прогресс-поллером.
|
||||
func TestRetryListShowsProgress(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user