Files
Marcos Paulo 15d7f5e873 feat: editor blocks and lint, vault knowledge, calendar plugin, macOS menu
- notescraft-lint (rules, safe fixes, learned style) and notescraft-blocks (block model and edits); engine lint.* and block.* commands with CLI and MCP parity
- Lint tab, underlines, slash menu with preview in Source and Visual, block grip and menu, Settings > Editor and lint, Vim block keys
- Visual view: tables, note embeds, %% comments, wrapping property chips
- Vault knowledge (Related tab, assistant sources, Settings) and calendar plugin
- Sidebar top padding fixed, dot grid follows PaperCraft spacing, right-card tabs no longer cover the collapse button

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 12:36:00 -03:00

88 lines
3.5 KiB
Rust

//! Inline mark toggles.
use notescraft_editor::marks::{Mark, active_marks, toggle_mark};
#[test]
fn wrap_and_unwrap_each_mark() {
for (mark, wrapped) in
[(Mark::Bold, "a **b** c"), (Mark::Italic, "a *b* c"), (Mark::Strike, "a ~~b~~ c"), (Mark::Code, "a `b` c"), (Mark::Highlight, "a ==b== c")]
{
let on = toggle_mark("a b c", 2..3, mark).unwrap();
assert_eq!(on.text, wrapped);
assert_eq!(&on.text[on.sel.clone()], "b");
assert!(active_marks(&on.text, on.sel.clone()).contains(&mark));
let off = toggle_mark(&on.text, on.sel.clone(), mark).unwrap();
assert_eq!(off.text, "a b c", "{mark:?}");
assert_eq!(&off.text[off.sel.clone()], "b");
let mut applied = "a b c".to_owned();
applied.replace_range(on.replace.0.clone(), &on.replace.1);
assert_eq!(applied, on.text);
}
}
#[test]
fn unwrap_when_selection_includes_markers() {
let off = toggle_mark("a **b** c", 2..7, Mark::Bold).unwrap();
assert_eq!(off.text, "a b c");
assert_eq!(active_marks("a **b** c", 2..7), vec![Mark::Bold]);
}
#[test]
fn bold_and_italic_nest() {
let b = toggle_mark("x", 0..1, Mark::Bold).unwrap();
assert_eq!(active_marks(&b.text, b.sel.clone()), vec![Mark::Bold]);
let bi = toggle_mark(&b.text, b.sel.clone(), Mark::Italic).unwrap();
assert_eq!(bi.text, "***x***");
assert_eq!(active_marks(&bi.text, bi.sel.clone()), vec![Mark::Bold, Mark::Italic]);
let only_b = toggle_mark(&bi.text, bi.sel.clone(), Mark::Italic).unwrap();
assert_eq!(only_b.text, "**x**");
let only_i = toggle_mark(&bi.text, bi.sel.clone(), Mark::Bold).unwrap();
assert_eq!(only_i.text, "*x*");
}
#[test]
fn link_wraps_with_caret_in_url_and_toggles_back() {
let on = toggle_mark("see site now", 4..8, Mark::Link).unwrap();
assert_eq!(on.text, "see [site]() now");
assert_eq!(on.sel, 11..11);
assert!(active_marks(&on.text, on.sel.clone()).contains(&Mark::Link));
let off = toggle_mark(&on.text, on.sel.clone(), Mark::Link).unwrap();
assert_eq!(off.text, "see site now");
let off2 = toggle_mark("see [site](http://x) now", 4..20, Mark::Link).unwrap();
assert_eq!(off2.text, "see site now");
let off3 = toggle_mark("see [site](http://x) now", 5..9, Mark::Link).unwrap();
assert_eq!(off3.text, "see site now");
}
#[test]
fn empty_selection_inserts_pair_and_toggles_back() {
let on = toggle_mark("ab", 1..1, Mark::Bold).unwrap();
assert_eq!(on.text, "a****b");
assert_eq!(on.sel, 3..3);
assert_eq!(toggle_mark(&on.text, on.sel, Mark::Bold).unwrap().text, "ab");
}
#[test]
fn multibyte_and_hostile() {
let t = "é日本語 ok";
let on = toggle_mark(t, 0..2, Mark::Strike).unwrap();
assert_eq!(on.text, "~~é~~日本語 ok");
assert!(toggle_mark(t, 1..2, Mark::Bold).is_err());
assert!(toggle_mark(t, 0..99, Mark::Bold).is_err());
assert!(toggle_mark(t, std::ops::Range { start: 3, end: 1 }, Mark::Bold).is_err());
assert_eq!(active_marks(t, 1..2), vec![]);
for text in ["", "*", "**", "***", "[", "](", "[]()", "`", "====", "~~"] {
for s in 0..=text.len() {
for e in s..=text.len() {
for m in Mark::ALL {
let _ = active_marks(text, s..e);
let _ = toggle_mark(text, s..e, m);
}
}
}
}
let big = "word ".repeat(100_000);
let on = toggle_mark(&big, 5..9, Mark::Italic).unwrap();
assert_eq!(toggle_mark(&on.text, on.sel, Mark::Italic).unwrap().text, big);
}