feat: track settings changes and enforce Chinese UI copy
This commit is contained in:
@@ -84,6 +84,44 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.assertIsNotNone(value)
|
||||
self.assertEqual(color, value.name())
|
||||
|
||||
def make_fake_message_box(self, selected_label):
|
||||
boxes = []
|
||||
|
||||
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
|
||||
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[selected_label]
|
||||
|
||||
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(
|
||||
@@ -936,6 +974,198 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_settings_tab_tracks_dirty_state_and_programmatic_cmhub_refresh_is_clean(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg["ai"] = appconfig.default_config()["ai"]
|
||||
cfg["ai"]["backend"] = "cmhub"
|
||||
cfg["ai"]["cmhub"] = {
|
||||
"base_url": "https://cmhub.old",
|
||||
"title_alias": "title-old",
|
||||
"image_alias": "image-old",
|
||||
"connect_timeout": 9,
|
||||
"check_balance_before_batch": False,
|
||||
}
|
||||
cfg["cmhub_config_path"] = os.path.join(temp_dir, "config", "cmhub.json")
|
||||
appconfig.save_cmhub_config({"api_key": "sk-old-secret"}, path=cfg["cmhub_config_path"])
|
||||
tab = SettingsTab(config=cfg, config_path=cfg["config_path"], ai_models_path=cfg["ai_models_path"])
|
||||
self.addCleanup(tab.close)
|
||||
|
||||
self.assertFalse(tab.is_dirty())
|
||||
self.assertTrue(tab.unsaved_changes_label.isHidden())
|
||||
|
||||
tab._on_cmhub_finished(
|
||||
{
|
||||
"ok": True,
|
||||
"models": [
|
||||
{
|
||||
"alias": "title-fresh",
|
||||
"operation_type": "title",
|
||||
"pricing_status": "priced",
|
||||
"prices": [{"resolution": "1K", "points_cost": 1}],
|
||||
},
|
||||
{
|
||||
"alias": "image-fresh",
|
||||
"operation_type": "image",
|
||||
"pricing_status": "priced",
|
||||
"requires_image": True,
|
||||
"prices": [{"resolution": "1K", "points_cost": 5}],
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertFalse(tab.is_dirty())
|
||||
self.assertIn("记得点『保存设置』", tab.cmhub_result_label.text())
|
||||
|
||||
tab.cmhub_base_url_edit.setText("https://cmhub.example.com/api/v1/")
|
||||
self.assertTrue(tab.is_dirty())
|
||||
self.assertFalse(tab.unsaved_changes_label.isHidden())
|
||||
|
||||
with mock.patch("app.gui.QMessageBox.information") as info:
|
||||
self.assertTrue(tab.save_app_settings())
|
||||
|
||||
info.assert_called_once_with(tab, "保存设置", "设置已保存")
|
||||
self.assertFalse(tab.is_dirty())
|
||||
self.assertTrue(tab.unsaved_changes_label.isHidden())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_settings_tab_discard_unsaved_changes_reloads_saved_config_and_key(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg["ai"] = appconfig.default_config()["ai"]
|
||||
cfg["ai"]["backend"] = "cmhub"
|
||||
cfg["ai"]["cmhub"] = {
|
||||
"base_url": "https://cmhub.saved",
|
||||
"title_alias": "title-saved",
|
||||
"image_alias": "image-saved",
|
||||
"connect_timeout": 11,
|
||||
"check_balance_before_batch": False,
|
||||
}
|
||||
cfg["cmhub_config_path"] = os.path.join(temp_dir, "config", "cmhub.json")
|
||||
appconfig.save_config(
|
||||
{key: value for key, value in cfg.items() if key not in {"config_path", "ai_models_path", "cmhub_config_path"}},
|
||||
path=cfg["config_path"],
|
||||
)
|
||||
appconfig.save_cmhub_config({"api_key": "sk-saved-secret"}, path=cfg["cmhub_config_path"])
|
||||
tab = SettingsTab(config=cfg, config_path=cfg["config_path"], ai_models_path=cfg["ai_models_path"])
|
||||
self.addCleanup(tab.close)
|
||||
|
||||
tab.cmhub_base_url_edit.setText("https://cmhub.changed")
|
||||
tab.cmhub_api_key_edit.setText("sk-changed-secret")
|
||||
self.assertTrue(tab.is_dirty())
|
||||
|
||||
self.assertTrue(tab.discard_unsaved_changes())
|
||||
|
||||
self.assertFalse(tab.is_dirty())
|
||||
self.assertEqual("https://cmhub.saved", tab.cmhub_base_url_edit.text())
|
||||
self.assertEqual("sk-saved-secret", tab.cmhub_api_key_edit.text())
|
||||
self.assertTrue(tab.unsaved_changes_label.isHidden())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_main_window_cancelled_tab_change_keeps_user_on_dirty_settings(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
window = MainWindow(config=self.make_config(temp_dir))
|
||||
self.addCleanup(window.close)
|
||||
settings_index = TAB_TITLES.index("⑤ 设置")
|
||||
window.tabs.setCurrentIndex(settings_index)
|
||||
settings_tab = window.tabs.widget(settings_index)
|
||||
settings_tab.cmhub_base_url_edit.setText("https://cmhub.changed")
|
||||
|
||||
message_box, boxes = self.make_fake_message_box("取消")
|
||||
with mock.patch("app.gui.main_window.QMessageBox", message_box):
|
||||
window.tabs.setCurrentIndex(0)
|
||||
|
||||
self.assertEqual(1, len(boxes))
|
||||
self.assertEqual("未保存更改", boxes[0].title)
|
||||
self.assertEqual(["保存", "放弃", "取消"], list(boxes[0].buttons))
|
||||
self.assertEqual(settings_index, window.tabs.currentIndex())
|
||||
self.assertTrue(settings_tab.is_dirty())
|
||||
settings_tab._set_dirty(False)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_main_window_save_failure_keeps_user_on_dirty_settings(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
window = MainWindow(config=self.make_config(temp_dir))
|
||||
self.addCleanup(window.close)
|
||||
settings_index = TAB_TITLES.index("⑤ 设置")
|
||||
window.tabs.setCurrentIndex(settings_index)
|
||||
settings_tab = window.tabs.widget(settings_index)
|
||||
settings_tab.cmhub_base_url_edit.setText("https://cmhub.changed")
|
||||
|
||||
message_box, _ = self.make_fake_message_box("保存")
|
||||
with mock.patch("app.gui.main_window.QMessageBox", message_box), \
|
||||
mock.patch.object(settings_tab, "save_app_settings", return_value=False) as save_settings:
|
||||
window.tabs.setCurrentIndex(0)
|
||||
|
||||
save_settings.assert_called_once_with()
|
||||
self.assertEqual(settings_index, window.tabs.currentIndex())
|
||||
self.assertTrue(settings_tab.is_dirty())
|
||||
settings_tab._set_dirty(False)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_main_window_discard_tab_change_restores_saved_settings(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg["ai"] = appconfig.default_config()["ai"]
|
||||
cfg["ai"]["backend"] = "cmhub"
|
||||
cfg["ai"]["cmhub"] = {
|
||||
"base_url": "https://cmhub.saved",
|
||||
"title_alias": "title-saved",
|
||||
"image_alias": "image-saved",
|
||||
"connect_timeout": 10,
|
||||
"check_balance_before_batch": False,
|
||||
}
|
||||
cfg["cmhub_config_path"] = os.path.join(temp_dir, "config", "cmhub.json")
|
||||
appconfig.save_config(
|
||||
{key: value for key, value in cfg.items() if key not in {"config_path", "ai_models_path", "cmhub_config_path"}},
|
||||
path=cfg["config_path"],
|
||||
)
|
||||
appconfig.save_cmhub_config({"api_key": "sk-saved-secret"}, path=cfg["cmhub_config_path"])
|
||||
window = MainWindow(config=cfg, config_path=cfg["config_path"], ai_models_path=cfg["ai_models_path"])
|
||||
self.addCleanup(window.close)
|
||||
settings_index = TAB_TITLES.index("⑤ 设置")
|
||||
window.tabs.setCurrentIndex(settings_index)
|
||||
settings_tab = window.tabs.widget(settings_index)
|
||||
settings_tab.cmhub_base_url_edit.setText("https://cmhub.changed")
|
||||
settings_tab.cmhub_api_key_edit.setText("sk-changed-secret")
|
||||
|
||||
message_box, _ = self.make_fake_message_box("放弃")
|
||||
with mock.patch("app.gui.main_window.QMessageBox", message_box):
|
||||
window.tabs.setCurrentIndex(0)
|
||||
|
||||
self.assertEqual(0, window.tabs.currentIndex())
|
||||
self.assertFalse(settings_tab.is_dirty())
|
||||
self.assertEqual("https://cmhub.saved", settings_tab.cmhub_base_url_edit.text())
|
||||
self.assertEqual("sk-saved-secret", settings_tab.cmhub_api_key_edit.text())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_main_window_close_cancel_ignores_dirty_settings_close(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
window = MainWindow(config=self.make_config(temp_dir))
|
||||
self.addCleanup(window.close)
|
||||
settings_index = TAB_TITLES.index("⑤ 设置")
|
||||
window.tabs.setCurrentIndex(settings_index)
|
||||
settings_tab = window.tabs.widget(settings_index)
|
||||
settings_tab.cmhub_base_url_edit.setText("https://cmhub.changed")
|
||||
event = SimpleNamespace(accepted=False, ignored=False)
|
||||
event.accept = lambda: setattr(event, "accepted", True)
|
||||
event.ignore = lambda: setattr(event, "ignored", True)
|
||||
|
||||
message_box, _ = self.make_fake_message_box("取消")
|
||||
with mock.patch("app.gui.main_window.QMessageBox", message_box):
|
||||
window.closeEvent(event)
|
||||
|
||||
self.assertFalse(event.accepted)
|
||||
self.assertTrue(event.ignored)
|
||||
settings_tab._set_dirty(False)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generate_tab_has_prompt_editors_and_task_table(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
title_prompt_path = os.path.join(temp_dir, "title_prompt.txt")
|
||||
|
||||
Reference in New Issue
Block a user