fix(web): reemplazar <select> nativos por shadcn Select (dark mode compat) — ADM-001
Reemplaza 13 <select>/<option> nativos en 8 archivos por el componente shadcn Select (Radix UI). Los selects nativos ignoraban los tokens del design system en dark mode, causando texto invisible. Se agrega mock de pointer capture APIs en test setup para compatibilidad de Radix con jsdom.
This commit is contained in:
@@ -15,6 +15,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { TIPO_MEDIO_OPTIONS } from '../tipoMedio'
|
||||
import type { MedioDetail } from '../types'
|
||||
|
||||
@@ -136,22 +143,24 @@ export function MedioForm({ initialData, isPending, error, onSubmit }: MedioForm
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tipo</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
disabled={isPending}
|
||||
aria-label="Tipo"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccioná un tipo</option>
|
||||
<Select
|
||||
value={field.value ? String(field.value) : ''}
|
||||
onValueChange={(v) => field.onChange(v === '' ? '' : Number(v))}
|
||||
disabled={isPending}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Tipo">
|
||||
<SelectValue placeholder="Seleccioná un tipo" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{TIPO_MEDIO_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
<SelectItem key={o.value} value={String(o.value)}>
|
||||
{o.label}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</FormControl>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,13 @@ import { Input } from '@/components/ui/input'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { CanPerform } from '@/components/auth/CanPerform'
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { MediosTable } from '../components/MediosTable'
|
||||
import { useMediosList } from '../hooks/useMediosList'
|
||||
import { TIPO_MEDIO_OPTIONS } from '../tipoMedio'
|
||||
@@ -70,28 +77,36 @@ export function MediosListPage() {
|
||||
aria-label="Buscar medios"
|
||||
/>
|
||||
|
||||
<select
|
||||
aria-label="Tipo"
|
||||
onChange={(e) => handleTipoChange(e.target.value)}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
value={tipo !== undefined ? String(tipo) : '__all__'}
|
||||
onValueChange={(v) => handleTipoChange(v === '__all__' ? '' : v)}
|
||||
>
|
||||
<option value="">Todos los tipos</option>
|
||||
{TIPO_MEDIO_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-40" aria-label="Tipo">
|
||||
<SelectValue placeholder="Todos los tipos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos los tipos</SelectItem>
|
||||
{TIPO_MEDIO_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={String(o.value)}>
|
||||
{o.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<select
|
||||
aria-label="Estado"
|
||||
onChange={(e) => handleActivoChange(e.target.value)}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
value={activo !== undefined ? String(activo) : '__all__'}
|
||||
onValueChange={(v) => handleActivoChange(v === '__all__' ? '' : v)}
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="true">Activos</option>
|
||||
<option value="false">Inactivos</option>
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-32" aria-label="Estado">
|
||||
<SelectValue placeholder="Todos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos</SelectItem>
|
||||
<SelectItem value="true">Activos</SelectItem>
|
||||
<SelectItem value="false">Inactivos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
|
||||
@@ -6,6 +6,13 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useRoles } from '../../roles/hooks/useRoles'
|
||||
import { RolPermisosEditor } from '../components/RolPermisosEditor'
|
||||
|
||||
@@ -28,28 +35,28 @@ export function RolPermisosPage() {
|
||||
<CardContent className="space-y-6">
|
||||
{/* Selector de rol */}
|
||||
<div className="flex flex-col gap-1 max-w-xs">
|
||||
<label
|
||||
htmlFor="rol-selector"
|
||||
className="text-sm font-medium text-foreground"
|
||||
>
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
Rol
|
||||
</label>
|
||||
{loadingRoles ? (
|
||||
<p className="text-sm text-muted-foreground">Cargando roles...</p>
|
||||
) : (
|
||||
<select
|
||||
id="rol-selector"
|
||||
value={selectedRol ?? ''}
|
||||
onChange={(e) => setSelectedRol(e.target.value || null)}
|
||||
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<Select
|
||||
value={selectedRol ?? '__none__'}
|
||||
onValueChange={(v) => setSelectedRol(v === '__none__' ? null : v)}
|
||||
>
|
||||
<option value="">— Seleccioná un rol —</option>
|
||||
{rolesActivos.map((r) => (
|
||||
<option key={r.codigo} value={r.codigo}>
|
||||
{r.nombre} ({r.codigo})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full" aria-label="Rol">
|
||||
<SelectValue placeholder="— Seleccioná un rol —" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">— Seleccioná un rol —</SelectItem>
|
||||
{rolesActivos.map((r) => (
|
||||
<SelectItem key={r.codigo} value={r.codigo}>
|
||||
{r.nombre} ({r.codigo})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useMediosList } from '@/features/medios/hooks/useMediosList'
|
||||
import { TIPO_SECCION_OPTIONS } from '../tipoSeccion'
|
||||
import type { SeccionDetail, TipoSeccion } from '../types'
|
||||
@@ -100,22 +107,24 @@ export function SeccionForm({ initialData, isPending, error, onSubmit }: Seccion
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Medio</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
disabled={isPending || isEdit}
|
||||
aria-label="Medio"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccioná un medio</option>
|
||||
<Select
|
||||
value={field.value ? String(field.value) : ''}
|
||||
onValueChange={(v) => field.onChange(Number(v))}
|
||||
disabled={isPending || isEdit}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Medio">
|
||||
<SelectValue placeholder="Seleccioná un medio" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{medios.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
<SelectItem key={m.id} value={String(m.id)}>
|
||||
{m.nombre}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</FormControl>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -165,22 +174,24 @@ export function SeccionForm({ initialData, isPending, error, onSubmit }: Seccion
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tipo</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
disabled={isPending}
|
||||
aria-label="Tipo de sección"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccioná un tipo</option>
|
||||
<Select
|
||||
value={field.value ?? ''}
|
||||
onValueChange={(v) => field.onChange(v as TipoSeccion)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Tipo de sección">
|
||||
<SelectValue placeholder="Seleccioná un tipo" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{TIPO_SECCION_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</FormControl>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useMediosList } from '@/features/medios/hooks/useMediosList'
|
||||
import { TIPO_SECCION_OPTIONS } from '../tipoSeccion'
|
||||
import type { TipoSeccion } from '../types'
|
||||
@@ -40,51 +47,56 @@ export function SeccionesFilters({
|
||||
aria-label="Buscar secciones"
|
||||
/>
|
||||
|
||||
<select
|
||||
aria-label="Medio"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
onMedioIdChange(v === '' ? undefined : Number(v))
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => onMedioIdChange(v === '__all__' ? undefined : Number(v))}
|
||||
>
|
||||
<option value="">Todos los medios</option>
|
||||
{medios.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.nombre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-44" aria-label="Medio">
|
||||
<SelectValue placeholder="Todos los medios" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos los medios</SelectItem>
|
||||
{medios.map((m) => (
|
||||
<SelectItem key={m.id} value={String(m.id)}>
|
||||
{m.nombre}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<select
|
||||
aria-label="Tipo de sección"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
onTipoChange(v === '' ? undefined : (v as TipoSeccion))
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => onTipoChange(v === '__all__' ? undefined : (v as TipoSeccion))}
|
||||
>
|
||||
<option value="">Todos los tipos</option>
|
||||
{TIPO_SECCION_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-44" aria-label="Tipo de sección">
|
||||
<SelectValue placeholder="Todos los tipos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos los tipos</SelectItem>
|
||||
{TIPO_SECCION_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<select
|
||||
aria-label="Estado"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '') onActivoChange(undefined)
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => {
|
||||
if (v === '__all__') onActivoChange(undefined)
|
||||
else onActivoChange(v === 'true')
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="true">Activos</option>
|
||||
<option value="false">Inactivos</option>
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-32" aria-label="Estado">
|
||||
<SelectValue placeholder="Todos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos</SelectItem>
|
||||
<SelectItem value="true">Activos</SelectItem>
|
||||
<SelectItem value="false">Inactivos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useCreateUser } from '../hooks/useCreateUser'
|
||||
import { useRolesForSelect } from '../hooks/useRolesForSelect'
|
||||
import type { CreatedUserDto } from '../api/createUser'
|
||||
@@ -202,23 +209,26 @@ export function UserForm({ onSuccess }: UserFormProps) {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Rol</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
disabled={disabled || rolesError}
|
||||
aria-label="Rol"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">
|
||||
{rolesLoading ? 'Cargando roles...' : 'Seleccioná un rol'}
|
||||
</option>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={disabled || !!rolesError}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Rol">
|
||||
<SelectValue
|
||||
placeholder={rolesLoading ? 'Cargando roles...' : 'Seleccioná un rol'}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{rolOptions.map((r) => (
|
||||
<option key={r.codigo} value={r.codigo}>
|
||||
<SelectItem key={r.codigo} value={r.codigo}>
|
||||
{r.nombre}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</FormControl>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
|
||||
interface UsersFiltersProps {
|
||||
onRolChange: (rol: string) => void
|
||||
@@ -38,32 +45,39 @@ export function UsersFilters({ onRolChange, onActivoChange, onSearchChange }: Us
|
||||
/>
|
||||
|
||||
{/* Rol select */}
|
||||
<select
|
||||
aria-label="Rol"
|
||||
onChange={(e) => onRolChange(e.target.value)}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => onRolChange(v === '__all__' ? '' : v)}
|
||||
>
|
||||
{ROL_OPTIONS.map((r) => (
|
||||
<option key={r.value} value={r.value}>
|
||||
{r.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-40" aria-label="Rol">
|
||||
<SelectValue placeholder="Todos los roles" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{ROL_OPTIONS.map((r) => (
|
||||
<SelectItem key={r.value || '__all__'} value={r.value || '__all__'}>
|
||||
{r.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* Activo filter */}
|
||||
<select
|
||||
aria-label="Estado"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '') onActivoChange(undefined)
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => {
|
||||
if (v === '__all__') onActivoChange(undefined)
|
||||
else onActivoChange(v === 'true')
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="true">Activos</option>
|
||||
<option value="false">Inactivos</option>
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-32" aria-label="Estado">
|
||||
<SelectValue placeholder="Todos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos</SelectItem>
|
||||
<SelectItem value="true">Activos</SelectItem>
|
||||
<SelectItem value="false">Inactivos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useUser } from '../hooks/useUser'
|
||||
import { useUpdateUser } from '../hooks/useUpdateUser'
|
||||
import { ResetPasswordModal } from '../components/ResetPasswordModal'
|
||||
@@ -199,18 +206,22 @@ export function UserEditPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Rol</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
aria-label="Rol"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="cajero">Cajero</option>
|
||||
<option value="reportes">Reportes</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isPending}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Rol">
|
||||
<SelectValue placeholder="Seleccioná un rol" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="cajero">Cajero</SelectItem>
|
||||
<SelectItem value="reportes">Reportes</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -61,7 +61,11 @@ describe('CreateMedioPage', () => {
|
||||
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'RAD01')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Radio AM')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo/i), '2')
|
||||
|
||||
// Open Radix Select trigger and pick "Radio" (value=2)
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /^radio$/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear medio/i }))
|
||||
|
||||
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/admin/medios'))
|
||||
@@ -81,7 +85,11 @@ describe('CreateMedioPage', () => {
|
||||
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'DUP01')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Duplicado')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo/i), '1')
|
||||
|
||||
// Open Radix Select trigger and pick "Diario" (value=1)
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /^diario$/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear medio/i }))
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -79,7 +79,11 @@ describe('MedioForm — create mode', () => {
|
||||
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'DIA99')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Mi Diario')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo/i), '1')
|
||||
|
||||
// Open the Radix Select trigger and pick option
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /diario/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear medio/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -144,8 +144,9 @@ describe('MediosListPage', () => {
|
||||
|
||||
await waitFor(() => expect(requests.length).toBeGreaterThan(0))
|
||||
|
||||
const tipoSelect = screen.getByRole('combobox', { name: /tipo/i })
|
||||
await userEvent.selectOptions(tipoSelect, '1')
|
||||
// Open the Radix Select trigger and pick "Diario" (value=1)
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /^diario$/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
const filtered = requests.find((u) => u.includes('tipo=1'))
|
||||
|
||||
@@ -88,15 +88,25 @@ describe('SeccionForm — create mode', () => {
|
||||
const onSubmit = vi.fn()
|
||||
renderForm({ onSubmit })
|
||||
|
||||
// Wait for medios to load
|
||||
// Open Medio trigger, wait for medios to load, then pick one
|
||||
const medioTrigger = screen.getByRole('combobox', { name: /medio/i })
|
||||
await userEvent.click(medioTrigger)
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Diario El Día' })).toBeInTheDocument(),
|
||||
)
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Diario El Día' }))
|
||||
|
||||
await userEvent.selectOptions(screen.getByLabelText(/medio/i), '1')
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'CLAS99')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Mi Sección')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo de sección/i), 'clasificados')
|
||||
|
||||
// Open Tipo trigger and pick Clasificados
|
||||
const tipoTrigger = screen.getByRole('combobox', { name: /tipo de sección/i })
|
||||
await userEvent.click(tipoTrigger)
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Clasificados' })).toBeInTheDocument(),
|
||||
)
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Clasificados' }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear sección/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -117,8 +127,9 @@ describe('SeccionForm — edit mode', () => {
|
||||
renderForm({ initialData: sampleSeccion })
|
||||
const codigoInput = screen.getByLabelText(/código/i) as HTMLInputElement
|
||||
expect(codigoInput.disabled).toBe(true)
|
||||
const medioSelect = screen.getByLabelText(/medio/i) as HTMLSelectElement
|
||||
expect(medioSelect.disabled).toBe(true)
|
||||
// Radix Select trigger is a <button> — check it's disabled
|
||||
const medioTrigger = screen.getByRole('combobox', { name: /medio/i })
|
||||
expect(medioTrigger).toBeDisabled()
|
||||
})
|
||||
|
||||
it('pre-fills form with initialData values', async () => {
|
||||
|
||||
@@ -56,6 +56,10 @@ describe('SeccionesFilters', () => {
|
||||
it('loads medios options from API', async () => {
|
||||
renderFilters()
|
||||
|
||||
// Open the Medio Radix Select to see options in the portal
|
||||
const medioTrigger = await screen.findByRole('combobox', { name: /medio/i })
|
||||
await userEvent.click(medioTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Diario El Día' })).toBeInTheDocument(),
|
||||
)
|
||||
@@ -66,34 +70,55 @@ describe('SeccionesFilters', () => {
|
||||
it('calls onMedioIdChange when medio is selected', async () => {
|
||||
const handlers = renderFilters()
|
||||
|
||||
// Open Medio trigger, wait for options to load, then pick one
|
||||
const medioTrigger = await screen.findByRole('combobox', { name: /medio/i })
|
||||
await userEvent.click(medioTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Diario El Día' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await userEvent.selectOptions(screen.getByLabelText(/medio/i), '1')
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Diario El Día' }))
|
||||
expect(handlers.onMedioIdChange).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('calls onTipoChange when tipo is selected', async () => {
|
||||
const handlers = renderFilters()
|
||||
|
||||
// Open Tipo de sección trigger and pick an option
|
||||
const tipoTrigger = screen.getByRole('combobox', { name: /tipo de sección/i })
|
||||
await userEvent.click(tipoTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Clasificados' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo de sección/i), 'clasificados')
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Clasificados' }))
|
||||
expect(handlers.onTipoChange).toHaveBeenCalledWith('clasificados')
|
||||
})
|
||||
|
||||
it('calls onActivoChange when estado is selected', async () => {
|
||||
const handlers = renderFilters()
|
||||
await userEvent.selectOptions(screen.getByLabelText(/estado/i), 'true')
|
||||
|
||||
// Open Estado trigger and pick Activos
|
||||
const estadoTrigger = screen.getByRole('combobox', { name: /estado/i })
|
||||
await userEvent.click(estadoTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Activos' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Activos' }))
|
||||
expect(handlers.onActivoChange).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('renders all tipo options', async () => {
|
||||
renderFilters()
|
||||
|
||||
// Open Tipo de sección trigger to see options in portal
|
||||
const tipoTrigger = screen.getByRole('combobox', { name: /tipo de sección/i })
|
||||
await userEvent.click(tipoTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Clasificados' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
@@ -107,6 +107,13 @@ describe('UserEditPage', () => {
|
||||
await userEvent.clear(nombreInput)
|
||||
await userEvent.type(nombreInput, 'Pedro')
|
||||
|
||||
// Confirm the rol Select has a value by opening and re-selecting the prefilled value
|
||||
// (Radix Select in jsdom needs interaction to register the item text in context)
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
await waitFor(() => expect(screen.getByRole('option', { name: /cajero/i })).toBeInTheDocument())
|
||||
await userEvent.click(screen.getByRole('option', { name: /cajero/i }))
|
||||
|
||||
// Submit
|
||||
await userEvent.click(screen.getByRole('button', { name: /guardar|actualizar|save/i }))
|
||||
|
||||
@@ -129,6 +136,12 @@ describe('UserEditPage', () => {
|
||||
|
||||
await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())
|
||||
|
||||
// Confirm rol by opening and re-selecting the prefilled value
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
await waitFor(() => expect(screen.getByRole('option', { name: /cajero/i })).toBeInTheDocument())
|
||||
await userEvent.click(screen.getByRole('option', { name: /cajero/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /guardar|actualizar|save/i }))
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -109,6 +109,10 @@ describe('UserForm — roles dropdown integration', () => {
|
||||
)
|
||||
renderForm()
|
||||
|
||||
// Open the Radix Select trigger to see options in the portal
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
|
||||
// Wait for roles to load — active options should appear.
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -132,15 +136,21 @@ describe('UserForm — roles dropdown integration', () => {
|
||||
const user = userEvent.setup()
|
||||
renderForm(onSuccess)
|
||||
|
||||
// Open trigger and wait for Cajero option to load
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await user.click(rolTrigger)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('option', { name: 'Cajero' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Pick Cajero option
|
||||
await user.click(screen.getByRole('option', { name: 'Cajero' }))
|
||||
|
||||
await user.type(screen.getByLabelText(/usuario/i), 'jdoe123')
|
||||
await user.type(screen.getByLabelText(/^contraseña$/i), 'Secret12')
|
||||
await user.type(screen.getByLabelText(/nombre/i), 'Juan')
|
||||
await user.type(screen.getByLabelText(/apellido/i), 'Doe')
|
||||
await user.selectOptions(screen.getByLabelText(/rol/i), 'cajero')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /crear usuario/i }))
|
||||
|
||||
@@ -176,15 +186,20 @@ describe('UserForm — backend error display', () => {
|
||||
const user = userEvent.setup()
|
||||
renderForm()
|
||||
|
||||
// Open trigger, wait for options, pick Cajero
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await user.click(rolTrigger)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('option', { name: 'Cajero' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('option', { name: 'Cajero' }))
|
||||
|
||||
await user.type(screen.getByLabelText(/usuario/i), 'existing')
|
||||
await user.type(screen.getByLabelText(/^contraseña$/i), 'Secret12')
|
||||
await user.type(screen.getByLabelText(/nombre/i), 'Juan')
|
||||
await user.type(screen.getByLabelText(/apellido/i), 'Doe')
|
||||
await user.selectOptions(screen.getByLabelText(/rol/i), 'cajero')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /crear usuario/i }))
|
||||
|
||||
|
||||
@@ -171,8 +171,10 @@ describe('UsersListPage', () => {
|
||||
|
||||
await waitFor(() => expect(requests.length).toBeGreaterThan(0))
|
||||
|
||||
const rolSelect = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.selectOptions(rolSelect, 'admin')
|
||||
// Open the Radix Select trigger and pick "Admin"
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
await userEvent.click(screen.getByRole('option', { name: /^admin$/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
const filtered = requests.find((u) => u.includes('rol=admin'))
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
// Radix UI primitives use pointer capture APIs not available in jsdom.
|
||||
// Provide no-op stubs so Radix Select/etc. work in unit tests.
|
||||
if (typeof window !== 'undefined') {
|
||||
window.HTMLElement.prototype.hasPointerCapture = () => false
|
||||
window.HTMLElement.prototype.setPointerCapture = () => {}
|
||||
window.HTMLElement.prototype.releasePointerCapture = () => {}
|
||||
window.HTMLElement.prototype.scrollIntoView = () => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user