diff --git a/crates/grida-canvas/src/html/mod.rs b/crates/grida-canvas/src/html/mod.rs index 6f2ac5557..cb851c964 100644 --- a/crates/grida-canvas/src/html/mod.rs +++ b/crates/grida-canvas/src/html/mod.rs @@ -1392,23 +1392,6 @@ mod tests { use crate::layout::ComputedLayout; use crate::node::schema::Scene; use std::collections::HashMap; - use std::sync::Mutex; - - /// Global mutex to serialize HTML tests. - /// - /// The CSS cascade adapter uses a process-global `DEMO_DOM` static, so - /// concurrent `from_html_str` calls race on that shared slot and cause - /// Stylo `debug_assert` panics ("Why are we here?"). A mutex ensures - /// only one test touches the global DOM at a time. - /// - /// We use `lock().unwrap_or_else(|e| e.into_inner())` to recover from - /// poison so that a single test failure doesn't cascade to all others. - static HTML_TEST_LOCK: Mutex<()> = Mutex::new(()); - - /// Lock the HTML test mutex, clearing poison if a prior test panicked. - fn lock_html() -> std::sync::MutexGuard<'static, ()> { - HTML_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()) - } /// Parse HTML and run the layout engine, returning the scene and a /// map of every node's computed layout. @@ -1455,7 +1438,7 @@ mod tests { #[test] fn smoke_test_basic_html() { - let _guard = lock_html(); + let _guard = crate::stylo_test::lock(); let html = r#"
@@ -1483,7 +1466,7 @@ mod tests { #[test] fn test_inline_style_attribute() { - let _guard = lock_html(); + let _guard = crate::stylo_test::lock(); let html = r#" @@ -1496,7 +1479,7 @@ mod tests { #[test] fn test_borders_and_shadows() { - let _guard = lock_html(); + let _guard = crate::stylo_test::lock(); let html = r#" @@ -1514,7 +1497,7 @@ mod tests { #[test] fn test_flex_alignment() { - let _guard = lock_html(); + let _guard = crate::stylo_test::lock(); let html = r#" @@ -1535,7 +1518,7 @@ mod tests { #[test] fn test_gradient_backgrounds() { - let _guard = lock_html(); + let _guard = crate::stylo_test::lock(); let html = r#" @@ -1560,7 +1543,7 @@ mod tests { /// 3 fixed-size divs in a flex row with gap. #[test] fn test_flex_row_positions() { - let _guard = lock_html(); + let _guard = crate::stylo_test::lock(); let html = r#") should merge into a single AttributedText.
#[test]
fn test_inline_elements_merge_to_attributed_text() {
- let _guard = lock_html();
+ let _guard = crate::stylo_test::lock();
let html = r#"
Hello world!
@@ -2470,7 +2453,7 @@ mod tests {
/// Whitespace between inline elements must be preserved.
#[test]
fn test_inline_whitespace_preserved() {
- let _guard = lock_html();
+ let _guard = crate::stylo_test::lock();
let html = r#"
Default red and green text.
diff --git a/crates/grida-canvas/src/htmlcss/mod.rs b/crates/grida-canvas/src/htmlcss/mod.rs
index 08cf029f6..250b8d493 100644
--- a/crates/grida-canvas/src/htmlcss/mod.rs
+++ b/crates/grida-canvas/src/htmlcss/mod.rs
@@ -82,18 +82,13 @@ mod tests {
use crate::resources::ByteStore;
use std::sync::{Arc, Mutex};
- /// Stylo uses a process-global DOM slot that is not thread-safe.
- /// All htmlcss tests must be serialized to avoid concurrent access.
- /// We also share this with the `html` module's tests via crate-level visibility.
- static TEST_LOCK: Mutex<()> = Mutex::new(());
-
fn test_fonts() -> FontRepository {
FontRepository::new(Arc::new(Mutex::new(ByteStore::new())))
}
#[test]
fn test_render_empty() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render("", 400.0, 300.0, &fonts);
assert!(pic.is_ok());
@@ -101,7 +96,7 @@ mod tests {
#[test]
fn test_render_heading() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render("Hello
", 400.0, 300.0, &fonts).unwrap();
assert!(pic.cull_rect().width() > 0.0);
@@ -109,7 +104,7 @@ mod tests {
#[test]
fn test_render_with_style_block() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
"Blue
",
@@ -122,7 +117,7 @@ mod tests {
#[test]
fn test_render_table() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
"A B
",
@@ -135,7 +130,7 @@ mod tests {
#[test]
fn test_render_flex() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"AB"#,
@@ -149,7 +144,7 @@ mod tests {
/// Verify grid properties are collected and layout produces columns.
#[test]
fn test_grid_layout_columns() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let html = r#"
@@ -222,7 +217,7 @@ mod tests {
#[test]
fn test_render_grid_basic() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"
@@ -237,7 +232,7 @@ mod tests {
#[test]
fn test_render_grid_fr() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"
@@ -252,7 +247,7 @@ mod tests {
#[test]
fn test_render_grid_repeat() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"
@@ -267,7 +262,7 @@ mod tests {
#[test]
fn test_render_grid_span() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"
@@ -284,7 +279,7 @@ mod tests {
#[test]
fn test_render_grid_auto_flow_dense() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"
@@ -302,7 +297,7 @@ mod tests {
#[test]
fn test_render_box_shadow_outer() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"shadow"#,
@@ -315,7 +310,7 @@ mod tests {
#[test]
fn test_render_box_shadow_inset() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"inset"#,
@@ -328,7 +323,7 @@ mod tests {
#[test]
fn test_render_box_shadow_combined() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"both"#,
@@ -342,7 +337,7 @@ mod tests {
/// Verify box-shadow properties are collected from Stylo.
#[test]
fn test_box_shadow_collection() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let html = r#"shadow"#;
let root = collect::collect_styled_tree(html).unwrap().unwrap();
@@ -372,7 +367,7 @@ mod tests {
#[test]
fn test_render_opacity() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"Semi-transparent
"#,
@@ -442,7 +437,7 @@ mod tests {
#[test]
fn test_measure_height() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let h = measure_content_height("Hello
", 400.0, &fonts).unwrap();
assert!(h > 0.0, "Content height should be positive, got {h}");
@@ -450,7 +445,7 @@ mod tests {
#[test]
fn test_head_hidden() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let pic = render(
r#"V
"#,
@@ -467,7 +462,7 @@ mod tests {
#[test]
fn test_markdown_heading() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let html = markdown_to_styled_html("# Hello World");
let pic = render(&html, 400.0, 300.0, &fonts);
@@ -477,7 +472,7 @@ mod tests {
#[test]
fn test_markdown_mixed_content() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let md = r#"# Title
@@ -509,7 +504,7 @@ code block
#[test]
fn test_markdown_table() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let md = r#"
| Name | Age | City |
@@ -524,7 +519,7 @@ code block
#[test]
fn test_markdown_empty() {
- let _guard = TEST_LOCK.lock().unwrap();
+ let _guard = crate::stylo_test::lock();
let fonts = test_fonts();
let html = markdown_to_styled_html("");
let pic = render(&html, 400.0, 300.0, &fonts);
diff --git a/crates/grida-canvas/src/lib.rs b/crates/grida-canvas/src/lib.rs
index bf483db08..ee5e18497 100644
--- a/crates/grida-canvas/src/lib.rs
+++ b/crates/grida-canvas/src/lib.rs
@@ -27,3 +27,20 @@ pub mod text_edit;
pub mod text_edit_session;
pub mod vectornetwork;
pub mod window;
+
+/// Shared test lock for Stylo's process-global DOM slot.
+///
+/// Both `html` and `htmlcss` modules use Stylo which is **not** thread-safe.
+/// All tests that call into Stylo must hold this lock. We use
+/// `unwrap_or_else(|e| e.into_inner())` to recover from poison so a single
+/// test panic does not cascade to every other Stylo test.
+#[cfg(test)]
+pub(crate) mod stylo_test {
+ use std::sync::{Mutex, MutexGuard};
+
+ static STYLO_TEST_LOCK: Mutex<()> = Mutex::new(());
+
+ pub fn lock() -> MutexGuard<'static, ()> {
+ STYLO_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner())
+ }
+}