# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

from twisted.trial.unittest import TestCase
import random
import pgm

from elisa.core.utils import defer
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.graph.group import Group
from elisa.plugins.pigment.graph.group import NodeNotInGroup
from elisa.plugins.pigment.tests.graph.test_node import GenericTestNode

class TestGroup(TestCase):

    def test_layer_at_instatiation(self):
        g = Group()

        # MIDDLE layer is the default
        self.assertEqual(g.layer, pgm.DRAWABLE_MIDDLE)

    def test_layer_change_outside_canvas(self):
        g = Group()
        g.layer = pgm.DRAWABLE_FAR

        # insertion in a canvas
        canvas = pgm.Canvas()
        g.canvas = canvas

        # MIDDLE layer is the default
        self.assertEqual(g.layer, pgm.DRAWABLE_FAR)

    def test_canvas_insertion(self):
        g = Group()
        canvas = pgm.Canvas()

        # empty group
        g.canvas = canvas
        self.assertEqual(g.canvas, canvas)
        g.canvas = None
        self.assertEqual(g.canvas, None)

        # group with an Image and a Text
        i = Image()
        t = Text()
        g.add(i)
        g.canvas = canvas
        g.add(t)
        self.assertEqual(g.canvas, canvas)
        self.assertEqual(canvas.get_order(i)[0], g.layer)
        self.assertEqual(canvas.get_order(t)[0], g.layer)

    def test_canvas_change(self):
        # one group with an image and a text
        g = Group()
        i = Image()
        t = Text()
        g.add(i)
        g.add(t)

        # two distinct canvas
        canvas1 = pgm.Canvas()
        canvas2 = pgm.Canvas()

        # insertion in first canvas
        g.canvas = canvas1
        self.assertEqual(g.canvas, canvas1)
        self.assertEqual(i.get_parent(), canvas1)
        self.assertEqual(t.get_parent(), canvas1)
        self.assertEqual(canvas1.get_order(i)[0], g.layer)
        self.assertEqual(canvas1.get_order(t)[0], g.layer)

        # insertion in second canvas
        g.canvas = canvas2
        self.assertEqual(g.canvas, canvas2)
        self.assertEqual(i.get_parent(), canvas2)
        self.assertEqual(t.get_parent(), canvas2)
        self.assertEqual(canvas2.get_order(i)[0], g.layer)
        self.assertEqual(canvas2.get_order(t)[0], g.layer)

    def test_layer_change(self):
        # one group with an image and a text
        g = Group()
        i = Image()
        t = Text()
        g.add(i)
        g.add(t)

        canvas = pgm.Canvas()
        g.canvas = canvas

        # default layer
        layer = g.layer
        self.assertEqual(canvas.get_order(i)[0], layer)
        self.assertEqual(canvas.get_order(t)[0], layer)

        # switch to FAR layer
        layer = pgm.DRAWABLE_FAR
        g.layer = layer
        self.assertEqual(canvas.get_order(i)[0], layer)
        self.assertEqual(canvas.get_order(t)[0], layer)

        # switch to NEAR layer
        layer = pgm.DRAWABLE_NEAR
        g.layer = layer
        self.assertEqual(canvas.get_order(i)[0], layer)
        self.assertEqual(canvas.get_order(t)[0], layer)

    def test_add_with_layer(self):
        g = Group()
        g.layer = pgm.DRAWABLE_MIDDLE

        child_group = Group()
        g.add(child_group, layer=pgm.DRAWABLE_NEAR)
        self.assertEqual(child_group.layer, pgm.DRAWABLE_NEAR)

        g.remove(child_group)
        self.assertEqual(child_group.layer, pgm.DRAWABLE_NEAR)

    def test_add_remove_drawables(self):
        drawable_classes = [Image, Text]
        drawables = []
        g = Group()
        canvas = pgm.Canvas()
        g.canvas = canvas

        self.assertEqual(len(g), 0)

        for i in xrange(10):
            Class = random.choice(drawable_classes)
            drawable = Class()
            drawables.append(drawable)
            g.add(drawable)
            self.assertEqual(canvas.get_order(drawable)[0], g.layer)
            self.assertEqual(len(g), i+1)

        for drawable in drawables:
            length = len(g)
            g.remove(drawable)
            self.assertEqual(canvas.get_order(drawable)[0],
                             pgm.DRAWABLE_UNBOUND)
            self.assertEqual(len(g), length-1)

        self.assertEqual(len(g), 0)

    test_add_remove_drawables.todo = "Known issue in pigment: order not reset" \
            " after removing drawable from a canvas."

    def test_add_remove_groups(self):
        g = Group()
        groups = []
        canvas = pgm.Canvas()
        g.canvas = canvas

        self.assertEqual(len(g), 0)

        for i in xrange(10):
            child_group = Group()
            groups.append(child_group)
            g.add(child_group)
            self.assertEqual(child_group.canvas, g.canvas)
            self.assertEqual(child_group.layer, g.layer)
            self.assertEqual(len(g), i+1)

        for child_group in groups:
            length = len(g)
            g.remove(child_group)
            self.assertEqual(child_group.canvas, None)
            self.assertEqual(len(g), length-1)

        self.assertEqual(len(g), 0)

    def test_add_remove_without_canvas(self):
        parent = Group()
        child_group = Group()
        child_image = Image()
        parent.add(child_image)
        parent.add(child_group)
        parent.remove(child_image)
        parent.remove(child_group)

    def test_non_child_removal(self):
        parent = Group()
        child_group = Group()
        child_image = Image()
        self.failUnlessRaises(NodeNotInGroup, parent.remove, child_image)
        self.failUnlessRaises(NodeNotInGroup, parent.remove, child_group)

    def test_empty(self):
        children_classes = [Image, Text, Group]

        g = Group()
        canvas = pgm.Canvas()
        g.canvas = canvas

        self.assertEqual(len(g), 0)

        for i in xrange(10):
            Class = random.choice(children_classes)
            g.add(Class())

        self.assertEqual(len(g), 10)
        g.empty()
        self.assertEqual(len(g), 0)

    def test_add_forward_signals(self):
        self.fail(NotImplementedError("Test not yet implemented"))

    test_add_forward_signals.todo = "Test not yet implemented"

    def test_parent(self):
        self.fail(NotImplementedError("Test not yet implemented"))

    test_parent.todo = "Test not yet implemented"

    def test_simple_visibility(self):
        g = Group()
        g.visible = True
        self.assertEqual(g.visible, True)
        g.visible = False
        self.assertEqual(g.visible, False)

    def test_nested_groups_visibility(self):
        # one level nesting of groups
        parent = Group()
        child = Group()
        parent.add(child)

        # parent visible
        parent.visible = True
        self.assertEqual(parent.visible, True)

        child.visible = True
        self.assertEqual(child.visible, True)
        child.visible = False
        self.assertEqual(child.visible, False)

        # parent invisible
        parent.visible = False
        self.assertEqual(parent.visible, False)

        child.visible = True
        self.assertEqual(child.visible, True)
        child.visible = False
        self.assertEqual(child.visible, False)

    def test_nested_drawable_visibility(self):
        # one group with an image
        g = Group()
        i = Image()
        g.add(i)

        # group visible
        g.visible = True

        i.visible = True
        self.assertEqual(i.visible, True)
        self.assertEqual(i.is_visible(), True)

        i.visible = False
        self.assertEqual(i.visible, False)
        self.assertEqual(i.is_visible(), False)

        # group invisible
        g.visible = False

        i.visible = True
        self.assertEqual(i.visible, True)
        real_visible = i.is_visible()
        self.assertEqual(real_visible, False)

        i.visible = False
        self.assertEqual(i.visible, False)
        real_visible = i.is_visible()
        self.assertEqual(real_visible, False)

    def test_addremove_drawable_visibility(self):
        # a group and an image
        g = Group()
        i = Image()

        # default values
        self.assertEqual(g.visible, False)
        self.assertEqual(i.visible, False)
        self.assertEqual(i.is_visible(), False)

        # adding the image to the group
        g.add(i)

        self.assertEqual(i.visible, False)
        self.assertEqual(i.is_visible(), False)

        # removing the image from the group
        g.remove(i)

        self.assertEqual(i.visible, False)
        self.assertEqual(i.is_visible(), False)

        # readding the image to the group with different initial visibility
        g.visible = False
        i.visible = True

        self.assertEqual(g.visible, False)
        self.assertEqual(i.visible, True)
        self.assertEqual(i.is_visible(), True)

        g.add(i)

        self.assertEqual(i.visible, True)
        self.assertEqual(i.is_visible(), False)

        # removing the image from the group
        g.remove(i)

        self.assertEqual(i.visible, True)
        self.assertEqual(i.is_visible(), True)

        # readding the image to the group with different initial visibility
        g.visible = True
        i.visible = True

        self.assertEqual(g.visible, True)
        self.assertEqual(i.visible, True)
        self.assertEqual(i.is_visible(), True)

        g.add(i)

        self.assertEqual(i.visible, True)
        self.assertEqual(i.is_visible(), True)


    def test_simple_width(self):
        # default width for new groups
        width0 = 1.0

        # random absolute widths
        width1 = 2.0
        width2 = 2.3

        g = Group()
        self.assertEqual(g.width, width0)
        self.assertEqual(g.size[0], width0)

        g.width = width1
        self.assertEqual(g.width, width1)
        self.assertEqual(g.size[0], width1)

        g.width = width2
        self.assertEqual(g.width, width2)
        self.assertEqual(g.size[0], width2)

    def test_nested_groups_width(self):
        # default width for new groups and newly parented groups
        width0 = 1.0

        # random absolute width
        width1 = 2.3

        # random relative width
        rel_width1 = 0.2

        # one level nesting of groups
        parent = Group()
        self.assertEqual(parent.width, width0)
        self.assertEqual(parent.size[0], width0)
        parent.width = width1
        self.assertEqual(parent.width, width1)
        self.assertEqual(parent.size[0], width1)

        child = Group()
        self.assertEqual(child.width, width0)
        self.assertEqual(child.size[0], width0)
        child.width = width1
        self.assertEqual(child.width, width1)
        self.assertEqual(child.size[0], width1)

        # parenting preserves the value of the 'width' property while changing
        # its meaning from absolute to relative
        parent.add(child)
        self.assertEqual(child.width, width1)
        self.assertEqual(child.size[0], width1)
        self.assertNotEqual(child.absolute_width, width1)
        self.assertNotEqual(child.absolute_size[0], width1)

        child.width = rel_width1
        self.assertEqual(child.width, rel_width1)
        self.assertEqual(child.size[0], rel_width1)

    def test_nested_drawable_width(self):
        # default width for new groups and newly parented groups
        width0 = 1.0

        # random absolute width
        width1 = 2.3

        # random relative width
        rel_width1 = 0.2

        # one group with a child image
        g = Group()
        self.assertEqual(g.width, width0)
        self.assertEqual(g.size[0], width0)

        i = Image()
        g.add(i)
        self.assertEqual(i.width, width0)
        real_width = i.get_size()[0]
        self.assertEqual(real_width, width0)

        # resizing parent group
        g.width = width1
        self.assertEqual(g.width, width1)
        self.assertEqual(i.width, width0)
        real_width = i.get_size()[0]
        self.assertAlmostEqual(real_width, width1)

    def test_simple_x(self):
        # default x for new groups
        x0 = 0.0

        # random absolute xs
        x1 = 1.0
        x2 = 2.0

        g = Group()
        self.assertEqual(g.x, x0)

        g.x = x1
        self.assertEqual(g.x, x1)

        g.x = x2
        self.assertEqual(g.x, x2)

    def test_nested_groups_x(self):
        # default x for new groups and newly parented groups
        x0 = 0.0

        # random absolute x
        x1 = 1.0

        # random relative x
        rel_x1 = 0.2

        # one level nesting of groups
        parent = Group()
        self.assertEqual(parent.x, x0)
        parent.x = x1
        self.assertEqual(parent.x, x1)

        child = Group()
        self.assertEqual(child.x, x0)
        child.x = x1
        self.assertEqual(child.x, x1)

        # parenting preserves the value of the 'x' property while changing
        # its meaning from absolute to relative
        parent.add(child)
        self.assertEqual(child.x, x1)
        self.assertNotEqual(child.absolute_x, x1)

        child.x = rel_x1
        self.assertEqual(child.x, rel_x1)

    def test_one_level_nested_drawable_x(self):
        # default x for new groups and newly parented groups
        x0 = 0.0

        # random absolute x
        x1 = 1.0

        # random relative x
        rel_x1 = 0.2

        # one group with a child image
        g = Group()
        self.assertEqual(g.x, x0)

        i = Image()
        g.add(i)
        self.assertEqual(i.x, x0)
        real_x = i.get_position()[0]
        self.assertEqual(real_x, x0)

        # moving parent group
        g.x = x1
        self.assertEqual(g.x, x1)
        self.assertEqual(i.x, x0)
        real_x = i.get_position()[0]
        self.assertEqual(real_x, x1)

        # moving child image
        # within [0.0, 1.0]
        xs = [random.random() for a in xrange(5)]
        # and outside [0.0, 1.0]
        xs += [random.random()*10.0-5.0 for a in xrange(10)]

        for x in xs:
            i.x = x
            self.assertEqual(i.x, x)
            real_x = i.get_position()[0]
            self.assertAlmostEqual(real_x, g.x+x*g.width, 5)

        # at the limits of [0.0, 1.0]
        i.x = 0.0
        self.assertEqual(i.x, 0.0)
        real_x = i.get_position()[0]
        self.assertEqual(real_x, g.x)

        i.x = 1.0
        self.assertEqual(i.x, 1.0)
        real_x = i.get_position()[0]
        self.assertEqual(real_x, g.x+g.width)

    def test_two_levels_nested_drawable_x(self):
        # default x for new groups and newly parented groups
        x0 = 0.0

        # random absolute x
        x1 = 1.0

        # random relative x
        rel_x1 = 0.2

        # one group with a child image
        child = Group()
        image = Image()
        child.add(image)

        self.assertEqual(image.x, x0)
        real_x = image.get_position()[0]
        self.assertEqual(real_x, x0)

        # moving child group
        child.x = x1

        self.assertEqual(child.x, x1)
        real_x = image.get_position()[0]
        self.assertEqual(real_x, x1)

        # parent group containing child group
        # parenting preserves the value of the 'x' property while changing
        # its meaning from absolute to relative
        parent = Group()
        self.assertEqual(parent.x, x0)
        parent.x = x1
        self.assertEqual(parent.x, x1)

        parent.add(child)

        self.assertEqual(child.x, x1)
        self.assertNotEqual(child.absolute_x, x1)
        real_x = image.get_position()[0]
        self.assertEqual(real_x, child.x+parent.x)

        # moving parent group
        parent.x = x1

        self.assertEqual(parent.x, x1)
        real_x = image.get_position()[0]
        self.assertEqual(real_x, x1+child.x)

        # moving child group relatively to parent group
        child.x = rel_x1

        self.assertEqual(parent.x, x1)
        self.assertEqual(child.x, rel_x1)
        self.assertEqual(child.width, 1.0)
        self.assertEqual(image.x, 0.0)
        self.assertEqual(image.width, 1.0)

        child_absolute_width = parent.width*child.width
        child_absolute_x = parent.x+child.x*parent.width
        real_x = image.get_position()[0]
        self.assertAlmostEqual(real_x,
                               child_absolute_x+image.x*child_absolute_width)

    def test_simple_z(self):
        # default z for new groups
        z0 = 0.0

        # random absolute zs
        z1 = 1.0
        z2 = 2.0

        g = Group()
        self.assertEqual(g.z, z0)

        g.z = z1
        self.assertEqual(g.z, z1)

        g.z = z2
        self.assertEqual(g.z, z2)

    def test_simple_position(self):
        # default position for new groups
        position0 = (0.0, 0.0, 0.0)

        # random absolute positions
        position1 = (1.0, 4.0, 0.5)
        position2 = (2.0, 3.2, 5.6)

        g = Group()
        self.assertEqual(g.position, position0)

        g.position = position1
        self.assertEqual(g.position, position1)

        g.position = position2
        self.assertEqual(g.position, position2)

    def test_one_level_nested_drawable_position(self):
        # default position for new groups and newly parented groups
        position0 = (0.0, 0.0, 0.0)

        # random absolute position
        position1 = (1.0, 4.0, 0.5)

        # one group with a child image
        g = Group()
        self.assertEqual(g.position, position0)

        i = Image()
        g.add(i)
        self.assertEqual(i.position, position0)
        real_position = i.get_position()
        self.assertEqual(real_position, position0)

        # moving parent group
        g.position = position1
        self.assertEqual(g.position, position1)
        self.assertEqual(i.position, position0)
        real_position = i.get_position()
        self.assertEqual(real_position, position1)

        # moving child image
        positions = []
        # within [0.0, 1.0]
        for a in xrange(5):
            p = (random.random(), random.random(), random.random())
            positions.append(p)
        # and outside [0.0, 1.0]
        for a in xrange(10):
            p = (random.random()*10.0-5.0, random.random()*10.0-5.0,
                 random.random()*10.0-5.0)
            positions.append(p)

        for position in positions:
            i.position = position
            self.assertEqual(i.position, position)
            real_position = i.get_position()
            self.assertAlmostEqual(real_position[0], g.position[0]+position[0]*g.size[0], 5)
            self.assertAlmostEqual(real_position[1], g.position[1]+position[1]*g.size[1], 5)
            self.assertAlmostEqual(real_position[2], g.position[2]+position[2], 5)

        # at the limits of [0.0, 1.0]
        i.position = (0.0, 0.0, 0.0)
        self.assertEqual(i.position, (0.0, 0.0, 0.0))
        real_position = i.get_position()
        self.assertEqual(real_position, g.position)

        i.position = (1.0, 1.0, 1.0)
        self.assertEqual(i.position, (1.0, 1.0, 1.0))
        real_position = i.get_position()
        self.assertEqual(real_position[0], g.position[0]+g.size[0])
        self.assertEqual(real_position[1], g.position[1]+g.size[1])
        self.assertEqual(real_position[2], g.position[2]+1.0)

    def test_nested_groups_position(self):
        # default position for new groups and newly parented groups
        position0 = (0.0, 0.0, 0.0)

        # random absolute position
        position1 = (1.0, 4.0, 0.5)

        # random relative position
        rel_position1 = (0.2, 0.3, 0.9)

        # one level nesting of groups
        parent = Group()
        self.assertEqual(parent.position, position0)
        parent.position = position1
        self.assertEqual(parent.position, position1)

        child = Group()
        self.assertEqual(child.position, position0)
        child.position = position1
        self.assertEqual(child.position, position1)

        # parenting preserves the value of the 'position' property while changing
        # its meaning from absolute to relative
        parent.add(child)
        self.assertEqual(child.position, position1)
        self.assertNotEqual(child.absolute_position, position1)

        child.position = rel_position1
        self.assertEqual(child.position, rel_position1)

    def test_simple_size(self):
        # default size for new groups
        size0 = (1.0, 1.0)

        # random absolute sizes
        size1 = (2.0, 1.2)
        size2 = (5.1, 2.3)

        g = Group()
        self.assertEqual(g.size, size0)

        g.size = size1
        self.assertEqual(g.size, size1)

        g.size = size2
        self.assertEqual(g.size, size2)

    def test_nested_drawable_size(self):
        # default size for new groups and newly parented groups
        size0 = (1.0, 1.0)

        # random absolute size
        size1 = (0.1, 1.2)

        # one group with a child image
        g = Group()
        self.assertEqual(g.size, size0)

        i = Image()
        g.add(i)
        self.assertEqual(i.size, size0)
        real_size = i.get_size()
        self.assertEqual(real_size, size0)

        # resizing parent group
        g.size = size1
        self.assertEqual(g.size, size1)
        self.assertEqual(i.size, size0)
        real_size = i.get_size()
        self.assertAlmostEqual(real_size[0], size1[0])
        self.assertAlmostEqual(real_size[1], size1[1])

    def test_nested_groups_size(self):
        # default size for new groups and newly parented groups
        size0 = (1.0, 1.0)

        # random absolute size
        size1 = (0.1, 1.2)

        # random relative size
        rel_size1 = (0.2, 0.8)

        # one level nesting of groups
        parent = Group()
        self.assertEqual(parent.size, size0)
        parent.size = size1
        self.assertEqual(parent.size, size1)

        child = Group()
        self.assertEqual(child.size, size0)
        child.size = size1
        self.assertEqual(child.size, size1)

        # parenting preserves the value of the 'size' property while changing
        # its meaning from absolute to relative
        parent.add(child)
        self.assertEqual(child.size, size1)
        self.assertNotEqual(child.absolute_size, size1)

        child.size = rel_size1
        self.assertEqual(child.size, rel_size1)

    def test_resized_signal(self):
        g = Group()

        def resized_callback(group, previous_width, previous_height):
            self.emitted = True
            width, height = self.previous_size
            self.assertEqual(previous_width, width)
            self.assertEqual(previous_height, height)

        id = g.connect("resized", resized_callback)

        self.emitted = False
        self.previous_size = g.absolute_size
        g.size = (1.0, 2.0)
        self.assertEqual(self.emitted, True)

        self.emitted = False
        self.previous_size = g.absolute_size
        g.width = 2.0
        self.assertEqual(self.emitted, True)

        self.emitted = False
        self.previous_size = g.absolute_size
        g.height = 1.5
        self.assertEqual(self.emitted, True)

        self.emitted = False
        g.disconnect(id)

    def test_nested_resized_signal(self):
        parent = Group()
        parent.absolute_size = (4.0, 1.0)

        child = Group()
        parent.add(child)

        def resized_callback(group, previous_width, previous_height):
            self.emitted = True
            width, height = self.previous_size
            self.assertEqual(previous_width, width)
            self.assertEqual(previous_height, height)

        id = child.connect("resized", resized_callback)

        # change size of child
        self.emitted = False
        self.previous_size = child.absolute_size
        child.size = (1.0, 2.0)
        self.assertEqual(self.emitted, True)

        self.emitted = False
        self.previous_size = child.absolute_size
        child.width = 2.0
        self.assertEqual(self.emitted, True)

        self.emitted = False
        self.previous_size = child.absolute_size
        child.height = 1.5
        self.assertEqual(self.emitted, True)

        # change size of parent
        self.emitted = False
        self.previous_size = child.absolute_size
        parent.size = (1.0, 2.0)
        self.assertEqual(self.emitted, True)

        self.emitted = False
        self.previous_size = child.absolute_size
        parent.width = 2.0
        self.assertEqual(self.emitted, True)

        self.emitted = False
        self.previous_size = child.absolute_size
        parent.height = 1.5
        self.assertEqual(self.emitted, True)

        self.emitted = False
        child.disconnect(id)

    def test_clean_remove_all(self):
        group = Group()
        for i in xrange(100):
            group.add(Text())

        group.clean()
        self.assertEqual(group._children, [])

