@@ -32470,6 +32470,76 @@ mod tests {
3247032470
3247132471 #[test]
3247232472 fn test_quoting_unquoting_urls() {
32473+ // Single quotes avoid escaping the double quotes in SVG attributes.
32474+ minify_test(
32475+ r#"
32476+ .foo {
32477+ background-image: url('data:image/svg+xml,<svg width="1" height="1"></svg>');
32478+ }
32479+ "#,
32480+ r#".foo{background-image:url('data:image/svg+xml,<svg width="1" height="1"></svg>')}"#,
32481+ );
32482+
32483+ // Keep double quotes when they are shorter or tied with single quotes.
32484+ minify_test(
32485+ r#"
32486+ .foo {
32487+ background-image: url("a 'b' c"), url('a "b" \'c\''), url('a "b" \'c');
32488+ }
32489+ "#,
32490+ r#".foo{background-image:url("a 'b' c"),url("a \"b\" 'c'"),url('a "b" \'c')}"#,
32491+ );
32492+
32493+ // Unquoted output still wins ties, including an empty URL.
32494+ minify_test(
32495+ r#"
32496+ .foo {
32497+ background-image: url('"'), url('""'), url('"""'), url('');
32498+ }
32499+ "#,
32500+ r#".foo{background-image:url(\"),url(\"\"),url('"""'),url()}"#,
32501+ );
32502+
32503+ // Escaped quotes and backslashes are parsed before choosing the quote style.
32504+ minify_test(
32505+ r#"
32506+ .foo {
32507+ background-image: url("a \\\"b\\\" \\c"), url('a "b" \\\'c');
32508+ }
32509+ "#,
32510+ r#".foo{background-image:url('a \\"b\\" \\c'),url('a "b" \\\'c')}"#,
32511+ );
32512+
32513+ // Preserve control characters and hex escape terminators before hex digits and spaces.
32514+ minify_test(
32515+ r#"
32516+ .foo {
32517+ background-image: url('"a"\a f\d \c a\1 b\7f c\9 "é😀"');
32518+ }
32519+ "#,
32520+ r#".foo{background-image:url('"a"\a f\d \c a\1 b\7f c\9 "é😀"')}"#,
32521+ );
32522+
32523+ minify_test(
32524+ r#"
32525+ .foo {
32526+ background-image: url('"a" \
32527+ b\0 c');
32528+ }
32529+ "#,
32530+ r#".foo{background-image:url('"a" b�c')}"#,
32531+ );
32532+
32533+ // Keep the existing double-quoted output when not minifying.
32534+ test(
32535+ r#"
32536+ .foo {
32537+ background-image: url('a "b" c');
32538+ }
32539+ "#,
32540+ ".foo {\n background-image: url(\"a \\\"b\\\" c\");\n}\n",
32541+ );
32542+
3247332543 // Quotes remain double quotes when not minifying
3247432544 test(
3247532545 r#".foo {
@@ -32509,6 +32579,99 @@ mod tests {
3250932579 );
3251032580 }
3251132581
32582+ #[test]
32583+ fn test_url_quote_roundtrip() {
32584+ use crate::values::url::Url;
32585+
32586+ // Exercise escape boundaries on both sides of quotes, including a literal
32587+ // backslash followed by a hex digit and non-ASCII text.
32588+ let parts = [
32589+ "\"", "'", "\\", "\n", "\r", "\x0c", "\x01", "\x7f", "\t", " ", "f", "é😀", "(", ")",
32590+ ];
32591+ for a in parts {
32592+ for b in parts {
32593+ for c in parts {
32594+ let value = format!("\"\"{a}{b}{c}\"\"");
32595+ let url = Url {
32596+ url: value.as_str().into(),
32597+ loc: crate::dependencies::Location { line: 1, column: 1 },
32598+ };
32599+ let serialized = url
32600+ .to_css_string(PrinterOptions {
32601+ minify: true,
32602+ ..Default::default()
32603+ })
32604+ .unwrap();
32605+ assert_eq!(Url::parse_string(&serialized).unwrap(), url);
32606+ }
32607+ }
32608+ }
32609+ }
32610+
32611+ #[test]
32612+ fn test_url_quoting_dependencies() {
32613+ let stylesheet = StyleSheet::parse(
32614+ r#"
32615+ .foo {
32616+ background-image: url('a "b" c');
32617+ }
32618+ "#,
32619+ ParserOptions::default(),
32620+ )
32621+ .unwrap();
32622+ let result = stylesheet
32623+ .to_css(
32624+ PrinterOptions {
32625+ minify: true,
32626+ analyze_dependencies: Some(Default::default()),
32627+ ..Default::default()
32628+ },
32629+ None::<&mut ()>,
32630+ )
32631+ .unwrap();
32632+ let dependencies = result.dependencies.unwrap();
32633+ assert_eq!(dependencies.len(), 1);
32634+ let Dependency::Url(dep) = &dependencies[0] else {
32635+ panic!("expected URL dependency")
32636+ };
32637+ assert_eq!(dep.url, r#"a "b" c"#);
32638+ assert_eq!(
32639+ result.code,
32640+ format!(".foo{{background-image:url(\"{}\")}}", dep.placeholder)
32641+ );
32642+ }
32643+
32644+ #[test]
32645+ #[cfg(feature = "sourcemap")]
32646+ fn test_url_quoting_source_map() {
32647+ let stylesheet = StyleSheet::parse(
32648+ r#"
32649+ .foo {
32650+ background-image: url('"a" \a b');
32651+ }
32652+ .bar {
32653+ color: red;
32654+ }
32655+ "#,
32656+ ParserOptions::default(),
32657+ )
32658+ .unwrap();
32659+ let mut sm = parcel_sourcemap::SourceMap::new("/");
32660+ let result = stylesheet
32661+ .to_css(
32662+ PrinterOptions {
32663+ minify: true,
32664+ ..Default::default()
32665+ },
32666+ Some(&mut sm),
32667+ )
32668+ .unwrap();
32669+ let column = result.code.find(".bar").unwrap() as u32;
32670+ let mapping = sm.find_closest_mapping(0, column).unwrap();
32671+ assert_eq!(mapping.generated_column, column);
32672+ assert_eq!(mapping.original.unwrap().original_line, 4);
32673+ }
32674+
3251232675 #[test]
3251332676 fn test_zindex() {
3251432677 minify_test(".foo { z-index: 2 }", ".foo{z-index:2}");
0 commit comments