''' Bubble unit tests ================= author: Anthony Zimmermann (Anthony.Zimmermann@protonmail.com) ''' import pytest from kivy.tests.common import GraphicUnitTest from kivy.base import EventLoop from kivy.uix.bubble import Bubble from kivy.uix.bubble import BubbleContent from kivy.uix.bubble import BubbleButton class _TestBubbleButton(BubbleButton): def __init__(self, button_size=(None, None), *args, **kwargs): super().__init__(*args, **kwargs) size_x, size_y = button_size if size_x is not None: self.size_hint_x = None self.width = size_x if size_y is not None: self.size_hint_y = None self.height = size_y class _TestBubbleContent(BubbleContent): def update_size(self, instance, value): self.size = self.minimum_size def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.bind(minimum_size=self.update_size) class _TestBubble(Bubble): @property def arrow_length(self): return self._arrow_image.height @property def arrow_width(self): return self._arrow_image.width @property def arrow_rotation(self): return self._arrow_image_scatter.rotation @property def arrow_layout_pos(self): return self._arrow_image_layout.pos @property def arrow_layout_size(self): return self._arrow_image_layout.size @property def arrow_center_pos_within_arrow_layout(self): x = self._arrow_image_scatter_wrapper.center_x y = self._arrow_image_scatter_wrapper.center_y return x, y # (bubble_width, button_height, arrow_pos) bubble_layout_with_predefined_arrow_pos_test_params = [ (158.9, 34.3, "bottom_left"), # noqa: E201,E241 (651.4, 26.1, "bottom_mid"), # noqa: E201,E241 ( 6.5, 44.7, "bottom_right"), # noqa: E201,E241 (754.6, 50.6, "top_left"), # noqa: E201,E241 (957.8, 74.1, "top_mid"), # noqa: E201,E241 (852.1, 33.1, "top_right"), # noqa: E201,E241 (472.9, 45.1, "left_top"), # noqa: E201,E241 (578.3, 52.7, "left_mid"), # noqa: E201,E241 (687.8, 17.7, "left_bottom"), # noqa: E201,E241 (313.7, 8.6, "right_top"), # noqa: E201,E241 (194.3, 46.4, "right_mid"), # noqa: E201,E241 ( 59.3, 29.7, "right_bottom"), # noqa: E201,E241 ] bubble_layout_with_flex_arrow_pos_test_params = [ (101.3, 346.0, 0.0, 73.6, "l"), # noqa: E201,E241 (489.0, 535.1, 0.0, 442.1, "l"), # noqa: E201,E241 (390.9, 728.1, 0.0, 114.3, "l"), # noqa: E201,E241 (224.5, 675.5, 0.0, 560.6, "l"), # noqa: E201,E241 (264.9, 677.3, 0.0, 8.3, "l"), # noqa: E201,E241 (544.6, 126.0, 0.0, 120.9, "l"), # noqa: E201,E241 (290.9, 962.6, 0.0, 275.5, "l"), # noqa: E201,E241 (358.4, 514.4, 0.0, 427.1, "l"), # noqa: E201,E241 (604.3, 648.2, 0.0, 226.1, "l"), # noqa: E201,E241 (287.4, 875.6, 0.0, 446.5, "l"), # noqa: E201,E241 (755.7, 103.5, 444.6, 0.0, "b"), # noqa: E201,E241 (307.9, 471.7, 80.9, 0.0, "b"), # noqa: E201,E241 (849.9, 194.8, 652.7, 0.0, "b"), # noqa: E201,E241 (975.7, 691.0, 120.9, 0.0, "b"), # noqa: E201,E241 (539.1, 903.3, 530.6, 0.0, "b"), # noqa: E201,E241 ( 37.5, 727.5, 37.0, 0.0, "b"), # noqa: E201,E241 (856.5, 779.0, 565.5, 0.0, "b"), # noqa: E201,E241 (536.7, 228.3, 48.4, 0.0, "b"), # noqa: E201,E241 (170.9, 870.4, 127.6, 0.0, "b"), # noqa: E201,E241 (955.7, 530.6, 526.0, 0.0, "b"), # noqa: E201,E241 (878.1, 690.4, 878.1, 18.8, "r"), # noqa: E201,E241 (771.6, 365.2, 771.6, 31.1, "r"), # noqa: E201,E241 (679.7, 305.4, 679.7, 259.6, "r"), # noqa: E201,E241 (700.2, 614.6, 700.2, 105.4, "r"), # noqa: E201,E241 (444.1, 864.5, 444.1, 152.3, "r"), # noqa: E201,E241 (189.0, 790.4, 189.0, 602.9, "r"), # noqa: E201,E241 (376.0, 993.9, 376.0, 486.4, "r"), # noqa: E201,E241 (518.5, 338.5, 518.5, 194.6, "r"), # noqa: E201,E241 (982.1, 666.1, 982.1, 282.5, "r"), # noqa: E201,E241 (926.4, 565.1, 926.4, 187.3, "r"), # noqa: E201,E241 (375.2, 746.6, 36.2, 746.6, "t"), # noqa: E201,E241 (448.9, 228.5, 297.4, 228.5, "t"), # noqa: E201,E241 (792.3, 593.5, 746.2, 593.5, "t"), # noqa: E201,E241 (856.1, 89.7, 23.1, 89.7, "t"), # noqa: E201,E241 (721.3, 319.0, 356.5, 319.0, "t"), # noqa: E201,E241 (127.7, 355.7, 69.3, 355.7, "t"), # noqa: E201,E241 (412.3, 493.8, 163.2, 493.8, "t"), # noqa: E201,E241 ( 40.8, 115.8, 15.8, 115.8, "t"), # noqa: E201,E241 (233.9, 148.5, 189.4, 148.5, "t"), # noqa: E201,E241 (982.4, 661.5, 105.9, 661.5, "t"), # noqa: E201,E241 ] class BubbleTest(GraphicUnitTest): def move_frames(self, t): for i in range(t): EventLoop.idle() def test_no_content(self): bubble = Bubble() self.render(bubble) def test_add_remove_content(self): bubble = Bubble() content = BubbleContent() bubble.add_widget(content) self.render(bubble) bubble.remove_widget(content) self.render(bubble) def test_add_arbitrary_content(self): from kivy.uix.gridlayout import GridLayout bubble = Bubble() content = GridLayout() bubble.add_widget(content) self.render(bubble) def test_add_two_content_widgets_fails(self): from kivy.uix.bubble import BubbleException bubble = Bubble() content_1 = BubbleContent() content_2 = BubbleContent() bubble.add_widget(content_1) with self.assertRaises(BubbleException): bubble.add_widget(content_2) def test_add_content_with_buttons(self): bubble = Bubble() content = BubbleContent() content.add_widget(BubbleButton(text="Option A")) content.add_widget(BubbleButton(text="Option B")) bubble.add_widget(content) self.render(bubble) def assertSequenceAlmostEqual(self, seq1, seq2, delta=None): assert len(seq1) == len(seq2) for a, b in zip(seq1, seq2): self.assertAlmostEqual(a, b, delta=delta) def assertTestBubbleLayoutWithPredefinedArrowPos(self, bubble): arrow_length = bubble.arrow_length arrow_width = bubble.arrow_width bubble_width = bubble.test_bubble_width button_height = bubble.test_button_height # assert content size expected_content_size = { "bottom_left": (bubble_width, button_height), "bottom_mid": (bubble_width, button_height), "bottom_right": (bubble_width, button_height), "top_left": (bubble_width, button_height), "top_mid": (bubble_width, button_height), "top_right": (bubble_width, button_height), "left_top": (bubble_width - arrow_length, button_height), "left_mid": (bubble_width - arrow_length, button_height), "left_bottom": (bubble_width - arrow_length, button_height), "right_top": (bubble_width - arrow_length, button_height), "right_mid": (bubble_width - arrow_length, button_height), "right_bottom": (bubble_width - arrow_length, button_height), }[bubble.arrow_pos] self.assertSequenceAlmostEqual( bubble.content.size, expected_content_size, ) # assert arrow layout size expected_arrow_layout_size = { "bottom_left": (bubble_width, arrow_length), "bottom_mid": (bubble_width, arrow_length), "bottom_right": (bubble_width, arrow_length), "top_left": (bubble_width, arrow_length), "top_mid": (bubble_width, arrow_length), "top_right": (bubble_width, arrow_length), "left_top": (arrow_length, button_height), "left_mid": (arrow_length, button_height), "left_bottom": (arrow_length, button_height), "right_top": (arrow_length, button_height), "right_mid": (arrow_length, button_height), "right_bottom": (arrow_length, button_height), }[bubble.arrow_pos] self.assertSequenceAlmostEqual( bubble.arrow_layout_size, expected_arrow_layout_size, ) # assert content position expected_content_position = { "bottom_left": (0, arrow_length), "bottom_mid": (0, arrow_length), "bottom_right": (0, arrow_length), "top_left": (0, 0), "top_mid": (0, 0), "top_right": (0, 0), "left_top": (arrow_length, 0), "left_mid": (arrow_length, 0), "left_bottom": (arrow_length, 0), "right_top": (0, 0), "right_mid": (0, 0), "right_bottom": (0, 0), }[bubble.arrow_pos] self.assertSequenceAlmostEqual( bubble.content.pos, expected_content_position, ) # assert arrow layout position expected_arrow_layout_position = { "bottom_left": (0, 0), "bottom_mid": (0, 0), "bottom_right": (0, 0), "top_left": (0, button_height), "top_mid": (0, button_height), "top_right": (0, button_height), "left_top": (0, 0), "left_mid": (0, 0), "left_bottom": (0, 0), "right_top": (bubble_width - arrow_length, 0), "right_mid": (bubble_width - arrow_length, 0), "right_bottom": (bubble_width - arrow_length, 0), }[bubble.arrow_pos] self.assertSequenceAlmostEqual( bubble.arrow_layout_pos, expected_arrow_layout_position, ) # assert arrow position within arrow layout hal = arrow_length / 2 # hal := half arrow length x_offset = 0.05 * bubble_width y_offset = 0.05 * button_height expected_arrow_center_pos_within_arrow_layout = { "bottom_left": (x_offset + arrow_width / 2, hal), "bottom_mid": (bubble_width / 2, hal), "bottom_right": (bubble_width - arrow_width / 2 - x_offset, hal), "top_left": (x_offset + arrow_width / 2, hal), "top_mid": (bubble_width / 2, hal), "top_right": (bubble_width - arrow_width / 2 - x_offset, hal), "left_top": (hal, button_height - arrow_width / 2 - y_offset), "left_mid": (hal, button_height / 2), "left_bottom": (hal, y_offset + arrow_width / 2), "right_top": (hal, button_height - arrow_width / 2 - y_offset), "right_mid": (hal, button_height / 2), "right_bottom": (hal, y_offset + arrow_width / 2), }[bubble.arrow_pos] self.assertSequenceAlmostEqual( bubble.arrow_center_pos_within_arrow_layout, expected_arrow_center_pos_within_arrow_layout, ) # assert arrow rotation expected_arrow_rotation = { "bottom_left": 0, "bottom_mid": 0, "bottom_right": 0, "top_left": 180, "top_mid": 180, "top_right": 180, "left_top": 270, "left_mid": 270, "left_bottom": 270, "right_top": 90, "right_mid": 90, "right_bottom": 90, }[bubble.arrow_pos] self.assertAlmostEqual( bubble.arrow_rotation, expected_arrow_rotation, ) def test_bubble_layout_with_predefined_arrow_pos(self): for params in bubble_layout_with_predefined_arrow_pos_test_params: bubble_width, button_height, arrow_pos = params with self.subTest(): print( "(bubble_width={}, button_height={}, arrow_pos={})".format( *params ) ) bubble = _TestBubble(arrow_pos=arrow_pos) bubble.size_hint = (None, None) bubble.test_bubble_width = bubble_width bubble.test_button_height = button_height def update_bubble_size(instance, value): w = bubble_width h = bubble.content_height + bubble.arrow_margin_y bubble.size = (w, h) bubble.bind( content_size=update_bubble_size, arrow_margin=update_bubble_size, ) content = _TestBubbleContent() for i in range(3): content.add_widget( _TestBubbleButton( button_size=(None, button_height), text="Option {}".format(i) ) ) bubble.add_widget(content) self.render(bubble) self.assertTestBubbleLayoutWithPredefinedArrowPos(bubble) def test_bubble_layout_without_arrow(self): bubble_width = 200 button_height = 30 bubble = _TestBubble(show_arrow=False) bubble.size_hint = (None, None) def update_bubble_size(instance, value): w = bubble_width h = bubble.content_height bubble.size = (w, h) bubble.bind(content_size=update_bubble_size) content = _TestBubbleContent(orientation="vertical") for i in range(7): content.add_widget( _TestBubbleButton( button_size=(None, button_height), text="Option_{}".format(i) ) ) bubble.add_widget(content) self.render(bubble) # assert content size self.assertSequenceAlmostEqual( bubble.content.size, (bubble_width, 7 * button_height), ) # assert content position self.assertSequenceAlmostEqual( bubble.content.pos, (0, 0), ) def test_bubble_layout_with_flex_arrow_pos(self): for params in bubble_layout_with_flex_arrow_pos_test_params: bubble_size = params[:2] flex_arrow_pos = params[2:4] arrow_side = params[4] with self.subTest(): print("(w={}, h={}, x={}, y={}, side={})".format(*params)) bubble = _TestBubble() bubble.size_hint = (None, None) bubble.size = bubble_size bubble.flex_arrow_pos = flex_arrow_pos content = _TestBubbleContent(orientation="vertical") content.size_hint = (1, 1) button = _TestBubbleButton( button_size=(None, None), text="Option", ) button.size_hint_y = 1 content.add_widget(button) bubble.add_widget(content) self.render(bubble) haw = bubble.arrow_width / 2 # half arrow_width if arrow_side in ["l", "r"]: self.assertSequenceAlmostEqual( bubble.arrow_center_pos_within_arrow_layout, (haw, flex_arrow_pos[1]), delta=haw, ) elif arrow_side in ["b", "t"]: self.assertSequenceAlmostEqual( bubble.arrow_center_pos_within_arrow_layout, (flex_arrow_pos[0], haw), delta=haw, )