1
1
from __future__ import annotations
2
2
3
+ from typing import TYPE_CHECKING , Generic , TypeVar
4
+
5
+ import pytest
3
6
from django .test import TestCase
4
7
5
8
from model_utils import Choices
6
9
10
+ T = TypeVar ("T" )
7
11
8
- class ChoicesTests (TestCase ):
9
- def setUp (self ) -> None :
10
- self .STATUS = Choices ('DRAFT' , 'PUBLISHED' )
11
12
12
- def test_getattr (self ) -> None :
13
- self .assertEqual (self .STATUS .DRAFT , 'DRAFT' )
13
+ class ChoicesTestsMixin (Generic [T ]):
14
14
15
- def test_indexing (self ) -> None :
16
- self .assertEqual (self .STATUS ['PUBLISHED' ], 'PUBLISHED' )
15
+ STATUS : Choices [T ]
17
16
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'
25
19
26
20
def test_len (self ) -> None :
27
- self . assertEqual ( len (self .STATUS ), 2 )
21
+ assert len (self .STATUS ) == 2
28
22
29
23
def test_repr (self ) -> None :
30
- self . assertEqual ( repr (self .STATUS ), "Choices" + repr ((
24
+ assert repr (self .STATUS ) == "Choices" + repr ((
31
25
('DRAFT' , 'DRAFT' , 'DRAFT' ),
32
26
('PUBLISHED' , 'PUBLISHED' , 'PUBLISHED' ),
33
- )))
27
+ ))
34
28
35
29
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]
45
32
46
33
def test_deepcopy (self ) -> None :
47
34
import copy
48
- self .assertEqual (list (self .STATUS ),
49
- list (copy .deepcopy (self .STATUS )))
35
+ assert list (self .STATUS ) == list (copy .deepcopy (self .STATUS ))
50
36
51
37
def test_equality (self ) -> None :
52
- self .assertEqual ( self . STATUS , Choices ('DRAFT' , 'PUBLISHED' ) )
38
+ assert self .STATUS == Choices ('DRAFT' , 'PUBLISHED' )
53
39
54
40
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' )
57
43
58
44
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
62
48
63
49
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 )
72
83
73
84
74
- class LabelChoicesTests (ChoicesTests ):
85
+ class LabelChoicesTests (TestCase , ChoicesTestsMixin [ str ] ):
75
86
def setUp (self ) -> None :
76
87
self .STATUS = Choices (
77
88
('DRAFT' , 'is draft' ),
@@ -157,10 +168,16 @@ def test_composability(self) -> None:
157
168
)
158
169
159
170
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
+ )
164
181
self .assertEqual (
165
182
list (c ),
166
183
[
@@ -170,7 +187,7 @@ def test_option_groups(self) -> None:
170
187
)
171
188
172
189
173
- class IdentifierChoicesTests (ChoicesTests ):
190
+ class IdentifierChoicesTests (TestCase , ChoicesTestsMixin [ int ] ):
174
191
def setUp (self ) -> None :
175
192
self .STATUS = Choices (
176
193
(0 , 'DRAFT' , 'is draft' ),
@@ -216,10 +233,10 @@ def test_doesnt_contain_value(self) -> None:
216
233
self .assertFalse (3 in self .STATUS )
217
234
218
235
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]
220
237
221
238
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]
223
240
224
241
def test_equality (self ) -> None :
225
242
self .assertEqual (self .STATUS , Choices (
@@ -268,10 +285,16 @@ def test_composability(self) -> None:
268
285
)
269
286
270
287
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
+ )
275
298
self .assertEqual (
276
299
list (c ),
277
300
[
@@ -284,7 +307,7 @@ def test_option_groups(self) -> None:
284
307
class SubsetChoicesTest (TestCase ):
285
308
286
309
def setUp (self ) -> None :
287
- self .choices = Choices (
310
+ self .choices = Choices [ int ] (
288
311
(0 , 'a' , 'A' ),
289
312
(1 , 'b' , 'B' ),
290
313
)
0 commit comments