11import pytest
22
33from bibtexparser import Library
4+ from bibtexparser import write_string
45from bibtexparser .middlewares .sorting_entry_fields import SortFieldsAlphabeticallyMiddleware
56from bibtexparser .middlewares .sorting_entry_fields import SortFieldsCustomMiddleware
67from bibtexparser .model import Entry
78from bibtexparser .model import Field
89
9- TEST_ENTRY = Entry (
10- "article" ,
11- "Cesar2013" ,
12- [
13- Field ("author" , "Cesar, J." , 1 ),
14- Field ("title" , "A title" , 2 ),
15- Field ("note" , "A note" , 3 ),
16- Field ("journal" , "A journal" , 3 ),
17- Field ("year" , "2013" , 4 ),
18- ],
19- )
10+
11+ def _test_entry ():
12+ """Return a fresh entry so in-place middleware tests cannot affect each other."""
13+ return Entry (
14+ "article" ,
15+ "Cesar2013" ,
16+ [
17+ Field ("author" , "Cesar, J." , 1 ),
18+ Field ("title" , "A title" , 2 ),
19+ Field ("note" , "A note" , 3 ),
20+ Field ("journal" , "A journal" , 3 ),
21+ Field ("year" , "2013" , 4 ),
22+ ],
23+ )
2024
2125
2226@pytest .mark .parametrize ("inplace" , [True , False ])
2327def test_sort_alphabetically (inplace : bool ):
24- library = Library (blocks = [TEST_ENTRY ])
28+ original_entry = _test_entry ()
29+ library = Library (blocks = [original_entry ])
2530 m = SortFieldsAlphabeticallyMiddleware (allow_inplace_modification = inplace )
2631 transformed = m .transform (library )
2732 entry = transformed .entries [0 ]
@@ -34,16 +39,17 @@ def test_sort_alphabetically(inplace: bool):
3439 # Sanity Checks against unintended external effects
3540 assert len (transformed .blocks ) == 1
3641 if inplace :
37- assert entry is TEST_ENTRY
42+ assert entry is original_entry
3843 else :
39- assert entry is not TEST_ENTRY
44+ assert entry is not original_entry
4045
4146
4247@pytest .mark .parametrize ("inplace" , [True , False ])
4348def test_sort_custom_order_case_insensitive (inplace : bool ):
4449 custom_order = ("author" , "yEar" , "title" , "Journal" ) # assess case insensitive
4550
46- library = Library (blocks = [TEST_ENTRY ])
51+ original_entry = _test_entry ()
52+ library = Library (blocks = [original_entry ])
4753 # case insensitivity is default
4854 m = SortFieldsCustomMiddleware (order = custom_order , allow_inplace_modification = inplace )
4955 transformed = m .transform (library )
@@ -57,28 +63,60 @@ def test_sort_custom_order_case_insensitive(inplace: bool):
5763 # Sanity Checks against unintended external effects
5864 assert len (transformed .blocks ) == 1
5965 if inplace :
60- assert entry is TEST_ENTRY
66+ assert entry is original_entry
6167 else :
62- assert entry is not TEST_ENTRY
68+ assert entry is not original_entry
6369
6470
6571def test_sort_order_custom_case_sensitive ():
6672 custom_order = ("author" , "yEar" , "title" , "Journal" ) # assess case sensitive
6773
68- library = Library (blocks = [TEST_ENTRY ])
74+ library = Library (blocks = [_test_entry () ])
6975 m = SortFieldsCustomMiddleware (order = custom_order , case_sensitive = True )
7076 transformed = m .transform (library )
7177 entry = transformed .entries [0 ]
7278 assert entry .fields [0 ].key == "author"
7379 assert entry .fields [1 ].key == "title"
7480
75- # Unspecified fields (due to case sensitivity) are appended
76- assert entry .fields [2 ].key == "year"
77-
78- # Unspecified fields (due to case sensitivity) are appended
81+ # Unspecified fields are appended in their original relative order.
82+ assert entry .fields [2 ].key == "note"
7983 assert entry .fields [3 ].key == "journal"
80-
81- # Unspecified fields are appended
82- assert entry .fields [4 ].key == "note"
84+ assert entry .fields [4 ].key == "year"
8385
8486 assert len (transformed .blocks ) == 1
87+
88+
89+ def test_custom_order_applies_during_public_write ():
90+ """The public writer applies custom ordering without mutating the source library."""
91+ original_entry = _test_entry ()
92+ library = Library (blocks = [original_entry ])
93+ middleware = SortFieldsCustomMiddleware (
94+ order = ("year" , "author" ), allow_inplace_modification = False
95+ )
96+
97+ written = write_string (library , prepend_middleware = [middleware ])
98+ written_field_keys = [
99+ line .split ("=" , maxsplit = 1 )[0 ].strip () for line in written .splitlines () if "=" in line
100+ ]
101+
102+ assert written_field_keys == ["year" , "author" , "title" , "note" , "journal" ]
103+ assert [field .key for field in original_entry .fields ] == [
104+ "author" ,
105+ "title" ,
106+ "note" ,
107+ "journal" ,
108+ "year" ,
109+ ]
110+
111+
112+ @pytest .mark .parametrize (
113+ "order" ,
114+ [
115+ pytest .param (("title" , "title" ), id = "identical" ),
116+ pytest .param (("title" , "TITLE" ), id = "case-insensitive" ),
117+ ],
118+ )
119+ def test_custom_order_rejects_duplicate_keys (order ):
120+ """Ambiguous custom orders fail early, including case-only duplicates by default."""
121+ with pytest .raises (ValueError , match = "title" ):
122+ SortFieldsCustomMiddleware (order = order )
0 commit comments