From 402aca285f6287026f1387acfd757e8aeaf697da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Sat, 4 Jun 2011 21:14:25 +0200 Subject: [PATCH] port doctests to unittests --- Products/TinyMCE/tests.py | 39 ---- Products/TinyMCE/tests/__init__.py | 0 Products/TinyMCE/tests/adapters.txt | 138 ------------- Products/TinyMCE/tests/base.py | 60 ++++++ Products/TinyMCE/tests/browser.txt | 153 -------------- Products/TinyMCE/tests/exportimport.txt | 65 ------ Products/TinyMCE/tests/setuphandlers.txt | 52 ----- Products/TinyMCE/tests/test_adapters.py | 107 ++++++++++ Products/TinyMCE/tests/test_browser.py | 88 ++++++++ Products/TinyMCE/tests/test_exportimport.py | 55 +++++ Products/TinyMCE/tests/test_setuphandlers.py | 37 ++++ Products/TinyMCE/tests/test_transforms.py | 65 ++++++ Products/TinyMCE/tests/test_upgrades.py | 21 ++ Products/TinyMCE/tests/test_utility.py | 170 ++++++++++++++++ .../TinyMCE/tests/test_wysiwyg_support.py | 120 +++++++++++ Products/TinyMCE/tests/transforms.txt | 73 ------- Products/TinyMCE/tests/upgrades.txt | 22 -- Products/TinyMCE/tests/utility.txt | 189 ------------------ Products/TinyMCE/tests/wysiwyg_support.txt | 160 --------------- buildout.cfg | 7 +- setup.py | 4 +- 21 files changed, 729 insertions(+), 896 deletions(-) delete mode 100644 Products/TinyMCE/tests.py create mode 100644 Products/TinyMCE/tests/__init__.py delete mode 100644 Products/TinyMCE/tests/adapters.txt create mode 100644 Products/TinyMCE/tests/base.py delete mode 100644 Products/TinyMCE/tests/browser.txt delete mode 100644 Products/TinyMCE/tests/exportimport.txt delete mode 100644 Products/TinyMCE/tests/setuphandlers.txt create mode 100644 Products/TinyMCE/tests/test_adapters.py create mode 100644 Products/TinyMCE/tests/test_browser.py create mode 100644 Products/TinyMCE/tests/test_exportimport.py create mode 100644 Products/TinyMCE/tests/test_setuphandlers.py create mode 100644 Products/TinyMCE/tests/test_transforms.py create mode 100644 Products/TinyMCE/tests/test_upgrades.py create mode 100644 Products/TinyMCE/tests/test_utility.py create mode 100644 Products/TinyMCE/tests/test_wysiwyg_support.py delete mode 100644 Products/TinyMCE/tests/transforms.txt delete mode 100644 Products/TinyMCE/tests/upgrades.txt delete mode 100644 Products/TinyMCE/tests/utility.txt delete mode 100644 Products/TinyMCE/tests/wysiwyg_support.txt diff --git a/Products/TinyMCE/tests.py b/Products/TinyMCE/tests.py deleted file mode 100644 index 23a231fc..00000000 --- a/Products/TinyMCE/tests.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Test setup for integration and functional tests.""" -import os -import pkg_resources -import unittest - -from Products.Five import zcml -from Products.Five import fiveconfigure - -from Testing import ZopeTestCase as ztc - -from Products.PloneTestCase import PloneTestCase as ptc -from Products.PloneTestCase.layer import onsetup - - -@onsetup -def setup_product(): - """Set up the package and its dependencies.""" - - fiveconfigure.debug_mode = True - import Products.TinyMCE - zcml.load_config('configure.zcml', Products.TinyMCE) - fiveconfigure.debug_mode = False - -ztc.installProduct('TinyMCE') -setup_product() -ptc.setupPloneSite(products=['Products.TinyMCE']) - -path = pkg_resources.resource_filename('Products.TinyMCE', 'tests') -doc_tests = [x for x in os.listdir(path) if x.endswith('.txt')] - - -def test_suite(): - """This sets up a test suite that actually runs the tests""" - return unittest.TestSuite( - [ztc.ZopeDocFileSuite( - 'tests/%s' % f, package='Products.TinyMCE', - test_class=ptc.FunctionalTestCase) - for f in doc_tests], - ) diff --git a/Products/TinyMCE/tests/__init__.py b/Products/TinyMCE/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Products/TinyMCE/tests/adapters.txt b/Products/TinyMCE/tests/adapters.txt deleted file mode 100644 index ddd22d58..00000000 --- a/Products/TinyMCE/tests/adapters.txt +++ /dev/null @@ -1,138 +0,0 @@ -======== -Adapters -======== - -This functionality is added to plone content types using adapters. - -Let's start by logging in as a manager. - - >>> self.setRoles(['Manager']) - -JSONDetails ------------ - -This class is used to get detail from a certain object. Let's create a Document. - - >>> document = portal.invokeFactory('Document', id='document') - >>> portal[document] - - -The basic details should return the following. - - >>> from Products.TinyMCE.adapters.interfaces.JSONDetails import IJSONDetails - >>> object = IJSONDetails(portal[document]) - >>> object.getDetails() - '{"thumb": "", "description": "", "anchors": [], "title": "document"}' - -Let's set some more details like description and body text. - - >>> portal[document].setDescription('Test') - >>> portal[document].setText(u'

anchor

', - ... mimetype='text/html') - -The details will now contain a bit more info. - - >>> object.getDetails() - '{"thumb": "", "description": "Test", "anchors": ["anchor"], "title": "document"}' - -We can also get the details of an image object. - - >>> image = portal.invokeFactory('Image', id='image') - >>> portal[image] - - - >>> import os - >>> imgdata = open(os.path.join(os.path.dirname(__file__), 'sample.png')) - >>> portal[image].setImage(imgdata) - -The details will now also include the thumbnail url and the imagescales. - - >>> object = IJSONDetails(portal[image]) - >>> object.getDetails() - '{"scales":..."thumb": "http://nohost/plone/resolveuid/.../@@images/image/thumb"...}' - -The first scale is always the original size: - - >>> object.getDetails() - '{"scales": [{"size": [52, 43], "value": "", "title": "Original"}... - -JSONFolderListing ------------------ - -The folder listing is used in the link and image drawers to show the contents -of a folder. Let's see what items our current siteroot has. - - >>> from zope.component import getUtility - >>> from Products.TinyMCE.interfaces.utility import ITinyMCE - >>> utility = getUtility(ITinyMCE) - >>> linkable_portal_types = utility.linkable.split('\n') - >>> linkable_portal_types.extend(utility.containsobjects.split('\n')) - >>> from Products.TinyMCE.adapters.interfaces.JSONFolderListing import \ - ... IJSONFolderListing - >>> object = IJSONFolderListing(portal) - >>> object.getListing(filter_portal_types=linkable_portal_types,\ - ... rooted=False,\ - ... document_base_url='http://nohost/plone',\ - ... upload_type='File',\ - ... ) - '{"parent_url": "", "path": [...],..."items": [...]}' - -Let's create some more content to get breadcrumbs. - - >>> folder = portal.invokeFactory('Folder', id='folder') - >>> folder_object = portal[folder] - >>> document = folder_object.invokeFactory('Document', id='document') - -When we call the getListing method on the document we should get the listing of -its parent. - - >>> object = IJSONFolderListing(folder_object.get(document)) - >>> object.getListing(filter_portal_types=[],\ - ... rooted='False',\ - ... document_base_url='http://nohost/plone/folder',\ - ... ) - '{"parent_url": "http://nohost/plone"...}' - -We can also select rooted so we don't get all the breadcrumbs. - - >>> object.getListing(filter_portal_types=[],\ - ... rooted='True',\ - ... document_base_url='http://nohost/plone/folder',\ - ... ) - '{..."path": [{"url": "http://nohost/plone/folder"...}' - -JSONSearch ----------- - -The json search is used the look for content types within the portal. Let's see -if we can find some items containing the searchterm 'Events' - - >>> linkable_portal_types = utility.linkable.split('\n') - >>> linkable_portal_types.extend(utility.containsobjects.split('\n')) - >>> from Products.TinyMCE.adapters.interfaces.JSONSearch import IJSONSearch - >>> object = IJSONSearch(portal) - >>> object.getSearchResults(filter_portal_types=linkable_portal_types,\ - ... searchtext='Events',\ - ... ) - '{..."title": "Events", "url": "http://nohost/plone/events"...}' - -Save ----- - -This class is used to save a document using json. Let's try and set some value. - - >>> from Products.TinyMCE.adapters.interfaces.Save import ISave - >>> object = ISave(folder_object[document]) - >>> object.save(fieldname='text',\ - ... text='

test

',\ - ... ) - 'saved' - -The document should now contain our new text. - - >>> folder_object[document].getText() - '

test

' - -Upload ------- - diff --git a/Products/TinyMCE/tests/base.py b/Products/TinyMCE/tests/base.py new file mode 100644 index 00000000..ac200b72 --- /dev/null +++ b/Products/TinyMCE/tests/base.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import unittest2 as unittest + +from plone.app.testing import FunctionalTesting +from plone.app.testing import IntegrationTesting +from plone.app.testing import PLONE_FIXTURE +from plone.app.testing import PloneSandboxLayer +from plone.app.testing import login +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID +from plone.app.testing import TEST_USER_NAME +from plone.testing import z2 + + +class TinyMCELayer(PloneSandboxLayer): + defaultBases = (PLONE_FIXTURE,) + + def setUpZope(self, app, configurationContext): + """Set up Zope.""" + import Products.TinyMCE + self.loadZCML(package=Products.TinyMCE) + z2.installProduct(app, 'Products.TinyMCE') + + def setUpPloneSite(self, portal): + """Set up Plone.""" + # Install into Plone site using portal_setup + self.applyProfile(portal, 'Products.TinyMCE:TinyMCE') + + def tearDownZope(self, app): + """Tear down Zope.""" + z2.uninstallProduct(app, 'Products.TinyMCE') + + +FIXTURE = TinyMCELayer() +INTEGRATION_TESTING = IntegrationTesting( + bases=(FIXTURE,), name="TinyMCELayer:Integration") +FUNCTIONAL_TESTING = FunctionalTesting( + bases=(FIXTURE,), name="TinyMCELayer:Functional") + + +class BaseTestCase(unittest.TestCase): + + def setUp(self): + self.portal = self.layer['portal'] + self.app = self.layer['app'] + setRoles(self.portal, TEST_USER_ID, ['Manager']) + login(self.portal, TEST_USER_NAME) + + +class IntegrationTestCase(BaseTestCase): + """Base class for integration tests.""" + + layer = INTEGRATION_TESTING + + +class FunctionalTestCase(BaseTestCase): + """Base class for functional tests.""" + + layer = FUNCTIONAL_TESTING diff --git a/Products/TinyMCE/tests/browser.txt b/Products/TinyMCE/tests/browser.txt deleted file mode 100644 index b5157d3f..00000000 --- a/Products/TinyMCE/tests/browser.txt +++ /dev/null @@ -1,153 +0,0 @@ -======= -Browser -======= - -The browser section adds browserviews used by TinyMCE. - -Let's start by logging in as a manager. - - >>> self.setRoles(['Manager']) - -url ---- - -We start by creating a normal Document. - - >>> document = portal.invokeFactory('Document', id='document') - >>> portal[document] - - -Then we get the url by specifying the uid of the document. - - >>> from Products.Five.testbrowser import Browser - >>> browser = Browser() - >>> self.app.acl_users.userFolderAddUser('root', 'secret', ['Manager'], []) - >>> browser.addHeader('Authorization', 'Basic root:secret') - >>> browser.open('http://nohost/plone/portal_tinymce/@@tinymce-getpathbyuid?uid=%s' % (portal[document].UID())) - >>> browser.contents - 'http://nohost/plone/document' - -If we don't specify the uid we get an empty string. - - >>> browser.open('http://nohost/plone/portal_tinymce/@@tinymce-getpathbyuid') - >>> browser.contents - '' - -If we specify a non existing uid we should get an empty string. - - >>> browser.open('http://nohost/plone/portal_tinymce/@@tinymce-getpathbyuid?uid=abc') - >>> browser.contents - '' - -style ------ - -This browserview will return all the stylesheets used. Let's call the -browser view. - - >>> browser.open('http://nohost/plone/portal_tinymce/@@tinymce-getstyle') - >>> browser.contents - '...' - -controlpanel ------------- - -Open the TinyMCE control panel. - - >>> browser.open('http://nohost/plone/portal_tinymce/@@tinymce-controlpanel') - -jsonlinkablefolderlisting -------------------------- - -We can call the linkable folder listing browserview on the site root to get a -list of linkable items. - - >>> browser.open('http://nohost/plone/@@tinymce-jsonlinkablefolderlisting?rooted=False&document_base_url=http://nohost/plone/') - >>> browser.contents - '..."id": "document"...' - -jsonimagefolderlisting ----------------------- - -Let's create an image first. - - >>> image = portal.invokeFactory('Image', id='image') - >>> portal[image] - - -Now we can get a listing of the images and check if our image is there. - - >>> browser.open('http://nohost/plone/@@tinymce-jsonimagefolderlisting?rooted=False&document_base_url=http://nohost/plone/') - >>> browser.contents - '..."id": "image"...' - -jsonlinkablesearch ------------------- - -If we want to search for a linkable item we can call the json linkable search -browser view and specify a searchtext. Let's find our document. - - >>> browser.open('http://nohost/plone/@@tinymce-jsonlinkablesearch?searchtext=Document') - >>> browser.contents - '..."id": "document"...' - -jsonimagesearch ---------------- - -The images have a similar search method. Let's find our image. - - >>> browser.open('http://nohost/plone/@@tinymce-jsonimagesearch?searchtext=Image') - >>> browser.contents - '..."id": "image"...' - -jsondetails ------------ - -When we call the json details view on a document we will get the details of -the specific item. - - >>> browser.open('http://nohost/plone/document/@@tinymce-jsondetails') - >>> browser.contents - '...document...' - -save ----- - -Let's call the save method to store some content in the document we created. - - >>> browser.open('http://nohost/plone/document/@@tinymce-save?text=test&fieldname=text') - >>> portal[document].getText() - 'test' - -upload ------- - -TODO - -configuration -------------- - - >>> document_jsonconfig_url = ('http://nohost/plone/document/' - ... '@@tinymce-jsonconfiguration?fieldname=text') - >>> browser.open(document_jsonconfig_url) - >>> browser.contents - '...buttons...' - -If we configure directivity to 'auto', the directivity is set depending -on the content language. - - >>> from Products.TinyMCE.utility import form_adapter - >>> utility = form_adapter(portal) - >>> utility.directionality = 'auto' - >>> doc = portal[document] - >>> doc.Language() - 'en' - >>> browser.open(document_jsonconfig_url) - >>> browser.contents - '..."directionality": "ltr"...' - >>> doc.setLanguage('ar') - >>> browser.open(document_jsonconfig_url) - >>> browser.contents - '..."directionality": "rtl"...' - - diff --git a/Products/TinyMCE/tests/exportimport.txt b/Products/TinyMCE/tests/exportimport.txt deleted file mode 100644 index 53f60723..00000000 --- a/Products/TinyMCE/tests/exportimport.txt +++ /dev/null @@ -1,65 +0,0 @@ -============= -Export/Import -============= - -Import ------- - -This module takes care of exporting and importing settings of TinyMCE. Let's -create a dummy import context. And a dummy file. - - >>> from Products.GenericSetup.tests.common import DummyImportContext - >>> xml = """\ - ... - ... - ... - ... - ... - ... - ... - ... - ... - ... - ... - ... - ... - ... - ... """ - >>> context = DummyImportContext(portal, purge=True) - >>> context._files = {'tinymce.xml': xml} - -Now import the file. - - >>> from Products.TinyMCE.exportimport import importTinyMCESettings - >>> importTinyMCESettings(context) - -Our specified plugin should now be stored in the utility. - - >>> from Products.TinyMCE.interfaces.utility import ITinyMCE - >>> from zope.component import getUtility - >>> tinymce_utility = getUtility(ITinyMCE) - >>> 'testplugin' in tinymce_utility.customplugins - True - -Export ------- - -Let's create a dummy export context. - - >>> from Products.GenericSetup.tests.common import DummyExportContext - >>> context = DummyExportContext(portal) - -And export the current settings. - - >>> from Products.TinyMCE.exportimport import exportTinyMCESettings - >>> exportTinyMCESettings(context) - -Check if tinymce.xml is exported. - - >>> context._wrote[0][0] - 'tinymce.xml' - -Check the contents of the export. - - >>> context._wrote[0][1] - '...testplugin...' diff --git a/Products/TinyMCE/tests/setuphandlers.txt b/Products/TinyMCE/tests/setuphandlers.txt deleted file mode 100644 index 61a5541a..00000000 --- a/Products/TinyMCE/tests/setuphandlers.txt +++ /dev/null @@ -1,52 +0,0 @@ -============= -Setuphandlers -============= - -Remove editor -------------- - -First let's remove the editor - - >>> from Products.TinyMCE.setuphandlers import remove_editor - >>> remove_editor(portal) - -Check if it is removed. - - >>> from Products.CMFCore.interfaces import IPropertiesTool - >>> from zope.component import getUtility - >>> portal_props = getUtility(IPropertiesTool) - >>> site_props=getattr(portal_props,'site_properties', None) - >>> attrname='available_editors' - >>> editors=list(site_props.getProperty(attrname)) - >>> 'TinyMCE' in editors - False - -Add editor ----------- - -And now add the editor. - - >>> from Products.TinyMCE.setuphandlers import add_editor - >>> add_editor(portal) - -And check if it is added. - - >>> editors=list(site_props.getProperty(attrname)) - >>> 'TinyMCE' in editors - True - - -Unregister utility ------------------- - -Unregister the utility. - - >>> from Products.TinyMCE.setuphandlers import unregisterUtility - >>> unregisterUtility(portal) - -And check if the utility is removed. - - >>> from Products.TinyMCE.interfaces.utility import ITinyMCE - >>> from zope.component import queryUtility - >>> queryUtility(ITinyMCE, default='Not found') - 'Not found' diff --git a/Products/TinyMCE/tests/test_adapters.py b/Products/TinyMCE/tests/test_adapters.py new file mode 100644 index 00000000..39417d06 --- /dev/null +++ b/Products/TinyMCE/tests/test_adapters.py @@ -0,0 +1,107 @@ +""" +This functionality is added to plone content types using adapters. +""" +import os + +from zope.component import getUtility + +from Products.TinyMCE.adapters.interfaces.JSONDetails import IJSONDetails +from Products.TinyMCE.adapters.interfaces.Save import ISave +from Products.TinyMCE.adapters.interfaces.JSONSearch import IJSONSearch +from Products.TinyMCE.adapters.interfaces.JSONFolderListing import IJSONFolderListing +from Products.TinyMCE.interfaces.utility import ITinyMCE +from Products.TinyMCE.tests.base import FunctionalTestCase + + +class AdaptersTestCase(FunctionalTestCase): + + def setUp(self): + super(AdaptersTestCase, self).setUp() + self.utility = getUtility(ITinyMCE) + folder = self.portal.invokeFactory('Folder', id='folder') + self.document = self.portal.invokeFactory('Document', id='document') + self.folder_object = self.portal[folder] + + def test_json_details_document(self): + # This class is used to get detail from a certain object. Let's create a Document. + self.assertEqual(repr(self.portal[self.document]), '') + + # The basic details should return the following. + obj = IJSONDetails(self.portal[self.document]) + self.assertEqual(obj.getDetails(), '{"thumb": "", "description": "", "anchors": [], "title": "document"}') + + # Let's set some more details like description and body text. + self.portal[self.document].setDescription('Test') + self.portal[self.document].setText(u'

anchor

', mimetype='text/html') + + # The details will now contain a bit more info. + self.assertEqual(obj.getDetails(), '{"thumb": "", "description": "Test", "anchors": ["anchor"], "title": "document"}') + + def test_json_details_image(self): + # We can also get the details of an image object. + image = self.portal.invokeFactory('Image', id='image') + self.assertEqual(repr(self.portal[image]), '') + + imgdata = open(os.path.join(os.path.dirname(__file__), 'sample.png')) + self.portal[image].setImage(imgdata) + + # The details will now also include the thumbnail url and the imagescales. + obj = IJSONDetails(self.portal[image]) + self.assertRegexpMatches(obj.getDetails(), r'\{"scales": \[\{"size": \[52, 43], "value": "", "title": "Original"}.+], "thumb": "http://nohost/plone/resolveuid/.*/@@images/image/thumb".+') + + def test_json_folder_listing(self): + # The folder listing is used in the link and image drawers to show the contents + # of a folder. Let's see what items our current siteroot has. + + linkableportal_types = self.utility.linkable.split('\n') + linkableportal_types.extend(self.utility.containsobjects.split('\n')) + object = IJSONFolderListing(self.portal) + self.assertRegexpMatches(object.getListing( + filter_portal_types=linkableportal_types, + rooted=False, + document_base_url='http://nohost/plone', + upload_type='File', + ), '\{"parent_url": "", "path": \[.+],.+"items": \[.+]}') + + # Let's create some more content to get breadcrumbs. + document = self.folder_object.invokeFactory('Document', id='document') + + # When we call the getListing method on the document we should get the listing of + # its parent. + obj = IJSONFolderListing(self.folder_object.get(document)) + self.assertRegexpMatches(obj.getListing( + filter_portal_types=[], + rooted='False', + document_base_url='http://nohost/plone/folder', + ), '\{"parent_url": "http://nohost/plone".+}') + + # We can also select rooted so we don't get all the breadcrumbs. + self.assertRegexpMatches(obj.getListing( + filter_portal_types=[], + rooted='True', + document_base_url='http://nohost/plone/folder', + ), '\{.*"path": \[{"url": "http://nohost/plone/folder".*}') + + def test_json_search(self): + # Create an Event + self.portal.invokeFactory('Event', id='events', title='Events') + + # The json search is used the look for content types within the self.portal. Let's see + # if we can find some items containing the searchterm 'Events' + linkable_portal_types = self.utility.linkable.split('\n') + linkable_portal_types.extend(self.utility.containsobjects.split('\n')) + + obj = IJSONSearch(self.portal) + self.assertRegexpMatches( + obj.getSearchResults(filter_portal_types=linkable_portal_types, + searchtext='Events', + ), '\{.*"title": "Events", "url": "http://nohost/plone/events".*}') + + def test_json_save(self): + # This class is used to save a document using json. Let's try and set some value. + document = self.folder_object.invokeFactory('Document', id='document') + obj = ISave(self.folder_object[document]) + obj.save(fieldname='text', text='

test

') + + # The document should now contain our new text. + self.assertEqual(self.folder_object[self.document].getText(), '

test

') diff --git a/Products/TinyMCE/tests/test_browser.py b/Products/TinyMCE/tests/test_browser.py new file mode 100644 index 00000000..d4b7508b --- /dev/null +++ b/Products/TinyMCE/tests/test_browser.py @@ -0,0 +1,88 @@ +from Products.TinyMCE.utility import form_adapter +from Products.TinyMCE.tests.base import FunctionalTestCase + + +class BrowserTestCase(FunctionalTestCase): + + def setUp(self): + super(BrowserTestCase, self).setUp() + self.image = self.portal.invokeFactory('Image', id='image') + self.document = self.portal.invokeFactory('Document', id='document') + + def test_url(self): + # We get the url by specifying the uid of the document. + self.portal.REQUEST['uid'] = self.portal[self.document].UID() + output = self.portal.restrictedTraverse('/plone/portal_tinymce/@@tinymce-getpathbyuid')() + self.assertEqual(output, 'http://nohost/plone/document') + + # If we don't specify the uid we get an empty string. + self.portal.REQUEST['uid'] = None + output = self.portal.restrictedTraverse('/plone/portal_tinymce/@@tinymce-getpathbyuid')() + self.assertEqual(output, '') + + self.portal.REQUEST['uid'] = 'asd' + # If we specify a non existing uid we should get an empty string. + output = self.portal.restrictedTraverse('/plone/portal_tinymce/@@tinymce-getpathbyuid')() + self.assertEqual(output, '') + + def test_getstyle(self): + # This browserview will return all the stylesheets used. Let's call the + # browser view. + output = self.portal.restrictedTraverse('/plone/portal_tinymce/@@tinymce-getstyle')() + self.assertRegexpMatches(output, '') + + def test_controlpanel(self): + # Open the TinyMCE control panel. + self.portal.restrictedTraverse('/plone/portal_tinymce/@@tinymce-controlpanel')() + + def test_jsonlinkablefolderlisting(self): + # We can call the linkable folder listing browserview on the site root to get a + # list of linkable items. + output = self.portal.restrictedTraverse('/plone/@@tinymce-jsonlinkablefolderlisting')(False, 'http://nohost/plone/') + self.assertIn('"id": "document"', output) + + def test_jsonimagefolderlisting(self): + # Now we can get a listing of the images and check if our image is there.e/'}) + output = self.portal.restrictedTraverse('/plone/@@tinymce-jsonimagefolderlisting')(False, 'http://nohost/plone/') + self.assertIn('"id": "image"', output) + + def test_jsonlinkablesearch(self): + # If we want to search for a linkable item we can call the json linkable search + # browser view and specify a searchtext. Let's find our document. + output = self.portal.restrictedTraverse('/plone/@@tinymce-jsonlinkablesearch')('Document') + self.assertIn('"id": "document"', output) + + def test_jsonimagesearch(self): + # The images have a similar search method. Let's find our image. + output = self.portal.restrictedTraverse('/plone/@@tinymce-jsonimagesearch')('Image') + self.assertIn('"id": "image"', output) + + def test_jsondetails(self): + # When we call the json details view on a document we will get the details of + # the specific item. + output = self.portal.restrictedTraverse('/plone/document/@@tinymce-jsondetails')() + self.assertIn('document', output) + + def test_save(self): + # Let's call the save method to store some content in the document we created. + self.portal.restrictedTraverse('/plone/document/@@tinymce-save')('test', 'text') + self.assertEqual(self.portal[self.document].getText(), 'test') + + def test_configuration(self): + document_jsonconfig_url = '/plone/document/@@tinymce-jsonconfiguration' + output = self.portal.restrictedTraverse(document_jsonconfig_url)('text') + self.assertIn('buttons', output) + + # If we configure directivity to 'auto', the directivity is set depending + # on the content language. + utility = form_adapter(self.portal) + utility.directionality = 'auto' + doc = self.portal[self.document] + self.assertEqual(doc.Language(), 'en') + output = self.portal.restrictedTraverse(document_jsonconfig_url)('text') + self.assertIn('"directionality": "ltr"', output) + doc.setLanguage('ar') + output = self.portal.restrictedTraverse(document_jsonconfig_url)('text') + self.assertIn('"directionality": "rtl"', output) + + # TODO: upload diff --git a/Products/TinyMCE/tests/test_exportimport.py b/Products/TinyMCE/tests/test_exportimport.py new file mode 100644 index 00000000..1893ea19 --- /dev/null +++ b/Products/TinyMCE/tests/test_exportimport.py @@ -0,0 +1,55 @@ +from zope.component import getUtility +from Products.GenericSetup.tests.common import DummyImportContext +from Products.GenericSetup.tests.common import DummyExportContext + +from Products.TinyMCE.interfaces.utility import ITinyMCE +from Products.TinyMCE.exportimport import importTinyMCESettings +from Products.TinyMCE.exportimport import exportTinyMCESettings +from Products.TinyMCE.tests.base import FunctionalTestCase + + +class ImportExportTestCase(FunctionalTestCase): + + def test_import_export(self): + # This module takes care of exporting and importing settings of TinyMCE. Let's + # create a dummy import context. And a dummy file. + + xml = """\ + + + + + + + + + + + + + + + """ + + context = DummyImportContext(self.portal, purge=True) + context._files = {'tinymce.xml': xml} + + # Now import the file. + importTinyMCESettings(context) + + # Our specified plugin should now be stored in the utility. + + tinymce_utility = getUtility(ITinyMCE) + self.assertIn('testplugin', tinymce_utility.customplugins) + + # Let's create a dummy export context. + context = DummyExportContext(self.portal) + + # And export the current settings. + exportTinyMCESettings(context) + + # Check if tinymce.xml is exported. + self.assertEqual(context._wrote[0][0], 'tinymce.xml') + + # Check the contents of the export. + self.assertIn('testplugin', context._wrote[0][1]) diff --git a/Products/TinyMCE/tests/test_setuphandlers.py b/Products/TinyMCE/tests/test_setuphandlers.py new file mode 100644 index 00000000..f163f691 --- /dev/null +++ b/Products/TinyMCE/tests/test_setuphandlers.py @@ -0,0 +1,37 @@ +from zope.component import queryUtility +from zope.component import getUtility +from Products.CMFCore.interfaces import IPropertiesTool + +from Products.TinyMCE.interfaces.utility import ITinyMCE +from Products.TinyMCE.tests.base import FunctionalTestCase +from Products.TinyMCE.setuphandlers import remove_editor +from Products.TinyMCE.setuphandlers import add_editor +from Products.TinyMCE.setuphandlers import unregisterUtility + + +class SetupHandlersTestCase(FunctionalTestCase): + + def test_remove_editor(self): + # First let's remove the editor + remove_editor(self.portal) + + # Check if it is removed. + + portal_props = getUtility(IPropertiesTool) + site_props = getattr(portal_props, 'site_properties', None) + attrname = 'available_editors' + editors = list(site_props.getProperty(attrname)) + self.assertNotIn('TinyMCE', editors) + + # And now add the editor. + add_editor(self.portal) + + # And check if it is added. + editors = list(site_props.getProperty(attrname)) + self.assertIn('TinyMCE', editors) + + def test_unregister_utility(self): + unregisterUtility(self.portal) + + # And check if the utility is removed. + self.assertEqual(queryUtility(ITinyMCE, default='Not found'), 'Not found') diff --git a/Products/TinyMCE/tests/test_transforms.py b/Products/TinyMCE/tests/test_transforms.py new file mode 100644 index 00000000..22d69c4d --- /dev/null +++ b/Products/TinyMCE/tests/test_transforms.py @@ -0,0 +1,65 @@ +from zope.component import getUtility + +from Products.CMFPlone.tests import dummy +from Products.TinyMCE.interfaces.utility import ITinyMCE +from Products.TinyMCE.tests.base import FunctionalTestCase + + +class TransformsTestCase(FunctionalTestCase): + + # Tests integration with plone.outputfilters, which provides 2 filters that are + # applied while rendering rich text: + + # * Resolving UID links to absolute URIs. + # * Adding captions to images with the "captioned" CSS class. + + # These filters used to be implemented in Products.TinyMCE, so for backwards + # compatibility they can be enabled via settings in the TinyMCE control panel. + + def test_filters(self): + # Let's turn these filters on first. + tinymce_utility = getUtility(ITinyMCE) + tinymce_utility.link_using_uids = True + tinymce_utility.allow_captioned_images = True + + # To test the settings, we'll need an image. + self.portal.invokeFactory('Image', id='image.jpg', title="Image", file=dummy.Image()) + image = getattr(self.portal, 'image.jpg') + image.setDescription('My caption') + image.reindexObject() + UID = image.UID() + + # Let's transform some text/html to text/x-html-safe, which should trigger the + # filters. + text = """ + + + + My alt text +

