7013: Impl. User.prompt based on QuestionDialog2
Reapply r22914 from trunk Unit tests pass svn: r22931
This commit is contained in:
parent
3bec0ee5aa
commit
4bfb2e082c
109
gramps/cli/test/user_test.py
Normal file
109
gramps/cli/test/user_test.py
Normal file
@ -0,0 +1,109 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2013 Vassilii Khachaturov <vassilii@tarunz.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
# $Id$
|
||||
|
||||
""" Unittest for user.py """
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
from .. import user
|
||||
from ...gen.test.user_test import TestUser
|
||||
import sys
|
||||
|
||||
try:
|
||||
if sys.version_info < (3,3):
|
||||
from mock import Mock
|
||||
else:
|
||||
from unittest.mock import Mock
|
||||
|
||||
MOCKING = True
|
||||
|
||||
except:
|
||||
MOCKING = False
|
||||
print ("Mocking disabled", sys.exc_info()[0:2])
|
||||
|
||||
class TestUser_prompt(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.real_user = user.User()
|
||||
if MOCKING:
|
||||
self.user = user.User()
|
||||
self.user._fileout = Mock()
|
||||
self.user._input = Mock()
|
||||
|
||||
def test_default_fileout_has_write(self):
|
||||
assert hasattr(self.real_user._fileout, 'write')
|
||||
|
||||
def test_default_input(self):
|
||||
assert self.real_user._input.__name__.endswith('input')
|
||||
|
||||
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
|
||||
def test_prompt_returns_True_if_ACCEPT_entered(self):
|
||||
self.user._input.configure_mock(return_value = TestUser.ACCEPT)
|
||||
assert self.user.prompt(
|
||||
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT
|
||||
), "True expected!"
|
||||
self.user._input.assert_called_once_with()
|
||||
|
||||
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
|
||||
def test_prompt_returns_False_if_REJECT_entered(self):
|
||||
self.user._input.configure_mock(return_value = TestUser.REJECT)
|
||||
assert not self.user.prompt(
|
||||
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT
|
||||
), "False expected!"
|
||||
self.user._input.assert_called_once_with()
|
||||
|
||||
def assert_prompt_contains_text(self, text):
|
||||
self.user._input.configure_mock(return_value = TestUser.REJECT)
|
||||
self.user.prompt(TestUser.TITLE, TestUser.MSG,
|
||||
TestUser.ACCEPT, TestUser.REJECT)
|
||||
for call in self.user._fileout.method_calls:
|
||||
name, args, kwargs = call
|
||||
for a in args:
|
||||
if a.find(text) >= 0:
|
||||
return
|
||||
assert False,"'{}' never printed in prompt".format(text)
|
||||
|
||||
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
|
||||
def test_prompt_contains_title_text(self):
|
||||
self.assert_prompt_contains_text(TestUser.TITLE)
|
||||
|
||||
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
|
||||
def test_prompt_contains_msg_text(self):
|
||||
self.assert_prompt_contains_text(TestUser.MSG)
|
||||
|
||||
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
|
||||
def test_prompt_contains_accept_text(self):
|
||||
self.assert_prompt_contains_text(TestUser.ACCEPT)
|
||||
|
||||
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
|
||||
def test_prompt_contains_reject_text(self):
|
||||
self.assert_prompt_contains_text(TestUser.REJECT)
|
||||
|
||||
if not MOCKING: #don't use SKIP, to avoid counting a skipped test
|
||||
def testManualRun(self):
|
||||
b = self.real_user.prompt(
|
||||
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT)
|
||||
print ("Returned: {}".format(b))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -29,6 +29,7 @@ The User class provides basic interaction with the user.
|
||||
# Python Modules
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
@ -38,7 +39,7 @@ import sys
|
||||
#------------------------------------------------------------------------
|
||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
from gramps.gen.user import User
|
||||
from gramps.gen import user
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
@ -52,16 +53,22 @@ _SPINNER = ['|', '/', '-', '\\']
|
||||
# User class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class User(User):
|
||||
class User(user.User):
|
||||
"""
|
||||
This class provides a means to interact with the user via CLI.
|
||||
It implements the interface in gramps.gen.user.User()
|
||||
"""
|
||||
def __init__(self, callback=None, error=None):
|
||||
"""
|
||||
Init.
|
||||
|
||||
@param error: If given, notify_error delegates to this callback
|
||||
@type error: function(title, error)
|
||||
"""
|
||||
user.User.__init__(self, callback, error)
|
||||
self.steps = 0;
|
||||
self.current_step = 0;
|
||||
self.callback_function = callback
|
||||
self.error_function = error
|
||||
self._input = raw_input if sys.version_info[0] < 3 else input
|
||||
|
||||
def begin_progress(self, title, message, steps):
|
||||
"""
|
||||
@ -77,13 +84,13 @@ class User(User):
|
||||
@type steps: int
|
||||
@returns: none
|
||||
"""
|
||||
sys.stderr.write(message)
|
||||
self._fileout.write(message)
|
||||
self.steps = steps
|
||||
self.current_step = 0;
|
||||
if self.steps == 0:
|
||||
sys.stderr.write(_SPINNER[self.current_step])
|
||||
self._fileout.write(_SPINNER[self.current_step])
|
||||
else:
|
||||
sys.stderr.write("00%")
|
||||
self._fileout.write("00%")
|
||||
|
||||
def step_progress(self):
|
||||
"""
|
||||
@ -92,45 +99,41 @@ class User(User):
|
||||
self.current_step += 1
|
||||
if self.steps == 0:
|
||||
self.current_step %= 4
|
||||
sys.stderr.write("\r %s " % _SPINNER[self.current_step])
|
||||
self._fileout.write("\r %s " % _SPINNER[self.current_step])
|
||||
else:
|
||||
percent = int((float(self.current_step) / self.steps) * 100)
|
||||
sys.stderr.write("\r%02d%%" % percent)
|
||||
|
||||
def callback(self, percentage, text=None):
|
||||
"""
|
||||
Display the precentage.
|
||||
"""
|
||||
if self.callback_function:
|
||||
if text:
|
||||
self.callback_function(percentage, text)
|
||||
else:
|
||||
self.callback_function(percentage)
|
||||
else:
|
||||
if text is None:
|
||||
sys.stderr.write("\r%02d%%" % percentage)
|
||||
else:
|
||||
sys.stderr.write("\r%02d%% %s" % (percentage, text))
|
||||
self._fileout.write("\r%02d%%" % percent)
|
||||
|
||||
def end_progress(self):
|
||||
"""
|
||||
Stop showing the progress indicator to the user.
|
||||
"""
|
||||
sys.stderr.write("\r100%\n")
|
||||
self._fileout.write("\r100%\n")
|
||||
|
||||
def prompt(self, title, question):
|
||||
def prompt(self, title, message, accept_label, reject_label):
|
||||
"""
|
||||
Ask the user a question. The answer must be "yes" or "no".
|
||||
The user will be forced to answer the question before proceeding.
|
||||
Prompt the user with a message to select an alternative.
|
||||
|
||||
@param title: the title of the question
|
||||
@param title: the title of the question, e.g.: "Undo history warning"
|
||||
@type title: str
|
||||
@param question: the question
|
||||
@param message: the message, e.g.: "Proceeding with the tool will
|
||||
erase the undo history. If you think you may want to revert
|
||||
running this tool, please stop here and make a backup of the DB."
|
||||
@type question: str
|
||||
@param accept_label: what to call the positive choice, e.g.: "Proceed"
|
||||
@type accept_label: str
|
||||
@param reject_label: what to call the negative choice, e.g.: "Stop"
|
||||
@type reject_label: str
|
||||
@returns: the user's answer to the question
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
text = "{t} {m} ({y}/{n}): ".format(
|
||||
t = title,
|
||||
m = message,
|
||||
y = accept_label,
|
||||
n = reject_label)
|
||||
print (text, file = self._fileout) # TODO python3 add flush=True
|
||||
return self._input() == accept_label
|
||||
|
||||
def warn(self, title, warning=""):
|
||||
"""
|
||||
@ -142,7 +145,7 @@ class User(User):
|
||||
@type warning: str
|
||||
@returns: none
|
||||
"""
|
||||
sys.stderr.write("%s %s" % (title, warning))
|
||||
self._fileout.write("%s %s" % (title, warning))
|
||||
|
||||
def notify_error(self, title, error=""):
|
||||
"""
|
||||
@ -157,7 +160,7 @@ class User(User):
|
||||
if self.error_function:
|
||||
self.error_function(title, error)
|
||||
else:
|
||||
sys.stderr.write("%s %s" % (title, error))
|
||||
self._fileout.write("%s %s" % (title, error))
|
||||
|
||||
def notify_db_error(self, error):
|
||||
"""
|
||||
@ -178,5 +181,5 @@ class User(User):
|
||||
"""
|
||||
Displays information to the CLI
|
||||
"""
|
||||
sys.stderr.write(msg1)
|
||||
sys.stderr.write(infotext)
|
||||
self._fileout.write(msg1)
|
||||
self._fileout.write(infotext)
|
||||
|
46
gramps/gen/test/user_test.py
Normal file
46
gramps/gen/test/user_test.py
Normal file
@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2013 Vassilii Khachaturov <vassilii@tarunz.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
# $Id$
|
||||
|
||||
""" Unittest for user.py """
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
from .. import user
|
||||
|
||||
class TestUser(object):
|
||||
TITLE = "Testing prompt"
|
||||
MSG = "Choices are hard. Nevertheless, please choose!"
|
||||
ACCEPT = "To be"
|
||||
REJECT = "Not to be"
|
||||
|
||||
class TestUser_prompt(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.user = user.User()
|
||||
|
||||
def test_returns_False(self):
|
||||
assert not self.user.prompt(
|
||||
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -20,21 +20,23 @@
|
||||
# $Id$
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
"""
|
||||
The User class provides basic interaction with the user.
|
||||
"""
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# User class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class User():
|
||||
"""
|
||||
This class provides a means to interact with the user in an abstract way.
|
||||
This class should be overridden by each respective user interface to
|
||||
provide the appropriate interaction (eg. dialogs for GTK, prompts for CLI).
|
||||
"""
|
||||
|
||||
def __init__(self, callback=None, error=None):
|
||||
self.callback_function = callback
|
||||
self.error_function = error
|
||||
self._fileout = sys.stderr # redirected to mocks by unit tests
|
||||
|
||||
def begin_progress(self, title, message, steps):
|
||||
"""
|
||||
Start showing a progress indicator to the user.
|
||||
@ -61,23 +63,37 @@ class User():
|
||||
"""
|
||||
Display the precentage.
|
||||
"""
|
||||
pass
|
||||
|
||||
if self.callback_function:
|
||||
if text:
|
||||
self.callback_function(percentage, text)
|
||||
else:
|
||||
self.callback_function(percentage)
|
||||
else:
|
||||
if text is None:
|
||||
self._fileout.write("\r%02d%%" % percentage)
|
||||
else:
|
||||
self._fileout.write("\r%02d%% %s" % (percentage, text))
|
||||
|
||||
def end_progress(self):
|
||||
"""
|
||||
Stop showing the progress indicator to the user.
|
||||
"""
|
||||
pass
|
||||
|
||||
def prompt(self, title, question):
|
||||
def prompt(self, title, message, accept_label, reject_label):
|
||||
"""
|
||||
Ask the user a question. The answer must be "yes" or "no".
|
||||
The user will be forced to answer the question before proceeding.
|
||||
Prompt the user with a message to select an alternative.
|
||||
|
||||
@param title: the title of the question
|
||||
@param title: the title of the question, e.g.: "Undo history warning"
|
||||
@type title: str
|
||||
@param question: the question
|
||||
@param message: the message, e.g.: "Proceeding with the tool will
|
||||
erase the undo history. If you think you may want to revert
|
||||
running this tool, please stop here and make a backup of the DB."
|
||||
@type question: str
|
||||
@param accept_label: what to call the positive choice, e.g.: "Proceed"
|
||||
@type accept_label: str
|
||||
@param reject_label: what to call the negative choice, e.g.: "Stop"
|
||||
@type reject_label: str
|
||||
@returns: the user's answer to the question
|
||||
@rtype: bool
|
||||
"""
|
||||
|
@ -36,24 +36,23 @@ import sys
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gramps.gen.user import User
|
||||
from gramps.gen import user
|
||||
from .utils import ProgressMeter
|
||||
from .dialog import (WarningDialog, ErrorDialog, DBErrorDialog,
|
||||
InfoDialog)
|
||||
InfoDialog, QuestionDialog2)
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# User class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class User(User):
|
||||
class User(user.User):
|
||||
"""
|
||||
This class provides a means to interact with the user via GTK.
|
||||
It implements the interface in gramps.gen.user.User()
|
||||
"""
|
||||
def __init__(self, callback=None, error=None):
|
||||
user.User.__init__(self, callback, error)
|
||||
self.progress = None
|
||||
self.callback_function = callback
|
||||
self.error_function = error
|
||||
|
||||
def begin_progress(self, title, message, steps):
|
||||
"""
|
||||
@ -82,21 +81,6 @@ class User(User):
|
||||
if self.progress:
|
||||
self.progress.step()
|
||||
|
||||
def callback(self, percentage, text=None):
|
||||
"""
|
||||
Display the precentage.
|
||||
"""
|
||||
if self.callback_function:
|
||||
if text:
|
||||
self.callback_function(percentage, text)
|
||||
else:
|
||||
self.callback_function(percentage)
|
||||
else:
|
||||
if text is None:
|
||||
sys.stdout.write("\r%02d%%" % percentage)
|
||||
else:
|
||||
sys.stdout.write("\r%02d%% %s" % (percentage, text))
|
||||
|
||||
def end_progress(self):
|
||||
"""
|
||||
Stop showing the progress indicator to the user.
|
||||
@ -105,19 +89,25 @@ class User(User):
|
||||
self.progress.close()
|
||||
self.progress = None
|
||||
|
||||
def prompt(self, title, question):
|
||||
def prompt(self, title, message, accept_label, reject_label):
|
||||
"""
|
||||
Ask the user a question. The answer must be "yes" or "no".
|
||||
The user will be forced to answer the question before proceeding.
|
||||
Prompt the user with a message to select an alternative.
|
||||
|
||||
@param title: the title of the question
|
||||
@param title: the title of the question, e.g.: "Undo history warning"
|
||||
@type title: str
|
||||
@param question: the question
|
||||
@param message: the message, e.g.: "Proceeding with the tool will
|
||||
erase the undo history. If you think you may want to revert
|
||||
running this tool, please stop here and make a backup of the DB."
|
||||
@type question: str
|
||||
@param accept_label: what to call the positive choice, e.g.: "Proceed"
|
||||
@type accept_label: str
|
||||
@param reject_label: what to call the negative choice, e.g.: "Stop"
|
||||
@type reject_label: str
|
||||
@returns: the user's answer to the question
|
||||
@rtype: bool
|
||||
"""
|
||||
return False
|
||||
dialog = QuestionDialog2(title, message, accept_label, reject_label)
|
||||
return dialog.run()
|
||||
|
||||
def warn(self, title, warning=""):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user