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
|
# Python Modules
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
@ -38,7 +39,7 @@ import sys
|
|||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||||
_ = glocale.translation.gettext
|
_ = glocale.translation.gettext
|
||||||
from gramps.gen.user import User
|
from gramps.gen import user
|
||||||
|
|
||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -52,16 +53,22 @@ _SPINNER = ['|', '/', '-', '\\']
|
|||||||
# User class
|
# User class
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class User(User):
|
class User(user.User):
|
||||||
"""
|
"""
|
||||||
This class provides a means to interact with the user via CLI.
|
This class provides a means to interact with the user via CLI.
|
||||||
It implements the interface in gramps.gen.user.User()
|
It implements the interface in gramps.gen.user.User()
|
||||||
"""
|
"""
|
||||||
def __init__(self, callback=None, error=None):
|
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.steps = 0;
|
||||||
self.current_step = 0;
|
self.current_step = 0;
|
||||||
self.callback_function = callback
|
self._input = raw_input if sys.version_info[0] < 3 else input
|
||||||
self.error_function = error
|
|
||||||
|
|
||||||
def begin_progress(self, title, message, steps):
|
def begin_progress(self, title, message, steps):
|
||||||
"""
|
"""
|
||||||
@ -77,13 +84,13 @@ class User(User):
|
|||||||
@type steps: int
|
@type steps: int
|
||||||
@returns: none
|
@returns: none
|
||||||
"""
|
"""
|
||||||
sys.stderr.write(message)
|
self._fileout.write(message)
|
||||||
self.steps = steps
|
self.steps = steps
|
||||||
self.current_step = 0;
|
self.current_step = 0;
|
||||||
if self.steps == 0:
|
if self.steps == 0:
|
||||||
sys.stderr.write(_SPINNER[self.current_step])
|
self._fileout.write(_SPINNER[self.current_step])
|
||||||
else:
|
else:
|
||||||
sys.stderr.write("00%")
|
self._fileout.write("00%")
|
||||||
|
|
||||||
def step_progress(self):
|
def step_progress(self):
|
||||||
"""
|
"""
|
||||||
@ -92,45 +99,41 @@ class User(User):
|
|||||||
self.current_step += 1
|
self.current_step += 1
|
||||||
if self.steps == 0:
|
if self.steps == 0:
|
||||||
self.current_step %= 4
|
self.current_step %= 4
|
||||||
sys.stderr.write("\r %s " % _SPINNER[self.current_step])
|
self._fileout.write("\r %s " % _SPINNER[self.current_step])
|
||||||
else:
|
else:
|
||||||
percent = int((float(self.current_step) / self.steps) * 100)
|
percent = int((float(self.current_step) / self.steps) * 100)
|
||||||
sys.stderr.write("\r%02d%%" % percent)
|
self._fileout.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))
|
|
||||||
|
|
||||||
def end_progress(self):
|
def end_progress(self):
|
||||||
"""
|
"""
|
||||||
Stop showing the progress indicator to the user.
|
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".
|
Prompt the user with a message to select an alternative.
|
||||||
The user will be forced to answer the question before proceeding.
|
|
||||||
|
|
||||||
@param title: the title of the question
|
@param title: the title of the question, e.g.: "Undo history warning"
|
||||||
@type title: str
|
@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
|
@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
|
@returns: the user's answer to the question
|
||||||
@rtype: bool
|
@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=""):
|
def warn(self, title, warning=""):
|
||||||
"""
|
"""
|
||||||
@ -142,7 +145,7 @@ class User(User):
|
|||||||
@type warning: str
|
@type warning: str
|
||||||
@returns: none
|
@returns: none
|
||||||
"""
|
"""
|
||||||
sys.stderr.write("%s %s" % (title, warning))
|
self._fileout.write("%s %s" % (title, warning))
|
||||||
|
|
||||||
def notify_error(self, title, error=""):
|
def notify_error(self, title, error=""):
|
||||||
"""
|
"""
|
||||||
@ -157,7 +160,7 @@ class User(User):
|
|||||||
if self.error_function:
|
if self.error_function:
|
||||||
self.error_function(title, error)
|
self.error_function(title, error)
|
||||||
else:
|
else:
|
||||||
sys.stderr.write("%s %s" % (title, error))
|
self._fileout.write("%s %s" % (title, error))
|
||||||
|
|
||||||
def notify_db_error(self, error):
|
def notify_db_error(self, error):
|
||||||
"""
|
"""
|
||||||
@ -178,5 +181,5 @@ class User(User):
|
|||||||
"""
|
"""
|
||||||
Displays information to the CLI
|
Displays information to the CLI
|
||||||
"""
|
"""
|
||||||
sys.stderr.write(msg1)
|
self._fileout.write(msg1)
|
||||||
sys.stderr.write(infotext)
|
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$
|
# $Id$
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The User class provides basic interaction with the user.
|
The User class provides basic interaction with the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# User class
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
class User():
|
class User():
|
||||||
"""
|
"""
|
||||||
This class provides a means to interact with the user in an abstract way.
|
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
|
This class should be overridden by each respective user interface to
|
||||||
provide the appropriate interaction (eg. dialogs for GTK, prompts for CLI).
|
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):
|
def begin_progress(self, title, message, steps):
|
||||||
"""
|
"""
|
||||||
Start showing a progress indicator to the user.
|
Start showing a progress indicator to the user.
|
||||||
@ -61,23 +63,37 @@ class User():
|
|||||||
"""
|
"""
|
||||||
Display the precentage.
|
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):
|
def end_progress(self):
|
||||||
"""
|
"""
|
||||||
Stop showing the progress indicator to the user.
|
Stop showing the progress indicator to the user.
|
||||||
"""
|
"""
|
||||||
pass
|
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".
|
Prompt the user with a message to select an alternative.
|
||||||
The user will be forced to answer the question before proceeding.
|
|
||||||
|
|
||||||
@param title: the title of the question
|
@param title: the title of the question, e.g.: "Undo history warning"
|
||||||
@type title: str
|
@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
|
@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
|
@returns: the user's answer to the question
|
||||||
@rtype: bool
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
|
@ -36,24 +36,23 @@ import sys
|
|||||||
# Gramps modules
|
# Gramps modules
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from gramps.gen.user import User
|
from gramps.gen import user
|
||||||
from .utils import ProgressMeter
|
from .utils import ProgressMeter
|
||||||
from .dialog import (WarningDialog, ErrorDialog, DBErrorDialog,
|
from .dialog import (WarningDialog, ErrorDialog, DBErrorDialog,
|
||||||
InfoDialog)
|
InfoDialog, QuestionDialog2)
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# User class
|
# User class
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class User(User):
|
class User(user.User):
|
||||||
"""
|
"""
|
||||||
This class provides a means to interact with the user via GTK.
|
This class provides a means to interact with the user via GTK.
|
||||||
It implements the interface in gramps.gen.user.User()
|
It implements the interface in gramps.gen.user.User()
|
||||||
"""
|
"""
|
||||||
def __init__(self, callback=None, error=None):
|
def __init__(self, callback=None, error=None):
|
||||||
|
user.User.__init__(self, callback, error)
|
||||||
self.progress = None
|
self.progress = None
|
||||||
self.callback_function = callback
|
|
||||||
self.error_function = error
|
|
||||||
|
|
||||||
def begin_progress(self, title, message, steps):
|
def begin_progress(self, title, message, steps):
|
||||||
"""
|
"""
|
||||||
@ -82,21 +81,6 @@ class User(User):
|
|||||||
if self.progress:
|
if self.progress:
|
||||||
self.progress.step()
|
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):
|
def end_progress(self):
|
||||||
"""
|
"""
|
||||||
Stop showing the progress indicator to the user.
|
Stop showing the progress indicator to the user.
|
||||||
@ -105,19 +89,25 @@ class User(User):
|
|||||||
self.progress.close()
|
self.progress.close()
|
||||||
self.progress = None
|
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".
|
Prompt the user with a message to select an alternative.
|
||||||
The user will be forced to answer the question before proceeding.
|
|
||||||
|
|
||||||
@param title: the title of the question
|
@param title: the title of the question, e.g.: "Undo history warning"
|
||||||
@type title: str
|
@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
|
@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
|
@returns: the user's answer to the question
|
||||||
@rtype: bool
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
return False
|
dialog = QuestionDialog2(title, message, accept_label, reject_label)
|
||||||
|
return dialog.run()
|
||||||
|
|
||||||
def warn(self, title, warning=""):
|
def warn(self, title, warning=""):
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user