Files
Marcos Paulo cac6b0ed39
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: registry consumers (grammar, theme, pack), seed reader, wasm plugin host with approval, wanted scan, STIX and Lottie store lookups, plugin auto-disable persistence
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 18:35:47 -03:00

103 lines
4.6 KiB
Rust

//! The theme consumer: themes installed from the add-on store join the user themes, and a
//! selected theme that is gone falls back to a built-in with a diagnostic.
mod addons_support;
mod common;
use std::sync::Arc;
use addons_support::{Shared, entry, index_json, serve};
use common::{TempVault, temp_config, temp_vault};
use notescraft_engine::addons::{FakeTransport, FakeVerifier, INDEX_URL, Response};
use notescraft_engine::{Registry, Session, register_core};
use serde_json::{Value, json};
fn theme_text(id: &str, name: &str, variant: &str) -> String {
format!("id = \"{id}\"\nname = \"{name}\"\nvariant = \"{variant}\"\n")
}
struct Fixture {
reg: Registry<Session>,
session: Session,
config: std::path::PathBuf,
_guards: (TempVault, TempVault),
}
/// A session whose index lists the themes `(id, text)`.
fn fixture(name: &str, themes: &[(&str, String)]) -> Fixture {
let (vault, config) = (temp_vault(name), temp_config(name));
let mut session = Session::open(vault.path()).unwrap();
session.use_settings_dir(config.path());
let files: Vec<(String, String)> = themes.iter().map(|(id, text)| (format!("{id}.toml"), text.clone())).collect();
let entries: Vec<Value> = themes.iter().zip(&files).map(|((id, _), (path, text))| entry("theme", id, "1.0.0", &[(path, text.as_bytes())], &[])).collect();
let mut net = FakeTransport::new().with_response(INDEX_URL, Ok(Response { status: 200, etag: None, bytes: index_json(&entries, &[]) }));
for ((id, _), (path, text)) in themes.iter().zip(&files) {
net = serve(net, "theme", id, "1.0.0", &[(path, text.as_bytes())]);
}
session.set_addons_transport(Box::new(Shared(Arc::new(net))));
session.set_addons_verifier(Box::new(FakeVerifier { accept: true }));
let mut reg = Registry::new();
register_core(&mut reg).unwrap();
let config_path = config.path().to_path_buf();
Fixture { reg, session, config: config_path, _guards: (vault, config) }
}
impl Fixture {
fn ok(&mut self, id: &str, params: &Value) -> Value {
self.reg.run(id, &mut self.session, params).unwrap_or_else(|e| panic!("{id}: {e}"))
}
fn install(&mut self, id: &str) {
self.ok("registry.check", &json!({}));
self.ok("registry.install", &json!({ "kind": "theme", "id": id }));
}
}
#[test]
fn an_installed_theme_resolves_and_is_listed() {
let mut f = fixture("th-ok", &[("mine", theme_text("mine", "Mine", "dark"))]);
assert!(f.session.theme_named("mine").is_none());
f.install("mine");
assert_eq!(f.session.theme_named("mine").map(|t| t.name), Some("Mine".to_owned()));
assert!(f.session.themes().contains(&"mine".to_owned()));
f.session.set_theme("mine").unwrap();
assert_eq!(f.session.theme().unwrap().id, "mine");
}
#[test]
fn a_missing_selected_theme_falls_back_to_the_builtin_of_its_variant_with_a_diagnostic() {
let mut f = fixture("th-gone", &[("mine", theme_text("mine", "Mine", "dark")), ("sun-light", theme_text("sun-light", "Sun", "light"))]);
f.ok("registry.check", &json!({}));
for id in ["mine", "sun-light"] {
f.ok("registry.install", &json!({ "kind": "theme", "id": id }));
}
f.session.set_theme("mine").unwrap();
f.ok("registry.remove", &json!({ "kind": "theme", "id": "mine" }));
assert_eq!(f.session.theme().unwrap().id, "dark");
assert!(f.session.diagnostics().iter().any(|d| d.contains("`mine`") && d.contains("`dark`")), "{:?}", f.session.diagnostics());
f.session.set_theme("sun-light").unwrap();
f.ok("registry.remove", &json!({ "kind": "theme", "id": "sun-light" }));
assert_eq!(f.session.theme().unwrap().id, "light");
}
#[test]
fn a_settings_folder_theme_beats_a_store_theme_of_the_same_id() {
let mut f = fixture("th-dup", &[("mine", theme_text("mine", "Store", "dark"))]);
f.install("mine");
std::fs::create_dir_all(f.config.join("themes")).unwrap();
std::fs::write(f.config.join("themes/zzz.toml"), theme_text("mine", "Folder", "dark")).unwrap();
assert_eq!(f.session.theme_named("mine").map(|t| t.name), Some("Folder".to_owned()));
assert_eq!(f.session.themes().iter().filter(|id| *id == "mine").count(), 1);
}
#[test]
fn a_store_theme_never_takes_a_builtin_id_and_a_corrupt_file_is_skipped() {
let mut f = fixture("th-bad", &[("mine", theme_text("mine", "Mine", "dark"))]);
f.install("mine");
let file = f.config.join("registry/store/theme/mine/1.0.0/mine.toml");
std::fs::write(&file, theme_text("dark", "Impostor", "dark")).unwrap();
assert_eq!(f.session.theme_named("dark").map(|t| t.name), notescraft_themes::builtin("dark").map(|t| t.name));
std::fs::write(&file, "not [ toml").unwrap();
assert_eq!(f.session.user_themes().len(), 0);
}