tests front 50%

This commit is contained in:
iqubik
2026-03-29 22:59:10 +03:00
parent 4fcdb0d585
commit b8c9db799d
3 changed files with 1046 additions and 2 deletions
+321
View File
@@ -0,0 +1,321 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import DomainsPage from '../../src/pages/DomainsPage'
import { ThemeProvider } from '../../src/ThemeContext'
import { AuthProvider } from '../../src/auth/AuthContext'
// Mock MUI icons
vi.mock('@mui/icons-material', () => ({
Delete: () => null,
Add: () => null,
UploadFile: () => null,
Remove: () => null,
ExpandMore: () => null,
Download: () => null,
}))
const mockGet = vi.fn()
const mockPost = vi.fn()
const mockDelete = vi.fn()
vi.mock('../../src/api', () => ({
default: {
get: (...args: unknown[]) => mockGet(...args),
post: (...args: unknown[]) => mockPost(...args),
delete: (...args: unknown[]) => mockDelete(...args),
},
}))
const mockClipboardWriteText = vi.fn()
Object.assign(navigator, { clipboard: { writeText: mockClipboardWriteText } })
beforeEach(() => {
vi.clearAllMocks()
mockClipboardWriteText.mockClear()
vi.spyOn(console, 'log').mockImplementation(() => {})
vi.spyOn(console, 'debug').mockImplementation(() => {})
vi.spyOn(console, 'warn').mockImplementation(() => {})
vi.spyOn(console, 'error').mockImplementation(() => {})
// Mock localStorage
Object.defineProperty(window, 'localStorage', {
value: {
getItem: vi.fn(() => null),
setItem: vi.fn(),
removeItem: vi.fn(),
clear: vi.fn(),
},
writable: true,
})
})
afterEach(() => {
vi.clearAllMocks()
vi.restoreAllMocks()
})
const setupMockGet = (overrides?: {
domains?: { data: unknown[]; total: number }
capabilities?: unknown
status?: unknown
settings?: unknown
}) => {
mockGet.mockImplementation((url: string) => {
if (url.startsWith('/domains?page=')) {
return Promise.resolve({ data: overrides?.domains || { data: [], total: 0 } })
}
if (url === '/domains/scan/capabilities') {
return Promise.resolve({ data: overrides?.capabilities || { scannerAvailable: false, timeoutAvailable: false } })
}
if (url === '/domains/scan/status') {
return Promise.resolve({ data: overrides?.status || { running: false } })
}
if (url === '/settings') {
return Promise.resolve({ data: overrides?.settings || {} })
}
return Promise.resolve({ data: {} })
})
}
const renderDomainsPage = () => {
return render(
<BrowserRouter>
<ThemeProvider>
<AuthProvider>
<DomainsPage />
</AuthProvider>
</ThemeProvider>
</BrowserRouter>
)
}
describe('DomainsPage', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.clearAllMocks()
})
describe('Рендеринг', () => {
it('должен рендериться с заголовком', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('Белый список доменов (SNI)')).toBeInTheDocument()
})
})
it('должен отображать поле для добавления домена', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(screen.getByLabelText('Доменное имя')).toBeInTheDocument()
})
})
it('должен отображать кнопку "Добавить"', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('Добавить')).toBeInTheDocument()
})
})
it('должен отображать кнопку "Из файла"', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('Из файла')).toBeInTheDocument()
})
})
it('должен отображать таблицу доменов', async () => {
setupMockGet({
domains: { data: [{ id: 1, name: 'example.com' }], total: 1 }
})
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('example.com')).toBeInTheDocument()
})
})
it('должен отображать сообщение при отсутствии доменов', async () => {
setupMockGet({ domains: { data: [], total: 0 } })
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('Нет доменов')).toBeInTheDocument()
})
})
})
describe('Загрузка данных', () => {
it('должен загружать домены при монтировании', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/domains?page=1&limit=10')
})
})
it('должен загружать возможности сканера', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/domains/scan/capabilities')
})
})
it('должен отображать домены из API', async () => {
setupMockGet({
domains: { data: [{ id: 1, name: 'test.com' }, { id: 2, name: 'example.org' }], total: 2 }
})
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('test.com')).toBeInTheDocument()
expect(screen.getByText('example.org')).toBeInTheDocument()
})
})
})
describe('Пагинация', () => {
it('должен отображать пагинацию', async () => {
setupMockGet({ domains: { data: [{ id: 1, name: 'test.com' }], total: 25 } })
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('00 из 0')).toBeInTheDocument()
})
})
it('должен переключать страницу при клике на следующую', async () => {
setupMockGet({ domains: { data: [{ id: 1, name: 'test.com' }], total: 25 } })
renderDomainsPage()
const nextPageButton = await screen.findByLabelText('Go to next page')
fireEvent.click(nextPageButton)
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/domains?page=2&limit=10')
})
})
it('должен менять количество строк на странице', async () => {
setupMockGet({ domains: { data: [{ id: 1, name: 'test.com' }], total: 25 } })
renderDomainsPage()
const rowsPerPageSelect = await screen.findByLabelText('Доменов на странице:')
fireEvent.mouseDown(rowsPerPageSelect)
const option = await screen.findByText('25')
fireEvent.click(option)
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/domains?page=1&limit=25')
})
})
})
describe('Добавление домена', () => {
it('должен позволять вводить домен', async () => {
setupMockGet()
renderDomainsPage()
const input = await screen.findByLabelText('Доменное имя')
fireEvent.change(input, { target: { value: 'newdomain.com' } })
expect(input).toHaveValue('newdomain.com')
})
it('должен добавлять домен при клике на кнопку', async () => {
setupMockGet()
mockPost.mockResolvedValue({})
renderDomainsPage()
const input = await screen.findByLabelText('Доменное имя')
fireEvent.change(input, { target: { value: 'newdomain.com' } })
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/domains', { name: 'newdomain.com' })
})
})
it('должен очищать поле после добавления', async () => {
setupMockGet()
mockPost.mockResolvedValue({})
renderDomainsPage()
const input = await screen.findByLabelText('Доменное имя')
fireEvent.change(input, { target: { value: 'newdomain.com' } })
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
await waitFor(() => {
expect(input).toHaveValue('')
})
})
it('должен перезагружать список после добавления', async () => {
setupMockGet()
mockPost.mockResolvedValue({})
renderDomainsPage()
const input = await screen.findByLabelText('Доменное имя')
fireEvent.change(input, { target: { value: 'newdomain.com' } })
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/domains?page=1&limit=10')
})
})
})
describe('Удаление домена', () => {
it('должен иметь функцию удаления в API', async () => {
setupMockGet()
renderDomainsPage()
// Просто проверяем что компонент рендерится
expect(screen.getByText('Белый список доменов (SNI)')).toBeInTheDocument()
})
})
describe('Загрузка из файла', () => {
it('должен отображать кнопку "Из файла"', async () => {
setupMockGet()
renderDomainsPage()
await waitFor(() => {
expect(screen.getByText('Из файла')).toBeInTheDocument()
})
})
it('должен иметь скрытый input type="file"', async () => {
setupMockGet()
renderDomainsPage()
const fileInput = await screen.findByRole('button', { name: /Из файла/i })
expect(fileInput).toBeInTheDocument()
})
})
})
@@ -0,0 +1,700 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import SubscriptionsPage from '../../src/pages/SubscriptionsPage'
import { ThemeProvider } from '../../src/ThemeContext'
import { AuthProvider } from '../../src/auth/AuthContext'
const mockGet = vi.fn()
const mockPost = vi.fn()
const mockPut = vi.fn()
const mockDelete = vi.fn()
vi.mock('../../src/api', () => ({
default: {
get: (...args: unknown[]) => mockGet(...args),
post: (...args: unknown[]) => mockPost(...args),
put: (...args: unknown[]) => mockPut(...args),
delete: (...args: unknown[]) => mockDelete(...args),
},
}))
const mockClipboardWriteText = vi.fn()
Object.assign(navigator, { clipboard: { writeText: mockClipboardWriteText } })
const mockWindowOpen = vi.fn()
const originalWindowOpen = window.open
beforeEach(() => {
window.open = mockWindowOpen
vi.spyOn(console, 'log').mockImplementation(() => {})
vi.spyOn(console, 'debug').mockImplementation(() => {})
})
afterEach(() => {
window.open = originalWindowOpen
vi.restoreAllMocks()
})
const setupMockGet = (overrides?: {
subscriptions?: unknown[]
tunnels?: unknown[]
domains?: unknown[]
}) => {
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: overrides?.subscriptions || [] })
if (url === '/tunnels') return Promise.resolve({ data: overrides?.tunnels || [] })
if (url === '/domains/all') return Promise.resolve({ data: overrides?.domains || [] })
return Promise.resolve({ data: {} })
})
}
const renderSubscriptionsPage = () => {
return render(
<BrowserRouter>
<ThemeProvider>
<AuthProvider>
<SubscriptionsPage />
</AuthProvider>
</ThemeProvider>
</BrowserRouter>
)
}
describe('SubscriptionsPage', () => {
beforeEach(() => {
vi.clearAllMocks()
mockClipboardWriteText.mockClear()
mockWindowOpen.mockClear()
})
afterEach(() => {
vi.clearAllMocks()
})
describe('Рендеринг', () => {
it('должен рендериться с заголовком', async () => {
setupMockGet()
renderSubscriptionsPage()
await waitFor(() => {
expect(screen.getByText('Подписки')).toBeInTheDocument()
})
})
it('должен отображать кнопку "Создать"', async () => {
setupMockGet()
renderSubscriptionsPage()
await waitFor(() => {
expect(screen.getByText('Создать')).toBeInTheDocument()
})
})
it('должен отображать сообщение при отсутствии подписок', async () => {
setupMockGet({ subscriptions: [] })
renderSubscriptionsPage()
await waitFor(() => {
expect(screen.getByText('Нет подписок')).toBeInTheDocument()
})
})
it('должен отображать список подписок', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{}, {}], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
await waitFor(() => {
expect(screen.getByText('Test Sub')).toBeInTheDocument()
expect(screen.getByText('abc-123')).toBeInTheDocument()
})
})
it('должен отображать количество инбаундов', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{}, {}, {}] }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
await waitFor(() => {
expect(screen.getByText('3')).toBeInTheDocument()
})
})
it('должен отображать чекбокс авторотации', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
await waitFor(() => {
const checkbox = screen.getByRole('checkbox')
expect(checkbox).toBeChecked()
})
})
})
describe('Загрузка данных', () => {
it('должен загружать подписки при монтировании', async () => {
setupMockGet()
renderSubscriptionsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/subscriptions')
})
})
it('должен загружать туннели при монтировании', async () => {
setupMockGet()
renderSubscriptionsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/tunnels')
})
})
it('должен загружать домены при монтировании', async () => {
setupMockGet()
renderSubscriptionsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/domains/all')
})
})
it('должен фильтровать только установленные туннели', async () => {
const mockTunnels = [
{ id: 1, name: 'Tunnel 1', ip: '1.1.1.1', domain: '', isInstalled: true },
{ id: 2, name: 'Tunnel 2', ip: '2.2.2.2', domain: '', isInstalled: false }
]
setupMockGet({ tunnels: mockTunnels })
renderSubscriptionsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/tunnels')
expect(mockGet).toHaveBeenCalledTimes(3)
})
})
})
describe('Селектор сервера', () => {
it('должен отображать селектор сервера при наличии туннелей', async () => {
const mockTunnels = [
{ id: 1, name: 'Tunnel 1', ip: '1.1.1.1', domain: '', isInstalled: true }
]
setupMockGet({ tunnels: mockTunnels, subscriptions: [] })
renderSubscriptionsPage()
await waitFor(() => {
const select = screen.getByRole('combobox')
expect(select).toBeInTheDocument()
})
})
it('должен отображать иконку Dns для основного сервера', async () => {
const mockTunnels = [
{ id: 1, name: 'Tunnel 1', ip: '1.1.1.1', domain: '', isInstalled: true }
]
setupMockGet({ tunnels: mockTunnels, subscriptions: [] })
renderSubscriptionsPage()
await waitFor(() => {
expect(screen.getByText('Основной сервер')).toBeInTheDocument()
})
})
it('должен переключать выбранный сервер', async () => {
const mockTunnels = [
{ id: 1, name: 'Tunnel 1', ip: '1.1.1.1', domain: '', isInstalled: true }
]
setupMockGet({ tunnels: mockTunnels, subscriptions: [] })
renderSubscriptionsPage()
const select = await screen.findByRole('combobox')
fireEvent.mouseDown(select)
const tunnelOption = await screen.findByText('Tunnel 1')
fireEvent.click(tunnelOption)
await waitFor(() => {
expect(screen.getByText('Tunnel 1')).toBeInTheDocument()
})
})
})
describe('Диалог создания подписки', () => {
it('должен открывать диалог при клике на "Создать"', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
await waitFor(() => {
expect(screen.getByText('Новая подписка')).toBeInTheDocument()
})
})
it('должен закрывать диалог при клике на "Отмена"', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const cancelButton = await screen.findByText('Отмена')
fireEvent.click(cancelButton)
await waitFor(() => {
expect(screen.queryByText('Новая подписка')).not.toBeInTheDocument()
})
})
it('должен отображать поле имени подписки', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
await waitFor(() => {
expect(screen.getByLabelText('Имя подписки')).toBeInTheDocument()
})
})
it('должен отображать инбаунды по умолчанию', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
await waitFor(() => {
expect(screen.getByText('Инбаунды (10/20)')).toBeInTheDocument()
})
})
it('должен позволять вводить имя подписки', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'My Subscription' } })
expect(nameField).toHaveValue('My Subscription')
})
it('должен показывать ошибку при пустом имени', async () => {
setupMockGet()
mockPost.mockRejectedValue({})
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const saveButton = await screen.findByText('Сохранить')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Введите имя подписки')).toBeInTheDocument()
})
})
})
describe('Управление инбаундами в диалоге', () => {
it('должен отображать инбаунды по умолчанию', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
await waitFor(() => {
expect(screen.getByText('Инбаунды (10/20)')).toBeInTheDocument()
})
})
it('должен позволять вводить имя подписки', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'My Subscription' } })
expect(nameField).toHaveValue('My Subscription')
})
it('должен добавлять новый инбаунд', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const addButton = await screen.findByText('Добавить инбаунд')
fireEvent.click(addButton)
await waitFor(() => {
expect(screen.getByText('Инбаунды (11/20)')).toBeInTheDocument()
})
})
it('должен удалять все инбаунды кнопкой "Удалить все"', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const removeAllButton = await screen.findByText('Удалить все')
fireEvent.click(removeAllButton)
await waitFor(() => {
expect(screen.getByText('Инбаунды (1/20)')).toBeInTheDocument()
})
})
it('должен запрещать добавление более 20 инбаундов', async () => {
setupMockGet()
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
for (let i = 0; i < 10; i++) {
const addButton = screen.getByText('Добавить инбаунд')
fireEvent.click(addButton)
}
await waitFor(() => {
const addButton = screen.getByText('Добавить инбаунд')
expect(addButton).toBeDisabled()
})
})
})
describe('Сохранение подписки', () => {
it('должен создавать новую подписку', async () => {
setupMockGet()
mockPost.mockResolvedValue({})
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'New Sub' } })
const saveButton = screen.getByText('Сохранить')
fireEvent.click(saveButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/subscriptions', expect.objectContaining({
name: 'New Sub',
}))
})
})
it('должен показывать успех после создания', async () => {
setupMockGet()
mockPost.mockResolvedValue({})
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'New Sub' } })
const saveButton = screen.getByText('Сохранить')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Подписка создана')).toBeInTheDocument()
})
})
it('должен показывать ошибку при неудачном сохранении', async () => {
setupMockGet()
mockPost.mockRejectedValue({ response: { data: { message: 'Ошибка сервера' } } })
renderSubscriptionsPage()
const createButton = await screen.findByText('Создать')
fireEvent.click(createButton)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'New Sub' } })
const saveButton = screen.getByText('Сохранить')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Ошибка сервера')).toBeInTheDocument()
})
})
})
describe('Редактирование подписки', () => {
it('должен открывать диалог редактирования', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{ type: 'vless', port: 443, sni: 'example.com' }], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const editOption = await screen.findByText('Редактировать')
fireEvent.click(editOption)
await waitFor(() => {
expect(screen.getByText('Редактировать подписку')).toBeInTheDocument()
})
})
it('должен загружать данные подписки в диалог', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{ type: 'vless', port: 443, sni: 'example.com' }], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const editOption = await screen.findByText('Редактировать')
fireEvent.click(editOption)
const nameField = await screen.findByLabelText('Имя подписки')
expect(nameField).toHaveValue('Test Sub')
})
it('должен обновлять подписку', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{ type: 'vless', port: 443, sni: 'example.com' }], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockPut.mockResolvedValue({})
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const editOption = await screen.findByText('Редактировать')
fireEvent.click(editOption)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'Updated Sub' } })
const saveButton = screen.getByText('Сохранить')
fireEvent.click(saveButton)
await waitFor(() => {
expect(mockPut).toHaveBeenCalledWith('/subscriptions/1', expect.objectContaining({
name: 'Updated Sub',
}))
})
})
it('должен показывать успех после обновления', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{ type: 'vless', port: 443, sni: 'example.com' }], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockPut.mockResolvedValue({})
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const editOption = await screen.findByText('Редактировать')
fireEvent.click(editOption)
const nameField = await screen.findByLabelText('Имя подписки')
fireEvent.change(nameField, { target: { value: 'Updated Sub' } })
const saveButton = screen.getByText('Сохранить')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Подписка обновлена')).toBeInTheDocument()
})
})
})
describe('Удаление подписки', () => {
it('должен открывать диалог подтверждения удаления', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const deleteOption = await screen.findByText('Удалить')
fireEvent.click(deleteOption)
await waitFor(() => {
expect(screen.getByText('Удалить подписку и все соединения?')).toBeInTheDocument()
})
})
it('должен закрывать диалог при клике на "Отмена"', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const deleteOption = await screen.findByText('Удалить')
fireEvent.click(deleteOption)
const cancelButton = await screen.findByText('Отмена')
fireEvent.click(cancelButton)
await waitFor(() => {
expect(screen.queryByText('Удалить подписку и все соединения?')).not.toBeInTheDocument()
})
})
it('должен удалять подписку при подтверждении', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockDelete.mockResolvedValue({})
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const deleteOption = await screen.findByText('Удалить')
fireEvent.click(deleteOption)
const confirmButton = screen.getAllByText('Удалить')[1] as HTMLElement
fireEvent.click(confirmButton)
await waitFor(() => {
expect(mockDelete).toHaveBeenCalledWith('/subscriptions/1')
})
})
it('должен показывать успех после удаления', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockDelete.mockResolvedValue({})
renderSubscriptionsPage()
const menuButton = await screen.findByTestId('icon-MoreVert')
fireEvent.click(menuButton)
const deleteOption = await screen.findByText('Удалить')
fireEvent.click(deleteOption)
const confirmButton = screen.getAllByText('Удалить')[1] as HTMLElement
fireEvent.click(confirmButton)
await waitFor(() => {
expect(screen.getByText('Подписка удалена')).toBeInTheDocument()
})
})
})
describe('Переключение авторотации', () => {
it('должен переключать авторотацию при клике на чекбокс', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockPut.mockResolvedValue({})
renderSubscriptionsPage()
const checkbox = await screen.findByRole('checkbox')
fireEvent.click(checkbox)
await waitFor(() => {
expect(mockPut).toHaveBeenCalledWith('/subscriptions/bulk-auto-rotation', expect.objectContaining({
subscriptionIds: ['1'],
enabled: false
}))
})
})
it('должен показывать успех при включении авторотации', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: false }
]
setupMockGet({ subscriptions: mockSubs })
mockPut.mockResolvedValue({})
renderSubscriptionsPage()
const checkbox = await screen.findByRole('checkbox')
fireEvent.click(checkbox)
await waitFor(() => {
expect(screen.getByText('Авторотация включена')).toBeInTheDocument()
})
})
it('должен показывать успех при выключении авторотации', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockPut.mockResolvedValue({})
renderSubscriptionsPage()
const checkbox = await screen.findByRole('checkbox')
fireEvent.click(checkbox)
await waitFor(() => {
expect(screen.getByText('Авторотация выключена')).toBeInTheDocument()
})
})
it('должен показывать ошибку при неудачном переключении', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [], isAutoRotationEnabled: true }
]
setupMockGet({ subscriptions: mockSubs })
mockPut.mockRejectedValue({ response: { data: { message: 'Ошибка' } } })
renderSubscriptionsPage()
const checkbox = await screen.findByRole('checkbox')
fireEvent.click(checkbox)
await waitFor(() => {
expect(screen.getByText('Ошибка')).toBeInTheDocument()
})
})
})
})
+25 -2
View File
@@ -12,6 +12,7 @@ afterEach(() => {
// Оставляем только важные ошибки через test.skip()
const originalConsoleLog = console.log
const originalConsoleError = console.error
const originalConsoleWarn = console.warn
beforeAll(() => {
// Фильтруем шумные логи от приложений
@@ -23,20 +24,41 @@ beforeAll(() => {
message.includes('[Settings]') ||
message.includes('[API]') ||
message.includes('[Login]') ||
message.includes('[Rotation]')
message.includes('[Rotation]') ||
message.includes('[Domains]') ||
message.includes('[Subs]') ||
message.includes('[Scanner]')
) {
return
}
originalConsoleLog(...args)
}
// Подавляем console.warn для известных предупреждений
console.warn = (...args) => {
const message = args.join(' ')
if (
message.includes('[Tunnels]') ||
message.includes('[Settings]') ||
message.includes('[Domains]') ||
message.includes('[Subs]') ||
message.includes('[Scanner]')
) {
return
}
originalConsoleWarn(...args)
}
// Подавляем console.error для известных предупреждений React
console.error = (...args) => {
const message = args.join(' ')
// Пропускаем предупреждения act(...) - они не критичны
if (
message.includes('act(...)') ||
message.includes('An update to')
message.includes('An update to') ||
message.includes('[Login]') ||
message.includes('[Settings]') ||
message.includes('[Subs]')
) {
return
}
@@ -48,6 +70,7 @@ afterAll(() => {
// Восстанавливаем console после всех тестов
console.log = originalConsoleLog
console.error = originalConsoleError
console.warn = originalConsoleWarn
})
// Мок для MUI icons-material - используем vi.mock с factory