food-market/tests/test_input.py
Claude 4dd9a071c0 Починен ввод цены и выделение в таблицах
Цену было невозможно набрать. В прошлой правке я стал вырезать пробелы
из вводимого текста, чтобы принимались разделители разрядов, — но у поля
цены суффикс « ₽» начинается с пробела. Каждое нажатие ломало суффикс,
Qt отвергало символ, и в поле оставалась только первая цифра: набираешь
«25,50», получаешь 2.

Теперь текст на каждом нажатии не трогается вообще: сначала пробуем как
есть, и только если Qt отверг именно из-за точки — повторяем с запятой.

Второе: форма продажи подключала сигнал редактора строк раньше, чем
создавала поле оплаты. Qt глотает исключения в слотах, поэтому вместо
падения оплата просто молча не подставлялась.

Третье: выделение в таблицах держалось за номер строки. Таблицы
отсортированы, и после перерисовки под тем же номером оказывался другой
объект — можно было удалить не тот товар. Теперь выделение
восстанавливается по идентификатору.

Тесты: настоящие нажатия клавиш через QTest вместо проверок validate(),
которые этот баг пропустили, и перехват sys.excepthook, чтобы
проглоченные Qt исключения считались провалом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:13:45 +03:00

161 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Ввод с клавиатуры и поведение таблиц.
Отдельный файл, потому что проверять надо именно нажатия клавиш через 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()