11from __future__ import annotations
22
3+ from typing import TYPE_CHECKING , Generic , TypeVar
4+
5+ import pytest
36from django .test import TestCase
47
58from model_utils import Choices
69
10+ T = TypeVar ("T" )
711
8- class ChoicesTests (TestCase ):
9- def setUp (self ) -> None :
10- self .STATUS = Choices ('DRAFT' , 'PUBLISHED' )
1112
12- def test_getattr (self ) -> None :
13- self .assertEqual (self .STATUS .DRAFT , 'DRAFT' )
13+ class ChoicesTestsMixin (Generic [T ]):
1414
15- def test_indexing (self ) -> None :
16- self .assertEqual (self .STATUS ['PUBLISHED' ], 'PUBLISHED' )
15+ STATUS : Choices [T ]
1716
18- def test_iteration (self ) -> None :
19- self .assertEqual (tuple (self .STATUS ),
20- (('DRAFT' , 'DRAFT' ), ('PUBLISHED' , 'PUBLISHED' )))
21-
22- def test_reversed (self ) -> None :
23- self .assertEqual (tuple (reversed (self .STATUS )),
24- (('PUBLISHED' , 'PUBLISHED' ), ('DRAFT' , 'DRAFT' )))
17+ def test_getattr (self ) -> None :
18+ assert self .STATUS .DRAFT == 'DRAFT'
2519
2620 def test_len (self ) -> None :
27- self . assertEqual ( len (self .STATUS ), 2 )
21+ assert len (self .STATUS ) == 2
2822
2923 def test_repr (self ) -> None :
30- self . assertEqual ( repr (self .STATUS ), "Choices" + repr ((
24+ assert repr (self .STATUS ) == "Choices" + repr ((
3125 ('DRAFT' , 'DRAFT' , 'DRAFT' ),
3226 ('PUBLISHED' , 'PUBLISHED' , 'PUBLISHED' ),
33- )))
27+ ))
3428
3529 def test_wrong_length_tuple (self ) -> None :
36- with self .assertRaises (ValueError ):
37- Choices (('a' ,))
38-
39- def test_contains_value (self ) -> None :
40- self .assertTrue ('PUBLISHED' in self .STATUS )
41- self .assertTrue ('DRAFT' in self .STATUS )
42-
43- def test_doesnt_contain_value (self ) -> None :
44- self .assertFalse ('UNPUBLISHED' in self .STATUS )
30+ with pytest .raises (ValueError ):
31+ Choices (('a' ,)) # type: ignore[arg-type]
4532
4633 def test_deepcopy (self ) -> None :
4734 import copy
48- self .assertEqual (list (self .STATUS ),
49- list (copy .deepcopy (self .STATUS )))
35+ assert list (self .STATUS ) == list (copy .deepcopy (self .STATUS ))
5036
5137 def test_equality (self ) -> None :
52- self .assertEqual ( self . STATUS , Choices ('DRAFT' , 'PUBLISHED' ) )
38+ assert self .STATUS == Choices ('DRAFT' , 'PUBLISHED' )
5339
5440 def test_inequality (self ) -> None :
55- self .assertNotEqual ( self . STATUS , ['DRAFT' , 'PUBLISHED' ])
56- self .assertNotEqual ( self . STATUS , Choices ('DRAFT' ) )
41+ assert self .STATUS != ['DRAFT' , 'PUBLISHED' ]
42+ assert self .STATUS != Choices ('DRAFT' )
5743
5844 def test_composability (self ) -> None :
59- self . assertEqual ( Choices ('DRAFT' ) + Choices ('PUBLISHED' ), self .STATUS )
60- self . assertEqual ( Choices ('DRAFT' ) + ('PUBLISHED' ,), self .STATUS )
61- self . assertEqual (( 'DRAFT' ,) + Choices ('PUBLISHED' ), self .STATUS )
45+ assert Choices ('DRAFT' ) + Choices ('PUBLISHED' ) == self .STATUS
46+ assert Choices ('DRAFT' ) + ('PUBLISHED' ,) == self .STATUS
47+ assert ( 'DRAFT' ,) + Choices ('PUBLISHED' ) == self .STATUS
6248
6349 def test_option_groups (self ) -> None :
64- c = Choices (('group a' , ['one' , 'two' ]), ['group b' , ('three' ,)])
65- self .assertEqual (
66- list (c ),
67- [
68- ('group a' , [('one' , 'one' ), ('two' , 'two' )]),
69- ('group b' , [('three' , 'three' )]),
70- ],
71- )
50+ # Note: The implementation accepts any kind of sequence, but the type system can only
51+ # track per-index types for tuples.
52+ if TYPE_CHECKING :
53+ c = Choices (('group a' , ['one' , 'two' ]), ('group b' , ('three' ,)))
54+ else :
55+ c = Choices (('group a' , ['one' , 'two' ]), ['group b' , ('three' ,)])
56+ assert list (c ) == [
57+ ('group a' , [('one' , 'one' ), ('two' , 'two' )]),
58+ ('group b' , [('three' , 'three' )]),
59+ ]
60+
61+
62+ class ChoicesTests (TestCase , ChoicesTestsMixin [str ]):
63+ def setUp (self ) -> None :
64+ self .STATUS = Choices ('DRAFT' , 'PUBLISHED' )
65+
66+ def test_indexing (self ) -> None :
67+ self .assertEqual (self .STATUS ['PUBLISHED' ], 'PUBLISHED' )
68+
69+ def test_iteration (self ) -> None :
70+ self .assertEqual (tuple (self .STATUS ),
71+ (('DRAFT' , 'DRAFT' ), ('PUBLISHED' , 'PUBLISHED' )))
72+
73+ def test_reversed (self ) -> None :
74+ self .assertEqual (tuple (reversed (self .STATUS )),
75+ (('PUBLISHED' , 'PUBLISHED' ), ('DRAFT' , 'DRAFT' )))
76+
77+ def test_contains_value (self ) -> None :
78+ self .assertTrue ('PUBLISHED' in self .STATUS )
79+ self .assertTrue ('DRAFT' in self .STATUS )
80+
81+ def test_doesnt_contain_value (self ) -> None :
82+ self .assertFalse ('UNPUBLISHED' in self .STATUS )
7283
7384
74- class LabelChoicesTests (ChoicesTests ):
85+ class LabelChoicesTests (TestCase , ChoicesTestsMixin [ str ] ):
7586 def setUp (self ) -> None :
7687 self .STATUS = Choices (
7788 ('DRAFT' , 'is draft' ),
@@ -157,10 +168,16 @@ def test_composability(self) -> None:
157168 )
158169
159170 def test_option_groups (self ) -> None :
160- c = Choices (
161- ('group a' , [(1 , 'one' ), (2 , 'two' )]),
162- ['group b' , ((3 , 'three' ),)]
163- )
171+ if TYPE_CHECKING :
172+ c = Choices [int ](
173+ ('group a' , [(1 , 'one' ), (2 , 'two' )]),
174+ ('group b' , ((3 , 'three' ),))
175+ )
176+ else :
177+ c = Choices (
178+ ('group a' , [(1 , 'one' ), (2 , 'two' )]),
179+ ['group b' , ((3 , 'three' ),)]
180+ )
164181 self .assertEqual (
165182 list (c ),
166183 [
@@ -170,7 +187,7 @@ def test_option_groups(self) -> None:
170187 )
171188
172189
173- class IdentifierChoicesTests (ChoicesTests ):
190+ class IdentifierChoicesTests (TestCase , ChoicesTestsMixin [ int ] ):
174191 def setUp (self ) -> None :
175192 self .STATUS = Choices (
176193 (0 , 'DRAFT' , 'is draft' ),
@@ -216,10 +233,10 @@ def test_doesnt_contain_value(self) -> None:
216233 self .assertFalse (3 in self .STATUS )
217234
218235 def test_doesnt_contain_display_value (self ) -> None :
219- self .assertFalse ('is draft' in self .STATUS )
236+ self .assertFalse ('is draft' in self .STATUS ) # type: ignore[operator]
220237
221238 def test_doesnt_contain_python_attr (self ) -> None :
222- self .assertFalse ('PUBLISHED' in self .STATUS )
239+ self .assertFalse ('PUBLISHED' in self .STATUS ) # type: ignore[operator]
223240
224241 def test_equality (self ) -> None :
225242 self .assertEqual (self .STATUS , Choices (
@@ -268,10 +285,16 @@ def test_composability(self) -> None:
268285 )
269286
270287 def test_option_groups (self ) -> None :
271- c = Choices (
272- ('group a' , [(1 , 'ONE' , 'one' ), (2 , 'TWO' , 'two' )]),
273- ['group b' , ((3 , 'THREE' , 'three' ),)]
274- )
288+ if TYPE_CHECKING :
289+ c = Choices [int ](
290+ ('group a' , [(1 , 'ONE' , 'one' ), (2 , 'TWO' , 'two' )]),
291+ ('group b' , ((3 , 'THREE' , 'three' ),))
292+ )
293+ else :
294+ c = Choices (
295+ ('group a' , [(1 , 'ONE' , 'one' ), (2 , 'TWO' , 'two' )]),
296+ ['group b' , ((3 , 'THREE' , 'three' ),)]
297+ )
275298 self .assertEqual (
276299 list (c ),
277300 [
@@ -284,7 +307,7 @@ def test_option_groups(self) -> None:
284307class SubsetChoicesTest (TestCase ):
285308
286309 def setUp (self ) -> None :
287- self .choices = Choices (
310+ self .choices = Choices [ int ] (
288311 (0 , 'a' , 'A' ),
289312 (1 , 'b' , 'B' ),
290313 )
0 commit comments