♻️ (bootsier): Cambia Offcanvas por Panel en Navbar
This commit is contained in:
parent
c47724bb68
commit
6df71885cc
8 changed files with 388 additions and 168 deletions
|
|
@ -1,5 +1,6 @@
|
|||
// Verifies that a `Navbar` with its content in an `Offcanvas` renders a working panel: the toggle
|
||||
// button must point to the `id` of the panel, and the panel must carry its own placement class.
|
||||
// Verifies that a `Navbar` with its content in a `navbar::Panel` renders a working side panel: the
|
||||
// toggle button points to the panel, the panel carries its own classes and attributes, and the
|
||||
// items of the navbar are its only content.
|
||||
|
||||
use pagetop::prelude::*;
|
||||
use pagetop_bootsier::theme::*;
|
||||
|
|
@ -8,31 +9,42 @@ fn cx() -> Context {
|
|||
Context::default().with_theme(&pagetop_bootsier::Bootsier)
|
||||
}
|
||||
|
||||
fn panel(placement: bs::offcanvas::Placement) -> bs::Offcanvas {
|
||||
bs::Offcanvas::new().with_placement(placement)
|
||||
fn panel(placement: bs::offcanvas::Placement) -> bs::navbar::Panel {
|
||||
bs::navbar::Panel::new().with_placement(placement)
|
||||
}
|
||||
|
||||
fn home() -> bs::navbar::Item {
|
||||
bs::navbar::Item::nav(bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")))
|
||||
}
|
||||
|
||||
async fn render(navbar: Navbar) -> String {
|
||||
let mut navbar = navbar.with_item(home());
|
||||
navbar.render(&mut cx()).await.into_string()
|
||||
}
|
||||
|
||||
// The `id` the toggle button points to.
|
||||
fn target(html: &str) -> &str {
|
||||
html.split(r##"data-bs-target="#"##)
|
||||
.nth(1)
|
||||
.and_then(|rest| rest.split('"').next())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_button_points_to_the_panel_id() {
|
||||
let brand = || Brand::new().with_title(Lc::n("PageTop"));
|
||||
let start = bs::offcanvas::Placement::Start;
|
||||
let navbars = [
|
||||
bs::Navbar::offcanvas(panel(bs::offcanvas::Placement::Start)),
|
||||
bs::Navbar::offcanvas_brand_left(brand(), panel(bs::offcanvas::Placement::Start)),
|
||||
bs::Navbar::offcanvas_brand_right(brand(), panel(bs::offcanvas::Placement::Start)),
|
||||
bs::Navbar::offcanvas(panel(start)),
|
||||
bs::Navbar::offcanvas_brand_left(brand(), panel(start)),
|
||||
bs::Navbar::offcanvas_brand_right(brand(), panel(start)),
|
||||
];
|
||||
|
||||
for mut navbar in navbars {
|
||||
navbar = navbar.with_item(bs::navbar::Item::nav(
|
||||
bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")),
|
||||
));
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
for navbar in navbars {
|
||||
let html = render(navbar).await;
|
||||
|
||||
// The panel is rendered by the navbar, so the navbar must set its `id` up.
|
||||
let target = html
|
||||
.split(r##"data-bs-target="#"##)
|
||||
.nth(1)
|
||||
.and_then(|rest| rest.split('"').next())
|
||||
.unwrap_or_default();
|
||||
// The panel is rendered by the navbar, which derives its `id` from its own.
|
||||
let target = target(&html);
|
||||
assert!(!target.is_empty(), "the button has no target in {html}");
|
||||
assert!(html.contains(&format!(r#"aria-controls="{target}""#)));
|
||||
assert!(
|
||||
|
|
@ -47,11 +59,10 @@ async fn offcanvas_panel_gets_its_own_classes() {
|
|||
for (placement, class) in [
|
||||
(bs::offcanvas::Placement::Start, "offcanvas-start"),
|
||||
(bs::offcanvas::Placement::End, "offcanvas-end"),
|
||||
(bs::offcanvas::Placement::Top, "offcanvas-top"),
|
||||
(bs::offcanvas::Placement::Bottom, "offcanvas-bottom"),
|
||||
] {
|
||||
let mut navbar = bs::Navbar::offcanvas(panel(placement)).with_item(bs::navbar::Item::nav(
|
||||
bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")),
|
||||
));
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
let html = render(bs::Navbar::offcanvas(panel(placement))).await;
|
||||
|
||||
// Without them Bootstrap would show the panel inline instead of hiding and placing it.
|
||||
assert!(
|
||||
|
|
@ -60,3 +71,72 @@ async fn offcanvas_panel_gets_its_own_classes() {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_panel_follows_the_expand_of_the_navbar() {
|
||||
let navbar = bs::Navbar::offcanvas(panel(bs::offcanvas::Placement::Start));
|
||||
let html = render(navbar.with_expand(Breakpoint::Lg)).await;
|
||||
|
||||
// Bootstrap shows the panel inline from `navbar-expand-lg`; the panel has no breakpoint of its
|
||||
// own that could contradict it, and it is not open on load.
|
||||
assert!(html.contains("navbar-expand-lg"));
|
||||
assert!(html.contains(r#"class="offcanvas offcanvas-start""#));
|
||||
assert!(!html.contains("offcanvas-lg") && !html.contains("offcanvas-md"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_panel_title_labels_the_panel() {
|
||||
let start = bs::offcanvas::Placement::Start;
|
||||
|
||||
let html = render(bs::Navbar::offcanvas(
|
||||
bs::navbar::Panel::new()
|
||||
.with_title(Lc::n("Main menu"))
|
||||
.with_placement(start),
|
||||
))
|
||||
.await;
|
||||
let target = target(&html);
|
||||
assert!(html.contains(&format!(
|
||||
r#"<h5 id="{target}-label" class="offcanvas-title">Main menu</h5>"#
|
||||
)));
|
||||
assert!(html.contains(&format!(r#"aria-labelledby="{target}-label""#)));
|
||||
|
||||
// Without a title there is nothing to point `aria-labelledby` to.
|
||||
let html = render(bs::Navbar::offcanvas(panel(start))).await;
|
||||
assert!(!html.contains("<h5"));
|
||||
assert!(!html.contains("aria-labelledby"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_panel_backdrop_and_body_scroll_are_attributes() {
|
||||
let panel = || bs::navbar::Panel::new();
|
||||
|
||||
let html = render(bs::Navbar::offcanvas(panel())).await;
|
||||
assert!(!html.contains("data-bs-backdrop") && !html.contains("data-bs-scroll"));
|
||||
|
||||
for (backdrop, value) in [
|
||||
(bs::offcanvas::Backdrop::Disabled, "false"),
|
||||
(bs::offcanvas::Backdrop::Static, "static"),
|
||||
] {
|
||||
let html = render(bs::Navbar::offcanvas(panel().with_backdrop(backdrop))).await;
|
||||
assert!(html.contains(&format!(r#"data-bs-backdrop="{value}""#)));
|
||||
}
|
||||
|
||||
let scrolling = panel().with_body_scroll(bs::offcanvas::BodyScroll::Enabled);
|
||||
let html = render(bs::Navbar::offcanvas(scrolling)).await;
|
||||
assert!(html.contains(r#"data-bs-scroll="true""#));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_panel_contains_the_items_once_and_can_be_closed() {
|
||||
let html = render(bs::Navbar::offcanvas(panel(bs::offcanvas::Placement::End))).await;
|
||||
let target = target(&html);
|
||||
|
||||
// The items are the body of the panel and are not rendered anywhere else.
|
||||
assert_eq!(html.matches(">Home<").count(), 1);
|
||||
assert!(html.contains(r#"<div class="offcanvas-body"><ul"#));
|
||||
|
||||
// The close button of the header points to the same panel.
|
||||
assert!(html.contains(&format!(
|
||||
r##"data-bs-dismiss="offcanvas" data-bs-target="#{target}""##
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
63
extensions/pagetop-bootsier/tests/offcanvas.rs
Normal file
63
extensions/pagetop-bootsier/tests/offcanvas.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Verifies the rendering of the standalone `Offcanvas`: its accessible name comes from the title,
|
||||
// and it is not rendered without content.
|
||||
|
||||
use pagetop::prelude::*;
|
||||
use pagetop_bootsier::theme::*;
|
||||
|
||||
fn cx() -> Context {
|
||||
Context::default().with_theme(&pagetop_bootsier::Bootsier)
|
||||
}
|
||||
|
||||
fn content() -> Html {
|
||||
Html::with(|_| html! { p { "Content" } })
|
||||
}
|
||||
|
||||
async fn render(mut offcanvas: bs::Offcanvas) -> String {
|
||||
offcanvas.render(&mut cx()).await.into_string()
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn title_labels_the_panel() {
|
||||
let html = render(
|
||||
bs::Offcanvas::new()
|
||||
.with_id("panel")
|
||||
.with_title(Lc::n("Menu"))
|
||||
.with_child(content()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(html.contains(r#"<h5 id="panel-label" class="offcanvas-title">Menu</h5>"#));
|
||||
assert!(html.contains(r#"aria-labelledby="panel-label""#));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn without_a_title_there_is_nothing_to_point_aria_labelledby_to() {
|
||||
let html = render(bs::Offcanvas::new().with_id("panel").with_child(content())).await;
|
||||
|
||||
assert!(!html.contains("<h5"));
|
||||
assert!(!html.contains("aria-labelledby"));
|
||||
// The close button and the body are still there.
|
||||
assert!(html.contains(r##"data-bs-target="#panel""##));
|
||||
assert!(html.contains(r#"<div class="offcanvas-body"><p>Content</p></div>"#));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn is_not_rendered_without_content() {
|
||||
let html = render(bs::Offcanvas::new().with_title(Lc::n("Menu"))).await;
|
||||
|
||||
assert!(html.is_empty());
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn getters_of_the_options_return_their_values() {
|
||||
let offcanvas = bs::Offcanvas::new()
|
||||
.with_placement(bs::offcanvas::Placement::End)
|
||||
.with_backdrop(bs::offcanvas::Backdrop::Static)
|
||||
.with_body_scroll(bs::offcanvas::BodyScroll::Enabled)
|
||||
.with_visibility(bs::offcanvas::Visibility::Show);
|
||||
|
||||
assert_eq!(offcanvas.placement(), bs::offcanvas::Placement::End);
|
||||
assert_eq!(offcanvas.backdrop(), bs::offcanvas::Backdrop::Static);
|
||||
assert_eq!(offcanvas.body_scroll(), bs::offcanvas::BodyScroll::Enabled);
|
||||
assert_eq!(offcanvas.visibility(), bs::offcanvas::Visibility::Show);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue