food-market/tests/test_input.py

161 lines
5.6 KiB
Python
Raw Permalink Normal View History

Починен ввод цены и выделение в таблицах Цену было невозможно набрать. В прошлой правке я стал вырезать пробелы из вводимого текста, чтобы принимались разделители разрядов, — но у поля цены суффикс « ₽» начинается с пробела. Каждое нажатие ломало суффикс, Qt отвергало символ, и в поле оставалась только первая цифра: набираешь «25,50», получаешь 2. Теперь текст на каждом нажатии не трогается вообще: сначала пробуем как есть, и только если Qt отверг именно из-за точки — повторяем с запятой. Второе: форма продажи подключала сигнал редактора строк раньше, чем создавала поле оплаты. Qt глотает исключения в слотах, поэтому вместо падения оплата просто молча не подставлялась. Третье: выделение в таблицах держалось за номер строки. Таблицы отсортированы, и после перерисовки под тем же номером оказывался другой объект — можно было удалить не тот товар. Теперь выделение восстанавливается по идентификатору. Тесты: настоящие нажатия клавиш через QTest вместо проверок validate(), которые этот баг пропустили, и перехват sys.excepthook, чтобы проглоченные Qt исключения считались провалом. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 03:13:45 +07:00
"""Ввод с клавиатуры и поведение таблиц.
Отдельный файл, потому что проверять надо именно нажатия клавиш через QTest.
Проверки уровня validate() однажды уже прошли зелёными, пока набрать цену
в поле было физически невозможно: у поля суффикс « », и «очистка» текста
на каждом нажатии его ломала, из-за чего принимался только первый символ.
"""
import os
from decimal import Decimal
import pytest
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6.QtTest import QTest # noqa: E402
from PySide6.QtWidgets import QApplication # noqa: E402
from app import journal # noqa: E402
from app.ui import theme # noqa: E402
from app.ui import widgets as w # noqa: E402
@pytest.fixture(scope="session")
def qapp():
app = QApplication.instance() or QApplication([])
theme.apply(app)
return app
def type_in(spin, keys: str):
"""Набрать значение так, как это делает человек: выделить и напечатать."""
spin.show()
spin.setFocus()
spin.lineEdit().selectAll()
QTest.keyClicks(spin.lineEdit(), keys)
spin.interpretText()
return spin.value_decimal()
# --- деньги ---------------------------------------------------------------
@pytest.mark.parametrize(
"keys,expected",
[
("25", "25.00"),
("25,50", "25.50"),
("25.50", "25.50"),
("1200", "1200.00"),
("1234,56", "1234.56"),
("0,5", "0.50"),
("0.5", "0.50"),
],
)
def test_price_can_actually_be_typed(qapp, keys, expected):
"""Каждый символ должен доходить до значения, а не только первый."""
spin = w.MoneySpin()
spin.set_decimal(20)
assert type_in(spin, keys) == Decimal(expected)
spin.deleteLater()
def test_currency_suffix_survives_typing(qapp):
spin = w.MoneySpin()
spin.set_decimal(20)
type_in(spin, "137,40")
assert spin.lineEdit().text().endswith("")
assert spin.value_decimal() == Decimal("137.40")
spin.deleteLater()
def test_price_without_currency_also_works(qapp):
spin = w.MoneySpin(currency="")
spin.set_decimal(0)
assert type_in(spin, "99.99") == Decimal("99.99")
spin.deleteLater()
# --- количества -----------------------------------------------------------
@pytest.mark.parametrize(
"keys,expected",
[("3", "3.000"), ("0,5", "0.500"), ("0.5", "0.500"), ("1,125", "1.125"), ("1200", "1200.000")],
)
def test_quantity_can_actually_be_typed(qapp, keys, expected):
spin = w.QtySpin()
spin.set_decimal(1)
assert type_in(spin, keys) == Decimal(expected)
spin.deleteLater()
def test_typing_over_an_existing_value_replaces_it(qapp):
"""Выделил всё, напечатал новое — старое не должно примешиваться."""
spin = w.MoneySpin()
spin.set_decimal(1999)
assert type_in(spin, "7") == Decimal("7.00")
spin.deleteLater()
# --- выделение в таблицах -------------------------------------------------
def rows_for(names):
return [[w.text_item(name)] for name in names]
def test_selection_follows_the_object_not_the_row(qapp):
"""Таблицы отсортированы: под тем же номером после перерисовки может
оказаться другой объект, и «Удалить» сработает не на том."""
table = w.table(["Название"])
w.fill(table, rows_for(["Булка", "Ватрушка"]), keys=["b", "v"])
w.select_by_key(table, "v")
assert w.selected_key(table) == "v"
# Появился товар, встающий при сортировке первым.
w.fill(table, rows_for(["Ананас", "Булка", "Ватрушка"]), keys=["a", "b", "v"])
assert w.selected_key(table) == "v"
table.deleteLater()
def test_selection_survives_a_plain_refresh(qapp):
table = w.table(["Название"])
w.fill(table, rows_for(["Булка", "Ватрушка"]), keys=["b", "v"])
w.select_by_key(table, "b")
w.fill(table, rows_for(["Булка", "Ватрушка"]), keys=["b", "v"])
assert w.selected_key(table) == "b"
table.deleteLater()
def test_selection_is_dropped_when_the_object_is_gone(qapp):
table = w.table(["Название"])
w.fill(table, rows_for(["Булка", "Ватрушка"]), keys=["b", "v"])
w.select_by_key(table, "v")
w.fill(table, rows_for(["Булка"]), keys=["b"])
assert w.selected_key(table) != "v"
table.deleteLater()
# --- правка цены целиком, как это делает пользователь ---------------------
def test_price_edit_reaches_the_product(qapp, vault, buns):
"""Сквозной путь: набрать цены в диалоге и увидеть их в товаре."""
from app.ui.page_products import PriceDialog
product = buns["мак"]
dialog = PriceDialog("", product)
assert type_in(dialog.cost, "25,50") == Decimal("25.50")
assert type_in(dialog.retail, "44.90") == Decimal("44.90")
journal.change_prices(
vault, product.id, dialog.cost.value_decimal(), dialog.retail.value_decimal()
)
updated = vault.doc.product(product.id)
assert updated.cost_price == Decimal("25.50")
assert updated.retail_price == Decimal("44.90")
assert len(updated.price_history) == 2
dialog.deleteLater()