//! The Askama entry point for a described "delete this version" button. //! //! Shape 3 step 4 (`108557ec`), the step the plan calls the clean tail: the one //! part of `static/item-upload.js` that retires with no host remnant. It waited //! on `27d5e5b8` (a described act inside an Askama page) and on `35756077` (the //! unlayered `button` rule taking the tone off every described act). Both are //! settled, so it converts. //! //! # What the description says that the markup did not //! //! Everything, in this case. There was no markup: the button carried a class //! and a `data-version-id`, and the whole act lived in a click handler in //! `item-upload.js` — a `confirm()` string, a `fetch` with CSRF headers, and a //! `btn.closest('tr').remove()` on success. None of that is legible to anything //! but a browser. As an [`Act`] it is one sentence: what it is called, where it //! goes, what it replaces, that it is destructive, and what to ask first. //! //! # Why it names what it replaced, and why the spelling changed //! //! `DELETE /api/items/{item}/versions/{version}` is a plain API route the //! description layer does not serve; it answers `200` with an empty body, so it //! cannot name what it changed and the control has to. See //! [`Action::replaces`]. //! //! It said `#version-row-{id}`, an id the Askama table grew in the same change //! that landed this module, because the handler it replaced reached the row //! through `closest('tr')`. The table is described now (`super::item_files`, //! `138ad5ab`) and a described row carries `data-row` and no id at all, so an //! id target would name nothing. [`Action::replacing_enclosing`] is the same //! sentence in the spelling a described row understands, and it needs no //! parameter: the act is inside the row it removes. //! //! # The download button beside it is sayable now, and was not when this landed //! //! `download-version-btn` is `Action::get`. `GET /api/versions/{id}/download` //! (`routes::storage::downloads`) answers **303 to the presigned URL**, so //! navigating to it is the download and `Action::get` says the truth. The button //! is a plain navigation in `static/item-upload.js` today and becomes an act //! whenever the files tab converts. use makeover_layout::Tone; use quasi_router::Action; use quasi_router::screen::Act; /// The delete button for one version's row. /// /// Both ids are needed for the address, which is item and version together. /// The row it removes is the one it sits in, so nothing has to name it. #[must_use] pub fn act(item_id: &str, version_id: &str) -> Act { Act::new( "Delete", Action::delete(format!("/api/items/{item_id}/versions/{version_id}")).replacing_enclosing(), ) .tone(Tone::Danger) .confirm("Delete this version?") } #[cfg(test)] mod tests { use quasi_router::Node; /// What the click handler said in four places, said once: where it goes, /// what it replaces, and what to ask first. /// /// Rendered through a bare `Node::Act` rather than through the panel: this /// module owns the sentence, and `super::item_files` asserts it arrives in /// the row. fn html() -> String { use quasi_axum::Serves as _; quasi_webview::Webview::new().fragment(&Node::Act(super::act("7", "42"))) } #[test] fn a_delete_names_its_route_its_row_and_its_prompt() { let html = html(); assert!( html.contains(r#"hx-delete="/api/items/7/versions/42""#), "{html}" ); // The row it sits in, rather than an id the table had to grow. assert!(!html.contains("version-row-42"), "{html}"); assert!( html.contains(r#"hx-confirm="Delete this version?""#), "{html}" ); } /// The tone. The template said nothing at all here -- the button was /// `btn-secondary`, so the page did not mark the destructive act even to /// the stylesheet. #[test] fn a_delete_says_it_is_dangerous_in_a_word_every_renderer_reads() { assert!(html().contains(r#"data-tone="danger""#)); } /// The negative half, as `export_act` asserts it: the dispatcher is gone /// from this control, not merely bypassed. #[test] fn nothing_here_goes_through_the_dispatcher() { let html = html(); assert!(!html.contains("data-action"), "{html}"); assert!(!html.contains("delete-version-btn"), "{html}"); } }