Compare commits

..

7 Commits

10 changed files with 16 additions and 49 deletions

View File

@@ -2,7 +2,8 @@ import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button" import type { ButtonProps } from "@/components/ui/button"
import { buttonVariants } from "@/components/ui/button"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav <nav

View File

@@ -34,11 +34,10 @@ const medioFormSchema = z.object({
.string() .string()
.min(1, 'El nombre es requerido') .min(1, 'El nombre es requerido')
.max(100, 'Máximo 100 caracteres'), .max(100, 'Máximo 100 caracteres'),
tipo: z.coerce.number().refine((v) => v >= 1, 'Seleccioná un tipo válido'), tipo: z.coerce.number<number>().refine((v) => v >= 1, 'Seleccioná un tipo válido'),
plataformaEmpresaId: z plataformaEmpresaId: z
.union([z.coerce.number().int().positive('Debe ser un número positivo'), z.literal('')]) .union([z.coerce.number<number>().int().positive('Debe ser un número positivo'), z.literal('')])
.optional() .optional(),
.transform((v) => (v === '' || v === undefined ? null : Number(v))),
}) })
export type MedioFormValues = z.infer<typeof medioFormSchema> export type MedioFormValues = z.infer<typeof medioFormSchema>

View File

@@ -29,7 +29,7 @@ import type { SeccionDetail, TipoSeccion } from '../types'
const TIPO_SECCION_VALUES = ['clasificados', 'notables', 'suplementos'] as const const TIPO_SECCION_VALUES = ['clasificados', 'notables', 'suplementos'] as const
const seccionFormSchema = z.object({ const seccionFormSchema = z.object({
medioId: z.coerce.number().refine((v) => v >= 1, 'Seleccioná un medio'), medioId: z.coerce.number<number>().refine((v) => v >= 1, 'Seleccioná un medio'),
codigo: z codigo: z
.string() .string()
.min(1, 'El código es requerido') .min(1, 'El código es requerido')
@@ -38,7 +38,7 @@ const seccionFormSchema = z.object({
.string() .string()
.min(1, 'El nombre es requerido') .min(1, 'El nombre es requerido')
.max(100, 'Máximo 100 caracteres'), .max(100, 'Máximo 100 caracteres'),
tipo: z.enum(TIPO_SECCION_VALUES, { errorMap: () => ({ message: 'Seleccioná un tipo válido' }) }), tipo: z.enum(TIPO_SECCION_VALUES, { error: 'Seleccioná un tipo válido' }),
}) })
export type SeccionFormValues = z.infer<typeof seccionFormSchema> export type SeccionFormValues = z.infer<typeof seccionFormSchema>

View File

@@ -5,42 +5,10 @@ import { useAuthStore } from '../../../stores/authStore'
import { ProtectedRoute } from '../../../components/routing/ProtectedRoute' import { ProtectedRoute } from '../../../components/routing/ProtectedRoute'
// Helper components for testing // Helper components for testing
function HomePage() {
return <div>Home Page</div>
}
function SecurePage() { function SecurePage() {
return <div>Secure Page</div> return <div>Secure Page</div>
} }
// Renders ProtectedRoute at a given path with optional navigation target capture
function renderProtected(
props: {
requiredRoles?: string[]
requiredPermissions?: string[]
children?: React.ReactNode
},
{ initialPath = '/' }: { initialPath?: string } = {},
) {
const { children, ...routeProps } = props
return render(
<MemoryRouter initialEntries={[initialPath]}>
<Routes>
<Route path="/login" element={<div>Login Page</div>} />
<Route path="/" element={<div>Root</div>} />
<Route
path="/secure"
element={
<ProtectedRoute {...routeProps}>
{children ?? <SecurePage />}
</ProtectedRoute>
}
/>
</Routes>
</MemoryRouter>,
)
}
beforeEach(() => { beforeEach(() => {
useAuthStore.setState({ user: null, accessToken: null, refreshToken: null, expiresAt: null }) useAuthStore.setState({ user: null, accessToken: null, refreshToken: null, expiresAt: null })
}) })

View File

@@ -1,7 +1,7 @@
// T600.5 — TDD: TipoDeIvaFormModal // T600.5 — TDD: TipoDeIvaFormModal
// CRÍTICO: verifica que el modal de Editar NO tiene campo Porcentaje [REQ-UI-003] // CRÍTICO: verifica que el modal de Editar NO tiene campo Porcentaje [REQ-UI-003]
// T600.10 — BUG-FE-03 regression: default vigenciaDesde usa todayArgentina (no UTC) // T600.10 — BUG-FE-03 regression: default vigenciaDesde usa todayArgentina (no UTC)
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest' import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react' import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest' import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react' import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

View File

@@ -80,7 +80,7 @@ describe('SeccionForm — create mode', () => {
await userEvent.type(screen.getByLabelText(/nombre/i), 'Mi Sección') await userEvent.type(screen.getByLabelText(/nombre/i), 'Mi Sección')
await userEvent.click(screen.getByRole('button', { name: /crear sección/i })) await userEvent.click(screen.getByRole('button', { name: /crear sección/i }))
await waitFor(() => await waitFor(() =>
expect(screen.getByText(/seleccioná un tipo/i)).toBeInTheDocument(), expect(screen.getByText(/seleccioná un tipo válido/i)).toBeInTheDocument(),
) )
}) })

View File

@@ -1,6 +1,5 @@
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest' import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react' import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw' import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node' import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest' import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest'
import { render, screen, waitFor, within } from '@testing-library/react' import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw' import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node' import { setupServer } from 'msw/node'

View File

@@ -6,11 +6,11 @@ import { updateUserPermisosOverrides } from '../../../features/users/api/updateU
const API_URL = 'http://localhost:5000' const API_URL = 'http://localhost:5000'
const mockResponse = { const mockResponse = {
usuarioId: 42,
rol: 'cajero',
rolPermisos: ['ventas:contado:crear'], rolPermisos: ['ventas:contado:crear'],
overrides: {
grant: ['textos:editar'], grant: ['textos:editar'],
deny: [], deny: [],
},
effective: ['ventas:contado:crear', 'textos:editar'], effective: ['ventas:contado:crear', 'textos:editar'],
} }
@@ -35,7 +35,7 @@ describe('updateUserPermisosOverrides api client', () => {
deny: [], deny: [],
}) })
expect(result.grant).toEqual(['textos:editar']) expect(result.overrides.grant).toEqual(['textos:editar'])
expect(result.effective).toContain('textos:editar') expect(result.effective).toContain('textos:editar')
expect(capturedBody).toMatchObject({ grant: ['textos:editar'], deny: [] }) expect(capturedBody).toMatchObject({ grant: ['textos:editar'], deny: [] })
}) })