1use cssbox_core::style::*;
6use cssbox_core::values::*;
7
8#[derive(Debug, Clone)]
10pub struct CssDeclaration {
11 pub property: String,
12 pub value: String,
13 pub important: bool,
14}
15
16pub fn parse_style_attribute(style: &str) -> Vec<CssDeclaration> {
18 let mut declarations = Vec::new();
19
20 for decl_str in style.split(';') {
21 let decl_str = decl_str.trim();
22 if decl_str.is_empty() {
23 continue;
24 }
25
26 if let Some((property, value)) = decl_str.split_once(':') {
27 let property = property.trim().to_lowercase();
28 let value = value.trim().to_string();
29 let important = value.contains("!important");
30 let value = value.replace("!important", "").trim().to_string();
31
32 declarations.push(CssDeclaration {
33 property,
34 value,
35 important,
36 });
37 }
38 }
39
40 declarations
41}
42
43pub fn parse_stylesheet(css: &str) -> Vec<CssRule> {
45 let mut rules = Vec::new();
46 let mut pos = 0;
47 let bytes = css.as_bytes();
48 let len = bytes.len();
49
50 while pos < len {
51 pos = skip_whitespace_comments(css, pos);
53 if pos >= len {
54 break;
55 }
56
57 let selector_start = pos;
59 while pos < len && bytes[pos] != b'{' {
60 pos += 1;
61 }
62 if pos >= len {
63 break;
64 }
65 let selector = css[selector_start..pos].trim().to_string();
66 pos += 1; let decl_start = pos;
70 let mut depth = 1;
71 while pos < len && depth > 0 {
72 if bytes[pos] == b'{' {
73 depth += 1;
74 } else if bytes[pos] == b'}' {
75 depth -= 1;
76 }
77 if depth > 0 {
78 pos += 1;
79 }
80 }
81 let decl_str = &css[decl_start..pos];
82 pos += 1; if !selector.is_empty() {
85 let declarations = parse_style_attribute(decl_str);
86 rules.push(CssRule {
87 selector,
88 declarations,
89 });
90 }
91 }
92
93 rules
94}
95
96fn skip_whitespace_comments(css: &str, mut pos: usize) -> usize {
97 let bytes = css.as_bytes();
98 let len = bytes.len();
99
100 while pos < len {
101 if bytes[pos].is_ascii_whitespace() {
102 pos += 1;
103 } else if pos + 1 < len && bytes[pos] == b'/' && bytes[pos + 1] == b'*' {
104 pos += 2;
106 while pos + 1 < len && !(bytes[pos] == b'*' && bytes[pos + 1] == b'/') {
107 pos += 1;
108 }
109 pos += 2;
110 } else {
111 break;
112 }
113 }
114 pos
115}
116
117#[derive(Debug, Clone)]
119pub struct CssRule {
120 pub selector: String,
121 pub declarations: Vec<CssDeclaration>,
122}
123
124pub fn apply_declarations(style: &mut ComputedStyle, declarations: &[CssDeclaration]) {
126 for decl in declarations {
127 apply_property(style, &decl.property, &decl.value);
128 }
129}
130
131pub fn apply_property(style: &mut ComputedStyle, property: &str, value: &str) {
133 match property {
134 "display" => {
135 style.display = parse_display(value);
136 }
137 "position" => {
138 style.position = match value {
139 "static" => Position::Static,
140 "relative" => Position::Relative,
141 "absolute" => Position::Absolute,
142 "fixed" => Position::Fixed,
143 "sticky" => Position::Sticky,
144 _ => Position::Static,
145 };
146 }
147 "float" => {
148 style.float = match value {
149 "left" => Float::Left,
150 "right" => Float::Right,
151 "none" => Float::None,
152 _ => Float::None,
153 };
154 }
155 "clear" => {
156 style.clear = match value {
157 "left" => Clear::Left,
158 "right" => Clear::Right,
159 "both" => Clear::Both,
160 "none" => Clear::None,
161 _ => Clear::None,
162 };
163 }
164 "box-sizing" => {
165 style.box_sizing = match value {
166 "border-box" => BoxSizing::BorderBox,
167 "content-box" => BoxSizing::ContentBox,
168 _ => BoxSizing::ContentBox,
169 };
170 }
171 "width" => {
172 style.width = parse_length_percentage_auto(value);
173 }
174 "height" => {
175 style.height = parse_length_percentage_auto(value);
176 }
177 "min-width" => {
178 style.min_width = parse_length_percentage(value);
179 }
180 "min-height" => {
181 style.min_height = parse_length_percentage(value);
182 }
183 "max-width" => {
184 style.max_width = parse_length_percentage_none(value);
185 }
186 "max-height" => {
187 style.max_height = parse_length_percentage_none(value);
188 }
189 "margin" => {
190 let edges = parse_shorthand_edges(value);
191 style.margin_top = edges.0;
192 style.margin_right = edges.1;
193 style.margin_bottom = edges.2;
194 style.margin_left = edges.3;
195 }
196 "margin-top" => style.margin_top = parse_length_percentage_auto(value),
197 "margin-right" => style.margin_right = parse_length_percentage_auto(value),
198 "margin-bottom" => style.margin_bottom = parse_length_percentage_auto(value),
199 "margin-left" => style.margin_left = parse_length_percentage_auto(value),
200 "padding" => {
201 let edges = parse_shorthand_lp_edges(value);
202 style.padding_top = edges.0;
203 style.padding_right = edges.1;
204 style.padding_bottom = edges.2;
205 style.padding_left = edges.3;
206 }
207 "padding-top" => style.padding_top = parse_length_percentage(value),
208 "padding-right" => style.padding_right = parse_length_percentage(value),
209 "padding-bottom" => style.padding_bottom = parse_length_percentage(value),
210 "padding-left" => style.padding_left = parse_length_percentage(value),
211 "border-width" => {
212 let w = parse_px(value);
213 style.border_top_width = w;
214 style.border_right_width = w;
215 style.border_bottom_width = w;
216 style.border_left_width = w;
217 }
218 "border-top-width" => style.border_top_width = parse_px(value),
219 "border-right-width" => style.border_right_width = parse_px(value),
220 "border-bottom-width" => style.border_bottom_width = parse_px(value),
221 "border-left-width" => style.border_left_width = parse_px(value),
222 "border" => {
223 let parts: Vec<&str> = value.split_whitespace().collect();
225 if let Some(first) = parts.first() {
226 let w = parse_px(first);
227 style.border_top_width = w;
228 style.border_right_width = w;
229 style.border_bottom_width = w;
230 style.border_left_width = w;
231 }
232 }
233 "top" => style.top = parse_length_percentage_auto(value),
234 "right" => style.right = parse_length_percentage_auto(value),
235 "bottom" => style.bottom = parse_length_percentage_auto(value),
236 "left" => style.left = parse_length_percentage_auto(value),
237 "overflow" => {
238 let v = parse_overflow(value);
239 style.overflow_x = v;
240 style.overflow_y = v;
241 }
242 "overflow-x" => style.overflow_x = parse_overflow(value),
243 "overflow-y" => style.overflow_y = parse_overflow(value),
244 "text-align" => {
245 style.text_align = match value {
246 "left" => TextAlign::Left,
247 "right" => TextAlign::Right,
248 "center" => TextAlign::Center,
249 "justify" => TextAlign::Justify,
250 _ => TextAlign::Left,
251 };
252 }
253 "line-height" => {
254 style.line_height = parse_px_or_number(value, 1.2);
255 }
256 "flex-direction" => {
258 style.flex_direction = match value {
259 "row" => FlexDirection::Row,
260 "row-reverse" => FlexDirection::RowReverse,
261 "column" => FlexDirection::Column,
262 "column-reverse" => FlexDirection::ColumnReverse,
263 _ => FlexDirection::Row,
264 };
265 }
266 "flex-wrap" => {
267 style.flex_wrap = match value {
268 "nowrap" => FlexWrap::Nowrap,
269 "wrap" => FlexWrap::Wrap,
270 "wrap-reverse" => FlexWrap::WrapReverse,
271 _ => FlexWrap::Nowrap,
272 };
273 }
274 "flex-grow" => {
275 style.flex_grow = value.parse().unwrap_or(0.0);
276 }
277 "flex-shrink" => {
278 style.flex_shrink = value.parse().unwrap_or(1.0);
279 }
280 "flex-basis" => {
281 style.flex_basis = parse_length_percentage_auto(value);
282 }
283 "flex" => {
284 parse_flex_shorthand(style, value);
285 }
286 "align-items" => {
287 style.align_items = parse_align_items(value);
288 }
289 "align-self" => {
290 style.align_self = parse_align_self(value);
291 }
292 "align-content" => {
293 style.align_content = parse_align_content(value);
294 }
295 "justify-content" => {
296 style.justify_content = parse_justify_content(value);
297 }
298 "order" => {
299 style.order = value.parse().unwrap_or(0);
300 }
301 "grid-template-columns" => {
303 style.grid_template_columns = parse_track_list(value);
304 }
305 "grid-template-rows" => {
306 style.grid_template_rows = parse_track_list(value);
307 }
308 "row-gap" | "grid-row-gap" => {
309 style.row_gap = parse_px(value);
310 }
311 "column-gap" | "grid-column-gap" => {
312 style.column_gap = parse_px(value);
313 }
314 "gap" | "grid-gap" => {
315 let parts: Vec<&str> = value.split_whitespace().collect();
316 style.row_gap = parse_px(parts.first().unwrap_or(&"0"));
317 style.column_gap = parse_px(parts.get(1).unwrap_or(parts.first().unwrap_or(&"0")));
318 }
319 "grid-row-start" => style.grid_row_start = parse_grid_placement(value),
320 "grid-row-end" => style.grid_row_end = parse_grid_placement(value),
321 "grid-column-start" => style.grid_column_start = parse_grid_placement(value),
322 "grid-column-end" => style.grid_column_end = parse_grid_placement(value),
323 "grid-auto-flow" => {
324 style.grid_auto_flow = match value {
325 "row" => GridAutoFlow::Row,
326 "column" => GridAutoFlow::Column,
327 "row dense" => GridAutoFlow::RowDense,
328 "column dense" => GridAutoFlow::ColumnDense,
329 _ => GridAutoFlow::Row,
330 };
331 }
332 "table-layout" => {
334 style.table_layout = match value {
335 "fixed" => TableLayout::Fixed,
336 "auto" => TableLayout::Auto,
337 _ => TableLayout::Auto,
338 };
339 }
340 "border-collapse" => {
341 style.border_collapse = match value {
342 "collapse" => BorderCollapse::Collapse,
343 "separate" => BorderCollapse::Separate,
344 _ => BorderCollapse::Separate,
345 };
346 }
347 "border-spacing" => {
348 style.border_spacing = parse_px(value);
349 }
350 "caption-side" => {
351 style.caption_side = match value {
352 "top" => CaptionSide::Top,
353 "bottom" => CaptionSide::Bottom,
354 _ => CaptionSide::Top,
355 };
356 }
357 _ => {
358 }
360 }
361}
362
363fn parse_px(value: &str) -> f32 {
366 let value = value.trim();
367 if value == "0" {
368 return 0.0;
369 }
370 if let Some(px) = value.strip_suffix("px") {
371 px.trim().parse().unwrap_or(0.0)
372 } else {
373 value.parse().unwrap_or(0.0)
374 }
375}
376
377fn parse_px_or_number(value: &str, default: f32) -> f32 {
378 let value = value.trim();
379 if value == "normal" {
380 return default;
381 }
382 if let Some(px) = value.strip_suffix("px") {
383 px.trim().parse().unwrap_or(default)
384 } else {
385 value.parse().unwrap_or(default)
386 }
387}
388
389fn parse_length_percentage(value: &str) -> LengthPercentage {
390 let value = value.trim();
391 if value == "0" {
392 return LengthPercentage::Length(0.0);
393 }
394 if let Some(pct) = value.strip_suffix('%') {
395 LengthPercentage::Percentage(pct.trim().parse::<f32>().unwrap_or(0.0) / 100.0)
396 } else if let Some(px) = value.strip_suffix("px") {
397 LengthPercentage::Length(px.trim().parse().unwrap_or(0.0))
398 } else {
399 LengthPercentage::Length(value.parse().unwrap_or(0.0))
400 }
401}
402
403fn parse_length_percentage_auto(value: &str) -> LengthPercentageAuto {
404 let value = value.trim();
405 if value == "auto" {
406 return LengthPercentageAuto::Auto;
407 }
408 if value == "0" {
409 return LengthPercentageAuto::Length(0.0);
410 }
411 if let Some(pct) = value.strip_suffix('%') {
412 LengthPercentageAuto::Percentage(pct.trim().parse::<f32>().unwrap_or(0.0) / 100.0)
413 } else if let Some(px) = value.strip_suffix("px") {
414 LengthPercentageAuto::Length(px.trim().parse().unwrap_or(0.0))
415 } else {
416 LengthPercentageAuto::Length(value.parse().unwrap_or(0.0))
417 }
418}
419
420fn parse_length_percentage_none(value: &str) -> LengthPercentageNone {
421 let value = value.trim();
422 if value == "none" {
423 return LengthPercentageNone::None;
424 }
425 if value == "0" {
426 return LengthPercentageNone::Length(0.0);
427 }
428 if let Some(pct) = value.strip_suffix('%') {
429 LengthPercentageNone::Percentage(pct.trim().parse::<f32>().unwrap_or(0.0) / 100.0)
430 } else if let Some(px) = value.strip_suffix("px") {
431 LengthPercentageNone::Length(px.trim().parse().unwrap_or(0.0))
432 } else {
433 LengthPercentageNone::Length(value.parse().unwrap_or(0.0))
434 }
435}
436
437fn parse_display(value: &str) -> Display {
438 match value.trim() {
439 "block" => Display::BLOCK,
440 "inline" => Display::INLINE,
441 "inline-block" => Display::INLINE_BLOCK,
442 "flex" => Display::FLEX,
443 "inline-flex" => Display::INLINE_FLEX,
444 "grid" => Display::GRID,
445 "inline-grid" => Display::INLINE_GRID,
446 "table" => Display::TABLE,
447 "table-row" => Display::TABLE_ROW,
448 "table-cell" => Display::TABLE_CELL,
449 "table-row-group" => Display::TABLE_ROW_GROUP,
450 "table-column" => Display::TABLE_COLUMN,
451 "table-column-group" => Display::TABLE_COLUMN_GROUP,
452 "table-caption" => Display::TABLE_CAPTION,
453 "table-header-group" => Display::TABLE_HEADER_GROUP,
454 "table-footer-group" => Display::TABLE_FOOTER_GROUP,
455 "flow-root" => Display::FLOW_ROOT,
456 "none" => Display::NONE,
457 _ => Display::INLINE,
458 }
459}
460
461fn parse_overflow(value: &str) -> Overflow {
462 match value.trim() {
463 "visible" => Overflow::Visible,
464 "hidden" => Overflow::Hidden,
465 "scroll" => Overflow::Scroll,
466 "auto" => Overflow::Auto,
467 _ => Overflow::Visible,
468 }
469}
470
471fn parse_shorthand_edges(
472 value: &str,
473) -> (
474 LengthPercentageAuto,
475 LengthPercentageAuto,
476 LengthPercentageAuto,
477 LengthPercentageAuto,
478) {
479 let parts: Vec<&str> = value.split_whitespace().collect();
480 match parts.len() {
481 1 => {
482 let v = parse_length_percentage_auto(parts[0]);
483 (v, v, v, v)
484 }
485 2 => {
486 let vert = parse_length_percentage_auto(parts[0]);
487 let horiz = parse_length_percentage_auto(parts[1]);
488 (vert, horiz, vert, horiz)
489 }
490 3 => {
491 let top = parse_length_percentage_auto(parts[0]);
492 let horiz = parse_length_percentage_auto(parts[1]);
493 let bottom = parse_length_percentage_auto(parts[2]);
494 (top, horiz, bottom, horiz)
495 }
496 4 => (
497 parse_length_percentage_auto(parts[0]),
498 parse_length_percentage_auto(parts[1]),
499 parse_length_percentage_auto(parts[2]),
500 parse_length_percentage_auto(parts[3]),
501 ),
502 _ => (
503 LengthPercentageAuto::px(0.0),
504 LengthPercentageAuto::px(0.0),
505 LengthPercentageAuto::px(0.0),
506 LengthPercentageAuto::px(0.0),
507 ),
508 }
509}
510
511fn parse_shorthand_lp_edges(
512 value: &str,
513) -> (
514 LengthPercentage,
515 LengthPercentage,
516 LengthPercentage,
517 LengthPercentage,
518) {
519 let parts: Vec<&str> = value.split_whitespace().collect();
520 match parts.len() {
521 1 => {
522 let v = parse_length_percentage(parts[0]);
523 (v, v, v, v)
524 }
525 2 => {
526 let vert = parse_length_percentage(parts[0]);
527 let horiz = parse_length_percentage(parts[1]);
528 (vert, horiz, vert, horiz)
529 }
530 3 => {
531 let top = parse_length_percentage(parts[0]);
532 let horiz = parse_length_percentage(parts[1]);
533 let bottom = parse_length_percentage(parts[2]);
534 (top, horiz, bottom, horiz)
535 }
536 4 => (
537 parse_length_percentage(parts[0]),
538 parse_length_percentage(parts[1]),
539 parse_length_percentage(parts[2]),
540 parse_length_percentage(parts[3]),
541 ),
542 _ => (
543 LengthPercentage::Length(0.0),
544 LengthPercentage::Length(0.0),
545 LengthPercentage::Length(0.0),
546 LengthPercentage::Length(0.0),
547 ),
548 }
549}
550
551fn parse_flex_shorthand(style: &mut ComputedStyle, value: &str) {
552 let parts: Vec<&str> = value.split_whitespace().collect();
553 match parts.len() {
554 1 => {
555 if parts[0] == "none" {
556 style.flex_grow = 0.0;
557 style.flex_shrink = 0.0;
558 style.flex_basis = LengthPercentageAuto::Auto;
559 } else if parts[0] == "auto" {
560 style.flex_grow = 1.0;
561 style.flex_shrink = 1.0;
562 style.flex_basis = LengthPercentageAuto::Auto;
563 } else if let Ok(grow) = parts[0].parse::<f32>() {
564 style.flex_grow = grow;
565 style.flex_shrink = 1.0;
566 style.flex_basis = LengthPercentageAuto::px(0.0);
567 }
568 }
569 2 => {
570 style.flex_grow = parts[0].parse().unwrap_or(0.0);
571 if let Ok(shrink) = parts[1].parse::<f32>() {
572 style.flex_shrink = shrink;
573 style.flex_basis = LengthPercentageAuto::px(0.0);
574 } else {
575 style.flex_shrink = 1.0;
576 style.flex_basis = parse_length_percentage_auto(parts[1]);
577 }
578 }
579 3 => {
580 style.flex_grow = parts[0].parse().unwrap_or(0.0);
581 style.flex_shrink = parts[1].parse().unwrap_or(1.0);
582 style.flex_basis = parse_length_percentage_auto(parts[2]);
583 }
584 _ => {}
585 }
586}
587
588fn parse_align_items(value: &str) -> AlignItems {
589 match value.trim() {
590 "stretch" => AlignItems::Stretch,
591 "flex-start" | "start" => AlignItems::FlexStart,
592 "flex-end" | "end" => AlignItems::FlexEnd,
593 "center" => AlignItems::Center,
594 "baseline" => AlignItems::Baseline,
595 _ => AlignItems::Stretch,
596 }
597}
598
599fn parse_align_self(value: &str) -> AlignSelf {
600 match value.trim() {
601 "auto" => AlignSelf::Auto,
602 "stretch" => AlignSelf::Stretch,
603 "flex-start" | "start" => AlignSelf::FlexStart,
604 "flex-end" | "end" => AlignSelf::FlexEnd,
605 "center" => AlignSelf::Center,
606 "baseline" => AlignSelf::Baseline,
607 _ => AlignSelf::Auto,
608 }
609}
610
611fn parse_align_content(value: &str) -> AlignContent {
612 match value.trim() {
613 "stretch" => AlignContent::Stretch,
614 "flex-start" | "start" => AlignContent::FlexStart,
615 "flex-end" | "end" => AlignContent::FlexEnd,
616 "center" => AlignContent::Center,
617 "space-between" => AlignContent::SpaceBetween,
618 "space-around" => AlignContent::SpaceAround,
619 "space-evenly" => AlignContent::SpaceEvenly,
620 _ => AlignContent::Stretch,
621 }
622}
623
624fn parse_justify_content(value: &str) -> JustifyContent {
625 match value.trim() {
626 "flex-start" | "start" => JustifyContent::FlexStart,
627 "flex-end" | "end" => JustifyContent::FlexEnd,
628 "center" => JustifyContent::Center,
629 "space-between" => JustifyContent::SpaceBetween,
630 "space-around" => JustifyContent::SpaceAround,
631 "space-evenly" => JustifyContent::SpaceEvenly,
632 _ => JustifyContent::FlexStart,
633 }
634}
635
636fn parse_track_list(value: &str) -> Vec<TrackDefinition> {
637 let mut tracks = Vec::new();
638
639 for token in split_track_tokens(value) {
641 let token = token.trim();
642 if token.is_empty() {
643 continue;
644 }
645 tracks.push(TrackDefinition::new(parse_track_sizing(token)));
646 }
647
648 tracks
649}
650
651fn split_track_tokens(value: &str) -> Vec<String> {
652 let mut tokens = Vec::new();
653 let mut current = String::new();
654 let mut paren_depth = 0;
655
656 for ch in value.chars() {
657 match ch {
658 '(' => {
659 paren_depth += 1;
660 current.push(ch);
661 }
662 ')' => {
663 paren_depth -= 1;
664 current.push(ch);
665 }
666 ' ' if paren_depth == 0 => {
667 if !current.is_empty() {
668 tokens.push(current.clone());
669 current.clear();
670 }
671 }
672 _ => {
673 current.push(ch);
674 }
675 }
676 }
677 if !current.is_empty() {
678 tokens.push(current);
679 }
680
681 tokens
682}
683
684fn parse_track_sizing(token: &str) -> TrackSizingFunction {
685 if let Some(fr) = token.strip_suffix("fr") {
686 TrackSizingFunction::Fr(fr.parse().unwrap_or(1.0))
687 } else if let Some(pct) = token.strip_suffix('%') {
688 TrackSizingFunction::Percentage(pct.parse::<f32>().unwrap_or(0.0) / 100.0)
689 } else if let Some(px) = token.strip_suffix("px") {
690 TrackSizingFunction::Length(px.parse().unwrap_or(0.0))
691 } else if token == "auto" {
692 TrackSizingFunction::Auto
693 } else if token == "min-content" {
694 TrackSizingFunction::MinContent
695 } else if token == "max-content" {
696 TrackSizingFunction::MaxContent
697 } else if token.starts_with("minmax(") {
698 parse_minmax(token)
699 } else if token.starts_with("fit-content(") {
700 let inner = &token["fit-content(".len()..token.len() - 1];
701 TrackSizingFunction::FitContent(parse_px(inner))
702 } else {
703 TrackSizingFunction::Length(token.parse().unwrap_or(0.0))
704 }
705}
706
707fn parse_minmax(token: &str) -> TrackSizingFunction {
708 let inner = &token["minmax(".len()..token.len() - 1];
709 if let Some((min, max)) = inner.split_once(',') {
710 TrackSizingFunction::MinMax(
711 Box::new(parse_track_sizing(min.trim())),
712 Box::new(parse_track_sizing(max.trim())),
713 )
714 } else {
715 TrackSizingFunction::Auto
716 }
717}
718
719fn parse_grid_placement(value: &str) -> GridPlacement {
720 let value = value.trim();
721 if value == "auto" {
722 return GridPlacement::Auto;
723 }
724 if let Some(span_val) = value.strip_prefix("span ") {
725 if let Ok(n) = span_val.trim().parse::<u32>() {
726 return GridPlacement::Span(n);
727 }
728 }
729 if let Ok(n) = value.parse::<i32>() {
730 return GridPlacement::Line(n);
731 }
732 GridPlacement::Named(value.to_string())
733}
734
735#[cfg(test)]
736mod tests {
737 use super::*;
738
739 #[test]
740 fn test_parse_style_attribute() {
741 let decls = parse_style_attribute("width: 100px; height: 50px; margin: 10px auto");
742 assert_eq!(decls.len(), 3);
743 assert_eq!(decls[0].property, "width");
744 assert_eq!(decls[0].value, "100px");
745 }
746
747 #[test]
748 fn test_apply_display() {
749 let mut style = ComputedStyle::default();
750 apply_property(&mut style, "display", "flex");
751 assert_eq!(style.display, Display::FLEX);
752 }
753
754 #[test]
755 fn test_apply_margin_shorthand() {
756 let mut style = ComputedStyle::default();
757 apply_property(&mut style, "margin", "10px 20px");
758 assert_eq!(style.margin_top, LengthPercentageAuto::px(10.0));
759 assert_eq!(style.margin_right, LengthPercentageAuto::px(20.0));
760 assert_eq!(style.margin_bottom, LengthPercentageAuto::px(10.0));
761 assert_eq!(style.margin_left, LengthPercentageAuto::px(20.0));
762 }
763
764 #[test]
765 fn test_parse_percentage() {
766 let lpa = parse_length_percentage_auto("50%");
767 assert_eq!(lpa, LengthPercentageAuto::Percentage(0.5));
768 }
769
770 #[test]
771 fn test_parse_stylesheet() {
772 let css = "div { width: 100px; height: 50px; } .box { display: flex; }";
773 let rules = parse_stylesheet(css);
774 assert_eq!(rules.len(), 2);
775 assert_eq!(rules[0].selector, "div");
776 assert_eq!(rules[0].declarations.len(), 2);
777 assert_eq!(rules[1].selector, ".box");
778 }
779
780 #[test]
781 fn test_parse_track_list() {
782 let tracks = parse_track_list("1fr 200px auto");
783 assert_eq!(tracks.len(), 3);
784 assert!(matches!(tracks[0].sizing, TrackSizingFunction::Fr(f) if (f - 1.0).abs() < 0.001));
785 assert!(
786 matches!(tracks[1].sizing, TrackSizingFunction::Length(v) if (v - 200.0).abs() < 0.001)
787 );
788 assert!(matches!(tracks[2].sizing, TrackSizingFunction::Auto));
789 }
790}