101 lines
6.3 KiB
Python
101 lines
6.3 KiB
Python
"""Real Qt/browser rendering with synthetic queues; no customer messages."""
|
|
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest import TestCase, mock
|
|
from PySide6.QtWidgets import QApplication, QMainWindow
|
|
from PySide6.QtCore import QEventLoop, QTimer
|
|
import wechat_gui_qt as qt
|
|
from review_state import pending_attention_counts
|
|
import test_desktop_review_visibility as review_tests
|
|
|
|
|
|
class AttentionBadgeTests(TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.app = QApplication.instance() or QApplication([])
|
|
|
|
def setUp(self):
|
|
review_tests.DesktopReviewTest.setUp(self)
|
|
|
|
def test_counts_are_disjoint_and_count_conversations_not_messages(self):
|
|
states = {'review': {'awaiting_review': True, 'reply_text': '合成草稿', 'last_lines': ['a'] * 50},
|
|
'human': {'awaiting_review': True, 'manual_required': True},
|
|
'uncertain': {'awaiting_review': True, 'send_state': 'uncertain'},
|
|
'ordinary': {'stage': 'queued'}, 'bad': None}
|
|
self.assertEqual(pending_attention_counts(states), {'review': 1, 'human': 2, 'total': 3})
|
|
self.assertEqual(pending_attention_counts({}), {'review': 0, 'human': 0, 'total': 0})
|
|
|
|
def test_queue_refresh_updates_both_native_entrances_under_unrelated_filter(self):
|
|
sidebar = qt.Sidebar(); self.addCleanup(sidebar.deleteLater)
|
|
self.page.attentionChanged.connect(sidebar.set_attention_counts)
|
|
review_tests.DesktopReviewTest.seed(self)
|
|
self.page._set_task_filter('等待中')
|
|
self.assertEqual([b.attention_count for b in sidebar.nav_buttons[:2]], [3, 3])
|
|
self.assertIn('待审核 1,需人工 2', sidebar.nav_buttons[0].toolTip())
|
|
self.assertEqual(sidebar.nav_buttons[2].attention_count, 0)
|
|
self.path.write_text('{}', encoding='utf-8')
|
|
self.page._load_pending_states()
|
|
self.assertEqual([b.attention_count for b in sidebar.nav_buttons[:2]], [0, 0])
|
|
|
|
def test_hidden_queue_refreshes_counts_without_rebuilding_widgets(self):
|
|
review_tests.DesktopReviewTest.seed(self)
|
|
host = SimpleNamespace(_console_view='automation', queue_page=self.page,
|
|
stack=SimpleNamespace(currentWidget=lambda: object()),
|
|
_schedule_console_push=mock.Mock())
|
|
with mock.patch.object(self.page, 'refresh_data') as full:
|
|
qt.MainWindow._refresh_queue_page(host)
|
|
full.assert_not_called(); host._schedule_console_push.assert_called_once()
|
|
self.assertEqual(pending_attention_counts(self.page.pending_states)['total'], 3)
|
|
|
|
def test_native_badge_scales_and_preserves_exact_accessible_count(self):
|
|
button = qt.SidebarNavButton('robot', '自动回复'); self.addCleanup(button.deleteLater)
|
|
for scale in (1, 2, 3):
|
|
with mock.patch.object(qt, '_UI_SCALE', scale):
|
|
button.resize(100 * scale, 77 * scale)
|
|
button.set_attention_counts(120, 3)
|
|
image = button.grab().toImage()
|
|
red = sum(1 for y in range(image.height()) for x in range(image.width())
|
|
if image.pixelColor(x,y).red() > 190 and image.pixelColor(x,y).green() < 90)
|
|
self.assertGreater(red, 30)
|
|
self.assertIn('123', button.accessibleName())
|
|
|
|
def test_web_badges_update_while_editing_and_hide_when_resolved(self):
|
|
owner = QMainWindow(); owner._push_console_state = mock.Mock()
|
|
shell = qt.ConsoleShell(owner); owner.setCentralWidget(shell); owner.resize(1440,1000); owner.show()
|
|
self.addCleanup(owner.deleteLater); self.addCleanup(owner.close)
|
|
if not shell.page_ready:
|
|
loaded=[]; loop=QEventLoop()
|
|
def finished(ok): loaded.append(bool(ok)); loop.quit()
|
|
shell.loadFinished.connect(finished); QTimer.singleShot(10000,loop.quit); loop.exec()
|
|
shell.loadFinished.disconnect(finished); self.assertEqual(loaded,[True])
|
|
def evaluate(script):
|
|
values=[]; loop=QEventLoop()
|
|
def completed(value): values.append(value); loop.quit()
|
|
shell.page().runJavaScript(script,completed); QTimer.singleShot(5000,loop.quit); loop.exec()
|
|
self.assertEqual(len(values),1); return values[0]
|
|
payload = {'view':'system','demo':False,'ready':True,'system':{'scale':'自动'},
|
|
'attention': {'review':1,'human':2,'total':3}}
|
|
evaluate('window.applyState(' + json.dumps(payload) + '); fit();')
|
|
loop=QEventLoop(); QTimer.singleShot(250,loop.quit); loop.exec()
|
|
query = '''JSON.stringify(Array.from(document.querySelectorAll('.nav-attention-badge')).map(b=>({
|
|
count:b.textContent, hidden:getComputedStyle(b).display==='none',
|
|
title:b.closest('button').title, red:getComputedStyle(b).backgroundColor,
|
|
inside:b.getBoundingClientRect().right<=b.closest('button').getBoundingClientRect().right, rect:[b.getBoundingClientRect().right,b.closest('button').getBoundingClientRect().right]
|
|
})))'''
|
|
badges = json.loads(evaluate(query))
|
|
self.assertEqual(len(badges),2)
|
|
self.assertTrue(all(b['count']=='3' and not b['hidden'] and b['inside'] for b in badges), badges)
|
|
self.assertTrue(all(b['red']=='rgb(230, 55, 79)' for b in badges))
|
|
evaluate("document.querySelector('#sys-server-url').focus(); document.querySelector('#sys-server-url').value='synthetic-edit';")
|
|
evaluate("window.applyState({view:'system',attention:{review:120,human:3,total:123}});")
|
|
self.assertTrue(all(b['count']=='99+' and '123' in b['title'] for b in json.loads(evaluate(query))))
|
|
self.assertEqual(evaluate("document.querySelector('#sys-server-url').value"), 'synthetic-edit')
|
|
evaluate("window.applyState({view:'system',attention:{review:0,human:0,total:0}});")
|
|
self.assertTrue(all(b['hidden'] for b in json.loads(evaluate(query))))
|
|
# Capture only this synthetic profile for visual verification.
|
|
evaluate("document.querySelector('#sys-server-url').blur(); window.applyState({view:'auto',people:[],task:null,metrics:{review:'12',human:'2'},attention:{review:12,human:2,total:14}});")
|
|
image_path = Path('C:/kefu/deploy/review-badges-20260917/badge-preview.png')
|
|
loop=QEventLoop(); QTimer.singleShot(150,loop.quit); loop.exec()
|
|
shell.grab().save(str(image_path))
|