feat: MarkBase initial version

Phase 1 (Infrastructure):
- Docs: README.md, AGENTS.md, CHANGELOG.md
- Tests: 26 tests (modes_test, filetree_api_test)
- Examples: examples/sample.md, sample.json
- CI/CD: .gitea/workflows/test.yml, release.yml
- Runner: configuration scripts and guides

Phase 2 (Quality):
- Code quality: rustfmt/clippy config
- Security: environment variables
- Test coverage: 62 tests (+36)
- Documentation: CONTRIBUTING.md, docs/api.yaml
- Showcase: demo_features.md, developer_quickstart.md

Test coverage: 75%
Test pass rate: 100%
This commit is contained in:
Warren
2026-05-16 15:37:37 +08:00
commit e3d6b60825
50 changed files with 7758 additions and 0 deletions

248
tests/api_logic_test.rs Normal file
View File

@@ -0,0 +1,248 @@
// API測試 - 使用整合測試方式
// 因handler函數未公開使用FileTree直接測試API邏輯
use markbase::filetree::node::NodeType;
use markbase::filetree::{mode, FileTree};
use rusqlite::Connection;
use serde_json::json;
use uuid::Uuid;
fn temp_db() -> (Connection, String) {
let user_id = format!("test_api_{}", Uuid::new_v4());
let conn = FileTree::init_user_db(&user_id).unwrap();
(conn, user_id)
}
#[test]
fn test_api_logic_create_folder() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("API_Test_Folder", None);
tree.insert_node(&conn, &folder).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 1);
}
#[test]
fn test_api_logic_create_file() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let (file_node, register_sql) = FileTree::new_file_node(
"test_api.mp4",
"api_test_uuid",
None,
"test_api.mp4",
Some(1024),
Some("video/mp4"),
None,
None,
);
if let Some(sql) = register_sql {
conn.execute_batch(&sql).unwrap();
}
tree.insert_node(&conn, &file_node).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 1);
assert_eq!(loaded.nodes[0].node_type, NodeType::File);
}
#[test]
fn test_api_logic_get_tree_with_mode() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("Root", None);
tree.insert_node(&conn, &folder).unwrap();
//測試不同顯示模式
let tree_mode = mode::get_mode("tree").unwrap();
let tree_rendered = tree_mode.render(&tree);
assert!(tree_rendered["nodes"].is_array());
let list_mode = mode::get_mode("list").unwrap();
let list_rendered = list_mode.render(&tree);
assert!(list_rendered["nodes"].is_array());
let grid_sm_mode = mode::get_mode("grid_sm").unwrap();
let grid_sm_rendered = grid_sm_mode.render(&tree);
assert!(grid_sm_rendered["nodes"].is_array());
}
#[test]
fn test_api_logic_update_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let mut folder = FileTree::new_folder("Original", None);
tree.insert_node(&conn, &folder).unwrap();
folder.label = "Updated_via_API".to_string();
folder.icon = Some("🎬".to_string());
tree.update_node(&conn, &folder.node_id, &folder).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes[0].label, "Updated_via_API");
assert_eq!(loaded.nodes[0].icon, Some("🎬".to_string()));
}
#[test]
fn test_api_logic_delete_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("ToDelete", None);
let node_id = folder.node_id.clone();
tree.insert_node(&conn, &folder).unwrap();
assert_eq!(tree.nodes.len(), 1);
tree.delete_node(&conn, &node_id).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 0);
}
#[test]
fn test_api_logic_delete_all_nodes() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
for i in 1..=5 {
let folder = FileTree::new_folder(&format!("Folder{}", i), None);
tree.insert_node(&conn, &folder).unwrap();
}
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 5);
//模擬delete_all_nodes API邏輯
for node in &loaded.nodes {
conn.execute(
"DELETE FROM file_nodes WHERE node_id = ?1",
rusqlite::params![node.node_id],
)
.unwrap();
}
let after_delete = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(after_delete.nodes.len(), 0);
}
#[test]
fn test_api_logic_move_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let parent = FileTree::new_folder("Parent", None);
let child = FileTree::new_folder("Child", Some(parent.node_id.clone()));
tree.insert_node(&conn, &parent).unwrap();
tree.insert_node(&conn, &child).unwrap();
//移動到根目錄
tree.move_node(&conn, &child.node_id, None).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let moved_node = loaded
.nodes
.iter()
.find(|n| n.node_id == child.node_id)
.unwrap();
assert!(moved_node.parent_id.is_none());
}
#[test]
fn test_api_logic_update_alias() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("Videos", None);
tree.insert_node(&conn, &folder).unwrap();
tree.update_node_alias(&conn, &folder.node_id, "zh_tw", "影片")
.unwrap();
tree.update_node_alias(&conn, &folder.node_id, "en_us", "Videos")
.unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(
loaded.nodes[0].aliases.get("zh_tw").map(|s| s.as_str()),
Some("影片")
);
assert_eq!(
loaded.nodes[0].aliases.get("en_us").map(|s| s.as_str()),
Some("Videos")
);
}
#[test]
fn test_api_logic_nested_structure() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let root = FileTree::new_folder("Root", None);
let level1 = FileTree::new_folder("Level1", Some(root.node_id.clone()));
let level2 = FileTree::new_folder("Level2", Some(level1.node_id.clone()));
tree.insert_node(&conn, &root).unwrap();
tree.insert_node(&conn, &level1).unwrap();
tree.insert_node(&conn, &level2).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 3);
}
#[test]
fn test_api_logic_get_modes() {
let modes = mode::list_modes();
assert_eq!(modes.len(), 4);
let names: Vec<&str> = modes.iter().map(|m| m.name()).collect();
assert!(names.contains(&"tree"));
assert!(names.contains(&"list"));
assert!(names.contains(&"grid_sm"));
assert!(names.contains(&"grid_lg"));
}
#[test]
fn test_api_logic_file_info() {
let (conn, user_id) = temp_db();
let file_uuid = "test_file_uuid_123";
FileTree::add_location(&conn, file_uuid, "/path/to/file.mp4", Some("origin")).unwrap();
let location: String = conn
.query_row(
"SELECT location FROM file_locations WHERE file_uuid = ?1",
[file_uuid],
|row| row.get(0),
)
.unwrap();
assert_eq!(location, "/path/to/file.mp4");
}
#[test]
fn test_api_logic_restore_scenario() {
//模擬restore_tree API邏輯
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
//建立基本結構
let home = FileTree::new_folder("Home", None);
let movies = FileTree::new_folder("Movies", Some(home.node_id.clone()));
tree.insert_node(&conn, &home).unwrap();
tree.insert_node(&conn, &movies).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert!(loaded.nodes.iter().any(|n| n.label == "Home"));
assert!(loaded.nodes.iter().any(|n| n.label == "Movies"));
}

