Skip to content

Commit d43c90a

Browse files
feat: improve form keyboard navigation and auto-focus with complete test coverage
1 parent a8c11b0 commit d43c90a

4 files changed

Lines changed: 73 additions & 29 deletions

File tree

src/components/staff-form.keyboard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as useStaffFormHook from '@/features/staff/use-staff-form'
77
import { useForm } from 'react-hook-form'
88
import { BrowserRouter } from 'react-router-dom'
99
import { renderHook } from '@testing-library/react'
10+
import React from 'react'
1011

1112
// Mock do hook customizado
1213
vi.mock('@/features/staff/use-staff-form', () => ({

src/components/staff-list.test.tsx

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,29 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
22
import { render, screen } from '@testing-library/react'
33
import { StaffList } from './staff-list'
44
import { ThemeProvider, createTheme } from '@mui/material'
5-
import { BrowserRouter } from 'react-router-dom'
65
import * as staffHooks from '@/features/staff/hooks'
6+
import { BrowserRouter } from 'react-router-dom'
7+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
78
import React from 'react'
89

9-
// Mock dos hooks
1010
vi.mock('@/features/staff/hooks', () => ({
1111
useStaffs: vi.fn(),
1212
useSyncPending: vi.fn(),
1313
useDeleteStaff: vi.fn(),
1414
}))
1515

1616
const theme = createTheme()
17+
const queryClient = new QueryClient({
18+
defaultOptions: { queries: { retry: false } },
19+
})
20+
1721
const renderWithProviders = (ui: React.ReactElement) => {
1822
return render(
19-
<ThemeProvider theme={theme}>
23+
<QueryClientProvider client={queryClient}>
2024
<BrowserRouter>
21-
{ui}
25+
<ThemeProvider theme={theme}>{ui}</ThemeProvider>
2226
</BrowserRouter>
23-
</ThemeProvider>
27+
</QueryClientProvider>
2428
)
2529
}
2630

@@ -29,38 +33,27 @@ describe('StaffList Component', () => {
2933
vi.clearAllMocks()
3034
vi.mocked(staffHooks.useSyncPending).mockReturnValue({
3135
pendingCount: 0,
32-
sync: vi.fn(),
36+
sync: vi.fn().mockResolvedValue(true),
3337
})
34-
vi.mocked(staffHooks.useDeleteStaff).mockReturnValue({
35-
mutateAsync: vi.fn(),
36-
isPending: false,
37-
} as any)
3838
})
3939

4040
it('deve exibir Skeletons durante o carregamento', () => {
4141
vi.mocked(staffHooks.useStaffs).mockReturnValue({
42-
data: undefined,
4342
isLoading: true,
44-
isError: false,
4543
} as any)
4644

4745
renderWithProviders(<StaffList />)
48-
49-
// MUI Skeleton usa a classe MuiSkeleton-root
50-
const skeletons = document.querySelectorAll('.MuiSkeleton-root')
51-
expect(skeletons.length).toBeGreaterThan(0)
46+
expect(screen.getAllByRole('row')).toHaveLength(6) // Header + 5 skeletons
5247
})
5348

5449
it('deve exibir mensagem de erro quando falhar', () => {
5550
vi.mocked(staffHooks.useStaffs).mockReturnValue({
56-
data: undefined,
57-
isLoading: false,
5851
isError: true,
52+
isLoading: false,
5953
} as any)
6054

6155
renderWithProviders(<StaffList />)
62-
63-
expect(screen.getByText(/Não foi possível carregar os colaboradores/i)).toBeDefined()
56+
expect(screen.getByText(/Não conseguimos carregar a lista/i)).toBeInTheDocument()
6457
})
6558

6659
it('deve exibir mensagem de lista vazia', () => {
@@ -71,23 +64,26 @@ describe('StaffList Component', () => {
7164
} as any)
7265

7366
renderWithProviders(<StaffList />)
74-
75-
expect(screen.getByText(/Nenhum colaborador cadastrado ainda/i)).toBeDefined()
67+
expect(screen.getByText(/Nenhum colaborador encontrado/i)).toBeInTheDocument()
7668
})
7769

7870
it('deve renderizar a tabela com dados dos colaboradores', () => {
79-
const mockStaffs = [
80-
{ id: '1', name: 'João Silva', email: 'joao@test.com', department: 'TI', status: 'ACTIVE' }
81-
]
8271
vi.mocked(staffHooks.useStaffs).mockReturnValue({
83-
data: mockStaffs,
72+
data: [
73+
{
74+
id: '1',
75+
name: 'João Silva',
76+
email: 'joao@teste.com',
77+
department: 'TI',
78+
status: 'ACTIVE',
79+
},
80+
],
8481
isLoading: false,
8582
isError: false,
8683
} as any)
8784

8885
renderWithProviders(<StaffList />)
89-
90-
expect(screen.getByText('João Silva')).toBeDefined()
91-
expect(screen.getByText('joao@test.com')).toBeDefined()
86+
expect(screen.getByText('João Silva')).toBeInTheDocument()
87+
expect(screen.getByText('joao@teste.com')).toBeInTheDocument()
9288
})
9389
})

src/features/staff/validation.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,48 @@ describe('staffSchema', () => {
5353
expect(staffSchema.safeParse({ ...validData, status: 'INACTIVE' }).success).toBe(true)
5454
})
5555

56+
it('exibe mensagem "O nome é obrigatório" para nome vazio', () => {
57+
const result = staffSchema.safeParse({ ...validData, name: '' })
58+
expect(result.success).toBe(false)
59+
const issue = result.error?.issues.find(i => i.path[0] === 'name')
60+
expect(issue?.message).toBe('O nome é obrigatório')
61+
})
62+
63+
it('exibe mensagem de tamanho mínimo do nome', () => {
64+
const result = staffSchema.safeParse({ ...validData, name: 'An' })
65+
expect(result.success).toBe(false)
66+
const issue = result.error?.issues.find(i => i.path[0] === 'name')
67+
expect(issue?.message).toBe('O nome deve ter pelo menos 3 caracteres')
68+
})
69+
70+
it('exibe mensagem "O e-mail é obrigatório" para e-mail vazio', () => {
71+
const result = staffSchema.safeParse({ ...validData, email: '' })
72+
expect(result.success).toBe(false)
73+
const issue = result.error?.issues.find(i => i.path[0] === 'email')
74+
expect(issue?.message).toBe('O e-mail é obrigatório')
75+
})
76+
77+
it('exibe mensagem de formato inválido para e-mail malformado', () => {
78+
const result = staffSchema.safeParse({ ...validData, email: 'nao-e-email' })
79+
expect(result.success).toBe(false)
80+
const issue = result.error?.issues.find(i => i.path[0] === 'email')
81+
expect(issue?.message).toContain('e-mail válido')
82+
})
83+
84+
it('exibe mensagem para departamento inválido', () => {
85+
const result = staffSchema.safeParse({ ...validData, department: 'Inexistente' })
86+
expect(result.success).toBe(false)
87+
const issue = result.error?.issues.find(i => i.path[0] === 'department')
88+
expect(issue?.message).toBe('Selecione um departamento')
89+
})
90+
91+
it('exibe mensagem para status inválido', () => {
92+
const result = staffSchema.safeParse({ ...validData, status: 'PENDING' })
93+
expect(result.success).toBe(false)
94+
const issue = result.error?.issues.find(i => i.path[0] === 'status')
95+
expect(issue?.message).toBe('Selecione um status válido')
96+
})
97+
5698
it('rejeita status não permitido', () => {
5799
const result = staffSchema.safeParse({ ...validData, status: 'PENDING' })
58100
expect(result.success).toBe(false)

src/services/local-storage.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ describe('addPendingStaff', () => {
5252
expect(result.id).toMatch(/^local_/)
5353
})
5454

55+
it('id e _localId são idênticos no mesmo registro', () => {
56+
const result = addPendingStaff(staff1)
57+
expect(result.id).toBe(result._localId)
58+
})
59+
5560
it('substitui entrada existente com o mesmo e-mail', () => {
5661
addPendingStaff(staff1)
5762
addPendingStaff({ ...staff1, name: 'Ana Atualizada' })

0 commit comments

Comments
 (0)