Let's test the behavior of the rating view aggregator.  If we call it on
some content with no assigned rating categories, we should get an
empty string::

    >>> from contentratings.browser.aggregator import UserRatingAggregatorView
    >>> request = {}
    >>> view = UserRatingAggregatorView(my_container, request)
    >>> view()
    u''

Lets assign a rating category to our content::

    >>> from zope.app.testing import ztapi
    >>> from zope.interface import Interface
    >>> from contentratings.category import RatingsCategoryFactory
    >>> from contentratings.interfaces import IUserRating
    >>> category = RatingsCategoryFactory(u"My Category",
    ...                                   name=u'rating1', view_name=u'test')
    >>> ztapi.provideAdapter((Interface,), IUserRating, category,
    ...                      name=u'rating1')

Then we assign a rating view::

    >>> from contentratings.browser.tests import DummyView
    >>> from contentratings.interfaces import IRatingManager
    >>> ztapi.provideAdapter((IRatingManager, dict), Interface,
    ...                      DummyView, name=u'test')

Note that a rating view should generally be registered for a specific
rating type (e.g. IUserRating) not for IRatingManager generally.

Now when we call our aggregator view, we get a different result::

    >>> view()
    u'<div class="UserRatings">DummyView on: My Category (rating1)</div>'

Our dummy view checks the categories read condition.  If it is not
met, then it returns None, which is a signal to the aggregator that
the category should be ignored.  Let's see this in action::

    >>> category.read_expr = 'python:False'
    >>> view()
    u''

Let's make another category to see some actual aggregation::

    >>> category2 = RatingsCategoryFactory(u"My Other Category", order=200,
    ...                                    name='rating2', view_name='test')
    >>> ztapi.provideAdapter((Interface,), IUserRating, category2,
    ...                      name='rating2')
    >>> view()
    u'<div class="UserRatings">DummyView on: My Other Category (rating2)</div>'

Let's re-enable our original category::

    >>> category.read_expr = None
    >>> view()
    u'<div class="UserRatings">DummyView on: My Category (rating1)\nDummyView on: My Other Category (rating2)</div>'

The aggregator respects the ordering of categories::

    >>> category2.order = 99
    >>> view()
    u'<div class="UserRatings">DummyView on: My Other Category (rating2)\nDummyView on: My Category (rating1)</div>'

Additionally, we can subclass this aggregator so that it works for
other rating interfaces, and provides a different class name::

    >>> from contentratings.interfaces import IEditorialRating
    >>> class EditorialAggregator(UserRatingAggregatorView):
    ...     RATING_IFACE = IEditorialRating
    ...     CLASS_NAME = 'EditorialRatings'
    >>> view2 = EditorialAggregator(my_container, request)

We have no categories with the specified interface, so we get nothing::

    >>> view2()
    u''

But we can make one::

    >>> from contentratings.storage import EditorialRatingStorage
    >>> editorial = RatingsCategoryFactory(u"My Editorial Category",
    ...                                    name=u'rating1', view_name=u'test',
    ...                                    storage=EditorialRatingStorage)
    >>> ztapi.provideAdapter((Interface,), IEditorialRating, editorial,
    ...                      name=u'rating1')
    >>> view2()
    u'<div class="EditorialRatings">DummyView on: My Editorial Category (rating1)</div>'
