T-569 增加封面画廊任务切换
This commit is contained in:
+261
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user