mod attributes;
mod key;
mod value;
pub use attributes::*;
pub use key::*;
use topcoat_core::context::Cx;
pub use value::*;
use crate::{HtmlContext, PartsWriter};
#[derive(Debug, Clone)]
pub struct Attribute<K, V> {
key: K,
value: V,
}
impl<K, V> Attribute<K, V> {
#[inline]
pub fn new(key: K, value: V) -> Self {
Self { key, value }
}
}
pub trait AttributeViewParts {
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>);
}
impl<K, V> AttributeViewParts for Attribute<K, V>
where
K: AttributeKeyViewParts,
V: AttributeValueViewParts,
{
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
if self.value.attribute_present() {
parts.push_promoted_str_unescaped(&" ");
parts.in_context(HtmlContext::AttributeKey, |parts| {
self.key.into_view_parts(cx, parts);
});
parts.push_promoted_str_unescaped(&"=\"");
parts.in_context(HtmlContext::AttributeValue, |parts| {
self.value.into_view_parts(cx, parts);
});
parts.push_promoted_str_unescaped(&"\"");
}
}
}
impl<T> AttributeViewParts for Option<T>
where
T: AttributeViewParts,
{
#[inline]
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
if let Some(value) = self {
value.into_view_parts(cx, parts);
}
}
}
impl<T> AttributeViewParts for Vec<T>
where
T: AttributeViewParts,
{
#[inline]
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
for value in self {
value.into_view_parts(cx, parts);
}
}
}
impl<'b, T: ?Sized> AttributeViewParts for &&'b T
where
&'b T: AttributeViewParts,
{
#[inline]
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
(*self).into_view_parts(cx, parts);
}
}
macro_rules! impl_tuple {
($($ty:ident),+) => {
impl<$($ty),+> AttributeViewParts for ($($ty,)+)
where
$($ty: AttributeViewParts,)+
{
#[inline]
#[allow(non_snake_case)]
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
let ($($ty,)+) = self;
$($ty.into_view_parts(cx, parts);)+
}
}
};
}
impl_tuple!(T1);
impl_tuple!(T1, T2);
impl_tuple!(T1, T2, T3);
impl_tuple!(T1, T2, T3, T4);
impl_tuple!(T1, T2, T3, T4, T5);
impl_tuple!(T1, T2, T3, T4, T5, T6);
impl_tuple!(T1, T2, T3, T4, T5, T6, T7);
impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
#[cfg(test)]
mod tests {
use super::*;
use crate::{
buffer::ViewBufferScope,
internal::{block, build_sync},
};
fn render(attribute: impl AttributeViewParts) -> String {
let (html, _) = ViewBufferScope::scope_sync(|| {
let cx = Cx::default();
build_sync(|| block(&cx, |b| b.attributes(attribute))).render(&cx)
});
html
}
#[test]
fn renders_key_and_escaped_value() {
let rendered = render(Attribute::new("data-x", "a\"b<c"));
assert_eq!(rendered, " data-x=\"a"b<c\"");
}
#[test]
fn omits_absent_value() {
assert_eq!(render(Attribute::new("disabled", false)), "");
}
#[test]
fn dynamic_key_is_validated() {
let rendered = render(Attribute::new(String::from("data-x"), "y"));
assert_eq!(rendered, " data-x=\"y\"");
}
#[test]
#[should_panic(expected = "invalid attribute key")]
fn dynamic_key_rejects_breakout() {
render(Attribute::new(String::from("x onmouseover"), "y"));
}
}