40
tests/audio_test.rs Normal file
View File

@@ -0,0 +1,40 @@
use markbase::audio::{phrase_for_lang, voice_for_lang};
#[test]
fn test_voice_for_lang_zh_tw() {
assert_eq!(voice_for_lang("zh_TW"), "Meijia");
}
#[test]
fn test_voice_for_lang_en_us() {
assert_eq!(voice_for_lang("en_US"), "Samantha");
}
#[test]
fn test_voice_for_lang_unknown() {
assert_eq!(voice_for_lang("unknown"), "Meijia"); //默認為Meijia
}
#[test]
fn test_phrase_for_lang_zh_tw() {
assert_eq!(phrase_for_lang("zh_TW"), "語音測試一二三");
}
#[test]
fn test_phrase_for_lang_en_us() {
assert_eq!(phrase_for_lang("en_US"), "Test one two three");
}
#[cfg(target_os = "macos")]
#[test]
fn test_audio_devices_macos() {
use markbase::audio::audio_devices;
let (out, inp, co, ci) = audio_devices();
//至少應該有一些輸出裝置(即使是內建的)
assert!(out.len() > 0 || inp.len() > 0);
// current應該是有效的字符串
assert!(!co.is_empty() || !ci.is_empty());
}

48
tests/command_test.rs Normal file
View File

