Merge branch 'main' into feature/UDT-011
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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: [] })
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user