chore(frontend): reorganize sidebar into grouped sections + remove disabled items
Problem: sidebar was growing unwieldy — 4 top-level disabled items marked 'Próx.' acted as visual noise, and 12 admin items sat in a flat list with no grouping (hard to scan). Changes: - Remove the 4 disabled top-level items (Ventas, Tasación, Integraciones, Administración-as-link). Those features will surface via the admin subsections when actually implemented, not as placeholder ghosts. - Group the 12 admin items into 4 domain-aligned sections: - Seguridad: Usuarios, Crear Usuario, Roles, Permisos, Auditoría - Maestros: Medios, Secciones, Puntos de Venta - Catálogo: Rubros, Tipos de Producto, Productos - Tasación: Caracteres Tasables - Sections auto-hide when no item passes the permission filter, preventing empty headers for users with limited roles. - Dashboard remains as the single top-level nav item (always visible). TDD: new AppSidebar.test.tsx covers 10 scenarios — section rendering, permission filtering, section auto-hide, role gating, active-route marking, and section ordering.
This commit is contained in:
@@ -1,12 +1,8 @@
|
|||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
ShoppingCart,
|
|
||||||
Calculator,
|
|
||||||
Zap,
|
|
||||||
Settings,
|
|
||||||
UserPlus,
|
|
||||||
Users,
|
Users,
|
||||||
|
UserPlus,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
FileClock,
|
FileClock,
|
||||||
@@ -21,7 +17,6 @@ import {
|
|||||||
Hash,
|
Hash,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { useAuthStore } from '@/stores/authStore'
|
import { useAuthStore } from '@/stores/authStore'
|
||||||
import { useSidebar } from '@/hooks/useSidebar'
|
import { useSidebar } from '@/hooks/useSidebar'
|
||||||
@@ -30,20 +25,32 @@ interface NavItem {
|
|||||||
label: string
|
label: string
|
||||||
href: string
|
href: string
|
||||||
icon: React.ElementType
|
icon: React.ElementType
|
||||||
disabled?: boolean
|
|
||||||
/** Si se define, el item solo se muestra si el user tiene este permiso. */
|
/** Si se define, el item solo se muestra si el user tiene este permiso. */
|
||||||
requiredPermission?: string
|
requiredPermission?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
interface NavSection {
|
||||||
{ label: 'Dashboard', href: '/', icon: LayoutDashboard },
|
label: string
|
||||||
{ label: 'Ventas', href: '/ventas', icon: ShoppingCart, disabled: true },
|
/** Si true, la sección solo se muestra si el user tiene rol admin. */
|
||||||
{ label: 'Tasación', href: '/tasacion', icon: Calculator, disabled: true },
|
adminOnly?: boolean
|
||||||
{ label: 'Integraciones', href: '/integraciones', icon: Zap, disabled: true },
|
items: NavItem[]
|
||||||
{ label: 'Administración', href: '/administracion', icon: Settings, disabled: true },
|
}
|
||||||
]
|
|
||||||
|
|
||||||
const adminItems: NavItem[] = [
|
// Item principal — siempre visible para usuarios autenticados
|
||||||
|
const dashboardItem: NavItem = {
|
||||||
|
label: 'Dashboard',
|
||||||
|
href: '/',
|
||||||
|
icon: LayoutDashboard,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secciones de navegación agrupadas por dominio.
|
||||||
|
// Orden: Seguridad → Maestros → Catálogo → Tasación (coincide con la arquitectura del proyecto).
|
||||||
|
// Cada sección se oculta si todos sus items están filtrados por permisos.
|
||||||
|
const navSections: NavSection[] = [
|
||||||
|
{
|
||||||
|
label: 'Seguridad',
|
||||||
|
adminOnly: true,
|
||||||
|
items: [
|
||||||
{ label: 'Usuarios', href: '/usuarios', icon: Users },
|
{ label: 'Usuarios', href: '/usuarios', icon: Users },
|
||||||
{ label: 'Crear Usuario', href: '/usuarios/nuevo', icon: UserPlus },
|
{ label: 'Crear Usuario', href: '/usuarios/nuevo', icon: UserPlus },
|
||||||
{ label: 'Roles', href: '/admin/roles', icon: ShieldCheck },
|
{ label: 'Roles', href: '/admin/roles', icon: ShieldCheck },
|
||||||
@@ -54,6 +61,12 @@ const adminItems: NavItem[] = [
|
|||||||
icon: FileClock,
|
icon: FileClock,
|
||||||
requiredPermission: 'administracion:auditoria:ver',
|
requiredPermission: 'administracion:auditoria:ver',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Maestros',
|
||||||
|
adminOnly: true,
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Medios',
|
label: 'Medios',
|
||||||
href: '/admin/medios',
|
href: '/admin/medios',
|
||||||
@@ -72,6 +85,12 @@ const adminItems: NavItem[] = [
|
|||||||
icon: Store,
|
icon: Store,
|
||||||
requiredPermission: 'administracion:puntos_de_venta:gestionar',
|
requiredPermission: 'administracion:puntos_de_venta:gestionar',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Catálogo',
|
||||||
|
adminOnly: true,
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Rubros',
|
label: 'Rubros',
|
||||||
href: '/admin/rubros',
|
href: '/admin/rubros',
|
||||||
@@ -90,12 +109,20 @@ const adminItems: NavItem[] = [
|
|||||||
icon: Package,
|
icon: Package,
|
||||||
requiredPermission: 'catalogo:productos:gestionar',
|
requiredPermission: 'catalogo:productos:gestionar',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tasación',
|
||||||
|
adminOnly: true,
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Caracteres Tasables',
|
label: 'Caracteres Tasables',
|
||||||
href: '/admin/tasacion/chargeable-chars',
|
href: '/admin/tasacion/chargeable-chars',
|
||||||
icon: Hash,
|
icon: Hash,
|
||||||
requiredPermission: 'tasacion:caracteres_especiales:gestionar',
|
requiredPermission: 'tasacion:caracteres_especiales:gestionar',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
interface SidebarNavProps {
|
interface SidebarNavProps {
|
||||||
@@ -111,7 +138,6 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
const collapsed = forceExpanded ? false : persisted
|
const collapsed = forceExpanded ? false : persisted
|
||||||
|
|
||||||
function isItemActive(item: NavItem): boolean {
|
function isItemActive(item: NavItem): boolean {
|
||||||
if (item.disabled) return false
|
|
||||||
if (item.href === '/') return pathname === '/'
|
if (item.href === '/') return pathname === '/'
|
||||||
if (item.href === '/usuarios') {
|
if (item.href === '/usuarios') {
|
||||||
return pathname.startsWith('/usuarios') && pathname !== '/usuarios/nuevo'
|
return pathname.startsWith('/usuarios') && pathname !== '/usuarios/nuevo'
|
||||||
@@ -120,6 +146,20 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
return pathname.startsWith(item.href)
|
return pathname.startsWith(item.href)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasAccess(item: NavItem): boolean {
|
||||||
|
return !item.requiredPermission || (user?.permisos.includes(item.requiredPermission) ?? false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computa las secciones visibles (con items filtrados por permiso).
|
||||||
|
// Si una sección queda sin items tras el filtro, se oculta el header.
|
||||||
|
const visibleSections = navSections
|
||||||
|
.filter((section) => !section.adminOnly || isAdmin)
|
||||||
|
.map((section) => ({
|
||||||
|
...section,
|
||||||
|
items: section.items.filter(hasAccess),
|
||||||
|
}))
|
||||||
|
.filter((section) => section.items.length > 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -128,7 +168,7 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
)}
|
)}
|
||||||
data-collapsed={collapsed}
|
data-collapsed={collapsed}
|
||||||
>
|
>
|
||||||
{/* Brand + Toggle (top header) */}
|
{/* Brand + Toggle */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-14 items-center border-b border-border shrink-0',
|
'flex h-14 items-center border-b border-border shrink-0',
|
||||||
@@ -166,25 +206,17 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
|
|
||||||
{/* Nav */}
|
{/* Nav */}
|
||||||
<nav className="flex-1 overflow-y-auto overflow-x-hidden py-3 px-2 space-y-1">
|
<nav className="flex-1 overflow-y-auto overflow-x-hidden py-3 px-2 space-y-1">
|
||||||
{navItems.map((item) => (
|
|
||||||
<NavRow
|
<NavRow
|
||||||
key={item.href}
|
item={dashboardItem}
|
||||||
item={item}
|
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
active={isItemActive(item)}
|
active={isItemActive(dashboardItem)}
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
|
|
||||||
{isAdmin && (
|
{visibleSections.map((section) => (
|
||||||
<>
|
<div key={section.label} className="pt-2">
|
||||||
<SectionLabel collapsed={collapsed}>Administración</SectionLabel>
|
<SectionLabel collapsed={collapsed}>{section.label}</SectionLabel>
|
||||||
{adminItems
|
<div className="space-y-1">
|
||||||
.filter(
|
{section.items.map((item) => (
|
||||||
(item) =>
|
|
||||||
!item.requiredPermission ||
|
|
||||||
user?.permisos.includes(item.requiredPermission),
|
|
||||||
)
|
|
||||||
.map((item) => (
|
|
||||||
<NavRow
|
<NavRow
|
||||||
key={item.href}
|
key={item.href}
|
||||||
item={item}
|
item={item}
|
||||||
@@ -192,8 +224,9 @@ export function SidebarNav({ forceExpanded = false }: SidebarNavProps) {
|
|||||||
active={isItemActive(item)}
|
active={isItemActive(item)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
@@ -217,41 +250,6 @@ function NavRow({ item, collapsed, active }: NavRowProps) {
|
|||||||
collapsed ? 'justify-center h-10 w-10 mx-auto' : 'gap-3 px-3 py-2',
|
collapsed ? 'justify-center h-10 w-10 mx-auto' : 'gap-3 px-3 py-2',
|
||||||
)
|
)
|
||||||
|
|
||||||
// Disabled item
|
|
||||||
if (item.disabled) {
|
|
||||||
const content = (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
baseClasses,
|
|
||||||
'text-muted-foreground/70 cursor-not-allowed opacity-60',
|
|
||||||
)}
|
|
||||||
aria-disabled="true"
|
|
||||||
>
|
|
||||||
<Icon className="h-4 w-4 shrink-0" />
|
|
||||||
{!collapsed && (
|
|
||||||
<>
|
|
||||||
<span className="flex-1 truncate">{item.label}</span>
|
|
||||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 shrink-0">
|
|
||||||
Próx.
|
|
||||||
</Badge>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!collapsed) return content
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
|
||||||
<TooltipContent side="right">
|
|
||||||
{item.label}{' '}
|
|
||||||
<span className="text-muted-foreground">· Próximamente</span>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Active link
|
|
||||||
const link = (
|
const link = (
|
||||||
<Link
|
<Link
|
||||||
to={item.href}
|
to={item.href}
|
||||||
@@ -263,7 +261,6 @@ function NavRow({ item, collapsed, active }: NavRowProps) {
|
|||||||
)}
|
)}
|
||||||
aria-current={active ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
>
|
>
|
||||||
{/* Active indicator bar (left edge) when expanded */}
|
|
||||||
{active && !collapsed && (
|
{active && !collapsed && (
|
||||||
<span className="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-0.5 rounded-r-full bg-primary" />
|
<span className="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-0.5 rounded-r-full bg-primary" />
|
||||||
)}
|
)}
|
||||||
@@ -292,7 +289,7 @@ function SectionLabel({
|
|||||||
return <div className="my-2 mx-2 border-t border-border" aria-hidden="true" />
|
return <div className="my-2 mx-2 border-t border-border" aria-hidden="true" />
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="pt-3 pb-1 px-3">
|
<div className="pt-1 pb-1 px-3">
|
||||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
161
src/web/src/components/layout/__tests__/AppSidebar.test.tsx
Normal file
161
src/web/src/components/layout/__tests__/AppSidebar.test.tsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest'
|
||||||
|
import { render, screen, within } from '@testing-library/react'
|
||||||
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
|
import { SidebarNav } from '../AppSidebar'
|
||||||
|
import { useAuthStore } from '@/stores/authStore'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estado inicial del authStore para cada test.
|
||||||
|
* El rol admin + set de permisos completos activa todas las secciones.
|
||||||
|
*/
|
||||||
|
function setUser(overrides: Partial<{ rol: string; permisos: string[] }> = {}) {
|
||||||
|
useAuthStore.setState({
|
||||||
|
user: {
|
||||||
|
id: 1,
|
||||||
|
username: 'admin',
|
||||||
|
nombre: 'Admin Test',
|
||||||
|
rol: 'admin',
|
||||||
|
permisos: [
|
||||||
|
'administracion:auditoria:ver',
|
||||||
|
'administracion:medios:gestionar',
|
||||||
|
'administracion:secciones:gestionar',
|
||||||
|
'administracion:puntos_de_venta:gestionar',
|
||||||
|
'catalogo:rubros:gestionar',
|
||||||
|
'catalogo:tipos:gestionar',
|
||||||
|
'catalogo:productos:gestionar',
|
||||||
|
'tasacion:caracteres_especiales:gestionar',
|
||||||
|
],
|
||||||
|
mustChangePassword: false,
|
||||||
|
...overrides,
|
||||||
|
} as never,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSidebar(initialPath = '/') {
|
||||||
|
return render(
|
||||||
|
<MemoryRouter initialEntries={[initialPath]}>
|
||||||
|
<SidebarNav forceExpanded />
|
||||||
|
</MemoryRouter>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AppSidebar', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setUser()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Dashboard visible para todo usuario autenticado', () => {
|
||||||
|
renderSidebar()
|
||||||
|
expect(screen.getByRole('link', { name: /dashboard/i })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('No muestra items disabled con badge "Próx." (limpieza)', () => {
|
||||||
|
renderSidebar()
|
||||||
|
// Los 4 items removidos del sidebar: Ventas, Tasación (nivel top), Integraciones, Administración (nivel top)
|
||||||
|
expect(screen.queryByText(/próx/i)).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('link', { name: /^ventas$/i })).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('link', { name: /^integraciones$/i })).not.toBeInTheDocument()
|
||||||
|
// "Administración" como link top-level también se elimina (queda solo como label de sección)
|
||||||
|
expect(screen.queryByRole('link', { name: /^administración$/i })).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Muestra las 4 secciones agrupadas para admin con todos los permisos', () => {
|
||||||
|
renderSidebar()
|
||||||
|
expect(screen.getByText('Seguridad')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Maestros')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Catálogo')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Tasación')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Cada item vive en la sección correcta', () => {
|
||||||
|
renderSidebar()
|
||||||
|
|
||||||
|
// Seguridad
|
||||||
|
expect(screen.getByRole('link', { name: /usuarios/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /crear usuario/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /roles/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /permisos/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /auditoría/i })).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Maestros
|
||||||
|
expect(screen.getByRole('link', { name: /^medios$/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /^secciones$/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /puntos de venta/i })).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Catálogo
|
||||||
|
expect(screen.getByRole('link', { name: /rubros/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /tipos de producto/i })).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('link', { name: /^productos$/i })).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Tasación
|
||||||
|
expect(screen.getByRole('link', { name: /caracteres tasables/i })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Oculta secciones sin items permitidos (ej: user sin permisos de catálogo)', () => {
|
||||||
|
setUser({
|
||||||
|
rol: 'admin',
|
||||||
|
permisos: [
|
||||||
|
// Solo permisos de Seguridad + Maestros
|
||||||
|
'administracion:medios:gestionar',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
renderSidebar()
|
||||||
|
|
||||||
|
// Seguridad se muestra (Usuarios/Crear Usuario/Roles/Permisos no requieren permiso custom)
|
||||||
|
expect(screen.getByText('Seguridad')).toBeInTheDocument()
|
||||||
|
// Maestros se muestra (tiene Medios con su permiso)
|
||||||
|
expect(screen.getByText('Maestros')).toBeInTheDocument()
|
||||||
|
// Catálogo desaparece (ningún permiso catalogo:*)
|
||||||
|
expect(screen.queryByText('Catálogo')).not.toBeInTheDocument()
|
||||||
|
// Tasación desaparece
|
||||||
|
expect(screen.queryByText('Tasación')).not.toBeInTheDocument()
|
||||||
|
|
||||||
|
// Items filtrados
|
||||||
|
expect(screen.queryByRole('link', { name: /rubros/i })).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('link', { name: /caracteres tasables/i })).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('link', { name: /^secciones$/i })).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Usuario no-admin no ve ninguna sección adminOnly', () => {
|
||||||
|
setUser({ rol: 'cajero', permisos: [] })
|
||||||
|
renderSidebar()
|
||||||
|
|
||||||
|
expect(screen.getByRole('link', { name: /dashboard/i })).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('Seguridad')).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('Maestros')).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('Catálogo')).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('Tasación')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Marca el item activo según la ruta actual (Caracteres Tasables)', () => {
|
||||||
|
renderSidebar('/admin/tasacion/chargeable-chars')
|
||||||
|
const link = screen.getByRole('link', { name: /caracteres tasables/i })
|
||||||
|
expect(link).toHaveAttribute('aria-current', 'page')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Dashboard activo solo en raíz exacta', () => {
|
||||||
|
const { unmount } = renderSidebar('/')
|
||||||
|
expect(screen.getByRole('link', { name: /dashboard/i })).toHaveAttribute('aria-current', 'page')
|
||||||
|
unmount()
|
||||||
|
|
||||||
|
renderSidebar('/admin/medios')
|
||||||
|
expect(screen.getByRole('link', { name: /dashboard/i })).not.toHaveAttribute('aria-current', 'page')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Header "SIG-CM 2.0" visible en modo expandido', () => {
|
||||||
|
renderSidebar()
|
||||||
|
expect(screen.getByText('SIG-CM 2.0')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Orden de secciones: Seguridad → Maestros → Catálogo → Tasación', () => {
|
||||||
|
renderSidebar()
|
||||||
|
const nav = screen.getByRole('navigation')
|
||||||
|
const labels = within(nav).getAllByText(/Seguridad|Maestros|Catálogo|Tasación/)
|
||||||
|
expect(labels.map((n) => n.textContent)).toEqual([
|
||||||
|
'Seguridad',
|
||||||
|
'Maestros',
|
||||||
|
'Catálogo',
|
||||||
|
'Tasación',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user