+
This is line 1
+             This is line 2
+ +""" % UID + transformed_text = self.portal.portal_transforms.convertTo('text/x-html-safe', text, mimetype='text/html', context=self.portal) + + # The UID reference should be converted to an absolute url, and a caption should be added. + self.assertEqual(unicode(transformed_text).replace(' ', ''), u""" +
+
My alt text
+
My caption
+
+

+
Image
+
My caption
+

+
This is line 1
+      This is line 2
+""".replace(' ', '')) + # Now turn off the settings. + tinymce_utility.link_using_uids = False + tinymce_utility.allow_captioned_images = False + + # Now the filters should not be applied. + transformed_text_2 = self.portal.portal_transforms.convertTo('text/x-html-safe', text, mimetype='text/html', context=self.portal) + self.assertNotEqual(unicode(transformed_text_2).replace(' ', ''), unicode(transformed_text).replace(' ', '')) diff --git a/Products/TinyMCE/tests/test_upgrades.py b/Products/TinyMCE/tests/test_upgrades.py new file mode 100644 index 00000000..e4c53729 --- /dev/null +++ b/Products/TinyMCE/tests/test_upgrades.py @@ -0,0 +1,21 @@ +from zope.component import getUtility +from Products.CMFCore.utils import getToolByName + +from Products.TinyMCE.upgrades import upgrade_10_to_11 +from Products.TinyMCE.interfaces.utility import ITinyMCE +from Products.TinyMCE.tests.base import FunctionalTestCase + + +class UpgradesTestCase(FunctionalTestCase): + + def test_upgrade_profile(self): + # First set the entity encoding to a different value. + tinymce = getUtility(ITinyMCE) + tinymce.entity_encoding = u"named" + + # Then run the upgrade from 1.0. to 1.1. + portal_setup = getToolByName(self.portal, 'portal_setup') + upgrade_10_to_11(portal_setup) + + # And check the outcome + self.assertEqual(tinymce.entity_encoding, u'raw') diff --git a/Products/TinyMCE/tests/test_utility.py b/Products/TinyMCE/tests/test_utility.py new file mode 100644 index 00000000..2c825b9a --- /dev/null +++ b/Products/TinyMCE/tests/test_utility.py @@ -0,0 +1,170 @@ +import os + +import transaction +from Products.CMFCore.utils import getToolByName +from plone.testing.z2 import Browser + +from Products.TinyMCE.tests.base import IntegrationTestCase +from Products.TinyMCE.utility import form_adapter + + +class UtilityTestCase(IntegrationTestCase): + + def setUp(self): + super(UtilityTestCase, self).setUp() + self.utility = form_adapter(self.portal) + + def test_tinymce_configuration(self): + # Let's get the configuration of TinyMCE and see if it returns a json structure. + self.assertTrue(self.utility.getConfiguration(self.portal)) + + # Now let's change some variables and see it still works. + self.utility.toolbar_cut = True + self.utility.toolbar_copy = True + self.utility.toolbar_paste = True + self.utility.toolbar_pastetext = True + self.utility.toolbar_pasteword = True + self.utility.toolbar_undo = True + self.utility.toolbar_redo = True + self.utility.toolbar_search = True + self.utility.toolbar_replace = True + self.utility.toolbar_underline = True + self.utility.toolbar_strikethrough = True + self.utility.toolbar_sub = True + self.utility.toolbar_sup = True + self.utility.toolbar_forecolor = True + self.utility.toolbar_backcolor = True + self.utility.toolbar_media = True + self.utility.toolbar_charmap = True + self.utility.toolbar_hr = True + self.utility.toolbar_advhr = True + self.utility.toolbar_insertdate = True + self.utility.toolbar_inserttime = True + self.utility.toolbar_emotions = True + self.utility.toolbar_nonbreaking = True + self.utility.toolbar_pagebreak = True + self.utility.toolbar_print = True + self.utility.toolbar_preview = True + self.utility.toolbar_spellchecker = True + self.utility.toolbar_removeformat = True + self.utility.toolbar_cleanup = True + self.utility.toolbar_visualaid = True + self.utility.toolbar_visualchars = True + self.utility.toolbar_attribs = True + self.utility.resizing = False + + # The result should at least contain the new buttons we added. + self.assertRegexpMatches(self.utility.getConfiguration(self.portal), '\{.+attribs.+}') + + # Let's change some more settings. + self.utility.toolbar_external = True + self.utility.autoresize = True + self.utility.editor_width = u'100' + self.utility.editor_height = u'abc' + self.utility.toolbar_width = u'abc' + self.utility.contextmenu = False + self.utility.content_css = u'test.css' + self.utility.link_using_uids = True + self.utility.allow_captioned_images = True + self.utility.rooted = True + + props = getToolByName(self, 'portal_properties') + livesearch = props.site_properties.getProperty('enable_livesearch', False) + livesearch = False + livesearch # pep8 + + # The result should contain the settings specified. + self.assertRegexpMatches(self.utility.getConfiguration(self.portal), '\{.+external.+}') + + # Let's call the portal_factory of a document and make sure the configuration + # doesn't contain the save button: + browser = Browser(self.app) + self.app.acl_users.userFolderAddUser('root', 'secret', ['Manager'], []) + transaction.commit() + browser.addHeader('Authorization', 'Basic root:secret') + browser.open('http://nohost/plone/createObject?type_name=Document') + self.assertNotIn(""save":", browser.contents) + + # Do some more toolbar tests, specifically testing the spellchecker button. + # First, we make sure that no spellchecker is loaded when the toolbar button is + # hidden. + self.utility.toolbar_spellchecker = False + transaction.commit() + browser.open('http://nohost/plone/createObject?type_name=Document') + + # AtD shouldn't be there: + self.assertNotIn(""AtD"", browser.contents) + + # Neither should iespell: + self.assertNotIn(""iespell"", browser.contents) + + # Now, we enable the button and set AtD as the checker: + self.utility.toolbar_spellchecker = True + self.utility.libraries_spellchecker_choice = u'AtD' + transaction.commit() + browser.open('http://nohost/plone/createObject?type_name=Document') + self.assertIn(""AtD"", browser.contents) + + # Now, we set iespell as the checker: + self.utility.libraries_spellchecker_choice = u'iespell' + transaction.commit() + browser.open('http://nohost/plone/createObject?type_name=Document') + self.assertIn(""iespell"", browser.contents) + + # When we have browser as the checker, neither iespell nor AtD should load: + self.utility.libraries_spellchecker_choice = u'browser' + transaction.commit() + browser.open('http://nohost/plone/createObject?type_name=Document') + self.assertNotIn(""iespell"", browser.contents) + self.assertNotIn(""AtD"", browser.contents) + + def test_tinymce_configurable_image_dimensions(self): + # The image scale dimensions being provided to the image chooser should be + # gathered from the chosen AT content type. Let's begin by creating an + # AT image: + id = self.portal.invokeFactory('News Item', id='test') + self.assertEqual(repr(self.portal[id]), '') + + # Now the values being provided through JSON should be gotten from the ATNewsItem + # schema: + primary_field = self.portal.test.schema['image'] + scales = self.utility.getImageScales(primary_field) + self.assertEqual(scales, + [{'size': [0, 0], 'title': 'Original', 'value': ''}, + {'size': [16, 16], 'title': 'Listing', 'value': 'image_listing'}, + {'size': [32, 32], 'title': 'Icon', 'value': 'image_icon'}, + {'size': [64, 64], 'title': 'Tile', 'value': 'image_tile'}, + {'size': [128, 128], 'title': 'Thumb', 'value': 'image_thumb'}, + {'size': [200, 200], 'title': 'Mini', 'value': 'image_mini'}, + {'size': [400, 400], 'title': 'Preview', 'value': 'image_preview'}, + {'size': [768, 768], 'title': 'Large', 'value': 'image_large'}] + ) + + # If no primary field is given, we should get the scale dimensions from ATImage: + scales = self.utility.getImageScales() + self.assertEqual(scales, + [{'size': [0, 0], 'title': 'Original', 'value': ''}, + {'size': [16, 16], 'title': 'Listing', 'value': 'image_listing'}, + {'size': [32, 32], 'title': 'Icon', 'value': 'image_icon'}, + {'size': [64, 64], 'title': 'Tile', 'value': 'image_tile'}, + {'size': [128, 128], 'title': 'Thumb', 'value': 'image_thumb'}, + {'size': [200, 200], 'title': 'Mini', 'value': 'image_mini'}, + {'size': [400, 400], 'title': 'Preview', 'value': 'image_preview'}, + {'size': [768, 768], 'title': 'Large', 'value': 'image_large'}] + ) + + # We need to specify a context, if we want the dimension of the original image + imgdata = open(os.path.join(os.path.dirname(__file__), 'sample.png')) + self.portal[id].setImage(imgdata) + + scales = self.utility.getImageScales(context=self.portal[id]) + self.assertEqual(scales, + [{'size': [52, 43], 'title': 'Original', 'value': ''}, + {'size': [16, 16], 'title': 'Listing', 'value': 'image_listing'}, + {'size': [32, 32], 'title': 'Icon', 'value': 'image_icon'}, + {'size': [64, 64], 'title': 'Tile', 'value': 'image_tile'}, + {'size': [128, 128], 'title': 'Thumb', 'value': 'image_thumb'}, + {'size': [200, 200], 'title': 'Mini', 'value': 'image_mini'}, + {'size': [400, 400], 'title': 'Preview', 'value': 'image_preview'}, + {'size': [768, 768], 'title': 'Large', 'value': 'image_large'}] + ) diff --git a/Products/TinyMCE/tests/test_wysiwyg_support.py b/Products/TinyMCE/tests/test_wysiwyg_support.py new file mode 100644 index 00000000..588f7f34 --- /dev/null +++ b/Products/TinyMCE/tests/test_wysiwyg_support.py @@ -0,0 +1,120 @@ +import transaction +from plone.testing.z2 import Browser + +from Products.TinyMCE.tests.base import IntegrationTestCase +from plone.app.testing import TEST_USER_NAME +from plone.app.testing import TEST_USER_PASSWORD + + +class WysiwygSupportTestCase(IntegrationTestCase): + + def setUp(self): + super(WysiwygSupportTestCase, self).setUp() + # Unhide exceptions. + + def test_personalized(self): + self.portal.error_log._ignored_exceptions = () + browser = Browser(self.app) + portal_url = self.portal.absolute_url() + + # Get an account and login via the login form. + browser.open(portal_url + '/login_form') + browser.getControl(name='__ac_name').value = TEST_USER_NAME + browser.getControl(name='__ac_password').value = TEST_USER_PASSWORD + browser.getControl(name='submit').click() + + # Create a blank page for test edits. (front-page is too noisy.) + testpage = self.portal.absolute_url() + '/' + self.portal.invokeFactory('Document', 'testpage') + '/edit' + self.assertEqual(testpage, 'http://nohost/plone/testpage/edit') + transaction.commit() + + # Set up personalize_form + personalizer = portal_url + '/personalize_form' + self.assertEqual(personalizer, 'http://nohost/plone/personalize_form') + browser.open(personalizer) + browser.getControl(name='email').value = 'test@example.org' + browser.getControl(name='form.button.Save').click() + self.assertIn('
Your personal settings have been saved.
', browser.contents) + + #Test different editors + #---------------------- + #Which editor is used is set globally and can be overridden in the personal preferences. + #This results in the following matrix: + #===== ===== ====== + #Global Personal Expected + #===== ===== ====== + #TinyMCE Use site's default TinyMCE + #TinyMCE None Basic textarea + #TinyMCE TinyMCE TinyMCE + # Use site's default Basic textarea + # None Basic textarea + # TinyMCE TinyMCE + #===== ===== ====== + + # Set the editor globally to TinyMCE: + self.portal.portal_properties.site_properties.default_editor = 'TinyMCE' + transaction.commit() + + # If the user sets 'Use site's default'... + browser.open(personalizer) + browser.getControl(name='wysiwyg_editor').value = [''] + browser.getControl(name='form.button.Save').click() + self.assertIn('
Your personal settings have been saved.
', browser.contents) + + # we should get TinyMCE: + browser.open(testpage) + self.assertIn('TinyMCEConfig', browser.contents) + + # If the user sets 'None'... + browser.open(personalizer) + browser.getControl(name='wysiwyg_editor').value = ['None'] + browser.getControl(name='form.button.Save').click() + self.assertIn('
Your personal settings have been saved.
', browser.contents) + + # We should get just a textarea: + browser.open(testpage) + self.assertIn(' ... - - -If the user sets 'TinyMCE'... - >>> browser.open(personalizer) - >>> browser.getControl(name='wysiwyg_editor').value = ['TinyMCE'] - >>> browser.getControl(name='form.button.Save').click() - >>> print(browser.contents) - Your personal settings have been saved. ... - -we should get TinyMCE: - >>> browser.open(testpage) - >>> print(browser.contents) - - tinymce.dom.Event.add(window, 'load', function(e) { var config = new TinyMCEConfig('text'); config.init(); }); ... - - -Set the editor globally to nothing: - >>> self.portal.portal_properties.site_properties.default_editor = '' - -If the user sets 'Use site's default'... - >>> browser.open(personalizer) - >>> browser.getControl(name='wysiwyg_editor').value = [''] - >>> browser.getControl(name='form.button.Save').click() - >>> print(browser.contents) - Your personal settings have been saved. ... - -we should get just a textarea: - >>> browser.open(testpage) - >>> print(browser.contents) - ... - - -If the user sets 'None'... - >>> browser.open(personalizer) - >>> browser.getControl(name='wysiwyg_editor').value = ['None'] - >>> browser.getControl(name='form.button.Save').click() - >>> print(browser.contents) - Your personal settings have been saved. ... - -we should get just a textarea: - >>> browser.open(testpage) - >>> print(browser.contents) - ... - - -If the user sets 'TinyMCE'... - >>> browser.open(personalizer) - >>> browser.getControl(name='wysiwyg_editor').value = ['TinyMCE'] - >>> browser.getControl(name='form.button.Save').click() - >>> print(browser.contents) - Your personal settings have been saved. ... - -we should get TinyMCE: - >>> browser.open(testpage) - >>> print(browser.contents) - - tinymce.dom.Event.add(window, 'load', function(e) { var config = new TinyMCEConfig('text'); config.init(); }); ... diff --git a/buildout.cfg b/buildout.cfg index 012b0b4e..6d5edd22 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -3,7 +3,9 @@ extends = http://svn.plone.org/svn/collective/buildout/plonetest/test-4.x.cfg versions = versions package-name = Products.TinyMCE -#test-eggs = PIL +test-eggs = + plone.app.testing + unittest2 parts += omelette @@ -13,8 +15,7 @@ extensions = develop = . [instance] -#eggs+=PIL -eggs += Products.TinyMCE +eggs += Products.TinyMCE [test] [versions] Products.PrintingMailHost = 0.7 diff --git a/setup.py b/setup.py index c8c8b95b..e02e6be6 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ 'plone.app.imaging>=1.0.2', 'plone.outputfilters', ] + requires, - extras_require = { - 'test': ['Products.PloneTestCase'], + extras_require={ + 'test': ['plone.app.testing', 'unittest2'], }, )