Files
rust_browser/tests/goldens.rs
Zachary D. Rowitsch 70ad1244d8 Implement base URL resolution and image rendering completeness with code review fixes (§4.2.3)
Add <base href> support with resolve_base_url() wired before all resource loading,
document.baseURI JS API (falling back to "about:blank" per spec), image format
verification golden tests (PNG/JPEG/GIF/WebP/SVG for both <img> and CSS
background-image), and aspect ratio preservation tests. Code review fixes:
baseURI spec compliance, restyle path no longer overwrites <base href> URL,
added CSS background-image format tests and base URL integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 01:36:35 -04:00

1786 lines
42 KiB
Rust

//! Golden tests for the rendering pipeline.
//!
//! These tests compare the output of the rendering pipeline against
//! expected "golden" files. If the output differs, the test fails.
//!
//! To update golden files when intentionally changing output:
//! ```
//! for f in tests/goldens/fixtures/*.html; do
//! cargo run -p app_browser -- --render "$f" --output-dir tests/goldens/expected
//! done
//! ```
use std::fs;
use std::path::{Path, PathBuf};
// Re-export pipeline for tests
mod pipeline_test {
use display_list::build_display_list;
use html::HtmlParser;
use image::{ImagePipeline, ImageStore};
use layout::LayoutEngine;
use std::collections::HashMap;
use std::path::Path;
use style::{BackgroundImage, Display, StyleContext};
pub fn run_pipeline(html: &str) -> (String, String) {
let html_parser = HtmlParser::new();
let style_context = StyleContext::new();
let layout_engine = LayoutEngine::new_proportional();
let document = html_parser.parse(html);
// Include UA stylesheet first (lowest priority), then author stylesheets
let stylesheets = style_context.extract_stylesheets(&document);
let inline_styles = style_context.extract_inline_styles(&document);
let computed_styles = style_context.compute_styles(
&document,
&stylesheets,
&inline_styles,
Some(&css::Viewport::new(800.0, 600.0)),
);
let layout_tree = layout_engine.layout(&document, &computed_styles, 800.0, 600.0);
let display_list = build_display_list(&layout_tree);
(layout_tree.dump(), display_list.dump())
}
/// Run the pipeline with image loading from a fixture directory.
/// Resolves `<img src="...">` and `<object data="...">` paths relative to `fixture_dir`.
/// `data:` URIs are loaded via the network stack.
pub fn run_pipeline_with_images(html: &str, fixture_dir: &Path) -> (String, String) {
let html_parser = HtmlParser::new();
let style_context = StyleContext::new();
let layout_engine = LayoutEngine::new_proportional();
let image_pipeline = ImagePipeline::new();
let document = html_parser.parse(html);
let stylesheets = style_context.extract_stylesheets(&document);
let inline_styles = style_context.extract_inline_styles(&document);
let computed_styles = style_context.compute_styles(
&document,
&stylesheets,
&inline_styles,
Some(&css::Viewport::new(800.0, 600.0)),
);
// Load images from <img> elements
let mut image_store = ImageStore::new();
let mut image_map = HashMap::new();
for node_id in document.get_elements_by_tag_name("img") {
// Skip display:none elements
if let Some(styles) = computed_styles.get(&node_id) {
if styles.display == Display::None {
continue;
}
}
if let Some(src) = document.get_attribute(node_id, "src") {
let image_path = fixture_dir.join(src);
if let Ok(bytes) = std::fs::read(&image_path) {
if let Ok(decoded) = image_pipeline.decode(&bytes, None) {
let img_id = image_store.insert(decoded);
image_map.insert(node_id, img_id);
}
}
}
}
// Load images from <object> elements' data attributes
let network = net::NetworkStack::new();
for node_id in document.get_elements_by_tag_name("object") {
if let Some(styles) = computed_styles.get(&node_id) {
if styles.display == Display::None {
continue;
}
}
if let Some(data) = document.get_attribute(node_id, "data") {
let bytes_and_mime = if data.starts_with("data:") {
// data: URIs — use network stack
shared::BrowserUrl::parse(data)
.ok()
.and_then(|url| network.load_sync(&url).ok())
.filter(|resp| resp.is_success())
.map(|resp| (resp.body, Some(resp.content_type.mime_type())))
} else {
// File path — read from fixture directory
let image_path = fixture_dir.join(data);
std::fs::read(&image_path).ok().map(|b| (b, None))
};
if let Some((bytes, mime)) = bytes_and_mime {
// Skip non-image MIME types — fall through to child fallback
if let Some(ref m) = mime {
if !m.starts_with("image/") && m != "application/octet-stream" {
continue;
}
}
if let Ok(decoded) = image_pipeline.decode(&bytes, mime.as_deref()) {
let img_id = image_store.insert(decoded);
image_map.insert(node_id, img_id);
}
}
}
}
// Load images from CSS background-image: url(...) properties.
// Iterate in document order (not HashMap order) for deterministic image IDs.
for node_id in document.iter_elements() {
if let Some(styles) = computed_styles.get(&node_id) {
if let BackgroundImage::Url(ref url) = styles.background_image {
// Skip if already loaded (multiple elements may share the same URL)
if image_store.get_by_url(url).is_some() {
continue;
}
let image_path = fixture_dir.join(url);
if let Ok(bytes) = std::fs::read(&image_path) {
if let Ok(decoded) = image_pipeline.decode(&bytes, None) {
image_store.insert_url(url, decoded);
}
}
}
}
}
let layout_tree = layout_engine.layout_with_images(
&document,
&computed_styles,
800.0,
600.0,
&image_map,
&image_store,
);
let display_list = build_display_list(&layout_tree);
(layout_tree.dump(), display_list.dump())
}
/// Run the pipeline with iframe support using `app_browser::Pipeline`.
///
/// Unlike `run_pipeline`, this uses the full `Pipeline::run_with_url()` path
/// which calls `fetch_and_render_iframes()`, so `<iframe srcdoc="...">` content
/// is actually parsed, styled, laid out, and rendered into the display list.
pub fn run_pipeline_with_iframes(html: &str, fixture_dir: &Path) -> (String, String) {
let base_url =
shared::BrowserUrl::parse(&format!("file://{}/_", fixture_dir.to_str().unwrap()))
.unwrap();
let network = net::NetworkStack::new();
let mut pipeline = app_browser::Pipeline::new(800.0, 600.0);
let result = pipeline.run_with_url(html, &base_url, &network).unwrap();
(result.layout_dump, result.display_list_dump)
}
}
fn get_goldens_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/goldens")
}
fn compare_golden_outputs(
fixture_name: &str,
actual_layout: &str,
actual_dl: &str,
expected_dir: &Path,
) {
let expected_layout_path = expected_dir.join(format!("{}.layout.txt", fixture_name));
let expected_dl_path = expected_dir.join(format!("{}.dl.txt", fixture_name));
let expected_layout = fs::read_to_string(&expected_layout_path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", expected_layout_path.display(), e));
let expected_dl = fs::read_to_string(&expected_dl_path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", expected_dl_path.display(), e));
// Auto-update mode: set UPDATE_GOLDENS=1 to write actual outputs as new expected
if std::env::var("UPDATE_GOLDENS").is_ok()
&& (actual_layout != expected_layout || actual_dl != expected_dl)
{
fs::write(&expected_layout_path, actual_layout).unwrap();
fs::write(&expected_dl_path, actual_dl).unwrap();
eprintln!("Updated golden files for {}", fixture_name);
return;
}
if actual_layout != expected_layout {
panic!(
"Layout mismatch for {}!\n\
=== Expected ===\n{}\n\
=== Actual ===\n{}\n",
fixture_name, expected_layout, actual_layout
);
}
if actual_dl != expected_dl {
panic!(
"Display list mismatch for {}!\n\
=== Expected ===\n{}\n\
=== Actual ===\n{}\n",
fixture_name, expected_dl, actual_dl
);
}
}
fn run_golden_test(fixture_name: &str) {
let goldens_dir = get_goldens_dir();
let fixtures_dir = goldens_dir.join("fixtures");
let expected_dir = goldens_dir.join("expected");
let html_path = fixtures_dir.join(format!("{}.html", fixture_name));
let html = fs::read_to_string(&html_path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", html_path.display(), e));
let (actual_layout, actual_dl) = pipeline_test::run_pipeline(&html);
compare_golden_outputs(fixture_name, &actual_layout, &actual_dl, &expected_dir);
}
fn run_golden_test_with_images(fixture_name: &str) {
let goldens_dir = get_goldens_dir();
let fixtures_dir = goldens_dir.join("fixtures");
let expected_dir = goldens_dir.join("expected");
let html_path = fixtures_dir.join(format!("{}.html", fixture_name));
let html = fs::read_to_string(&html_path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", html_path.display(), e));
let (actual_layout, actual_dl) = pipeline_test::run_pipeline_with_images(&html, &fixtures_dir);
compare_golden_outputs(fixture_name, &actual_layout, &actual_dl, &expected_dir);
}
fn run_golden_test_with_iframes(fixture_name: &str) {
let goldens_dir = get_goldens_dir();
let fixtures_dir = goldens_dir.join("fixtures");
let expected_dir = goldens_dir.join("expected");
let html_path = fixtures_dir.join(format!("{}.html", fixture_name));
let html = fs::read_to_string(&html_path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", html_path.display(), e));
let (actual_layout, actual_dl) = pipeline_test::run_pipeline_with_iframes(&html, &fixtures_dir);
compare_golden_outputs(fixture_name, &actual_layout, &actual_dl, &expected_dir);
}
#[test]
fn golden_001_empty() {
run_golden_test("001-empty");
}
#[test]
fn golden_002_single_div() {
run_golden_test("002-single-div");
}
#[test]
fn golden_003_nested_divs() {
run_golden_test("003-nested-divs");
}
#[test]
fn golden_004_text_content() {
run_golden_test("004-text-content");
}
#[test]
fn golden_005_inline_style() {
run_golden_test("005-inline-style");
}
#[test]
fn golden_006_style_tag() {
run_golden_test("006-style-tag");
}
#[test]
fn golden_007_class_selector() {
run_golden_test("007-class-selector");
}
#[test]
fn golden_008_id_selector() {
run_golden_test("008-id-selector");
}
#[test]
fn golden_009_margin_padding() {
run_golden_test("009-margin-padding");
}
#[test]
fn golden_010_block_layout() {
run_golden_test("010-block-layout");
}
#[test]
fn golden_011_width_height() {
run_golden_test("011-width-height");
}
#[test]
fn golden_012_background_color() {
run_golden_test("012-background-color");
}
#[test]
fn golden_013_border_color() {
run_golden_test("013-border-color");
}
#[test]
fn golden_014_border_style_none() {
run_golden_test("014-border-style-none");
}
#[test]
fn golden_015_border_shorthand() {
run_golden_test("015-border-shorthand");
}
#[test]
fn golden_016_border_per_side() {
run_golden_test("016-border-per-side");
}
// Phase 1B: Inline Layout Tests
#[test]
fn golden_017_inline_span() {
run_golden_test("017-inline-span");
}
#[test]
fn golden_018_multiple_inline() {
run_golden_test("018-multiple-inline");
}
#[test]
fn golden_019_text_wrapping() {
run_golden_test("019-text-wrapping");
}
#[test]
fn golden_020_mixed_content() {
run_golden_test("020-mixed-content");
}
#[test]
fn golden_021_nested_inline() {
run_golden_test("021-nested-inline");
}
#[test]
fn golden_022_inline_background_border() {
run_golden_test("022-inline-background-border");
}
#[test]
fn golden_023_nested_inline_styles() {
run_golden_test("023-nested-inline-styles");
}
#[test]
fn golden_024_inline_border_wrap() {
run_golden_test("024-inline-border-wrap");
}
// Phase 1D: Golden Test Expansion (Edge Cases)
#[test]
fn golden_025_empty_inline() {
run_golden_test("025-empty-inline");
}
#[test]
fn golden_026_whitespace_only_inline() {
run_golden_test("026-whitespace-only-inline");
}
#[test]
fn golden_027_unbreakable_long_word() {
run_golden_test("027-unbreakable-long-word");
}
#[test]
fn golden_028_inline_at_block_start() {
run_golden_test("028-inline-at-block-start");
}
#[test]
fn golden_029_inline_at_block_end() {
run_golden_test("029-inline-at-block-end");
}
#[test]
fn golden_030_adjacent_inline_no_space() {
run_golden_test("030-adjacent-inline-no-space");
}
#[test]
fn golden_031_different_border_widths() {
run_golden_test("031-different-border-widths");
}
#[test]
fn golden_032_full_box_model() {
run_golden_test("032-full-box-model");
}
#[test]
fn golden_033_zero_width_border() {
run_golden_test("033-zero-width-border");
}
#[test]
fn golden_034_deeply_nested_inline() {
run_golden_test("034-deeply-nested-inline");
}
#[test]
fn golden_035_multiple_blocks_interrupting() {
run_golden_test("035-multiple-blocks-interrupting");
}
#[test]
fn golden_036_empty_nested_inline() {
run_golden_test("036-empty-nested-inline");
}
#[test]
fn golden_037_entities() {
run_golden_test("037-entities");
}
#[test]
fn golden_038_inline_link() {
run_golden_test("038-inline-link");
}
// Phase 2: Navigation Milestone Tests
#[test]
fn golden_039_anchor_link() {
run_golden_test("039-anchor-link");
}
#[test]
fn golden_040_multiple_anchors() {
run_golden_test("040-multiple-anchors");
}
#[test]
fn golden_041_anchor_with_style() {
run_golden_test("041-anchor-with-style");
}
#[test]
fn golden_042_nested_anchor() {
run_golden_test("042-nested-anchor");
}
#[test]
fn golden_043_heading_levels() {
run_golden_test("043-heading-levels");
}
#[test]
fn golden_044_paragraph_text() {
run_golden_test("044-paragraph-text");
}
#[test]
fn golden_045_mixed_headings_para() {
run_golden_test("045-mixed-headings-para");
}
#[test]
fn golden_046_list_like_divs() {
run_golden_test("046-list-like-divs");
}
#[test]
fn golden_047_wide_content() {
run_golden_test("047-wide-content");
}
#[test]
fn golden_048_tall_content() {
run_golden_test("048-tall-content");
}
#[test]
fn golden_049_test2() {
run_golden_test("049-css-width-percentage");
}
// Milestone 3: Expand CSS for Real Pages - Phase 1: Selector Validation
#[test]
fn golden_050_descendant_deep() {
run_golden_test("050-descendant-deep");
}
#[test]
fn golden_051_child_vs_descendant() {
run_golden_test("051-child-vs-descendant");
}
// Milestone 3: Phase 2 - Position Relative/Absolute
#[test]
fn golden_052_position_relative() {
run_golden_test("052-position-relative");
}
#[test]
fn golden_053_position_relative_offset() {
run_golden_test("053-position-relative-offset");
}
#[test]
fn golden_054_position_absolute() {
run_golden_test("054-position-absolute");
}
#[test]
fn golden_055_absolute_in_relative() {
run_golden_test("055-absolute-in-relative");
}
// Milestone 3: Phase 3 - Stacking Contexts & Z-Index
#[test]
fn golden_056_z_index_basic() {
run_golden_test("056-z-index-basic");
}
#[test]
fn golden_057_z_index_negative() {
run_golden_test("057-z-index-negative");
}
#[test]
fn golden_058_stacking_context() {
run_golden_test("058-stacking-context");
}
// Milestone 3: Phase 4 - Overflow & Clipping
#[test]
fn golden_059_overflow_hidden() {
run_golden_test("059-overflow-hidden");
}
#[test]
fn golden_060_overflow_nested() {
run_golden_test("060-overflow-nested");
}
// Milestone 3: Phase 5 - Flexbox Layout
#[test]
fn golden_061_flex_row() {
run_golden_test("061-flex-row");
}
#[test]
fn golden_062_flex_column() {
run_golden_test("062-flex-column");
}
#[test]
fn golden_063_flex_justify() {
run_golden_test("063-flex-justify");
}
#[test]
fn golden_064_flex_align() {
run_golden_test("064-flex-align");
}
#[test]
fn golden_065_flex_grow() {
run_golden_test("065-flex-grow");
}
#[test]
fn golden_066_flex_gap() {
run_golden_test("066-flex-gap");
}
// Phase 3: white-space property and <pre> tag support
#[test]
fn golden_067_pre_basic() {
run_golden_test("067-pre-basic");
}
#[test]
fn golden_068_pre_whitespace() {
run_golden_test("068-pre-whitespace");
}
#[test]
fn golden_069_white_space_pre_css() {
run_golden_test("069-white-space-pre-css");
}
#[test]
fn golden_070_white_space_nowrap() {
run_golden_test("070-white-space-nowrap");
}
#[test]
fn golden_071_white_space_pre_wrap() {
run_golden_test("071-white-space-pre-wrap");
}
#[test]
fn golden_072_white_space_pre_line() {
run_golden_test("072-white-space-pre-line");
}
#[test]
fn golden_073_pre_tabs() {
run_golden_test("073-pre-tabs");
}
#[test]
fn golden_074_pre_empty() {
run_golden_test("074-pre-empty");
}
#[test]
fn golden_075_pre_nested_inline() {
run_golden_test("075-pre-nested-inline");
}
#[test]
fn golden_076_pre_override_css() {
run_golden_test("076-pre-override-css");
}
#[test]
fn golden_077_pre_multiple_newlines() {
run_golden_test("077-pre-multiple-newlines");
}
// Phase: Table Layout Support
#[test]
fn golden_078_table_basic() {
run_golden_test("078-table-basic");
}
#[test]
fn golden_079_table_headers() {
run_golden_test("079-table-headers");
}
#[test]
fn golden_080_table_sections() {
run_golden_test("080-table-sections");
}
#[test]
fn golden_081_table_colspan() {
run_golden_test("081-table-colspan");
}
#[test]
fn golden_082_table_rowspan() {
run_golden_test("082-table-rowspan");
}
#[test]
fn golden_083_table_mixed_spans() {
run_golden_test("083-table-mixed-spans");
}
#[test]
fn golden_084_table_column_widths() {
run_golden_test("084-table-column-widths");
}
#[test]
fn golden_085_table_cell_padding() {
run_golden_test("085-table-cell-padding");
}
#[test]
fn golden_086_table_backgrounds() {
run_golden_test("086-table-backgrounds");
}
#[test]
fn golden_087_table_width() {
run_golden_test("087-table-width");
}
#[test]
fn golden_088_table_nested() {
run_golden_test("088-table-nested");
}
#[test]
fn golden_089_table_text_wrap() {
run_golden_test("089-table-text-wrap");
}
#[test]
fn golden_090_table_empty_cells() {
run_golden_test("090-table-empty-cells");
}
// XML/XHTML compatibility
#[test]
fn golden_091_xml_processing_instructions() {
run_golden_test("091-xml-processing-instructions");
}
// HTML parsing edge cases
#[test]
fn golden_092_unquoted_attr_with_slash() {
run_golden_test("092-unquoted-attr-with-slash");
}
// Border styles: dashed, dotted, CSS variables
#[test]
fn golden_093_dashed_borders() {
run_golden_test("093-dashed-borders");
}
// Table cell vertical-align
#[test]
fn golden_094_table_vertical_align() {
run_golden_test("094-table-vertical-align");
}
// CSS Feature Expansion: min/max dimensions, position: fixed, inline-block
#[test]
fn golden_095_max_width() {
run_golden_test("095-max-width");
}
#[test]
fn golden_096_min_height() {
run_golden_test("096-min-height");
}
#[test]
fn golden_097_position_fixed() {
run_golden_test("097-position-fixed");
}
#[test]
fn golden_098_inline_block_basic() {
run_golden_test("098-inline-block-basic");
}
#[test]
fn golden_099_min_max_width_conflict() {
run_golden_test("099-min-max-width-conflict");
}
#[test]
fn golden_100_fixed_nested() {
run_golden_test("100-fixed-nested");
}
#[test]
fn golden_101_inline_block_wrapping() {
run_golden_test("101-inline-block-wrapping");
}
#[test]
fn golden_102_inline_block_padding_margin() {
run_golden_test("102-inline-block-padding-margin");
}
#[test]
fn golden_103_max_width_percentage() {
run_golden_test("103-max-width-percentage");
}
#[test]
fn golden_104_fixed_with_scroll() {
run_golden_test("104-fixed-with-scroll");
}
// CSS Float Layout
#[test]
fn golden_105_float_left_basic() {
run_golden_test("105-float-left-basic");
}
#[test]
fn golden_106_float_right_basic() {
run_golden_test("106-float-right-basic");
}
#[test]
fn golden_107_float_multiple_left() {
run_golden_test("107-float-multiple-left");
}
#[test]
fn golden_108_float_clear_left() {
run_golden_test("108-float-clear-left");
}
#[test]
fn golden_109_float_clear_both() {
run_golden_test("109-float-clear-both");
}
#[test]
fn golden_110_float_overflow_contain() {
run_golden_test("110-float-overflow-contain");
}
#[test]
fn golden_111_float_no_parent_expand() {
run_golden_test("111-float-no-parent-expand");
}
#[test]
fn golden_112_float_inline_wrap() {
run_golden_test("112-float-inline-wrap");
}
#[test]
fn golden_113_float_drop_below() {
run_golden_test("113-float-drop-below");
}
#[test]
fn golden_114_float_with_margin() {
run_golden_test("114-float-with-margin");
}
#[test]
fn golden_115_float_with_padding_border() {
run_golden_test("115-float-with-padding-border");
}
#[test]
fn golden_116_float_percentage_width() {
run_golden_test("116-float-percentage-width");
}
#[test]
fn golden_117_float_next_to_inline_block() {
run_golden_test("117-float-next-to-inline-block");
}
#[test]
fn golden_118_float_with_clear_on_float() {
run_golden_test("118-float-with-clear-on-float");
}
#[test]
fn golden_119_float_empty() {
run_golden_test("119-float-empty");
}
#[test]
fn golden_120_float_mixed_left_right() {
run_golden_test("120-float-mixed-left-right");
}
#[test]
fn golden_121_float_nested_bfc() {
run_golden_test("121-float-nested-bfc");
}
#[test]
fn golden_122_float_with_text_content() {
run_golden_test("122-float-with-text-content");
}
#[test]
fn golden_123_float_stacking_multiple_heights() {
run_golden_test("123-float-stacking-multiple-heights");
}
#[test]
fn golden_124_float_clear_right() {
run_golden_test("124-float-clear-right");
}
// Milestone 4: Image Support
#[test]
fn golden_125_img_basic() {
run_golden_test_with_images("125-img-basic");
}
#[test]
fn golden_126_img_width_attr() {
run_golden_test_with_images("126-img-width-attr");
}
#[test]
fn golden_127_img_height_attr() {
run_golden_test_with_images("127-img-height-attr");
}
#[test]
fn golden_128_img_both_attrs() {
run_golden_test_with_images("128-img-both-attrs");
}
#[test]
fn golden_129_img_css_width() {
run_golden_test_with_images("129-img-css-width");
}
#[test]
fn golden_130_img_css_both() {
run_golden_test_with_images("130-img-css-both");
}
#[test]
fn golden_131_img_in_div() {
run_golden_test_with_images("131-img-in-div");
}
#[test]
fn golden_132_img_inline() {
run_golden_test_with_images("132-img-inline");
}
#[test]
fn golden_133_img_missing_src() {
run_golden_test_with_images("133-img-missing-src");
}
#[test]
fn golden_134_img_broken_src() {
run_golden_test_with_images("134-img-broken-src");
}
#[test]
fn golden_135_img_with_border() {
run_golden_test_with_images("135-img-with-border");
}
#[test]
fn golden_136_img_display_block() {
run_golden_test_with_images("136-img-display-block");
}
// Milestone 5: Font Properties
#[test]
fn golden_137_font_weight_bold() {
run_golden_test("137-font-weight-bold");
}
#[test]
fn golden_138_font_style_italic() {
run_golden_test("138-font-style-italic");
}
#[test]
fn golden_139_font_family_monospace() {
run_golden_test("139-font-family-monospace");
}
#[test]
fn golden_140_mixed_font_styles() {
run_golden_test("140-mixed-font-styles");
}
#[test]
fn golden_141_heading_font_weight() {
run_golden_test("141-heading-font-weight");
}
// Regression: Image inside inline wrapper (e.g., <font><img></font>)
#[test]
fn golden_142_img_in_inline_wrapper() {
run_golden_test_with_images("142-img-in-inline-wrapper");
}
#[test]
fn golden_143_img_link_in_table() {
run_golden_test_with_images("143-img-link-in-table");
}
#[test]
fn golden_144_grid_fixed_columns() {
run_golden_test("144-grid-fixed-columns");
}
#[test]
fn golden_145_grid_fr_units() {
run_golden_test("145-grid-fr-units");
}
#[test]
fn golden_146_grid_gap() {
run_golden_test("146-grid-gap");
}
#[test]
fn golden_147_grid_explicit_placement() {
run_golden_test("147-grid-explicit-placement");
}
#[test]
fn golden_148_grid_span() {
run_golden_test("148-grid-span");
}
// Float children positioning fix
#[test]
fn golden_149_float_right_with_block_children() {
run_golden_test("149-float-right-with-block-children");
}
// Flex container Y positioning + CSS min() in grid templates
#[test]
fn golden_150_flex_nav_in_grid() {
run_golden_test("150-flex-nav-in-grid");
}
// Grid items with explicit column but auto row should not overlap earlier rows
#[test]
fn golden_151_grid_explicit_col_auto_row() {
run_golden_test("151-grid-explicit-col-auto-row");
}
// Negative grid line numbers: 1/-1 spans all columns, 2/-1 spans last two
#[test]
fn golden_152_grid_negative_line_numbers() {
run_golden_test("152-grid-negative-line-numbers");
}
// List marker support
#[test]
fn golden_153_unordered_list() {
run_golden_test("153-unordered-list");
}
#[test]
fn golden_154_ordered_list() {
run_golden_test("154-ordered-list");
}
#[test]
fn golden_155_ordered_list_start() {
run_golden_test("155-ordered-list-start");
}
#[test]
fn golden_156_nested_lists() {
run_golden_test("156-nested-lists");
}
#[test]
fn golden_157_ordered_list_type() {
run_golden_test("157-ordered-list-type");
}
// Table with caption regression: captions must not cause row groups to be
// wrapped in anonymous blocks (which hides them from the table layout algorithm)
#[test]
fn golden_158_table_with_caption() {
run_golden_test("158-table-with-caption");
}
// Flexbox comprehensive tests
#[test]
fn golden_159_flex_wrap() {
run_golden_test("159-flex-wrap");
}
#[test]
fn golden_160_flex_direction_reverse() {
run_golden_test("160-flex-direction-reverse");
}
#[test]
fn golden_161_flex_align_self() {
run_golden_test("161-flex-align-self");
}
#[test]
fn golden_162_flex_shorthand() {
run_golden_test("162-flex-shorthand");
}
#[test]
fn golden_163_flex_order() {
run_golden_test("163-flex-order");
}
#[test]
fn golden_164_inline_flex() {
run_golden_test("164-inline-flex");
}
#[test]
fn golden_165_justify_content_space_evenly() {
run_golden_test("165-justify-content-space-evenly");
}
#[test]
fn golden_166_box_sizing() {
run_golden_test("166-box-sizing");
}
#[test]
fn golden_167_box_sizing_comprehensive() {
run_golden_test("167-box-sizing-comprehensive");
}
#[test]
fn golden_168_bootstrap_fixed_top() {
run_golden_test("168-bootstrap-fixed-top");
}
#[test]
fn golden_169_img_svg_basic() {
run_golden_test_with_images("169-img-svg-basic");
}
#[test]
fn golden_171_center_element_block() {
run_golden_test("171-center-element-block");
}
#[test]
fn golden_172_line_height_var() {
run_golden_test("172-line-height-var");
}
#[test]
fn golden_173_table_anonymous_row() {
run_golden_test("173-table-anonymous-row");
}
#[test]
fn golden_174_object_fallback() {
run_golden_test_with_images("174-object-fallback");
}
#[test]
fn golden_175_object_data_uri() {
run_golden_test_with_images("175-object-data-uri");
}
#[test]
fn golden_176_object_fallback_chain() {
run_golden_test_with_images("176-object-fallback-chain");
}
#[test]
fn golden_177_z_index_deep_stacking() {
run_golden_test("177-z-index-deep-stacking");
}
#[test]
fn golden_178_z_index_tree_order() {
run_golden_test("178-z-index-tree-order");
}
#[test]
fn golden_179_z_index_overflow_clip() {
run_golden_test("179-z-index-overflow-clip");
}
#[test]
fn golden_180_absolute_shrink_to_fit() {
run_golden_test("180-absolute-shrink-to-fit");
}
// Border-radius support
#[test]
fn golden_181_border_radius_basic() {
run_golden_test("181-border-radius-basic");
}
#[test]
fn golden_182_border_radius_per_corner() {
run_golden_test("182-border-radius-per-corner");
}
#[test]
fn golden_183_border_radius_circle() {
run_golden_test("183-border-radius-circle");
}
#[test]
fn golden_184_border_radius_clamping() {
run_golden_test("184-border-radius-clamping");
}
#[test]
fn golden_185_border_radius_no_border() {
run_golden_test("185-border-radius-no-border");
}
#[test]
fn golden_186_negative_margin_auto_height() {
run_golden_test("186-negative-margin-auto-height");
}
#[test]
fn golden_187_table_anonymous_objects() {
run_golden_test("187-table-anonymous-objects");
}
#[test]
fn golden_188_object_non_image_mime_fallback() {
run_golden_test_with_images("188-object-non-image-mime-fallback");
}
#[test]
fn golden_189_table_col_width() {
run_golden_test("189-table-col-width");
}
#[test]
fn golden_190_table_colgroup() {
run_golden_test("190-table-colgroup");
}
#[test]
fn golden_191_table_col_span() {
run_golden_test("191-table-col-span");
}
#[test]
fn golden_192_float_stacking_context() {
run_golden_test("192-float-stacking-context");
}
#[test]
fn golden_193_abspos_inline_ifc() {
run_golden_test("193-abspos-inline-ifc");
}
#[test]
fn golden_194_float_right_stacking_context() {
run_golden_test("194-float-right-stacking-context");
}
#[test]
fn golden_195_abspos_inline_ifc_with_offset() {
run_golden_test("195-abspos-inline-ifc-with-offset");
}
#[test]
fn golden_199_text_transform() {
run_golden_test("199-text-transform");
}
#[test]
fn golden_200_table_layout_fixed() {
run_golden_test("200-table-layout-fixed");
}
#[test]
fn golden_201_table_layout_fixed_first_row() {
run_golden_test("201-table-layout-fixed-first-row");
}
#[test]
fn golden_202_table_layout_fixed_col() {
run_golden_test("202-table-layout-fixed-col");
}
#[test]
fn golden_203_table_layout_fixed_auto_width() {
run_golden_test("203-table-layout-fixed-auto-width");
}
#[test]
fn golden_204_table_layout_fixed_colspan_first_row() {
// colspan > 1 in the first row: the spanning cell's width hint must be
// ignored for fixed layout; columns fall back to equal distribution.
run_golden_test("204-table-layout-fixed-colspan-first-row");
}
#[test]
fn golden_205_table_layout_fixed_percentage_first_row() {
// Percentage widths on first-row cells are resolved against the table
// content width, not the containing block.
run_golden_test("205-table-layout-fixed-percentage-first-row");
}
#[test]
fn golden_206_table_layout_fixed_all_explicit() {
// All columns have explicit widths that don't sum to the table width:
// the implementation scales them proportionally to fill.
run_golden_test("206-table-layout-fixed-all-explicit");
}
#[test]
fn golden_207_content_attr() {
// CSS attr() function in content: resolves the element's href attribute and
// appends it after the link text via ::after.
run_golden_test("207-content-attr");
}
#[test]
fn golden_208_flex_row_auto_height() {
// Flex row container with height:auto must expand to contain children
// whose height comes from their own block children (F-001 regression).
run_golden_test("208-flex-row-auto-height");
}
#[test]
fn golden_209_margin_collapse_parent_first_child() {
run_golden_test("209-margin-collapse-parent-first-child");
}
#[test]
fn golden_210_margin_collapse_parent_last_child() {
run_golden_test("210-margin-collapse-parent-last-child");
}
#[test]
fn golden_211_margin_collapse_nested_chain() {
run_golden_test("211-margin-collapse-nested-chain");
}
#[test]
fn golden_212_margin_collapse_min_height() {
run_golden_test("212-margin-collapse-min-height");
}
#[test]
fn golden_213_z_index_negative() {
run_golden_test("213-z-index-negative");
}
#[test]
fn golden_214_z_index_positioned_auto_tree_order() {
run_golden_test("214-z-index-positioned-auto-tree-order");
}
#[test]
fn golden_215_z_index_interleaved_positioned() {
run_golden_test("215-z-index-interleaved-positioned");
}
#[test]
fn golden_216_z_index_nested_atomicity() {
// Atomicity: child z-index:999 inside parent z-index:1 must NOT paint above sibling z-index:2
run_golden_test("216-z-index-nested-atomicity");
}
#[test]
fn golden_217_z_index_three_level_nesting() {
run_golden_test("217-z-index-three-level-nesting");
}
#[test]
fn golden_218_z_index_zero_step6() {
// z-index:0 stacking contexts must paint at step 6, not step 7
run_golden_test("218-z-index-zero-step6");
}
// Tests 219 and 220 replaced by stacking_order_tests.rs integration tests
// that assert paint-order invariants programmatically instead of relying
// on golden file snapshots.
#[test]
fn golden_222_abspos_auto_offset_static() {
// §10.3.7: Both left and right auto — uses static position
run_golden_test("222-abspos-auto-offset-static");
}
#[test]
fn golden_223_abspos_left_auto_right_specified() {
// §10.3.7: Left auto, right specified — position from right
run_golden_test("223-abspos-left-auto-right-specified");
}
#[test]
fn golden_224_abspos_overconstrained() {
// §10.3.7: Over-constrained (left+width+right) and auto-margin centering
run_golden_test("224-abspos-overconstrained");
}
#[test]
fn golden_225_clip_rect() {
// §11.1.2: clip: rect() on absolute and fixed elements
run_golden_test("225-clip-rect");
}
#[test]
fn golden_226_abspos_containing_block_chains() {
// §10.1: Containing block chains with padding edge verification
run_golden_test("226-abspos-containing-block-chains");
}
#[test]
fn golden_227_counter_basic() {
// §12.4: counter-reset, counter-increment, counter() in content
run_golden_test("227-counter-basic");
}
#[test]
fn golden_228_counters_nested() {
// §12.4.1: Nested counters with counters() function and separator
run_golden_test("228-counters-nested");
}
#[test]
fn golden_229_counter_upper_roman() {
// §12.4.2: counter() with upper-roman list-style-type
run_golden_test("229-counter-upper-roman");
}
#[test]
fn golden_230_quote_keywords() {
// §12.3: open-quote/close-quote keywords in content
run_golden_test("230-quote-keywords");
}
#[test]
fn golden_231_table_caption_side_bottom() {
// §17.4: caption-side: bottom places caption below table rows
run_golden_test("231-table-caption-side-bottom");
}
#[test]
fn golden_232_table_empty_cells_hide() {
// §17.6.1.1: empty-cells: hide suppresses borders/backgrounds for empty cells
run_golden_test("232-table-empty-cells-hide");
}
#[test]
fn golden_233_table_collapsed_groove_ridge() {
// §17.6.2: collapsed borders with groove, ridge, outset styles
run_golden_test("233-table-collapsed-groove-ridge");
}
#[test]
fn golden_234_table_fixed_collapsed() {
// §17.5.2.1 + §17.6.2: table-layout: fixed with border-collapse: collapse
run_golden_test("234-table-fixed-collapsed");
}
#[test]
fn golden_235_list_style_position_inside() {
// §12.6.3: list-style-position: inside places marker within content flow
run_golden_test("235-list-style-position-inside");
}
#[test]
fn golden_236_list_style_position_outside() {
// §12.6.3: list-style-position: outside places marker in margin area
run_golden_test("236-list-style-position-outside");
}
#[test]
fn golden_237_counter_reset_increment() {
// §12.4: counter-reset + counter-increment with content: counter()
run_golden_test("237-counter-reset-increment");
}
#[test]
fn golden_238_nested_counters() {
// §12.4.1: nested counter scoping with counters() separator
run_golden_test("238-nested-counters");
}
#[test]
fn golden_239_counter_with_list_style() {
// §12.4: counters with list-style-type formatting (upper-roman)
run_golden_test("239-counter-with-list-style");
}
#[test]
fn golden_240_list_style_image() {
// §12.6.2: list-style-image with fallback to list-style-type
run_golden_test("240-list-style-image");
}
// Story 1.7: Backgrounds — CSS 2.1 §14.2
#[test]
fn golden_241_background_image_url() {
// §14.2.1: background-image: url() with no-repeat
run_golden_test_with_images("241-background-image-url");
}
#[test]
fn golden_242_background_repeat_modes() {
// §14.2.2: all 4 background-repeat values with visible tiling
run_golden_test_with_images("242-background-repeat-modes");
}
#[test]
fn golden_243_background_position_keywords() {
// §14.2.1: background-position with keyword values
run_golden_test_with_images("243-background-position-keywords");
}
#[test]
fn golden_244_background_position_percentage() {
// §14.2.1: background-position with percentage values
run_golden_test_with_images("244-background-position-percentage");
}
#[test]
fn golden_245_background_attachment_fixed() {
// §14.2.1: background-attachment: fixed (viewport-relative)
run_golden_test_with_images("245-background-attachment-fixed");
}
#[test]
fn golden_246_background_shorthand() {
// §14.2.1: full background shorthand with all components
run_golden_test_with_images("246-background-shorthand");
}
#[test]
fn golden_247_background_gradient() {
// linear-gradient() background
run_golden_test("247-background-gradient");
}
#[test]
fn golden_248_background_canvas() {
// §14.2: root element background covering canvas with image
run_golden_test_with_images("248-background-canvas");
}
// Border styles and outline (CSS 2.1 §8.5.3, §18.4)
#[test]
fn golden_249_border_style_double() {
run_golden_test("249-border-style-double");
}
#[test]
fn golden_250_border_style_3d() {
run_golden_test("250-border-style-3d");
}
#[test]
fn golden_251_border_style_mixed() {
run_golden_test("251-border-style-mixed");
}
#[test]
fn golden_252_outline_basic() {
run_golden_test("252-outline-basic");
}
#[test]
fn golden_253_outline_offset() {
run_golden_test("253-outline-offset");
}
#[test]
fn golden_254_outline_styles() {
run_golden_test("254-outline-styles");
}
#[test]
fn golden_260_font_shorthand_complete() {
run_golden_test("260-font-shorthand-complete");
}
#[test]
fn golden_261_font_variant_small_caps() {
run_golden_test("261-font-variant-small-caps");
}
#[test]
fn golden_262_ex_units() {
run_golden_test("262-ex-units");
}
#[test]
fn golden_263_overflow_scroll() {
run_golden_test("263-overflow-scroll");
}
#[test]
fn golden_264_overflow_auto_overflow() {
run_golden_test("264-overflow-auto-overflow");
}
#[test]
fn golden_265_overflow_auto_no_overflow() {
run_golden_test("265-overflow-auto-no-overflow");
}
#[test]
fn golden_266_overflow_positioned_children() {
run_golden_test("266-overflow-positioned-children");
}
#[test]
fn golden_267_overflow_abspos_escape() {
run_golden_test("267-overflow-abspos-escape");
}
#[test]
fn golden_268_media_screen_applies() {
run_golden_test("268-media-screen-applies");
}
#[test]
fn golden_269_media_print_ignored() {
run_golden_test("269-media-print-ignored");
}
#[test]
fn golden_275_rcdata_rawtext_states() {
run_golden_test("275-rcdata-rawtext-states");
}
// HTML tree builder insertion mode tests
#[test]
fn golden_276_insertion_mode_transitions() {
run_golden_test("276-insertion-mode-transitions");
}
#[test]
fn golden_277_implicit_element_creation() {
run_golden_test("277-implicit-element-creation");
}
// HTML tree builder adoption agency and foster parenting
#[test]
fn golden_278_adoption_agency() {
run_golden_test("278-adoption-agency");
}
#[test]
fn golden_279_foster_parenting() {
run_golden_test("279-foster-parenting");
}
// iframe support (uses run_golden_test_with_iframes to exercise the full
// iframe rendering pipeline including srcdoc parsing and style isolation)
#[test]
fn golden_280_iframe_srcdoc() {
run_golden_test_with_iframes("280-iframe-srcdoc");
}
#[test]
fn golden_281_iframe_dimensions() {
run_golden_test_with_iframes("281-iframe-dimensions");
}
#[test]
fn golden_282_iframe_style_isolation() {
run_golden_test_with_iframes("282-iframe-style-isolation");
}
#[test]
fn golden_283_iframe_default_size() {
run_golden_test_with_iframes("283-iframe-default-size");
}
// Image format rendering verification (Story 2.9)
#[test]
fn golden_284_img_png_format() {
run_golden_test_with_images("284-img-png-format");
}
#[test]
fn golden_285_img_jpeg_format() {
run_golden_test_with_images("285-img-jpeg-format");
}
#[test]
fn golden_286_img_gif_format() {
run_golden_test_with_images("286-img-gif-format");
}
#[test]
fn golden_287_img_webp_format() {
run_golden_test_with_images("287-img-webp-format");
}
#[test]
fn golden_288_img_svg_format() {
run_golden_test_with_images("288-img-svg-format");
}
// Image aspect ratio preservation (Story 2.9)
#[test]
fn golden_289_img_aspect_ratio_width_only() {
run_golden_test_with_images("289-img-aspect-ratio-width-only");
}
#[test]
fn golden_290_img_aspect_ratio_height_only() {
run_golden_test_with_images("290-img-aspect-ratio-height-only");
}
#[test]
fn golden_291_img_aspect_ratio_both_attrs() {
run_golden_test_with_images("291-img-aspect-ratio-both-attrs");
}
#[test]
fn golden_292_img_aspect_ratio_css_width() {
run_golden_test_with_images("292-img-aspect-ratio-css-width");
}
#[test]
fn golden_293_img_aspect_ratio_max_width() {
run_golden_test_with_images("293-img-aspect-ratio-max-width");
}
#[test]
fn golden_294_img_intrinsic_size() {
run_golden_test_with_images("294-img-intrinsic-size");
}
// CSS background-image format rendering verification (Story 2.9)
#[test]
fn golden_295_bg_img_png_format() {
run_golden_test_with_images("295-bg-img-png-format");
}
#[test]
fn golden_296_bg_img_jpeg_format() {
run_golden_test_with_images("296-bg-img-jpeg-format");
}
#[test]
fn golden_297_bg_img_gif_format() {
run_golden_test_with_images("297-bg-img-gif-format");
}
#[test]
fn golden_298_bg_img_webp_format() {
run_golden_test_with_images("298-bg-img-webp-format");
}
#[test]
fn golden_299_bg_img_svg_format() {
run_golden_test_with_images("299-bg-img-svg-format");
}