| 178 |
178 |
|
//! `Cell` was measured to hold, and the count is in that crate's history
|
| 179 |
179 |
|
//! rather than assumed here.
|
| 180 |
180 |
|
//!
|
|
181 |
+ |
//! 0.15.0 adds [`FieldKind::Date`] and [`FieldKind::DateTime`], on the argument
|
|
182 |
+ |
//! [`FieldKind::Email`] was admitted on: a webview emits a different `type=`,
|
|
183 |
+ |
//! which is a native picker, the platform's validation and a different keyboard
|
|
184 |
+ |
//! on a touch device. Described as text with a "YYYY-MM-DD" hint, all three are
|
|
185 |
+ |
//! lost.
|
|
186 |
+ |
//!
|
|
187 |
+ |
//! Two members and not one or five, from a count rather than from symmetry: 13
|
|
188 |
+ |
//! sites of `date` and 13 of `datetime-local` across the MNW server and
|
|
189 |
+ |
//! goingson, and zero of `time`, `month` or `week`. The wire format each takes
|
|
190 |
+ |
//! is named here as [`DATE_FORMAT`] and [`DATETIME_FORMAT`], because a host
|
|
191 |
+ |
//! left to pick its own would disagree with a server silently, and
|
|
192 |
+ |
//! [`FieldKind::temporal`] is the pair asked about once rather than at each
|
|
193 |
+ |
//! renderer. `FieldKind`'s own comment claiming `radio` was the last HTML input
|
|
194 |
+ |
//! type missing was already false when 0.8.1 wrote it; these are what it was
|
|
195 |
+ |
//! missing.
|
|
196 |
+ |
//!
|
| 181 |
197 |
|
//! What each of the 0.12.0 findings deliberately leaves out is the address — what pressing a
|
| 182 |
198 |
|
//! header calls, and where an empty state's "Add your first project" button
|
| 183 |
199 |
|
//! goes. That is the boundary this crate is defined by, and four findings moved
|
| 1284 |
1300 |
|
///
|
| 1285 |
1301 |
|
/// Added 0.5.0, from goingson's contact-phone form.
|
| 1286 |
1302 |
|
Tel,
|
|
1303 |
+ |
/// A calendar day, with no time of day in it.
|
|
1304 |
+ |
///
|
|
1305 |
+ |
/// [`Email`](Self::Email)'s argument, and it carries further: a webview
|
|
1306 |
+ |
/// emits `type="date"`, which is a native picker, the platform's own
|
|
1307 |
+ |
/// validation, and on a touch device the date keyboard. Described as
|
|
1308 |
+ |
/// [`Text`](Self::Text) with a hint reading "YYYY-MM-DD", all three are
|
|
1309 |
+ |
/// lost and the hint is doing the platform's job in prose.
|
|
1310 |
+ |
///
|
|
1311 |
+ |
/// The membership test passes on every host without stretching: a webview
|
|
1312 |
+ |
/// and a Tauri app emit the input, egui has a date picker, a terminal
|
|
1313 |
+ |
/// prompts for a day and can validate it, a CLI takes an argument.
|
|
1314 |
+ |
///
|
|
1315 |
+ |
/// # The value is ISO 8601, `YYYY-MM-DD`
|
|
1316 |
+ |
///
|
|
1317 |
+ |
/// Named here rather than left to each host, because a host that picks
|
|
1318 |
+ |
/// differently sends a server something it parses differently, and the
|
|
1319 |
+ |
/// failure is silent and per-host. It is `<input type="date">`'s own wire
|
|
1320 |
+ |
/// format, so the webview renderer owes nothing to honour it and the other
|
|
1321 |
+ |
/// hosts have one spelling to meet. [`DATE_FORMAT`] is the constant, and a
|
|
1322 |
+ |
/// test asserts this doc and that constant agree.
|
|
1323 |
+ |
///
|
|
1324 |
+ |
/// Added 0.15.0, from the MNW server's git access-token expiry
|
|
1325 |
+ |
/// (`user_ssh_keys_tab.html`) and six further sites across the server and
|
|
1326 |
+ |
/// goingson.
|
|
1327 |
+ |
Date,
|
|
1328 |
+ |
/// A calendar day and a time of day together.
|
|
1329 |
+ |
///
|
|
1330 |
+ |
/// Apart from [`Date`](Self::Date) because the question is different rather
|
|
1331 |
+ |
/// than more precise: "which day does this expire" and "at what moment does
|
|
1332 |
+ |
/// this publish" are asked by different screens and answered by different
|
|
1333 |
+ |
/// controls. A webview emits `type="datetime-local"` for one and
|
|
1334 |
+ |
/// `type="date"` for the other, and a host that collapsed them would ask
|
|
1335 |
+ |
/// half the tree for a precision it does not want.
|
|
1336 |
+ |
///
|
|
1337 |
+ |
/// Both arrived together on measurement rather than on symmetry: 13 sites
|
|
1338 |
+ |
/// of each across the MNW server and goingson, and **zero** of `time`,
|
|
1339 |
+ |
/// `month` or `week`, which is why those are not here. A member added for a
|
|
1340 |
+ |
/// case nobody has is a member designed against nothing, which is
|
|
1341 |
+ |
/// [`File`](Self::File)'s reasoning about `accept` applied to a whole
|
|
1342 |
+ |
/// member.
|
|
1343 |
+ |
///
|
|
1344 |
+ |
/// # The value is `YYYY-MM-DDTHH:MM`, local, with no zone
|
|
1345 |
+ |
///
|
|
1346 |
+ |
/// `<input type="datetime-local">`'s own format, and the "local" is the
|
|
1347 |
+ |
/// load-bearing half: the value carries no offset and no `Z`, so the moment
|
|
1348 |
+ |
/// it names is only fixed once something supplies a zone. That is the app's
|
|
1349 |
+ |
/// business and not the description's. Seconds are absent, which is the
|
|
1350 |
+ |
/// browser's own default and is left as the rule rather than restated as a
|
|
1351 |
+ |
/// constraint. [`DATETIME_FORMAT`] is the constant.
|
|
1352 |
+ |
///
|
|
1353 |
+ |
/// [`Field::min`] and [`Field::max`] already take "the host's own spelling
|
|
1354 |
+ |
/// of a bound", so a floor of *not in the past* needs nothing new here: it
|
|
1355 |
+ |
/// is a string in this same format.
|
|
1356 |
+ |
///
|
|
1357 |
+ |
/// Added 0.15.0, from goingson's snooze picker and day planner and the MNW
|
|
1358 |
+ |
/// server's publish-at fields.
|
|
1359 |
+ |
DateTime,
|
| 1287 |
1360 |
|
/// Several lines of text.
|
| 1288 |
1361 |
|
Textarea,
|
| 1289 |
1362 |
|
/// One of a fixed set, offered behind a control that shows one at a time.
|
| 1300 |
1373 |
|
/// afterwards — and had already promoted that out of a checkbox by hand,
|
| 1301 |
1374 |
|
/// with a comment giving this reason, before the description could say it.
|
| 1302 |
1375 |
|
///
|
| 1303 |
|
- |
/// It is also the one HTML input type this enum was missing. Everything
|
| 1304 |
|
- |
/// else here is an `<input type=...>`, a `<select>` or a `<textarea>`, and
|
| 1305 |
|
- |
/// the hole was `radio`.
|
|
1376 |
+ |
/// It was described here at 0.8.1 as "the one HTML input type this enum was
|
|
1377 |
+ |
/// missing", which was not true then and is not true now: `file` arrived at
|
|
1378 |
+ |
/// 0.11.0 and `date` and `datetime-local` at 0.15.0. Everything here is
|
|
1379 |
+ |
/// still an `<input type=...>`, a `<select>` or a `<textarea>`, and the way
|
|
1380 |
+ |
/// this enum grows is by a site being measured rather than by a list being
|
|
1381 |
+ |
/// completed, so "the last one" is not a claim it should make again.
|
| 1306 |
1382 |
|
///
|
| 1307 |
1383 |
|
/// Added 0.8.1, from audiofiles' Add Library form.
|
| 1308 |
1384 |
|
Radio,
|
| 1332 |
1408 |
|
Hidden,
|
| 1333 |
1409 |
|
}
|
| 1334 |
1410 |
|
|
|
1411 |
+ |
/// The wire format a [`FieldKind::Date`] value takes: ISO 8601, `YYYY-MM-DD`.
|
|
1412 |
+ |
///
|
|
1413 |
+ |
/// A constant rather than a sentence in a doc comment, because the reason to
|
|
1414 |
+ |
/// name the format at all is that a host picking its own would fail silently
|
|
1415 |
+ |
/// against a server parsing another. A host that cannot emit the native control
|
|
1416 |
+ |
/// still has one spelling to meet, and can say which one it meant.
|
|
1417 |
+ |
pub const DATE_FORMAT: &str = "%Y-%m-%d";
|
|
1418 |
+ |
|
|
1419 |
+ |
/// The wire format a [`FieldKind::DateTime`] value takes: `YYYY-MM-DDTHH:MM`,
|
|
1420 |
+ |
/// local, carrying no zone and no seconds.
|
|
1421 |
+ |
///
|
|
1422 |
+ |
/// [`DATE_FORMAT`]'s sibling and there for its reason. The absent zone is a
|
|
1423 |
+ |
/// property of the value rather than an omission: the moment is not fixed until
|
|
1424 |
+ |
/// something outside the description supplies one.
|
|
1425 |
+ |
pub const DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M";
|
|
1426 |
+ |
|
| 1335 |
1427 |
|
impl FieldKind {
|
|
1428 |
+ |
/// Whether the value the kind takes is a moment rather than a string.
|
|
1429 |
+ |
///
|
|
1430 |
+ |
/// Named once here for the reason [`offers_options`](Self::offers_options)
|
|
1431 |
+ |
/// is: two kinds answer yes, and a host that has to parse or format a value
|
|
1432 |
+ |
/// needs to ask without spelling the pair out at each renderer. A third
|
|
1433 |
+ |
/// temporal kind should land here and nowhere else.
|
|
1434 |
+ |
///
|
|
1435 |
+ |
/// The format each one takes is [`DATE_FORMAT`] and [`DATETIME_FORMAT`].
|
|
1436 |
+ |
#[must_use]
|
|
1437 |
+ |
pub const fn temporal(self) -> bool {
|
|
1438 |
+ |
matches!(self, Self::Date | Self::DateTime)
|
|
1439 |
+ |
}
|
|
1440 |
+ |
|
| 1336 |
1441 |
|
/// Whether the field is drawn at all.
|
| 1337 |
1442 |
|
#[must_use]
|
| 1338 |
1443 |
|
pub const fn visible(self) -> bool {
|
| 2152 |
2257 |
|
assert_eq!(RowPart::Tokens.intent(), "content");
|
| 2153 |
2258 |
|
}
|
| 2154 |
2259 |
|
|
|
2260 |
+ |
#[test]
|
|
2261 |
+ |
fn the_two_temporal_kinds_are_the_two_that_name_a_moment() {
|
|
2262 |
+ |
// The pair is named once so a host with parsing to do asks here rather
|
|
2263 |
+ |
// than spelling it out, which is `offers_options`' reason.
|
|
2264 |
+ |
assert!(FieldKind::Date.temporal());
|
|
2265 |
+ |
assert!(FieldKind::DateTime.temporal());
|
|
2266 |
+ |
|
|
2267 |
+ |
for kind in [
|
|
2268 |
+ |
FieldKind::Text,
|
|
2269 |
+ |
FieldKind::Secret,
|
|
2270 |
+ |
FieldKind::Number,
|
|
2271 |
+ |
FieldKind::Email,
|
|
2272 |
+ |
FieldKind::Url,
|
|
2273 |
+ |
FieldKind::Tel,
|
|
2274 |
+ |
FieldKind::Textarea,
|
|
2275 |
+ |
FieldKind::Select,
|
|
2276 |
+ |
FieldKind::Radio,
|
|
2277 |
+ |
FieldKind::Checkbox,
|
|
2278 |
+ |
FieldKind::File,
|
|
2279 |
+ |
FieldKind::Hidden,
|
|
2280 |
+ |
] {
|
|
2281 |
+ |
assert!(!kind.temporal(), "{kind:?}");
|
|
2282 |
+ |
}
|
|
2283 |
+ |
}
|
|
2284 |
+ |
|
|
2285 |
+ |
#[test]
|
|
2286 |
+ |
fn a_date_carries_no_time_and_a_datetime_carries_no_zone() {
|
|
2287 |
+ |
// The formats are the whole reason the members are worth naming apart
|
|
2288 |
+ |
// from text, so the doc comments and the constants have to agree. A
|
|
2289 |
+ |
// host reading one and meeting the other is the silent failure.
|
|
2290 |
+ |
assert_eq!(DATE_FORMAT, "%Y-%m-%d");
|
|
2291 |
+ |
assert!(!DATE_FORMAT.contains("%H"), "a day carries no hour");
|
|
2292 |
+ |
|
|
2293 |
+ |
assert_eq!(DATETIME_FORMAT, "%Y-%m-%dT%H:%M");
|
|
2294 |
+ |
assert!(
|
|
2295 |
+ |
DATETIME_FORMAT.starts_with(DATE_FORMAT),
|
|
2296 |
+ |
"a moment starts with the day it is on"
|
|
2297 |
+ |
);
|
|
2298 |
+ |
// Local, and that is a property of the value rather than an omission.
|
|
2299 |
+ |
assert!(!DATETIME_FORMAT.contains("%Z"), "no zone name");
|
|
2300 |
+ |
assert!(!DATETIME_FORMAT.ends_with('Z'), "not UTC-stamped");
|
|
2301 |
+ |
assert!(!DATETIME_FORMAT.contains("%S"), "no seconds by default");
|
|
2302 |
+ |
}
|
|
2303 |
+ |
|
|
2304 |
+ |
#[test]
|
|
2305 |
+ |
fn a_temporal_kind_takes_a_label_above_it_and_offers_no_options() {
|
|
2306 |
+ |
// Neither is a checkbox and neither is a fixed set, so both fall where
|
|
2307 |
+ |
// text does. Asserted because a new kind lands in three predicates and
|
|
2308 |
+ |
// only one of them is the interesting one.
|
|
2309 |
+ |
for kind in [FieldKind::Date, FieldKind::DateTime] {
|
|
2310 |
+ |
assert!(kind.visible(), "{kind:?}");
|
|
2311 |
+ |
assert!(!kind.confidential(), "{kind:?}");
|
|
2312 |
+ |
assert!(!kind.labels_itself(), "{kind:?}");
|
|
2313 |
+ |
assert!(!kind.offers_options(), "{kind:?}");
|
|
2314 |
+ |
}
|
|
2315 |
+ |
}
|
|
2316 |
+ |
|
| 2155 |
2317 |
|
#[test]
|
| 2156 |
2318 |
|
fn a_cell_part_names_an_intent_and_only_the_value_is_text() {
|
| 2157 |
2319 |
|
// The table half of what RowPart::intent does for rows. A cell holding
|