import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import { ConfigProvider } from 'antd'; import authReducer from '../../store/slices/authSlice'; import LoginPage from './LoginPage'; function makeStore() { return configureStore({ reducer: { auth: authReducer } }); } function renderLogin() { const store = makeStore(); return { store, ...render( } /> USERS} /> , ), }; } async function fillAndSubmit(username: string, password: string) { const user = userEvent.setup(); await user.clear(screen.getByPlaceholderText('请输入你的用户名')); await user.type(screen.getByPlaceholderText('请输入你的用户名'), username); await user.clear(screen.getByPlaceholderText('请输入你的密码')); await user.type(screen.getByPlaceholderText('请输入你的密码'), password); // companyCode 默认已选 HQ;改其他公司需要复杂的 AntD Select 交互,单独的测试用 MSW 路径模拟 await user.click(screen.getByTestId('login-submit')); } describe('LoginPage', () => { it('renders login page with form', () => { renderLogin(); expect(screen.getByText('用户登录')).toBeInTheDocument(); expect(screen.getByTestId('login-form')).toBeInTheDocument(); expect(screen.getByTestId('login-submit')).toBeInTheDocument(); }); it('success flow: dispatches setSession and navigates to /users', async () => { const { store } = renderLogin(); await fillAndSubmit('alice', 'Password1!'); await waitFor(() => expect(screen.queryByTestId('users-page')).toBeInTheDocument(), { timeout: 3000, }); expect(store.getState().auth.accessToken).toBe('fake-jwt'); expect(store.getState().auth.userInfo?.username).toBe('alice'); }); it('bad credentials: shows 40101 error message', async () => { renderLogin(); await fillAndSubmit('alice', 'WRONG'); await waitFor(() => expect(screen.getByText('用户名或密码错误')).toBeInTheDocument(), ); }); it('locked account: shows 42301 with lockUntil time', async () => { renderLogin(); await fillAndSubmit('locked', 'X'); await waitFor(() => { const alert = screen.getByTestId('login-error-alert'); expect(alert.textContent).toMatch(/账号已锁定/); expect(alert.textContent).toMatch(/12:00/); }); }); it('deleted account: shows 40103 message', async () => { renderLogin(); await fillAndSubmit('deleted', 'X'); await waitFor(() => expect(screen.getByText('账号已被作废,禁止登录')).toBeInTheDocument(), ); }); it('empty fields: form-level required errors', async () => { renderLogin(); const user = userEvent.setup(); await user.click(screen.getByTestId('login-submit')); await waitFor(() => expect(screen.getByText('请输入用户名')).toBeInTheDocument()); expect(screen.getByText('请输入密码')).toBeInTheDocument(); }); it('locked account: submit stays disabled while lockUntil in the future', async () => { renderLogin(); await fillAndSubmit('locked', 'X'); await waitFor(() => expect(screen.getByTestId('login-error-alert')).toBeInTheDocument()); // 锁定后 submit 应处于 disabled 态(lockUntil = 2030-01-01 远在未来) const submitBtn = screen.getByTestId('login-submit') as HTMLButtonElement; expect(submitBtn).toBeDisabled(); }); it('form fields are labeled (a11y)', () => { renderLogin(); expect(screen.getByLabelText('用户名')).toBeInTheDocument(); expect(screen.getByLabelText('密码')).toBeInTheDocument(); expect(screen.getByLabelText('公司')).toBeInTheDocument(); }); });