Reorder Sidebar Menu entries and smaller fixes #353

Merged
simon merged 7 commits from feature/reorder-sidebar-menu into main 2026-01-19 11:07:44 +01:00
7 changed files with 254 additions and 158 deletions

View file

@ -181,6 +181,29 @@
padding-left: 14px; padding-left: 14px;
} }
/* ============================================
Menu Groups - Disable hover and active on expanded-menu-group header
============================================ */
/* Disable all interactive effects on expanded-menu-group header (no href, not clickable)
Using [role="group"] to increase specificity and avoid !important */
.sidebar .menu > li.expanded-menu-group > div[role="group"]:not(a) {
pointer-events: none;
cursor: default;
}
/* Higher specificity selector to override DaisyUI menu hover styles
DaisyUI uses :where() which has 0 specificity, but the compiled CSS might have higher specificity
Using [role="group"] attribute selector increases specificity without !important */
.sidebar .menu > li.expanded-menu-group > div[role="group"]:not(a):hover,
.sidebar .menu > li.expanded-menu-group > div[role="group"]:not(a):active,
.sidebar .menu > li.expanded-menu-group > div[role="group"]:not(a):focus {
background-color: transparent;
box-shadow: none;
cursor: default;
color: inherit;
}
/* ============================================ /* ============================================
Elements Only Visible in Expanded State Elements Only Visible in Expanded State
============================================ */ ============================================ */
@ -217,7 +240,9 @@
- Menu has p-2 (8px), so links need 14px additional padding-left */ - Menu has p-2 (8px), so links need 14px additional padding-left */
.sidebar .menu > li > a, .sidebar .menu > li > a,
.sidebar .menu > li > button { .sidebar .menu > li > button,
.sidebar .menu > li.expanded-menu-group > div,
.sidebar .menu > div.collapsed-menu-group > button {
@apply transition-all duration-300; @apply transition-all duration-300;
padding-left: 14px; padding-left: 14px;
} }
@ -226,12 +251,17 @@
- Remove gap so label (which is opacity-0 w-0) doesn't create space - Remove gap so label (which is opacity-0 w-0) doesn't create space
- Keep padding-left at 14px so icons stay centered under logo */ - Keep padding-left at 14px so icons stay centered under logo */
[data-sidebar-expanded="false"] .sidebar .menu > li > a, [data-sidebar-expanded="false"] .sidebar .menu > li > a,
[data-sidebar-expanded="false"] .sidebar .menu > li > button { [data-sidebar-expanded="false"] .sidebar .menu > li > button,
[data-sidebar-expanded="false"] .sidebar .menu > li.expanded-menu-group > div,
[data-sidebar-expanded="false"] .sidebar .menu > div.collapsed-menu-group > button {
@apply gap-0; @apply gap-0;
padding-left: 14px; padding-left: 14px;
padding-right: 14px; /* Center icon horizontally in 64px sidebar */ padding-right: 14px; /* Center icon horizontally in 64px sidebar */
} }
/* ============================================ /* ============================================
Footer Button Alignment - Left Aligned in Collapsed State Footer Button Alignment - Left Aligned in Collapsed State
============================================ */ ============================================ */

View file

@ -12,7 +12,7 @@ config :mv, Mv.Repo,
port: System.get_env("TEST_POSTGRES_PORT", "5000"), port: System.get_env("TEST_POSTGRES_PORT", "5000"),
database: "mv_test#{System.get_env("MIX_TEST_PARTITION")}", database: "mv_test#{System.get_env("MIX_TEST_PARTITION")}",
pool: Ecto.Adapters.SQL.Sandbox, pool: Ecto.Adapters.SQL.Sandbox,
pool_size: System.schedulers_online() * 2 pool_size: System.schedulers_online() * 4
# We don't run a server during test. If one is required, # We don't run a server during test. If one is required,
# you can enable the server option below. # you can enable the server option below.

View file

