feat: add collapsible resizable navigation

This commit is contained in:
2026-07-31 10:25:47 +02:00
parent 2cbce03206
commit 6b36184f9d
14 changed files with 567 additions and 35 deletions
@@ -1,6 +1,13 @@
from django.test import SimpleTestCase
from netbox_utilities.navigation_helpers import normalize_preferences
from netbox_utilities.navigation_helpers import (
SIDEBAR_WIDTH_DEFAULT,
SIDEBAR_WIDTH_MAX,
SIDEBAR_WIDTH_MIN,
normalize_preferences,
normalize_sidebar_width,
update_sidebar_layout,
)
class NormalizePreferencesTest(SimpleTestCase):
@@ -19,3 +26,29 @@ class NormalizePreferencesTest(SimpleTestCase):
self.assertEqual(order, ["devices", "vpn"])
self.assertEqual(hidden, [])
class SidebarLayoutTest(SimpleTestCase):
def test_normalizes_width_to_supported_steps(self):
self.assertEqual(normalize_sidebar_width(None), SIDEBAR_WIDTH_DEFAULT)
self.assertEqual(normalize_sidebar_width(100), SIDEBAR_WIDTH_MIN)
self.assertEqual(normalize_sidebar_width(500), SIDEBAR_WIDTH_MAX)
self.assertEqual(normalize_sidebar_width(300), 312)
def test_toggles_icon_only_mode(self):
collapsed, width = update_sidebar_layout(False, SIDEBAR_WIDTH_DEFAULT, "toggle")
self.assertTrue(collapsed)
self.assertEqual(width, SIDEBAR_WIDTH_DEFAULT)
def test_resizes_within_limits(self):
self.assertEqual(update_sidebar_layout(False, SIDEBAR_WIDTH_MIN, "smaller")[1], SIDEBAR_WIDTH_MIN)
self.assertEqual(update_sidebar_layout(False, SIDEBAR_WIDTH_MAX, "larger")[1], SIDEBAR_WIDTH_MAX)
self.assertLess(
update_sidebar_layout(False, SIDEBAR_WIDTH_DEFAULT, "smaller")[1],
SIDEBAR_WIDTH_DEFAULT,
)
def test_rejects_unknown_action(self):
with self.assertRaisesMessage(ValueError, "Unbekannte Navigationsaktion"):
update_sidebar_layout(False, SIDEBAR_WIDTH_DEFAULT, "invalid")
@@ -0,0 +1,37 @@
import json
from unittest.mock import MagicMock, patch
from django.test import RequestFactory, SimpleTestCase
from netbox_utilities.views import NavigationLayoutView
class NavigationLayoutViewTest(SimpleTestCase):
def setUp(self):
self.factory = RequestFactory()
@patch("netbox_utilities.views.navigation_customization_enabled", return_value=True)
@patch("netbox_utilities.views.NavigationPreference.objects.get_or_create")
def test_toggles_and_persists_icon_mode(self, get_or_create, _feature_enabled):
preference = MagicMock(sidebar_collapsed=False, sidebar_width=288)
get_or_create.return_value = (preference, False)
request = self.factory.post("/plugins/utilities/navigation/layout/", {"action": "toggle"})
request.user = MagicMock()
response = NavigationLayoutView().post(request)
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.content), {"collapsed": True, "width": 288})
self.assertTrue(preference.sidebar_collapsed)
preference.save.assert_called_once_with(update_fields=("sidebar_collapsed", "sidebar_width", "updated"))
@patch("netbox_utilities.views.navigation_customization_enabled", return_value=True)
@patch("netbox_utilities.views.NavigationPreference.objects.get_or_create")
def test_rejects_unknown_layout_action(self, get_or_create, _feature_enabled):
get_or_create.return_value = (MagicMock(sidebar_collapsed=False, sidebar_width=288), False)
request = self.factory.post("/plugins/utilities/navigation/layout/", {"action": "unknown"})
request.user = MagicMock()
response = NavigationLayoutView().post(request)
self.assertEqual(response.status_code, 400)