From edee09596c2ce1624eb0f3a4298562d43eb9acf5 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 2 Jul 2026 17:39:15 +0800 Subject: [PATCH] =?UTF-8?q?test(api/markdown):=20=E8=A7=A3=E8=80=A6=20wrap?= =?UTF-8?q?=5Fimages=5Fwith=5Fblur=20=E5=B9=B6=E8=84=B1=E7=A6=BB=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=B3=BB=E7=BB=9F=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 wrap_images_with_blur 拆成两部分: - wrap_images_with_blur_with(html, dims_fn):纯函数核心,接受 dimensions 查询闭包,不碰文件系统 - wrap_images_with_blur(html):薄包装,注入真实 get_image_dimensions 测试改用纯函数核心,注入确定性 dimensions(如 Some((800,600)) 或 None), 彻底脱离 uploads/ 真实文件依赖。此前的 aspect-ratio 测试硬编码了 /uploads/2026/06/18/...webp 真实路径——文件不存在则静默走缺省分支, 断言虽能过但实际验证内容被削弱。 新增覆盖(共 8 个 wrap 相关测试): - --ar 缺省/有值两种路径 - rel_path 去查询的验证(用 RefCell 捕获 Fn 闭包入参) - alt 保留(双层)/空 alt 不生成属性 - 斜杠分隔的 --ar 格式 + sanitizer 管线不破坏斜杠 --- src/api/markdown.rs | 89 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/src/api/markdown.rs b/src/api/markdown.rs index 2f6fce3..0a1f5cb 100644 --- a/src/api/markdown.rs +++ b/src/api/markdown.rs @@ -209,6 +209,18 @@ pub fn render_markdown_enhanced(md: &str) -> RenderedContent { /// 3. 生成 `` 包裹两层 img #[cfg(feature = "server")] fn wrap_images_with_blur(html: &str) -> String { + wrap_images_with_blur_with(html, crate::api::image::get_image_dimensions) +} + +/// `wrap_images_with_blur` 的纯函数核心,接受 dimensions 查询闭包以便单测。 +/// +/// `dims_fn(rel_path) -> Option<(u32, u32)>` 注入真实或测试用的 dimensions 来源, +/// 使本函数不依赖文件系统——测试可注入已知宽高,生产注入 get_image_dimensions。 +#[cfg(feature = "server")] +fn wrap_images_with_blur_with(html: &str, dims_fn: F) -> String +where + F: Fn(&str) -> Option<(u32, u32)>, +{ use regex::Regex; use std::sync::LazyLock; @@ -233,7 +245,7 @@ fn wrap_images_with_blur(html: &str) -> String { // 查 dimensions,算 aspect-ratio // 注意:CSS aspect-ratio 用斜杠分隔(width / height),不是冒号 - let ar_style = crate::api::image::get_image_dimensions(rel_path) + let ar_style = dims_fn(rel_path) .map(|(w, h)| format!(" style=\"--ar:{} / {};\"", w, h)) .unwrap_or_default(); @@ -345,10 +357,10 @@ mod tests { #[test] fn wrap_images_with_blur_wraps_uploads_image() { - // 注意:此测试依赖 uploads/ 目录下存在对应文件才能拿到 dimensions。 - // 用一个不含 dimensions 的路径验证 --ar 缺省时的结构正确性。 + // 注入返回 None 的 dims_fn,验证 --ar 缺省时的结构正确性。 + // 不依赖 uploads/ 文件系统。 let html = r#"

test

"#; - let result = wrap_images_with_blur(html); + let result = wrap_images_with_blur_with(html, |_| None); assert!( result.contains("blur-img-placeholder"), "should have placeholder" @@ -361,9 +373,9 @@ mod tests { #[test] fn wrap_images_with_blur_skips_external_image() { + // 外链图不进入 dims_fn,保持原样。 let html = r#"ext"#; - let result = wrap_images_with_blur(html); - // 外链图不处理,保持原样 + let result = wrap_images_with_blur_with(html, |_| None); assert!( !result.contains("blur-img"), "external image should not be wrapped" @@ -372,28 +384,71 @@ mod tests { #[test] fn wrap_images_with_blur_uses_slash_in_aspect_ratio() { - // 用真实存在的 uploads 文件,让 dimensions 成功返回,验证 --ar 格式 - let html = r#"t"#; - let result = wrap_images_with_blur(html); - println!("ACTUAL OUTPUT: {}", result); - // aspect-ratio 必须用斜杠分隔,如 "--ar:800 / 600;" + // 注入已知 dimensions,验证 --ar 用斜杠分隔(如 "--ar:800 / 600;")。 + // 此前依赖 uploads/ 真实文件,现已解耦。 + let html = r#"t"#; + let result = wrap_images_with_blur_with(html, |_| Some((800, 600))); assert!(result.contains("--ar:"), "should have --ar"); assert!( result.contains(" / "), "aspect-ratio must use slash separator, got: {}", result ); + assert!(result.contains("--ar:800 / 600;"), "应含精确宽高"); + } + + #[test] + fn wrap_images_with_blur_omits_ar_when_no_dimensions() { + // dims_fn 返回 None 时不输出 --ar,避免空 style 属性。 + let html = r#"t"#; + let result = wrap_images_with_blur_with(html, |_| None); + assert!(!result.contains("--ar"), "无 dimensions 不应有 --ar"); + // 但包裹结构仍应生成 + assert!(result.contains("blur-img")); + } + + #[test] + fn wrap_images_with_blur_strips_query_from_rel_path() { + // rel_path 提取应去 query 后缀,dims_fn 收到的是去 query 的路径。 + // dims_fn 是 Fn(replace_all 要求),用 RefCell 捕获传入值。 + use std::cell::RefCell; + let html = r#"t"#; + let received = RefCell::new(String::new()); + let _ = wrap_images_with_blur_with(html, |p| { + *received.borrow_mut() = p.to_string(); + Some((100, 100)) + }); + assert_eq!(received.borrow().as_str(), "2026/x.webp", "rel_path 应去 query"); + } + + #[test] + fn wrap_images_with_blur_preserves_alt() { + let html = r#"描述"#; + let result = wrap_images_with_blur_with(html, |_| Some((10, 10))); + assert!(result.contains(r#"alt="描述""#), "placeholder 应保留 alt"); + assert!( + result.matches(r#"alt="描述""#).count() == 2, + "两层 img 都应带 alt" + ); + } + + #[test] + fn wrap_images_with_blur_omits_alt_attr_when_empty() { + let html = r#""#; + let result = wrap_images_with_blur_with(html, |_| None); + assert!( + !result.contains("alt=\""), + "无 alt 时不应生成空 alt 属性" + ); } #[test] fn full_pipeline_wrap_then_clean_preserves_slash() { - // 模拟完整渲染管线:wrap_images_with_blur → clean_html - // 验证 sanitizer 不把斜杠转义或删掉 - let html = r#"t"#; - let wrapped = wrap_images_with_blur(html); + // 模拟完整渲染管线:wrap → clean_html,验证 sanitizer 不破坏斜杠。 + // 注入确定 dimensions,脱离文件系统依赖。 + let html = r#"t"#; + let wrapped = wrap_images_with_blur_with(html, |_| Some((800, 600))); let cleaned = clean_html(&wrapped); - println!("WRAPPED: {}", wrapped); - println!("CLEANED: {}", cleaned); assert!( cleaned.contains(" / "), "clean_html must preserve slash in --ar, got: {}",