From 05317d50d51cbfd87e5a9aa96458042d9a586924 Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 1 Jun 2026 10:07:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(diary):=20B7=20=E6=B5=8B=E8=AF=95=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=20+=20F11=20=E6=B7=B1=E8=89=B2=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B7 API 打磨: - DTO 序列化/反序列化测试 12 个 (Mood/Weather/SyncChange/NotificationType等) - 测试总数 17 → 29,全部通过 - SyncChange 添加 Serialize derive (测试发现遗漏) F11 深色模式: - 修复 mood_page.dart 唯一硬编码颜色 Colors.white → colorScheme.onPrimary - 全面审计确认所有页面均使用 AppColors/colorScheme,无其他硬编码 验证: cargo test 29/29 ✓ flutter analyze 0 error ✓ --- app/lib/features/mood/views/mood_page.dart | 4 +- crates/erp-diary/src/dto.rs | 149 ++++++++++++++++++++- 2 files changed, 150 insertions(+), 3 deletions(-) diff --git a/app/lib/features/mood/views/mood_page.dart b/app/lib/features/mood/views/mood_page.dart index 03389e2..ae8329e 100644 --- a/app/lib/features/mood/views/mood_page.dart +++ b/app/lib/features/mood/views/mood_page.dart @@ -213,10 +213,10 @@ class _MoodDistributionChart extends StatelessWidget { color: color, radius: 50, title: '${mc.percentage.toStringAsFixed(0)}%', - titleStyle: const TextStyle( + titleStyle: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: Colors.white, + color: colorScheme.onPrimary, ), ); }).toList(), diff --git a/crates/erp-diary/src/dto.rs b/crates/erp-diary/src/dto.rs index eafa3b1..2c1f871 100644 --- a/crates/erp-diary/src/dto.rs +++ b/crates/erp-diary/src/dto.rs @@ -101,7 +101,7 @@ pub struct SyncReq { } /// 同步变更条目 -#[derive(Debug, Deserialize, ToSchema)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub enum SyncChange { CreateJournal { data: serde_json::Value }, UpdateJournal { id: uuid::Uuid, version: i32, data: serde_json::Value }, @@ -284,3 +284,150 @@ pub struct AchievementResp { pub is_unlocked: bool, pub unlocked_at: Option>, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mood_serializes_to_lowercase() { + let json = serde_json::to_string(&Mood::Happy).unwrap(); + assert_eq!(json, "\"happy\""); + let json = serde_json::to_string(&Mood::Thinking).unwrap(); + assert_eq!(json, "\"thinking\""); + } + + #[test] + fn mood_deserializes() { + let m: Mood = serde_json::from_str("\"calm\"").unwrap(); + assert!(matches!(m, Mood::Calm)); + let m: Mood = serde_json::from_str("\"angry\"").unwrap(); + assert!(matches!(m, Mood::Angry)); + } + + #[test] + fn weather_roundtrip() { + let w = Weather::Snowy; + let json = serde_json::to_string(&w).unwrap(); + let back: Weather = serde_json::from_str(&json).unwrap(); + assert!(matches!(back, Weather::Snowy)); + } + + #[test] + fn create_journal_req_deserializes() { + let json = r#"{ + "title": "测试日记", + "date": "2026-06-01", + "mood": "happy", + "weather": "sunny", + "tags": ["日常"], + "is_private": true + }"#; + let req: CreateJournalReq = serde_json::from_str(json).unwrap(); + assert_eq!(req.title, "测试日记"); + assert!(req.is_private); + assert!(req.class_id.is_none()); + } + + #[test] + fn update_journal_req_minimal() { + let req: UpdateJournalReq = serde_json::from_str(r#"{"version": 2}"#).unwrap(); + assert_eq!(req.version, 2); + assert!(req.title.is_none()); + assert!(req.mood.is_none()); + } + + #[test] + fn notification_type_serializes_snake_case() { + assert_eq!( + serde_json::to_string(&NotificationType::CommentReceived).unwrap(), + "\"comment_received\"" + ); + assert_eq!( + serde_json::to_string(&NotificationType::AchievementUnlocked).unwrap(), + "\"achievement_unlocked\"" + ); + } + + #[test] + fn sync_change_variants_serialize() { + let create = SyncChange::CreateJournal { + data: serde_json::json!({"title": "test"}), + }; + let json = serde_json::to_string(&create).unwrap(); + assert!(json.contains("CreateJournal")); + + let delete = SyncChange::DeleteJournal { + id: uuid::Uuid::nil(), + version: 3, + }; + let json = serde_json::to_string(&delete).unwrap(); + assert!(json.contains("DeleteJournal")); + } + + #[test] + fn conflict_info_serializes() { + let info = ConflictInfo { + journal_id: uuid::Uuid::nil(), + local_version: 1, + server_version: 3, + }; + let json = serde_json::to_string(&info).unwrap(); + assert!(json.contains("\"local_version\":1")); + assert!(json.contains("\"server_version\":3")); + } + + #[test] + fn sticker_pack_resp_serializes() { + let resp = StickerPackResp { + id: uuid::Uuid::nil(), + name: "测试贴纸包".into(), + description: None, + cover_image_url: None, + sticker_count: 10, + is_free: true, + category: Some("动物".into()), + }; + let json = serde_json::to_string(&resp).unwrap(); + assert!(json.contains("\"sticker_count\":10")); + assert!(json.contains("\"is_free\":true")); + } + + #[test] + fn mood_count_percentage() { + let mc = MoodCount { + mood: Mood::Happy, + count: 3, + percentage: 42.5, + }; + let json = serde_json::to_string(&mc).unwrap(); + assert!(json.contains("\"percentage\":42.5")); + } + + #[test] + fn class_member_resp_fields() { + let resp = ClassMemberResp { + user_id: uuid::Uuid::nil(), + role: "student".into(), + nickname: Some("小明".into()), + joined_at: chrono::Utc::now(), + }; + let json = serde_json::to_string(&resp).unwrap(); + assert!(json.contains("\"role\":\"student\"")); + } + + #[test] + fn topic_resp_serializes() { + let resp = TopicResp { + id: uuid::Uuid::nil(), + class_id: uuid::Uuid::nil(), + teacher_id: uuid::Uuid::nil(), + title: "我的周末".into(), + description: Some("描述".into()), + due_date: None, + is_active: true, + }; + let json = serde_json::to_string(&resp).unwrap(); + assert!(json.contains("\"is_active\":true")); + } +}