From fa0eb8453a46fcf445aafd9e22d05fd7fb495410 Mon Sep 17 00:00:00 2001 From: Baptiste Parmantier Date: Sat, 8 Aug 2026 14:53:21 +0200 Subject: [PATCH] fix(html): turn the dialect's silent losses into named errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HTML dialect degraded four classes of input without a word, and `rustmotion validate` answered "Valid scenario" for all of them. An author who writes a construct the transpiler cannot honour has to be told. `"#); + assert!( + matches!(e, crate::HtmlError::StyleElementUnsupported), + "expected StyleElementUnsupported, got: {e:?}" + ); + } + + #[test] + fn script_element_is_skipped_not_painted() { + let v = map_first(r#"

real

"#); + let children = v["children"].as_array().expect("children array"); + assert_eq!( + children.len(), + 1, + "script content must not become a component: {v}" + ); + assert_eq!(children[0]["content"], json!("real")); + } + + #[test] + fn title_and_noscript_and_template_elements_are_skipped_not_painted() { + let v = map_first( + r#"
tt

real

"#, + ); + let children = v["children"].as_array().expect("children array"); + assert_eq!( + children.len(), + 1, + "title/noscript/template content must not become a component: {v}" + ); + assert_eq!(children[0]["content"], json!("real")); + } + + #[test] + fn tag_kind_head_is_ignored() { + // content never survives as a distinct DOM node when authored + // inline (html5ever drops the wrapper per HTML5 "in body" parsing + // rules and lets its text bleed into the parent), so this can only be + // exercised at the `tag_kind` unit level, not through the full + // element_to_value/html_to_scenario_value pipeline. + assert!(matches!(tag_kind("head"), TagKind::Ignored)); + } + + // --- unsupported native elements (constat 3) --- + + #[test] + fn img_element_is_refused_with_rm_image_suggestion() { + let e = map_first_err(r#""#); + match e { + crate::HtmlError::UnsupportedNativeElement { tag, suggestion } => { + assert_eq!(tag, "img"); + assert_eq!(suggestion, "rm-image"); + } + other => panic!("expected UnsupportedNativeElement, got: {other:?}"), + } + } + + #[test] + fn video_element_is_refused_with_rm_video_suggestion() { + let e = map_first_err(r#""#); + match e { + crate::HtmlError::UnsupportedNativeElement { tag, suggestion } => { + assert_eq!(tag, "video"); + assert_eq!(suggestion, "rm-video"); + } + other => panic!("expected UnsupportedNativeElement, got: {other:?}"), + } + } + + #[test] + fn svg_element_is_refused_with_rm_svg_suggestion() { + let e = map_first_err(r#""#); + match e { + crate::HtmlError::UnsupportedNativeElement { tag, suggestion } => { + assert_eq!(tag, "svg"); + assert_eq!(suggestion, "rm-svg"); + } + other => panic!("expected UnsupportedNativeElement, got: {other:?}"), + } + } + + // --- boolean attributes on custom elements (constat 4) --- + + #[test] + fn custom_element_bool_attribute_true_and_false() { + let v = map_first(r#""#); + assert_eq!(v["auto_scroll"], json!(false)); + assert_eq!(v["diff"], json!(true)); + } + + #[test] + fn custom_element_bare_attribute_becomes_true() { + let v = map_first(r#""#); + assert_eq!( + v["diff"], + json!(true), + "bare boolean attribute must become true, not be dropped: {v}" + ); + } } diff --git a/crates/rustmotion-html/src/lib.rs b/crates/rustmotion-html/src/lib.rs index dcfd7eb..092bd1d 100644 --- a/crates/rustmotion-html/src/lib.rs +++ b/crates/rustmotion-html/src/lib.rs @@ -69,6 +69,34 @@ pub enum HtmlError { /// Emitted when `` sets both `path`/`src` and `source` — they are mutually exclusive. #[error(": 'path'/'src' and 'source' are mutually exclusive")] FontPathAndSourceConflict { family: String }, + /// Emitted for `

Styled by class

"##; + let err = html_to_scenario_value(html).expect_err("

Hi

"##; + let err = + html_to_scenario_value(html).expect_err("root-level