1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 use properties::{parse, parse_input};
6 use style::computed_values::display::T as Display;
7 use style::properties::{PropertyDeclaration, Importance};
8 use style::properties::declaration_block::PropertyDeclarationBlock;
9 use style::properties::parse_property_declaration_list;
10 use style::values::RGBA;
11 use style::values::specified::{BorderStyle, BorderSideWidth, Color};
12 use style::values::specified::{Length, LengthPercentage, LengthPercentageOrAuto};
13 use style::values::specified::NoCalcLength;
14 use style::values::specified::url::SpecifiedUrl;
15 use style_traits::ToCss;
16 use stylesheets::block_from;
17 
18 trait ToCssString {
to_css_string(&self) -> String19     fn to_css_string(&self) -> String;
20 }
21 
22 impl ToCssString for PropertyDeclarationBlock {
to_css_string(&self) -> String23     fn to_css_string(&self) -> String {
24         let mut css = String::new();
25         self.to_css(&mut css).unwrap();
26         css
27     }
28 }
29 
30 #[test]
property_declaration_block_should_serialize_correctly()31 fn property_declaration_block_should_serialize_correctly() {
32     use style::properties::longhands::overflow_x::SpecifiedValue as OverflowValue;
33 
34     let declarations = vec![
35         (PropertyDeclaration::Width(
36             LengthPercentageOrAuto::Length(NoCalcLength::from_px(70f32))),
37          Importance::Normal),
38 
39         (PropertyDeclaration::MinHeight(
40             LengthPercentage::Length(NoCalcLength::from_px(20f32))),
41          Importance::Normal),
42 
43         (PropertyDeclaration::Height(
44             LengthPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
45          Importance::Important),
46 
47         (PropertyDeclaration::Display(Display::InlineBlock),
48          Importance::Normal),
49 
50         (PropertyDeclaration::OverflowX(
51             OverflowValue::Auto),
52          Importance::Normal),
53 
54         (PropertyDeclaration::OverflowY(
55             OverflowValue::Auto),
56          Importance::Normal),
57     ];
58 
59     let block = block_from(declarations);
60 
61     let css_string = block.to_css_string();
62 
63     assert_eq!(
64         css_string,
65         "width: 70px; min-height: 20px; height: 20px !important; display: inline-block; overflow: auto;"
66     );
67 }
68 
69 mod shorthand_serialization {
70     pub use super::*;
71 
shorthand_properties_to_string(properties: Vec<PropertyDeclaration>) -> String72     pub fn shorthand_properties_to_string(properties: Vec<PropertyDeclaration>) -> String {
73         let block = block_from(properties.into_iter().map(|d| (d, Importance::Normal)));
74 
75         block.to_css_string()
76     }
77 
78     mod four_sides_shorthands {
79         pub use super::*;
80 
81         // we can use margin as a base to test out the different combinations
82         // but afterwards, we only need to to one test per "four sides shorthand"
83         #[test]
all_equal_properties_should_serialize_to_one_value()84         fn all_equal_properties_should_serialize_to_one_value() {
85             let mut properties = Vec::new();
86 
87             let px_70 = LengthPercentageOrAuto::Length(NoCalcLength::from_px(70f32));
88             properties.push(PropertyDeclaration::MarginTop(px_70.clone()));
89             properties.push(PropertyDeclaration::MarginRight(px_70.clone()));
90             properties.push(PropertyDeclaration::MarginBottom(px_70.clone()));
91             properties.push(PropertyDeclaration::MarginLeft(px_70));
92 
93             let serialization = shorthand_properties_to_string(properties);
94             assert_eq!(serialization, "margin: 70px;");
95         }
96 
97         #[test]
equal_vertical_and_equal_horizontal_properties_should_serialize_to_two_value()98         fn equal_vertical_and_equal_horizontal_properties_should_serialize_to_two_value() {
99             let mut properties = Vec::new();
100 
101             let vertical_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(10f32));
102             let horizontal_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(5f32));
103 
104             properties.push(PropertyDeclaration::MarginTop(vertical_px.clone()));
105             properties.push(PropertyDeclaration::MarginRight(horizontal_px.clone()));
106             properties.push(PropertyDeclaration::MarginBottom(vertical_px));
107             properties.push(PropertyDeclaration::MarginLeft(horizontal_px));
108 
109             let serialization = shorthand_properties_to_string(properties);
110             assert_eq!(serialization, "margin: 10px 5px;");
111         }
112 
113         #[test]
different_vertical_and_equal_horizontal_properties_should_serialize_to_three_values()114         fn different_vertical_and_equal_horizontal_properties_should_serialize_to_three_values() {
115             let mut properties = Vec::new();
116 
117             let top_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(8f32));
118             let bottom_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(10f32));
119             let horizontal_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(5f32));
120 
121             properties.push(PropertyDeclaration::MarginTop(top_px));
122             properties.push(PropertyDeclaration::MarginRight(horizontal_px.clone()));
123             properties.push(PropertyDeclaration::MarginBottom(bottom_px));
124             properties.push(PropertyDeclaration::MarginLeft(horizontal_px));
125 
126             let serialization = shorthand_properties_to_string(properties);
127             assert_eq!(serialization, "margin: 8px 5px 10px;");
128         }
129 
130         #[test]
different_properties_should_serialize_to_four_values()131         fn different_properties_should_serialize_to_four_values() {
132             let mut properties = Vec::new();
133 
134             let top_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(8f32));
135             let right_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(12f32));
136             let bottom_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(10f32));
137             let left_px = LengthPercentageOrAuto::Length(NoCalcLength::from_px(14f32));
138 
139             properties.push(PropertyDeclaration::MarginTop(top_px));
140             properties.push(PropertyDeclaration::MarginRight(right_px));
141             properties.push(PropertyDeclaration::MarginBottom(bottom_px));
142             properties.push(PropertyDeclaration::MarginLeft(left_px));
143 
144             let serialization = shorthand_properties_to_string(properties);
145             assert_eq!(serialization, "margin: 8px 12px 10px 14px;");
146         }
147 
148         #[test]
different_longhands_should_serialize_to_long_form()149         fn different_longhands_should_serialize_to_long_form() {
150           let mut properties = Vec::new();
151 
152           let solid = BorderStyle::Solid;
153 
154           properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
155           properties.push(PropertyDeclaration::BorderRightStyle(solid.clone()));
156           properties.push(PropertyDeclaration::BorderBottomStyle(solid.clone()));
157           properties.push(PropertyDeclaration::BorderLeftStyle(solid.clone()));
158 
159           let px_30 = BorderSideWidth::Length(Length::from_px(30f32));
160           let px_10 = BorderSideWidth::Length(Length::from_px(10f32));
161 
162           properties.push(PropertyDeclaration::BorderTopWidth(px_30.clone()));
163           properties.push(PropertyDeclaration::BorderRightWidth(px_30.clone()));
164           properties.push(PropertyDeclaration::BorderBottomWidth(px_30.clone()));
165           properties.push(PropertyDeclaration::BorderLeftWidth(px_10.clone()));
166 
167           let blue = Color::rgba(RGBA::new(0, 0, 255, 255));
168 
169           properties.push(PropertyDeclaration::BorderTopColor(blue.clone()));
170           properties.push(PropertyDeclaration::BorderRightColor(blue.clone()));
171           properties.push(PropertyDeclaration::BorderBottomColor(blue.clone()));
172           properties.push(PropertyDeclaration::BorderLeftColor(blue.clone()));
173 
174           let serialization = shorthand_properties_to_string(properties);
175           assert_eq!(serialization,
176           "border-style: solid; border-width: 30px 30px 30px 10px; border-color: rgb(0, 0, 255);");
177         }
178 
179         #[test]
same_longhands_should_serialize_correctly()180         fn same_longhands_should_serialize_correctly() {
181           let mut properties = Vec::new();
182 
183           let solid = BorderStyle::Solid;
184 
185           properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
186           properties.push(PropertyDeclaration::BorderRightStyle(solid.clone()));
187           properties.push(PropertyDeclaration::BorderBottomStyle(solid.clone()));
188           properties.push(PropertyDeclaration::BorderLeftStyle(solid.clone()));
189 
190           let px_30 = BorderSideWidth::Length(Length::from_px(30f32));
191 
192           properties.push(PropertyDeclaration::BorderTopWidth(px_30.clone()));
193           properties.push(PropertyDeclaration::BorderRightWidth(px_30.clone()));
194           properties.push(PropertyDeclaration::BorderBottomWidth(px_30.clone()));
195           properties.push(PropertyDeclaration::BorderLeftWidth(px_30.clone()));
196 
197           let blue = Color::rgba(RGBA::new(0, 0, 255, 255));
198 
199           properties.push(PropertyDeclaration::BorderTopColor(blue.clone()));
200           properties.push(PropertyDeclaration::BorderRightColor(blue.clone()));
201           properties.push(PropertyDeclaration::BorderBottomColor(blue.clone()));
202           properties.push(PropertyDeclaration::BorderLeftColor(blue.clone()));
203 
204           let serialization = shorthand_properties_to_string(properties);
205           assert_eq!(serialization, "border-style: solid; border-width: 30px; border-color: rgb(0, 0, 255);");
206         }
207 
208         #[test]
padding_should_serialize_correctly()209         fn padding_should_serialize_correctly() {
210             use style::values::specified::NonNegativeLengthPercentage;
211 
212             let mut properties = Vec::new();
213 
214             let px_10: NonNegativeLengthPercentage = NoCalcLength::from_px(10f32).into();
215             let px_15: NonNegativeLengthPercentage = NoCalcLength::from_px(15f32).into();
216             properties.push(PropertyDeclaration::PaddingTop(px_10.clone()));
217             properties.push(PropertyDeclaration::PaddingRight(px_15.clone()));
218             properties.push(PropertyDeclaration::PaddingBottom(px_10));
219             properties.push(PropertyDeclaration::PaddingLeft(px_15));
220 
221             let serialization = shorthand_properties_to_string(properties);
222             assert_eq!(serialization, "padding: 10px 15px;");
223         }
224 
225         #[test]
border_width_should_serialize_correctly()226         fn border_width_should_serialize_correctly() {
227             let mut properties = Vec::new();
228 
229             let top_px = BorderSideWidth::Length(Length::from_px(10f32));
230             let bottom_px = BorderSideWidth::Length(Length::from_px(10f32));
231 
232             let right_px = BorderSideWidth::Length(Length::from_px(15f32));
233             let left_px = BorderSideWidth::Length(Length::from_px(15f32));
234 
235             properties.push(PropertyDeclaration::BorderTopWidth(top_px));
236             properties.push(PropertyDeclaration::BorderRightWidth(right_px));
237             properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px));
238             properties.push(PropertyDeclaration::BorderLeftWidth(left_px));
239 
240             let serialization = shorthand_properties_to_string(properties);
241             assert_eq!(serialization, "border-width: 10px 15px;");
242         }
243 
244         #[test]
border_width_with_keywords_should_serialize_correctly()245         fn border_width_with_keywords_should_serialize_correctly() {
246             let mut properties = Vec::new();
247 
248             let top_px = BorderSideWidth::Thin;
249             let right_px = BorderSideWidth::Medium;
250             let bottom_px = BorderSideWidth::Thick;
251             let left_px = BorderSideWidth::Length(Length::from_px(15f32));
252 
253             properties.push(PropertyDeclaration::BorderTopWidth(top_px));
254             properties.push(PropertyDeclaration::BorderRightWidth(right_px));
255             properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px));
256             properties.push(PropertyDeclaration::BorderLeftWidth(left_px));
257 
258             let serialization = shorthand_properties_to_string(properties);
259             assert_eq!(serialization, "border-width: thin medium thick 15px;");
260         }
261 
262         #[test]
border_color_should_serialize_correctly()263         fn border_color_should_serialize_correctly() {
264             let mut properties = Vec::new();
265 
266             let red = Color::rgba(RGBA::new(255, 0, 0, 255));
267             let blue = Color::rgba(RGBA::new(0, 0, 255, 255));
268 
269             properties.push(PropertyDeclaration::BorderTopColor(blue.clone()));
270             properties.push(PropertyDeclaration::BorderRightColor(red.clone()));
271             properties.push(PropertyDeclaration::BorderBottomColor(blue));
272             properties.push(PropertyDeclaration::BorderLeftColor(red));
273 
274             let serialization = shorthand_properties_to_string(properties);
275 
276             // TODO: Make the rgb test show border-color as blue red instead of below tuples
277             assert_eq!(serialization, "border-color: rgb(0, 0, 255) rgb(255, 0, 0);");
278         }
279 
280         #[test]
border_style_should_serialize_correctly()281         fn border_style_should_serialize_correctly() {
282             let mut properties = Vec::new();
283 
284             let solid = BorderStyle::Solid;
285             let dotted = BorderStyle::Dotted;
286             properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
287             properties.push(PropertyDeclaration::BorderRightStyle(dotted.clone()));
288             properties.push(PropertyDeclaration::BorderBottomStyle(solid));
289             properties.push(PropertyDeclaration::BorderLeftStyle(dotted));
290 
291             let serialization = shorthand_properties_to_string(properties);
292             assert_eq!(serialization, "border-style: solid dotted;");
293         }
294 
295         use style::values::specified::{BorderCornerRadius, Percentage};
296 
297         #[test]
border_radius_should_serialize_correctly()298         fn border_radius_should_serialize_correctly() {
299             let mut properties = Vec::new();
300             properties.push(PropertyDeclaration::BorderTopLeftRadius(Box::new(BorderCornerRadius::new(
301                 Percentage::new(0.01).into(), Percentage::new(0.05).into()
302             ))));
303             properties.push(PropertyDeclaration::BorderTopRightRadius(Box::new(BorderCornerRadius::new(
304                 Percentage::new(0.02).into(), Percentage::new(0.06).into()
305             ))));
306             properties.push(PropertyDeclaration::BorderBottomRightRadius(Box::new(BorderCornerRadius::new(
307                 Percentage::new(0.03).into(), Percentage::new(0.07).into()
308             ))));
309             properties.push(PropertyDeclaration::BorderBottomLeftRadius(Box::new(BorderCornerRadius::new(
310                 Percentage::new(0.04).into(), Percentage::new(0.08).into()
311             ))));
312 
313             let serialization = shorthand_properties_to_string(properties);
314             assert_eq!(serialization, "border-radius: 1% 2% 3% 4% / 5% 6% 7% 8%;");
315         }
316     }
317 
318 
319     mod border_shorthands {
320         use super::*;
321 
322         #[test]
border_top_and_color()323         fn border_top_and_color() {
324             let mut properties = Vec::new();
325             properties.push(PropertyDeclaration::BorderTopWidth(BorderSideWidth::Length(Length::from_px(1.))));
326             properties.push(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
327             let c = Color::Numeric {
328                 parsed: RGBA::new(255, 0, 0, 255),
329                 authored: Some("green".to_string().into_boxed_str())
330             };
331             properties.push(PropertyDeclaration::BorderTopColor(c));
332             let c = Color::Numeric {
333                 parsed: RGBA::new(0, 255, 0, 255),
334                 authored: Some("red".to_string().into_boxed_str())
335             };
336             properties.push(PropertyDeclaration::BorderTopColor(c.clone()));
337             properties.push(PropertyDeclaration::BorderBottomColor(c.clone()));
338             properties.push(PropertyDeclaration::BorderLeftColor(c.clone()));
339             properties.push(PropertyDeclaration::BorderRightColor(c.clone()));
340 
341             let serialization = shorthand_properties_to_string(properties);
342             assert_eq!(serialization, "border-top: 1px solid red; border-color: red;");
343         }
344 
345         #[test]
border_color_and_top()346         fn border_color_and_top() {
347             let mut properties = Vec::new();
348                 let c = Color::Numeric {
349                 parsed: RGBA::new(0, 255, 0, 255),
350                 authored: Some("red".to_string().into_boxed_str())
351             };
352             properties.push(PropertyDeclaration::BorderTopColor(c.clone()));
353             properties.push(PropertyDeclaration::BorderBottomColor(c.clone()));
354             properties.push(PropertyDeclaration::BorderLeftColor(c.clone()));
355             properties.push(PropertyDeclaration::BorderRightColor(c.clone()));
356 
357             properties.push(PropertyDeclaration::BorderTopWidth(BorderSideWidth::Length(Length::from_px(1.))));
358             properties.push(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
359             let c = Color::Numeric {
360                 parsed: RGBA::new(255, 0, 0, 255),
361                 authored: Some("green".to_string().into_boxed_str())
362             };
363             properties.push(PropertyDeclaration::BorderTopColor(c));
364 
365             let serialization = shorthand_properties_to_string(properties);
366             assert_eq!(serialization, "border-color: green red red; border-top: 1px solid green;");
367         }
368 
369         // we can use border-top as a base to test out the different combinations
370         // but afterwards, we only need to to one test per "directional border shorthand"
371 
372         #[test]
directional_border_should_show_all_properties_when_values_are_set()373         fn directional_border_should_show_all_properties_when_values_are_set() {
374             let mut properties = Vec::new();
375 
376             let width = BorderSideWidth::Length(Length::from_px(4f32));
377             let style = BorderStyle::Solid;
378             let color = RGBA::new(255, 0, 0, 255).into();
379 
380             properties.push(PropertyDeclaration::BorderTopWidth(width));
381             properties.push(PropertyDeclaration::BorderTopStyle(style));
382             properties.push(PropertyDeclaration::BorderTopColor(color));
383 
384             let serialization = shorthand_properties_to_string(properties);
385             assert_eq!(serialization, "border-top: 4px solid rgb(255, 0, 0);");
386         }
387 
get_border_property_values() -> (BorderSideWidth, BorderStyle, Color)388         fn get_border_property_values() -> (BorderSideWidth, BorderStyle, Color) {
389             (BorderSideWidth::Length(Length::from_px(4f32)),
390              BorderStyle::Solid,
391              Color::currentcolor())
392         }
393 
394         #[test]
border_top_should_serialize_correctly()395         fn border_top_should_serialize_correctly() {
396             let mut properties = Vec::new();
397             let (width, style, color) = get_border_property_values();
398             properties.push(PropertyDeclaration::BorderTopWidth(width));
399             properties.push(PropertyDeclaration::BorderTopStyle(style));
400             properties.push(PropertyDeclaration::BorderTopColor(color));
401 
402             let serialization = shorthand_properties_to_string(properties);
403             assert_eq!(serialization, "border-top: 4px solid;");
404         }
405 
406         #[test]
border_right_should_serialize_correctly()407         fn border_right_should_serialize_correctly() {
408             let mut properties = Vec::new();
409             let (width, style, color) = get_border_property_values();
410             properties.push(PropertyDeclaration::BorderRightWidth(width));
411             properties.push(PropertyDeclaration::BorderRightStyle(style));
412             properties.push(PropertyDeclaration::BorderRightColor(color));
413 
414             let serialization = shorthand_properties_to_string(properties);
415             assert_eq!(serialization, "border-right: 4px solid;");
416         }
417 
418         #[test]
border_bottom_should_serialize_correctly()419         fn border_bottom_should_serialize_correctly() {
420             let mut properties = Vec::new();
421             let (width, style, color) = get_border_property_values();
422             properties.push(PropertyDeclaration::BorderBottomWidth(width));
423             properties.push(PropertyDeclaration::BorderBottomStyle(style));
424             properties.push(PropertyDeclaration::BorderBottomColor(color));
425 
426             let serialization = shorthand_properties_to_string(properties);
427             assert_eq!(serialization, "border-bottom: 4px solid;");
428         }
429 
430         #[test]
border_left_should_serialize_correctly()431         fn border_left_should_serialize_correctly() {
432             let mut properties = Vec::new();
433             let (width, style, color) = get_border_property_values();
434             properties.push(PropertyDeclaration::BorderLeftWidth(width));
435             properties.push(PropertyDeclaration::BorderLeftStyle(style));
436             properties.push(PropertyDeclaration::BorderLeftColor(color));
437 
438             let serialization = shorthand_properties_to_string(properties);
439             assert_eq!(serialization, "border-left: 4px solid;");
440         }
441 
442         #[test]
border_should_serialize_correctly()443         fn border_should_serialize_correctly() {
444             // According to https://drafts.csswg.org/css-backgrounds-3/#the-border-shorthands,
445             // the ‘border’ shorthand resets ‘border-image’ to its initial value. To verify the
446             // serialization of 'border' shorthand, we need to set 'border-image' as well.
447             let block_text = "\
448                 border-top: 4px solid; \
449                 border-right: 4px solid; \
450                 border-bottom: 4px solid; \
451                 border-left: 4px solid; \
452                 border-image: none;";
453 
454             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
455 
456             let serialization = block.to_css_string();
457 
458             assert_eq!(serialization, "border: 4px solid;");
459         }
460     }
461 
462     mod background {
463         use super::*;
464 
465         #[test]
background_should_serialize_all_available_properties_when_specified()466         fn background_should_serialize_all_available_properties_when_specified() {
467             let block_text = "\
468                 background-color: rgb(255, 0, 0); \
469                 background-image: url(\"http://servo/test.png\"); \
470                 background-repeat: repeat-x; \
471                 background-attachment: scroll; \
472                 background-size: 70px 50px; \
473                 background-position-x: 7px; \
474                 background-position-y: bottom 4px; \
475                 background-origin: border-box; \
476                 background-clip: padding-box;";
477 
478             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
479 
480             let serialization = block.to_css_string();
481 
482             assert_eq!(
483                 serialization,
484                 "background: rgb(255, 0, 0) url(\"http://servo/test.png\") repeat-x \
485                 scroll left 7px bottom 4px / 70px 50px border-box padding-box;"
486             );
487         }
488 
489         #[test]
background_should_combine_origin_and_clip_properties_when_equal()490         fn background_should_combine_origin_and_clip_properties_when_equal() {
491             let block_text = "\
492                 background-color: rgb(255, 0, 0); \
493                 background-image: url(\"http://servo/test.png\"); \
494                 background-repeat: repeat-x; \
495                 background-attachment: scroll; \
496                 background-size: 70px 50px; \
497                 background-position-x: 7px; \
498                 background-position-y: 4px; \
499                 background-origin: padding-box; \
500                 background-clip: padding-box;";
501 
502             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
503 
504             let serialization = block.to_css_string();
505 
506             assert_eq!(
507                 serialization,
508                 "background: rgb(255, 0, 0) url(\"http://servo/test.png\") repeat-x \
509                 scroll 7px 4px / 70px 50px padding-box;"
510             );
511         }
512 
513         #[test]
serialize_multiple_backgrounds()514         fn serialize_multiple_backgrounds() {
515             let block_text = "\
516                 background-color: rgb(0, 0, 255); \
517                 background-image: url(\"http://servo/test.png\"), none; \
518                 background-repeat: repeat-x, repeat-y; \
519                 background-attachment: scroll, scroll; \
520                 background-size: 70px 50px, 20px 30px; \
521                 background-position-x: 7px, 70px; \
522                 background-position-y: 4px, 40px; \
523                 background-origin: border-box, padding-box; \
524                 background-clip: padding-box, padding-box;";
525 
526             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
527 
528             let serialization = block.to_css_string();
529 
530             assert_eq!(
531                 serialization, "background: \
532                 url(\"http://servo/test.png\") repeat-x scroll 7px 4px / 70px 50px border-box padding-box, \
533                 rgb(0, 0, 255) none repeat-y scroll 70px 40px / 20px 30px padding-box;"
534             );
535         }
536 
537         #[test]
serialize_multiple_backgrounds_unequal_property_lists()538         fn serialize_multiple_backgrounds_unequal_property_lists() {
539             // When the lengths of property values are different, the shorthand serialization
540             // should not be used. Previously the implementation cycled values if the lists were
541             // uneven. This is incorrect, in that we should serialize to a shorthand only when the
542             // lists have the same length (this affects background, transition and animation).
543             // https://github.com/servo/servo/issues/15398 )
544             // With background, the color is one exception as it should only appear once for
545             // multiple backgrounds.
546             // Below background-origin only has one value.
547             let block_text = "\
548                 background-color: rgb(0, 0, 255); \
549                 background-image: url(\"http://servo/test.png\"), none; \
550                 background-repeat: repeat-x, repeat-y; \
551                 background-attachment: scroll, scroll; \
552                 background-size: 70px 50px, 20px 30px; \
553                 background-position: 7px 4px, 5px 6px; \
554                 background-origin: border-box; \
555                 background-clip: padding-box, padding-box;";
556 
557             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
558 
559             let serialization = block.to_css_string();
560 
561             assert_eq!(serialization, block_text);
562         }
563 
564         #[test]
background_position_should_be_a_valid_form_its_longhands()565         fn background_position_should_be_a_valid_form_its_longhands() {
566             // If there is any longhand consisted of both keyword and position,
567             // the shorthand result should be the 4-value format.
568             let block_text = "\
569                 background-position-x: 30px;\
570                 background-position-y: bottom 20px;";
571             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
572             let serialization = block.to_css_string();
573             assert_eq!(serialization, "background-position: left 30px bottom 20px;");
574 
575             // If there is no longhand consisted of both keyword and position,
576             // the shorthand result should be the 2-value format.
577             let block_text = "\
578                 background-position-x: center;\
579                 background-position-y: 20px;";
580             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
581             let serialization = block.to_css_string();
582             assert_eq!(serialization, "background-position: center 20px;");
583         }
584     }
585 
586     mod quotes {
587         pub use super::*;
588 
589         #[test]
should_serialize_none_correctly()590         fn should_serialize_none_correctly() {
591             use style::properties::longhands::quotes;
592 
593             assert_roundtrip_with_context!(quotes::parse, "none");
594         }
595     }
596 
597     mod animation {
598         pub use super::*;
599 
600         #[test]
serialize_single_animation()601         fn serialize_single_animation() {
602             let block_text = "\
603                 animation-name: bounce;\
604                 animation-duration: 1s;\
605                 animation-timing-function: ease-in;\
606                 animation-delay: 0s;\
607                 animation-direction: normal;\
608                 animation-fill-mode: forwards;\
609                 animation-iteration-count: infinite;\
610                 animation-play-state: paused;";
611 
612             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
613 
614             let serialization = block.to_css_string();
615 
616             assert_eq!(serialization, "animation: 1s ease-in 0s infinite normal forwards paused bounce;")
617         }
618 
619         #[test]
serialize_multiple_animations()620         fn serialize_multiple_animations() {
621             let block_text = "\
622                 animation-name: bounce, roll;\
623                 animation-duration: 1s, 0.2s;\
624                 animation-timing-function: ease-in, linear;\
625                 animation-delay: 0s, 1s;\
626                 animation-direction: normal, reverse;\
627                 animation-fill-mode: forwards, backwards;\
628                 animation-iteration-count: infinite, 2;\
629                 animation-play-state: paused, running;";
630 
631             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
632 
633             let serialization = block.to_css_string();
634 
635             assert_eq!(serialization,
636                        "animation: 1s ease-in 0s infinite normal forwards paused bounce, \
637                                    0.2s linear 1s 2 reverse backwards running roll;");
638         }
639 
640         #[test]
serialize_multiple_animations_unequal_property_lists()641         fn serialize_multiple_animations_unequal_property_lists() {
642             // When the lengths of property values are different, the shorthand serialization
643             // should not be used. Previously the implementation cycled values if the lists were
644             // uneven. This is incorrect, in that we should serialize to a shorthand only when the
645             // lists have the same length (this affects background, transition and animation).
646             // https://github.com/servo/servo/issues/15398 )
647             let block_text = "\
648                 animation-name: bounce, roll, flip, jump; \
649                 animation-duration: 1s, 0.2s; \
650                 animation-timing-function: ease-in, linear; \
651                 animation-delay: 0s, 1s, 0.5s; \
652                 animation-direction: normal; \
653                 animation-fill-mode: forwards, backwards; \
654                 animation-iteration-count: infinite, 2; \
655                 animation-play-state: paused, running;";
656 
657             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
658 
659             let serialization = block.to_css_string();
660 
661             assert_eq!(serialization, block_text);
662         }
663 
664         #[test]
serialize_multiple_without_all_properties_returns_longhand()665         fn serialize_multiple_without_all_properties_returns_longhand() {
666             // timing function and direction are missing, so no shorthand is returned.
667             let block_text = "animation-name: bounce, roll; \
668                               animation-duration: 1s, 0.2s; \
669                               animation-delay: 0s, 1s; \
670                               animation-fill-mode: forwards, backwards; \
671                               animation-iteration-count: infinite, 2; \
672                               animation-play-state: paused, running;";
673 
674             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
675 
676             let serialization = block.to_css_string();
677 
678             assert_eq!(serialization, block_text);
679         }
680     }
681 
682     mod keywords {
683         pub use super::*;
684         #[test]
css_wide_keywords_should_be_parsed()685         fn css_wide_keywords_should_be_parsed() {
686             let block_text = "--a:inherit;";
687             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
688 
689             let serialization = block.to_css_string();
690             assert_eq!(serialization, "--a: inherit;");
691         }
692 
693         #[test]
non_keyword_custom_property_should_be_unparsed()694         fn non_keyword_custom_property_should_be_unparsed() {
695             let block_text = "--main-color: #06c;";
696             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
697 
698             let serialization = block.to_css_string();
699             assert_eq!(serialization, block_text);
700         }
701     }
702 
703     mod effects {
704         pub use super::*;
705         pub use style::properties::longhands::box_shadow::SpecifiedValue as BoxShadowList;
706         pub use style::values::specified::effects::{BoxShadow, SimpleShadow};
707 
708         #[test]
box_shadow_should_serialize_correctly()709         fn box_shadow_should_serialize_correctly() {
710             use style::values::specified::length::NonNegativeLength;
711 
712             let mut properties = Vec::new();
713             let shadow_val = BoxShadow {
714                 base: SimpleShadow {
715                     color: None,
716                     horizontal: Length::from_px(1f32),
717                     vertical: Length::from_px(2f32),
718                     blur: Some(NonNegativeLength::from_px(3f32)),
719                 },
720                 spread: Some(Length::from_px(4f32)),
721                 inset: false,
722             };
723             let shadow_decl = BoxShadowList(vec![shadow_val]);
724             properties.push(PropertyDeclaration::BoxShadow(shadow_decl));
725             let shadow_css = "box-shadow: 1px 2px 3px 4px;";
726             let shadow = parse(|c, i| Ok(parse_property_declaration_list(c, i)), shadow_css).unwrap();
727 
728             assert_eq!(shadow.to_css_string(), shadow_css);
729         }
730     }
731 }
732