refactor: migration Clean Architecture — module commands avec render et watch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
pub mod render;
|
||||
pub mod watch;
|
||||
@@ -0,0 +1,101 @@
|
||||
use crate::application::{file_service, render_service};
|
||||
use crate::infrastructure::comrak_renderer::{ComrakPreviewRenderer, ComrakRenderer};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn render_markdown(content: String) -> String {
|
||||
let renderer = ComrakPreviewRenderer {
|
||||
theme: "base16-ocean.dark".to_string(),
|
||||
};
|
||||
render_service::render_string(&renderer, &content)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn convert_file(path: String) -> Result<String, String> {
|
||||
let renderer = ComrakRenderer {
|
||||
theme: "InspiredGitHub".to_string(),
|
||||
};
|
||||
render_service::render_file(&renderer, &path)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_md_files(dir: String) -> Result<Vec<String>, String> {
|
||||
file_service::list_markdown_files(&dir)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
fn tmpdir(name: &str) -> std::path::PathBuf {
|
||||
std::env::temp_dir().join(name)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_markdown_heading() {
|
||||
let html = render_markdown("# Hello\n".to_string());
|
||||
assert!(html.contains("<h1>"));
|
||||
assert!(html.contains("Hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_markdown_code_block() {
|
||||
let html = render_markdown("```rust\nfn main() {}\n```\n".to_string());
|
||||
assert!(html.contains("<code") || html.contains("<pre>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_markdown_empty() {
|
||||
let html = render_markdown(String::new());
|
||||
assert!(!html.contains("<h1>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_file_success() {
|
||||
let dir = tmpdir("pena_convert_success");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
let file = dir.join("test.md");
|
||||
fs::write(&file, "# Title\n\nParagraph.").unwrap();
|
||||
let result = convert_file(file.to_string_lossy().to_string());
|
||||
fs::remove_dir_all(&dir).unwrap();
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().contains("<h1>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_file_with_table() {
|
||||
let dir = tmpdir("pena_convert_table");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
let file = dir.join("table.md");
|
||||
fs::write(&file, "| A | B |\n|---|---|\n| 1 | 2 |").unwrap();
|
||||
let result = convert_file(file.to_string_lossy().to_string());
|
||||
fs::remove_dir_all(&dir).unwrap();
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().contains("<table>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_file_not_found() {
|
||||
let result = convert_file("/nonexistent/path/does/not/exist.md".to_string());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_md_files_sorted() {
|
||||
let dir = tmpdir("pena_list_sorted");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
fs::write(dir.join("z.md"), "").unwrap();
|
||||
fs::write(dir.join("a.md"), "").unwrap();
|
||||
fs::write(dir.join("m.md"), "").unwrap();
|
||||
let result = list_md_files(dir.to_string_lossy().to_string()).unwrap();
|
||||
fs::remove_dir_all(&dir).unwrap();
|
||||
assert_eq!(result.len(), 3);
|
||||
assert!(result[0] < result[1] && result[1] < result[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_md_files_not_a_dir() {
|
||||
let result = list_md_files("/nonexistent/does/not/exist".to_string());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
use crate::infrastructure::notify_watcher::run_debounce_loop;
|
||||
|
||||
pub struct WatcherState(pub Mutex<Option<RecommendedWatcher>>);
|
||||
|
||||
#[tauri::command]
|
||||
pub fn start_watch(
|
||||
app: AppHandle,
|
||||
path: String,
|
||||
state: State<WatcherState>,
|
||||
) -> Result<(), String> {
|
||||
// Drop the previous watcher — disconnects the channel and stops the background thread
|
||||
{
|
||||
let mut guard = state.0.lock().unwrap();
|
||||
*guard = None;
|
||||
}
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel::<notify::Result<notify::Event>>();
|
||||
|
||||
let mut watcher = notify::recommended_watcher(move |res| {
|
||||
let _ = tx.send(res);
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let watch_path = PathBuf::from(&path);
|
||||
let mode = if watch_path.is_dir() {
|
||||
RecursiveMode::Recursive
|
||||
} else {
|
||||
RecursiveMode::NonRecursive
|
||||
};
|
||||
|
||||
watcher
|
||||
.watch(&watch_path, mode)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
run_debounce_loop(rx, app);
|
||||
});
|
||||
|
||||
let mut guard = state.0.lock().unwrap();
|
||||
*guard = Some(watcher);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stop_watch(state: State<WatcherState>) {
|
||||
let mut guard = state.0.lock().unwrap();
|
||||
*guard = None;
|
||||
}
|
||||
Reference in New Issue
Block a user