T-569 增加封面画廊任务切换

This commit is contained in:
chengma
2026-07-09 12:19:29 +08:00
parent ae4daacbbf
commit 4e98a86fb3
3 changed files with 447 additions and 18 deletions
+168 -15
View File
@@ -86,12 +86,19 @@ class CoverGalleryDialog(QDialog):
bulk_running_checker=None,
regenerate_running_callback=None,
gallery_updated_callback=None,
visible_tasks=None,
task_index=None,
account_by_alias=None,
):
super().__init__(parent)
self.task = task
self.image_root = image_root
self.db_path = db_path
self.account = account
self.visible_tasks = list(visible_tasks or [task])
self.task_index = self._initial_task_index(task, task_index)
self.task = self.visible_tasks[self.task_index] if self.visible_tasks else task
self.account_by_alias = dict(account_by_alias or {})
self.fallback_account = account
self.account = self._account_for_task(self.task) or account
self.config = config or {}
self.prompt_values = dict(prompt_values or {})
self.diagnostic_log_dir = diagnostic_log_dir
@@ -106,8 +113,9 @@ class CoverGalleryDialog(QDialog):
self.button_group.setExclusive(True)
self.regenerate_worker = None
self.regenerate_thread = None
self.regenerating_task_id = None
self._regenerate_last_error = ""
self.candidates = image_paths.list_task_cover_candidates(image_root, task, account=account)
self.candidates = image_paths.list_task_cover_candidates(image_root, self.task, account=self.account)
self.setWindowTitle(f"封面画廊:{getattr(task, 'item_id', '')}")
layout = QVBoxLayout(self)
@@ -117,6 +125,12 @@ class CoverGalleryDialog(QDialog):
layout.addLayout(body_layout, 1)
button_layout = QHBoxLayout()
self.previous_button = QPushButton("上一条")
self.next_button = QPushButton("下一条")
self.previous_button.clicked.connect(lambda checked=False: self.switch_task(-1))
self.next_button.clicked.connect(lambda checked=False: self.switch_task(1))
button_layout.addWidget(self.previous_button)
button_layout.addWidget(self.next_button)
self.regenerate_button = QPushButton("重新生成")
self.regenerate_button.clicked.connect(self.regenerate_cover)
button_layout.addWidget(self.regenerate_button)
@@ -131,6 +145,7 @@ class CoverGalleryDialog(QDialog):
self._sync_initial_selection()
self._update_status()
self._update_navigation_buttons()
self._update_regenerate_button_state()
self._fit_to_screen()
@@ -138,17 +153,29 @@ class CoverGalleryDialog(QDialog):
panel = QWidget()
layout = QVBoxLayout(panel)
layout.addWidget(QLabel("旧封面"))
old_path = getattr(self.task, "old_cover_path", None)
layout.addWidget(
_build_image_label(
old_path,
self.OLD_THUMBNAIL_SIZE,
empty_text="暂无旧封面",
)
self.old_cover_label = _build_image_label(
getattr(self.task, "old_cover_path", None),
self.OLD_THUMBNAIL_SIZE,
empty_text="暂无旧封面",
)
layout.addWidget(self.old_cover_label)
layout.addStretch(1)
return panel
def _initial_task_index(self, task, task_index):
if self.visible_tasks:
if task_index is not None and 0 <= int(task_index) < len(self.visible_tasks):
return int(task_index)
task_id = getattr(task, "id", None)
for index, visible_task in enumerate(self.visible_tasks):
if getattr(visible_task, "id", None) == task_id:
return index
return 0
def _account_for_task(self, task):
alias = str(getattr(task, "alias", "") or "").strip()
return self.account_by_alias.get(alias) or self.fallback_account
def _candidate_panel(self):
panel = QWidget()
layout = QVBoxLayout(panel)
@@ -207,6 +234,7 @@ class CoverGalleryDialog(QDialog):
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
radio = QRadioButton(_candidate_label(candidate_path, self.current_path))
radio.setObjectName(f"coverCandidateRadio{index}")
radio.setFocusPolicy(Qt.ClickFocus)
radio.toggled.connect(
lambda checked, path=candidate_path: self._select_candidate(path) if checked else None
)
@@ -235,6 +263,9 @@ class CoverGalleryDialog(QDialog):
self._update_status()
def _update_status(self):
if self._is_current_task_regenerating():
self.status_label.setText("封面重新生成中,请稍候")
return
if not self.candidates:
self.status_label.setText("暂无生成封面图片")
self.save_button.setEnabled(False)
@@ -254,12 +285,16 @@ class CoverGalleryDialog(QDialog):
self.status_label.setText("已选择新的生成封面,保存后用于后续更新蝦皮")
def save_selection(self, checked=False):
return self._save_selected_cover(close_on_success=True)
def _save_selected_cover(self, close_on_success=False):
if not self.selected_path:
QMessageBox.warning(self, "封面画廊", "请先选择一张生成封面")
return False
if _same_file(self.selected_path, self.current_path):
self.changed = False
self.accept()
if close_on_success:
self.accept()
return True
if self._needs_committed_confirmation() and not self._confirm_committed_save():
return False
@@ -268,9 +303,17 @@ class CoverGalleryDialog(QDialog):
except Exception as exc:
QMessageBox.warning(self, "封面画廊", f"保存当前封面失败:{exc}")
return False
fresh_task = db.get_task(self.task.id, path=self.db_path)
if fresh_task is not None:
self.task = fresh_task
self.visible_tasks[self.task_index] = fresh_task
self.current_path = _normalize_file_path(self.selected_path)
self.changed = True
self.accept()
self._update_status()
if self.gallery_updated_callback is not None:
self.gallery_updated_callback(self.task)
if close_on_success:
self.accept()
return True
def regenerate_cover(self, checked=False):
@@ -301,6 +344,7 @@ class CoverGalleryDialog(QDialog):
QMessageBox.warning(self, "重新生成封面", "任务不存在或已删除")
self.status_label.setText("重新生成封面失败:任务不存在或已删除")
return False
self.regenerating_task_id = getattr(fresh_task, "id", None)
self._reload_task_and_candidates(fresh_task)
self.status_label.setText(f"开始重新生成商品 {getattr(fresh_task, 'item_id', '')} 的封面图片")
worker = GenerateWorker(
@@ -349,6 +393,96 @@ class CoverGalleryDialog(QDialog):
box.exec()
return box.clickedButton() is save_button
def switch_task(self, offset):
target_index = self.task_index + int(offset)
if target_index < 0 or target_index >= len(self.visible_tasks):
return False
if not self._confirm_unsaved_selection():
return False
self._load_task_at_index(target_index)
return True
def _load_task_at_index(self, target_index):
self.task_index = int(target_index)
target_task = self.visible_tasks[self.task_index]
fresh_task = db.get_task(getattr(target_task, "id", None), path=self.db_path) or target_task
self.visible_tasks[self.task_index] = fresh_task
self.task = fresh_task
self.account = self._account_for_task(fresh_task)
self.setWindowTitle(f"封面画廊:{getattr(fresh_task, 'item_id', '')}")
_set_image_label(
self.old_cover_label,
getattr(fresh_task, "old_cover_path", None),
self.OLD_THUMBNAIL_SIZE,
"暂无旧封面",
)
self._reload_task_and_candidates(fresh_task)
self._update_navigation_buttons()
self._update_regenerate_button_state()
def _update_navigation_buttons(self):
self.previous_button.setEnabled(self.task_index > 0)
self.next_button.setEnabled(self.task_index < len(self.visible_tasks) - 1)
def _has_unsaved_selection(self):
if not self.selected_path:
return False
return not _same_file(self.selected_path, self.current_path)
def _confirm_unsaved_selection(self):
if not self._has_unsaved_selection():
return True
box = QMessageBox(self)
box.setWindowTitle("未保存封面选择")
box.setText("当前已选择另一张生成封面,但尚未保存。")
save_button = box.addButton("保存", QMessageBox.AcceptRole)
discard_button = box.addButton("不保存", QMessageBox.DestructiveRole)
box.addButton("取消", QMessageBox.RejectRole)
box.setDefaultButton(save_button)
box.exec()
clicked = box.clickedButton()
if clicked is save_button:
return self._save_selected_cover(close_on_success=False)
if clicked is discard_button:
return True
return False
def reject(self):
if self._confirm_unsaved_selection():
super().reject()
def closeEvent(self, event):
if self._confirm_unsaved_selection():
event.accept()
return
event.ignore()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Up and self._should_consume_navigation_key():
self.switch_task(-1)
event.accept()
return
if event.key() == Qt.Key_Down and self._should_consume_navigation_key():
self.switch_task(1)
event.accept()
return
super().keyPressEvent(event)
def _should_consume_navigation_key(self):
focus = QApplication.focusWidget()
if focus is None or focus is self:
return True
return not isinstance(
focus,
(
QPushButton,
QLineEdit,
QPlainTextEdit,
QComboBox,
QSpinBox,
),
)
def open_original_image(self, image_path):
dialog = OriginalImageDialog(image_path, self)
dialog.exec()
@@ -356,6 +490,13 @@ class CoverGalleryDialog(QDialog):
def _bulk_generate_running(self):
return bool(self.bulk_running_checker and self.bulk_running_checker())
def _is_current_task_regenerating(self):
return (
self.regenerate_thread is not None
and self.regenerating_task_id is not None
and getattr(self.task, "id", None) == self.regenerating_task_id
)
def _set_regenerate_running(self, running):
self.save_button.setEnabled(not running and self.selected_path is not None)
self.regenerate_button.setEnabled(not running and not self._bulk_generate_running())
@@ -379,6 +520,7 @@ class CoverGalleryDialog(QDialog):
def _reload_task_and_candidates(self, task=None):
if task is not None:
self.task = task
self.account = self._account_for_task(task)
self.current_path = _normalize_file_path(getattr(self.task, "new_cover_path", None))
self.selected_path = None
self.candidates = image_paths.list_task_cover_candidates(self.image_root, self.task, account=self.account)
@@ -431,6 +573,7 @@ class CoverGalleryDialog(QDialog):
self._finish_regenerate(summary, cancelled=True)
def _finish_regenerate(self, summary, cancelled=False):
self.regenerating_task_id = None
fresh_task = db.get_task(self.task.id, path=self.db_path)
if fresh_task is not None:
self._reload_task_and_candidates(fresh_task)
@@ -450,6 +593,7 @@ class CoverGalleryDialog(QDialog):
if self.regenerate_thread is thread:
self.regenerate_thread = None
self.regenerate_worker = None
self.regenerating_task_id = None
self._set_regenerate_running(False)
def _fit_to_screen(self):
@@ -488,10 +632,16 @@ def _build_image_label(image_path, size, empty_text):
label = QLabel()
label.setAlignment(Qt.AlignCenter)
label.setMinimumSize(size, size)
_set_image_label(label, image_path, size, empty_text)
return label
def _set_image_label(label, image_path, size, empty_text):
label.clear()
image = _load_image(image_path) if image_path else QImage()
if image.isNull():
label.setText(empty_text)
return label
return
label.setPixmap(
QPixmap.fromImage(
image.scaled(
@@ -502,7 +652,6 @@ def _build_image_label(image_path, size, empty_text):
)
)
)
return label
def _candidate_label(candidate_path, current_path):
@@ -1416,7 +1565,8 @@ class GenerateTab(QWidget):
def show_task_images(self, index):
if index.isValid() and index.column() == 3:
return
task = self.model.task_at(index.row()) if index.isValid() else self._selected_task()
row = index.row() if index.isValid() else self.task_table.currentIndex().row()
task = self.model.task_at(row) if row >= 0 else self._selected_task()
if task is None:
self._set_status("没有可预览的任务")
return
@@ -1436,6 +1586,9 @@ class GenerateTab(QWidget):
bulk_running_checker=lambda: self.generate_thread is not None,
regenerate_running_callback=self._set_cover_gallery_regenerate_running,
gallery_updated_callback=self._on_cover_gallery_updated,
visible_tasks=list(self.model.tasks),
task_index=row if row >= 0 else 0,
account_by_alias=self.model.account_by_alias,
)
if dialog.exec() == QDialog.Accepted:
self.refresh_tasks()
+18 -2
View File
@@ -3,7 +3,7 @@ id: T-569
title: ②封面画廊上下条切换与未保存选择拦截
phase: 7
deps: [T-567]
status: TODO
status: DONE
created: 2026-07-09
---
@@ -66,4 +66,20 @@ T-567 的封面画廊只处理当前单条任务。运营批量检查时需要
## 执行记录
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)
- 2026-07-09 完成 T-569。
- 修改 `app/gui/tabs/generate.py`:
- ②封面画廊新增「上一条」「下一条」按钮,目标任务来自打开画廊时②当前可见表格任务列表,按相邻行切换,首尾边界按钮自动置灰。
- 切换任务时只刷新弹窗内部任务、旧封面和候选画廊内容,不重建整个窗口,窗口尺寸和位置保持不变;切换时会重新读取 DB 最新任务和候选文件。
- 支持 `↑` / `↓` 键走同一套上一条/下一条切换逻辑;候选 `QRadioButton` 改为 `ClickFocus`,避免方向键在 radio 组内改候选。
- 增加统一未保存选择拦截:按钮切换、方向键切换、取消/关闭弹窗前,如果当前选中候选不同于当前 DB 封面指针,会弹中文三态确认「保存 / 不保存 / 取消」。保存路径复用 `db.update_generated_cover()`,已提交线上任务仍先弹“本地换封面不会回滚蝦皮,重复更新会再次提交线上”确认;保存失败保留当前任务。
- 与 T-568 兼容:单任务封面重生运行中切换回该任务时显示“封面重新生成中”,完成/失败后继续刷新 DB 和候选列表。
- 修改 `tests/test_gui.py`:
- 覆盖上一条/下一条按当前可见列表切换、边界按钮状态、切换不改变窗口尺寸/位置。
- 覆盖 `↑`/`↓` 键切换任务且不改变候选 radio 选择。
- 覆盖未保存选择在切换和关闭时的「保存 / 不保存 / 取消」三种路径,以及已提交任务保存前的二次确认。
- 验证通过:
- `py -3.10 -m unittest tests.test_gui`
- `python -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `py -3.10 -m unittest discover -s tests`(302 tests;PySide6 字体目录警告不影响结果)
- `git diff --check`
+261 -1
View File
@@ -18,7 +18,7 @@ if gui.QT_IMPORT_ERROR is not None:
raise unittest.SkipTest("PySide6 未安装")
from PySide6.QtCore import QItemSelectionModel, QRect
from PySide6.QtGui import QImage, QTextCursor
from PySide6.QtGui import QImage, QKeyEvent, QTextCursor
from PySide6.QtWidgets import QApplication, QCheckBox, QLineEdit, QPlainTextEdit, QProgressBar, QTableView
from app.gui import (
@@ -156,6 +156,40 @@ class GuiTests(TempDirMixin, unittest.TestCase):
db.set_generated(task.id, "新标题", canonical, path=cfg["db_path"])
return cfg, account, db.get_task(task.id, path=cfg["db_path"]), canonical
def _cover_gallery_task_set(self, temp_dir, count=3):
cfg = self.make_config(temp_dir)
cfg.setdefault("ai", {})["generate_cover"] = True
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
db.init_db(cfg["db_path"])
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
rows = []
for index in range(count):
rows.append(
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": index + 2,
"account_name": "Excel主店",
"alias": "alias-a",
"item_id": f"5110063951{index}",
}
)
db.insert_tasks(batch_id, rows, path=cfg["db_path"])
tasks = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])
canonicals = []
archives = []
for index, task in enumerate(tasks):
old_cover = self.write_test_image(os.path.join(temp_dir, f"old-{index}.jpg"))
canonical = image_paths.task_image_path(cfg["image_dir"], task, account, "new")
archive = os.path.splitext(canonical)[0] + "_20260709101000.jpg"
self.write_test_image(canonical)
self.write_test_image(archive)
db.set_collected(task.id, f"旧标题{index}", old_cover, path=cfg["db_path"])
db.set_generated(task.id, f"新标题{index}", canonical, path=cfg["db_path"])
canonicals.append(canonical)
archives.append(archive)
return cfg, account, db.list_tasks(batch_id=batch_id, path=cfg["db_path"]), canonicals, archives
def assert_foreground(self, model, row, column, color):
value = model.data(model.index(row, column), gui.Qt.ForegroundRole)
self.assertIsNotNone(value)
@@ -217,6 +251,53 @@ class GuiTests(TempDirMixin, unittest.TestCase):
return FakeMessageBox, boxes
def make_sequence_message_box(self, selected_labels):
boxes = []
labels = list(selected_labels)
class FakeMessageBox:
AcceptRole = object()
DestructiveRole = object()
RejectRole = object()
def __init__(self, parent=None):
self.parent = parent
self.title = ""
self.text = ""
self.buttons = {}
self.default_button = None
self.selected_label = labels.pop(0)
boxes.append(self)
def setWindowTitle(self, title):
self.title = title
def setText(self, text):
self.text = text
def addButton(self, label, role):
button = object()
self.buttons[label] = button
return button
def setDefaultButton(self, button):
self.default_button = button
def exec(self):
return 0
def clickedButton(self):
return self.buttons[self.selected_label]
@staticmethod
def warning(parent, title, text):
box = FakeMessageBox(parent)
box.setWindowTitle(title)
box.setText(text)
return 0
return FakeMessageBox, boxes
def test_collect_table_shows_product_unavailable_only_for_explicit_error(self):
account = SimpleNamespace(alias="papa", account_name="papa 店铺")
invalid_task = SimpleNamespace(
@@ -2277,6 +2358,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual(1, updated.committed)
self.assertIn("重复更新会再次提交线上", boxes[0].text)
self.assertFalse(dialog.changed)
dialog.selected_path = dialog.current_path
self.assert_removed(temp_dir)
@@ -2343,6 +2425,184 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_cover_gallery_previous_next_switch_visible_tasks_without_resizing(self):
with self.make_temp_dir() as temp_dir:
cfg, account, tasks, canonicals, _archives = self._cover_gallery_task_set(temp_dir, count=3)
dialog = CoverGalleryDialog(
tasks[1],
cfg["image_dir"],
cfg["db_path"],
account=account,
visible_tasks=tasks,
task_index=1,
account_by_alias={"alias-a": account},
)
self.addCleanup(dialog.close)
dialog.resize(820, 520)
dialog.move(25, 35)
size = dialog.size()
position = dialog.pos()
self.assertTrue(dialog.previous_button.isEnabled())
self.assertTrue(dialog.next_button.isEnabled())
self.assertTrue(dialog.switch_task(1))
self.assertEqual(tasks[2].id, dialog.task.id)
self.assertTrue(dialog.previous_button.isEnabled())
self.assertFalse(dialog.next_button.isEnabled())
self.assertEqual(canonicals[2], dialog.selected_path)
self.assertEqual(size, dialog.size())
self.assertEqual(position, dialog.pos())
self.assert_removed(temp_dir)
def test_cover_gallery_arrow_keys_switch_tasks_without_changing_radio_choice(self):
with self.make_temp_dir() as temp_dir:
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
dialog = CoverGalleryDialog(
tasks[0],
cfg["image_dir"],
cfg["db_path"],
account=account,
visible_tasks=tasks,
task_index=0,
account_by_alias={"alias-a": account},
)
self.addCleanup(dialog.close)
dialog.candidate_buttons[canonicals[0]].setFocus()
event = QKeyEvent(QKeyEvent.KeyPress, gui.Qt.Key_Down, gui.Qt.NoModifier)
dialog.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(tasks[1].id, dialog.task.id)
self.assertEqual(canonicals[1], dialog.selected_path)
self.assertFalse(dialog.candidate_buttons[archives[1]].isChecked())
self.assert_removed(temp_dir)
def test_cover_gallery_unsaved_switch_save_discard_and_cancel_paths(self):
with self.make_temp_dir() as temp_dir:
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
save_dialog = CoverGalleryDialog(
tasks[0],
cfg["image_dir"],
cfg["db_path"],
account=account,
visible_tasks=tasks,
task_index=0,
account_by_alias={"alias-a": account},
)
self.addCleanup(save_dialog.close)
save_dialog.candidate_buttons[archives[0]].setChecked(True)
message_box, _ = self.make_fake_message_box("保存")
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
self.assertTrue(save_dialog.switch_task(1))
self.assertEqual(os.path.abspath(archives[0]), db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
self.assertEqual(tasks[1].id, save_dialog.task.id)
db.update_generated_cover(tasks[0].id, canonicals[0], path=cfg["db_path"])
tasks = [db.get_task(task.id, path=cfg["db_path"]) for task in tasks]
discard_dialog = CoverGalleryDialog(
tasks[0],
cfg["image_dir"],
cfg["db_path"],
account=account,
visible_tasks=tasks,
task_index=0,
account_by_alias={"alias-a": account},
)
self.addCleanup(discard_dialog.close)
discard_dialog.candidate_buttons[archives[0]].setChecked(True)
message_box, _ = self.make_fake_message_box("不保存")
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
self.assertTrue(discard_dialog.switch_task(1))
self.assertEqual(canonicals[0], db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
self.assertEqual(tasks[1].id, discard_dialog.task.id)
cancel_dialog = CoverGalleryDialog(
tasks[0],
cfg["image_dir"],
cfg["db_path"],
account=account,
visible_tasks=tasks,
task_index=0,
account_by_alias={"alias-a": account},
)
self.addCleanup(cancel_dialog.close)
cancel_dialog.candidate_buttons[archives[0]].setChecked(True)
message_box, _ = self.make_fake_message_box("取消")
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
self.assertFalse(cancel_dialog.switch_task(1))
self.assertEqual(canonicals[0], db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
self.assertEqual(tasks[0].id, cancel_dialog.task.id)
cancel_dialog.selected_path = cancel_dialog.current_path
self.assert_removed(temp_dir)
def test_cover_gallery_unsaved_close_uses_same_three_choice_guard(self):
class FakeCloseEvent:
def __init__(self):
self.accepted = False
self.ignored = False
def accept(self):
self.accepted = True
def ignore(self):
self.ignored = True
with self.make_temp_dir() as temp_dir:
cfg, account, tasks, canonicals, archives = self._cover_gallery_task_set(temp_dir, count=1)
dialog = CoverGalleryDialog(tasks[0], cfg["image_dir"], cfg["db_path"], account=account)
self.addCleanup(dialog.close)
dialog.candidate_buttons[archives[0]].setChecked(True)
event = FakeCloseEvent()
message_box, _ = self.make_fake_message_box("取消")
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
dialog.closeEvent(event)
self.assertTrue(event.ignored)
self.assertFalse(event.accepted)
self.assertEqual(canonicals[0], db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
message_box, _ = self.make_fake_message_box("不保存")
event = FakeCloseEvent()
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
dialog.closeEvent(event)
self.assertTrue(event.accepted)
dialog.selected_path = dialog.current_path
self.assert_removed(temp_dir)
def test_cover_gallery_unsaved_save_for_committed_task_keeps_warning(self):
with self.make_temp_dir() as temp_dir:
cfg, account, tasks, _canonicals, archives = self._cover_gallery_task_set(temp_dir, count=2)
db.set_applied(tasks[0].id, True, path=cfg["db_path"])
tasks = [db.get_task(task.id, path=cfg["db_path"]) for task in tasks]
dialog = CoverGalleryDialog(
tasks[0],
cfg["image_dir"],
cfg["db_path"],
account=account,
visible_tasks=tasks,
task_index=0,
account_by_alias={"alias-a": account},
)
self.addCleanup(dialog.close)
dialog.candidate_buttons[archives[0]].setChecked(True)
message_box, boxes = self.make_sequence_message_box(["保存", "确认保存"])
with mock.patch("app.gui.tabs.generate.QMessageBox", message_box):
self.assertTrue(dialog.switch_task(1))
self.assertEqual(os.path.abspath(archives[0]), db.get_task(tasks[0].id, path=cfg["db_path"]).new_cover_path)
self.assertIn("未保存", boxes[0].title)
self.assertIn("不会回滚蝦皮", boxes[1].text)
self.assertEqual(tasks[1].id, dialog.task.id)
self.assert_removed(temp_dir)
def test_cover_gallery_regenerate_cancel_has_no_side_effect(self):
with self.make_temp_dir() as temp_dir:
cfg, account, task, canonical = self._cover_gallery_task(temp_dir)