diff --git a/cot-core/src/html.rs b/cot-core/src/html.rs
index bdbd8ce9..7264d59d 100644
--- a/cot-core/src/html.rs
+++ b/cot-core/src/html.rs
@@ -189,6 +189,37 @@ impl HtmlTag {
input
}
+ /// Creates a new `HtmlTag` instance for a datalist element.
+ ///
+ /// # Examples
+ /// ```
+ /// use cot::html::HtmlTag;
+ /// let data_list = HtmlTag::data_list(vec!["Option 1", "Option 2"], "my-datalist");
+ /// let rendered = data_list.render();
+ /// ```
+ #[must_use]
+ pub fn data_list(list: I, id: &str) -> Self
+ where
+ I: IntoIterator,
+ S: AsRef,
+ {
+ let mut data_list = Self::new("datalist");
+ data_list.attr("id", id);
+
+ let mut options: Vec = Vec::new();
+
+ for item in list {
+ let l = item.as_ref();
+ let mut option = HtmlTag::new("option");
+ option.attr("value", l);
+ option.push_str(l);
+ options.push(HtmlNode::Tag(option));
+ }
+
+ data_list.children = options;
+ data_list
+ }
+
/// Adds an attribute to the HTML tag.
///
/// # Safety
diff --git a/cot/src/form/fields.rs b/cot/src/form/fields.rs
index 1fcb4f14..8375bdcb 100644
--- a/cot/src/form/fields.rs
+++ b/cot/src/form/fields.rs
@@ -10,7 +10,7 @@ use std::num::{
};
use askama::filters::HtmlSafe;
-pub use attrs::Step;
+pub use attrs::{AutoCapitalize, AutoComplete, Dir, List, Step};
pub use chrono::{
DateField, DateFieldOptions, DateTimeField, DateTimeFieldOptions, DateTimeWithTimezoneField,
DateTimeWithTimezoneFieldOptions, TimeField, TimeFieldOptions,
@@ -73,11 +73,39 @@ pub(crate) use impl_form_field;
impl_form_field!(StringField, StringFieldOptions, "a string");
/// Custom options for a [`StringField`].
-#[derive(Debug, Default, Copy, Clone)]
+#[derive(Debug, Default, Clone)]
pub struct StringFieldOptions {
/// The maximum length of the field. Used to set the `maxlength` attribute
/// in the HTML input element.
pub max_length: Option,
+ /// The minimum length of the field. Used to set the `minlength` attribute
+ /// in the HTML input element.
+ pub min_length: Option,
+ /// The size of the field. Used to set the `size` attribute in the HTML
+ /// input element.
+ pub size: Option,
+ /// Corresponds to the [`AutoCapitalize`] attribute in the HTML input
+ /// element.
+ pub autocapitalize: Option,
+ /// Corresponds to the [`AutoComplete`] attribute in the HTML input element.
+ pub autocomplete: Option,
+ /// The direction of the text input, which can be set to `ltr`
+ /// (left-to-right) or `rtl` (right-to-left). This corresponds to the
+ /// [`Dir`] attribute in the HTML input element.
+ pub dir: Option,
+ /// The [`dirname`] attribute in the HTML input element, which is used to
+ /// specify the direction of the text input.
+ ///
+ /// [`dirname`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#dirname
+ pub dirname: Option,
+ /// A [`List`] of options for the `datalist` element, which can be used to
+ /// provide predefined options for the input.
+ pub list: Option,
+ /// The placeholder text for the input field, which is displayed when the
+ /// field is empty.
+ pub placeholder: Option,
+ /// If `true`, the field is read-only and cannot be modified by the user.
+ pub readonly: Option,
}
impl Display for StringField {
@@ -91,6 +119,42 @@ impl Display for StringField {
if let Some(max_length) = self.custom_options.max_length {
tag.attr("maxlength", max_length.to_string());
}
+ if let Some(min_length) = self.custom_options.min_length {
+ tag.attr("minlength", min_length.to_string());
+ }
+
+ if let Some(placeholder) = &self.custom_options.placeholder {
+ tag.attr("placeholder", placeholder);
+ }
+ if let Some(readonly) = self.custom_options.readonly
+ && readonly
+ {
+ tag.bool_attr("readonly");
+ }
+ if let Some(autocomplete) = &self.custom_options.autocomplete {
+ tag.attr("autocomplete", autocomplete.to_string());
+ }
+
+ if let Some(size) = self.custom_options.size {
+ tag.attr("size", size.to_string());
+ }
+
+ if let Some(dir) = &self.custom_options.dir {
+ tag.attr("dir", dir.as_str());
+ }
+
+ if let Some(dirname) = &self.custom_options.dirname {
+ tag.attr("dirname", dirname);
+ }
+
+ if let Some(list) = &self.custom_options.list {
+ let list_id = format!("__{}_datalist", self.id());
+ tag.attr("list", &list_id);
+
+ let data_list = HtmlTag::data_list(list.clone(), &list_id);
+ tag.push_tag(data_list);
+ }
+
if let Some(value) = &self.value {
tag.attr("value", value);
}
@@ -143,11 +207,26 @@ impl AsFormField for LimitedString {
impl_form_field!(PasswordField, PasswordFieldOptions, "a password");
/// Custom options for a [`PasswordField`].
-#[derive(Debug, Default, Copy, Clone)]
+#[derive(Debug, Default, Clone)]
pub struct PasswordFieldOptions {
/// The maximum length of the field. Used to set the `maxlength` attribute
/// in the HTML input element.
pub max_length: Option,
+ /// The minimum length of the field. Used to set the `minlength` attribute
+ /// in the HTML input element.
+ pub min_length: Option,
+ /// The size of the field. Used to set the [`size`] attribute in the HTML
+ /// input element.
+ ///
+ /// [`size`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#size
+ pub size: Option,
+ /// Corresponds to the [`AutoComplete`] attribute in the HTML input element.
+ pub autocomplete: Option,
+ /// The placeholder text for the input field, which is displayed when the
+ /// field is empty.
+ pub placeholder: Option,
+ /// If `true`, the field is read-only and cannot be modified by the user.
+ pub readonly: Option,
}
impl Display for PasswordField {
@@ -161,6 +240,26 @@ impl Display for PasswordField {
if let Some(max_length) = self.custom_options.max_length {
tag.attr("maxlength", max_length.to_string());
}
+
+ if let Some(min_length) = self.custom_options.min_length {
+ tag.attr("minlength", min_length.to_string());
+ }
+ if let Some(placeholder) = &self.custom_options.placeholder {
+ tag.attr("placeholder", placeholder);
+ }
+ if let Some(readonly) = self.custom_options.readonly
+ && readonly
+ {
+ tag.bool_attr("readonly");
+ }
+ if let Some(autocomplete) = &self.custom_options.autocomplete {
+ tag.attr("autocomplete", autocomplete.to_string());
+ }
+
+ if let Some(size) = self.custom_options.size {
+ tag.attr("size", size.to_string());
+ }
+
// we don't set the value attribute for password fields
// to avoid leaking the password in the HTML
@@ -218,7 +317,7 @@ impl AsFormField for PasswordHash {
impl_form_field!(EmailField, EmailFieldOptions, "an email");
/// Custom options for [`EmailField`]
-#[derive(Debug, Default, Copy, Clone)]
+#[derive(Debug, Default, Clone)]
pub struct EmailFieldOptions {
/// The maximum length of the field used to set the `maxlength` attribute
/// in the HTML input element.
@@ -226,11 +325,38 @@ pub struct EmailFieldOptions {
/// The minimum length of the field used to set the `minlength` attribute
/// in the HTML input element.
pub min_length: Option,
+ /// The size of the field used to set the [`size`] attribute in the HTML
+ /// input element.
+ ///
+ /// [`size`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#size
+ pub size: Option,
+ /// Corresponds to the [`AutoCapitalize`] attribute in the HTML input
+ /// element.
+ pub autocomplete: Option,
+ /// The direction of the text input, which can be set to `ltr`
+ /// (left-to-right) or `rtl` (right-to-left). This corresponds to the
+ /// [`Dir`] attribute in the HTML input element.
+ pub dir: Option,
+ /// The [`dirname`] attribute in the HTML input element, which is used to
+ /// specify the direction of the text input.
+ ///
+ /// [`dirname`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#dirname
+ pub dirname: Option,
+ /// A [`List`] of options for the `datalist` element, which can be used to
+ /// provide predefined options for the input.
+ pub list: Option,
+ /// The placeholder text for the input field, which is displayed when the
+ /// field is empty.
+ pub placeholder: Option,
+ /// If `true`, the field is read-only and cannot be modified by the user.
+ pub readonly: Option,
}
impl Display for EmailField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut tag = HtmlTag::input("email");
+ let mut data_list: Option = None;
+
tag.attr("name", self.id());
tag.attr("id", self.id());
if self.options.required {
@@ -242,10 +368,49 @@ impl Display for EmailField {
if let Some(min_length) = self.custom_options.min_length {
tag.attr("minlength", min_length.to_string());
}
+ if let Some(placeholder) = &self.custom_options.placeholder {
+ tag.attr("placeholder", placeholder);
+ }
+ if let Some(readonly) = self.custom_options.readonly
+ && readonly
+ {
+ tag.bool_attr("readonly");
+ }
+ if let Some(autocomplete) = &self.custom_options.autocomplete {
+ tag.attr("autocomplete", autocomplete.to_string());
+ }
+
+ if let Some(size) = self.custom_options.size {
+ tag.attr("size", size.to_string());
+ }
+
+ if let Some(dir) = &self.custom_options.dir {
+ tag.attr("dir", dir.as_str());
+ }
+
+ if let Some(dirname) = &self.custom_options.dirname {
+ tag.attr("dirname", dirname);
+ }
+
+ if let Some(list) = &self.custom_options.list {
+ let list_id = format!("__{}_datalist", self.id());
+ tag.attr("list", &list_id);
+
+ data_list = Some(HtmlTag::data_list(list.clone(), &list_id));
+ }
+
if let Some(value) = &self.value {
tag.attr("value", value);
}
+ if let Some(data_list) = data_list {
+ let mut wrapper = HtmlTag::new("div");
+ wrapper
+ .attr("id", format!("__{}_datalist_wrapper", self.id()))
+ .push_tag(tag)
+ .push_tag(data_list);
+ return write!(f, "{}", wrapper.render());
+ }
write!(f, "{}", tag.render())
}
}
@@ -290,10 +455,10 @@ impl AsFormField for Email {
impl HtmlSafe for EmailField {}
-impl_form_field!(IntegerField, IntegerFieldOptions, "an integer", T: Integer);
+impl_form_field!(IntegerField, IntegerFieldOptions, "an integer", T: Integer + Display);
/// Custom options for a [`IntegerField`].
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
pub struct IntegerFieldOptions {
/// The minimum value of the field. Used to set the `min` attribute in the
/// HTML input element.
@@ -301,6 +466,14 @@ pub struct IntegerFieldOptions {
/// The maximum value of the field. Used to set the `max` attribute in the
/// HTML input element.
pub max: Option,
+ /// The placeholder text for the input field, which is displayed when the
+ /// field is empty.
+ pub placeholder: Option,
+ /// If `true`, the field is read-only and cannot be modified by the user.
+ pub readonly: Option,
+ /// The step size for the field. Used to set the [`Step`] attribute in the
+ /// HTML input element.
+ pub step: Option>,
}
impl Default for IntegerFieldOptions {
@@ -308,11 +481,14 @@ impl Default for IntegerFieldOptions {
Self {
min: T::MIN,
max: T::MAX,
+ placeholder: None,
+ readonly: None,
+ step: None,
}
}
}
-impl Display for IntegerField {
+impl Display for IntegerField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut tag = HtmlTag::input("number");
tag.attr("name", self.id());
@@ -326,6 +502,19 @@ impl Display for IntegerField {
if let Some(max) = &self.custom_options.max {
tag.attr("max", max.to_string());
}
+
+ if let Some(placeholder) = &self.custom_options.placeholder {
+ tag.attr("placeholder", placeholder);
+ }
+ if let Some(readonly) = self.custom_options.readonly
+ && readonly
+ {
+ tag.bool_attr("readonly");
+ }
+ if let Some(step) = &self.custom_options.step {
+ tag.attr("step", step.to_string());
+ }
+
if let Some(value) = &self.value {
tag.attr("value", value);
}
@@ -334,7 +523,7 @@ impl Display for IntegerField {
}
}
-impl HtmlSafe for IntegerField {}
+impl HtmlSafe for IntegerField {}
/// A trait for numerical types that optionally have minimum and maximum values.
///
@@ -647,10 +836,10 @@ pub(crate) fn check_required(field: &T) -> Result<&str, FormFieldV
}
}
-impl_form_field!(FloatField, FloatFieldOptions, "a float", T: Float);
+impl_form_field!(FloatField, FloatFieldOptions, "a float", T: Float + Display);
/// Custom options for a [`FloatField`].
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
pub struct FloatFieldOptions {
/// The minimum value of the field. Used to set the `min` attribute in the
/// HTML input element.
@@ -658,6 +847,14 @@ pub struct FloatFieldOptions {
/// The maximum value of the field. Used to set the `max` attribute in the
/// HTML input element.
pub max: Option,
+ /// The placeholder text for the input field, which is displayed when the
+ /// field is empty.
+ pub placeholder: Option,
+ /// If `true`, the field is read-only and cannot be modified by the user.
+ pub readonly: Option,
+ /// The step size for the field. Used to set the [`Step`] attribute in the
+ /// HTML input element.
+ pub step: Option>,
}
impl Default for FloatFieldOptions {
@@ -665,11 +862,14 @@ impl Default for FloatFieldOptions {
Self {
min: T::MIN,
max: T::MAX,
+ placeholder: None,
+ readonly: None,
+ step: None,
}
}
}
-impl Display for FloatField {
+impl Display for FloatField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut tag: HtmlTag = HtmlTag::input("number");
tag.attr("name", self.id());
@@ -684,6 +884,17 @@ impl Display for FloatField {
if let Some(max) = &self.custom_options.max {
tag.attr("max", max.to_string());
}
+ if let Some(placeholder) = &self.custom_options.placeholder {
+ tag.attr("placeholder", placeholder);
+ }
+ if let Some(readonly) = self.custom_options.readonly
+ && readonly
+ {
+ tag.bool_attr("readonly");
+ }
+ if let Some(step) = &self.custom_options.step {
+ tag.attr("step", step.to_string());
+ }
if let Some(value) = &self.value {
tag.attr("value", value);
}
@@ -692,7 +903,7 @@ impl Display for FloatField {
}
}
-impl HtmlSafe for FloatField {}
+impl HtmlSafe for FloatField {}
/// A trait for types that can be represented as a float.
///
@@ -777,19 +988,86 @@ impl_float_as_form_field!(f64);
impl_form_field!(UrlField, UrlFieldOptions, "a URL");
/// Custom options for a [`UrlField`].
-#[derive(Debug, Default, Copy, Clone)]
-pub struct UrlFieldOptions;
+#[derive(Debug, Default, Clone)]
+pub struct UrlFieldOptions {
+ /// The maximum length of the field. Used to set the `maxlength` attribute
+ /// in the HTML input element.
+ pub max_length: Option,
+ /// The minimum length of the field. Used to set the `minlength` attribute
+ /// in the HTML input element.
+ pub min_length: Option,
+ /// The size of the field. Used to set the [`size`]attribute in the HTML
+ /// input element.
+ ///
+ /// [`size`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#size
+ pub size: Option,
+ /// The [`List`] of options for the `datalist` element, which can be used to
+ /// provide predefined options for the input.
+ pub list: Option,
+ /// The direction of the text input, which can be set to `ltr`
+ /// (left-to-right) or `rtl` (right-to-left). This corresponds to the
+ /// [`Dir`] attribute in the HTML input element.
+ pub dir: Option,
+ /// The [`dirname`] attribute in the HTML input element, which is used to
+ /// specify the direction of the text input.
+ ///
+ /// [`dirname`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#dirname
+ pub dirname: Option,
+ /// The [`AutoComplete`] attribute in the HTML input element, which is used
+ /// to specify how the browser should handle autocomplete for the input.
+ pub autocomplete: Option,
+ /// The placeholder text for the input field, which is displayed when the
+ /// field is empty.
+ pub placeholder: Option,
+ /// If `true`, the field is read-only and cannot be modified by the user.
+ pub readonly: Option,
+}
impl Display for UrlField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- // no custom options
- let _ = self.custom_options;
let mut tag = HtmlTag::input("url");
tag.attr("name", self.id());
tag.attr("id", self.id());
if self.options.required {
tag.bool_attr("required");
}
+ if let Some(max_length) = self.custom_options.max_length {
+ tag.attr("maxlength", max_length.to_string());
+ }
+ if let Some(min_length) = self.custom_options.min_length {
+ tag.attr("minlength", min_length.to_string());
+ }
+ if let Some(placeholder) = &self.custom_options.placeholder {
+ tag.attr("placeholder", placeholder);
+ }
+ if let Some(readonly) = self.custom_options.readonly
+ && readonly
+ {
+ tag.bool_attr("readonly");
+ }
+ if let Some(autocomplete) = &self.custom_options.autocomplete {
+ tag.attr("autocomplete", autocomplete.to_string());
+ }
+
+ if let Some(size) = self.custom_options.size {
+ tag.attr("size", size.to_string());
+ }
+
+ if let Some(dir) = &self.custom_options.dir {
+ tag.attr("dir", dir.as_str());
+ }
+
+ if let Some(dirname) = &self.custom_options.dirname {
+ tag.attr("dirname", dirname);
+ }
+
+ if let Some(list) = &self.custom_options.list {
+ let list_id = format!("__{}_datalist", self.id());
+ tag.attr("list", &list_id);
+
+ let data_list = HtmlTag::data_list(list, &list_id);
+ tag.push_tag(data_list);
+ }
if let Some(value) = &self.value {
tag.attr("value", value);
}
@@ -832,12 +1110,30 @@ mod tests {
},
StringFieldOptions {
max_length: Some(10),
+ min_length: Some(5),
+ size: Some(15),
+ autocapitalize: Some(AutoCapitalize::Words),
+ autocomplete: Some(AutoComplete::Value("foo bar".to_string())),
+ dir: Some(Dir::Ltr),
+ dirname: Some("dir".to_string()),
+ list: Some(List::new(["bar", "baz"])),
+ placeholder: Some("Enter text".to_string()),
+ readonly: Some(true),
},
);
let html = field.to_string();
assert!(html.contains("type=\"text\""));
+ assert!(html.contains("name=\"test\""));
+ assert!(html.contains("id=\"test\""));
assert!(html.contains("required"));
assert!(html.contains("maxlength=\"10\""));
+ assert!(html.contains("minlength=\"5\""));
+ assert!(html.contains("size=\"15\""));
+ assert!(html.contains("autocomplete=\"foo bar\""));
+ assert!(html.contains("dir=\"ltr\""));
+ assert!(html.contains("dirname=\"dir\""));
+ assert!(html.contains("placeholder=\"Enter text\""));
+ assert!(html.contains("readonly"));
}
#[cot::test]
@@ -850,6 +1146,7 @@ mod tests {
},
StringFieldOptions {
max_length: Some(10),
+ ..Default::default()
},
);
field
@@ -870,6 +1167,7 @@ mod tests {
},
StringFieldOptions {
max_length: Some(10),
+ ..Default::default()
},
);
field.set_value(FormFieldValue::new_text("")).await.unwrap();
@@ -887,13 +1185,22 @@ mod tests {
},
PasswordFieldOptions {
max_length: Some(10),
+ min_length: Some(5),
+ size: Some(15),
+ autocomplete: Some(AutoComplete::Value("foo bar".to_string())),
+ placeholder: Some("Enter password".to_string()),
+ readonly: Some(false),
},
);
let html = field.to_string();
assert!(html.contains("type=\"password\""));
assert!(html.contains("required"));
assert!(html.contains("maxlength=\"10\""));
+ assert!(html.contains("minlength=\"5\""));
+ assert!(html.contains("autocomplete=\"foo bar\""));
+ assert!(html.contains("placeholder=\"Enter password\""));
}
+
#[cot::test]
async fn password_field_clean_value() {
let mut field = PasswordField::with_options(
@@ -904,6 +1211,7 @@ mod tests {
},
PasswordFieldOptions {
max_length: Some(10),
+ ..Default::default()
},
);
field
@@ -923,18 +1231,33 @@ mod tests {
required: true,
},
EmailFieldOptions {
- min_length: Some(10),
- max_length: Some(50),
+ max_length: Some(10),
+ min_length: Some(5),
+ size: Some(15),
+ autocomplete: Some(AutoComplete::Value("foo bar".to_string())),
+ dir: Some(Dir::Ltr),
+ dirname: Some("dir".to_string()),
+ list: Some(List::new(["foo@example.com", "baz@example.com"])),
+ placeholder: Some("Enter text".to_string()),
+ readonly: Some(true),
},
);
let html = field.to_string();
+
assert!(html.contains("type=\"email\""));
assert!(html.contains("required"));
- assert!(html.contains("minlength=\"10\""));
- assert!(html.contains("maxlength=\"50\""));
+ assert!(html.contains("minlength=\"5\""));
+ assert!(html.contains("maxlength=\"10\""));
assert!(html.contains("name=\"test_id\""));
assert!(html.contains("id=\"test_id\""));
+ assert!(html.contains("placeholder=\"Enter text\""));
+ assert!(html.contains("readonly"));
+ assert!(html.contains("autocomplete=\"foo bar\""));
+ assert!(html.contains("dir=\"ltr\""));
+ assert!(html.contains("dirname=\"dir\""));
+ assert!(html.contains("list=\"__test_id_datalist\""));
+ assert!(html.contains(r#""#));
}
#[cot::test]
@@ -948,6 +1271,7 @@ mod tests {
EmailFieldOptions {
min_length: Some(10),
max_length: Some(50),
+ ..Default::default()
},
);
@@ -971,6 +1295,7 @@ mod tests {
EmailFieldOptions {
min_length: Some(10),
max_length: Some(50),
+ ..Default::default()
},
);
@@ -994,6 +1319,7 @@ mod tests {
EmailFieldOptions {
min_length: Some(5),
max_length: Some(10),
+ ..Default::default()
},
);
@@ -1020,6 +1346,7 @@ mod tests {
EmailFieldOptions {
min_length: Some(5),
max_length: Some(10),
+ ..Default::default()
},
);
@@ -1046,6 +1373,7 @@ mod tests {
EmailFieldOptions {
min_length: Some(50),
max_length: Some(10),
+ ..Default::default()
},
);
@@ -1073,13 +1401,22 @@ mod tests {
IntegerFieldOptions {
min: Some(1),
max: Some(10),
+ placeholder: Some("Enter text".to_string()),
+ readonly: Some(false),
+ step: Some(Step::Value(10)),
},
);
let html = field.to_string();
+
assert!(html.contains("type=\"number\""));
assert!(html.contains("required"));
assert!(html.contains("min=\"1\""));
assert!(html.contains("max=\"10\""));
+ assert!(html.contains("step=\"10\""));
+ assert!(html.contains("placeholder=\"Enter text\""));
+ assert!(html.contains("name=\"test\""));
+ assert!(html.contains("id=\"test\""));
+ assert!(!html.contains("readonly"));
}
#[cot::test]
@@ -1093,6 +1430,7 @@ mod tests {
IntegerFieldOptions {
min: Some(1),
max: Some(10),
+ ..Default::default()
},
);
field
@@ -1114,6 +1452,7 @@ mod tests {
IntegerFieldOptions {
min: Some(10),
max: Some(50),
+ ..Default::default()
},
);
field
@@ -1138,6 +1477,7 @@ mod tests {
IntegerFieldOptions {
min: Some(10),
max: Some(50),
+ ..Default::default()
},
);
field
@@ -1218,13 +1558,22 @@ mod tests {
FloatFieldOptions {
min: Some(1.5),
max: Some(10.7),
+ placeholder: Some("Enter text".to_string()),
+ readonly: Some(true),
+ step: Some(Step::Any),
},
);
let html = field.to_string();
+
assert!(html.contains("type=\"number\""));
assert!(html.contains("required"));
assert!(html.contains("min=\"1.5\""));
assert!(html.contains("max=\"10.7\""));
+ assert!(html.contains("step=\"any\""));
+ assert!(html.contains("placeholder=\"Enter text\""));
+ assert!(html.contains("name=\"test\""));
+ assert!(html.contains("id=\"test\""));
+ assert!(html.contains("readonly"));
}
#[cot::test]
@@ -1239,6 +1588,7 @@ mod tests {
FloatFieldOptions {
min: Some(1.0),
max: Some(10.0),
+ ..Default::default()
},
);
field
@@ -1260,6 +1610,7 @@ mod tests {
FloatFieldOptions {
min: Some(5.0),
max: Some(10.0),
+ ..Default::default()
},
);
field
@@ -1284,6 +1635,7 @@ mod tests {
FloatFieldOptions {
min: Some(5.0),
max: Some(10.0),
+ ..Default::default()
},
);
field
@@ -1308,6 +1660,7 @@ mod tests {
FloatFieldOptions {
min: Some(1.0),
max: Some(10.0),
+ ..Default::default()
},
);
let bad_inputs = ["NaN", "inf"];
@@ -1338,6 +1691,7 @@ mod tests {
FloatFieldOptions {
min: Some(1.0),
max: Some(10.0),
+ ..Default::default()
},
);
field.set_value(FormFieldValue::new_text("")).await.unwrap();
@@ -1353,12 +1707,24 @@ mod tests {
name: "test".to_owned(),
required: true,
},
- UrlFieldOptions,
+ UrlFieldOptions {
+ max_length: Some(100),
+ min_length: Some(5),
+ size: Some(30),
+ list: Some(List::new(["https://example.com"])),
+ dir: Some(Dir::Ltr),
+ dirname: Some("dir".to_owned()),
+ autocomplete: Some(AutoComplete::Value("url".to_owned())),
+ placeholder: Some("Enter URL".to_owned()),
+ readonly: Some(true),
+ },
);
+
field
.set_value(FormFieldValue::new_text("https://example.com"))
.await
.unwrap();
+
let value = Url::clean_value(&field).unwrap();
assert_eq!(
value.as_str(),
@@ -1374,16 +1740,40 @@ mod tests {
name: "url".to_owned(),
required: true,
},
- UrlFieldOptions,
+ UrlFieldOptions {
+ max_length: Some(120),
+ min_length: Some(10),
+ size: Some(40),
+ list: Some(List::new(["https://one.com", "https://two.com"])),
+ dir: Some(Dir::Ltr),
+ dirname: Some("lang".to_owned()),
+ autocomplete: Some(AutoComplete::Value("url".to_owned())),
+ placeholder: Some("Paste link".to_owned()),
+ readonly: Some(true),
+ },
);
+
field
.set_value(FormFieldValue::new_text("http://example.com"))
.await
.unwrap();
+
let html = field.to_string();
+
assert!(html.contains("type=\"url\""));
+ assert!(html.contains("id=\"id_url\""));
+ assert!(html.contains("name=\"id_url\""));
assert!(html.contains("required"));
assert!(html.contains("value=\"http://example.com\""));
+ assert!(html.contains("maxlength=\"120\""));
+ assert!(html.contains("minlength=\"10\""));
+ assert!(html.contains("size=\"40\""));
+ assert!(html.contains("placeholder=\"Paste link\""));
+ assert!(html.contains("autocomplete=\"url\""));
+ assert!(html.contains("dir=\"ltr\""));
+ assert!(html.contains("dirname=\"lang\""));
+ assert!(html.contains("readonly"));
+ assert!(html.contains("