#[cfg(all(feature = "leptos-0-8", not(feature = "leptos")))]
extern crate leptos_0_8 as leptos;
#[cfg(feature = "leptos")]
extern crate leptos_0_9 as leptos;
#[cfg(all(feature = "leptos-0-8", not(feature = "leptos")))]
extern crate tachys_0_2 as tachys;
#[cfg(feature = "leptos")]
extern crate tachys_0_3 as tachys;
use std::sync::Arc;
use leptos::prelude::*;
use mf2::leptos::{RequestI18n, Setup, install};
use mf2::{
ArgValue, Catalog, Compiled, Date, DateTime, Dir, Function, MsgId, Registry, Time, Tr, TrArgs,
functions, markup, markup_key, tr, tr_args1, tr_rich,
};
use tachys::html::attribute::AttributeValue;
use tachys::view::RenderHtml;
static FUNCTIONS: [(&str, &dyn Function); 2] = [
("integer", &functions::INTEGER),
("string", &functions::STRING),
];
static REGISTRY: Registry = Registry::new(&FUNCTIONS);
static LOCALES: &[(&str, Dir)] = &[("en", Dir::Ltr), ("ar", Dir::Rtl)];
fn installed() {
install(Setup::new(
®ISTRY,
&mf2::host_std::HOST,
0,
"en",
LOCALES,
));
}
fn compiled(source: &str, locale: &str) -> Compiled {
mf2::compile_str(source, locale).expect("the message compiles")
}
fn catalog_of(source: &str, locale: &str) -> Arc<Catalog> {
Arc::new(compiled(source, locale).catalog)
}
fn in_request<R>(catalog: &Arc<Catalog>, body: impl FnOnce() -> R) -> R {
installed();
let owner = Owner::new();
owner.with(|| {
RequestI18n::new(Arc::clone(catalog)).provide();
body()
})
}
const ID: MsgId = Compiled::ID;
fn kbd_handlers() -> Box<[(u64, Arc<dyn mf2::MarkupHandler>)]> {
[(
markup_key("kbd"),
markup(|children: AnyView| view! { <kbd>{children}</kbd> }),
)]
.into()
}
#[test]
fn a_simple_message_renders_its_text() {
let catalog = catalog_of("Save", "en");
let html = in_request(&catalog, || RenderHtml::to_html(tr(ID)));
assert_eq!(html, "Save");
}
#[test]
fn text_is_escaped_the_way_tachys_escapes_it() {
let catalog = catalog_of("a < b & c", "en");
let html = in_request(&catalog, || RenderHtml::to_html(tr(ID)));
assert_eq!(html, "a < b & c");
}
#[test]
fn a_pattern_formats_its_arguments() {
let catalog = catalog_of("You have {$n :integer} messages", "en");
let html = in_request(&catalog, || {
RenderHtml::to_html(tr_args1(ID, ArgValue::from(3)))
});
assert_eq!(html, "You have 3 messages");
}
#[test]
fn a_string_is_isolated_and_a_plain_string_is_not() {
let catalog = catalog_of("Hello, {$name}!", "en");
let (html, string, from, plain, displayed) = in_request(&catalog, || {
let d: TrArgs = tr_args1(ID, ArgValue::str_static("Ada"));
(
RenderHtml::to_html(d.clone()),
d.to_string(),
String::from(d.clone()),
d.to_plain_string(),
d.to_display_string(),
)
});
let isolated = "Hello, \u{2068}Ada\u{2069}!";
assert_eq!(html, isolated);
assert_eq!(string, isolated);
assert_eq!(from, isolated);
assert_eq!(displayed, isolated);
assert_eq!(plain, "Hello, Ada!");
}
#[test]
fn display_is_the_inherent_to_string_and_debug_shows_the_arguments() {
struct Opaque;
impl mf2::CustomValue for Opaque {}
let catalog = catalog_of("Hello, {$name}!", "en");
let (string, shown, padded, via_trait) = in_request(&catalog, || {
let d: TrArgs = tr_args1(ID, ArgValue::str_static("Ada"));
(
d.to_string(),
format!("{d}"),
format!("[{:<16}]", tr_args1(ID, ArgValue::str_static("Ada"))),
ToString::to_string(&d),
)
});
let isolated = "Hello, \u{2068}Ada\u{2069}!";
assert_eq!(string, isolated);
assert_eq!(shown, isolated);
assert_eq!(via_trait, isolated);
assert_eq!(padded, format!("[{isolated} ]"));
let d: TrArgs = tr_args1(ID, ArgValue::str_static("Ada"));
assert_eq!(
format!("{d:?}"),
"TrArgs { id: MsgId(0), args: [Str(\"Ada\")] }"
);
assert_eq!(format!("{:?}", tr(ID)), "Tr { id: MsgId(0) }");
let rich = tr_rich(d, kbd_handlers());
assert_eq!(
format!("{rich:?}"),
"TrRich { id: MsgId(0), args: [Str(\"Ada\")], handlers: 1 }"
);
assert_eq!(format!("{:?}", ArgValue::custom(Opaque)), "Custom(..)");
assert_eq!(
format!(
"{:?}",
tr_args1(
ID,
ArgValue::from(DateTime::floating(
Date::new(2026, 9, 28).expect("a date"),
Time::new(14, 5, 9, 7).expect("a time"),
))
)
),
"TrArgs { id: MsgId(0), args: [DateTime(DateTimeValue(2026-09-28T14:05:09.007))] }"
);
assert_eq!(
format!(
"{:?}",
mf2::TrDyn::new(ID, [("n", ArgValue::from(2.5)), ("m", ArgValue::from(-3))])
),
"TrDyn { id: MsgId(0), args: [(\"n\", Float(2.5)), (\"m\", Int(-3))] }"
);
}
#[test]
fn a_description_is_an_attribute_value() {
let catalog = catalog_of("Search \"everything\"", "en");
let mut buf = String::new();
in_request(&catalog, || {
AttributeValue::to_html(tr(ID), "placeholder", &mut buf);
});
assert_eq!(buf, " placeholder=\"Search "everything"\"");
}
#[test]
fn an_attribute_is_isolated_or_plain_by_its_name() {
let catalog = catalog_of("Hello, {$name}!", "en");
let attr = |key: &str| {
let mut buf = String::new();
in_request(&catalog, || {
let d: TrArgs = tr_args1(ID, ArgValue::str_static("Ada"));
AttributeValue::to_html(d, key, &mut buf);
});
buf
};
for key in ["value", "data-x", "href", "download", "VALUE", "Data-X"] {
assert_eq!(
attr(key),
format!(" {key}=\"Hello, Ada!\""),
"{key}= is plain"
);
}
for key in [
"title",
"aria-label",
"placeholder",
"alt",
"database",
"values",
] {
assert_eq!(
attr(key),
format!(" {key}=\"Hello, \u{2068}Ada\u{2069}!\""),
"{key}= is isolated"
);
}
}
#[test]
fn a_missing_catalog_renders_empty_text_and_does_not_panic() {
installed();
let html = Owner::new().with(|| RenderHtml::to_html(tr(MsgId::from_raw(7))));
assert_eq!(html, " ");
}
#[test]
fn markup_becomes_elements_in_the_message_s_own_order() {
let catalog = catalog_of("Press {#kbd}Esc{/kbd} to close", "en");
let html = in_request(&catalog, || {
RenderHtml::to_html(tr_rich(TrArgs::from(tr(ID)), kbd_handlers()))
});
assert_eq!(html, "Press <kbd>Esc<!></kbd> to close<!>");
}
#[test]
fn an_open_that_is_never_closed_closes_at_the_end_of_the_pattern() {
let catalog = catalog_of("Press {#kbd}Esc to close", "en");
let html = in_request(&catalog, || {
RenderHtml::to_html(tr_rich(TrArgs::from(tr(ID)), kbd_handlers()))
});
assert!(html.contains("<kbd"), "{html}");
assert!(html.contains("Esc to close"), "{html}");
}
#[test]
fn a_close_with_no_open_is_dropped() {
let catalog = catalog_of("Press Esc{/kbd} to close", "en");
let html = in_request(&catalog, || {
RenderHtml::to_html(tr_rich(TrArgs::from(tr(ID)), kbd_handlers()))
});
assert!(!html.contains("<kbd"), "{html}");
assert!(html.contains("Press Esc"), "{html}");
}
#[test]
fn a_rich_message_with_no_handler_renders_only_its_text() {
let catalog = catalog_of("Press {#kbd}Esc{/kbd} to close", "en");
let html = in_request(&catalog, || {
RenderHtml::to_html(tr_rich(TrArgs::from(tr(ID)), Vec::new().into()))
});
assert_eq!(html, "Press Esc to close<!>");
}
#[test]
fn a_text_prop_captures_the_request_s_catalog() {
let catalog = catalog_of("Inbox", "en");
let prop: TextProp = in_request(&catalog, || TextProp::from(tr(ID)));
assert_eq!(prop.get().as_str(), "Inbox");
}
#[test]
fn the_description_of_a_call_site_is_still_four_bytes() {
assert_eq!(size_of::<Tr>(), 4);
}
#[test]
fn the_switcher_is_a_get_form_applied_by_a_button() {
use mf2::leptos::{LocaleOption, LocaleSwitcher};
let catalog = catalog_of("Save", "ar");
let html = in_request(&catalog, || {
view! {
<LocaleSwitcher label="Language" button="Apply">
<LocaleOption tag="en">"English"</LocaleOption>
<LocaleOption tag="ar">"العربية"</LocaleOption>
</LocaleSwitcher>
}
.to_html()
});
let form_tag = &html[..html.find('>').expect("a tag")];
assert!(form_tag.starts_with("<form "), "{html}");
assert!(form_tag.contains(" method=\"get\""), "{html}");
assert!(
form_tag.contains(" class=\"mf2-locale-switcher\""),
"{html}"
);
let label = html.find("<label>").expect("a label with no `for`");
let select = html
.find("<select name=\"lang\"")
.expect("a select named `lang`");
let label_end = html.find("</label>").expect("the label closes");
assert!(
label < select && select < label_end,
"the select is inside its label: {html}"
);
assert!(html.contains("<span>Language</span>"), "{html}");
assert!(
html.contains("<button type=\"submit\">Apply</button>"),
"{html}"
);
assert!(
html.contains("<option value=\"en\" lang=\"en\">English</option>"),
"{html}"
);
assert!(
html.contains("<option value=\"ar\" lang=\"ar\" selected>العربية</option>"),
"the page's locale is selected in the markup: {html}"
);
assert!(!html.contains(" id="), "no fixed id: {html}");
assert!(!html.contains(" for="), "{html}");
}
#[test]
fn a_signal_of_any_argument_type_is_read_at_format_time() {
use mf2::IntoArg;
let catalog = catalog_of(
".input {$flag :string} .match $flag true {{{$n :integer} on}} * {{{$n :integer} off}}",
"en",
);
let (first, second) = in_request(&catalog, || {
let n = RwSignal::new(u64::MAX);
let flag = RwSignal::new(true);
let d = mf2::tr_args2(ID, flag.into_arg(), n.into_arg());
let first = d.to_plain_string();
n.set(3);
flag.set(false);
(first, d.to_plain_string())
});
assert_eq!(first, "18446744073709551615 on");
assert_eq!(second, "3 off");
}