520 lines
17 KiB
TypeScript
520 lines
17 KiB
TypeScript
/**
|
||
* @jest-environment jsdom
|
||
*/
|
||
/**
|
||
* 激活页面测试
|
||
* 测试许可证激活和免费试用功能
|
||
*/
|
||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||
import ActivatePage from '../page';
|
||
|
||
// Mock next/navigation
|
||
const mockPush = jest.fn();
|
||
jest.mock('next/navigation', () => ({
|
||
useRouter: () => ({
|
||
push: mockPush,
|
||
}),
|
||
}));
|
||
|
||
// Mock fetch
|
||
global.fetch = jest.fn();
|
||
|
||
const createJsonResponse = (data: any, init?: { ok?: boolean; status?: number; statusText?: string }) => ({
|
||
ok: init?.ok ?? true,
|
||
status: init?.status ?? 200,
|
||
statusText: init?.statusText ?? 'OK',
|
||
json: () => Promise.resolve(data),
|
||
text: () => Promise.resolve(JSON.stringify(data)),
|
||
});
|
||
|
||
const mockFetchRoutes = (routes: Record<string, any>) => {
|
||
(global.fetch as jest.Mock).mockImplementation((input: RequestInfo | URL) => {
|
||
const url =
|
||
typeof input === 'string'
|
||
? input
|
||
: input instanceof URL
|
||
? input.toString()
|
||
: input.url;
|
||
|
||
const handler = routes[url] ?? routes['*'];
|
||
if (handler instanceof Error) {
|
||
return Promise.reject(handler);
|
||
}
|
||
|
||
if (!handler) {
|
||
throw new Error(`Unhandled fetch request: ${url}`);
|
||
}
|
||
|
||
return Promise.resolve(typeof handler === 'function' ? handler() : handler);
|
||
});
|
||
};
|
||
|
||
const renderPage = async () => {
|
||
await act(async () => {
|
||
render(<ActivatePage />);
|
||
});
|
||
await screen.findByText('FLNXI 素材系统');
|
||
};
|
||
|
||
describe('ActivatePage', () => {
|
||
beforeEach(() => {
|
||
jest.clearAllMocks();
|
||
(global.fetch as jest.Mock).mockReset();
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'*': createJsonResponse({ success: true }),
|
||
});
|
||
});
|
||
|
||
describe('渲染测试', () => {
|
||
it('应该正确渲染页面标题和表单', async () => {
|
||
await renderPage();
|
||
|
||
expect(screen.getByText('FLNXI 素材系统')).toBeInTheDocument();
|
||
expect(screen.getByText('请激活您的许可证')).toBeInTheDocument();
|
||
expect(screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX')).toBeInTheDocument();
|
||
expect(screen.getByText('激活许可证')).toBeInTheDocument();
|
||
expect(screen.getByText('免费试用')).toBeInTheDocument();
|
||
});
|
||
|
||
it('应该显示免费版限制说明', async () => {
|
||
await renderPage();
|
||
expect(screen.getByText('免费版限制:10 个用户、100 个素材')).toBeInTheDocument();
|
||
});
|
||
|
||
it('应该显示联系信息', async () => {
|
||
await renderPage();
|
||
expect(screen.getByText('需要购买许可证?请联系我们')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('许可证密钥格式化', () => {
|
||
it('应该自动将输入转换为大写', async () => {
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
|
||
|
||
fireEvent.change(input, { target: { value: 'abcd' } });
|
||
expect(input.value).toBe('ABCD');
|
||
});
|
||
|
||
it('应该自动添加横杠分隔符', async () => {
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
|
||
|
||
fireEvent.change(input, { target: { value: 'abcd1234' } });
|
||
expect(input.value).toBe('ABCD-1234');
|
||
});
|
||
|
||
it('应该过滤非字母数字字符', async () => {
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
|
||
|
||
fireEvent.change(input, { target: { value: 'ab!@#cd12' } });
|
||
expect(input.value).toBe('ABCD-12');
|
||
});
|
||
|
||
it('应该限制最大长度为19个字符(含横杠)', async () => {
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
|
||
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678EXTRA' } });
|
||
expect(input.value).toBe('ABCD-1234-EFGH-5678');
|
||
});
|
||
});
|
||
|
||
describe('激活按钮状态', () => {
|
||
it('密钥不完整时激活按钮应该禁用', async () => {
|
||
await renderPage();
|
||
const button = screen.getByText('激活许可证');
|
||
expect(button).toBeDisabled();
|
||
});
|
||
|
||
it('密钥完整时激活按钮应该启用', async () => {
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const button = screen.getByText('激活许可证');
|
||
expect(button).not.toBeDisabled();
|
||
});
|
||
});
|
||
|
||
describe('许可证激活', () => {
|
||
it('激活成功后应该显示成功页面', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate': createJsonResponse({
|
||
success: true,
|
||
initialAdmin: {
|
||
email: 'admin@local.flnxi',
|
||
username: 'admin',
|
||
password: 'Generated-Password-123',
|
||
mustChangePassword: true,
|
||
},
|
||
}),
|
||
});
|
||
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const button = screen.getByText('激活许可证');
|
||
await act(async () => {
|
||
fireEvent.click(button);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('激活成功')).toBeInTheDocument();
|
||
expect(screen.getByText('系统已准备就绪')).toBeInTheDocument();
|
||
expect(screen.getByText('初始管理员账号')).toBeInTheDocument();
|
||
expect(screen.getByText('admin@local.flnxi')).toBeInTheDocument();
|
||
expect(screen.getByText('Generated-Password-123')).toBeInTheDocument();
|
||
expect(screen.getByText('前往登录')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
it('点击前往登录按钮应该跳转到登录页', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate': createJsonResponse({
|
||
success: true,
|
||
initialAdmin: {
|
||
email: 'admin@local.flnxi',
|
||
username: 'admin',
|
||
password: 'Generated-Password-123',
|
||
mustChangePassword: true,
|
||
},
|
||
}),
|
||
});
|
||
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const activateButton = screen.getByText('激活许可证');
|
||
await act(async () => {
|
||
fireEvent.click(activateButton);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('前往登录')).toBeInTheDocument();
|
||
});
|
||
|
||
const loginButton = screen.getByText('前往登录');
|
||
await act(async () => {
|
||
fireEvent.click(loginButton);
|
||
});
|
||
|
||
expect(mockPush).toHaveBeenCalledWith('/login');
|
||
});
|
||
|
||
it('激活失败应该显示错误信息', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate': createJsonResponse({ success: false, error: '许可证无效' }),
|
||
});
|
||
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const button = screen.getByText('激活许可证');
|
||
await act(async () => {
|
||
fireEvent.click(button);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('许可证无效')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
it('网络错误应该显示网络错误提示', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate': new Error('Network error'),
|
||
});
|
||
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const button = screen.getByText('激活许可证');
|
||
await act(async () => {
|
||
fireEvent.click(button);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('网络错误,请检查网络连接')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
it('激活时应该显示加载状态', async () => {
|
||
let resolvePromise: (value: unknown) => void;
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate': () =>
|
||
new Promise((resolve) => {
|
||
resolvePromise = resolve;
|
||
}),
|
||
});
|
||
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const button = screen.getByText('激活许可证');
|
||
await act(async () => {
|
||
fireEvent.click(button);
|
||
});
|
||
|
||
expect(screen.getByText('激活中...')).toBeInTheDocument();
|
||
|
||
// 清理
|
||
await act(async () => {
|
||
resolvePromise!(createJsonResponse({
|
||
success: true,
|
||
initialAdmin: {
|
||
email: 'admin@local.flnxi',
|
||
username: 'admin',
|
||
password: 'Generated-Password-123',
|
||
mustChangePassword: true,
|
||
},
|
||
}));
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('免费试用', () => {
|
||
it('点击免费试用按钮应该显示客户名称输入弹窗', async () => {
|
||
await renderPage();
|
||
const button = screen.getByText('免费试用');
|
||
|
||
await act(async () => {
|
||
fireEvent.click(button);
|
||
});
|
||
|
||
expect(screen.getByText('申请免费试用')).toBeInTheDocument();
|
||
expect(screen.getByPlaceholderText('请输入您的公司或个人名称')).toBeInTheDocument();
|
||
expect(screen.getByText('确认申请')).toBeInTheDocument();
|
||
expect(screen.getByText('取消')).toBeInTheDocument();
|
||
});
|
||
|
||
it('未输入客户名称时确认申请按钮应该禁用', async () => {
|
||
await renderPage();
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
const confirmButton = screen.getByText('确认申请');
|
||
expect(confirmButton).toBeDisabled();
|
||
});
|
||
|
||
it('只输入客户名称未输入联系方式时确认申请按钮应该禁用', async () => {
|
||
await renderPage();
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
const nameInput = screen.getByPlaceholderText('请输入您的公司或个人名称');
|
||
fireEvent.change(nameInput, { target: { value: '测试公司' } });
|
||
|
||
const confirmButton = screen.getByText('确认申请');
|
||
expect(confirmButton).toBeDisabled();
|
||
});
|
||
|
||
it('输入客户名称和联系方式后确认申请按钮应该启用', async () => {
|
||
await renderPage();
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
const nameInput = screen.getByPlaceholderText('请输入您的公司或个人名称');
|
||
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
|
||
fireEvent.change(nameInput, { target: { value: '测试公司' } });
|
||
fireEvent.change(contactInput, { target: { value: '13800138000' } });
|
||
|
||
const confirmButton = screen.getByText('确认申请');
|
||
expect(confirmButton).not.toBeDisabled();
|
||
});
|
||
|
||
it('点击取消按钮应该关闭弹窗', async () => {
|
||
await renderPage();
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
expect(screen.getByText('申请免费试用')).toBeInTheDocument();
|
||
|
||
const cancelButton = screen.getByText('取消');
|
||
await act(async () => {
|
||
fireEvent.click(cancelButton);
|
||
});
|
||
|
||
expect(screen.queryByText('申请免费试用')).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('免费试用成功后应该显示成功页面', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate-free': createJsonResponse({
|
||
success: true,
|
||
initialAdmin: {
|
||
email: 'admin@local.flnxi',
|
||
username: 'admin',
|
||
password: 'Generated-Password-123',
|
||
mustChangePassword: true,
|
||
},
|
||
}),
|
||
});
|
||
|
||
await renderPage();
|
||
|
||
// 点击免费试用按钮打开弹窗
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
// 输入客户名称和联系方式
|
||
const nameInput = screen.getByPlaceholderText('请输入您的公司或个人名称');
|
||
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
|
||
fireEvent.change(nameInput, { target: { value: '测试公司' } });
|
||
fireEvent.change(contactInput, { target: { value: '13800138000' } });
|
||
|
||
// 点击确认申请
|
||
const confirmButton = screen.getByText('确认申请');
|
||
await act(async () => {
|
||
fireEvent.click(confirmButton);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('激活成功')).toBeInTheDocument();
|
||
expect(screen.getByText('前往登录')).toBeInTheDocument();
|
||
});
|
||
|
||
expect(global.fetch).toHaveBeenCalledWith('/api/license/activate-free', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ customerName: '测试公司', contactInfo: '13800138000' }),
|
||
});
|
||
});
|
||
|
||
it('免费试用失败应该显示错误信息', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate-free': createJsonResponse({ success: false, error: '试用已过期' }),
|
||
});
|
||
|
||
await renderPage();
|
||
|
||
// 点击免费试用按钮打开弹窗
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
// 输入客户名称和联系方式
|
||
const nameInput = screen.getByPlaceholderText('请输入您的公司或个人名称');
|
||
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
|
||
fireEvent.change(nameInput, { target: { value: '测试公司' } });
|
||
fireEvent.change(contactInput, { target: { value: '13800138000' } });
|
||
|
||
// 点击确认申请
|
||
const confirmButton = screen.getByText('确认申请');
|
||
await act(async () => {
|
||
fireEvent.click(confirmButton);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('试用已过期')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
it('网络错误应该显示网络错误提示', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate-free': new Error('Network error'),
|
||
});
|
||
|
||
await renderPage();
|
||
|
||
// 点击免费试用按钮打开弹窗
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
// 输入客户名称和联系方式
|
||
const nameInput = screen.getByPlaceholderText('请输入您的公司或个人名称');
|
||
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
|
||
fireEvent.change(nameInput, { target: { value: '测试公司' } });
|
||
fireEvent.change(contactInput, { target: { value: '13800138000' } });
|
||
|
||
// 点击确认申请
|
||
const confirmButton = screen.getByText('确认申请');
|
||
await act(async () => {
|
||
fireEvent.click(confirmButton);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('网络错误,请检查网络连接')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
it('应该显示联系方式输入框', async () => {
|
||
await renderPage();
|
||
const freeTrialButton = screen.getByText('免费试用');
|
||
|
||
await act(async () => {
|
||
fireEvent.click(freeTrialButton);
|
||
});
|
||
|
||
expect(screen.getByPlaceholderText('手机号/微信/邮箱')).toBeInTheDocument();
|
||
expect(screen.getByText('联系方式')).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('API 调用', () => {
|
||
it('激活时应该发送正确的请求', async () => {
|
||
mockFetchRoutes({
|
||
'/api/license/check': createJsonResponse({ activated: false }),
|
||
'/api/license/activate': createJsonResponse({
|
||
success: true,
|
||
initialAdmin: {
|
||
email: 'admin@local.flnxi',
|
||
username: 'admin',
|
||
password: 'Generated-Password-123',
|
||
mustChangePassword: true,
|
||
},
|
||
}),
|
||
});
|
||
|
||
await renderPage();
|
||
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
|
||
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
|
||
|
||
const button = screen.getByText('激活许可证');
|
||
await act(async () => {
|
||
fireEvent.click(button);
|
||
});
|
||
|
||
await waitFor(() => {
|
||
expect(global.fetch).toHaveBeenCalledWith('/api/license/activate', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ licenseKey: 'ABCD-1234-EFGH-5678' }),
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|