fix(photos): убрать мигание предпросмотра и задержку сброса правок

- фото и photo.updated несут edit_pending: рендер сохранённых правок ещё идёт
- клиент держит предпросмотр, пока серверная версия не загружена и не
  декодирована, поэтому оригинал не мелькает при подмене
- сброс возвратом контролов в значения по умолчанию виден сразу, не через 10 с
- Viewer берёт URL из актуального объекта (SSE), fresh остаётся фолбэком
- попутно исправлен порядок чтения tags и album_title в ListTrash
This commit is contained in:
iu
2026-10-25 10:02:04 +03:00
parent 1c29f5d961
commit 2b8d329a3e
10 changed files with 495 additions and 89 deletions
@@ -48,6 +48,7 @@ func Run(t *testing.T, r Repos) {
{"ShareLinks", testShareLinks},
{"Social", testSocial},
{"Photos", testPhotos},
{"EditPending", testEditPending},
{"Objects", testObjects},
{"Clone", testClone},
{"UserAvatar", testUserAvatar},
@@ -490,6 +491,11 @@ func testPhotos(t *testing.T, ctx context.Context, r Repos) {
t.Fatalf("cover not cleared: %+v", al.CoverPhotoID)
}
// Trash rows are read with the same column list as the album list, so the
// tags JSON and the album title must land in their own fields.
if err := r.Photos.AttachTag(ctx, p.ID, tag.ID); err != nil {
t.Fatalf("re-attach tag for trash: %v", err)
}
if err := r.Photos.SetDeleted(ctx, p.ID, true); err != nil {
t.Fatalf("soft delete: %v", err)
}
@@ -500,6 +506,12 @@ func testPhotos(t *testing.T, ctx context.Context, r Repos) {
if err != nil || len(trash) != 1 {
t.Fatalf("trash: %v len=%d", err, len(trash))
}
if trash[0].AlbumTitle != a.Title {
t.Fatalf("trash album title = %q, want %q", trash[0].AlbumTitle, a.Title)
}
if len(trash[0].Tags) != 1 || trash[0].Tags[0].Name != "Vacation" {
t.Fatalf("trash tags = %+v", trash[0].Tags)
}
if err := r.Photos.RestoreToAlbum(ctx, p.ID, a.ID); err != nil {
t.Fatalf("restore: %v", err)
}
@@ -563,6 +575,56 @@ func testPhotos(t *testing.T, ctx context.Context, r Repos) {
}
}
// testEditPending verifies that reads expose whether an edit render is still
// queued: clients rely on it to tell stale edit URLs from the current ones.
func testEditPending(t *testing.T, ctx context.Context, r Repos) {
owner := seedUser(t, ctx, r, "Owner")
a := seedAlbum(t, ctx, r, owner.ID)
p := seedPhoto(t, ctx, r, owner.ID, a.ID)
getPending := func() bool {
t.Helper()
got, err := r.Photos.GetByID(ctx, p.ID, owner.ID)
if err != nil {
t.Fatalf("get photo: %v", err)
}
return got.EditPending
}
listPending := func() bool {
t.Helper()
list, err := r.Photos.List(ctx, photos.ListParams{AlbumID: a.ID, Viewer: owner.ID, Limit: 10})
if err != nil || len(list) != 1 {
t.Fatalf("list: %v len=%d", err, len(list))
}
return list[0].EditPending
}
if getPending() || listPending() {
t.Fatalf("photo without an edit job reports a pending edit render")
}
if err := r.Photos.EnqueueEditJob(ctx, p.ID); err != nil {
t.Fatalf("enqueue edit job: %v", err)
}
if !getPending() || !listPending() {
t.Fatalf("photo with a queued edit render does not report edit_pending")
}
if err := r.Photos.CompleteEditJob(ctx, p.ID); err != nil {
t.Fatalf("complete edit job: %v", err)
}
if getPending() || listPending() {
t.Fatalf("rendered photo still reports edit_pending")
}
if err := r.Photos.EnqueueEditJob(ctx, p.ID); err != nil {
t.Fatalf("enqueue again: %v", err)
}
if err := r.Photos.DeleteEditJob(ctx, p.ID); err != nil {
t.Fatalf("delete edit job: %v", err)
}
if getPending() {
t.Fatalf("photo reports edit_pending after the job was dropped")
}
}
func testObjects(t *testing.T, ctx context.Context, r Repos) {
owner := seedUser(t, ctx, r, "Owner")
a := seedAlbum(t, ctx, r, owner.ID)