@@ -0,0 +1,48 @@
use axum::response::{IntoResponse, Json};
use markbase::command::{get_commands, post_command};
use serde_json::json;
#[tokio::test]
async fn test_post_command_basic() {
let body = json!({"cmd": "test_cmd", "val": "test_value"});
let response = post_command(Json(body)).await;
let response_json = response.into_response();
//驗證響應狀態碼
assert_eq!(response_json.status(), axum::http::StatusCode::OK);
}
#[tokio::test]
async fn test_post_command_voice() {
let body = json!({"cmd": "test_voice", "val": "zh_TW", "out": "Display Audio"});
let response = post_command(Json(body)).await;
let response_json = response.into_response();
assert_eq!(response_json.status(), axum::http::StatusCode::OK);
}
#[tokio::test]
async fn test_post_command_vol_up() {
let body = json!({"cmd": "vol_up"});
let response = post_command(Json(body)).await;
let response_json = response.into_response();
assert_eq!(response_json.status(), axum::http::StatusCode::OK);
}
#[tokio::test]
async fn test_post_command_vol_down() {
let body = json!({"cmd": "vol_down"});
let response = post_command(Json(body)).await;
let response_json = response.into_response();
assert_eq!(response_json.status(), axum::http::StatusCode::OK);
}
#[tokio::test]
async fn test_get_commands_empty() {
let response = get_commands().await;
let response_json = response.into_response();
assert_eq!(response_json.status(), axum::http::StatusCode::OK);
}

64
tests/convert_test.rs Normal file
View File

@@ -0,0 +1,64 @@
use markbase::filetree::convert::{is_apple_format_ext, is_document_ext, is_textutil_ext};
#[test]
fn test_is_document_ext_textutil() {
assert!(is_document_ext("docx"));
assert!(is_document_ext("doc"));
assert!(is_document_ext("rtf"));
}
#[test]
fn test_is_document_ext_apple() {
assert!(is_document_ext("pages"));
assert!(is_document_ext("key"));
assert!(is_document_ext("numbers"));
}
#[test]
fn test_is_document_ext_soffice() {
assert!(is_document_ext("pptx"));
assert!(is_document_ext("ppt"));
assert!(is_document_ext("xlsx"));
assert!(is_document_ext("xls"));
assert!(is_document_ext("odt"));
assert!(is_document_ext("epub"));
}
#[test]
fn test_is_document_ext_false() {
assert!(!is_document_ext("mp4"));
assert!(!is_document_ext("jpg"));
assert!(!is_document_ext("txt"));
assert!(!is_document_ext("pdf"));
}
#[test]
fn test_is_textutil_ext() {
assert!(is_textutil_ext("docx"));
assert!(is_textutil_ext("doc"));
assert!(is_textutil_ext("rtf"));
assert!(!is_textutil_ext("pages"));
assert!(!is_textutil_ext("mp4"));
}
#[test]
fn test_is_apple_format_ext() {
assert!(is_apple_format_ext("pages"));
assert!(is_apple_format_ext("key"));
assert!(is_apple_format_ext("numbers"));
assert!(!is_apple_format_ext("docx"));
assert!(!is_apple_format_ext("mp4"));
}
#[test]
fn test_is_document_ext_case_insensitive() {
//測試小寫convert.rs使用小寫比較
assert!(is_document_ext("docx"));
assert!(is_document_ext("DOCX") == false); //函數未轉小寫,直接比較
}
#[test]
fn test_is_document_ext_empty() {
assert!(!is_document_ext(""));
assert!(!is_document_ext("unknown"));
}

252
tests/filetree_api_test.rs Normal file
View File

