test(build): close target and identifier mutants
Prove first-seen cross-kind generated-target ordering and deduplication, exact identifier/literal/canonical-symbol boundaries, invalid UTF-8 and component names, and resource insertion reuse/collisions; simplify CSS class normalization through the shared Rust identifier validator. req: codegen/005 req: diagnostics/004 req: diagnostics/005 req: surface/008 req: test/021
This commit is contained in:
+149
-21
@@ -1492,16 +1492,7 @@ fn is_class_continue(byte: u8) -> bool {
|
||||
}
|
||||
|
||||
fn class_ident(token: &str) -> Option<String> {
|
||||
let mut ident = String::with_capacity(token.len());
|
||||
for ch in token.chars() {
|
||||
match ch {
|
||||
'-' => ident.push('_'),
|
||||
'_' => ident.push('_'),
|
||||
ch if ch.is_ascii_alphanumeric() => ident.push(ch),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
rust_ident(&ident)
|
||||
rust_ident(&token.replace('-', "_"))
|
||||
}
|
||||
|
||||
fn is_handle_param_attr(attr: &SurfaceAttribute) -> bool {
|
||||
@@ -2373,6 +2364,114 @@ mod tests {
|
||||
// test req: diagnostics/004 req: diagnostics/006 req: surface/008
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identifier_and_literal_helpers_fail_closed_at_boundaries() {
|
||||
assert_eq!(rust_ident("alpha_9"), Some("alpha_9".into()));
|
||||
for invalid in ["", "9alpha", "alpha-beta", "alpha beta", "alphaé"] {
|
||||
assert_eq!(
|
||||
rust_ident(invalid),
|
||||
None,
|
||||
"accepted Rust identifier {invalid:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!(class_ident("alpha-beta_9"), Some("alpha_beta_9".into()));
|
||||
for invalid in ["", "9alpha", "alphaé", "alpha.beta", "alpha beta"] {
|
||||
assert_eq!(class_ident(invalid), None, "accepted CSS class {invalid:?}");
|
||||
}
|
||||
|
||||
assert_eq!(rust_str("a\n\"b\\c"), "\"a\\n\\\"b\\\\c\"");
|
||||
assert_eq!(rust_str_opt(None), "None");
|
||||
assert_eq!(rust_str_opt(Some("a\n\"b\\c")), "Some(\"a\\n\\\"b\\\\c\")");
|
||||
assert_eq!(
|
||||
canonical_symbol(
|
||||
Path::new("templates"),
|
||||
Path::new("templates/nested/panel.heml"),
|
||||
"save",
|
||||
),
|
||||
"nested/panel.heml::save"
|
||||
);
|
||||
|
||||
let root = Path::new("templates");
|
||||
assert_eq!(
|
||||
component_ident(root, Path::new("templates/nested/profile_card.heml")).unwrap(),
|
||||
"profile_card"
|
||||
);
|
||||
assert_eq!(
|
||||
component_ident(root, Path::new("templates/9bad.heml"))
|
||||
.unwrap_err()
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidData
|
||||
);
|
||||
assert_eq!(
|
||||
component_ident(root, Path::new("outside.heml")).unwrap(),
|
||||
"outside"
|
||||
);
|
||||
|
||||
let mut resources = BTreeMap::new();
|
||||
let first = insert_resource(
|
||||
&mut resources,
|
||||
"slot",
|
||||
"panel.heml::summary".into(),
|
||||
"summary".into(),
|
||||
"panel".into(),
|
||||
)
|
||||
.unwrap();
|
||||
first.keyed = true;
|
||||
assert!(
|
||||
insert_resource(
|
||||
&mut resources,
|
||||
"slot",
|
||||
"panel.heml::summary".into(),
|
||||
"summary".into(),
|
||||
"panel".into(),
|
||||
)
|
||||
.unwrap()
|
||||
.keyed
|
||||
);
|
||||
assert_eq!(
|
||||
insert_resource(
|
||||
&mut resources,
|
||||
"slot",
|
||||
"other.heml::summary".into(),
|
||||
"summary".into(),
|
||||
"other".into(),
|
||||
)
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"duplicate generated identifier `summary` for `panel.heml::summary` and `other.heml::summary`"
|
||||
);
|
||||
|
||||
let invalid_surface = surface_for_heml_source(
|
||||
Path::new("123.heml"),
|
||||
r#"<section data-hemx-slot="summary"></section>"#.to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut extracted = Resources::default();
|
||||
assert_eq!(
|
||||
extracted
|
||||
.add_surface(
|
||||
Path::new("templates"),
|
||||
Path::new("templates/123.heml"),
|
||||
&invalid_surface,
|
||||
)
|
||||
.unwrap_err()
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidData
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let invalid =
|
||||
PathBuf::from("templates").join(std::ffi::OsStr::from_bytes(b"\xff.heml"));
|
||||
assert_eq!(
|
||||
component_ident(root, &invalid).unwrap_err().kind(),
|
||||
io::ErrorKind::InvalidData
|
||||
);
|
||||
}
|
||||
// test req: codegen/005 req: diagnostics/004
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_contract_fingerprint_and_client_bootstrap_are_deterministic() {
|
||||
let resource = |symbol: &str, component: &str, id| Resource {
|
||||
@@ -3463,20 +3562,49 @@ fn main() {{
|
||||
// req: diagnostics/004 req: diagnostics/005
|
||||
let targets = generated_targets_for_heml_source(
|
||||
"inline.heml",
|
||||
r#"<main data-hemx-root="todos"><section data-hemx-slot="summary"></section><form data-hemx-form="save"><button data-hemx-handle="submit">Save</button></form></main>"#,
|
||||
r#"
|
||||
<main data-hemx-root="todos">
|
||||
<section class="card" data-hemx-slot="summary"></section>
|
||||
<section data-hemx-root="nested" data-hemx-slot="after-root"></section>
|
||||
<section data-hemx-slot="summary"></section>
|
||||
<form data-hemx-form="summary"></form>
|
||||
<span data-hemx-handle data-hemx-slot="after-missing"></span>
|
||||
<button data-hemx-handle="submit">Save</button>
|
||||
<aside data-hemx-slot="later"></aside>
|
||||
</main>
|
||||
"#,
|
||||
)
|
||||
.expect("generated targets");
|
||||
|
||||
assert_eq!(targets.len(), 3);
|
||||
assert!(targets
|
||||
.iter()
|
||||
.any(|target| target.kind == "slot" && target.name == "summary"));
|
||||
assert!(targets
|
||||
.iter()
|
||||
.any(|target| target.kind == "form" && target.name == "save"));
|
||||
assert!(targets
|
||||
.iter()
|
||||
.any(|target| target.kind == "handle" && target.name == "submit"));
|
||||
assert_eq!(
|
||||
targets,
|
||||
vec![
|
||||
GeneratedTarget {
|
||||
kind: "slot".into(),
|
||||
name: "summary".into(),
|
||||
},
|
||||
GeneratedTarget {
|
||||
kind: "slot".into(),
|
||||
name: "after-root".into(),
|
||||
},
|
||||
GeneratedTarget {
|
||||
kind: "form".into(),
|
||||
name: "summary".into(),
|
||||
},
|
||||
GeneratedTarget {
|
||||
kind: "slot".into(),
|
||||
name: "after-missing".into(),
|
||||
},
|
||||
GeneratedTarget {
|
||||
kind: "handle".into(),
|
||||
name: "submit".into(),
|
||||
},
|
||||
GeneratedTarget {
|
||||
kind: "slot".into(),
|
||||
name: "later".into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user