| 233 |
233 |
|
/// name is what submits and is fixed by the description; the id has to be
|
| 234 |
234 |
|
/// unique in the document and so carries [`Filling::id_prefix`] when a form
|
| 235 |
235 |
|
/// appears more than once.
|
|
236 |
+ |
/// The `accept` attribute, from the description's accept list.
|
|
237 |
+ |
///
|
|
238 |
+ |
/// makeover-layout 0.31.0. The list is comma-joined because that is the
|
|
239 |
+ |
/// attribute's own format, and each entry writes itself: a family is its
|
|
240 |
+ |
/// wildcard media type, a media type is itself, a suffix is itself with its
|
|
241 |
+ |
/// leading dot. Nothing is normalised on the way through -- `.tar.gz` is two
|
|
242 |
+ |
/// dots and the browser is fine with it.
|
|
243 |
+ |
///
|
|
244 |
+ |
/// An empty list emits no attribute at all, which is the browser's own "any
|
|
245 |
+ |
/// file" and is what the description means by listing nothing. Emitting
|
|
246 |
+ |
/// `accept=""` instead would be a filter that matches nothing on some browsers
|
|
247 |
+ |
/// and everything on others.
|
|
248 |
+ |
///
|
|
249 |
+ |
/// It is a filter and not a guarantee, on the browser's side as much as here:
|
|
250 |
+ |
/// the picker keeps an "All Files" escape and the user may take it. Whoever
|
|
251 |
+ |
/// validated still validates.
|
|
252 |
+ |
fn push_accept(out: &mut String, field: &Field<'_>) {
|
|
253 |
+ |
if field.accept.is_empty() {
|
|
254 |
+ |
return;
|
|
255 |
+ |
}
|
|
256 |
+ |
out.push_str(" accept=\"");
|
|
257 |
+ |
for (index, one) in field.accept.iter().enumerate() {
|
|
258 |
+ |
if index > 0 {
|
|
259 |
+ |
out.push(',');
|
|
260 |
+ |
}
|
|
261 |
+ |
escape_into(one.as_str(), out);
|
|
262 |
+ |
}
|
|
263 |
+ |
out.push('"');
|
|
264 |
+ |
}
|
|
265 |
+ |
|
| 236 |
266 |
|
fn push_control_attributes(out: &mut String, field: &Field<'_>, id: &str, name: &str) {
|
| 237 |
267 |
|
let _ = write!(out, " id=\"{id}\" name=\"");
|
| 238 |
268 |
|
escape_into(name, out);
|
| 540 |
570 |
|
push_class(out, "field", opts);
|
| 541 |
571 |
|
out.push('"');
|
| 542 |
572 |
|
push_control_attributes(out, field, &id, field.name);
|
|
573 |
+ |
push_accept(out, field);
|
|
574 |
+ |
if field.multiple {
|
|
575 |
+ |
out.push_str(" multiple");
|
|
576 |
+ |
}
|
| 543 |
577 |
|
out.push('>');
|
| 544 |
578 |
|
}
|
| 545 |
579 |
|
kind => {
|
| 664 |
698 |
|
#[cfg(test)]
|
| 665 |
699 |
|
mod tests {
|
| 666 |
700 |
|
use super::*;
|
|
701 |
+ |
use makeover_layout::{Accepted, Family};
|
| 667 |
702 |
|
|
| 668 |
703 |
|
fn field(kind: FieldKind) -> Field<'static> {
|
| 669 |
704 |
|
Field::new(kind, "title", "Title")
|
| 1266 |
1301 |
|
|
| 1267 |
1302 |
|
#[test]
|
| 1268 |
1303 |
|
fn a_file_field_is_a_file_input() {
|
| 1269 |
|
- |
// `844b5ae0`. It carries no `accept`, which is measured rather than
|
| 1270 |
|
- |
// deferred: zero sites in either app.
|
|
1304 |
+ |
// `844b5ae0`. A field that takes any file emits no `accept` at all,
|
|
1305 |
+ |
// which is the browser's own "any file". `accept=""` is a filter that
|
|
1306 |
+ |
// means nothing on one browser and everything on another.
|
| 1271 |
1307 |
|
let html = field_html(
|
| 1272 |
1308 |
|
&Field::new(FieldKind::File, "attachment", "Attachment"),
|
| 1273 |
1309 |
|
&Filling::default(),
|
| 1275 |
1311 |
|
);
|
| 1276 |
1312 |
|
assert!(html.contains(r#"type="file""#));
|
| 1277 |
1313 |
|
assert!(!html.contains("accept="));
|
|
1314 |
+ |
assert!(!html.contains("multiple"));
|
| 1278 |
1315 |
|
// And it never carries a value: a file input's value is not settable
|
| 1279 |
1316 |
|
// from markup, and the browser refuses one that tries.
|
| 1280 |
1317 |
|
assert!(!html.contains("value="));
|
| 1281 |
1318 |
|
}
|
| 1282 |
1319 |
|
|
|
1320 |
+ |
#[test]
|
|
1321 |
+ |
fn an_accept_list_is_comma_joined_in_the_attributes_own_format() {
|
|
1322 |
+ |
// `f7261a5a`, makeover-layout 0.31.0. Each entry writes itself: a
|
|
1323 |
+ |
// family is its wildcard, a media type is itself, a suffix keeps its
|
|
1324 |
+ |
// leading dot and however many more it has.
|
|
1325 |
+ |
const MIXED: &[Accepted<'_>] = &[
|
|
1326 |
+ |
Accepted::Family(Family::Image),
|
|
1327 |
+ |
Accepted::Type("text/csv"),
|
|
1328 |
+ |
Accepted::Suffix(".tar.gz"),
|
|
1329 |
+ |
];
|
|
1330 |
+ |
let html = field_html(
|
|
1331 |
+ |
&Field {
|
|
1332 |
+ |
multiple: true,
|
|
1333 |
+ |
..Field::upload("drop", "Drop files", MIXED)
|
|
1334 |
+ |
},
|
|
1335 |
+ |
&Filling::default(),
|
|
1336 |
+ |
&Emit::default(),
|
|
1337 |
+ |
);
|
|
1338 |
+ |
assert!(
|
|
1339 |
+ |
html.contains(r#"accept="image/*,text/csv,.tar.gz""#),
|
|
1340 |
+ |
"{html}"
|
|
1341 |
+ |
);
|
|
1342 |
+ |
assert!(html.contains(" multiple"), "{html}");
|
|
1343 |
+ |
}
|
|
1344 |
+ |
|
|
1345 |
+ |
#[test]
|
|
1346 |
+ |
fn an_accept_entry_cannot_end_the_attribute_it_sits_in() {
|
|
1347 |
+ |
// The list reaches an attribute value, so it is escaped like every
|
|
1348 |
+ |
// other string that does. Nothing in the tree writes a quote into one;
|
|
1349 |
+ |
// that it cannot is the point.
|
|
1350 |
+ |
const HOSTILE: &[Accepted<'_>] = &[Accepted::Type(r#"image/x" onload="x"#)];
|
|
1351 |
+ |
let html = field_html(
|
|
1352 |
+ |
&Field::upload("cover", "Cover", HOSTILE),
|
|
1353 |
+ |
&Filling::default(),
|
|
1354 |
+ |
&Emit::default(),
|
|
1355 |
+ |
);
|
|
1356 |
+ |
assert!(!html.contains(r#"onload="x"#), "{html}");
|
|
1357 |
+ |
}
|
|
1358 |
+ |
|
| 1283 |
1359 |
|
#[test]
|
| 1284 |
1360 |
|
fn the_typed_text_kinds_keep_their_input_type() {
|
| 1285 |
1361 |
|
for (kind, expected) in [
|