Три секции экрана ревью (Догадка/Источник/Раскладка) слиты в один блок: список вариантов (радио) → инфо о выбранном → предпросмотр раскладки. Клик по варианту сразу выбирает и сохраняет источник и обновляет инфо+раскладку частичным htmx-свопом блока, без полной перезагрузки и без кнопки «выбрать». - httpapi: reviewBlockAction (htmx-aware, детект HX-Request) для candidate/nobase/source; вынос buildReviewView; поля SeasonSummary и BlockError; сводка сезонов (seasonSummary/seasonRanges) - тип movie↔series убран из UI (read-only); удалён веб-роут /type и handleSetType, метод SetType из интерфейса httpapi (worker/Telegram не тронуты) - шаблон: партиал review_source_block, ссылка «запись ↗» вне кликабельного label, фокус радио с клавиатуры; чистка мёртвого sourceView.Files/IsSeries - тесты: htmx-своп выбора, htmx-путь ошибки, юнит-тесты сводки сезонов - openspec: спеки review/web-ui синхронизированы, change заархивирован - беклог: сложные сериальные раздачи; oob-обновление панели действий Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
54 lines
2.0 KiB
Go
54 lines
2.0 KiB
Go
package httpapi
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"git.vakhrushev.me/av/jellybit/internal/recognize"
|
||
)
|
||
|
||
func TestSeasonSummary(t *testing.T) {
|
||
// ep — эпизодный файл с заданным (или nil) сезоном.
|
||
ep := func(season *int) recognize.PlanFile {
|
||
return recognize.PlanFile{Role: recognize.RoleEpisode, Season: season}
|
||
}
|
||
n := func(v int) *int { return &v }
|
||
|
||
cases := []struct {
|
||
name string
|
||
files []recognize.PlanFile
|
||
want string
|
||
}{
|
||
{"пусто", nil, ""},
|
||
{"один сезон", []recognize.PlanFile{ep(n(2)), ep(n(2))}, "Сезон 2"},
|
||
{"диапазон", []recognize.PlanFile{ep(n(1)), ep(n(2)), ep(n(3))}, "Сезоны 1–3"},
|
||
{"разрыв", []recognize.PlanFile{ep(n(1)), ep(n(3)), ep(n(4))}, "Сезоны 1, 3–4"},
|
||
{"несортированный вход", []recognize.PlanFile{ep(n(3)), ep(n(1)), ep(n(2))}, "Сезоны 1–3"},
|
||
{"только спецвыпуски (nil)", []recognize.PlanFile{ep(nil)}, "Спецвыпуски"},
|
||
{"только спецвыпуски (0)", []recognize.PlanFile{ep(n(0))}, "Спецвыпуски"},
|
||
{"сезоны и спецвыпуски", []recognize.PlanFile{ep(n(1)), ep(n(2)), ep(nil)}, "Сезоны 1–2, спецвыпуски"},
|
||
{"один сезон и спецвыпуски", []recognize.PlanFile{ep(n(1)), ep(nil)}, "Сезон 1, спецвыпуски"},
|
||
}
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
got := seasonSummary(recognize.Plan{Files: c.files})
|
||
if got != c.want {
|
||
t.Errorf("seasonSummary = %q, want %q", got, c.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// Не-эпизодные файлы (main/subtitle/…) не влияют на сводку сезонов.
|
||
func TestSeasonSummary_IgnoresNonEpisodes(t *testing.T) {
|
||
s := 2
|
||
plan := recognize.Plan{Files: []recognize.PlanFile{
|
||
{Role: recognize.RoleEpisode, Season: &s},
|
||
{Role: recognize.RoleMain},
|
||
{Role: recognize.RoleSubtitle},
|
||
{Role: recognize.RoleIgnore},
|
||
}}
|
||
if got := seasonSummary(plan); got != "Сезон 2" {
|
||
t.Errorf("seasonSummary = %q, want «Сезон 2»", got)
|
||
}
|
||
}
|