825 lines
28 KiB
TypeScript
825 lines
28 KiB
TypeScript
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()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Утилитные функции', () => {
|
|
it('должен генерировать уникальный ID', async () => {
|
|
setupMockGet()
|
|
renderSubscriptionsPage()
|
|
|
|
const createButton = await screen.findByText('Создать')
|
|
fireEvent.click(createButton)
|
|
|
|
// Проверяем что ID генерируется (просто что форма открывается)
|
|
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', link: 'vless://test' }], isAutoRotationEnabled: true }
|
|
]
|
|
setupMockGet({ subscriptions: mockSubs })
|
|
|
|
renderSubscriptionsPage()
|
|
|
|
const menuButton = await screen.findByTestId('icon-MoreVert')
|
|
fireEvent.click(menuButton)
|
|
|
|
const linksOption = await screen.findByText('Показать конфиги')
|
|
fireEvent.click(linksOption)
|
|
|
|
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', link: 'vless://test' }], isAutoRotationEnabled: true }
|
|
]
|
|
setupMockGet({ subscriptions: mockSubs })
|
|
|
|
renderSubscriptionsPage()
|
|
|
|
const menuButton = await screen.findByTestId('icon-MoreVert')
|
|
fireEvent.click(menuButton)
|
|
|
|
const linksOption = await screen.findByText('Показать конфиги')
|
|
fireEvent.click(linksOption)
|
|
|
|
const closeButton = await screen.findByText('Закрыть')
|
|
fireEvent.click(closeButton)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('Активные ссылки')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('должен копировать ссылку при клике на кнопку копирования', async () => {
|
|
const mockSubs = [
|
|
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{ type: 'vless', port: 443, sni: 'example.com', link: 'vless://test' }], isAutoRotationEnabled: true }
|
|
]
|
|
setupMockGet({ subscriptions: mockSubs })
|
|
mockClipboardWriteText.mockResolvedValue(undefined)
|
|
|
|
renderSubscriptionsPage()
|
|
|
|
const menuButton = await screen.findByTestId('icon-MoreVert')
|
|
fireEvent.click(menuButton)
|
|
|
|
const linksOption = await screen.findByText('Показать конфиги')
|
|
fireEvent.click(linksOption)
|
|
|
|
const copyButton = await screen.findByText('Копировать все')
|
|
fireEvent.click(copyButton)
|
|
|
|
await waitFor(() => {
|
|
expect(mockClipboardWriteText).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
it('должен открывать ссылку в новой вкладке при клике на иконку открытия', async () => {
|
|
const mockSubs = [
|
|
{ id: '1', name: 'Test Sub', uuid: 'abc-123', inbounds: [{ type: 'vless', port: 443, sni: 'example.com', link: 'vless://test' }], isAutoRotationEnabled: true }
|
|
]
|
|
setupMockGet({ subscriptions: mockSubs })
|
|
|
|
renderSubscriptionsPage()
|
|
|
|
const menuButton = await screen.findByTestId('icon-MoreVert')
|
|
fireEvent.click(menuButton)
|
|
|
|
const linksOption = await screen.findByText('Показать конфиги')
|
|
fireEvent.click(linksOption)
|
|
|
|
const openButtons = await screen.findAllByTestId('icon-OpenInNew')
|
|
if (openButtons.length > 0) {
|
|
fireEvent.click(openButtons[0])
|
|
}
|
|
|
|
await waitFor(() => {
|
|
expect(mockWindowOpen).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
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 linksOption = await screen.findByText('Показать конфиги')
|
|
fireEvent.click(linksOption)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Нет активных ссылок (ждите ротации)')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
})
|