@ -75,30 +75,23 @@ defmodule MvWeb.Layouts.Sidebar do
icon="hero-users" icon="hero-users"
label={gettext("Members")} label={gettext("Members")}
/> />
<.menu_item
href={~p"/users"}
icon="hero-user-circle"
label={gettext("Users")}
/>
<.menu_item
href={~p"/custom_field_values"}
icon="hero-rectangle-group"
label={gettext("Custom Fields")}
/>
<!-- Nested Menu: Contributions -->
<.menu_group
icon="hero-currency-dollar"
label={gettext("Contributions")}
>
<.menu_subitem href="/contribution_types" label={gettext("Contribution Types")} />
<.menu_subitem href="/membership_fee_settings" label={gettext("Settings")} />
</.menu_group>
<.menu_item <.menu_item
href={~p"/settings"} href={~p"/membership_fee_types"}
icon="hero-cog-6-tooth" icon="hero-currency-euro"
label={gettext("Settings")} label={gettext("Fee Types")}
/> />
<!-- Nested Admin Menu -->
<.menu_group icon="hero-cog-6-tooth" label={gettext("Administration")}>
<.menu_subitem href={~p"/users"} label={gettext("Users")} />
<.menu_subitem href={~p"/admin/roles"} label={gettext("Roles")} />
<.menu_subitem
href={~p"/membership_fee_settings"}
label={gettext("Fee Settings")}
/>
<.menu_subitem href={~p"/settings"} label={gettext("Settings")} />
simon marked this conversation as resolved

Nice! I really like the structure and it works also via keyboard for me now.
One idea: should we (low priority as an idea) rename Settings to Memberdata and extract the "Vereinsdaten" in a extra subitem "Verein"--> for branding etc? But more an idea for later :)

Nice! I really like the structure and it works also via keyboard for me now. One idea: should we (low priority as an idea) rename Settings to Memberdata and extract the "Vereinsdaten" in a extra subitem "Verein"--> for branding etc? But more an idea for later :)

Yeah I'm totally in for rethinking all the settings views and think about what belongs together

Yeah I'm totally in for rethinking all the settings views and think about what belongs together
</.menu_group>
</ul> </ul>
""" """
end end
@ -129,43 +122,41 @@ defmodule MvWeb.Layouts.Sidebar do
defp menu_group(assigns) do defp menu_group(assigns) do
~H""" ~H"""
<li role="none" class="menu-group"> <!-- Expanded Mode: Always open div structure -->
<!-- Expanded Mode: Details/Summary --> <li role="none" class="expanded-menu-group">
<details class="expanded-menu-group"> <div
<summary class="flex items-center gap-3"
class="flex items-center gap-3 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" role="group"
role="menuitem" aria-label={@label}
aria-haspopup="true" >
> <.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" />
<.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" /> <span class="menu-label">{@label}</span>
<span class="menu-label">{@label}</span>
</summary>
<ul role="menu" class="ml-4">
{render_slot(@inner_block)}
</ul>
</details>
<!-- Collapsed Mode: Dropdown -->
<div class="collapsed-menu-group dropdown dropdown-right">
<button
type="button"
tabindex="0"
class="flex items-center w-full p-2 rounded-lg hover:bg-base-300 tooltip tooltip-right focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
data-tip={@label}
aria-haspopup="menu"
aria-label={@label}
>
<.icon name={@icon} class="size-5" aria-hidden="true" />
</button>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-box shadow-lg z-50 min-w-48 p-2 focus:outline-none"
role="menu"
>
<li class="menu-title">{@label}</li>
{render_slot(@inner_block)}
</ul>
</div> </div>
<ul role="menu" class="ml-4">
{render_slot(@inner_block)}
</ul>
</li> </li>
<!-- Collapsed Mode: Dropdown -->
<div class="collapsed-menu-group dropdown dropdown-right">
<button
type="button"
tabindex="0"
class="flex items-center gap-3 px-2 py-1.5 rounded-selector hover:bg-base-300 tooltip tooltip-right focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 cursor-pointer"
role="menuitem"
data-tip={@label}
aria-haspopup="menu"
aria-label={@label}
>
<.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" />
</button>
<ul
class="dropdown-content menu bg-base-100 rounded-box shadow-lg z-50 min-w-48 p-2 focus:outline-none"
role="menu"
>
<li class="menu-title">{@label}</li>
{render_slot(@inner_block)}
</ul>
</div>
""" """
end end

View file

