1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Unit tests for CSS margin collapsing behavior
//!
//! Tests verify that adjacent vertical margins collapse according to CSS spec:
//! https://www.w3.org/TR/CSS2/box.html#collapsing-margins
//!
//! Key rules:
//! 1. Adjacent vertical margins collapse to the larger of the two
//! 2. Parent's top margin collapses with first child's top margin (if no border/padding)
//! 3. Parent's bottom margin collapses with last child's bottom margin
//! 4. Empty block's top and bottom margins collapse with each other
/// Implementation of CSS 2.1 margin collapsing rules (section 8.3.1)
/// This duplicates the function from solver3/fc.rs for testing purposes
///
/// Rules:
/// 1. Both positive: result = max(a, b) - larger margin wins
/// 2. Both negative: result = min(a, b) - more negative wins
/// 3. Mixed signs: result = a + b - margins are effectively summed
fn collapse_margins(a: f32, b: f32) -> f32 {
if a.is_sign_positive() && b.is_sign_positive() {
a.max(b)
} else if a.is_sign_negative() && b.is_sign_negative() {
a.min(b)
} else {
a + b
}
}
#[test]
fn test_both_positive_margins_use_maximum() {
// When both margins are positive, the larger one wins
assert_eq!(collapse_margins(20.0, 10.0), 20.0);
assert_eq!(collapse_margins(10.0, 20.0), 20.0);
assert_eq!(collapse_margins(15.0, 15.0), 15.0);
// CSS spec example: h1 margin-bottom 30px collapses with p margin-top 20px → 30px
assert_eq!(collapse_margins(30.0, 20.0), 30.0);
// Real UA CSS values (at 16px font size):
// p margin-top (1em = 16px) with p margin-bottom (1em = 16px) → 16px
assert_eq!(collapse_margins(16.0, 16.0), 16.0);
// body margin (20px) with h1 margin-top (10px) → 20px
assert_eq!(collapse_margins(20.0, 10.0), 20.0);
}
#[test]
fn test_both_negative_margins_use_minimum() {
// When both margins are negative, the more negative one wins
assert_eq!(collapse_margins(-20.0, -10.0), -20.0);
assert_eq!(collapse_margins(-10.0, -20.0), -20.0);
assert_eq!(collapse_margins(-15.0, -15.0), -15.0);
// More negative = smaller value
assert_eq!(collapse_margins(-5.0, -25.0), -25.0);
}
#[test]
fn test_mixed_sign_margins_are_summed() {
// When margins have opposite signs, they are summed
assert_eq!(collapse_margins(20.0, -10.0), 10.0);
assert_eq!(collapse_margins(-10.0, 20.0), 10.0);
assert_eq!(collapse_margins(10.0, -10.0), 0.0);
assert_eq!(collapse_margins(30.0, -20.0), 10.0);
assert_eq!(collapse_margins(-30.0, 20.0), -10.0);
}
#[test]
fn test_zero_margins() {
// Zero is treated as positive
assert_eq!(collapse_margins(0.0, 0.0), 0.0);
assert_eq!(collapse_margins(0.0, 10.0), 10.0);
assert_eq!(collapse_margins(10.0, 0.0), 10.0);
// Zero with negative margin - treated as mixed signs
assert_eq!(collapse_margins(0.0, -10.0), -10.0);
assert_eq!(collapse_margins(-10.0, 0.0), -10.0);
}
#[test]
fn test_real_world_scenarios_from_test_html() {
// These scenarios match the margin_collapse_test.html file
// Scenario 1: body margin-top (20px) with h1 margin-top (10px)
// Expected: 20px (larger positive wins)
assert_eq!(collapse_margins(20.0, 10.0), 20.0);
// Scenario 2: h1 margin-bottom (30px) with p margin-top (20px)
// Expected: 30px (larger positive wins)
assert_eq!(collapse_margins(30.0, 20.0), 30.0);
// Scenario 3: p margin-bottom (20px) with next p margin-top (20px)
// Expected: 20px (equal margins collapse to that value)
assert_eq!(collapse_margins(20.0, 20.0), 20.0);
// Scenario 4: p margin-bottom (20px) with div.box margin-top (40px)
// Expected: 40px (larger positive wins)
assert_eq!(collapse_margins(20.0, 40.0), 40.0);
}
#[test]
fn test_ua_css_default_margins() {
// UA CSS default margins at 16px font-size
// h1: font-size 2em = 32px, margin 0.67em = 0.67 * 32 = 21.44px
let h1_margin = 0.67 * 32.0;
// p: font-size 1em = 16px, margin 1em = 16px
let p_margin = 16.0;
// h1 margin-bottom collapses with p margin-top
// Expected: 21.44px (larger wins)
let collapsed = collapse_margins(h1_margin, p_margin);
assert!((collapsed - 21.44).abs() < 0.01);
// p margin-bottom with p margin-top
// Expected: 16px
assert_eq!(collapse_margins(p_margin, p_margin), 16.0);
}
#[test]
fn test_floating_point_precision() {
// Test with typical CSS computed values that might have floating point imprecision
assert_eq!(collapse_margins(10.72, 16.0), 16.0);
assert_eq!(collapse_margins(16.0, 10.72), 16.0);
assert_eq!(collapse_margins(21.44, 16.0), 21.44);
// Very close values
assert!((collapse_margins(10.0, 10.001) - 10.001).abs() < 0.001);
assert!((collapse_margins(10.001, 10.0) - 10.001).abs() < 0.001);
}
#[test]
fn test_edge_cases() {
// Very large margins
assert_eq!(collapse_margins(1000.0, 500.0), 1000.0);
assert_eq!(collapse_margins(-1000.0, -500.0), -1000.0);
// Very small margins
assert_eq!(collapse_margins(0.1, 0.2), 0.2);
assert!((collapse_margins(0.01, 0.02) - 0.02).abs() < 0.0001);
// Asymmetric cases
assert_eq!(collapse_margins(100.0, 1.0), 100.0);
assert_eq!(collapse_margins(1.0, 100.0), 100.0);
}