class GenericTestGroup(GenericTestNode):
    """
    These are tests that all classes inheriting from Group should pass.
    You should reimplement this class for anything that inherits from Group.
    """

    # FIXME: copy/pasted from test_widget. That piece of code would be
    # more suited somewhere in elisa.core
    def signal_connect_after(self, emitter, signal, callback, *args, **kwargs):
        """
        Connect L{callback} to L{signal} emitted by L{emitter} object and
        return a deferred that will fail if the callback raises an exception
        and succeed otherwise.
        L{callback} is added to the signal handler list after the default class
        signal handler; see gobject.GObject.connect_object_after.
        Disconnection is performed automatically when the signal has been
        emitted once.
        """
        dfr = defer.Deferred()
        id = None
        def callback_wrapper(*args, **kwargs):
            emitter.disconnect(id)
            try:
                callback(*args, **kwargs)
            except Exception, e:
                dfr.errback(e)
            else:
                dfr.callback(None)

        id = emitter.connect_after(signal, callback_wrapper, *args, **kwargs)
        return dfr


    def test_not_mapped(self):
        """ Check the mapped signal is not emitted for group not in a
        Canvas and not visible.
        """
        self._mapped = False

        def mapped(group):
            self._mapped = True

        g = Group()
        g.connect_after('mapped', mapped)
        self.assertFalse(self._mapped)
        self.assertFalse(g.is_mapped)

    def test_mapped_after_canvas_set(self):
        """ Check the mapped signal closure is executed when the
        canvas of a group is set after its visible property.
        """
        self._mapped = False

        def mapped(group):
            self._mapped = True

        g = Group()
        g.connect_after('mapped', mapped)
        g.visible = True
        self.assertFalse(g.is_mapped)

        g.canvas = pgm.Canvas()
        self.assertTrue(self._mapped)
        self.assertTrue(g.is_mapped)

    def test_mapped_after_visible_set(self):
        """ Check the mapped signal closure is executed when the
        canvas of a group is set, followed by its visible property.
        """
        self._mapped = False

        def mapped(group):
            self._mapped = True

        g = Group()
        g.absolute_visible = False
        self.assertFalse(g.is_mapped)

        g.connect_after('mapped', mapped)
        g.canvas = pgm.Canvas()
        self.assertFalse(g.is_mapped)

        g.visible = True
        self.assertTrue(self._mapped)
        self.assertTrue(g.is_mapped)

    def test_mapped_on_invisible_children(self):
        """ Check that if a group contains an invisible group, the
        child won't emit the mapped signal if it's not visible yet.
        """
        g = Group()
        g2 = Group()
        g.add(g2)
        canvas = pgm.Canvas()
        g.visible = True
        g.canvas = canvas

        self.assertTrue(g.is_mapped)
        # g2 is not mapped because its absolute_visible property is
        # False
        self.assertFalse(g2.is_mapped)
        self.assertFalse(g2.absolute_visible)

    def test_mapped_on_visible_children(self):
        """ Check that nested visible groups are mapped is the canvas
        of the parent has been set.
        """
        g = Group()
        g2 = Group()
        g.add(g2)
        g2.visible = True
        canvas = pgm.Canvas()
        g.visible = True
        g.canvas = canvas

        self.assertTrue(g.is_mapped)
        self.assertTrue(g2.is_mapped)

    def test_mapped_emission_order(self):
        """ Test the order of the mapped signal emission. In case of
        nested groups it should be sent from bottom to top of the
        tree.
        """
        self._g2_mapped = False
        self._g_mapped = False

        g = Group()
        g2 = Group()

        def g_mapped(group):
            self.assertTrue(self._g2_mapped)
            self._g_mapped = True

        def g2_mapped(group):
            self.assertFalse(self._g_mapped)
            self._g2_mapped = True

        dfr_g = self.signal_connect_after(g, 'mapped', g_mapped)
        dfr_g2 = self.signal_connect_after(g2, 'mapped', g2_mapped)

        g.add(g2)
        g2.visible = True
        canvas = pgm.Canvas()
        g.visible = True
        g.canvas = canvas

        self.assertTrue(g.is_mapped)
        self.assertTrue(g2.is_mapped)
        return defer.DeferredList([dfr_g, dfr_g2])


class GenericTestGroupImpl(GenericTestGroup, TestCase):
    tested_class = Group

