Files
Marcos Paulo 5e9b5d3cfb
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: audio player, code files with highlighting, ico, draw tools fixed, activity panel, calendar-title link, lightbox and shell fixes
Audio tab and note embeds with waveform, live spectrum (own FFT), seek, speed, loop and volume through ffplay/ffmpeg; code and text files open in an editable source editor with syntax highlighting, a Language chip and a banner to install, pick or ignore a grammar; ico decoding through the largest entry; info card as a clean grid; draw tools: arrows and lines clamped and clipped to the picture with a head, inline text tool, hotkeys; Activity tab with unread count and hotkey; calendar title shows a link icon and a pointing hand; explorer trash button works on press; right panel tab pills shrink so they never cover the collapse button; math: unknown command keeps a parser tooltip; test files grouped into tests/ folders (12-file rule); flaky search-order test waits for the rows. Built by Claude Sonnet and MiniMax M3.1 Flash; the whole tree verified with cargo xtask ci (fmt, clippy -D warnings, 4422 tests, layers, tree, i18n) on a copy of the working tree.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 02:31:52 -03:00

105 lines
4.4 KiB
Rust

//! The language controls of a source file's tab, driven headless: the hint banner for a file type no
//! grammar fits, Ignore, the chip and its picker, and that a choice is remembered and highlights.
use egui_kittest::kittest::Queryable;
use notescraft_themes::builtin;
use notescraft_ui_egui::code_view::language::{self, Action, Prefs};
use notescraft_ui_egui::theme::Tokens;
/// What the tab draws and what the user did.
struct Tab {
path: &'static str,
tokens: Tokens,
prefs: Prefs,
warmed: bool,
}
/// The header chip and, when the type needs a hint, the banner, as the tab draws them.
fn tab(path: &'static str) -> egui_kittest::Harness<'static, Tab> {
let tokens = Tokens::new(builtin("catppuccin-mocha").expect("the theme is there"));
let state = Tab { path, tokens, prefs: Prefs::default(), warmed: false };
egui_kittest::Harness::builder().with_size(egui::Vec2::new(900.0, 500.0)).with_max_steps(200).build_ui_state(
|ui, state: &mut Tab| {
if !state.warmed {
notescraft_ui_egui::fonts::install(ui.ctx());
state.warmed = true;
return;
}
if let Some(action) = language::take_action(ui.ctx()) {
language::apply(&mut state.prefs, action);
}
let resolved = language::resolve(state.path, "", &state.prefs.chosen);
language::set_current(ui.ctx(), state.path, resolved.as_deref());
let row = egui::Rect::from_min_size(ui.cursor().min, egui::vec2(220.0, 28.0));
ui.allocate_rect(row, egui::Sense::hover());
notescraft_ui_egui::code_view::chip(ui, &state.tokens, row, state.path);
if language::needs_hint(state.path, resolved.as_deref(), &state.prefs) {
notescraft_ui_egui::code_view::banner(ui, &state.tokens, state.path);
}
},
state,
)
}
#[test]
fn the_banner_shows_for_an_unknown_type_and_not_for_a_known_one() {
let mut unknown = tab("data/file.xyz");
unknown.run();
unknown.run();
assert!(unknown.query_by_label("No syntax highlighting found for .xyz").is_some());
assert!(unknown.query_by_label("Install a grammar").is_some());
assert!(unknown.query_by_label("Use another language").is_some());
assert!(unknown.query_by_label("Language: Plain text").is_some(), "the chip is always there");
let mut known = tab("ar/publish/publish.css");
known.run();
known.run();
assert!(known.query_by_label_contains("No syntax highlighting").is_none());
assert!(known.query_by_label_contains("Language: CSS").is_some());
}
#[test]
fn ignore_is_remembered_and_the_chip_stays() {
let mut harness = tab("data/file.xyz");
harness.run();
harness.run();
harness.get_by_label("Ignore").click();
harness.run();
harness.run();
assert!(harness.state().prefs.ignored.contains(&"xyz".to_owned()));
assert!(harness.query_by_label_contains("No syntax highlighting").is_none());
assert!(harness.query_by_label("Language: Plain text").is_some());
}
#[test]
fn the_chip_picks_a_language_that_is_remembered_and_highlights() {
let mut harness = tab("data/file.xyz");
harness.run();
harness.run();
harness.get_by_label("Language: Plain text").click();
harness.run();
harness.run();
harness.event(egui::Event::Text("rust".to_owned()));
harness.run();
harness.run();
harness.get_by_label("Rust").click();
for _ in 0..4 {
harness.run();
}
assert_eq!(harness.state().prefs.chosen.get("xyz").map(String::as_str), Some("Rust"));
assert!(harness.query_by_label_contains("No syntax highlighting").is_none());
assert!(harness.query_by_label("Language: Rust").is_some());
let chosen = &harness.state().prefs.chosen;
assert!(language::resolve("other/b.xyz", "", chosen).is_some(), "the type is remembered for every file of it");
// The remembered grammar colours the text.
assert!(notescraft_syntax::Highlighter::new("Rust", "fn main() {}\n").is_some());
}
#[test]
fn a_choice_changes_with_the_chip_at_any_time() {
let mut prefs = Prefs::default();
language::apply(&mut prefs, Action::Choose { key: "css".to_owned(), language: "Python".to_owned() });
assert_eq!(language::resolve("a.css", "", &prefs.chosen).as_deref(), Some("Python"));
language::apply(&mut prefs, Action::Choose { key: "css".to_owned(), language: "CSS".to_owned() });
assert_eq!(language::resolve("a.css", "", &prefs.chosen).as_deref(), Some("CSS"));
}