feat(apply): open product page on left double click

This commit is contained in:
chengma
2026-07-20 11:22:26 +08:00
parent 9f43e5547a
commit 7dbfcb5a24
11 changed files with 445 additions and 5 deletions
+83
View File
@@ -611,6 +611,89 @@ class EditorLoginTests(unittest.TestCase):
fake.sent,
)
def test_open_or_focus_product_tab_reuses_exact_target_without_navigation(self):
fake = FakeProductCDP("ws-existing")
existing = {
"id": "target-existing",
"type": "page",
"url": "https://seller.shopee.tw/portal/product/51100639510?draft=1",
"webSocketDebuggerUrl": "ws-existing",
}
with mock.patch("app.editor.http_get", return_value=[existing]), mock.patch(
"app.editor.create_tab_info"
) as create_tab_info, mock.patch(
"app.editor.CDP", return_value=fake
), mock.patch("app.editor._ensure_page_domains"):
result = editor.open_or_focus_product_tab(
{"debug_port": 9222}, "51100639510"
)
self.assertEqual(
{"item_id": "51100639510", "created": False, "target_id": "target-existing"},
result,
)
create_tab_info.assert_not_called()
self.assertIn(("Page.bringToFront", {}), fake.sent)
self.assertNotIn(
(
"Page.navigate",
{
"url": (
"https://seller.shopee.tw/portal/product/51100639510"
"?pageEntry=product_list&ignore-html-cache=1"
)
},
),
fake.sent,
)
self.assertTrue(fake.closed)
def test_open_or_focus_product_tab_ignores_non_exact_product_target(self):
fake = FakeProductCDP("ws-new")
non_exact = {
"id": "target-other",
"type": "page",
"url": "https://seller.shopee.tw/portal/product/511006395101",
"webSocketDebuggerUrl": "ws-other",
}
with mock.patch("app.editor.http_get", return_value=[non_exact]), mock.patch(
"app.editor.create_tab_info",
return_value={"id": "target-new", "webSocketDebuggerUrl": "ws-new"},
) as create_tab_info, mock.patch(
"app.editor.CDP", return_value=fake
), mock.patch("app.editor._ensure_page_domains"), mock.patch(
"app.editor._wait_ready", return_value=True
) as wait_ready:
result = editor.open_or_focus_product_tab(
{"debug_port": 9222}, "51100639510"
)
self.assertTrue(result["created"])
create_tab_info.assert_called_once_with(
"https://seller.shopee.tw/portal/product/51100639510"
"?pageEntry=product_list&ignore-html-cache=1",
host="127.0.0.1:9222",
background=False,
)
wait_ready.assert_called_once_with(fake)
self.assertTrue(fake.closed)
def test_open_or_focus_product_tab_cleans_only_new_target_on_failure(self):
fake = FakeProductCDP("ws-new")
with mock.patch("app.editor.http_get", return_value=[]), mock.patch(
"app.editor.create_tab_info",
return_value={"id": "target-new", "webSocketDebuggerUrl": "ws-new"},
), mock.patch("app.editor.CDP", return_value=fake), mock.patch(
"app.editor._ensure_page_domains"
), mock.patch(
"app.editor._wait_ready", side_effect=editor.EditorError("商品失效:测试")
), mock.patch("app.editor.close_tab", return_value=True) as close_tab:
with self.assertRaises(editor.EditorError):
editor.open_or_focus_product_tab({"debug_port": 9222}, "bad-item")
close_tab.assert_called_once_with("target-new", host="127.0.0.1:9222")
self.assertTrue(fake.closed)
def test_open_product_marks_auto_created_tab(self):
fake = FakeProductCDP("ws-new")
with mock.patch("app.editor.find_product_tab", return_value=None), mock.patch(
+83
View File
@@ -124,6 +124,19 @@ class FakeGenerateWorker:
FakeGenerateWorker.instances.append(self)
class FakeProductTabOpenWorker:
instances = []
def __init__(self, alias, item_id, db_path=None, config=None):
self.alias = alias
self.item_id = item_id
self.db_path = db_path
self.config = config
self.failed = DummySignal()
self.finished = DummySignal()
FakeProductTabOpenWorker.instances.append(self)
class FakeThumbnailLoader:
def __init__(self):
self.submissions = []
@@ -9212,6 +9225,76 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_apply_tab_left_double_click_opens_product_without_right_double_click(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "商品",
"source_row": 2,
"account_name": "主店",
"alias": "alias-a",
"item_id": "51100639510",
}
],
path=cfg["db_path"],
)
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
db.set_collected(task.id, "旧标题", "old.jpg", path=cfg["db_path"])
db.set_generated(task.id, "新标题", "new.jpg", path=cfg["db_path"])
statuses = []
tab = ApplyTab(config=cfg, status_callback=statuses.append)
self.addCleanup(tab.close)
tab.resize(1000, 600)
tab.show()
QApplication.processEvents()
index = tab.model.index(0, 0)
rect = tab.task_table.visualRect(index)
self.assertTrue(rect.isValid())
FakeProductTabOpenWorker.instances.clear()
with mock.patch(
"app.gui.ProductTabOpenWorker", FakeProductTabOpenWorker
), mock.patch("app.gui.run_worker", return_value=FakeThread()) as run_worker:
QTest.mouseDClick(
tab.task_table.viewport(), Qt.RightButton, pos=rect.center()
)
QApplication.processEvents()
self.assertEqual([], FakeProductTabOpenWorker.instances)
tab.task_table.leftDoubleClicked.emit(index)
self.assertEqual(1, len(FakeProductTabOpenWorker.instances))
worker = FakeProductTabOpenWorker.instances[0]
self.assertEqual("alias-a", worker.alias)
self.assertEqual("51100639510", worker.item_id)
self.assertEqual(cfg["db_path"], worker.db_path)
self.assertEqual("ProductTabOpenWorker", run_worker.call_args.kwargs["thread_name"])
self.assertTrue(tab.product_tab_open_thread.started)
self.assertIn("正在打开商品 51100639510", statuses[-1])
worker.finished.emit(
{
"ok": True,
"item_id": "51100639510",
"account_name": "主店",
"created": False,
}
)
self.assertIn("主店已聚焦现有页面", statuses[-1])
self.assertIsNotNone(db.get_task(task.id, path=cfg["db_path"]))
tab.apply_thread = object()
tab.task_table.leftDoubleClicked.emit(index)
self.assertIn("更新或回写正在进行", statuses[-1])
self.assertEqual(1, len(FakeProductTabOpenWorker.instances))
self.assert_removed(temp_dir)
def test_collect_tab_can_filter_unmatched_tasks_from_summary_bar(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
+41
View File
@@ -24,6 +24,7 @@ from app.gui.workers import (
ProductSuiteAiWriteWorker,
ProductSuiteGenerateWorker,
ProductSuiteHistoryExportWorker,
ProductTabOpenWorker,
CollectWorker,
)
@@ -120,6 +121,46 @@ class WorkerTests(unittest.TestCase):
self.assertEqual([(-1, "模拟失败")], failed)
self.assertEqual([{"ok": False, "error": "模拟失败"}], finished)
def test_product_tab_open_worker_focuses_product_without_task_write(self):
account = SimpleNamespace(
alias="alias-a", account_name="主店", debug_port=9222
)
with mock.patch(
"app.gui.workers.db.get_account_by_alias", return_value=account
) as get_account, mock.patch(
"app.gui.workers.chrome.is_running", return_value=True
) as is_running, mock.patch(
"app.gui.workers.editor.open_or_focus_product_tab",
return_value={"created": False, "target_id": "target-existing"},
) as open_tab, mock.patch("app.gui.workers.db.set_generated") as set_generated:
result = ProductTabOpenWorker(
"alias-a", "51100639510", db_path="test.db"
).execute()
self.assertTrue(result["ok"])
self.assertFalse(result["created"])
self.assertEqual("主店", result["account_name"])
get_account.assert_called_once_with("alias-a", path="test.db")
is_running.assert_called_once_with(9222)
open_tab.assert_called_once_with(account, "51100639510")
set_generated.assert_not_called()
def test_product_tab_open_worker_reports_missing_chrome_without_starting_it(self):
account = SimpleNamespace(
alias="alias-a", account_name="主店", debug_port=9222
)
with mock.patch(
"app.gui.workers.db.get_account_by_alias", return_value=account
), mock.patch(
"app.gui.workers.chrome.is_running", return_value=False
), mock.patch("app.gui.workers.editor.open_or_focus_product_tab") as open_tab:
result = ProductTabOpenWorker("alias-a", "51100639510").execute()
self.assertFalse(result["ok"])
self.assertEqual("CHROME_NOT_RUNNING", result["reason"])
self.assertIn("④账号管理", result["message"])
open_tab.assert_not_called()
def test_collect_worker_scope_skips_non_normal_without_overwriting_old_content(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = os.path.join(temp_dir, "cmshopee.db")