186 lines
5.9 KiB
Python
186 lines
5.9 KiB
Python
|
|
"""Диалоги должны собираться без единого проглоченного исключения.
|
|||
|
|
|
|||
|
|
Qt не роняет приложение, когда слот падает: он печатает traceback и живёт
|
|||
|
|
дальше. Внешне это выглядит как «работает, но криво» — например, у формы
|
|||
|
|
продажи однажды не подставлялась оплата, потому что сигнал редактора строк
|
|||
|
|
приходил раньше, чем создавалось поле оплаты.
|
|||
|
|
|
|||
|
|
Поэтому здесь перехватывается sys.excepthook, а не просто проверяется, что
|
|||
|
|
код не бросил исключение наружу.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
from datetime import date, timedelta
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
|
|||
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|||
|
|
|
|||
|
|
from PySide6.QtWidgets import QApplication # noqa: E402
|
|||
|
|
|
|||
|
|
from app import journal, ledger # noqa: E402
|
|||
|
|
from app.models import KIND_RETAIL, KIND_SELF # noqa: E402
|
|||
|
|
from app.ui import theme # noqa: E402
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture(scope="session")
|
|||
|
|
def qapp():
|
|||
|
|
app = QApplication.instance() or QApplication([])
|
|||
|
|
theme.apply(app)
|
|||
|
|
return app
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def silent():
|
|||
|
|
"""Собрать всё, что Qt проглотил бы, напечатав в stderr."""
|
|||
|
|
caught = []
|
|||
|
|
|
|||
|
|
def hook(exc_type, exc, tb):
|
|||
|
|
caught.append(f"{exc_type.__name__}: {exc}")
|
|||
|
|
|
|||
|
|
previous = sys.excepthook
|
|||
|
|
sys.excepthook = hook
|
|||
|
|
try:
|
|||
|
|
yield caught
|
|||
|
|
finally:
|
|||
|
|
sys.excepthook = previous
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Ctx:
|
|||
|
|
def __init__(self, vault):
|
|||
|
|
self.vault = vault
|
|||
|
|
self.report = ledger.build(vault.doc)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def currency(self):
|
|||
|
|
return self.vault.doc.settings.currency
|
|||
|
|
|
|||
|
|
def changed(self):
|
|||
|
|
self.report = ledger.build(self.vault.doc)
|
|||
|
|
|
|||
|
|
def sync_now(self, quiet=False):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def sync_status_text(self):
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
def restart_sync_timer(self):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
conflict_message = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def ctx(qapp, vault, buns):
|
|||
|
|
today = date.today()
|
|||
|
|
journal.create_batch(
|
|||
|
|
vault, today - timedelta(days=3), today + timedelta(days=11),
|
|||
|
|
[{"product_id": buns["мак"].id, "qty": 20, "unit_cost": 20}],
|
|||
|
|
)
|
|||
|
|
vasya = journal.create_counterparty(vault, "Вася")
|
|||
|
|
journal.create_sale(
|
|||
|
|
vault, today, KIND_RETAIL, [{"product_id": buns["мак"].id, "qty": 2, "unit_price": 35}],
|
|||
|
|
counterparty_id=vasya.id, paid_amount=0,
|
|||
|
|
)
|
|||
|
|
journal.create_sale(
|
|||
|
|
vault, today, KIND_SELF, [{"product_id": buns["повидло"].id, "qty": 1, "unit_price": 0}]
|
|||
|
|
)
|
|||
|
|
return Ctx(vault)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_new_sale_dialog_is_quiet(ctx, silent):
|
|||
|
|
from app.ui.page_sales import SaleDialog
|
|||
|
|
|
|||
|
|
dialog = SaleDialog(ctx)
|
|||
|
|
assert silent == []
|
|||
|
|
# Оплата должна подставиться сразу — ради этого сигнал и существует.
|
|||
|
|
assert dialog.paid.value_decimal() == dialog.lines.total()
|
|||
|
|
assert dialog.paid_hint.text()
|
|||
|
|
dialog.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_existing_sale_dialog_is_quiet(ctx, silent):
|
|||
|
|
from app.ui.page_sales import SaleDialog
|
|||
|
|
|
|||
|
|
sale = ctx.vault.doc.sales[0]
|
|||
|
|
dialog = SaleDialog(ctx, sale)
|
|||
|
|
assert silent == []
|
|||
|
|
# У существующей продажи платежи правятся на экране долгов.
|
|||
|
|
assert dialog.paid is None
|
|||
|
|
dialog.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_switching_kind_is_quiet(ctx, silent):
|
|||
|
|
from app.ui.page_sales import SaleDialog
|
|||
|
|
from app.models import SALE_KINDS
|
|||
|
|
|
|||
|
|
dialog = SaleDialog(ctx)
|
|||
|
|
for index in range(len(SALE_KINDS)):
|
|||
|
|
dialog.kind.setCurrentIndex(index)
|
|||
|
|
assert silent == []
|
|||
|
|
dialog.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_batch_dialogs_are_quiet(ctx, silent):
|
|||
|
|
from app.ui.page_batches import BatchDialog, PaymentDialog
|
|||
|
|
|
|||
|
|
new = BatchDialog(ctx)
|
|||
|
|
new.date.set_date(date.today() - timedelta(days=7)) # двигает дедлайн
|
|||
|
|
existing = BatchDialog(ctx, ctx.vault.doc.batches[0])
|
|||
|
|
payment = PaymentDialog(ctx, ctx.vault.doc.batches[0], 100)
|
|||
|
|
assert silent == []
|
|||
|
|
for d in (new, existing, payment):
|
|||
|
|
d.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_product_dialogs_are_quiet(ctx, silent):
|
|||
|
|
from app.ui.page_products import PriceDialog, ProductDialog
|
|||
|
|
|
|||
|
|
product = ctx.vault.doc.products[0]
|
|||
|
|
creating = ProductDialog("₽")
|
|||
|
|
creating.unit.setText("л") # пересобирает заголовок фасовок
|
|||
|
|
creating.packs.add_pack("бутылка", "1.5", 170)
|
|||
|
|
editing = ProductDialog("₽", product)
|
|||
|
|
prices = PriceDialog("₽", product)
|
|||
|
|
assert silent == []
|
|||
|
|
for d in (creating, editing, prices):
|
|||
|
|
d.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_quick_sales_dialog_is_quiet(ctx, silent):
|
|||
|
|
from app.ui.quick_sales import QuickSalesDialog
|
|||
|
|
|
|||
|
|
dialog = QuickSalesDialog(ctx)
|
|||
|
|
dialog.add_row()
|
|||
|
|
dialog.table.setCurrentCell(0, 0)
|
|||
|
|
dialog.duplicate_row()
|
|||
|
|
dialog.period_from.set_date(date.today() - timedelta(days=60))
|
|||
|
|
dialog.remove_row()
|
|||
|
|
assert silent == []
|
|||
|
|
dialog.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_payment_dialog_is_quiet(ctx, silent):
|
|||
|
|
from app.ui.page_debts import PaymentDialog
|
|||
|
|
|
|||
|
|
dialog = PaymentDialog(ctx.report.summary.receivable, "₽")
|
|||
|
|
assert silent == []
|
|||
|
|
dialog.deleteLater()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_every_page_refreshes_quietly(ctx, silent):
|
|||
|
|
from app.ui.page_batches import BatchesPage
|
|||
|
|
from app.ui.page_dashboard import DashboardPage
|
|||
|
|
from app.ui.page_debts import DebtsPage
|
|||
|
|
from app.ui.page_journal import JournalPage
|
|||
|
|
from app.ui.page_products import ProductsPage
|
|||
|
|
from app.ui.page_sales import SalesPage
|
|||
|
|
|
|||
|
|
for cls in (DashboardPage, BatchesPage, SalesPage, DebtsPage, ProductsPage, JournalPage):
|
|||
|
|
page = cls(ctx)
|
|||
|
|
page.refresh()
|
|||
|
|
page.refresh() # повторная перерисовка — там живёт восстановление выделения
|
|||
|
|
assert silent == [], f"{cls.__name__}: {silent}"
|
|||
|
|
page.deleteLater()
|