from decimal import Decimal import time from grid_trading.domain.models import Instrument def test_formatters_render_money_percent_and_empty_values(): from grid_trading.ui.formatters import format_money, format_percent, format_price, format_quantity assert format_money(Decimal("1234.5")) == "1,234.50" assert format_price(Decimal("3.956")) == "3.96" assert format_percent(Decimal("0.1234")) == "12.34%" assert format_quantity(1200) == "1,200" assert format_money(None) == "-" def test_main_window_can_be_constructed_offscreen(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication, QPushButton from grid_trading.services.trading_service import TradingService from grid_trading.ui.main_window import MainWindow app = QApplication.instance() or QApplication([]) service = TradingService(tmp_path / "grid.db") window = MainWindow(service) assert window.windowTitle() == "Grid Trading Manager" assert window.holdings_table.columnCount() > 0 assert any(button.text() == "刷新行情" for button in window.findChildren(QPushButton)) window.close() service.close() app.processEvents() def test_main_window_contains_grid_level_table(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication, QGroupBox from grid_trading.services.trading_service import TradingService from grid_trading.ui.main_window import MainWindow app = QApplication.instance() or QApplication([]) service = TradingService(tmp_path / "grid.db") window = MainWindow(service) assert window.grid_levels_table.columnCount() == 7 assert any(group.title() == "网格档位" for group in window.findChildren(QGroupBox)) window.close() service.close() app.processEvents() def test_main_window_contains_open_grid_lots_table(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication, QGroupBox from grid_trading.services.trading_service import TradingService from grid_trading.ui.main_window import MainWindow app = QApplication.instance() or QApplication([]) service = TradingService(tmp_path / "grid.db") window = MainWindow(service) assert window.open_grid_lots_table.columnCount() == 8 assert any(group.title() == "待卖网格" for group in window.findChildren(QGroupBox)) window.close() service.close() app.processEvents() def test_instrument_dialog_does_not_collect_manual_current_price(monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication, QLabel from grid_trading.ui.dialogs import InstrumentDialog app = QApplication.instance() or QApplication([]) dialog = InstrumentDialog() labels = [label.text() for label in dialog.findChildren(QLabel)] assert "手动价格" not in labels assert not hasattr(dialog, "manual_price_edit") assert dialog.to_instrument().manual_price is None dialog.close() app.processEvents() def test_trade_dialog_labels_price_as_trade_price(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication, QLabel from grid_trading.services.trading_service import TradingService from grid_trading.ui.dialogs import TradeDialog app = QApplication.instance() or QApplication([]) service = TradingService(tmp_path / "grid.db") service.ensure_defaults() account = service.get_active_account() dialog = TradeDialog(service, account.id, [Instrument(id=1, code="510300", name="沪深300ETF")]) labels = [label.text() for label in dialog.findChildren(QLabel)] assert "成交价" in labels dialog.close() service.close() app.processEvents() def test_quote_refresh_does_not_block_main_window(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication from grid_trading.services.trading_service import TradingService from grid_trading.ui.main_window import MainWindow class SlowQuoteProvider: def fetch_quotes(self, instruments): time.sleep(0.3) return {} app = QApplication.instance() or QApplication([]) service = TradingService(tmp_path / "grid.db", quote_provider=SlowQuoteProvider()) service.ensure_defaults() service.add_instrument(Instrument(id=None, code="000001", name="Ping An Bank")) window = MainWindow(service) started_at = time.perf_counter() window._refresh_quotes() elapsed = time.perf_counter() - started_at assert elapsed < 0.15 deadline = time.perf_counter() + 2 while getattr(window, "_quote_thread", None) is not None and time.perf_counter() < deadline: app.processEvents() time.sleep(0.01) assert getattr(window, "_quote_thread", None) is None window.close() service.close() app.processEvents()