mirror of
https://github.com/cloud-shuttle/leptos-shadcn-ui.git
synced 2026-05-15 02:50:45 +00:00
- All 9 components fully refactored with modular architecture - 45+ test modules created and organized - File size compliance achieved (99% reduction) - Enterprise-grade code quality implemented - All compilation issues resolved Ready for v0.9.0 release publication!
270 lines
9.2 KiB
Rust
270 lines
9.2 KiB
Rust
//! DOM testing utilities for Leptos ShadCN UI components
|
|
//!
|
|
//! This module provides basic DOM rendering test capabilities to complement
|
|
//! the unit tests. It uses wasm-bindgen-test for browser-based testing.
|
|
|
|
use leptos::prelude::*;
|
|
use leptos::mount::mount_to;
|
|
use wasm_bindgen_test::*;
|
|
use web_sys::wasm_bindgen::JsCast;
|
|
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
/// A test harness for rendering Leptos components in a test environment
|
|
pub struct ComponentTestHarness {
|
|
mount_point: String,
|
|
}
|
|
|
|
impl Default for ComponentTestHarness {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ComponentTestHarness {
|
|
/// Create a new test harness with a unique mount point
|
|
pub fn new() -> Self {
|
|
let mount_id = format!("test-mount-{}", uuid::Uuid::new_v4());
|
|
Self {
|
|
mount_point: mount_id,
|
|
}
|
|
}
|
|
|
|
/// Render a component and return the DOM element for testing
|
|
pub fn render<F>(&self, component: F) -> web_sys::HtmlElement
|
|
where
|
|
F: Fn() -> AnyView + 'static,
|
|
{
|
|
let document = web_sys::window().unwrap().document().unwrap();
|
|
|
|
// Create mount point
|
|
let mount_element = document.create_element("div").unwrap();
|
|
let mount_element = mount_element.dyn_into::<web_sys::HtmlElement>().unwrap();
|
|
mount_element.set_id(&self.mount_point);
|
|
document.body().unwrap().append_child(&mount_element).unwrap();
|
|
|
|
// Mount the component
|
|
let _dispose = mount_to(
|
|
mount_element.clone(),
|
|
component
|
|
);
|
|
|
|
// Store dispose function for cleanup (in real implementation)
|
|
// For now, return the mount element
|
|
mount_element
|
|
}
|
|
|
|
/// Helper to query for elements by CSS selector
|
|
pub fn query_selector(&self, selector: &str) -> Option<web_sys::Element> {
|
|
let document = web_sys::window().unwrap().document().unwrap();
|
|
let mount_element = document.get_element_by_id(&self.mount_point)?;
|
|
mount_element.query_selector(selector).unwrap_or(None)
|
|
}
|
|
|
|
/// Helper to get element text content
|
|
pub fn get_text_content(&self, selector: &str) -> Option<String> {
|
|
self.query_selector(selector)?.text_content()
|
|
}
|
|
|
|
/// Helper to check if element has specific class
|
|
pub fn has_class(&self, selector: &str, class_name: &str) -> bool {
|
|
if let Some(element) = self.query_selector(selector) {
|
|
element.class_list().contains(class_name)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Helper to get computed style
|
|
pub fn get_computed_style(&self, selector: &str, property: &str) -> Option<String> {
|
|
let element = self.query_selector(selector)?;
|
|
let window = web_sys::window().unwrap();
|
|
let computed_style = window.get_computed_style(&element).unwrap()?;
|
|
computed_style.get_property_value(property).unwrap_or_default().into()
|
|
}
|
|
|
|
/// Cleanup the test harness
|
|
pub fn cleanup(&self) {
|
|
if let Some(document) = web_sys::window().and_then(|w| w.document())
|
|
&& let Some(element) = document.get_element_by_id(&self.mount_point) {
|
|
element.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for ComponentTestHarness {
|
|
fn drop(&mut self) {
|
|
self.cleanup();
|
|
}
|
|
}
|
|
|
|
/// Test utilities for component accessibility
|
|
pub struct AccessibilityTester;
|
|
|
|
impl AccessibilityTester {
|
|
/// Check if element has proper ARIA attributes
|
|
pub fn check_aria_attributes(element: &web_sys::Element) -> Vec<String> {
|
|
let mut missing_attributes = Vec::new();
|
|
|
|
// Check for common ARIA attributes based on element type
|
|
let tag_name = element.tag_name().to_lowercase();
|
|
|
|
match tag_name.as_str() {
|
|
"button" => {
|
|
if !element.has_attribute("aria-label") && element.text_content().unwrap_or_default().is_empty() {
|
|
missing_attributes.push("aria-label or text content".to_string());
|
|
}
|
|
},
|
|
"input" => {
|
|
if !element.has_attribute("aria-label") && !element.has_attribute("aria-labelledby") {
|
|
missing_attributes.push("aria-label or aria-labelledby".to_string());
|
|
}
|
|
},
|
|
_ => {}
|
|
}
|
|
|
|
missing_attributes
|
|
}
|
|
|
|
/// Check color contrast (simplified)
|
|
pub fn check_color_contrast(element: &web_sys::Element) -> bool {
|
|
// Simplified contrast check - in real implementation would use proper algorithms
|
|
let window = web_sys::window().unwrap();
|
|
if let Ok(Some(computed_style)) = window.get_computed_style(element) {
|
|
let color = computed_style.get_property_value("color").unwrap_or_default();
|
|
let background = computed_style.get_property_value("background-color").unwrap_or_default();
|
|
|
|
// Basic check - ensure we have both color and background
|
|
!color.is_empty() && !background.is_empty()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Check keyboard navigation
|
|
pub fn is_keyboard_accessible(element: &web_sys::Element) -> bool {
|
|
// Check if element is focusable
|
|
element.has_attribute("tabindex") ||
|
|
matches!(element.tag_name().to_lowercase().as_str(), "button" | "input" | "select" | "textarea" | "a")
|
|
}
|
|
}
|
|
|
|
/// Performance testing utilities
|
|
pub struct PerformanceTester;
|
|
|
|
impl PerformanceTester {
|
|
/// Measure component render time
|
|
pub fn measure_render_time<F>(render_fn: F) -> f64
|
|
where
|
|
F: FnOnce(),
|
|
{
|
|
let performance = web_sys::window().unwrap().performance().unwrap();
|
|
let start = performance.now();
|
|
render_fn();
|
|
let end = performance.now();
|
|
end - start
|
|
}
|
|
|
|
/// Check bundle size impact (simplified)
|
|
pub fn estimate_bundle_impact(component_name: &str) -> usize {
|
|
// Simplified estimation - in real implementation would measure actual bundle sizes
|
|
match component_name {
|
|
"button" | "input" | "label" => 1024, // ~1KB
|
|
"card" | "dialog" => 2048, // ~2KB
|
|
"table" | "calendar" => 4096, // ~4KB
|
|
_ => 1500, // Default estimation
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Macro to create DOM tests more easily
|
|
#[macro_export]
|
|
macro_rules! dom_test {
|
|
($test_name:ident, $component:expr, $test_body:block) => {
|
|
#[wasm_bindgen_test]
|
|
fn $test_name() {
|
|
let harness = ComponentTestHarness::new();
|
|
let _element = harness.render(|| $component);
|
|
|
|
$test_body
|
|
|
|
// Cleanup is handled by Drop trait
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Example usage and integration tests
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use leptos::prelude::*;
|
|
|
|
// Example DOM test for Button component
|
|
// Note: This would require the actual Button component to be imported
|
|
#[wasm_bindgen_test]
|
|
fn test_button_dom_rendering() {
|
|
let harness = ComponentTestHarness::new();
|
|
|
|
// This is a conceptual test - would need actual Button component
|
|
let _element = harness.render(|| {
|
|
view! {
|
|
<button class="btn-primary">{"Test Button"}</button>
|
|
}.into_any()
|
|
});
|
|
|
|
// Test that button rendered correctly
|
|
assert!(harness.query_selector("button").is_some());
|
|
assert_eq!(harness.get_text_content("button"), Some("Test Button".to_string()));
|
|
assert!(harness.has_class("button", "btn-primary"));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_accessibility_checking() {
|
|
let document = web_sys::window().unwrap().document().unwrap();
|
|
let button = document.create_element("button").unwrap();
|
|
button.set_text_content(Some("Accessible Button"));
|
|
|
|
let missing_attrs = AccessibilityTester::check_aria_attributes(&button);
|
|
assert!(missing_attrs.is_empty(), "Button with text content should not need additional ARIA labels");
|
|
|
|
assert!(AccessibilityTester::is_keyboard_accessible(&button));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_performance_measurement() {
|
|
let render_time = PerformanceTester::measure_render_time(|| {
|
|
// Simulate component rendering work
|
|
for _ in 0..1000 {
|
|
let _ = web_sys::window().unwrap().document().unwrap().create_element("div");
|
|
}
|
|
});
|
|
|
|
assert!(render_time > 0.0, "Should measure some render time");
|
|
assert!(render_time < 1000.0, "Render time should be reasonable (< 1 second)");
|
|
}
|
|
}
|
|
|
|
/// Integration with existing TDD framework
|
|
impl crate::TestResult {
|
|
/// Create a DOM test result
|
|
pub fn dom_test(passed: bool, component: &str, test_type: &str, details: Option<String>) -> Self {
|
|
let message = if passed {
|
|
format!("✅ DOM test passed: {} {}", component, test_type)
|
|
} else {
|
|
format!("❌ DOM test failed: {} {}", component, test_type)
|
|
};
|
|
|
|
let mut result = if passed {
|
|
Self::success(message)
|
|
} else {
|
|
Self::failure(message)
|
|
};
|
|
|
|
if let Some(details) = details {
|
|
result = result.with_detail("details", details);
|
|
}
|
|
|
|
result.with_detail("test_type", "dom")
|
|
.with_detail("component", component)
|
|
}
|
|
} |