Add new empty scheme creation action and support empty JSON load
- Добавлено действие Создать... (Ctrl+N) в меню Файл для инициализации нового файла схемы. - Реализована автоматическая запись дефолтной схемы circles при открытии пустого или содержащего только пробелы json-файла. - Добавлены соответствующие автоматические тесты в qa_smoke.py.
This commit is contained in:
parent
fa1497bee7
commit
c3b98d7fe8
@ -336,6 +336,7 @@ class MainWindow(QMainWindow):
|
||||
self.actions[key] = action
|
||||
return action
|
||||
|
||||
add_action("new", "Создать...", self.new_file, "file-plus", QKeySequence.New)
|
||||
add_action("open", "Открыть", self.open_file, "folder-open", QKeySequence.Open)
|
||||
add_action("save", "Сохранить", self.save_file, "save", QKeySequence.Save)
|
||||
add_action("save_as", "Сохранить как", self.save_file_as, "save-all", QKeySequence.SaveAs)
|
||||
@ -446,6 +447,7 @@ class MainWindow(QMainWindow):
|
||||
menu_bar = self.menuBar()
|
||||
menu_bar.clear()
|
||||
file_menu = menu_bar.addMenu("Файл")
|
||||
file_menu.addAction(self.actions["new"])
|
||||
file_menu.addAction(self.actions["open"])
|
||||
file_menu.addAction(self.actions["save"])
|
||||
file_menu.addAction(self.actions["save_as"])
|
||||
@ -904,6 +906,43 @@ class MainWindow(QMainWindow):
|
||||
action.setText("Скрыть боковую панель")
|
||||
action.setToolTip("Скрыть боковую панель")
|
||||
|
||||
def new_file(self) -> None:
|
||||
if not self._confirm_discard_changes():
|
||||
return
|
||||
path, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Создать новую схему",
|
||||
str(APP_DIR),
|
||||
"JSON files (*.json);;All files (*.*)",
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
p = Path(path)
|
||||
if not p.suffix:
|
||||
p = p.with_suffix(".json")
|
||||
try:
|
||||
p.write_text('{"circles": []}', encoding="utf-8")
|
||||
except Exception as exc:
|
||||
show_critical(self, "Ошибка создания файла", str(exc))
|
||||
return
|
||||
loaded = MappingDocument()
|
||||
try:
|
||||
loaded.load(p)
|
||||
except Exception as exc:
|
||||
show_critical(self, "Ошибка загрузки нового файла", str(exc))
|
||||
return
|
||||
self.document = loaded
|
||||
self._dirty = False
|
||||
self._undo_stack.clear()
|
||||
self._redo_stack.clear()
|
||||
self.active_line_id = None
|
||||
self.canvas.set_document(self.document)
|
||||
self.canvas.set_active_line(self.active_line_id)
|
||||
self.refresh_tree()
|
||||
self._sync_canvas_to_active_line()
|
||||
self.status.setText(f"Создана новая пустая схема: {p}")
|
||||
self._refresh_ui_state()
|
||||
|
||||
def open_file(self) -> None:
|
||||
if not self._confirm_discard_changes():
|
||||
return
|
||||
|
||||
10
models.py
10
models.py
@ -43,6 +43,16 @@ _LED_POINT_KNOWN_FIELDS = {
|
||||
|
||||
def load_json_document(path: str | Path) -> dict[str, Any]:
|
||||
path = Path(path)
|
||||
if path.exists():
|
||||
try:
|
||||
if path.stat().st_size == 0:
|
||||
path.write_text('{"circles": []}', encoding="utf-8")
|
||||
else:
|
||||
content = path.read_bytes()
|
||||
if not content.strip():
|
||||
path.write_text('{"circles": []}', encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
text, was_converted = _read_text_as_utf8(path)
|
||||
if was_converted:
|
||||
path.write_text(text, encoding="utf-8")
|
||||
|
||||
62
qa_smoke.py
62
qa_smoke.py
@ -69,6 +69,13 @@ except ImportError:
|
||||
QDialogButtonBox = None
|
||||
QGraphicsItem = None
|
||||
QMessageBox = None
|
||||
QColor = None
|
||||
QImage = None
|
||||
QKeySequence = None
|
||||
QApplication = None
|
||||
QDialogButtonBox = None
|
||||
QGraphicsItem = None
|
||||
QMessageBox = None
|
||||
QToolBar = None
|
||||
|
||||
|
||||
@ -77,6 +84,7 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
def main() -> None:
|
||||
tests: list[Callable[[], None]] = [
|
||||
test_load_empty_json_file,
|
||||
test_load_supported_json_files,
|
||||
test_load_raw_json_with_trailing_commas_and_bare_decimal_points,
|
||||
test_non_utf8_json_is_converted_to_utf8_on_load,
|
||||
@ -121,6 +129,7 @@ def main() -> None:
|
||||
test_panel_mapping_generation_with_strategies,
|
||||
test_block_renaming_and_naming_strategy,
|
||||
test_line_renaming_and_tree_shortcuts,
|
||||
test_new_file_action,
|
||||
test_export_mapping_to_image,
|
||||
test_save_preserves_unknown_source_fields,
|
||||
test_ordered_json_preserves_document_and_remaps_uids,
|
||||
@ -221,6 +230,25 @@ def main() -> None:
|
||||
print(f"All {len(tests)} QA smoke checks passed.")
|
||||
|
||||
|
||||
def test_load_empty_json_file() -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Test 1: Completely empty file (0 bytes)
|
||||
path = Path(temp_dir) / "empty.json"
|
||||
path.write_bytes(b"")
|
||||
doc = MappingDocument()
|
||||
doc.load(path)
|
||||
assert len(doc.points) == 0
|
||||
assert path.read_text(encoding="utf-8") == '{"circles": []}'
|
||||
|
||||
# Test 2: Whitespace-only file
|
||||
path_ws = Path(temp_dir) / "whitespace.json"
|
||||
path_ws.write_text(" \n \t ", encoding="utf-8")
|
||||
doc_ws = MappingDocument()
|
||||
doc_ws.load(path_ws)
|
||||
assert len(doc_ws.points) == 0
|
||||
assert path_ws.read_text(encoding="utf-8") == '{"circles": []}'
|
||||
|
||||
|
||||
def test_load_supported_json_files() -> None:
|
||||
fixtures = [
|
||||
PROJECT_ROOT / "circles.json",
|
||||
@ -1132,6 +1160,31 @@ def test_line_renaming_and_tree_shortcuts() -> None:
|
||||
|
||||
window.close()
|
||||
|
||||
def test_new_file_action() -> None:
|
||||
if MainWindow is None or QApplication is None:
|
||||
return
|
||||
app = QApplication.instance() or QApplication([])
|
||||
window = MainWindow()
|
||||
|
||||
from PySide6.QtWidgets import QFileDialog
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
new_path = Path(temp_dir) / "created_scheme.json"
|
||||
|
||||
original_getSaveFileName = QFileDialog.getSaveFileName
|
||||
QFileDialog.getSaveFileName = lambda *args, **kwargs: (str(new_path), "JSON files (*.json)")
|
||||
try:
|
||||
window.new_file()
|
||||
finally:
|
||||
QFileDialog.getSaveFileName = original_getSaveFileName
|
||||
|
||||
assert new_path.exists()
|
||||
assert new_path.stat().st_size > 0
|
||||
assert len(window.document.points) == 0
|
||||
assert window.document.path == new_path
|
||||
|
||||
window.close()
|
||||
|
||||
|
||||
|
||||
def test_export_mapping_to_image() -> None:
|
||||
if QApplication is None:
|
||||
@ -1856,6 +1909,11 @@ def test_canvas_refresh_does_not_accumulate_connection_items() -> None:
|
||||
assert len(canvas.items_by_id) == len(doc.points)
|
||||
|
||||
|
||||
def test_main_window_has_classic_menu_bar() -> None:
|
||||
if MainWindow is None or QApplication is None:
|
||||
return
|
||||
app = QApplication.instance() or QApplication([])
|
||||
window = MainWindow()
|
||||
def test_main_window_has_classic_menu_bar() -> None:
|
||||
if MainWindow is None or QApplication is None:
|
||||
return
|
||||
@ -1877,7 +1935,7 @@ def test_classic_menu_contains_expected_actions() -> None:
|
||||
app.processEvents()
|
||||
|
||||
expected = {
|
||||
"Файл": ["Открыть", "Сохранить", "Сохранить как", "Сгенерировать скрипт", "Параметры"],
|
||||
"Файл": ["Создать...", "Открыть", "Сохранить", "Сохранить как", "Сгенерировать скрипт", "Параметры"],
|
||||
"Правка": ["Отменить", "Повторить", "Удалить фиксацию", "Удалить точки", "Удалить все", "Очистить выбор"],
|
||||
"Вид": [
|
||||
"Показать всё",
|
||||
@ -1974,6 +2032,7 @@ def test_main_window_actions_use_lucide_icons() -> None:
|
||||
app.processEvents()
|
||||
|
||||
expected_icon_actions = {
|
||||
"new",
|
||||
"open",
|
||||
"save",
|
||||
"save_as",
|
||||
@ -2017,6 +2076,7 @@ def test_main_window_action_shortcuts_are_assigned() -> None:
|
||||
app.processEvents()
|
||||
|
||||
expected = {
|
||||
"new": "Ctrl+N",
|
||||
"generate_script": "Ctrl+G",
|
||||
"settings": "Ctrl+,",
|
||||
"delete_points": "Ctrl+Del",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user