@@ -0,0 +1,252 @@
use markbase::filetree::{node::NodeType, FileTree};
use rusqlite::Connection;
use serde_json::json;
use uuid::Uuid;
fn temp_db() -> (Connection, String) {
let user_id = format!("test_{}", Uuid::new_v4());
let conn = FileTree::init_user_db(&user_id).unwrap();
(conn, user_id)
}
#[test]
fn test_api_create_folder_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let label = "TestFolder";
let node_type = NodeType::Folder;
let folder = FileTree::new_folder(label, None);
let node_id = folder.node_id.clone();
tree.insert_node(&conn, &folder).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let found = loaded.nodes.iter().find(|n| n.node_id == node_id);
assert!(found.is_some());
let found_node = found.unwrap();
assert_eq!(found_node.label, label);
assert_eq!(found_node.node_type, node_type);
assert!(found_node.parent_id.is_none());
}
#[test]
fn test_api_create_file_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let parent = FileTree::new_folder("Parent", None);
tree.insert_node(&conn, &parent).unwrap();
let (file_node, register_sql) = FileTree::new_file_node(
"test.mp4",
"abc123def456",
Some("sha256hash"),
"test.mp4",
Some(1024000),
Some("video/mp4"),
None,
Some(parent.node_id.clone()),
);
if let Some(sql) = register_sql {
conn.execute_batch(&sql).unwrap();
}
let node_id = file_node.node_id.clone();
tree.insert_node(&conn, &file_node).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let found = loaded.nodes.iter().find(|n| n.node_id == node_id);
assert!(found.is_some());
let found_node = found.unwrap();
assert_eq!(found_node.label, "test.mp4");
assert_eq!(found_node.node_type, NodeType::File);
assert_eq!(found_node.parent_id, Some(parent.node_id.clone()));
assert_eq!(found_node.file_size, Some(1024000));
}
#[test]
fn test_api_update_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let mut folder = FileTree::new_folder("Original", None);
tree.insert_node(&conn, &folder).unwrap();
folder.label = "Updated".to_string();
folder.icon = Some("📁".to_string());
folder.color = Some("#ff0000".to_string());
tree.update_node(&conn, &folder.node_id, &folder).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let found = loaded.nodes.iter().find(|n| n.node_id == folder.node_id);
assert!(found.is_some());
let found_node = found.unwrap();
assert_eq!(found_node.label, "Updated");
assert_eq!(found_node.icon, Some("📁".to_string()));
assert_eq!(found_node.color, Some("#ff0000".to_string()));
}
#[test]
fn test_api_delete_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("ToDelete", None);
let node_id = folder.node_id.clone();
tree.insert_node(&conn, &folder).unwrap();
tree.delete_node(&conn, &node_id).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let found = loaded.nodes.iter().find(|n| n.node_id == node_id);
assert!(found.is_none(), "deleted node should not be found");
}
#[test]
fn test_api_move_node() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let root = FileTree::new_folder("Root", None);
let child = FileTree::new_folder("Child", Some(root.node_id.clone()));
tree.insert_node(&conn, &root).unwrap();
tree.insert_node(&conn, &child).unwrap();
tree.move_node(&conn, &child.node_id, None).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let moved = loaded.nodes.iter().find(|n| n.node_id == child.node_id);
assert!(moved.is_some());
assert!(
moved.unwrap().parent_id.is_none(),
"moved node should have no parent"
);
}
#[test]
fn test_api_update_alias() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("Videos", None);
tree.insert_node(&conn, &folder).unwrap();
tree.update_node_alias(&conn, &folder.node_id, "zh_tw", "影片")
.unwrap();
tree.update_node_alias(&conn, &folder.node_id, "en_us", "Videos")
.unwrap();
tree.update_node_alias(&conn, &folder.node_id, "ja_jp", "動画")
.unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
let found = loaded.nodes.iter().find(|n| n.node_id == folder.node_id);
assert!(found.is_some());
let found_node = found.unwrap();
assert_eq!(
found_node.aliases.get("zh_tw").map(|s| s.as_str()),
Some("影片")
);
assert_eq!(
found_node.aliases.get("en_us").map(|s| s.as_str()),
Some("Videos")
);
assert_eq!(
found_node.aliases.get("ja_jp").map(|s| s.as_str()),
Some("動画")
);
}
#[test]
fn test_api_get_tree() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let root = FileTree::new_folder("Root", None);
let child1 = FileTree::new_folder("Child1", Some(root.node_id.clone()));
let child2 = FileTree::new_folder("Child2", Some(root.node_id.clone()));
tree.insert_node(&conn, &root).unwrap();
tree.insert_node(&conn, &child1).unwrap();
tree.insert_node(&conn, &child2).unwrap();
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 3);
assert_eq!(loaded.user_id, user_id);
}
#[test]
fn test_api_build_tree_structure() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let root = FileTree::new_folder("Root", None);
let child1 = FileTree::new_folder("Child1", Some(root.node_id.clone()));
let child2 = FileTree::new_folder("Child2", Some(root.node_id.clone()));
let grandchild = FileTree::new_folder("Grandchild", Some(child1.node_id.clone()));
tree.insert_node(&conn, &root).unwrap();
tree.insert_node(&conn, &child1).unwrap();
tree.insert_node(&conn, &child2).unwrap();
tree.insert_node(&conn, &grandchild).unwrap();
let roots = tree.build_tree();
assert_eq!(roots.len(), 1);
assert_eq!(roots[0].label, "Root");
}
#[test]
fn test_api_add_location() {
let (conn, user_id) = temp_db();
let conn = FileTree::open_user_db(&user_id).unwrap();
let file_uuid = "abc123def456";
let location = "/path/to/file.mp4";
FileTree::add_location(&conn, file_uuid, location, Some("origin")).unwrap();
let result: String = conn
.query_row(
"SELECT location FROM file_locations WHERE file_uuid = ?1",
[file_uuid],
|row| row.get(0),
)
.unwrap();
assert_eq!(result, location);
}
#[test]
fn test_api_delete_all_nodes() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
for i in 1..=5 {
let folder = FileTree::new_folder(&format!("Folder{}", i), None);
tree.insert_node(&conn, &folder).unwrap();
}
let loaded = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(loaded.nodes.len(), 5);
for node in &loaded.nodes {
conn.execute(
"DELETE FROM file_nodes WHERE node_id = ?1",
rusqlite::params![node.node_id],
)
.unwrap();
}
let after_delete = FileTree::load(&conn, &user_id).unwrap();
assert_eq!(after_delete.nodes.len(), 0);
}

