feat: 支持创作入口公告配置

This commit is contained in:
2026-06-03 03:31:45 +08:00
parent 1cb11bc1dd
commit 70ff18ad90
52 changed files with 3045 additions and 504 deletions

View File

@@ -253,11 +253,135 @@ mod tests {
assert!(rpg.open);
assert_eq!(rpg.badge, "可创建");
assert_eq!(rpg.sort_order, 10);
assert_eq!(rpg.category_id, "recent");
assert_eq!(rpg.category_label, "最近创作");
assert_eq!(rpg.category_id, "recommended");
assert_eq!(rpg.category_label, "热门推荐");
assert_eq!(rpg.category_sort_order, 20);
assert_eq!(rpg.image_src, "/creation-type-references/rpg.webp");
}
#[test]
fn default_creation_entry_types_do_not_seed_recent_as_template_category() {
let configs = default_creation_entry_type_snapshots(1);
assert!(configs.iter().all(|item| item.category_id != "recent"));
assert!(configs.iter().all(|item| item.category_label != "最近创作"));
}
#[test]
fn creation_entry_event_banners_json_normalizes_multiple_banners() {
let normalized = normalize_creation_entry_event_banners_json(
r#"[
{
"title": " 周末拼图赛 ",
"description": " 拼一个新主题 ",
"coverImageSrc": "/creation-type-references/puzzle.webp",
"prizePoolMudPoints": 1200,
"startsAtText": "2026-06-01",
"endsAtText": "2026-06-30",
"renderMode": "structured",
"htmlCode": "<div>ignored</div>"
},
{
"title": "HTML 横幅",
"description": "沙箱片段",
"coverImageSrc": "/creation-type-references/match3d.webp",
"prizePoolMudPoints": 900,
"startsAtText": "2026-07-01",
"endsAtText": "2026-07-31",
"renderMode": "html",
"htmlCode": " <section>安全片段</section> "
}
]"#,
)
.expect("valid banner json should normalize");
let banners = decode_creation_entry_event_banner_snapshots(&normalized)
.expect("normalized banner json should decode");
assert_eq!(banners.len(), 2);
assert_eq!(banners[0].title, "周末拼图赛");
assert_eq!(banners[0].description, "拼一个新主题");
assert_eq!(banners[0].render_mode, "structured");
assert!(banners[0].html_code.is_none());
assert_eq!(banners[1].render_mode, "html");
assert_eq!(
banners[1].html_code.as_deref(),
Some("<section>安全片段</section>")
);
}
#[test]
fn creation_entry_event_banners_json_empty_input_returns_defaults() {
let normalized = normalize_creation_entry_event_banners_json(" ")
.expect("blank banner json should use defaults");
let banners = decode_creation_entry_event_banner_snapshots(&normalized)
.expect("default banner json should decode");
assert_eq!(banners, default_creation_entry_event_banner_snapshots());
}
#[test]
fn creation_entry_event_banners_json_accepts_announcement_html_code() {
let normalized = normalize_creation_entry_event_banners_json(
r#"[
"<section>纯 HTML 公告</section>",
{"title": "后台公告", "htmlCode": "<article>自定义公告</article>"}
]"#,
)
.expect("announcement html json should normalize");
let banners = decode_creation_entry_event_banner_snapshots(&normalized)
.expect("normalized announcement json should decode");
assert_eq!(banners.len(), 2);
assert_eq!(banners[0].title, "公告 1");
assert_eq!(banners[0].render_mode, "html");
assert_eq!(
banners[0].html_code.as_deref(),
Some("<section>纯 HTML 公告</section>")
);
assert_eq!(banners[1].title, "后台公告");
assert_eq!(
banners[1].html_code.as_deref(),
Some("<article>自定义公告</article>")
);
}
#[test]
fn creation_entry_event_banners_json_rejects_script_like_html() {
let script_error = normalize_creation_entry_event_banners_json(
r#"[
{
"title": "脚本横幅",
"description": "不允许脚本",
"coverImageSrc": "/creation-type-references/puzzle.webp",
"prizePoolMudPoints": 100,
"startsAtText": "2026-06-01",
"endsAtText": "2026-06-30",
"renderMode": "html",
"htmlCode": "<script>alert(1)</script>"
}
]"#,
)
.expect_err("script tag should be rejected");
let javascript_url_error = normalize_creation_entry_event_banners_json(
r#"[
{
"title": "链接横幅",
"description": "不允许 javascript URL",
"coverImageSrc": "/creation-type-references/puzzle.webp",
"prizePoolMudPoints": 100,
"startsAtText": "2026-06-01",
"endsAtText": "2026-06-30",
"renderMode": "html",
"htmlCode": "<a href=\"javascript:alert(1)\">bad</a>"
}
]"#,
)
.expect_err("javascript url should be rejected");
assert!(script_error.contains("脚本代码"));
assert!(javascript_url_error.contains("脚本代码"));
}
#[test]
fn default_creation_entry_types_include_bark_battle() {
let configs = default_creation_entry_type_snapshots(1);