# ============================================================================
#
# Copyright (C) 2007-2016 Conceptive Engineering bvba.
# www.conceptive.be / info@conceptive.be
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Conceptive Engineering nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ============================================================================
"""
Camelot unittest helpers. This module contains helper classes and functions
to write unittests for Camelot applications. These are not the unittests for
Camelot itself.
"""
import logging
import unittest
import six
from ..admin.action.application_action import ApplicationActionGuiContext
from ..admin.entity_admin import EntityAdmin
from ..core.orm import Session
from ..core.qt import Qt, QtCore, QtGui, QtWidgets
from ..view import action_steps
has_programming_error = False
_application_ = []
LOGGER = logging.getLogger('camelot.test')
[docs]def get_application():
"""Get the singleton QApplication"""
if not len(_application_):
#
# Uniform style for screenshot generation
#
application = QtWidgets.QApplication.instance()
if not application:
import sys
from camelot.view import art
QtWidgets.QApplication.setStyle('cleanlooks')
application = QtWidgets.QApplication(sys.argv)
application.setStyleSheet( art.read('stylesheet/office2007_blue.qss').decode('utf-8') )
QtCore.QLocale.setDefault( QtCore.QLocale('nl_BE') )
#try:
# from PyTitan import QtnOfficeStyle
# QtnOfficeStyle.setApplicationStyle( QtnOfficeStyle.Windows7Scenic )
#except:
# pass
_application_.append( application )
return _application_[0]
[docs]class ModelThreadTestCase(unittest.TestCase):
"""Base class for implementing test cases that need a running model_thread.
"""
images_path = ''
[docs] def process(self):
"""Wait until all events are processed and the queues of the model thread are empty"""
self.mt.wait_on_work()
def setUp(self):
from camelot.core.conf import settings
self.app = get_application()
from camelot.view import model_thread
from camelot.view.model_thread.no_thread_model_thread import NoThreadModelThread
from camelot.view.model_thread import get_model_thread, has_model_thread
from camelot.view.remote_signals import construct_signal_handler, has_signal_handler
if not has_model_thread():
#
# Run the tests without real threading, to avoid timing problems with screenshots etc.
#
model_thread._model_thread_.insert( 0, NoThreadModelThread() )
if not has_signal_handler():
construct_signal_handler()
self.mt = get_model_thread()
if not self.mt.isRunning():
self.mt.start()
# make sure the startup sequence has passed
self.mt.post( settings.setup_model )
self.process()
def tearDown(self):
self.process()
#self.mt.exit(0)
#self.mt.wait()
[docs]class ApplicationViewsTest(ModelThreadTestCase):
"""Test various application level views, like the main window, the
sidepanel"""
def setUp(self):
super(ApplicationViewsTest, self).setUp()
self.gui_context = ApplicationActionGuiContext()
[docs] def get_application_admin(self):
"""Overwrite this method to make use of a custom application admin"""
from camelot.admin.application_admin import ApplicationAdmin
return ApplicationAdmin()
def install_translators(self, app_admin):
for translator in app_admin.get_translator():
QtCore.QCoreApplication.installTranslator(translator)
def test_navigation_pane(self):
from camelot.view.controls.section_widget import NavigationPane
app_admin = self.get_application_admin()
self.install_translators(app_admin)
nav_pane = NavigationPane(None, None)
nav_pane.set_sections(app_admin.get_sections())
self.grab_widget(nav_pane, subdir='applicationviews')
def test_main_window(self):
app_admin = self.get_application_admin()
self.gui_context.admin = app_admin
self.install_translators(app_admin)
step = action_steps.MainWindow(app_admin)
widget = step.render(self.gui_context)
self.grab_widget(widget, subdir='applicationviews')
[docs]class EntityViewsTest(ModelThreadTestCase):
"""Test the views of all the Entity subclasses, subclass this class to test all views
in your application. This is done by calling the create_table_view and create_new_view
on a set of admin objects. To tell the test case which admin objects should be tested,
overwrite the get_admins method.
"""
def setUp(self):
super(EntityViewsTest, self).setUp()
global has_programming_error
translators = self.get_application_admin().get_translator()
for translator in translators:
QtCore.QCoreApplication.installTranslator(translator)
has_programming_error = False
self.session = Session()
[docs] def get_application_admin(self):
"""Overwrite this method to make use of a custom application admin"""
from camelot.admin.application_admin import ApplicationAdmin
return ApplicationAdmin()
[docs] def get_admins(self):
"""Should return all admin for which a table and a form view should be displayed,
by default, returns for all entities their default admin"""
from sqlalchemy.orm.mapper import _mapper_registry
classes = []
for mapper in six.iterkeys(_mapper_registry):
if hasattr(mapper, 'class_'):
classes.append( mapper.class_ )
else:
raise Exception()
app_admin = self.get_application_admin()
for cls in classes:
admin = app_admin.get_related_admin(cls)
if admin is not None:
yield admin
def test_table_view(self):
from camelot.admin.action.base import GuiContext
from camelot.view.action_steps import OpenTableView
gui_context = GuiContext()
for admin in self.get_admins():
if isinstance(admin, EntityAdmin):
step = OpenTableView(admin, admin.get_query())
widget = step.render(gui_context)
self.grab_widget(widget, suffix=admin.entity.__name__.lower(),
subdir='entityviews')
self.assertFalse( has_programming_error )
def test_new_view(self):
from camelot.admin.action.base import GuiContext
from camelot.admin.entity_admin import EntityAdmin
from ..view.action_steps import OpenFormView
gui_context = GuiContext()
for admin in self.get_admins():
verbose_name = six.text_type(admin.get_verbose_name())
LOGGER.debug('create new view for admin {0}'.format(verbose_name))
# create an object or take one from the db
obj = None
new_obj = False
if isinstance(admin, EntityAdmin):
obj = admin.get_query().first()
if obj is None:
obj = admin.entity()
new_obj = True
# create a form view
form_view_step = OpenFormView([obj], admin)
widget = form_view_step.render(gui_context)
mapper = widget.findChild(QtGui.QDataWidgetMapper, 'widget_mapper')
mapper.revert()
self.process()
if admin.form_state != None:
# virtually maximize the widget
widget.setMinimumSize(1200, 800)
self.grab_widget(widget, suffix=admin.entity.__name__.lower(), subdir='entityviews')
self.assertFalse( has_programming_error )
if new_obj:
self.session.expunge(obj)