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 list_style {
463         use style::properties::longhands::list_style_position::SpecifiedValue as ListStylePosition;
464         use style::properties::longhands::list_style_type::SpecifiedValue as ListStyleType;
465         use style::values::generics::url::UrlOrNone as ImageUrlOrNone;
466         use super::*;
467 
468         #[test]
list_style_should_show_all_properties_when_values_are_set()469         fn list_style_should_show_all_properties_when_values_are_set() {
470             let mut properties = Vec::new();
471 
472             let position = ListStylePosition::Inside;
473             let image = ImageUrlOrNone::Url(SpecifiedUrl::new_for_testing("http://servo/test.png"));
474             let style_type = ListStyleType::Disc;
475 
476             properties.push(PropertyDeclaration::ListStylePosition(position));
477 
478             #[cfg(feature = "gecko")]
479             properties.push(PropertyDeclaration::ListStyleImage(Box::new(image)));
480             #[cfg(not(feature = "gecko"))]
481             properties.push(PropertyDeclaration::ListStyleImage(image));
482 
483             properties.push(PropertyDeclaration::ListStyleType(style_type));
484 
485             let serialization = shorthand_properties_to_string(properties);
486             assert_eq!(serialization, "list-style: inside url(\"http://servo/test.png\") disc;");
487         }
488     }
489 
490     mod background {
491         use super::*;
492 
493         #[test]
background_should_serialize_all_available_properties_when_specified()494         fn background_should_serialize_all_available_properties_when_specified() {
495             let block_text = "\
496                 background-color: rgb(255, 0, 0); \
497                 background-image: url(\"http://servo/test.png\"); \
498                 background-repeat: repeat-x; \
499                 background-attachment: scroll; \
500                 background-size: 70px 50px; \
501                 background-position-x: 7px; \
502                 background-position-y: bottom 4px; \
503                 background-origin: border-box; \
504                 background-clip: padding-box;";
505 
506             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
507 
508             let serialization = block.to_css_string();
509 
510             assert_eq!(
511                 serialization,
512                 "background: rgb(255, 0, 0) url(\"http://servo/test.png\") repeat-x \
513                 scroll left 7px bottom 4px / 70px 50px border-box padding-box;"
514             );
515         }
516 
517         #[test]
background_should_combine_origin_and_clip_properties_when_equal()518         fn background_should_combine_origin_and_clip_properties_when_equal() {
519             let block_text = "\
520                 background-color: rgb(255, 0, 0); \
521                 background-image: url(\"http://servo/test.png\"); \
522                 background-repeat: repeat-x; \
523                 background-attachment: scroll; \
524                 background-size: 70px 50px; \
525                 background-position-x: 7px; \
526                 background-position-y: 4px; \
527                 background-origin: padding-box; \
528                 background-clip: padding-box;";
529 
530             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
531 
532             let serialization = block.to_css_string();
533 
534             assert_eq!(
535                 serialization,
536                 "background: rgb(255, 0, 0) url(\"http://servo/test.png\") repeat-x \
537                 scroll 7px 4px / 70px 50px padding-box;"
538             );
539         }
540 
541         #[test]
serialize_multiple_backgrounds()542         fn serialize_multiple_backgrounds() {
543             let block_text = "\
544                 background-color: rgb(0, 0, 255); \
545                 background-image: url(\"http://servo/test.png\"), none; \
546                 background-repeat: repeat-x, repeat-y; \
547                 background-attachment: scroll, scroll; \
548                 background-size: 70px 50px, 20px 30px; \
549                 background-position-x: 7px, 70px; \
550                 background-position-y: 4px, 40px; \
551                 background-origin: border-box, padding-box; \
552                 background-clip: padding-box, padding-box;";
553 
554             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
555 
556             let serialization = block.to_css_string();
557 
558             assert_eq!(
559                 serialization, "background: \
560                 url(\"http://servo/test.png\") repeat-x scroll 7px 4px / 70px 50px border-box padding-box, \
561                 rgb(0, 0, 255) none repeat-y scroll 70px 40px / 20px 30px padding-box;"
562             );
563         }
564 
565         #[test]
serialize_multiple_backgrounds_unequal_property_lists()566         fn serialize_multiple_backgrounds_unequal_property_lists() {
567             // When the lengths of property values are different, the shorthand serialization
568             // should not be used. Previously the implementation cycled values if the lists were
569             // uneven. This is incorrect, in that we should serialize to a shorthand only when the
570             // lists have the same length (this affects background, transition and animation).
571             // https://github.com/servo/servo/issues/15398 )
572             // With background, the color is one exception as it should only appear once for
573             // multiple backgrounds.
574             // Below background-origin only has one value.
575             let block_text = "\
576                 background-color: rgb(0, 0, 255); \
577                 background-image: url(\"http://servo/test.png\"), none; \
578                 background-repeat: repeat-x, repeat-y; \
579                 background-attachment: scroll, scroll; \
580                 background-size: 70px 50px, 20px 30px; \
581                 background-position: 7px 4px, 5px 6px; \
582                 background-origin: border-box; \
583                 background-clip: padding-box, padding-box;";
584 
585             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
586 
587             let serialization = block.to_css_string();
588 
589             assert_eq!(serialization, block_text);
590         }
591 
592         #[test]
background_position_should_be_a_valid_form_its_longhands()593         fn background_position_should_be_a_valid_form_its_longhands() {
594             // If there is any longhand consisted of both keyword and position,
595             // the shorthand result should be the 4-value format.
596             let block_text = "\
597                 background-position-x: 30px;\
598                 background-position-y: bottom 20px;";
599             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
600             let serialization = block.to_css_string();
601             assert_eq!(serialization, "background-position: left 30px bottom 20px;");
602 
603             // If there is no longhand consisted of both keyword and position,
604             // the shorthand result should be the 2-value format.
605             let block_text = "\
606                 background-position-x: center;\
607                 background-position-y: 20px;";
608             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
609             let serialization = block.to_css_string();
610             assert_eq!(serialization, "background-position: center 20px;");
611         }
612     }
613 
614     mod quotes {
615         pub use super::*;
616 
617         #[test]
should_serialize_none_correctly()618         fn should_serialize_none_correctly() {
619             use style::properties::longhands::quotes;
620 
621             assert_roundtrip_with_context!(quotes::parse, "none");
622         }
623     }
624 
625     mod animation {
626         pub use super::*;
627 
628         #[test]
serialize_single_animation()629         fn serialize_single_animation() {
630             let block_text = "\
631                 animation-name: bounce;\
632                 animation-duration: 1s;\
633                 animation-timing-function: ease-in;\
634                 animation-delay: 0s;\
635                 animation-direction: normal;\
636                 animation-fill-mode: forwards;\
637                 animation-iteration-count: infinite;\
638                 animation-play-state: paused;";
639 
640             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
641 
642             let serialization = block.to_css_string();
643 
644             assert_eq!(serialization, "animation: 1s ease-in 0s infinite normal forwards paused bounce;")
645         }
646 
647         #[test]
serialize_multiple_animations()648         fn serialize_multiple_animations() {
649             let block_text = "\
650                 animation-name: bounce, roll;\
651                 animation-duration: 1s, 0.2s;\
652                 animation-timing-function: ease-in, linear;\
653                 animation-delay: 0s, 1s;\
654                 animation-direction: normal, reverse;\
655                 animation-fill-mode: forwards, backwards;\
656                 animation-iteration-count: infinite, 2;\
657                 animation-play-state: paused, running;";
658 
659             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
660 
661             let serialization = block.to_css_string();
662 
663             assert_eq!(serialization,
664                        "animation: 1s ease-in 0s infinite normal forwards paused bounce, \
665                                    0.2s linear 1s 2 reverse backwards running roll;");
666         }
667 
668         #[test]
serialize_multiple_animations_unequal_property_lists()669         fn serialize_multiple_animations_unequal_property_lists() {
670             // When the lengths of property values are different, the shorthand serialization
671             // should not be used. Previously the implementation cycled values if the lists were
672             // uneven. This is incorrect, in that we should serialize to a shorthand only when the
673             // lists have the same length (this affects background, transition and animation).
674             // https://github.com/servo/servo/issues/15398 )
675             let block_text = "\
676                 animation-name: bounce, roll, flip, jump; \
677                 animation-duration: 1s, 0.2s; \
678                 animation-timing-function: ease-in, linear; \
679                 animation-delay: 0s, 1s, 0.5s; \
680                 animation-direction: normal; \
681                 animation-fill-mode: forwards, backwards; \
682                 animation-iteration-count: infinite, 2; \
683                 animation-play-state: paused, running;";
684 
685             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
686 
687             let serialization = block.to_css_string();
688 
689             assert_eq!(serialization, block_text);
690         }
691 
692         #[test]
serialize_multiple_without_all_properties_returns_longhand()693         fn serialize_multiple_without_all_properties_returns_longhand() {
694             // timing function and direction are missing, so no shorthand is returned.
695             let block_text = "animation-name: bounce, roll; \
696                               animation-duration: 1s, 0.2s; \
697                               animation-delay: 0s, 1s; \
698                               animation-fill-mode: forwards, backwards; \
699                               animation-iteration-count: infinite, 2; \
700                               animation-play-state: paused, running;";
701 
702             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
703 
704             let serialization = block.to_css_string();
705 
706             assert_eq!(serialization, block_text);
707         }
708     }
709 
710     mod keywords {
711         pub use super::*;
712         #[test]
css_wide_keywords_should_be_parsed()713         fn css_wide_keywords_should_be_parsed() {
714             let block_text = "--a:inherit;";
715             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
716 
717             let serialization = block.to_css_string();
718             assert_eq!(serialization, "--a: inherit;");
719         }
720 
721         #[test]
non_keyword_custom_property_should_be_unparsed()722         fn non_keyword_custom_property_should_be_unparsed() {
723             let block_text = "--main-color: #06c;";
724             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
725 
726             let serialization = block.to_css_string();
727             assert_eq!(serialization, block_text);
728         }
729     }
730 
731     mod effects {
732         pub use super::*;
733         pub use style::properties::longhands::box_shadow::SpecifiedValue as BoxShadowList;
734         pub use style::values::specified::effects::{BoxShadow, SimpleShadow};
735 
736         #[test]
box_shadow_should_serialize_correctly()737         fn box_shadow_should_serialize_correctly() {
738             use style::values::specified::length::NonNegativeLength;
739 
740             let mut properties = Vec::new();
741             let shadow_val = BoxShadow {
742                 base: SimpleShadow {
743                     color: None,
744                     horizontal: Length::from_px(1f32),
745                     vertical: Length::from_px(2f32),
746                     blur: Some(NonNegativeLength::from_px(3f32)),
747                 },
748                 spread: Some(Length::from_px(4f32)),
749                 inset: false,
750             };
751             let shadow_decl = BoxShadowList(vec![shadow_val]);
752             properties.push(PropertyDeclaration::BoxShadow(shadow_decl));
753             let shadow_css = "box-shadow: 1px 2px 3px 4px;";
754             let shadow = parse(|c, i| Ok(parse_property_declaration_list(c, i)), shadow_css).unwrap();
755 
756             assert_eq!(shadow.to_css_string(), shadow_css);
757         }
758     }
759 }
760