1import math
2import sys
3import unittest
4import platform
5
6from pygame import Rect, Vector2, get_sdl_version
7from pygame.tests import test_utils
8
9
10PY3 = sys.version_info >= (3, 0, 0)
11SDL1 = get_sdl_version()[0] < 2
12IS_PYPY = "PyPy" == platform.python_implementation()
13
14
15class RectTypeTest(unittest.TestCase):
16    def _assertCountEqual(self, *args, **kwargs):
17        # Handle method name differences between Python versions.
18        if PY3:
19            self.assertCountEqual(*args, **kwargs)
20        else:
21            self.assertItemsEqual(*args, **kwargs)
22
23    def testConstructionXYWidthHeight(self):
24        r = Rect(1, 2, 3, 4)
25        self.assertEqual(1, r.left)
26        self.assertEqual(2, r.top)
27        self.assertEqual(3, r.width)
28        self.assertEqual(4, r.height)
29
30    def testConstructionTopLeftSize(self):
31        r = Rect((1, 2), (3, 4))
32        self.assertEqual(1, r.left)
33        self.assertEqual(2, r.top)
34        self.assertEqual(3, r.width)
35        self.assertEqual(4, r.height)
36
37    def testCalculatedAttributes(self):
38        r = Rect(1, 2, 3, 4)
39
40        self.assertEqual(r.left + r.width, r.right)
41        self.assertEqual(r.top + r.height, r.bottom)
42        self.assertEqual((r.width, r.height), r.size)
43        self.assertEqual((r.left, r.top), r.topleft)
44        self.assertEqual((r.right, r.top), r.topright)
45        self.assertEqual((r.left, r.bottom), r.bottomleft)
46        self.assertEqual((r.right, r.bottom), r.bottomright)
47
48        midx = r.left + r.width // 2
49        midy = r.top + r.height // 2
50
51        self.assertEqual(midx, r.centerx)
52        self.assertEqual(midy, r.centery)
53        self.assertEqual((r.centerx, r.centery), r.center)
54        self.assertEqual((r.centerx, r.top), r.midtop)
55        self.assertEqual((r.centerx, r.bottom), r.midbottom)
56        self.assertEqual((r.left, r.centery), r.midleft)
57        self.assertEqual((r.right, r.centery), r.midright)
58
59    def test_normalize(self):
60        """Ensures normalize works when width and height are both negative."""
61        test_rect = Rect((1, 2), (-3, -6))
62        expected_normalized_rect = (
63            (test_rect.x + test_rect.w, test_rect.y + test_rect.h),
64            (-test_rect.w, -test_rect.h),
65        )
66
67        test_rect.normalize()
68
69        self.assertEqual(test_rect, expected_normalized_rect)
70
71    @unittest.skipIf(IS_PYPY, "fails on pypy sometimes")
72    def test_normalize__positive_height(self):
73        """Ensures normalize works with a negative width and a positive height.
74        """
75        test_rect = Rect((1, 2), (-3, 6))
76        expected_normalized_rect = (
77            (test_rect.x + test_rect.w, test_rect.y),
78            (-test_rect.w, test_rect.h),
79        )
80
81        test_rect.normalize()
82
83        self.assertEqual(test_rect, expected_normalized_rect)
84
85    @unittest.skipIf(IS_PYPY, "fails on pypy sometimes")
86    def test_normalize__positive_width(self):
87        """Ensures normalize works with a positive width and a negative height.
88        """
89        test_rect = Rect((1, 2), (3, -6))
90        expected_normalized_rect = (
91            (test_rect.x, test_rect.y + test_rect.h),
92            (test_rect.w, -test_rect.h),
93        )
94
95        test_rect.normalize()
96
97        self.assertEqual(test_rect, expected_normalized_rect)
98
99    @unittest.skipIf(IS_PYPY, "fails on pypy sometimes")
100    def test_normalize__zero_height(self):
101        """Ensures normalize works with a negative width and a zero height."""
102        test_rect = Rect((1, 2), (-3, 0))
103        expected_normalized_rect = (
104            (test_rect.x + test_rect.w, test_rect.y),
105            (-test_rect.w, test_rect.h),
106        )
107
108        test_rect.normalize()
109
110        self.assertEqual(test_rect, expected_normalized_rect)
111
112    @unittest.skipIf(IS_PYPY, "fails on pypy sometimes")
113    def test_normalize__zero_width(self):
114        """Ensures normalize works with a zero width and a negative height."""
115        test_rect = Rect((1, 2), (0, -6))
116        expected_normalized_rect = (
117            (test_rect.x, test_rect.y + test_rect.h),
118            (test_rect.w, -test_rect.h),
119        )
120
121        test_rect.normalize()
122
123        self.assertEqual(test_rect, expected_normalized_rect)
124
125    @unittest.skipIf(IS_PYPY, "fails on pypy")
126    def test_normalize__non_negative(self):
127        """Ensures normalize works when width and height are both non-negative.
128
129        Tests combinations of positive and zero values for width and height.
130        The normalize method has no impact when both width and height are
131        non-negative.
132        """
133        for size in ((3, 6), (3, 0), (0, 6), (0, 0)):
134            test_rect = Rect((1, 2), size)
135            expected_normalized_rect = Rect(test_rect)
136
137            test_rect.normalize()
138
139            self.assertEqual(test_rect, expected_normalized_rect)
140
141    def test_x(self):
142        """Ensures changing the x attribute moves the rect and does not change
143           the rect's size.
144        """
145        expected_x = 10
146        expected_y = 2
147        expected_size = (3, 4)
148        r = Rect((1, expected_y), expected_size)
149
150        r.x = expected_x
151
152        self.assertEqual(r.x, expected_x)
153        self.assertEqual(r.x, r.left)
154        self.assertEqual(r.y, expected_y)
155        self.assertEqual(r.size, expected_size)
156
157    def test_x__invalid_value(self):
158        """Ensures the x attribute handles invalid values correctly."""
159        r = Rect(0, 0, 1, 1)
160
161        for value in (None, [], "1", (1,), [1, 2, 3]):
162            with self.assertRaises(TypeError):
163                r.x = value
164
165    def test_x__del(self):
166        """Ensures the x attribute can't be deleted."""
167        r = Rect(0, 0, 1, 1)
168
169        with self.assertRaises(AttributeError):
170            del r.x
171
172    def test_y(self):
173        """Ensures changing the y attribute moves the rect and does not change
174           the rect's size.
175        """
176        expected_x = 1
177        expected_y = 20
178        expected_size = (3, 4)
179        r = Rect((expected_x, 2), expected_size)
180
181        r.y = expected_y
182
183        self.assertEqual(r.y, expected_y)
184        self.assertEqual(r.y, r.top)
185        self.assertEqual(r.x, expected_x)
186        self.assertEqual(r.size, expected_size)
187
188    def test_y__invalid_value(self):
189        """Ensures the y attribute handles invalid values correctly."""
190        r = Rect(0, 0, 1, 1)
191
192        for value in (None, [], "1", (1,), [1, 2, 3]):
193            with self.assertRaises(TypeError):
194                r.y = value
195
196    def test_y__del(self):
197        """Ensures the y attribute can't be deleted."""
198        r = Rect(0, 0, 1, 1)
199
200        with self.assertRaises(AttributeError):
201            del r.y
202
203    def test_left(self):
204        """Changing the left attribute moves the rect and does not change
205           the rect's width
206        """
207        r = Rect(1, 2, 3, 4)
208        new_left = 10
209
210        r.left = new_left
211        self.assertEqual(new_left, r.left)
212        self.assertEqual(Rect(new_left, 2, 3, 4), r)
213
214    def test_left__invalid_value(self):
215        """Ensures the left attribute handles invalid values correctly."""
216        r = Rect(0, 0, 1, 1)
217
218        for value in (None, [], "1", (1,), [1, 2, 3]):
219            with self.assertRaises(TypeError):
220                r.left = value
221
222    def test_left__del(self):
223        """Ensures the left attribute can't be deleted."""
224        r = Rect(0, 0, 1, 1)
225
226        with self.assertRaises(AttributeError):
227            del r.left
228
229    def test_right(self):
230        """Changing the right attribute moves the rect and does not change
231           the rect's width
232        """
233        r = Rect(1, 2, 3, 4)
234        new_right = r.right + 20
235        expected_left = r.left + 20
236        old_width = r.width
237
238        r.right = new_right
239        self.assertEqual(new_right, r.right)
240        self.assertEqual(expected_left, r.left)
241        self.assertEqual(old_width, r.width)
242
243    def test_right__invalid_value(self):
244        """Ensures the right attribute handles invalid values correctly."""
245        r = Rect(0, 0, 1, 1)
246
247        for value in (None, [], "1", (1,), [1, 2, 3]):
248            with self.assertRaises(TypeError):
249                r.right = value
250
251    def test_right__del(self):
252        """Ensures the right attribute can't be deleted."""
253        r = Rect(0, 0, 1, 1)
254
255        with self.assertRaises(AttributeError):
256            del r.right
257
258    def test_top(self):
259        """Changing the top attribute moves the rect and does not change
260           the rect's width
261        """
262        r = Rect(1, 2, 3, 4)
263        new_top = 10
264
265        r.top = new_top
266        self.assertEqual(Rect(1, new_top, 3, 4), r)
267        self.assertEqual(new_top, r.top)
268
269    def test_top__invalid_value(self):
270        """Ensures the top attribute handles invalid values correctly."""
271        r = Rect(0, 0, 1, 1)
272
273        for value in (None, [], "1", (1,), [1, 2, 3]):
274            with self.assertRaises(TypeError):
275                r.top = value
276
277    def test_top__del(self):
278        """Ensures the top attribute can't be deleted."""
279        r = Rect(0, 0, 1, 1)
280
281        with self.assertRaises(AttributeError):
282            del r.top
283
284    def test_bottom(self):
285        """Changing the bottom attribute moves the rect and does not change
286           the rect's height
287        """
288        r = Rect(1, 2, 3, 4)
289        new_bottom = r.bottom + 20
290        expected_top = r.top + 20
291        old_height = r.height
292
293        r.bottom = new_bottom
294        self.assertEqual(new_bottom, r.bottom)
295        self.assertEqual(expected_top, r.top)
296        self.assertEqual(old_height, r.height)
297
298    def test_bottom__invalid_value(self):
299        """Ensures the bottom attribute handles invalid values correctly."""
300        r = Rect(0, 0, 1, 1)
301
302        for value in (None, [], "1", (1,), [1, 2, 3]):
303            with self.assertRaises(TypeError):
304                r.bottom = value
305
306    def test_bottom__del(self):
307        """Ensures the bottom attribute can't be deleted."""
308        r = Rect(0, 0, 1, 1)
309
310        with self.assertRaises(AttributeError):
311            del r.bottom
312
313    def test_centerx(self):
314        """Changing the centerx attribute moves the rect and does not change
315           the rect's width
316        """
317        r = Rect(1, 2, 3, 4)
318        new_centerx = r.centerx + 20
319        expected_left = r.left + 20
320        old_width = r.width
321
322        r.centerx = new_centerx
323        self.assertEqual(new_centerx, r.centerx)
324        self.assertEqual(expected_left, r.left)
325        self.assertEqual(old_width, r.width)
326
327    def test_centerx__invalid_value(self):
328        """Ensures the centerx attribute handles invalid values correctly."""
329        r = Rect(0, 0, 1, 1)
330
331        for value in (None, [], "1", (1,), [1, 2, 3]):
332            with self.assertRaises(TypeError):
333                r.centerx = value
334
335    def test_centerx__del(self):
336        """Ensures the centerx attribute can't be deleted."""
337        r = Rect(0, 0, 1, 1)
338
339        with self.assertRaises(AttributeError):
340            del r.centerx
341
342    def test_centery(self):
343        """Changing the centery attribute moves the rect and does not change
344           the rect's width
345        """
346        r = Rect(1, 2, 3, 4)
347        new_centery = r.centery + 20
348        expected_top = r.top + 20
349        old_height = r.height
350
351        r.centery = new_centery
352        self.assertEqual(new_centery, r.centery)
353        self.assertEqual(expected_top, r.top)
354        self.assertEqual(old_height, r.height)
355
356    def test_centery__invalid_value(self):
357        """Ensures the centery attribute handles invalid values correctly."""
358        r = Rect(0, 0, 1, 1)
359
360        for value in (None, [], "1", (1,), [1, 2, 3]):
361            with self.assertRaises(TypeError):
362                r.centery = value
363
364    def test_centery__del(self):
365        """Ensures the centery attribute can't be deleted."""
366        r = Rect(0, 0, 1, 1)
367
368        with self.assertRaises(AttributeError):
369            del r.centery
370
371    def test_topleft(self):
372        """Changing the topleft attribute moves the rect and does not change
373           the rect's size
374        """
375        r = Rect(1, 2, 3, 4)
376        new_topleft = (r.left + 20, r.top + 30)
377        old_size = r.size
378
379        r.topleft = new_topleft
380        self.assertEqual(new_topleft, r.topleft)
381        self.assertEqual(old_size, r.size)
382
383    def test_topleft__invalid_value(self):
384        """Ensures the topleft attribute handles invalid values correctly."""
385        r = Rect(0, 0, 1, 1)
386
387        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
388            with self.assertRaises(TypeError):
389                r.topleft = value
390
391    def test_topleft__del(self):
392        """Ensures the topleft attribute can't be deleted."""
393        r = Rect(0, 0, 1, 1)
394
395        with self.assertRaises(AttributeError):
396            del r.topleft
397
398    def test_bottomleft(self):
399        """Changing the bottomleft attribute moves the rect and does not change
400           the rect's size
401        """
402        r = Rect(1, 2, 3, 4)
403        new_bottomleft = (r.left + 20, r.bottom + 30)
404        expected_topleft = (r.left + 20, r.top + 30)
405        old_size = r.size
406
407        r.bottomleft = new_bottomleft
408        self.assertEqual(new_bottomleft, r.bottomleft)
409        self.assertEqual(expected_topleft, r.topleft)
410        self.assertEqual(old_size, r.size)
411
412    def test_bottomleft__invalid_value(self):
413        """Ensures the bottomleft attribute handles invalid values correctly.
414        """
415        r = Rect(0, 0, 1, 1)
416
417        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
418            with self.assertRaises(TypeError):
419                r.bottomleft = value
420
421    def test_bottomleft__del(self):
422        """Ensures the bottomleft attribute can't be deleted."""
423        r = Rect(0, 0, 1, 1)
424
425        with self.assertRaises(AttributeError):
426            del r.bottomleft
427
428    def test_topright(self):
429        """Changing the topright attribute moves the rect and does not change
430           the rect's size
431        """
432        r = Rect(1, 2, 3, 4)
433        new_topright = (r.right + 20, r.top + 30)
434        expected_topleft = (r.left + 20, r.top + 30)
435        old_size = r.size
436
437        r.topright = new_topright
438        self.assertEqual(new_topright, r.topright)
439        self.assertEqual(expected_topleft, r.topleft)
440        self.assertEqual(old_size, r.size)
441
442    def test_topright__invalid_value(self):
443        """Ensures the topright attribute handles invalid values correctly."""
444        r = Rect(0, 0, 1, 1)
445
446        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
447            with self.assertRaises(TypeError):
448                r.topright = value
449
450    def test_topright__del(self):
451        """Ensures the topright attribute can't be deleted."""
452        r = Rect(0, 0, 1, 1)
453
454        with self.assertRaises(AttributeError):
455            del r.topright
456
457    def test_bottomright(self):
458        """Changing the bottomright attribute moves the rect and does not change
459           the rect's size
460        """
461        r = Rect(1, 2, 3, 4)
462        new_bottomright = (r.right + 20, r.bottom + 30)
463        expected_topleft = (r.left + 20, r.top + 30)
464        old_size = r.size
465
466        r.bottomright = new_bottomright
467        self.assertEqual(new_bottomright, r.bottomright)
468        self.assertEqual(expected_topleft, r.topleft)
469        self.assertEqual(old_size, r.size)
470
471    def test_bottomright__invalid_value(self):
472        """Ensures the bottomright attribute handles invalid values correctly.
473        """
474        r = Rect(0, 0, 1, 1)
475
476        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
477            with self.assertRaises(TypeError):
478                r.bottomright = value
479
480    def test_bottomright__del(self):
481        """Ensures the bottomright attribute can't be deleted."""
482        r = Rect(0, 0, 1, 1)
483
484        with self.assertRaises(AttributeError):
485            del r.bottomright
486
487    def test_center(self):
488        """Changing the center attribute moves the rect and does not change
489           the rect's size
490        """
491        r = Rect(1, 2, 3, 4)
492        new_center = (r.centerx + 20, r.centery + 30)
493        expected_topleft = (r.left + 20, r.top + 30)
494        old_size = r.size
495
496        r.center = new_center
497        self.assertEqual(new_center, r.center)
498        self.assertEqual(expected_topleft, r.topleft)
499        self.assertEqual(old_size, r.size)
500
501    def test_center__invalid_value(self):
502        """Ensures the center attribute handles invalid values correctly."""
503        r = Rect(0, 0, 1, 1)
504
505        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
506            with self.assertRaises(TypeError):
507                r.center = value
508
509    def test_center__del(self):
510        """Ensures the center attribute can't be deleted."""
511        r = Rect(0, 0, 1, 1)
512
513        with self.assertRaises(AttributeError):
514            del r.center
515
516    def test_midleft(self):
517        """Changing the midleft attribute moves the rect and does not change
518           the rect's size
519        """
520        r = Rect(1, 2, 3, 4)
521        new_midleft = (r.left + 20, r.centery + 30)
522        expected_topleft = (r.left + 20, r.top + 30)
523        old_size = r.size
524
525        r.midleft = new_midleft
526        self.assertEqual(new_midleft, r.midleft)
527        self.assertEqual(expected_topleft, r.topleft)
528        self.assertEqual(old_size, r.size)
529
530    def test_midleft__invalid_value(self):
531        """Ensures the midleft attribute handles invalid values correctly."""
532        r = Rect(0, 0, 1, 1)
533
534        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
535            with self.assertRaises(TypeError):
536                r.midleft = value
537
538    def test_midleft__del(self):
539        """Ensures the midleft attribute can't be deleted."""
540        r = Rect(0, 0, 1, 1)
541
542        with self.assertRaises(AttributeError):
543            del r.midleft
544
545    def test_midright(self):
546        """Changing the midright attribute moves the rect and does not change
547           the rect's size
548        """
549        r = Rect(1, 2, 3, 4)
550        new_midright = (r.right + 20, r.centery + 30)
551        expected_topleft = (r.left + 20, r.top + 30)
552        old_size = r.size
553
554        r.midright = new_midright
555        self.assertEqual(new_midright, r.midright)
556        self.assertEqual(expected_topleft, r.topleft)
557        self.assertEqual(old_size, r.size)
558
559    def test_midright__invalid_value(self):
560        """Ensures the midright attribute handles invalid values correctly."""
561        r = Rect(0, 0, 1, 1)
562
563        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
564            with self.assertRaises(TypeError):
565                r.midright = value
566
567    def test_midright__del(self):
568        """Ensures the midright attribute can't be deleted."""
569        r = Rect(0, 0, 1, 1)
570
571        with self.assertRaises(AttributeError):
572            del r.midright
573
574    def test_midtop(self):
575        """Changing the midtop attribute moves the rect and does not change
576           the rect's size
577        """
578        r = Rect(1, 2, 3, 4)
579        new_midtop = (r.centerx + 20, r.top + 30)
580        expected_topleft = (r.left + 20, r.top + 30)
581        old_size = r.size
582
583        r.midtop = new_midtop
584        self.assertEqual(new_midtop, r.midtop)
585        self.assertEqual(expected_topleft, r.topleft)
586        self.assertEqual(old_size, r.size)
587
588    def test_midtop__invalid_value(self):
589        """Ensures the midtop attribute handles invalid values correctly."""
590        r = Rect(0, 0, 1, 1)
591
592        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
593            with self.assertRaises(TypeError):
594                r.midtop = value
595
596    def test_midtop__del(self):
597        """Ensures the midtop attribute can't be deleted."""
598        r = Rect(0, 0, 1, 1)
599
600        with self.assertRaises(AttributeError):
601            del r.midtop
602
603    def test_midbottom(self):
604        """Changing the midbottom attribute moves the rect and does not change
605           the rect's size
606        """
607        r = Rect(1, 2, 3, 4)
608        new_midbottom = (r.centerx + 20, r.bottom + 30)
609        expected_topleft = (r.left + 20, r.top + 30)
610        old_size = r.size
611
612        r.midbottom = new_midbottom
613        self.assertEqual(new_midbottom, r.midbottom)
614        self.assertEqual(expected_topleft, r.topleft)
615        self.assertEqual(old_size, r.size)
616
617    def test_midbottom__invalid_value(self):
618        """Ensures the midbottom attribute handles invalid values correctly."""
619        r = Rect(0, 0, 1, 1)
620
621        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
622            with self.assertRaises(TypeError):
623                r.midbottom = value
624
625    def test_midbottom__del(self):
626        """Ensures the midbottom attribute can't be deleted."""
627        r = Rect(0, 0, 1, 1)
628
629        with self.assertRaises(AttributeError):
630            del r.midbottom
631
632    def test_width(self):
633        """Changing the width resizes the rect from the top-left corner
634        """
635        r = Rect(1, 2, 3, 4)
636        new_width = 10
637        old_topleft = r.topleft
638        old_height = r.height
639
640        r.width = new_width
641        self.assertEqual(new_width, r.width)
642        self.assertEqual(old_height, r.height)
643        self.assertEqual(old_topleft, r.topleft)
644
645    def test_width__invalid_value(self):
646        """Ensures the width attribute handles invalid values correctly."""
647        r = Rect(0, 0, 1, 1)
648
649        for value in (None, [], "1", (1,), [1, 2, 3]):
650            with self.assertRaises(TypeError):
651                r.width = value
652
653    def test_width__del(self):
654        """Ensures the width attribute can't be deleted."""
655        r = Rect(0, 0, 1, 1)
656
657        with self.assertRaises(AttributeError):
658            del r.width
659
660    def test_height(self):
661        """Changing the height resizes the rect from the top-left corner
662        """
663        r = Rect(1, 2, 3, 4)
664        new_height = 10
665        old_topleft = r.topleft
666        old_width = r.width
667
668        r.height = new_height
669        self.assertEqual(new_height, r.height)
670        self.assertEqual(old_width, r.width)
671        self.assertEqual(old_topleft, r.topleft)
672
673    def test_height__invalid_value(self):
674        """Ensures the height attribute handles invalid values correctly."""
675        r = Rect(0, 0, 1, 1)
676
677        for value in (None, [], "1", (1,), [1, 2, 3]):
678            with self.assertRaises(TypeError):
679                r.height = value
680
681    def test_height__del(self):
682        """Ensures the height attribute can't be deleted."""
683        r = Rect(0, 0, 1, 1)
684
685        with self.assertRaises(AttributeError):
686            del r.height
687
688    def test_size(self):
689        """Changing the size resizes the rect from the top-left corner
690        """
691        r = Rect(1, 2, 3, 4)
692        new_size = (10, 20)
693        old_topleft = r.topleft
694
695        r.size = new_size
696        self.assertEqual(new_size, r.size)
697        self.assertEqual(old_topleft, r.topleft)
698
699    def test_size__invalid_value(self):
700        """Ensures the size attribute handles invalid values correctly."""
701        r = Rect(0, 0, 1, 1)
702
703        for value in (None, [], "1", 1, (1,), [1, 2, 3]):
704            with self.assertRaises(TypeError):
705                r.size = value
706
707    def test_size__del(self):
708        """Ensures the size attribute can't be deleted."""
709        r = Rect(0, 0, 1, 1)
710
711        with self.assertRaises(AttributeError):
712            del r.size
713
714    def test_contains(self):
715        r = Rect(1, 2, 3, 4)
716
717        self.assertTrue(
718            r.contains(Rect(2, 3, 1, 1)), "r does not contain Rect(2, 3, 1, 1)"
719        )
720        self.assertTrue(
721            r.contains(Rect(r)), "r does not contain the same rect as itself"
722        )
723        self.assertTrue(
724            r.contains(Rect(2, 3, 0, 0)),
725            "r does not contain an empty rect within its bounds",
726        )
727        self.assertFalse(r.contains(Rect(0, 0, 1, 2)), "r contains Rect(0, 0, 1, 2)")
728        self.assertFalse(r.contains(Rect(4, 6, 1, 1)), "r contains Rect(4, 6, 1, 1)")
729        self.assertFalse(r.contains(Rect(4, 6, 0, 0)), "r contains Rect(4, 6, 0, 0)")
730
731    def test_collidepoint(self):
732        r = Rect(1, 2, 3, 4)
733
734        self.assertTrue(
735            r.collidepoint(r.left, r.top), "r does not collide with point (left, top)"
736        )
737        self.assertFalse(
738            r.collidepoint(r.left - 1, r.top), "r collides with point (left - 1, top)"
739        )
740        self.assertFalse(
741            r.collidepoint(r.left, r.top - 1), "r collides with point (left, top - 1)"
742        )
743        self.assertFalse(
744            r.collidepoint(r.left - 1, r.top - 1),
745            "r collides with point (left - 1, top - 1)",
746        )
747
748        self.assertTrue(
749            r.collidepoint(r.right - 1, r.bottom - 1),
750            "r does not collide with point (right - 1, bottom - 1)",
751        )
752        self.assertFalse(
753            r.collidepoint(r.right, r.bottom), "r collides with point (right, bottom)"
754        )
755        self.assertFalse(
756            r.collidepoint(r.right - 1, r.bottom),
757            "r collides with point (right - 1, bottom)",
758        )
759        self.assertFalse(
760            r.collidepoint(r.right, r.bottom - 1),
761            "r collides with point (right, bottom - 1)",
762        )
763
764    def test_inflate__larger(self):
765        """The inflate method inflates around the center of the rectangle
766        """
767        r = Rect(2, 4, 6, 8)
768        r2 = r.inflate(4, 6)
769
770        self.assertEqual(r.center, r2.center)
771        self.assertEqual(r.left - 2, r2.left)
772        self.assertEqual(r.top - 3, r2.top)
773        self.assertEqual(r.right + 2, r2.right)
774        self.assertEqual(r.bottom + 3, r2.bottom)
775        self.assertEqual(r.width + 4, r2.width)
776        self.assertEqual(r.height + 6, r2.height)
777
778    def test_inflate__smaller(self):
779        """The inflate method inflates around the center of the rectangle
780        """
781        r = Rect(2, 4, 6, 8)
782        r2 = r.inflate(-4, -6)
783
784        self.assertEqual(r.center, r2.center)
785        self.assertEqual(r.left + 2, r2.left)
786        self.assertEqual(r.top + 3, r2.top)
787        self.assertEqual(r.right - 2, r2.right)
788        self.assertEqual(r.bottom - 3, r2.bottom)
789        self.assertEqual(r.width - 4, r2.width)
790        self.assertEqual(r.height - 6, r2.height)
791
792    def test_inflate_ip__larger(self):
793        """The inflate_ip method inflates around the center of the rectangle
794        """
795        r = Rect(2, 4, 6, 8)
796        r2 = Rect(r)
797        r2.inflate_ip(-4, -6)
798
799        self.assertEqual(r.center, r2.center)
800        self.assertEqual(r.left + 2, r2.left)
801        self.assertEqual(r.top + 3, r2.top)
802        self.assertEqual(r.right - 2, r2.right)
803        self.assertEqual(r.bottom - 3, r2.bottom)
804        self.assertEqual(r.width - 4, r2.width)
805        self.assertEqual(r.height - 6, r2.height)
806
807    def test_inflate_ip__smaller(self):
808        """The inflate method inflates around the center of the rectangle
809        """
810        r = Rect(2, 4, 6, 8)
811        r2 = Rect(r)
812        r2.inflate_ip(-4, -6)
813
814        self.assertEqual(r.center, r2.center)
815        self.assertEqual(r.left + 2, r2.left)
816        self.assertEqual(r.top + 3, r2.top)
817        self.assertEqual(r.right - 2, r2.right)
818        self.assertEqual(r.bottom - 3, r2.bottom)
819        self.assertEqual(r.width - 4, r2.width)
820        self.assertEqual(r.height - 6, r2.height)
821
822    def test_clamp(self):
823        r = Rect(10, 10, 10, 10)
824        c = Rect(19, 12, 5, 5).clamp(r)
825        self.assertEqual(c.right, r.right)
826        self.assertEqual(c.top, 12)
827        c = Rect(1, 2, 3, 4).clamp(r)
828        self.assertEqual(c.topleft, r.topleft)
829        c = Rect(5, 500, 22, 33).clamp(r)
830        self.assertEqual(c.center, r.center)
831
832    def test_clamp_ip(self):
833        r = Rect(10, 10, 10, 10)
834        c = Rect(19, 12, 5, 5)
835        c.clamp_ip(r)
836        self.assertEqual(c.right, r.right)
837        self.assertEqual(c.top, 12)
838        c = Rect(1, 2, 3, 4)
839        c.clamp_ip(r)
840        self.assertEqual(c.topleft, r.topleft)
841        c = Rect(5, 500, 22, 33)
842        c.clamp_ip(r)
843        self.assertEqual(c.center, r.center)
844
845    def test_clip(self):
846        r1 = Rect(1, 2, 3, 4)
847        self.assertEqual(Rect(1, 2, 2, 2), r1.clip(Rect(0, 0, 3, 4)))
848        self.assertEqual(Rect(2, 2, 2, 4), r1.clip(Rect(2, 2, 10, 20)))
849        self.assertEqual(Rect(2, 3, 1, 2), r1.clip(Rect(2, 3, 1, 2)))
850        self.assertEqual((0, 0), r1.clip(20, 30, 5, 6).size)
851        self.assertEqual(
852            r1, r1.clip(Rect(r1)), "r1 does not clip an identical rect to itself"
853        )
854
855    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
856    def test_clipline(self):
857        """Ensures clipline handles four int parameters.
858
859        Tests the clipline(x1, y1, x2, y2) format.
860        """
861        rect = Rect((1, 2), (35, 40))
862        x1 = 5
863        y1 = 6
864        x2 = 11
865        y2 = 19
866        expected_line = ((x1, y1), (x2, y2))
867
868        clipped_line = rect.clipline(x1, y1, x2, y2)
869
870        self.assertIsInstance(clipped_line, tuple)
871        self.assertTupleEqual(clipped_line, expected_line)
872
873    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
874    def test_clipline__two_sequences(self):
875        """Ensures clipline handles a sequence of two sequences.
876
877        Tests the clipline((x1, y1), (x2, y2)) format.
878        Tests the sequences as different types.
879        """
880        rect = Rect((1, 2), (35, 40))
881        pt1 = (5, 6)
882        pt2 = (11, 19)
883
884        INNER_SEQUENCES = (list, tuple, Vector2)
885        expected_line = (pt1, pt2)
886
887        for inner_seq1 in INNER_SEQUENCES:
888            endpt1 = inner_seq1(pt1)
889
890            for inner_seq2 in INNER_SEQUENCES:
891                clipped_line = rect.clipline((endpt1, inner_seq2(pt2)))
892
893                self.assertIsInstance(clipped_line, tuple)
894                self.assertTupleEqual(clipped_line, expected_line)
895
896    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
897    def test_clipline__sequence_of_four_ints(self):
898        """Ensures clipline handles a sequence of four ints.
899
900        Tests the clipline((x1, y1, x2, y2)) format.
901        Tests the sequence as different types.
902        """
903        rect = Rect((1, 2), (35, 40))
904        line = (5, 6, 11, 19)
905        expected_line = ((line[0], line[1]), (line[2], line[3]))
906
907        for outer_seq in (list, tuple):
908            clipped_line = rect.clipline(outer_seq(line))
909
910            self.assertIsInstance(clipped_line, tuple)
911            self.assertTupleEqual(clipped_line, expected_line)
912
913    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
914    def test_clipline__sequence_of_two_sequences(self):
915        """Ensures clipline handles a sequence of two sequences.
916
917        Tests the clipline(((x1, y1), (x2, y2))) format.
918        Tests the sequences as different types.
919        """
920        rect = Rect((1, 2), (35, 40))
921        pt1 = (5, 6)
922        pt2 = (11, 19)
923
924        INNER_SEQUENCES = (list, tuple, Vector2)
925        expected_line = (pt1, pt2)
926
927        for inner_seq1 in INNER_SEQUENCES:
928            endpt1 = inner_seq1(pt1)
929
930            for inner_seq2 in INNER_SEQUENCES:
931                endpt2 = inner_seq2(pt2)
932
933                for outer_seq in (list, tuple):
934                    clipped_line = rect.clipline(outer_seq((endpt1, endpt2)))
935
936                    self.assertIsInstance(clipped_line, tuple)
937                    self.assertTupleEqual(clipped_line, expected_line)
938
939    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
940    def test_clipline__floats(self):
941        """Ensures clipline handles float parameters."""
942        rect = Rect((1, 2), (35, 40))
943        x1 = 5.9
944        y1 = 6.9
945        x2 = 11.9
946        y2 = 19.9
947
948        # Floats are truncated.
949        expected_line = (
950            (math.floor(x1), math.floor(y1)),
951            (math.floor(x2), math.floor(y2)),
952        )
953
954        clipped_line = rect.clipline(x1, y1, x2, y2)
955
956        self.assertIsInstance(clipped_line, tuple)
957        self.assertTupleEqual(clipped_line, expected_line)
958
959    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
960    def test_clipline__no_overlap(self):
961        """Ensures lines that do not overlap the rect are not clipped."""
962        rect = Rect((10, 25), (15, 20))
963        # Use a bigger rect to help create test lines.
964        big_rect = rect.inflate(2, 2)
965        lines = (
966            (big_rect.bottomleft, big_rect.topleft),  # Left edge.
967            (big_rect.topleft, big_rect.topright),  # Top edge.
968            (big_rect.topright, big_rect.bottomright),  # Right edge.
969            (big_rect.bottomright, big_rect.bottomleft),
970        )  # Bottom edge.
971        expected_line = ()
972
973        # Test lines outside rect.
974        for line in lines:
975            clipped_line = rect.clipline(line)
976
977            self.assertTupleEqual(clipped_line, expected_line)
978
979    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
980    def test_clipline__both_endpoints_outside(self):
981        """Ensures lines that overlap the rect are clipped.
982
983        Testing lines with both endpoints outside the rect.
984        """
985        rect = Rect((0, 0), (20, 20))
986        # Use a bigger rect to help create test lines.
987        big_rect = rect.inflate(2, 2)
988
989        # Create a dict of lines and expected results.
990        line_dict = {
991            (big_rect.midleft, big_rect.midright): (
992                rect.midleft,
993                (rect.midright[0] - 1, rect.midright[1]),
994            ),
995            (big_rect.midtop, big_rect.midbottom): (
996                rect.midtop,
997                (rect.midbottom[0], rect.midbottom[1] - 1),
998            ),
999            # Diagonals.
1000            (big_rect.topleft, big_rect.bottomright): (
1001                rect.topleft,
1002                (rect.bottomright[0] - 1, rect.bottomright[1] - 1),
1003            ),
1004            # This line needs a small adjustment to make sure it intersects
1005            # the rect correctly.
1006            (
1007                (big_rect.topright[0] - 1, big_rect.topright[1]),
1008                (big_rect.bottomleft[0], big_rect.bottomleft[1] - 1),
1009            ): (
1010                (rect.topright[0] - 1, rect.topright[1]),
1011                (rect.bottomleft[0], rect.bottomleft[1] - 1),
1012            ),
1013        }
1014
1015        for line, expected_line in line_dict.items():
1016            clipped_line = rect.clipline(line)
1017
1018            self.assertTupleEqual(clipped_line, expected_line)
1019
1020            # Swap endpoints to test for symmetry.
1021            expected_line = (expected_line[1], expected_line[0])
1022
1023            clipped_line = rect.clipline((line[1], line[0]))
1024
1025            self.assertTupleEqual(clipped_line, expected_line)
1026
1027    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1028    def test_clipline__both_endpoints_inside(self):
1029        """Ensures lines that overlap the rect are clipped.
1030
1031        Testing lines with both endpoints inside the rect.
1032        """
1033        rect = Rect((-10, -5), (20, 20))
1034        # Use a smaller rect to help create test lines.
1035        small_rect = rect.inflate(-2, -2)
1036
1037        lines = (
1038            (small_rect.midleft, small_rect.midright),
1039            (small_rect.midtop, small_rect.midbottom),
1040            # Diagonals.
1041            (small_rect.topleft, small_rect.bottomright),
1042            (small_rect.topright, small_rect.bottomleft),
1043        )
1044
1045        for line in lines:
1046            expected_line = line
1047
1048            clipped_line = rect.clipline(line)
1049
1050            self.assertTupleEqual(clipped_line, expected_line)
1051
1052            # Swap endpoints to test for symmetry.
1053            expected_line = (expected_line[1], expected_line[0])
1054
1055            clipped_line = rect.clipline((line[1], line[0]))
1056
1057            self.assertTupleEqual(clipped_line, expected_line)
1058
1059    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1060    def test_clipline__endpoints_inside_and_outside(self):
1061        """Ensures lines that overlap the rect are clipped.
1062
1063        Testing lines with one endpoint outside the rect and the other is
1064        inside the rect.
1065        """
1066        rect = Rect((0, 0), (21, 21))
1067        # Use a bigger rect to help create test lines.
1068        big_rect = rect.inflate(2, 2)
1069
1070        # Create a dict of lines and expected results.
1071        line_dict = {
1072            (big_rect.midleft, rect.center): (rect.midleft, rect.center),
1073            (big_rect.midtop, rect.center): (rect.midtop, rect.center),
1074            (big_rect.midright, rect.center): (
1075                (rect.midright[0] - 1, rect.midright[1]),
1076                rect.center,
1077            ),
1078            (big_rect.midbottom, rect.center): (
1079                (rect.midbottom[0], rect.midbottom[1] - 1),
1080                rect.center,
1081            ),
1082            # Diagonals.
1083            (big_rect.topleft, rect.center): (rect.topleft, rect.center),
1084            (big_rect.topright, rect.center): (
1085                (rect.topright[0] - 1, rect.topright[1]),
1086                rect.center,
1087            ),
1088            (big_rect.bottomright, rect.center): (
1089                (rect.bottomright[0] - 1, rect.bottomright[1] - 1),
1090                rect.center,
1091            ),
1092            # This line needs a small adjustment to make sure it intersects
1093            # the rect correctly.
1094            ((big_rect.bottomleft[0], big_rect.bottomleft[1] - 1), rect.center): (
1095                (rect.bottomleft[0], rect.bottomleft[1] - 1),
1096                rect.center,
1097            ),
1098        }
1099
1100        for line, expected_line in line_dict.items():
1101            clipped_line = rect.clipline(line)
1102
1103            self.assertTupleEqual(clipped_line, expected_line)
1104
1105            # Swap endpoints to test for symmetry.
1106            expected_line = (expected_line[1], expected_line[0])
1107
1108            clipped_line = rect.clipline((line[1], line[0]))
1109
1110            self.assertTupleEqual(clipped_line, expected_line)
1111
1112    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1113    def test_clipline__edges(self):
1114        """Ensures clipline properly clips line that are along the rect edges.
1115        """
1116        rect = Rect((10, 25), (15, 20))
1117
1118        # Create a dict of edges and expected results.
1119        edge_dict = {
1120            # Left edge.
1121            (rect.bottomleft, rect.topleft): (
1122                (rect.bottomleft[0], rect.bottomleft[1] - 1),
1123                rect.topleft,
1124            ),
1125            # Top edge.
1126            (rect.topleft, rect.topright): (
1127                rect.topleft,
1128                (rect.topright[0] - 1, rect.topright[1]),
1129            ),
1130            # Right edge.
1131            (rect.topright, rect.bottomright): (),
1132            # Bottom edge.
1133            (rect.bottomright, rect.bottomleft): (),
1134        }
1135
1136        for edge, expected_line in edge_dict.items():
1137            clipped_line = rect.clipline(edge)
1138
1139            self.assertTupleEqual(clipped_line, expected_line)
1140
1141            # Swap endpoints to test for symmetry.
1142            if expected_line:
1143                expected_line = (expected_line[1], expected_line[0])
1144
1145            clipped_line = rect.clipline((edge[1], edge[0]))
1146
1147            self.assertTupleEqual(clipped_line, expected_line)
1148
1149    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1150    def test_clipline__equal_endpoints_with_overlap(self):
1151        """Ensures clipline handles lines with both endpoints the same.
1152
1153        Testing lines that overlap the rect.
1154        """
1155        rect = Rect((10, 25), (15, 20))
1156
1157        # Test all the points in and on a rect.
1158        pts = (
1159            (x, y)
1160            for x in range(rect.left, rect.right)
1161            for y in range(rect.top, rect.bottom)
1162        )
1163
1164        for pt in pts:
1165            expected_line = (pt, pt)
1166
1167            clipped_line = rect.clipline((pt, pt))
1168
1169            self.assertTupleEqual(clipped_line, expected_line)
1170
1171    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1172    def test_clipline__equal_endpoints_no_overlap(self):
1173        """Ensures clipline handles lines with both endpoints the same.
1174
1175        Testing lines that do not overlap the rect.
1176        """
1177        expected_line = ()
1178        rect = Rect((10, 25), (15, 20))
1179
1180        # Test points outside rect.
1181        for pt in test_utils.rect_perimeter_pts(rect.inflate(2, 2)):
1182            clipped_line = rect.clipline((pt, pt))
1183
1184            self.assertTupleEqual(clipped_line, expected_line)
1185
1186    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1187    def test_clipline__zero_size_rect(self):
1188        """Ensures clipline handles zero sized rects correctly."""
1189        expected_line = ()
1190
1191        for size in ((0, 15), (15, 0), (0, 0)):
1192            rect = Rect((10, 25), size)
1193
1194            clipped_line = rect.clipline(rect.topleft, rect.topleft)
1195
1196            self.assertTupleEqual(clipped_line, expected_line)
1197
1198    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1199    def test_clipline__negative_size_rect(self):
1200        """Ensures clipline handles negative sized rects correctly."""
1201        expected_line = ()
1202
1203        for size in ((-15, 20), (15, -20), (-15, -20)):
1204            rect = Rect((10, 25), size)
1205            norm_rect = rect.copy()
1206            norm_rect.normalize()
1207            # Use a bigger rect to help create test lines.
1208            big_rect = norm_rect.inflate(2, 2)
1209
1210            # Create a dict of lines and expected results. Some line have both
1211            # endpoints outside the rect and some have one inside and one
1212            # outside.
1213            line_dict = {
1214                (big_rect.midleft, big_rect.midright): (
1215                    norm_rect.midleft,
1216                    (norm_rect.midright[0] - 1, norm_rect.midright[1]),
1217                ),
1218                (big_rect.midtop, big_rect.midbottom): (
1219                    norm_rect.midtop,
1220                    (norm_rect.midbottom[0], norm_rect.midbottom[1] - 1),
1221                ),
1222                (big_rect.midleft, norm_rect.center): (
1223                    norm_rect.midleft,
1224                    norm_rect.center,
1225                ),
1226                (big_rect.midtop, norm_rect.center): (
1227                    norm_rect.midtop,
1228                    norm_rect.center,
1229                ),
1230                (big_rect.midright, norm_rect.center): (
1231                    (norm_rect.midright[0] - 1, norm_rect.midright[1]),
1232                    norm_rect.center,
1233                ),
1234                (big_rect.midbottom, norm_rect.center): (
1235                    (norm_rect.midbottom[0], norm_rect.midbottom[1] - 1),
1236                    norm_rect.center,
1237                ),
1238            }
1239
1240            for line, expected_line in line_dict.items():
1241                clipped_line = rect.clipline(line)
1242
1243                # Make sure rect wasn't normalized.
1244                self.assertNotEqual(rect, norm_rect)
1245                self.assertTupleEqual(clipped_line, expected_line)
1246
1247                # Swap endpoints to test for symmetry.
1248                expected_line = (expected_line[1], expected_line[0])
1249
1250                clipped_line = rect.clipline((line[1], line[0]))
1251
1252                self.assertTupleEqual(clipped_line, expected_line)
1253
1254    @unittest.skipIf(SDL1, "rect.clipline not available in SDL1")
1255    def test_clipline__invalid_line(self):
1256        """Ensures clipline handles invalid lines correctly."""
1257        rect = Rect((0, 0), (10, 20))
1258        invalid_lines = (
1259            (),
1260            (1,),
1261            (1, 2),
1262            (1, 2, 3),
1263            (1, 2, 3, 4, 5),
1264            ((1, 2),),
1265            ((1, 2), (3,)),
1266            ((1, 2), 3),
1267            ((1, 2, 5), (3, 4)),
1268            ((1, 2), (3, 4, 5)),
1269            ((1, 2), (3, 4), (5, 6)),
1270        )
1271
1272        for line in invalid_lines:
1273            with self.assertRaises(TypeError):
1274                clipped_line = rect.clipline(line)
1275
1276            with self.assertRaises(TypeError):
1277                clipped_line = rect.clipline(*line)
1278
1279    @unittest.skipIf(IS_PYPY, "fails on pypy sometimes")
1280    def test_move(self):
1281        r = Rect(1, 2, 3, 4)
1282        move_x = 10
1283        move_y = 20
1284        r2 = r.move(move_x, move_y)
1285        expected_r2 = Rect(r.left + move_x, r.top + move_y, r.width, r.height)
1286        self.assertEqual(expected_r2, r2)
1287
1288    @unittest.skipIf(IS_PYPY, "fails on pypy sometimes")
1289    def test_move_ip(self):
1290        r = Rect(1, 2, 3, 4)
1291        r2 = Rect(r)
1292        move_x = 10
1293        move_y = 20
1294        r2.move_ip(move_x, move_y)
1295        expected_r2 = Rect(r.left + move_x, r.top + move_y, r.width, r.height)
1296        self.assertEqual(expected_r2, r2)
1297
1298    def test_update_XYWidthHeight(self):
1299        """Test update with 4 int values(x, y, w, h)"""
1300        rect = Rect(0, 0, 1, 1)
1301        rect.update(1, 2, 3, 4)
1302
1303        self.assertEqual(1, rect.left)
1304        self.assertEqual(2, rect.top)
1305        self.assertEqual(3, rect.width)
1306        self.assertEqual(4, rect.height)
1307
1308    def test_update__TopLeftSize(self):
1309        """Test update with 2 tuples((x, y), (w, h))"""
1310        rect = Rect(0, 0, 1, 1)
1311        rect.update((1, 2), (3, 4))
1312
1313        self.assertEqual(1, rect.left)
1314        self.assertEqual(2, rect.top)
1315        self.assertEqual(3, rect.width)
1316        self.assertEqual(4, rect.height)
1317
1318    def test_update__List(self):
1319        """Test update with list"""
1320        rect = Rect(0, 0, 1, 1)
1321        rect2 = [1, 2, 3, 4]
1322        rect.update(rect2)
1323
1324        self.assertEqual(1, rect.left)
1325        self.assertEqual(2, rect.top)
1326        self.assertEqual(3, rect.width)
1327        self.assertEqual(4, rect.height)
1328
1329    def test_update__RectObject(self):
1330        """Test update with other rect object"""
1331        rect = Rect(0, 0, 1, 1)
1332        rect2 = Rect(1, 2, 3, 4)
1333        rect.update(rect2)
1334
1335        self.assertEqual(1, rect.left)
1336        self.assertEqual(2, rect.top)
1337        self.assertEqual(3, rect.width)
1338        self.assertEqual(4, rect.height)
1339
1340    def test_union(self):
1341        r1 = Rect(1, 1, 1, 2)
1342        r2 = Rect(-2, -2, 1, 2)
1343        self.assertEqual(Rect(-2, -2, 4, 5), r1.union(r2))
1344
1345    def test_union__with_identical_Rect(self):
1346        r1 = Rect(1, 2, 3, 4)
1347        self.assertEqual(r1, r1.union(Rect(r1)))
1348
1349    def test_union_ip(self):
1350        r1 = Rect(1, 1, 1, 2)
1351        r2 = Rect(-2, -2, 1, 2)
1352        r1.union_ip(r2)
1353        self.assertEqual(Rect(-2, -2, 4, 5), r1)
1354
1355    def test_unionall(self):
1356        r1 = Rect(0, 0, 1, 1)
1357        r2 = Rect(-2, -2, 1, 1)
1358        r3 = Rect(2, 2, 1, 1)
1359
1360        r4 = r1.unionall([r2, r3])
1361        self.assertEqual(Rect(-2, -2, 5, 5), r4)
1362
1363    def test_unionall__invalid_rect_format(self):
1364        """Ensures unionall correctly handles invalid rect parameters."""
1365        numbers = [0, 1.2, 2, 3.3]
1366        strs = ["a", "b", "c"]
1367        nones = [None, None]
1368
1369        for invalid_rects in (numbers, strs, nones):
1370            with self.assertRaises(TypeError):
1371                Rect(0, 0, 1, 1).unionall(invalid_rects)
1372
1373    def test_unionall_ip(self):
1374        r1 = Rect(0, 0, 1, 1)
1375        r2 = Rect(-2, -2, 1, 1)
1376        r3 = Rect(2, 2, 1, 1)
1377
1378        r1.unionall_ip([r2, r3])
1379        self.assertEqual(Rect(-2, -2, 5, 5), r1)
1380
1381        # Bug for an empty list. Would return a Rect instead of None.
1382        self.assertTrue(r1.unionall_ip([]) is None)
1383
1384    def test_unionall__invalid_rect_format(self):
1385        """Ensures unionall_ip correctly handles invalid rect parameters."""
1386        numbers = [0, 1.2, 2, 3.3]
1387        strs = ["a", "b", "c"]
1388        nones = [None, None]
1389
1390        for invalid_rects in (numbers, strs, nones):
1391            with self.assertRaises(TypeError):
1392                Rect(0, 0, 1, 1).unionall_ip(invalid_rects)
1393
1394    def test_colliderect(self):
1395        r1 = Rect(1, 2, 3, 4)
1396        self.assertTrue(
1397            r1.colliderect(Rect(0, 0, 2, 3)),
1398            "r1 does not collide with Rect(0, 0, 2, 3)",
1399        )
1400        self.assertFalse(
1401            r1.colliderect(Rect(0, 0, 1, 2)), "r1 collides with Rect(0, 0, 1, 2)"
1402        )
1403        self.assertFalse(
1404            r1.colliderect(Rect(r1.right, r1.bottom, 2, 2)),
1405            "r1 collides with Rect(r1.right, r1.bottom, 2, 2)",
1406        )
1407        self.assertTrue(
1408            r1.colliderect(Rect(r1.left + 1, r1.top + 1, r1.width - 2, r1.height - 2)),
1409            "r1 does not collide with Rect(r1.left + 1, r1.top + 1, "
1410            + "r1.width - 2, r1.height - 2)",
1411        )
1412        self.assertTrue(
1413            r1.colliderect(Rect(r1.left - 1, r1.top - 1, r1.width + 2, r1.height + 2)),
1414            "r1 does not collide with Rect(r1.left - 1, r1.top - 1, "
1415            + "r1.width + 2, r1.height + 2)",
1416        )
1417        self.assertTrue(
1418            r1.colliderect(Rect(r1)), "r1 does not collide with an identical rect"
1419        )
1420        self.assertFalse(
1421            r1.colliderect(Rect(r1.right, r1.bottom, 0, 0)),
1422            "r1 collides with Rect(r1.right, r1.bottom, 0, 0)",
1423        )
1424        self.assertFalse(
1425            r1.colliderect(Rect(r1.right, r1.bottom, 1, 1)),
1426            "r1 collides with Rect(r1.right, r1.bottom, 1, 1)",
1427        )
1428
1429    @unittest.skipIf(IS_PYPY, "fails on pypy3 sometimes")
1430    def testEquals(self):
1431        """ check to see how the rect uses __eq__
1432        """
1433        r1 = Rect(1, 2, 3, 4)
1434        r2 = Rect(10, 20, 30, 40)
1435        r3 = (10, 20, 30, 40)
1436        r4 = Rect(10, 20, 30, 40)
1437
1438        class foo(Rect):
1439            def __eq__(self, other):
1440                return id(self) == id(other)
1441
1442            def __ne__(self, other):
1443                return id(self) != id(other)
1444
1445        class foo2(Rect):
1446            pass
1447
1448        r5 = foo(10, 20, 30, 40)
1449        r6 = foo2(10, 20, 30, 40)
1450
1451        self.assertNotEqual(r5, r2)
1452
1453        # because we define equality differently for this subclass.
1454        self.assertEqual(r6, r2)
1455
1456        rect_list = [r1, r2, r3, r4, r6]
1457
1458        # see if we can remove 4 of these.
1459        rect_list.remove(r2)
1460        rect_list.remove(r2)
1461        rect_list.remove(r2)
1462        rect_list.remove(r2)
1463        self.assertRaises(ValueError, rect_list.remove, r2)
1464
1465    def test_collidedict(self):
1466        """Ensures collidedict detects collisions."""
1467        rect = Rect(1, 1, 10, 10)
1468
1469        collide_item1 = ("collide 1", rect.copy())
1470        collide_item2 = ("collide 2", Rect(5, 5, 10, 10))
1471        no_collide_item1 = ("no collide 1", Rect(60, 60, 10, 10))
1472        no_collide_item2 = ("no collide 2", Rect(70, 70, 10, 10))
1473
1474        # Dict to check collisions with values.
1475        rect_values = dict(
1476            (collide_item1, collide_item2, no_collide_item1, no_collide_item2)
1477        )
1478        value_collide_items = (collide_item1, collide_item2)
1479
1480        # Dict to check collisions with keys.
1481        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1482        key_collide_items = tuple((tuple(v), k) for k, v in value_collide_items)
1483
1484        for use_values in (True, False):
1485            if use_values:
1486                expected_items = value_collide_items
1487                d = rect_values
1488            else:
1489                expected_items = key_collide_items
1490                d = rect_keys
1491
1492            collide_item = rect.collidedict(d, use_values)
1493
1494            # The detected collision could be any of the possible items.
1495            self.assertIn(collide_item, expected_items)
1496
1497    def test_collidedict__no_collision(self):
1498        """Ensures collidedict returns None when no collisions."""
1499        rect = Rect(1, 1, 10, 10)
1500
1501        no_collide_item1 = ("no collide 1", Rect(50, 50, 10, 10))
1502        no_collide_item2 = ("no collide 2", Rect(60, 60, 10, 10))
1503        no_collide_item3 = ("no collide 3", Rect(70, 70, 10, 10))
1504
1505        # Dict to check collisions with values.
1506        rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3))
1507
1508        # Dict to check collisions with keys.
1509        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1510
1511        for use_values in (True, False):
1512            d = rect_values if use_values else rect_keys
1513
1514            collide_item = rect.collidedict(d, use_values)
1515
1516            self.assertIsNone(collide_item)
1517
1518    def test_collidedict__barely_touching(self):
1519        """Ensures collidedict works correctly for rects that barely touch."""
1520        rect = Rect(1, 1, 10, 10)
1521        # Small rect to test barely touching collisions.
1522        collide_rect = Rect(0, 0, 1, 1)
1523
1524        collide_item1 = ("collide 1", collide_rect)
1525        no_collide_item1 = ("no collide 1", Rect(50, 50, 10, 10))
1526        no_collide_item2 = ("no collide 2", Rect(60, 60, 10, 10))
1527        no_collide_item3 = ("no collide 3", Rect(70, 70, 10, 10))
1528
1529        # Dict to check collisions with values.
1530        no_collide_rect_values = dict(
1531            (no_collide_item1, no_collide_item2, no_collide_item3)
1532        )
1533
1534        # Dict to check collisions with keys.
1535        no_collide_rect_keys = {tuple(v): k for k, v in no_collide_rect_values.items()}
1536
1537        # Tests the collide_rect on each of the rect's corners.
1538        for attr in ("topleft", "topright", "bottomright", "bottomleft"):
1539            setattr(collide_rect, attr, getattr(rect, attr))
1540
1541            for use_values in (True, False):
1542                if use_values:
1543                    expected_item = collide_item1
1544                    d = dict(no_collide_rect_values)
1545                else:
1546                    expected_item = (tuple(collide_item1[1]), collide_item1[0])
1547                    d = dict(no_collide_rect_keys)
1548
1549                d.update((expected_item,))  # Add in the expected item.
1550
1551                collide_item = rect.collidedict(d, use_values)
1552
1553                self.assertTupleEqual(collide_item, expected_item)
1554
1555    def test_collidedict__zero_sized_rects(self):
1556        """Ensures collidedict works correctly with zero sized rects.
1557
1558        There should be no collisions with zero sized rects.
1559        """
1560        zero_rect1 = Rect(1, 1, 0, 0)
1561        zero_rect2 = Rect(1, 1, 1, 0)
1562        zero_rect3 = Rect(1, 1, 0, 1)
1563        zero_rect4 = Rect(1, 1, -1, 0)
1564        zero_rect5 = Rect(1, 1, 0, -1)
1565
1566        no_collide_item1 = ("no collide 1", zero_rect1.copy())
1567        no_collide_item2 = ("no collide 2", zero_rect2.copy())
1568        no_collide_item3 = ("no collide 3", zero_rect3.copy())
1569        no_collide_item4 = ("no collide 4", zero_rect4.copy())
1570        no_collide_item5 = ("no collide 5", zero_rect5.copy())
1571        no_collide_item6 = ("no collide 6", Rect(0, 0, 10, 10))
1572        no_collide_item7 = ("no collide 7", Rect(0, 0, 2, 2))
1573
1574        # Dict to check collisions with values.
1575        rect_values = dict(
1576            (
1577                no_collide_item1,
1578                no_collide_item2,
1579                no_collide_item3,
1580                no_collide_item4,
1581                no_collide_item5,
1582                no_collide_item6,
1583                no_collide_item7,
1584            )
1585        )
1586
1587        # Dict to check collisions with keys.
1588        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1589
1590        for use_values in (True, False):
1591            d = rect_values if use_values else rect_keys
1592
1593            for zero_rect in (
1594                zero_rect1,
1595                zero_rect2,
1596                zero_rect3,
1597                zero_rect4,
1598                zero_rect5,
1599            ):
1600                collide_item = zero_rect.collidedict(d, use_values)
1601
1602                self.assertIsNone(collide_item)
1603
1604    def test_collidedict__zero_sized_rects_as_args(self):
1605        """Ensures collidedict works correctly with zero sized rects as args.
1606
1607        There should be no collisions with zero sized rects.
1608        """
1609        rect = Rect(0, 0, 10, 10)
1610
1611        no_collide_item1 = ("no collide 1", Rect(1, 1, 0, 0))
1612        no_collide_item2 = ("no collide 2", Rect(1, 1, 1, 0))
1613        no_collide_item3 = ("no collide 3", Rect(1, 1, 0, 1))
1614
1615        # Dict to check collisions with values.
1616        rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3))
1617
1618        # Dict to check collisions with keys.
1619        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1620
1621        for use_values in (True, False):
1622            d = rect_values if use_values else rect_keys
1623
1624            collide_item = rect.collidedict(d, use_values)
1625
1626            self.assertIsNone(collide_item)
1627
1628    def test_collidedict__negative_sized_rects(self):
1629        """Ensures collidedict works correctly with negative sized rects."""
1630        neg_rect = Rect(1, 1, -1, -1)
1631
1632        collide_item1 = ("collide 1", neg_rect.copy())
1633        collide_item2 = ("collide 2", Rect(0, 0, 10, 10))
1634        no_collide_item1 = ("no collide 1", Rect(1, 1, 10, 10))
1635
1636        # Dict to check collisions with values.
1637        rect_values = dict((collide_item1, collide_item2, no_collide_item1))
1638        value_collide_items = (collide_item1, collide_item2)
1639
1640        # Dict to check collisions with keys.
1641        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1642        key_collide_items = tuple((tuple(v), k) for k, v in value_collide_items)
1643
1644        for use_values in (True, False):
1645            if use_values:
1646                collide_items = value_collide_items
1647                d = rect_values
1648            else:
1649                collide_items = key_collide_items
1650                d = rect_keys
1651
1652            collide_item = neg_rect.collidedict(d, use_values)
1653
1654            # The detected collision could be any of the possible items.
1655            self.assertIn(collide_item, collide_items)
1656
1657    def test_collidedict__negative_sized_rects_as_args(self):
1658        """Ensures collidedict works correctly with negative sized rect args.
1659        """
1660        rect = Rect(0, 0, 10, 10)
1661
1662        collide_item1 = ("collide 1", Rect(1, 1, -1, -1))
1663        no_collide_item1 = ("no collide 1", Rect(1, 1, -1, 0))
1664        no_collide_item2 = ("no collide 2", Rect(1, 1, 0, -1))
1665
1666        # Dict to check collisions with values.
1667        rect_values = dict((collide_item1, no_collide_item1, no_collide_item2))
1668
1669        # Dict to check collisions with keys.
1670        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1671
1672        for use_values in (True, False):
1673            if use_values:
1674                expected_item = collide_item1
1675                d = rect_values
1676            else:
1677                expected_item = (tuple(collide_item1[1]), collide_item1[0])
1678                d = rect_keys
1679
1680            collide_item = rect.collidedict(d, use_values)
1681
1682            self.assertTupleEqual(collide_item, expected_item)
1683
1684    def test_collidedict__invalid_dict_format(self):
1685        """Ensures collidedict correctly handles invalid dict parameters."""
1686        rect = Rect(0, 0, 10, 10)
1687
1688        invalid_value_dict = ("collide", rect.copy())
1689        invalid_key_dict = tuple(invalid_value_dict[1]), invalid_value_dict[0]
1690
1691        for use_values in (True, False):
1692            d = invalid_value_dict if use_values else invalid_key_dict
1693
1694            with self.assertRaises(TypeError):
1695                collide_item = rect.collidedict(d, use_values)
1696
1697    def test_collidedict__invalid_dict_value_format(self):
1698        """Ensures collidedict correctly handles dicts with invalid values."""
1699        rect = Rect(0, 0, 10, 10)
1700        rect_keys = {tuple(rect): "collide"}
1701
1702        with self.assertRaises(TypeError):
1703            collide_item = rect.collidedict(rect_keys, 1)
1704
1705    def test_collidedict__invalid_dict_key_format(self):
1706        """Ensures collidedict correctly handles dicts with invalid keys."""
1707        rect = Rect(0, 0, 10, 10)
1708        rect_values = {"collide": rect.copy()}
1709
1710        with self.assertRaises(TypeError):
1711            collide_item = rect.collidedict(rect_values)
1712
1713    def test_collidedict__invalid_use_values_format(self):
1714        """Ensures collidedict correctly handles invalid use_values parameters.
1715        """
1716        rect = Rect(0, 0, 1, 1)
1717        d = {}
1718
1719        for invalid_param in (None, d, 1.1):
1720            with self.assertRaises(TypeError):
1721                collide_item = rect.collidedict(d, invalid_param)
1722
1723    def test_collidedictall(self):
1724        """Ensures collidedictall detects collisions."""
1725        rect = Rect(1, 1, 10, 10)
1726
1727        collide_item1 = ("collide 1", rect.copy())
1728        collide_item2 = ("collide 2", Rect(5, 5, 10, 10))
1729        no_collide_item1 = ("no collide 1", Rect(60, 60, 20, 20))
1730        no_collide_item2 = ("no collide 2", Rect(70, 70, 20, 20))
1731
1732        # Dict to check collisions with values.
1733        rect_values = dict(
1734            (collide_item1, collide_item2, no_collide_item1, no_collide_item2)
1735        )
1736        value_collide_items = [collide_item1, collide_item2]
1737
1738        # Dict to check collisions with keys.
1739        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1740        key_collide_items = [(tuple(v), k) for k, v in value_collide_items]
1741
1742        for use_values in (True, False):
1743            if use_values:
1744                expected_items = value_collide_items
1745                d = rect_values
1746            else:
1747                expected_items = key_collide_items
1748                d = rect_keys
1749
1750            collide_items = rect.collidedictall(d, use_values)
1751
1752            self._assertCountEqual(collide_items, expected_items)
1753
1754    def test_collidedictall__no_collision(self):
1755        """Ensures collidedictall returns an empty list when no collisions."""
1756        rect = Rect(1, 1, 10, 10)
1757
1758        no_collide_item1 = ("no collide 1", Rect(50, 50, 20, 20))
1759        no_collide_item2 = ("no collide 2", Rect(60, 60, 20, 20))
1760        no_collide_item3 = ("no collide 3", Rect(70, 70, 20, 20))
1761
1762        # Dict to check collisions with values.
1763        rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3))
1764
1765        # Dict to check collisions with keys.
1766        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1767
1768        expected_items = []
1769
1770        for use_values in (True, False):
1771            d = rect_values if use_values else rect_keys
1772
1773            collide_items = rect.collidedictall(d, use_values)
1774
1775            self._assertCountEqual(collide_items, expected_items)
1776
1777    def test_collidedictall__barely_touching(self):
1778        """Ensures collidedictall works correctly for rects that barely touch.
1779        """
1780        rect = Rect(1, 1, 10, 10)
1781        # Small rect to test barely touching collisions.
1782        collide_rect = Rect(0, 0, 1, 1)
1783
1784        collide_item1 = ("collide 1", collide_rect)
1785        no_collide_item1 = ("no collide 1", Rect(50, 50, 20, 20))
1786        no_collide_item2 = ("no collide 2", Rect(60, 60, 20, 20))
1787        no_collide_item3 = ("no collide 3", Rect(70, 70, 20, 20))
1788
1789        # Dict to check collisions with values.
1790        no_collide_rect_values = dict(
1791            (no_collide_item1, no_collide_item2, no_collide_item3)
1792        )
1793
1794        # Dict to check collisions with keys.
1795        no_collide_rect_keys = {tuple(v): k for k, v in no_collide_rect_values.items()}
1796
1797        # Tests the collide_rect on each of the rect's corners.
1798        for attr in ("topleft", "topright", "bottomright", "bottomleft"):
1799            setattr(collide_rect, attr, getattr(rect, attr))
1800
1801            for use_values in (True, False):
1802                if use_values:
1803                    expected_items = [collide_item1]
1804                    d = dict(no_collide_rect_values)
1805                else:
1806                    expected_items = [(tuple(collide_item1[1]), collide_item1[0])]
1807                    d = dict(no_collide_rect_keys)
1808
1809                d.update(expected_items)  # Add in the expected items.
1810
1811                collide_items = rect.collidedictall(d, use_values)
1812
1813                self._assertCountEqual(collide_items, expected_items)
1814
1815    def test_collidedictall__zero_sized_rects(self):
1816        """Ensures collidedictall works correctly with zero sized rects.
1817
1818        There should be no collisions with zero sized rects.
1819        """
1820        zero_rect1 = Rect(2, 2, 0, 0)
1821        zero_rect2 = Rect(2, 2, 2, 0)
1822        zero_rect3 = Rect(2, 2, 0, 2)
1823        zero_rect4 = Rect(2, 2, -2, 0)
1824        zero_rect5 = Rect(2, 2, 0, -2)
1825
1826        no_collide_item1 = ("no collide 1", zero_rect1.copy())
1827        no_collide_item2 = ("no collide 2", zero_rect2.copy())
1828        no_collide_item3 = ("no collide 3", zero_rect3.copy())
1829        no_collide_item4 = ("no collide 4", zero_rect4.copy())
1830        no_collide_item5 = ("no collide 5", zero_rect5.copy())
1831        no_collide_item6 = ("no collide 6", Rect(0, 0, 10, 10))
1832        no_collide_item7 = ("no collide 7", Rect(0, 0, 2, 2))
1833
1834        # Dict to check collisions with values.
1835        rect_values = dict(
1836            (
1837                no_collide_item1,
1838                no_collide_item2,
1839                no_collide_item3,
1840                no_collide_item4,
1841                no_collide_item5,
1842                no_collide_item6,
1843                no_collide_item7,
1844            )
1845        )
1846
1847        # Dict to check collisions with keys.
1848        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1849
1850        expected_items = []
1851
1852        for use_values in (True, False):
1853            d = rect_values if use_values else rect_keys
1854
1855            for zero_rect in (
1856                zero_rect1,
1857                zero_rect2,
1858                zero_rect3,
1859                zero_rect4,
1860                zero_rect5,
1861            ):
1862                collide_items = zero_rect.collidedictall(d, use_values)
1863
1864                self._assertCountEqual(collide_items, expected_items)
1865
1866    def test_collidedictall__zero_sized_rects_as_args(self):
1867        """Ensures collidedictall works correctly with zero sized rects
1868        as args.
1869
1870        There should be no collisions with zero sized rects.
1871        """
1872        rect = Rect(0, 0, 20, 20)
1873
1874        no_collide_item1 = ("no collide 1", Rect(2, 2, 0, 0))
1875        no_collide_item2 = ("no collide 2", Rect(2, 2, 2, 0))
1876        no_collide_item3 = ("no collide 3", Rect(2, 2, 0, 2))
1877
1878        # Dict to check collisions with values.
1879        rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3))
1880
1881        # Dict to check collisions with keys.
1882        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1883
1884        expected_items = []
1885
1886        for use_values in (True, False):
1887            d = rect_values if use_values else rect_keys
1888
1889            collide_items = rect.collidedictall(d, use_values)
1890
1891            self._assertCountEqual(collide_items, expected_items)
1892
1893    def test_collidedictall__negative_sized_rects(self):
1894        """Ensures collidedictall works correctly with negative sized rects."""
1895        neg_rect = Rect(2, 2, -2, -2)
1896
1897        collide_item1 = ("collide 1", neg_rect.copy())
1898        collide_item2 = ("collide 2", Rect(0, 0, 20, 20))
1899        no_collide_item1 = ("no collide 1", Rect(2, 2, 20, 20))
1900
1901        # Dict to check collisions with values.
1902        rect_values = dict((collide_item1, collide_item2, no_collide_item1))
1903        value_collide_items = [collide_item1, collide_item2]
1904
1905        # Dict to check collisions with keys.
1906        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1907        key_collide_items = [(tuple(v), k) for k, v in value_collide_items]
1908
1909        for use_values in (True, False):
1910            if use_values:
1911                expected_items = value_collide_items
1912                d = rect_values
1913            else:
1914                expected_items = key_collide_items
1915                d = rect_keys
1916
1917            collide_items = neg_rect.collidedictall(d, use_values)
1918
1919            self._assertCountEqual(collide_items, expected_items)
1920
1921    def test_collidedictall__negative_sized_rects_as_args(self):
1922        """Ensures collidedictall works correctly with negative sized rect
1923        args.
1924        """
1925        rect = Rect(0, 0, 10, 10)
1926
1927        collide_item1 = ("collide 1", Rect(1, 1, -1, -1))
1928        no_collide_item1 = ("no collide 1", Rect(1, 1, -1, 0))
1929        no_collide_item2 = ("no collide 2", Rect(1, 1, 0, -1))
1930
1931        # Dict to check collisions with values.
1932        rect_values = dict((collide_item1, no_collide_item1, no_collide_item2))
1933        value_collide_items = [collide_item1]
1934
1935        # Dict to check collisions with keys.
1936        rect_keys = {tuple(v): k for k, v in rect_values.items()}
1937        key_collide_items = [(tuple(v), k) for k, v in value_collide_items]
1938
1939        for use_values in (True, False):
1940            if use_values:
1941                expected_items = value_collide_items
1942                d = rect_values
1943            else:
1944                expected_items = key_collide_items
1945                d = rect_keys
1946
1947            collide_items = rect.collidedictall(d, use_values)
1948
1949            self._assertCountEqual(collide_items, expected_items)
1950
1951    def test_collidedictall__invalid_dict_format(self):
1952        """Ensures collidedictall correctly handles invalid dict parameters."""
1953        rect = Rect(0, 0, 10, 10)
1954
1955        invalid_value_dict = ("collide", rect.copy())
1956        invalid_key_dict = tuple(invalid_value_dict[1]), invalid_value_dict[0]
1957
1958        for use_values in (True, False):
1959            d = invalid_value_dict if use_values else invalid_key_dict
1960
1961            with self.assertRaises(TypeError):
1962                collide_item = rect.collidedictall(d, use_values)
1963
1964    def test_collidedictall__invalid_dict_value_format(self):
1965        """Ensures collidedictall correctly handles dicts with invalid values.
1966        """
1967        rect = Rect(0, 0, 10, 10)
1968        rect_keys = {tuple(rect): "collide"}
1969
1970        with self.assertRaises(TypeError):
1971            collide_items = rect.collidedictall(rect_keys, 1)
1972
1973    def test_collidedictall__invalid_dict_key_format(self):
1974        """Ensures collidedictall correctly handles dicts with invalid keys."""
1975        rect = Rect(0, 0, 10, 10)
1976        rect_values = {"collide": rect.copy()}
1977
1978        with self.assertRaises(TypeError):
1979            collide_items = rect.collidedictall(rect_values)
1980
1981    def test_collidedictall__invalid_use_values_format(self):
1982        """Ensures collidedictall correctly handles invalid use_values
1983        parameters.
1984        """
1985        rect = Rect(0, 0, 1, 1)
1986        d = {}
1987
1988        for invalid_param in (None, d, 1.1):
1989            with self.assertRaises(TypeError):
1990                collide_items = rect.collidedictall(d, invalid_param)
1991
1992    def test_collidelist(self):
1993
1994        # __doc__ (as of 2008-08-02) for pygame.rect.Rect.collidelist:
1995
1996        # Rect.collidelist(list): return index
1997        # test if one rectangle in a list intersects
1998        #
1999        # Test whether the rectangle collides with any in a sequence of
2000        # rectangles. The index of the first collision found is returned. If
2001        # no collisions are found an index of -1 is returned.
2002
2003        r = Rect(1, 1, 10, 10)
2004        l = [Rect(50, 50, 1, 1), Rect(5, 5, 10, 10), Rect(15, 15, 1, 1)]
2005
2006        self.assertEqual(r.collidelist(l), 1)
2007
2008        f = [Rect(50, 50, 1, 1), (100, 100, 4, 4)]
2009        self.assertEqual(r.collidelist(f), -1)
2010
2011    def test_collidelistall(self):
2012
2013        # __doc__ (as of 2008-08-02) for pygame.rect.Rect.collidelistall:
2014
2015        # Rect.collidelistall(list): return indices
2016        # test if all rectangles in a list intersect
2017        #
2018        # Returns a list of all the indices that contain rectangles that
2019        # collide with the Rect. If no intersecting rectangles are found, an
2020        # empty list is returned.
2021
2022        r = Rect(1, 1, 10, 10)
2023
2024        l = [
2025            Rect(1, 1, 10, 10),
2026            Rect(5, 5, 10, 10),
2027            Rect(15, 15, 1, 1),
2028            Rect(2, 2, 1, 1),
2029        ]
2030        self.assertEqual(r.collidelistall(l), [0, 1, 3])
2031
2032        f = [Rect(50, 50, 1, 1), Rect(20, 20, 5, 5)]
2033        self.assertFalse(r.collidelistall(f))
2034
2035    def test_fit(self):
2036
2037        # __doc__ (as of 2008-08-02) for pygame.rect.Rect.fit:
2038
2039        # Rect.fit(Rect): return Rect
2040        # resize and move a rectangle with aspect ratio
2041        #
2042        # Returns a new rectangle that is moved and resized to fit another.
2043        # The aspect ratio of the original Rect is preserved, so the new
2044        # rectangle may be smaller than the target in either width or height.
2045
2046        r = Rect(10, 10, 30, 30)
2047
2048        r2 = Rect(30, 30, 15, 10)
2049
2050        f = r.fit(r2)
2051        self.assertTrue(r2.contains(f))
2052
2053        f2 = r2.fit(r)
2054        self.assertTrue(r.contains(f2))
2055
2056    def test_copy(self):
2057        r = Rect(1, 2, 10, 20)
2058        c = r.copy()
2059        self.assertEqual(c, r)
2060
2061    def test_subscript(self):
2062        r = Rect(1, 2, 3, 4)
2063        self.assertEqual(r[0], 1)
2064        self.assertEqual(r[1], 2)
2065        self.assertEqual(r[2], 3)
2066        self.assertEqual(r[3], 4)
2067        self.assertEqual(r[-1], 4)
2068        self.assertEqual(r[-2], 3)
2069        self.assertEqual(r[-4], 1)
2070        self.assertRaises(IndexError, r.__getitem__, 5)
2071        self.assertRaises(IndexError, r.__getitem__, -5)
2072        self.assertEqual(r[0:2], [1, 2])
2073        self.assertEqual(r[0:4], [1, 2, 3, 4])
2074        self.assertEqual(r[0:-1], [1, 2, 3])
2075        self.assertEqual(r[:], [1, 2, 3, 4])
2076        self.assertEqual(r[...], [1, 2, 3, 4])
2077        self.assertEqual(r[0:4:2], [1, 3])
2078        self.assertEqual(r[0:4:3], [1, 4])
2079        self.assertEqual(r[3::-1], [4, 3, 2, 1])
2080        self.assertRaises(TypeError, r.__getitem__, None)
2081
2082    def test_ass_subscript(self):
2083        r = Rect(0, 0, 0, 0)
2084        r[...] = 1, 2, 3, 4
2085        self.assertEqual(r, [1, 2, 3, 4])
2086        self.assertRaises(TypeError, r.__setitem__, None, 0)
2087        self.assertEqual(r, [1, 2, 3, 4])
2088        self.assertRaises(TypeError, r.__setitem__, 0, "")
2089        self.assertEqual(r, [1, 2, 3, 4])
2090        self.assertRaises(IndexError, r.__setitem__, 4, 0)
2091        self.assertEqual(r, [1, 2, 3, 4])
2092        self.assertRaises(IndexError, r.__setitem__, -5, 0)
2093        self.assertEqual(r, [1, 2, 3, 4])
2094        r[0] = 10
2095        self.assertEqual(r, [10, 2, 3, 4])
2096        r[3] = 40
2097        self.assertEqual(r, [10, 2, 3, 40])
2098        r[-1] = 400
2099        self.assertEqual(r, [10, 2, 3, 400])
2100        r[-4] = 100
2101        self.assertEqual(r, [100, 2, 3, 400])
2102        r[1:3] = 0
2103        self.assertEqual(r, [100, 0, 0, 400])
2104        r[...] = 0
2105        self.assertEqual(r, [0, 0, 0, 0])
2106        r[:] = 9
2107        self.assertEqual(r, [9, 9, 9, 9])
2108        r[:] = 11, 12, 13, 14
2109        self.assertEqual(r, [11, 12, 13, 14])
2110        r[::-1] = r
2111        self.assertEqual(r, [14, 13, 12, 11])
2112
2113
2114@unittest.skipIf(IS_PYPY, "fails on pypy")
2115class SubclassTest(unittest.TestCase):
2116    class MyRect(Rect):
2117        def __init__(self, *args, **kwds):
2118            super(SubclassTest.MyRect, self).__init__(*args, **kwds)
2119            self.an_attribute = True
2120
2121    def test_copy(self):
2122        mr1 = self.MyRect(1, 2, 10, 20)
2123        self.assertTrue(mr1.an_attribute)
2124        mr2 = mr1.copy()
2125        self.assertTrue(isinstance(mr2, self.MyRect))
2126        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2127
2128    def test_move(self):
2129        mr1 = self.MyRect(1, 2, 10, 20)
2130        self.assertTrue(mr1.an_attribute)
2131        mr2 = mr1.move(1, 2)
2132        self.assertTrue(isinstance(mr2, self.MyRect))
2133        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2134
2135    def test_inflate(self):
2136        mr1 = self.MyRect(1, 2, 10, 20)
2137        self.assertTrue(mr1.an_attribute)
2138        mr2 = mr1.inflate(2, 4)
2139        self.assertTrue(isinstance(mr2, self.MyRect))
2140        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2141
2142    def test_clamp(self):
2143        mr1 = self.MyRect(19, 12, 5, 5)
2144        self.assertTrue(mr1.an_attribute)
2145        mr2 = mr1.clamp(Rect(10, 10, 10, 10))
2146        self.assertTrue(isinstance(mr2, self.MyRect))
2147        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2148
2149    def test_clip(self):
2150        mr1 = self.MyRect(1, 2, 3, 4)
2151        self.assertTrue(mr1.an_attribute)
2152        mr2 = mr1.clip(Rect(0, 0, 3, 4))
2153        self.assertTrue(isinstance(mr2, self.MyRect))
2154        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2155
2156    def test_union(self):
2157        mr1 = self.MyRect(1, 1, 1, 2)
2158        self.assertTrue(mr1.an_attribute)
2159        mr2 = mr1.union(Rect(-2, -2, 1, 2))
2160        self.assertTrue(isinstance(mr2, self.MyRect))
2161        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2162
2163    def test_unionall(self):
2164        mr1 = self.MyRect(0, 0, 1, 1)
2165        self.assertTrue(mr1.an_attribute)
2166        mr2 = mr1.unionall([Rect(-2, -2, 1, 1), Rect(2, 2, 1, 1)])
2167        self.assertTrue(isinstance(mr2, self.MyRect))
2168        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2169
2170    def test_fit(self):
2171        mr1 = self.MyRect(10, 10, 30, 30)
2172        self.assertTrue(mr1.an_attribute)
2173        mr2 = mr1.fit(Rect(30, 30, 15, 10))
2174        self.assertTrue(isinstance(mr2, self.MyRect))
2175        self.assertRaises(AttributeError, getattr, mr2, "an_attribute")
2176
2177
2178if __name__ == "__main__":
2179    unittest.main()
2180