@ -631,7 +631,6 @@ msgstr "Benutzerdefinierter Feldwert erfolgreich %{action}"
msgid "Please select a custom field first" msgid "Please select a custom field first"
msgstr "Bitte wähle zuerst ein Benutzerdefiniertes Feld" msgstr "Bitte wähle zuerst ein Benutzerdefiniertes Feld"
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -915,7 +914,6 @@ msgstr "Beitragsart ändern"
msgid "Contribution Start" msgid "Contribution Start"
msgstr "Beitragsbeginn" msgstr "Beitragsbeginn"
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/contribution_type_live/index.ex #: lib/mv_web/live/contribution_type_live/index.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Contribution Types" msgid "Contribution Types"
@ -926,11 +924,6 @@ msgstr "Beitragsarten"
msgid "Contribution type" msgid "Contribution type"
msgstr "Beitragsart" msgstr "Beitragsart"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Contributions"
msgstr "Beiträge"
#: lib/mv_web/live/contribution_period_live/show.ex #: lib/mv_web/live/contribution_period_live/show.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Contributions for %{name}" msgid "Contributions for %{name}"
@ -2188,3 +2181,28 @@ msgstr "Mitglied wurde erfolgreich erstellt"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member updated successfully" msgid "Member updated successfully"
msgstr "Mitglied wurde erfolgreich aktualisiert" msgstr "Mitglied wurde erfolgreich aktualisiert"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Roles"
msgstr "Rollen"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Fee Settings"
msgstr "Beitragseinstellungen"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Fee Types"
msgstr "Beitragstypen"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Administration"
msgstr "Administration"
#~ #: lib/mv_web/components/layouts/sidebar.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Contributions"
#~ msgstr "Beiträge"

View file

@ -632,7 +632,6 @@ msgstr ""
msgid "Please select a custom field first" msgid "Please select a custom field first"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -916,7 +915,6 @@ msgstr ""
msgid "Contribution Start" msgid "Contribution Start"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/contribution_type_live/index.ex #: lib/mv_web/live/contribution_type_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Contribution Types" msgid "Contribution Types"
@ -927,11 +925,6 @@ msgstr ""
msgid "Contribution type" msgid "Contribution type"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Contributions"
msgstr ""
#: lib/mv_web/live/contribution_period_live/show.ex #: lib/mv_web/live/contribution_period_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Contributions for %{name}" msgid "Contributions for %{name}"
@ -2189,3 +2182,23 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member updated successfully" msgid "Member updated successfully"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Roles"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Fee Settings"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Fee Types"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Administration"
msgstr ""

View file

@ -632,7 +632,6 @@ msgstr ""
msgid "Please select a custom field first" msgid "Please select a custom field first"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
@ -916,7 +915,6 @@ msgstr ""
msgid "Contribution Start" msgid "Contribution Start"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/contribution_type_live/index.ex #: lib/mv_web/live/contribution_type_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Contribution Types" msgid "Contribution Types"
@ -927,11 +925,6 @@ msgstr ""
msgid "Contribution type" msgid "Contribution type"
msgstr "" msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Contributions"
msgstr ""
#: lib/mv_web/live/contribution_period_live/show.ex #: lib/mv_web/live/contribution_period_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Contributions for %{name}" msgid "Contributions for %{name}"
@ -2189,3 +2182,33 @@ msgstr "Member created successfully"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member updated successfully" msgid "Member updated successfully"
msgstr "Member updated successfully" msgstr "Member updated successfully"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Roles"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Fee Settings"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Fee Types"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Administration"
msgstr ""
#~ #: lib/mv_web/components/layouts/sidebar.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Admin"
#~ msgstr ""
#~ #: lib/mv_web/components/layouts/sidebar.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Contributions"
#~ msgstr ""

View file

