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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Table width distribution and cell alignment regression tests
// Tests for issues fixed during table layout implementation
#[cfg(test)]
mod table_width_distribution_tests {
use azul_layout::solver3::fc::TableColumnInfo;
/// Test Case 1: Table with width:100% should distribute columns to fill available space
///
/// Problem: When available_width > total_max_width, columns were not expanded to fill
/// the table's full width. They stayed at their max-content width.
///
/// Fix: Distribute excess width proportionally based on max-content widths.
///
/// Example:
/// - Table width: 555px (width: 100% of body)
/// - Column 1 max-content: 80px
/// - Column 2 max-content: 80px
/// - Total max-content: 160px
/// - Excess: 555 - 160 = 395px
/// - Each column gets: 80px + (395px * 0.5) = 277.5px
#[test]
fn test_table_width_100_percent_distributes_columns() {
let mut columns = vec![
TableColumnInfo {
min_width: 60.0,
max_width: 80.0,
computed_width: None,
},
TableColumnInfo {
min_width: 60.0,
max_width: 80.0,
computed_width: None,
},
];
let table_width = 555.0;
let total_max_width = 160.0;
let available_width = table_width;
// Simulate the fixed algorithm
if available_width >= total_max_width {
let excess_width = available_width - total_max_width;
let total_weight: f32 = columns.iter().map(|c| c.max_width.max(1.0)).sum();
for col in &mut columns {
let weight_factor = col.max_width.max(1.0) / total_weight;
col.computed_width = Some(col.max_width + (excess_width * weight_factor));
}
}
// Verify columns are distributed to fill table width
let total_computed: f32 = columns.iter().filter_map(|c| c.computed_width).sum();
assert!(
(total_computed - table_width).abs() < 0.01,
"Columns should sum to table width. Expected: {}, Got: {}",
table_width,
total_computed
);
// Each column should be approximately equal (same max-content width)
assert!(
columns[0].computed_width.unwrap() > 200.0,
"Column should be expanded beyond max-content width"
);
assert!(
(columns[0].computed_width.unwrap() - columns[1].computed_width.unwrap()).abs() < 0.01,
"Columns with equal max-width should get equal final width"
);
}
/// Test Case 2: Columns with different max-widths get proportional shares
#[test]
fn test_proportional_distribution_unequal_columns() {
let mut columns = vec![
TableColumnInfo {
min_width: 40.0,
max_width: 100.0, // Wider column
computed_width: None,
},
TableColumnInfo {
min_width: 30.0,
max_width: 50.0, // Narrower column
computed_width: None,
},
];
let table_width = 600.0;
let total_max_width = 150.0;
let available_width = table_width;
let excess_width = available_width - total_max_width; // 450px
// Simulate the fixed algorithm
let total_weight: f32 = columns.iter().map(|c| c.max_width.max(1.0)).sum(); // 150
for col in &mut columns {
let weight_factor = col.max_width.max(1.0) / total_weight;
col.computed_width = Some(col.max_width + (excess_width * weight_factor));
}
// Column 1: 100 + (450 * 100/150) = 100 + 300 = 400px
// Column 2: 50 + (450 * 50/150) = 50 + 150 = 200px
assert!((columns[0].computed_width.unwrap() - 400.0).abs() < 0.01);
assert!((columns[1].computed_width.unwrap() - 200.0).abs() < 0.01);
let total: f32 = columns.iter().filter_map(|c| c.computed_width).sum();
assert!((total - table_width).abs() < 0.01);
}
/// Test Case 3: All columns have zero max-width (edge case)
#[test]
fn test_zero_max_width_equal_distribution() {
let mut columns = vec![
TableColumnInfo {
min_width: 0.0,
max_width: 0.0,
computed_width: None,
},
TableColumnInfo {
min_width: 0.0,
max_width: 0.0,
computed_width: None,
},
];
let table_width = 400.0;
let total_max_width = 0.0;
let available_width = table_width;
let excess_width = available_width - total_max_width;
// When all max_widths are 0, distribute equally
let total_weight: f32 = columns.iter().map(|c| c.max_width.max(1.0)).sum();
let num_columns = columns.len();
for col in &mut columns {
if total_weight > 0.0 {
let weight_factor = col.max_width.max(1.0) / total_weight;
col.computed_width = Some(col.max_width + (excess_width * weight_factor));
} else {
col.computed_width = Some(available_width / num_columns as f32);
}
}
// Each column should get equal width
assert!((columns[0].computed_width.unwrap() - 200.0).abs() < 0.01);
assert!((columns[1].computed_width.unwrap() - 200.0).abs() < 0.01);
}
}
#[cfg(test)]
mod vertical_alignment_tests {
/// Test Case 4: Vertical alignment should use content-box height, not border-box
///
/// Problem: y_offset was calculated using the full border-box height (including padding),
/// causing text to be displaced downward.
///
/// Fix: Calculate y_offset using content-box height (height - padding - border).
///
/// Example:
/// - Cell border-box height: 178px
/// - Padding: 80px top + 80px bottom = 160px
/// - Content-box height: 178 - 160 = 18px
/// - Content height: 16px (text)
/// - y_offset (middle): (18 - 16) / 2 = 1px (correct)
/// - OLD y_offset: (178 - 16) / 2 = 81px (wrong!)
#[test]
fn test_vertical_alignment_uses_content_box() {
let border_box_height = 178.0;
let padding_top = 80.0;
let padding_bottom = 80.0;
let border_top = 1.0;
let border_bottom = 1.0;
let content_height = 16.0;
// Calculate content-box height
let content_box_height =
border_box_height - padding_top - padding_bottom - border_top - border_bottom;
// Calculate y_offset for middle alignment (align_factor = 0.5)
let align_factor = 0.5_f32;
let y_offset: f32 = (content_box_height - content_height) * align_factor;
// Verify correct calculation
assert!(
(content_box_height - 16.0).abs() < 0.01,
"Content-box height should be ~16px, got {}",
content_box_height
);
assert!(
y_offset.abs() < 2.0,
"y_offset should be near 0, got {}",
y_offset
);
// OLD WRONG calculation for comparison
let wrong_y_offset: f32 = (border_box_height - content_height) * align_factor;
assert!(
wrong_y_offset > 80.0,
"Old calculation would give ~81px offset"
);
}
/// Test Case 5: Vertical alignment with different align values
#[test]
fn test_vertical_alignment_top_middle_bottom() {
let content_box_height = 100.0;
let content_height = 20.0;
// Top alignment (align_factor = 0.0)
let y_offset_top = (content_box_height - content_height) * 0.0;
assert_eq!(y_offset_top, 0.0);
// Middle alignment (align_factor = 0.5)
let y_offset_middle = (content_box_height - content_height) * 0.5;
assert_eq!(y_offset_middle, 40.0);
// Bottom alignment (align_factor = 1.0)
let y_offset_bottom = (content_box_height - content_height) * 1.0;
assert_eq!(y_offset_bottom, 80.0);
}
}
#[cfg(test)]
mod cell_padding_tests {
/// Test Case 6: Cell padding should be subtracted before passing to IFC
///
/// Problem: layout_cell_for_height() passed border-box width to IFC,
/// but IFC needs content-box width to layout text correctly.
///
/// Fix: Subtract padding and border from cell_width before creating IFC constraints.
///
/// Example:
/// - Cell border-box width: 277.64px
/// - Padding: 8px left + 8px right = 16px
/// - Border: 1px left + 1px right = 2px
/// - Content-box width: 277.64 - 16 - 2 = 259.64px
#[test]
fn test_cell_padding_subtracted_for_ifc() {
let cell_border_box_width = 277.64;
let padding_left = 8.0;
let padding_right = 8.0;
let border_left = 1.0;
let border_right = 1.0;
// Calculate content-box width for IFC
let content_box_width: f32 =
cell_border_box_width - padding_left - padding_right - border_left - border_right;
// Verify
assert!(
(content_box_width - 259.64).abs() < 0.01,
"Content-box width should be ~259.64px, got {}",
content_box_width
);
// OLD WRONG: Would pass full border-box width
assert!(
cell_border_box_width > content_box_width + 15.0,
"Border-box should be significantly larger than content-box"
);
}
}
#[cfg(test)]
mod body_margin_tests {
/// Test Case 7: Body margin should be preserved from original XML node
///
/// Problem: render_dom_from_body_node() created a new body element,
/// losing the style="margin: 20px" attribute from the original HTML.
///
/// Fix: Render the original body_node to preserve all attributes.
///
/// This is a regression test to ensure body styles are preserved.
#[test]
fn test_body_margin_preserved() {
// This is more of an integration test - just document the expected behavior
// Given: HTML with <body style="margin: 20px;">
// Expected: Body should have 20px margin on all sides
// Previous bug: Body had only 8px margin (default user-agent style)
let expected_margin = 20.0;
let default_ua_margin = 8.0;
assert_ne!(
expected_margin, default_ua_margin,
"Custom body margin should override UA default"
);
}
}
#[cfg(test)]
mod type_safety_tests {
/// Test Case 8: BorderBoxRect and ContentBoxRect newtypes
///
/// Enhancement: Added BorderBoxRect and ContentBoxRect newtypes to make
/// coordinate system explicit and prevent errors.
///
/// This test documents the conversion logic.
#[test]
fn test_border_box_to_content_box_conversion() {
use azul_core::geom::{LogicalPosition, LogicalRect, LogicalSize};
let border_box = LogicalRect {
origin: LogicalPosition { x: 100.0, y: 200.0 },
size: LogicalSize {
width: 300.0,
height: 200.0,
},
};
let padding_left = 10.0;
let padding_right = 10.0;
let padding_top = 15.0;
let padding_bottom = 15.0;
let border_left = 2.0;
let border_right = 2.0;
let border_top = 3.0;
let border_bottom = 3.0;
// Manual conversion
let content_box = LogicalRect {
origin: LogicalPosition {
x: border_box.origin.x + padding_left + border_left,
y: border_box.origin.y + padding_top + border_top,
},
size: LogicalSize {
width: border_box.size.width
- padding_left
- padding_right
- border_left
- border_right,
height: border_box.size.height
- padding_top
- padding_bottom
- border_top
- border_bottom,
},
};
// Verify conversion
assert_eq!(content_box.origin.x, 112.0); // 100 + 10 + 2
assert_eq!(content_box.origin.y, 218.0); // 200 + 15 + 3
assert_eq!(content_box.size.width, 276.0); // 300 - 10 - 10 - 2 - 2
assert_eq!(content_box.size.height, 164.0); // 200 - 15 - 15 - 3 - 3
}
}