tests front 50%
This commit is contained in:
@@ -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('0–0 из 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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user