From 3837302c1b429ca1d77595a6db34cef643da3faa Mon Sep 17 00:00:00 2001
From: Allie Crevier <4522213+creviera@users.noreply.github.com>
Date: Fri, 8 Mar 2019 09:39:30 -0800
Subject: [PATCH] UX parity between prototypes and coded client
---
securedrop_client/gui/main.py | 38 +++---
securedrop_client/gui/widgets.py | 114 ++++++++++++------
securedrop_client/resources/images/delete.png | Bin 0 -> 2275 bytes
.../resources/images/refresh.svg | 20 +++
securedrop_client/resources/images/send.png | Bin 0 -> 2275 bytes
securedrop_client/resources/images/trash.png | Bin 0 -> 1846 bytes
tests/gui/test_main.py | 18 +--
tests/gui/test_widgets.py | 9 +-
8 files changed, 132 insertions(+), 67 deletions(-)
create mode 100644 securedrop_client/resources/images/delete.png
create mode 100644 securedrop_client/resources/images/refresh.svg
create mode 100644 securedrop_client/resources/images/send.png
create mode 100644 securedrop_client/resources/images/trash.png
diff --git a/securedrop_client/gui/main.py b/securedrop_client/gui/main.py
index 877da095c8..ae08e2c867 100644
--- a/securedrop_client/gui/main.py
+++ b/securedrop_client/gui/main.py
@@ -20,13 +20,13 @@
along with this program. If not, see .
"""
import logging
-from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QDesktopWidget, QStatusBar
+from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QDesktopWidget, \
+ QStatusBar
from typing import List
from securedrop_client import __version__
from securedrop_client.db import Source
-from securedrop_client.gui.widgets import (ToolBar, MainView, LoginDialog,
- SourceConversationWrapper)
+from securedrop_client.gui.widgets import ToolBar, MainView, LoginDialog, SourceConversationWrapper
from securedrop_client.resources import load_icon
logger = logging.getLogger(__name__)
@@ -57,18 +57,29 @@ def __init__(self, sdc_home: str):
self.setWindowTitle(_("SecureDrop Client {}").format(__version__))
self.setWindowIcon(load_icon(self.icon))
+ self.central_widget = QWidget()
+ central_widget_layout = QVBoxLayout()
+ central_widget_layout.setContentsMargins(0, 0, 0, 0)
+ self.central_widget.setLayout(central_widget_layout)
+ self.setCentralWidget(self.central_widget)
+
+ self.status_bar = QStatusBar(self)
+ self.status_bar.setStyleSheet('background-color: #fff;')
+ central_widget_layout.addWidget(self.status_bar)
+
self.widget = QWidget()
- widget_layout = QVBoxLayout()
+ widget_layout = QHBoxLayout()
+ widget_layout.setContentsMargins(0, 0, 0, 0)
self.widget.setLayout(widget_layout)
- self.setCentralWidget(self.widget)
self.tool_bar = ToolBar(self.widget)
-
self.main_view = MainView(self.widget)
self.main_view.source_list.itemSelectionChanged.connect(self.on_source_changed)
widget_layout.addWidget(self.tool_bar, 1)
- widget_layout.addWidget(self.main_view, 6)
+ widget_layout.addWidget(self.main_view, 8)
+
+ central_widget_layout.addWidget(self.widget)
# Cache a dict of source.uuid -> SourceConversationWrapper
# We do this to not create/destroy widgets constantly (because it causes UI "flicker")
@@ -88,9 +99,7 @@ def setup(self, controller):
self.controller = controller # Reference the Client logic instance.
self.tool_bar.setup(self, controller)
- self.status_bar = QStatusBar(self)
- self.setStatusBar(self.status_bar)
- self.set_status('Started SecureDrop Client. Please sign in.', 20000)
+ self.set_status(_('Started SecureDrop Client. Please sign in.'), 20000)
self.login_dialog = LoginDialog(self)
self.main_view.setup(self.controller)
@@ -144,10 +153,9 @@ def show_sync(self, updated_on):
Display a message indicating the data-sync state.
"""
if updated_on:
- self.main_view.status.setText('Last refresh: ' +
- updated_on.humanize())
+ self.set_status(_('Last refresh: ' + updated_on.humanize()))
else:
- self.main_view.status.setText(_('Waiting to refresh...'))
+ self.set_status(_('Waiting to refresh...'), 5000)
def set_logged_in_as(self, username):
"""
@@ -188,9 +196,9 @@ def show_conversation_for(self, source: Source, is_authenticated: bool):
self.main_view.set_conversation(conversation_container)
- def set_status(self, message, duration=5000):
+ def set_status(self, message, duration=0):
"""
Display a status message to the user. Optionally, supply a duration
- (in milliseconds), the default value being a duration of 5 seconds.
+ (in milliseconds), the default will continuously show the message.
"""
self.status_bar.showMessage(message, duration)
diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py
index 4ec8ddf891..133dc691d9 100644
--- a/securedrop_client/gui/widgets.py
+++ b/securedrop_client/gui/widgets.py
@@ -22,8 +22,8 @@
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QListWidget, QLabel, QWidget, QListWidgetItem, QHBoxLayout, \
- QPushButton, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, \
- QMessageBox, QToolButton, QSizePolicy, QTextEdit
+ QPushButton, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, QMessageBox, \
+ QToolButton, QSizePolicy, QTextEdit
from typing import List
from uuid import uuid4
@@ -43,29 +43,42 @@ class ToolBar(QWidget):
def __init__(self, parent: QWidget):
super().__init__(parent)
- layout = QHBoxLayout(self)
- self.logo = QLabel()
- self.logo.setPixmap(load_image('header_logo.png'))
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
- self.user_state = QLabel(_("Signed out."))
+ self.user_state = QLabel(_('Signed out.'))
self.login = QPushButton(_('Sign in'))
+ self.login.setMaximumSize(80, 30)
self.login.clicked.connect(self.on_login_clicked)
self.logout = QPushButton(_('Sign out'))
self.logout.clicked.connect(self.on_logout_clicked)
+ self.logout.setMaximumSize(80, 30)
self.logout.setVisible(False)
- self.refresh = QPushButton(_('Refresh'))
+ self.refresh = QPushButton()
self.refresh.clicked.connect(self.on_refresh_clicked)
- self.refresh.setVisible(False)
+ self.refresh.setMaximumSize(30, 30)
+ refresh_pixmap = load_image('refresh.svg')
+
+ self.refresh.setIcon(QIcon(refresh_pixmap))
+ self.refresh.show()
+
+ self.logo = QLabel()
+ self.logo.setPixmap(load_image('icon.png'))
+ self.logo.setMinimumSize(200, 200)
+
+ journalist_layout = QHBoxLayout(self)
+ journalist_layout.addWidget(self.refresh, 1)
+ journalist_layout.addWidget(self.user_state, 5)
+ journalist_layout.addWidget(self.login, 5)
+ journalist_layout.addWidget(self.logout, 5)
+ journalist_layout.addStretch()
+ layout.addLayout(journalist_layout)
layout.addWidget(self.logo)
layout.addStretch()
- layout.addWidget(self.user_state)
- layout.addWidget(self.login)
- layout.addWidget(self.logout)
- layout.addWidget(self.refresh)
def setup(self, window, controller):
"""
@@ -84,7 +97,7 @@ def set_logged_in_as(self, username):
"""
Update the UI to reflect that the user is logged in as "username".
"""
- self.user_state.setText(_('Signed in as: ' + html.escape(username)))
+ self.user_state.setText(_(html.escape(username)))
self.login.setVisible(False)
self.logout.setVisible(True)
self.refresh.setVisible(True)
@@ -132,27 +145,29 @@ class MainView(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.layout = QHBoxLayout(self)
+ self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
left_column = QWidget(parent=self)
left_layout = QVBoxLayout()
+ left_layout.setContentsMargins(0, 0, 0, 0)
left_column.setLayout(left_layout)
- self.status = QLabel(_('Waiting to refresh...'))
self.error_status = QLabel('')
self.error_status.setObjectName('error_label')
-
- left_layout.addWidget(self.status)
- left_layout.addWidget(self.error_status)
+ # TODO: move this to the main window QStatusBar
+ # left_layout.addWidget(self.error_status)
self.source_list = SourceList(left_column)
left_layout.addWidget(self.source_list)
- self.layout.addWidget(left_column, 2)
+ self.layout.addWidget(left_column, 4)
- self.view_holder = QWidget()
self.view_layout = QVBoxLayout()
+ self.view_layout.setContentsMargins(0, 0, 0, 0)
+ self.view_holder = QWidget()
self.view_holder.setLayout(self.view_layout)
+
self.layout.addWidget(self.view_holder, 6)
def setup(self, controller):
@@ -372,11 +387,13 @@ def setup(self, controller):
self.setWindowTitle(_('Sign in to SecureDrop'))
main_layout = QHBoxLayout()
+ main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addStretch()
self.setLayout(main_layout)
form = QWidget()
layout = QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
form.setLayout(layout)
main_layout.addWidget(form)
@@ -486,7 +503,7 @@ class SpeechBubble(QWidget):
and journalist.
"""
- css = "padding: 10px; min-height:20px;border: 1px solid #999; border-radius: 18px;"
+ css = "padding:8px; min-height:32px; border:1px solid #999; border-radius:18px;"
def __init__(self, message_id: str, text: str, update_signal) -> None:
super().__init__()
@@ -494,7 +511,6 @@ def __init__(self, message_id: str, text: str, update_signal) -> None:
layout = QVBoxLayout()
self.setLayout(layout)
-
self.message = QLabel(html.escape(text, quote=False))
self.message.setWordWrap(True)
self.message.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -508,6 +524,7 @@ def _update_text(self, message_id: str, text: str) -> None:
Conditionally update this SpeechBubble's text if and only if the message_id of the emitted
signal matches the message_id of this speech bubble.
"""
+
if message_id == self.message_id:
self.message.setText(html.escape(text, quote=False))
@@ -529,12 +546,14 @@ def __init__(self,
"""
super().__init__()
layout = QHBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+
label = SpeechBubble(message_id, message, update_signal)
if align != "left":
# Float right...
layout.addStretch(5)
- label.setStyleSheet(label.css + 'border-bottom-right-radius: 0px;')
+ label.setStyleSheet(label.css)
layout.addWidget(label, 6)
@@ -543,10 +562,7 @@ def __init__(self,
layout.addStretch(5)
label.setStyleSheet(label.css + 'border-bottom-left-radius: 0px;')
- layout.setContentsMargins(0, 0, 0, 0)
-
self.setLayout(layout)
- self.setContentsMargins(0, 0, 0, 0)
class MessageWidget(ConversationWidget):
@@ -679,6 +695,7 @@ def __init__(self, source_db_object: Source, sdc_home: str, controller: Client,
self.container = QWidget()
self.conversation_layout = QVBoxLayout()
+ self.conversation_layout.setContentsMargins(0, 0, 0, 0)
self.container.setLayout(self.conversation_layout)
self.container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -694,6 +711,7 @@ def __init__(self, source_db_object: Source, sdc_home: str, controller: Client,
sb.rangeChanged.connect(self.update_conversation_position)
main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(self.scroll)
self.setLayout(main_layout)
self.update_conversation(self.source.collection)
@@ -788,14 +806,15 @@ def __init__(
self.sdc_home = sdc_home
self.layout = QVBoxLayout()
+ self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.conversation = ConversationView(self.source, self.sdc_home, self.controller,
parent=self)
self.source_profile = SourceProfileShortWidget(self.source, self.controller)
- self.layout.addWidget(self.source_profile)
- self.layout.addWidget(self.conversation)
+ self.layout.addWidget(self.source_profile, 1)
+ self.layout.addWidget(self.conversation, 9)
self.controller.authentication_state.connect(self._show_or_hide_replybox)
self._show_or_hide_replybox(is_authenticated)
@@ -816,7 +835,7 @@ def _show_or_hide_replybox(self, show: bool) -> None:
old_widget.widget().deleteLater()
self.reply_box = new_widget
- self.layout.addWidget(new_widget)
+ self.layout.addWidget(new_widget, 3)
class ReplyBoxWidget(QWidget):
@@ -830,12 +849,20 @@ def __init__(self, conversation: SourceConversationWrapper) -> None:
self.text_edit = QTextEdit()
- self.send_button = QPushButton('Send')
+ self.send_button = QPushButton()
self.send_button.clicked.connect(self.send_reply)
+ self.send_button.setMaximumSize(40, 40)
- layout = QHBoxLayout()
+ button_pixmap = load_image('send.png')
+ button_icon = QIcon(button_pixmap)
+ self.send_button.setIcon(button_icon)
+ self.send_button.setIconSize(button_pixmap.rect().size())
+
+ layout = QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.text_edit)
- layout.addWidget(self.send_button)
+
+ layout.addWidget(self.send_button, 0, Qt.AlignRight)
self.setLayout(layout)
def send_reply(self) -> None:
@@ -915,12 +942,19 @@ def __init__(self, source, controller):
class TitleLabel(QLabel):
- """Centered aligned, HTML heading level 3 label."""
+ """The title for a conversation."""
def __init__(self, text):
- html_text = "
%s
" % (text,)
+ html_text = '{}
'.format(text)
+ super().__init__(_(html_text))
+
+
+class LastUpdatedLabel(QLabel):
+ """Time the conversation was last updated."""
+
+ def __init__(self, last_updated):
+ html_text = '{}
'.format(arrow.get(last_updated).humanize())
super().__init__(_(html_text))
- self.setAlignment(Qt.AlignCenter)
class SourceProfileShortWidget(QWidget):
@@ -939,10 +973,10 @@ def __init__(self, source, controller):
self.layout = QHBoxLayout()
self.setLayout(self.layout)
- widgets = (
- TitleLabel(self.source.journalist_designation),
- SourceMenuButton(self.source, self.controller),
- )
+ self.title = TitleLabel(self.source.journalist_designation)
+ self.updated = LastUpdatedLabel(self.source.last_updated)
+ self.menu = SourceMenuButton(self.source, self.controller)
- for widget in widgets:
- self.layout.addWidget(widget)
+ self.layout.addWidget(self.title, 10, Qt.AlignLeft)
+ self.layout.addWidget(self.updated, 1, Qt.AlignRight)
+ self.layout.addWidget(self.menu, 1, Qt.AlignRight)
diff --git a/securedrop_client/resources/images/delete.png b/securedrop_client/resources/images/delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c9656c98a183ad6d284c315642aa9f5a344d413
GIT binary patch
literal 2275
zcmeAS@N?(olHy`uVBq!ia0vp^Nq5QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x2?hoh?#z&gk_cZPtK|G#y~LFKq*T3%+ybC#1_ql7D*
z7iAWdWaj57fXq!y$}cUkRZ;?31P4%e<`%#$$}5KY3g|!mio^naLp=k1Y??|k(-6)>
z(FKyhsV%i46NfgC3`83^Fd%NU0fwlRZ+=Q3NvfTZfuW_YfswAEX^5e*m7$T9
zp{b2Nni_-;!MdG`QWHz^i$e1AbL<>J5vCB3kzbNuoRMFk;OwjbG|^l^!#6QGGY=%F
z2{8^?6Owr#uUYvQWu^kd92By4hBo>bV(984gHnt0b4tPL&5fWMkOk2-IOpdU6r~my
zBU^whhOQoAerR51i5<)eG+}gI;7|Z2%c9I=&%BbORAFK^Y7+nY0
zsz@ZOjFGKEl7MQo(FbK0r0fI9n_y93=CtDirVv<0x8riFnBWR5779IG978;KuZDT&
zgocXjU0xf)lAU2y>96NB}
zhzsN6{z)8}Qa)0_S<4SKoL|zLApCG@LB8_
zz&7X57J=!#N{UO)EJ+mKEZVp7=;X(jH*S#P+yAETAWo$xiyh(Yw3!6A8Je!Ru(Kd{AH$`
zz6|5?_Oi3*WoGXCSQzdo#&YLDjp?l^>+~j>DRNIXUM?51xMqTDMssAA3s^Y6jed52~-CKIN>G`dFf0DWr;(Z@j-~1uF?dtVaLf7}k?kxE^EBDc>
zInvU$OFFluZhP_b?Zk$Ymp?zBXR+_<73Fhsn`TwBt+}*h_i`J{pU-WrBl_lRS6@(l
zD{IMr`1#rFpAUD-&TyVv7j#;lakZ;%+=ram(@)>OHF4iL|Ap*3xW(-be{oJ$j11{L
zbFeME{=d0BU*Gdsxec#gRu2db=`mf){Cdq~IKC>{jIR8evuR+pH@RW9IM}2Kg
z$^s(^&dciZU)R2jyPIesEhgua@+tXow1~m#7P-d1*QE86O#>_?HFo}fH#d9tE-}yc
z`urKHuNBlq!oTnR_)TwT;NRV!uh#bLJ})76>-L>L8Bb=FMK!)aoE-pYE9yoYmhauj2_q?&hC)R&wCYYk8As{MWcQjeS1
z#FGt~oos)ut+nnqnOd_=at_PNr~fw_Hutn&OfxZ!n!VwCqSGs$>6w?VgfN}R5KV0T
zrE+Bb_j?@+4xRCJHEQ>fi@0gfyy21UtVHd2Ub9M*6BQc&{gX`>s;&`n&V6`)k&d5$
zhn9^T%hbFb-zqmgPv=+r6M5nIu~`w1J3aVqvWmBw`5o>RKKxcSxx>$0>_7inTb{+b
SN@=%2Ee}suKbLh*2~7Y(_Lz
+
diff --git a/securedrop_client/resources/images/send.png b/securedrop_client/resources/images/send.png
new file mode 100644
index 0000000000000000000000000000000000000000..1fe8922a1e116ca99d0aea918944560255858694
GIT binary patch
literal 2275
zcmeAS@N?(olHy`uVBq!ia0vp^DnP8r!2~4j*3YW}QjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x2?hoh?#z&gk_cZPtK|G#y~LFKq*T3%+ybC#1_ql7D*
z7iAWdWaj57fXq!y$}cUkRZ;?31P4%e<`%#$$}5KY3g|!mio^naLp=k1Y??|k(-6)>
z(FKyhsV%i46NfgC3`83^Fd%NU0fwlRZ+=Q3NvfTZfuW_YfswAEX^5e*m7$T9
zp{b2Nni_-;!MdG`QWHz^i$e1AbL<>J5vCB3kzbNuoRMFk;OwjbG|^l^!#6QGGY=%F
z2{8^?6Owr#uUYvQWu^kd92By4hBo>bV(984gHnt0b4tPL&5fWMkOk2-IOpdU6r~my
zBU^whhOQoAerR51i5<)eG+}gI;7|Z2%c9I=&%BbORAFK^Y7+nY0
zsz@ZOjFGKEl7MQo(FbK0r0fI9n_y93=CtDirVv<0x8o{1(9Z`f779IG978;K-%j(+
zkq#AUdw%Xl-`hRH_9D|oW;Mi^>t?nF1+vV}b8uOd8mJNwx`c^!>N|l&T>T;ujT0iu
zg1J0g-!`!}akvPsSam>m(Uw(LT1+~2bOiRt_{yXmXT
zvfsVtnKeuAiP({u{$ZDjnVzduFK?Rh!#yEEfkSIaN}cB{+o?=x4|lIQqRn&lf&X8L
zqHop`Zjt;vGaC$}VkZ9op)`4~=DE3^oS92Q0yI+J$j({ZEo^-1++yZ`8~rByb4zhs
zEwD;!WAV&8z7KDmoSL*~O1EcLr1+FY%Wi3{ak%?>&vE#$yBb63r`jh&1?QueNyxG^Mf`k`!w80(Ee9o{!PR;=P#
zqb%Hau1Kxt?1_MtD|Y?LoqcR~QJ|)2@D{15MW>YIdCKyOq7NTpVs_)=w6qcQjniHC
zFDyhQD_gnca&Tx-z&{-A=z>c4GV23G@5A>}7k8>5I$E-G1~?Wuw@=
z(wyeAys9Qo*NShfIUliV)r+6ple>>D>G?8&D^M_`<+kAqj<@gSZQqptjJbaPNPUH&
zQ|O_c9T#`D1+Lu~Ctva5#<3_Nv53TDReHMv_E_w!_*>Fd+~780_CDQn4=&HsWw&|D
zxcviD&1$F~~cl5b81e?M$E7RujN={xO2h}9pro`N&ql>a?c
zufAP%LC@#e_dnau-|P5)$a+^!=+rirgKIyYf4^3%Tzj3FQSmh4OOwue=IHHyr#918
zbicvQDe-NGa<<-YX>5p||M`ubT}-&c;T2oI`1TkEl$aWXiFE$_`uFMl`>`%puPP?C
zdRVi|^6!yavDRVr#oMhZwb7E<&vo{B8q2F6%0ISc(nF3@TaBW33i}?|c%5xm`-KS8
z%~L~nn72(8=UUD6ONu{0^scV>ze^4oyBB_ZH}QEbPw=LQb;dbd%*|rLMp~@C+g{J*
z^{hU|Lxvj&v?-I``5jR
RlU9RT9-gj#F6*2UngFRTk+}c>
literal 0
HcmV?d00001
diff --git a/securedrop_client/resources/images/trash.png b/securedrop_client/resources/images/trash.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bd54f6e227d43911b7cb8152e96f490c425e432
GIT binary patch
literal 1846
zcmeAS@N?(olHy`uVBq!ia0vp^YCx>R!2~4Rj>OIfQjEnx?oJHr&dIz4aySb-B8wRq
zxP?KOkzv*x2?hoh?#z&gk_cZPtK|G#y~LFKq*T3%+ybC#1_ql7D*
z7iAWdWaj57fXq!y$}cUkRZ;?31P4%e<`%#$$}5KY3g|!mio^naLp=k1Y??|k(-6)>
z(FKyhsV%i46NfgC3`83^Fd%NU0fwlRZ+=Q3NvfTZfuW_YfswAEX^5e*m7$T9
zp{b2Nni_-;!MdG`QWHz^i$e1AbL<>J5vCB3kzbNuoRMFk;OwjbG|^l^!#6QGGY=%F
z2{8^?6Owr#uUYvQWu^kd92By4hBo>bV(984gHnt0b4tPL&5fWMkOk2-IOpdU6r~my
zBU^whhOQoAerR51i5<)eG+}gI;7|Z2%c9I=&%Bb1=CD1iG=jY@X1s5bHr`nmBni>ORAFK^Y7+nY0
zsz`)YCML*MAxS_r+USF_3sUxh@ai}*FfbkQba4!c;Jg~<
zn{mZKWS-}?yS|r>Ud{{D78PXSS88If=Uen*=?aA~f7dSx8X6q)zNl0SAL)Lxw<3$5
zRq&!#hlZHo$3^blN?UG;d0&5f&G*!~ADfgj%Zw)9^qkyuSm@sGPxpR5yM1Q1WQ^QA
z7fBwUj==TY+|o`Ze-~WLIMJ2jd^1bY$!yA{n7C;l8RzZudb888Mlv8Mf~EX
zmE0bK?QLGm6P(ii?|lBSCM{l0YE8_I(qAXGbxyy(IN#F6S#`Q(+1_RUIY00HH)jg-
z>2Ei77QRdm7UN|~dlvq!--a_L%_H=jzNXbIZIzOITLB%7m0n>@nWA;}Vhx&K{dv4v
zr#JJQVkT%kureyr7ag
zo{NiH=GbMgUC-vl>R|Tu>!D)rlWC&cr`0Ae_?yD<{mQZ@pM-b(=U=)%Yf@i}*3{<(
zXKIQ#_&A<`bMD!T-Fh*iR}d-kw?DbK_X*#a>+*L-R75H)YBDe{U&x_Dnl+iIeJ*uk-6azB%kP-Mshc
ztm>ENLU`mwLRL@lop$)Icfi_XjZWcfgJy`aY96~%xTr$JvscJV_(kBmGo3=q_C9ZZK^p=~{vN8=&G_BAs~r*NvSP};U7
ianqtN{H8j3|HLEGwoH+f)w&0&%sgHFT-G@yGywpoqqf=r
literal 0
HcmV?d00001
diff --git a/tests/gui/test_main.py b/tests/gui/test_main.py
index 48198a67df..3d756c8514 100644
--- a/tests/gui/test_main.py
+++ b/tests/gui/test_main.py
@@ -1,7 +1,7 @@
"""
Check the core Window UI class works as expected.
"""
-from PyQt5.QtWidgets import QApplication, QVBoxLayout
+from PyQt5.QtWidgets import QApplication, QHBoxLayout
from securedrop_client.gui.main import Window
from securedrop_client.resources import load_icon
from securedrop_client.db import Message
@@ -16,13 +16,13 @@ def test_init(mocker):
Ensure the Window instance is setup in the expected manner.
"""
mock_li = mocker.MagicMock(return_value=load_icon('icon.png'))
- mock_lo = mocker.MagicMock(return_value=QVBoxLayout())
+ mock_lo = mocker.MagicMock(return_value=QHBoxLayout())
mock_lo().addWidget = mocker.MagicMock()
mocker.patch('securedrop_client.gui.main.load_icon', mock_li)
mock_tb = mocker.patch('securedrop_client.gui.main.ToolBar')
mock_mv = mocker.patch('securedrop_client.gui.main.MainView')
- mocker.patch('securedrop_client.gui.main.QVBoxLayout', mock_lo)
+ mocker.patch('securedrop_client.gui.main.QHBoxLayout', mock_lo)
mocker.patch('securedrop_client.gui.main.QMainWindow')
w = Window('mock')
@@ -126,13 +126,13 @@ def test_update_error_status(mocker):
def test_show_sync(mocker):
"""
- If there's a value display the result of its "humanize" method.
+ If there's a value display the result of its "humanize" method.humanize
"""
w = Window('mock')
- w.main_view = mocker.MagicMock()
+ w.set_status = mocker.MagicMock()
updated_on = mocker.MagicMock()
w.show_sync(updated_on)
- w.main_view.status.setText.assert_called_once_with('Last refresh: ' + updated_on.humanize())
+ w.set_status.assert_called_once_with('Last refresh: ' + updated_on.humanize())
def test_show_sync_no_sync(mocker):
@@ -140,9 +140,9 @@ def test_show_sync_no_sync(mocker):
If there's no value to display, default to a "waiting" message.
"""
w = Window('mock')
- w.main_view = mocker.MagicMock()
+ w.set_status = mocker.MagicMock()
w.show_sync(None)
- w.main_view.status.setText.assert_called_once_with('Waiting to refresh...')
+ w.set_status.assert_called_once_with('Waiting to refresh...', 5000)
def test_set_logged_in_as(mocker):
@@ -255,7 +255,7 @@ def test_conversation_pending_message(mocker):
mock_source.collection = [message]
mocked_add_message = mocker.patch('securedrop_client.gui.widgets.ConversationView.add_message')
- mocker.patch('securedrop_client.gui.main.QVBoxLayout')
+ mocker.patch('securedrop_client.gui.main.QHBoxLayout')
mocker.patch('securedrop_client.gui.main.QWidget')
w.show_conversation_for(mock_source, True)
diff --git a/tests/gui/test_widgets.py b/tests/gui/test_widgets.py
index 69a4926842..2e3800d421 100644
--- a/tests/gui/test_widgets.py
+++ b/tests/gui/test_widgets.py
@@ -2,7 +2,7 @@
Make sure the UI widgets are configured correctly and work as expected.
"""
from PyQt5.QtWidgets import QWidget, QApplication, QWidgetItem, QSpacerItem, QVBoxLayout, \
- QMessageBox
+ QMessageBox, QLabel
from tests import factory
from securedrop_client import db
from securedrop_client import logic
@@ -52,7 +52,7 @@ def test_ToolBar_set_logged_in_as(mocker):
tb.set_logged_in_as('test')
- tb.user_state.setText.assert_called_once_with('Signed in as: test')
+ tb.user_state.setText.assert_called_once_with('test')
tb.login.setVisible.assert_called_once_with(False)
tb.logout.setVisible.assert_called_once_with(True)
tb.refresh.setVisible.assert_called_once_with(True)
@@ -1068,6 +1068,7 @@ def test_SourceConversationWrapper_send_reply(mocker):
mock_uuid = '456xyz'
mocker.patch('securedrop_client.gui.widgets.uuid4', return_value=mock_uuid)
mock_controller = mocker.MagicMock()
+ mocker.patch('securedrop_client.gui.widgets.LastUpdatedLabel', return_value=QLabel('now'))
cw = SourceConversationWrapper(mock_source, 'mock home', mock_controller, True)
mock_add_reply = mocker.Mock()
@@ -1211,6 +1212,8 @@ def test_SourceConversationWrapper_auth_signals(mocker, homedir):
mock_is_auth = mocker.MagicMock()
mock_sh = mocker.patch.object(SourceConversationWrapper, '_show_or_hide_replybox')
+ mocker.patch('securedrop_client.gui.widgets.LastUpdatedLabel', return_value=QLabel('now'))
+
SourceConversationWrapper(mock_source, 'mock home', mock_controller, mock_is_auth)
mock_connect.assert_called_once_with(mock_sh)
@@ -1224,9 +1227,9 @@ def test_SourceConversationWrapper_set_widgets_via_auth_value(mocker, homedir):
mock_source = mocker.Mock(collection=[])
mock_controller = mocker.MagicMock()
+ mocker.patch('securedrop_client.gui.widgets.LastUpdatedLabel', return_value=QLabel('now'))
cw = SourceConversationWrapper(mock_source, 'mock home', mock_controller, True)
mocker.patch.object(cw, 'layout')
-
mock_reply_box = mocker.patch('securedrop_client.gui.widgets.ReplyBoxWidget',
return_value=QWidget())
mock_label = mocker.patch('securedrop_client.gui.widgets.QLabel', return_value=QWidget())