@@ -190,6 +190,39 @@ def test_replace_roundtrip(self):
190190
191191 assert durl == durl2
192192
193+ def test_replace_fragment_percent_encoding (self ):
194+ # type: () -> None
195+ """Fragment replacement treats percent escapes as literal text."""
196+ durl = DecodedURL .from_text ("https://example.com/path?q=1#original" )
197+ for fragment in ("%41" , "%25" , "%20" , "%C3%A9" , "100%" , "" ):
198+ replaced = durl .replace (fragment = fragment )
199+ assert replaced .fragment == fragment
200+ assert DecodedURL .from_text (replaced .to_text ()).fragment == fragment
201+ assert replaced .replace (fragment = replaced .fragment ) == replaced
202+ assert replaced .path == durl .path
203+ assert replaced .query == durl .query
204+ assert durl .fragment == "original"
205+
206+ def test_replace_fragment_percent_bytes (self ):
207+ # type: () -> None
208+ """Literal percent bytes need not represent valid UTF-8."""
209+ for lazy in (False , True ):
210+ durl = DecodedURL .from_text ("https://example.com/" , lazy = lazy )
211+ replaced = durl .replace (fragment = "%FF" )
212+ assert replaced .fragment == "%FF"
213+ assert replaced .to_text () == "https://example.com/#%25FF"
214+
215+ def test_replace_fragment_preservation (self ):
216+ # type: () -> None
217+ """Reserved characters round-trip and omitted fragments stay encoded."""
218+ durl = DecodedURL .from_text ("https://example.com/#%2541" )
219+ assert durl .replace ().to_text () == durl .to_text ()
220+ assert durl .replace (query = (("q" , "1" ),)).fragment == "%41"
221+ fragment = "résumé #/?+"
222+ replaced = durl .replace (fragment = fragment )
223+ assert replaced .fragment == fragment
224+ assert DecodedURL .from_text (replaced .to_text ()).fragment == fragment
225+
193226 def test_replace_userinfo (self ):
194227 # type: () -> None
195228 durl = DecodedURL .from_text (TOTAL_URL )
0 commit comments