|
| 1 | +#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] |
| 2 | + |
| 3 | +use wasm_bindgen::prelude::*; |
| 4 | +use wasm_bindgen_test::*; |
| 5 | +use web_sys::wasm_bindgen::JsCast; |
| 6 | +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlVideoElement}; |
| 7 | +use yew::prelude::*; |
| 8 | + |
| 9 | +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); |
| 10 | + |
| 11 | +#[function_component] |
| 12 | +fn SvgWithDropShadow() -> Html { |
| 13 | + html! { |
| 14 | + <svg id="test-svg"> |
| 15 | + <defs> |
| 16 | + <filter id="glow"> |
| 17 | + <feDropShadow dx="0" dy="0" stdDeviation="10" flood-color="red"/> |
| 18 | + </filter> |
| 19 | + </defs> |
| 20 | + <rect width="100" height="100" filter="url(#glow)" /> |
| 21 | + </svg> |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +#[wasm_bindgen_test] |
| 26 | +async fn svg_camel_case_elements_render() { |
| 27 | + let window = web_sys::window().unwrap(); |
| 28 | + let document = window.document().unwrap(); |
| 29 | + let body = document.body().unwrap(); |
| 30 | + |
| 31 | + // Create container |
| 32 | + let container = document.create_element("div").unwrap(); |
| 33 | + container.set_id("test-container"); |
| 34 | + body.append_child(&container).unwrap(); |
| 35 | + |
| 36 | + // Mount component |
| 37 | + yew::Renderer::<SvgWithDropShadow>::with_root(container.clone().unchecked_into()).render(); |
| 38 | + |
| 39 | + // Wait for render to complete |
| 40 | + yew::platform::time::sleep(std::time::Duration::from_millis(1000)).await; |
| 41 | + |
| 42 | + // Get the SVG element to verify it exists |
| 43 | + let svg_element = document |
| 44 | + .get_element_by_id("test-svg") |
| 45 | + .expect("SVG element should exist"); |
| 46 | + |
| 47 | + // Get SVG bounds for pixel analysis |
| 48 | + let rect = svg_element.get_bounding_client_rect(); |
| 49 | + |
| 50 | + // Use getDisplayMedia to capture the screen |
| 51 | + let navigator = window.navigator(); |
| 52 | + let media_devices = navigator.media_devices().unwrap(); |
| 53 | + |
| 54 | + // Create options for getDisplayMedia |
| 55 | + let constraints = web_sys::DisplayMediaStreamConstraints::new(); |
| 56 | + // Set preferCurrentTab via reflection (Chrome-only feature) |
| 57 | + let _ = js_sys::Reflect::set( |
| 58 | + &constraints, |
| 59 | + &JsValue::from("preferCurrentTab"), |
| 60 | + &JsValue::from(true), |
| 61 | + ); |
| 62 | + // Set video with frameRate |
| 63 | + let video_constraints = js_sys::Object::new(); |
| 64 | + let _ = js_sys::Reflect::set( |
| 65 | + &video_constraints, |
| 66 | + &JsValue::from("frameRate"), |
| 67 | + &JsValue::from(30), |
| 68 | + ); |
| 69 | + constraints.set_video(&video_constraints); |
| 70 | + |
| 71 | + // Try to get the display media stream |
| 72 | + let stream_promise = media_devices |
| 73 | + .get_display_media_with_constraints(&constraints) |
| 74 | + .unwrap(); |
| 75 | + let stream_result = wasm_bindgen_futures::JsFuture::from(stream_promise).await; |
| 76 | + |
| 77 | + // Check if getDisplayMedia failed (likely Firefox without user interaction) |
| 78 | + let stream = match stream_result { |
| 79 | + Ok(stream) => stream.dyn_into::<web_sys::MediaStream>().unwrap(), |
| 80 | + Err(_) => { |
| 81 | + // We are likely in Firefox, there is no way of granting permission |
| 82 | + // for screen capture without user interaction in automated tests |
| 83 | + web_sys::console::log_1( |
| 84 | + &"getDisplayMedia failed - likely Firefox, skipping pixel test".into(), |
| 85 | + ); |
| 86 | + return; |
| 87 | + } |
| 88 | + }; |
| 89 | + |
| 90 | + // Create a video element to capture frames |
| 91 | + let video = document |
| 92 | + .create_element("video") |
| 93 | + .unwrap() |
| 94 | + .dyn_into::<HtmlVideoElement>() |
| 95 | + .unwrap(); |
| 96 | + video.set_autoplay(true); |
| 97 | + video.set_muted(true); |
| 98 | + js_sys::Reflect::set( |
| 99 | + &video, |
| 100 | + &JsValue::from("playsInline"), |
| 101 | + &JsValue::from(true), |
| 102 | + ); |
| 103 | + video.set_src_object(Some(&stream)); |
| 104 | + |
| 105 | + // Add video to DOM (invisible but positioned at 0,0) |
| 106 | + let style = video.dyn_ref::<web_sys::HtmlElement>().unwrap().style(); |
| 107 | + style.set_property("position", "fixed").unwrap(); |
| 108 | + style.set_property("top", "0").unwrap(); |
| 109 | + style.set_property("left", "0").unwrap(); |
| 110 | + style.set_property("pointer-events", "none").unwrap(); |
| 111 | + style.set_property("visibility", "hidden").unwrap(); |
| 112 | + body.append_child(&video).unwrap(); |
| 113 | + |
| 114 | + // Wait for video to start playing |
| 115 | + let play_promise = video.play().unwrap(); |
| 116 | + wasm_bindgen_futures::JsFuture::from(play_promise) |
| 117 | + .await |
| 118 | + .ok(); |
| 119 | + |
| 120 | + // Wait a bit for the video feed to stabilize |
| 121 | + yew::platform::time::sleep(std::time::Duration::from_millis(500)).await; |
| 122 | + |
| 123 | + // Get video track settings to know dimensions |
| 124 | + let tracks = stream.get_video_tracks(); |
| 125 | + let track = tracks |
| 126 | + .get(0) |
| 127 | + .dyn_into::<web_sys::MediaStreamTrack>() |
| 128 | + .unwrap(); |
| 129 | + let settings = track.get_settings(); |
| 130 | + |
| 131 | + let width = settings.get_width().unwrap_or(1920) as u32; |
| 132 | + let height = settings.get_height().unwrap_or(1080) as u32; |
| 133 | + |
| 134 | + // Create canvas and draw the video frame |
| 135 | + let canvas = document |
| 136 | + .create_element("canvas") |
| 137 | + .unwrap() |
| 138 | + .dyn_into::<HtmlCanvasElement>() |
| 139 | + .unwrap(); |
| 140 | + canvas.set_width(width); |
| 141 | + canvas.set_height(height); |
| 142 | + |
| 143 | + let ctx = canvas |
| 144 | + .get_context("2d") |
| 145 | + .unwrap() |
| 146 | + .unwrap() |
| 147 | + .dyn_into::<CanvasRenderingContext2d>() |
| 148 | + .unwrap(); |
| 149 | + |
| 150 | + // Draw the video frame to canvas |
| 151 | + ctx.draw_image_with_html_video_element(&video, 0.0, 0.0) |
| 152 | + .unwrap(); |
| 153 | + |
| 154 | + // Stop the capture |
| 155 | + let tracks = stream.get_tracks(); |
| 156 | + |
| 157 | + // Get image data for pixel analysis |
| 158 | + let image_data = ctx |
| 159 | + .get_image_data(0.0, 0.0, width as f64, height as f64) |
| 160 | + .unwrap(); |
| 161 | + let data = image_data.data(); |
| 162 | + |
| 163 | + // Analyze pixels around the center where the rect should be |
| 164 | + let center_x = (rect.left() + rect.width() / 2.0) as i32; |
| 165 | + let center_y = (rect.top() + rect.height() / 2.0) as i32; |
| 166 | + |
| 167 | + let mut has_non_white_pixels = false; |
| 168 | + let mut sample_pixels = Vec::new(); |
| 169 | + |
| 170 | + // Check a grid of pixels around the center |
| 171 | + for dy in (-60..=60).step_by(10) { |
| 172 | + for dx in (-60..=60).step_by(10) { |
| 173 | + let x = center_x + dx; |
| 174 | + let y = center_y + dy; |
| 175 | + |
| 176 | + if x >= 0 && x < width as i32 && y >= 0 && y < height as i32 { |
| 177 | + let idx = ((y * width as i32 + x) * 4) as usize; |
| 178 | + let r = data[idx]; |
| 179 | + let g = data[idx + 1]; |
| 180 | + let b = data[idx + 2]; |
| 181 | + |
| 182 | + // Log some sample pixels for debugging |
| 183 | + if sample_pixels.len() < 10 { |
| 184 | + sample_pixels.push(format!("({},{}): rgb({},{},{})", x, y, r, g, b)); |
| 185 | + } |
| 186 | + |
| 187 | + // Check if pixel is not white (with tolerance) |
| 188 | + if r < 250 || g < 250 || b < 250 { |
| 189 | + has_non_white_pixels = true; |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + // Convert canvas to base64 for inspection |
| 196 | + let data_url = canvas.to_data_url().unwrap(); |
| 197 | + |
| 198 | + // Log pixel analysis |
| 199 | + web_sys::console::log_1(&format!("Has non-white pixels: {}", has_non_white_pixels).into()); |
| 200 | + web_sys::console::log_1(&format!("Sample pixels: {:?}", sample_pixels).into()); |
| 201 | + web_sys::console::log_1(&format!("Screenshot data URL (copy and paste in browser): {}", data_url).into()); |
| 202 | + |
| 203 | + // Also log the SVG bounds and center position |
| 204 | + web_sys::console::log_1(&format!("SVG bounds: left={}, top={}, width={}, height={}", |
| 205 | + rect.left(), rect.top(), rect.width(), rect.height()).into()); |
| 206 | + web_sys::console::log_1(&format!("Center position: ({}, {})", center_x, center_y).into()); |
| 207 | + web_sys::console::log_1(&format!("Canvas size: {}x{}", width, height).into()); |
| 208 | + |
| 209 | + assert!(has_non_white_pixels, "Expected the rect to render"); |
| 210 | + |
| 211 | + for i in 0..tracks.length() { |
| 212 | + let track = tracks.get(i); |
| 213 | + if !track.is_undefined() { |
| 214 | + track |
| 215 | + .dyn_into::<web_sys::MediaStreamTrack>() |
| 216 | + .unwrap() |
| 217 | + .stop(); |
| 218 | + } |
| 219 | + } |
| 220 | +} |
0 commit comments