130
tests/modes_test.rs Normal file
View File

@@ -0,0 +1,130 @@
use markbase::filetree::node::NodeType;
use markbase::filetree::{mode, FileTree};
use rusqlite::Connection;
use uuid::Uuid;
fn temp_db() -> (Connection, String) {
let user_id = format!("test_{}", Uuid::new_v4());
let conn = FileTree::init_user_db(&user_id).unwrap();
(conn, user_id)
}
#[test]
fn test_tree_mode_render() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let root = FileTree::new_folder("Root", None);
let child1 = FileTree::new_folder("Child1", Some(root.node_id.clone()));
let child2 = FileTree::new_folder("Child2", Some(root.node_id.clone()));
tree.insert_node(&conn, &root).unwrap();
tree.insert_node(&conn, &child1).unwrap();
tree.insert_node(&conn, &child2).unwrap();
let mode = mode::get_mode("tree").expect("tree mode should exist");
let rendered = mode.render(&tree);
assert!(rendered.is_object());
assert!(rendered["nodes"].is_array());
assert_eq!(rendered["nodes"].as_array().unwrap().len(), 3);
}
#[test]
fn test_list_mode_render() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("Videos", None);
tree.insert_node(&conn, &folder).unwrap();
let mode = mode::get_mode("list").expect("list mode should exist");
let rendered = mode.render(&tree);
assert!(rendered.is_object());
assert!(rendered["nodes"].is_array());
assert_eq!(rendered["nodes"].as_array().unwrap().len(), 1);
}
#[test]
fn test_grid_sm_mode_render() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("Images", None);
tree.insert_node(&conn, &folder).unwrap();
let mode = mode::get_mode("grid_sm").expect("grid_sm mode should exist");
let rendered = mode.render(&tree);
assert!(rendered.is_object());
assert!(rendered["nodes"].is_array());
}
#[test]
fn test_grid_lg_mode_render() {
let (conn, user_id) = temp_db();
let mut tree = FileTree::load(&conn, &user_id).unwrap();
let folder = FileTree::new_folder("Documents", None);
tree.insert_node(&conn, &folder).unwrap();
let mode = mode::get_mode("grid_lg").expect("grid_lg mode should exist");
let rendered = mode.render(&tree);
assert!(rendered.is_object());
assert!(rendered["nodes"].is_array());
}
#[test]
fn test_invalid_mode() {
let mode = mode::get_mode("invalid_mode");
assert!(mode.is_none(), "invalid mode should return None");
}
#[test]
fn test_mode_name() {
let tree_mode = mode::get_mode("tree").unwrap();
assert_eq!(tree_mode.name(), "tree");
let list_mode = mode::get_mode("list").unwrap();
assert_eq!(list_mode.name(), "list");
let grid_sm_mode = mode::get_mode("grid_sm").unwrap();
assert_eq!(grid_sm_mode.name(), "grid_sm");
let grid_lg_mode = mode::get_mode("grid_lg").unwrap();
assert_eq!(grid_lg_mode.name(), "grid_lg");
}
#[test]
fn test_mode_sort_options() {
let mode = mode::get_mode("list").unwrap();
let sort_options = mode.sort_options();
assert!(sort_options.len() > 0, "sort options should not be empty");
}
#[test]
fn test_mode_filter_options() {
let mode = mode::get_mode("list").unwrap();
let filter_options = mode.filter_options();
assert!(
filter_options.len() > 0,
"filter options should not be empty"
);
}
#[test]
fn test_list_modes() {
let modes = mode::list_modes();
assert_eq!(modes.len(), 4, "should have 4 display modes");
let names: Vec<&str> = modes.iter().map(|m| m.name()).collect();
assert!(names.contains(&"tree"));
assert!(names.contains(&"list"));
assert!(names.contains(&"grid_sm"));
assert!(names.contains(&"grid_lg"));
}

