mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
test(layout): verify native table heights and aligned painting
This commit is contained in:
@@ -2872,6 +2872,78 @@ async fn screenshot_preserves_table_cell_dimension_hints_and_avatar_columns() {
|
||||
.expect("table-cell dimension hints should preserve avatar alignment");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn screenshot_table_row_heights_match_chromium() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?;
|
||||
let mut page = test_page_vm_with_loader_and_document_url(
|
||||
&loader, Vec::new(), Url::parse("https://example.com/table-row-heights.html")?,
|
||||
);
|
||||
page.vm_mut().set_layout_policy(moli_page_types::LayoutPolicy::OnDemand);
|
||||
let fixture = include_str!("../../../../tests/fixtures/table-row-heights.html");
|
||||
page.vm_mut().eval(&format!("document.open();document.write({});document.close()", serde_json::to_string(fixture)?))?;
|
||||
page.vm_mut().prime_document_lifecycle_processing_and_record_stylesheet_network_results();
|
||||
page.vm_mut().eval("if (!cases.length) buildTableRowHeightCases()")?;
|
||||
let expected: serde_json::Value = serde_json::from_str(include_str!("../../../../tests/fixtures/table-row-heights.chromium.json"))?;
|
||||
let mut failures = Vec::new();
|
||||
for phase in 0..3 {
|
||||
page.vm_mut().eval(&format!("setTableRowHeightPhase({phase})"))?;
|
||||
// Both initial layout and a repeated read must agree with the oracle.
|
||||
for read in 0..2 {
|
||||
page.vm_mut().screenshot_layout_snapshot(moli_layout::PaintViewport::new(800, 600, 1.0))?.expect("table layout root");
|
||||
let actual: serde_json::Value = serde_json::from_str(&page.vm_mut().eval("JSON.stringify(collectTableRowHeights())")?)?;
|
||||
let actual = actual.as_array().unwrap();
|
||||
let expected_cases = expected["phases"][phase].as_array().unwrap();
|
||||
assert_eq!(actual.len(), expected_cases.len());
|
||||
for (actual, expected) in actual.iter().zip(expected_cases) {
|
||||
assert_eq!(actual["name"], expected["name"]);
|
||||
let name = actual["name"].as_str().unwrap();
|
||||
let actual_rects = actual["rects"].as_array().unwrap();
|
||||
let expected_rects = expected["rects"].as_array().unwrap();
|
||||
assert_eq!(actual_rects.len(), expected_rects.len());
|
||||
for (index, (actual, expected)) in actual_rects.iter().zip(expected_rects).enumerate() {
|
||||
if (0..4).any(|axis| (actual[axis].as_f64().unwrap() - expected[axis].as_f64().unwrap()).abs() > 0.05) {
|
||||
failures.push(format!("phase {phase}, read {read}, {name}, rect {index}: {actual} != {expected}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(failures.is_empty(), "{} table geometry differences:\n{}", failures.len(), failures.join("\n"));
|
||||
// Check that final percentage-dependent baseline alignment reaches
|
||||
// paint as well as CSSOM, including a subsequent group-height change.
|
||||
page.vm_mut().eval(r#"
|
||||
for (const {name,owner} of cases) owner.style.display = name === 'percent-baseline' ? 'block' : 'none';
|
||||
const paintedCase = cases.find(c => c.name === 'percent-baseline').owner;
|
||||
paintedCase.querySelectorAll('td > div')[0].style.background = 'rgb(255,0,0)';
|
||||
paintedCase.querySelectorAll('td > div')[1].style.background = 'rgb(0,255,0)';
|
||||
"#)?;
|
||||
for height in [120u32, 160] {
|
||||
page.vm_mut().eval(&format!("paintedCase.querySelector('tbody').style.height = '{height}px'"))?;
|
||||
let snapshot = page.vm_mut().screenshot_layout_snapshot(moli_layout::PaintViewport::new(240, 200, 1.0))?.expect("painted table root");
|
||||
let image = moli_paint::raster_snapshot(&snapshot)?;
|
||||
for (x, y, color) in [(5, height / 4, [255, 0, 0, 255]), (105, height / 2 - 10, [0, 255, 0, 255])] {
|
||||
let offset = ((y * image.width + x) * 4) as usize;
|
||||
assert_eq!(&image.rgba[offset..offset + 4], color, "group height {height}, pixel ({x}, {y})");
|
||||
}
|
||||
}
|
||||
page.vm_mut().eval(r#"
|
||||
paintedCase.style.display = 'none';
|
||||
const borderedCase = cases.find(c => c.name === 'empty-groups-collapsed').owner;
|
||||
borderedCase.style.display = 'block';
|
||||
borderedCase.querySelector('thead').style.height = '80px';
|
||||
borderedCase.querySelector('tfoot').style.height = '80px';
|
||||
"#)?;
|
||||
let snapshot = page.vm_mut().screenshot_layout_snapshot(moli_layout::PaintViewport::new(240, 200, 1.0))?.expect("collapsed table root");
|
||||
let image = moli_paint::raster_snapshot(&snapshot)?;
|
||||
for (x, y) in [(100, 1), (100, 187), (1, 100)] {
|
||||
let offset = ((y * image.width + x) * 4) as usize;
|
||||
assert_eq!(&image.rgba[offset..offset + 4], [0, 0, 255, 255], "collapsed border at ({x}, {y})");
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}).await.expect("table row heights should match Chromium");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn screenshot_recascades_table_part_dimension_hints() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
+3
-19
@@ -36,7 +36,6 @@
|
||||
];
|
||||
const checks = [];
|
||||
const mutations = [];
|
||||
const cssHeightReferences = [];
|
||||
for (const dimension of dimensions) {
|
||||
for (const test of cases) {
|
||||
const owner = document.createElement('div');
|
||||
@@ -64,10 +63,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Table/row heights size real rows; zero preserves the content minimum.
|
||||
// Moli's table formatter currently ignores CSS row-group heights too, so
|
||||
// compare those native groups with authored CSS. The block cases above
|
||||
// independently verify the hint values, including percentages and zero.
|
||||
// Native table, row and row-group hints constrain actual row geometry.
|
||||
// Zero/removal preserves the content minimum (Chromium 147 oracle).
|
||||
for (const tag of ['table','thead','tbody','tfoot','tr']) {
|
||||
const table = document.createElement('table');
|
||||
table.style.width = '200px';
|
||||
@@ -81,15 +78,6 @@
|
||||
document.body.append(table);
|
||||
const check = {id:`native-${tag}`, element:row,
|
||||
expected:[[200,32],[200,64],[200,8],[200,8]]};
|
||||
if (element === section) {
|
||||
const reference = table.cloneNode(true);
|
||||
const referenceGroup = reference.querySelector(tag);
|
||||
referenceGroup.removeAttribute('height');
|
||||
referenceGroup.style.height = '32px';
|
||||
document.body.append(reference);
|
||||
check.reference = reference.rows[0];
|
||||
cssHeightReferences.push(referenceGroup);
|
||||
}
|
||||
checks.push(check);
|
||||
mutations.push({element, names:['height']});
|
||||
}
|
||||
@@ -127,18 +115,14 @@
|
||||
else if (phase === 3) element.setAttribute(name, '0');
|
||||
}
|
||||
}
|
||||
for (const element of cssHeightReferences) {
|
||||
element.style.height = ['32px','64px','auto','0px'][phase];
|
||||
}
|
||||
}
|
||||
function collectTablePartDimensionChecks() {
|
||||
return checks.map(check => {
|
||||
const rect = check.element && check.element.getBoundingClientRect();
|
||||
const reference = check.reference && check.reference.getBoundingClientRect();
|
||||
return {id:check.id,
|
||||
actual:check.cells ? check.cells.map(cell => cell.getBoundingClientRect().width)
|
||||
: [rect.width,rect.height],
|
||||
expected:reference ? [reference.width,reference.height] : check.expected[dimensionPhase]};
|
||||
expected:check.expected[dimensionPhase]};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user