feat(apply): open product page on left double click
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user