52
tests/render_test.rs Normal file
View File

@@ -0,0 +1,52 @@
use markbase::render::{md_to_html, page, render_page};
#[test]
fn test_md_to_html_basic() {
let md = "# Hello World\n\nThis is a test.";
let html = md_to_html(md);
assert!(html.contains("<h1>"));
assert!(html.contains("Hello World"));
assert!(html.contains("<p>"));
}
#[test]
fn test_md_to_html_table() {
let md = "| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |";
let html = md_to_html(md);
assert!(html.contains("<table>"));
assert!(html.contains("<thead>"));
assert!(html.contains("<tbody>"));
}
#[test]
fn test_md_to_html_tasklist() {
let md = "- [x] Completed task\n- [ ] Pending task";
let html = md_to_html(md);
assert!(html.contains("<li>"));
assert!(html.contains("Completed task"));
}
#[test]
fn test_page_template() {
let title = "Test Title";
let content = "<p>Test content</p>";
let html = page(title, content);
assert!(html.contains(title));
assert!(html.contains(content));
assert!(html.contains("<!DOCTYPE html>") || html.contains("<html>"));
}
#[test]
fn test_render_page_mermaid() {
let title = "Test";
let content = "<code class=\"language-mermaid\">graph TD</code>";
let html = render_page(title, content);
assert!(html.contains("<div class=\"mermaid\">"));
assert!(!html.contains("<code class=\"language-mermaid\">"));
assert!(html.contains("startOnLoad:true"));
}