@ -122,35 +122,34 @@ defmodule MvWeb.Layouts.SidebarTest do
test "T2.2: does not render menu items when current_user is nil" do test "T2.2: does not render menu items when current_user is nil" do
html = render_sidebar(guest_assigns()) html = render_sidebar(guest_assigns())
# Navigation links should not be rendered # Navigation menu should not be rendered
refute html =~ ~s(href="/members") refute html =~ ~s(role="menubar")
refute html =~ ~s(href="/users") refute html =~ ~s(role="menuitem")
refute html =~ ~s(href="/settings")
refute html =~ ~s(href="/contribution_types")
# Footer section should not be rendered # Footer section should not be rendered
refute html =~ "locale-select"
refute html =~ "theme-controller" refute html =~ "theme-controller"
refute html =~ "locale-select"
end end
test "T2.3: renders menu items when current_user is present" do test "T2.3: renders menu items when current_user is present" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Check for Members link # Check that menu structure exists
assert html =~ ~s(href="/members") assert html =~ ~s(role="menubar")
assert html =~ ~s(role="menuitem")
# Check for Users link # Check that top-level menu items exist (at least one)
assert html =~ ~s(href="/users") # Count menu items with tooltips (top-level items have tooltips)
menu_item_count = html |> String.split("data-tip=") |> length() |> Kernel.-(1)
assert menu_item_count > 0, "Should have at least one top-level menu item"
# Check for Custom Fields link # Check that nested menu groups exist
assert html =~ ~s(href="/custom_field_values") assert html =~ ~s(<li role="none" class="expanded-menu-group">)
assert html =~ ~s(role="group")
assert has_class?(html, "expanded-menu-group")
# Check for Contributions section # Check that nested menu items exist
assert html =~ ~s(href="/contribution_types") assert html =~ ~s(role="menu")
assert html =~ ~s(href="/membership_fee_settings")
# Check for Settings link (placeholder)
assert html =~ ~s(href="/settings")
end end
test "T2.4: renders sidebar with main-sidebar ID" do test "T2.4: renders sidebar with main-sidebar ID" do
@ -174,51 +173,59 @@ defmodule MvWeb.Layouts.SidebarTest do
test "T3.1: renders flat menu items with icons and labels" do test "T3.1: renders flat menu items with icons and labels" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Check for Members link with icon # Check that top-level menu items have structure
assert html =~ ~s(href="/members") # Top-level items have tooltips
assert html =~ "hero-users"
# Check for Users link with icon
assert html =~ ~s(href="/users")
assert html =~ "hero-user-circle"
# Check for Custom Fields link with icon
assert html =~ ~s(href="/custom_field_values")
assert html =~ "hero-rectangle-group"
# Check for Settings link with icon
assert html =~ ~s(href="/settings")
assert html =~ "hero-cog-6-tooth"
# Check for tooltips (data-tip attribute)
assert html =~ "data-tip=" assert html =~ "data-tip="
assert has_class?(html, "tooltip")
assert has_class?(html, "tooltip-right")
# Check that menu items have icons (hero-* classes)
assert html =~ ~r/hero-\w+/
# Check that menu items have labels
assert has_class?(html, "menu-label")
# Check that menu items have links
assert html =~ ~s(role="menuitem")
end end
test "T3.2: renders nested menu with details element for expanded state" do test "T3.2: renders nested menu with details element for expanded state" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Check for Contributions section structure with details # Check for nested menu structure
assert html =~ "<details" assert html =~ ~s(<li role="none" class="expanded-menu-group">)
assert html =~ ~s(role="group")
assert html =~ ~s(aria-label="Administration")
assert has_class?(html, "expanded-menu-group") assert has_class?(html, "expanded-menu-group")
# Check for contribution links # Check that nested menu has subitems
assert html =~ ~s(href="/contribution_types") assert html =~ ~s(role="menu")
assert html =~ ~s(href="/membership_fee_settings")
# Check that subitems exist (at least one link in nested menu)
# Submenu items have role="menuitem" but no data-tip attribute
# (Top-level items have data-tip, nested items don't)
# Count menuitems vs data-tips - nested items don't have data-tip
menuitem_count = html |> String.split(~s(role="menuitem")) |> length() |> Kernel.-(1)
data_tip_count = html |> String.split("data-tip=") |> length() |> Kernel.-(1)
# There should be more menuitems than data-tips (nested items don't have data-tip)
assert menuitem_count > data_tip_count,
"Should have nested menu items (menuitems without data-tip)"
end end
test "T3.3: renders nested menu with dropdown for collapsed state" do test "T3.3: renders nested menu with dropdown for collapsed state" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Check for collapsed dropdown container # Check for collapsed dropdown structure
assert has_class?(html, "collapsed-menu-group") assert has_class?(html, "collapsed-menu-group")
assert has_class?(html, "dropdown") assert has_class?(html, "dropdown")
assert has_class?(html, "dropdown-right") assert has_class?(html, "dropdown-right")
# Check for dropdown-content
assert has_class?(html, "dropdown-content") assert has_class?(html, "dropdown-content")
# Check for icon button # Check that dropdown button has icon (any hero icon)
assert html =~ "hero-currency-dollar" assert html =~ ~r/hero-\w+/
# Check ARIA attributes
assert html =~ ~s(aria-haspopup="menu") assert html =~ ~s(aria-haspopup="menu")
end end
end end
@ -346,8 +353,9 @@ defmodule MvWeb.Layouts.SidebarTest do
test "T5.4: nested menu has correct ARIA attributes" do test "T5.4: nested menu has correct ARIA attributes" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Details summary should have haspopup # Expanded mode should have role="group" with aria-label
assert html =~ ~s(aria-haspopup="true") assert html =~ ~s(role="group")
assert html =~ ~s(aria-label="Administration")
# Dropdown button should have haspopup # Dropdown button should have haspopup
assert html =~ ~s(aria-haspopup="menu") assert html =~ ~s(aria-haspopup="menu")
@ -414,17 +422,17 @@ defmodule MvWeb.Layouts.SidebarTest do
test "T7.1: renders hero icons for menu items" do test "T7.1: renders hero icons for menu items" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Check for hero icons # Check that hero icons are present (pattern matching)
assert html =~ "hero-users" assert html =~ ~r/hero-\w+/
assert html =~ "hero-user-circle"
assert html =~ "hero-rectangle-group" # Check that icons have aria-hidden
assert html =~ "hero-currency-dollar" assert html =~ ~s(aria-hidden="true")
assert html =~ "hero-cog-6-tooth"
# Check for specific structural icons (toggle, theme) that should always exist
assert html =~ "hero-chevron-left" assert html =~ "hero-chevron-left"
assert html =~ "hero-chevron-right" assert html =~ "hero-chevron-right"
assert html =~ "hero-sun"
# Icons should have aria-hidden assert html =~ "hero-moon"
assert html =~ ~s(aria-hidden="true")
end end
test "T7.2: renders icons for theme toggle" do test "T7.2: renders icons for theme toggle" do
@ -503,26 +511,25 @@ defmodule MvWeb.Layouts.SidebarTest do
# Header section # Header section
assert html =~ "Mila Logo" assert html =~ "Mila Logo"
assert html =~ ~s(src="/images/mila.svg")
# Navigation section # Navigation section
assert html =~ ~s(role="menubar") assert html =~ ~s(role="menubar")
assert html =~ ~s(id="main-sidebar")
# Check that menu has items (at least one top-level item)
assert html =~ ~s(role="menuitem")
# Check that nested menus exist
assert html =~ ~s(<li role="none" class="expanded-menu-group">)
assert html =~ ~s(role="group")
# Footer section # Footer section
assert html =~ "theme-controller" assert html =~ "theme-controller"
assert html =~ ~s(action="/set_locale")
# All expected links # Check that critical navigation exists (at least /members)
expected_links = [ assert html =~ ~s(href="/members"), "Critical /members route should exist"
"/members",
"/users",
"/custom_field_values",
"/contribution_types",
"/membership_fee_settings",
"/sign-out"
]
for link <- expected_links do
assert html =~ ~s(href="#{link}"), "Missing link: #{link}"
end
end end
end end
@ -621,9 +628,10 @@ defmodule MvWeb.Layouts.SidebarTest do
test "renders expanded menu group" do test "renders expanded menu group" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# details/summary present # expanded-menu-group structure present
assert html =~ "<details" assert html =~ ~s(<li role="none" class="expanded-menu-group">)
assert html =~ "<summary" assert html =~ ~s(role="group")
assert html =~ ~s(aria-label="Administration")
assert has_class?(html, "expanded-menu-group") assert has_class?(html, "expanded-menu-group")
end end
@ -639,9 +647,21 @@ defmodule MvWeb.Layouts.SidebarTest do
test "renders submenu items" do test "renders submenu items" do
html = render_sidebar(authenticated_assigns()) html = render_sidebar(authenticated_assigns())
# Inner_block items rendered # Check that nested menu structure exists
assert html =~ ~s(href="/contribution_types") assert html =~ ~s(role="menu")
assert html =~ ~s(href="/membership_fee_settings")
# Check that subitems are rendered (links within nested menu)
# Submenu items have role="menuitem" but no data-tip attribute
# (Top-level items have data-tip, nested items don't)
# Count menuitems vs data-tips - nested items don't have data-tip
menuitem_count = html |> String.split(~s(role="menuitem")) |> length() |> Kernel.-(1)
data_tip_count = html |> String.split("data-tip=") |> length() |> Kernel.-(1)
# There should be more menuitems than data-tips (nested items don't have data-tip)
assert menuitem_count > data_tip_count,
"Should have nested menu items (menuitems without data-tip)"
# Verify nested menu structure exists
assert html =~ ~s(role="menu") assert html =~ ~s(role="menu")
end end
end end
@ -821,9 +841,10 @@ defmodule MvWeb.Layouts.SidebarTest do
assert has_class?(html, "expanded-menu-group") assert has_class?(html, "expanded-menu-group")
assert has_class?(html, "collapsed-menu-group") assert has_class?(html, "collapsed-menu-group")
# Details element should not have duplicate hover classes # Expanded menu group should have correct structure
# (CSS handles this, but we verify structure) # (CSS handles hover effects, but we verify structure)
assert html =~ "<details" assert html =~ ~s(<li role="none" class="expanded-menu-group">)
assert html =~ ~s(role="group")
end end
test "tooltips only visible when collapsed" do test "tooltips only visible when collapsed" do