|
@@ -1535,4 +1535,284 @@ describe('残疾人个人管理集成测试', () => {
|
|
|
expect(screen.getByText(/支持的照片格式/)).toBeInTheDocument();
|
|
expect(screen.getByText(/支持的照片格式/)).toBeInTheDocument();
|
|
|
expect(screen.getByText(/文件大小限制/)).toBeInTheDocument();
|
|
expect(screen.getByText(/文件大小限制/)).toBeInTheDocument();
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ it('应该处理API返回400错误(身份证号重复)', async () => {
|
|
|
|
|
+ // 导入toast以验证错误消息显示
|
|
|
|
|
+ const { toast } = await import('sonner');
|
|
|
|
|
+
|
|
|
|
|
+ renderComponent();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待数据加载
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('张三')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 打开创建模态框
|
|
|
|
|
+ const addButton = screen.getByTestId('add-disabled-person-button');
|
|
|
|
|
+ fireEvent.click(addButton);
|
|
|
|
|
+
|
|
|
|
|
+ // 等待模态框打开
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 填写表单
|
|
|
|
|
+ const nameInput = screen.getByPlaceholderText('请输入姓名');
|
|
|
|
|
+ const idCardInput = screen.getByPlaceholderText('请输入身份证号');
|
|
|
|
|
+ const disabilityIdInput = screen.getByPlaceholderText('请输入残疾证号');
|
|
|
|
|
+ const phoneInput = screen.getByPlaceholderText('请输入联系电话');
|
|
|
|
|
+ const idAddressInput = screen.getByPlaceholderText('请输入身份证地址');
|
|
|
|
|
+
|
|
|
|
|
+ fireEvent.change(nameInput, { target: { value: '李四' } });
|
|
|
|
|
+ fireEvent.change(idCardInput, { target: { value: '110101199002021234' } });
|
|
|
|
|
+ fireEvent.change(disabilityIdInput, { target: { value: 'D123456790' } });
|
|
|
|
|
+ fireEvent.change(phoneInput, { target: { value: '13900139000' } });
|
|
|
|
|
+ fireEvent.change(idAddressInput, { target: { value: '上海市黄浦区' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择性别
|
|
|
|
|
+ const genderSelect = screen.getByTestId('gender-select');
|
|
|
|
|
+ fireEvent.change(genderSelect, { target: { value: '女' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择残疾类型
|
|
|
|
|
+ const disabilityTypeSelect = screen.getByTestId('disability-type-select');
|
|
|
|
|
+ fireEvent.change(disabilityTypeSelect, { target: { value: '视力残疾' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择残疾等级
|
|
|
|
|
+ const disabilityLevelSelect = screen.getByTestId('disability-level-select');
|
|
|
|
|
+ fireEvent.change(disabilityLevelSelect, { target: { value: '二级' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择省份和城市
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ await completeRadixSelectFlow('area-select-province', '1', { useFireEvent: true });
|
|
|
|
|
+ });
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ await completeRadixSelectFlow('area-select-city', '3', { useFireEvent: true });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Mock API返回400错误(身份证号重复)
|
|
|
|
|
+ const mockClient = (disabilityClientManager.get as any)();
|
|
|
|
|
+ mockClient.createAggregatedDisabledPerson.$post.mockResolvedValueOnce(
|
|
|
|
|
+ createMockResponse(400, {
|
|
|
|
|
+ error: '身份证号已存在',
|
|
|
|
|
+ message: '该身份证号已被其他残疾人使用'
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 提交表单
|
|
|
|
|
+ const submitButton = screen.getByText('创建');
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ fireEvent.click(submitButton);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 等待toast错误消息显示
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(toast.error).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('身份证号已存在') || expect.stringContaining('创建失败'),
|
|
|
|
|
+ expect.any(Object)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 验证表单没有被关闭(仍然可见)
|
|
|
|
|
+ expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('应该处理API返回500错误(服务器内部错误)', async () => {
|
|
|
|
|
+ // 导入toast以验证错误消息显示
|
|
|
|
|
+ const { toast } = await import('sonner');
|
|
|
|
|
+
|
|
|
|
|
+ renderComponent();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待数据加载
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('张三')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 点击编辑按钮
|
|
|
|
|
+ const editButton = screen.getByTestId('edit-person-1');
|
|
|
|
|
+ fireEvent.click(editButton);
|
|
|
|
|
+
|
|
|
|
|
+ // 等待编辑模态框打开
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('编辑残疾人信息')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 修改电话号码
|
|
|
|
|
+ const phoneInput = screen.getByPlaceholderText('请输入联系电话');
|
|
|
|
|
+ fireEvent.change(phoneInput, { target: { value: '13800138001' } });
|
|
|
|
|
+
|
|
|
|
|
+ // Mock API返回500错误(服务器内部错误)
|
|
|
|
|
+ const mockClient = (disabilityClientManager.get as any)();
|
|
|
|
|
+ mockClient.updateAggregatedDisabledPerson[':id']['$put'].mockResolvedValueOnce(
|
|
|
|
|
+ createMockResponse(500, {
|
|
|
|
|
+ error: 'Internal Server Error',
|
|
|
|
|
+ message: '服务器内部错误,请稍后再试'
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 提交表单
|
|
|
|
|
+ const submitButton = screen.getByText('更新');
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ fireEvent.click(submitButton);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 等待toast错误消息显示
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(toast.error).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('服务器内部错误') || expect.stringContaining('更新失败'),
|
|
|
|
|
+ expect.any(Object)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 验证表单没有被关闭(仍然可见)
|
|
|
|
|
+ expect(screen.getByText('编辑残疾人信息')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('应该处理网络错误场景(请求失败)', async () => {
|
|
|
|
|
+ // 导入toast以验证错误消息显示
|
|
|
|
|
+ const { toast } = await import('sonner');
|
|
|
|
|
+
|
|
|
|
|
+ renderComponent();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待数据加载
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('张三')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 点击删除按钮
|
|
|
|
|
+ const deleteButton = screen.getByTestId('delete-person-1');
|
|
|
|
|
+ fireEvent.click(deleteButton);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证删除对话框打开
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByTestId('delete-confirmation-dialog-title')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Mock API网络错误(请求失败)
|
|
|
|
|
+ const mockClient = (disabilityClientManager.get as any)();
|
|
|
|
|
+ mockClient.deleteDisabledPerson.$post.mockRejectedValueOnce(
|
|
|
|
|
+ new Error('网络连接失败')
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 确认删除
|
|
|
|
|
+ const confirmButtons = screen.getAllByText('确认删除');
|
|
|
|
|
+ const confirmButton = confirmButtons.find(btn => btn.getAttribute('type') === 'button' || btn.getAttribute('data-slot') === 'button') || confirmButtons[0];
|
|
|
|
|
+ fireEvent.click(confirmButton);
|
|
|
|
|
+
|
|
|
|
|
+ // 等待toast错误消息显示
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(toast.error).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('网络连接失败') || expect.stringContaining('删除失败'),
|
|
|
|
|
+ expect.any(Object)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 验证删除对话框被关闭(确认删除后)
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.queryByTestId('delete-confirmation-dialog-title')).not.toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('应该验证错误信息在界面上正确显示给用户', async () => {
|
|
|
|
|
+ // 导入toast以验证错误消息显示
|
|
|
|
|
+ const { toast } = await import('sonner');
|
|
|
|
|
+
|
|
|
|
|
+ renderComponent();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待数据加载
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('张三')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 打开创建模态框
|
|
|
|
|
+ const addButton = screen.getByTestId('add-disabled-person-button');
|
|
|
|
|
+ fireEvent.click(addButton);
|
|
|
|
|
+
|
|
|
|
|
+ // 等待模态框打开
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 填写表单
|
|
|
|
|
+ const nameInput = screen.getByPlaceholderText('请输入姓名');
|
|
|
|
|
+ const idCardInput = screen.getByPlaceholderText('请输入身份证号');
|
|
|
|
|
+ const disabilityIdInput = screen.getByPlaceholderText('请输入残疾证号');
|
|
|
|
|
+ const phoneInput = screen.getByPlaceholderText('请输入联系电话');
|
|
|
|
|
+ const idAddressInput = screen.getByPlaceholderText('请输入身份证地址');
|
|
|
|
|
+
|
|
|
|
|
+ fireEvent.change(nameInput, { target: { value: '李四' } });
|
|
|
|
|
+ fireEvent.change(idCardInput, { target: { value: '110101199002021234' } });
|
|
|
|
|
+ fireEvent.change(disabilityIdInput, { target: { value: 'D123456790' } });
|
|
|
|
|
+ fireEvent.change(phoneInput, { target: { value: '13900139000' } });
|
|
|
|
|
+ fireEvent.change(idAddressInput, { target: { value: '上海市黄浦区' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择性别
|
|
|
|
|
+ const genderSelect = screen.getByTestId('gender-select');
|
|
|
|
|
+ fireEvent.change(genderSelect, { target: { value: '女' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择残疾类型
|
|
|
|
|
+ const disabilityTypeSelect = screen.getByTestId('disability-type-select');
|
|
|
|
|
+ fireEvent.change(disabilityTypeSelect, { target: { value: '视力残疾' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择残疾等级
|
|
|
|
|
+ const disabilityLevelSelect = screen.getByTestId('disability-level-select');
|
|
|
|
|
+ fireEvent.change(disabilityLevelSelect, { target: { value: '二级' } });
|
|
|
|
|
+
|
|
|
|
|
+ // 选择省份和城市
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ await completeRadixSelectFlow('area-select-province', '1', { useFireEvent: true });
|
|
|
|
|
+ });
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ await completeRadixSelectFlow('area-select-city', '3', { useFireEvent: true });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Mock API返回400错误,包含详细的错误信息
|
|
|
|
|
+ const mockClient = (disabilityClientManager.get as any)();
|
|
|
|
|
+ const errorResponse = {
|
|
|
|
|
+ error: 'VALIDATION_ERROR',
|
|
|
|
|
+ message: '身份证号已存在,请使用其他身份证号',
|
|
|
|
|
+ details: {
|
|
|
|
|
+ field: 'idCard',
|
|
|
|
|
+ value: '110101199002021234',
|
|
|
|
|
+ suggestion: '请检查身份证号是否正确'
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ mockClient.createAggregatedDisabledPerson.$post.mockResolvedValueOnce(
|
|
|
|
|
+ createMockResponse(400, errorResponse)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 提交表单
|
|
|
|
|
+ const submitButton = screen.getByText('创建');
|
|
|
|
|
+ await act(async () => {
|
|
|
|
|
+ fireEvent.click(submitButton);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 验证toast.error被调用,并且错误信息包含有用的用户提示
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(toast.error).toHaveBeenCalled();
|
|
|
|
|
+
|
|
|
|
|
+ // 获取toast.error的调用参数
|
|
|
|
|
+ const errorCalls = (toast.error as any).mock.calls;
|
|
|
|
|
+ expect(errorCalls.length).toBeGreaterThan(0);
|
|
|
|
|
+
|
|
|
|
|
+ const lastCall = errorCalls[errorCalls.length - 1];
|
|
|
|
|
+ const errorMessage = lastCall[0];
|
|
|
|
|
+ const toastOptions = lastCall[1];
|
|
|
|
|
+
|
|
|
|
|
+ // 验证错误消息包含有用的信息
|
|
|
|
|
+ expect(errorMessage).toMatch(/身份证号已存在|创建失败|错误/i);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证toast选项包含适当的配置
|
|
|
|
|
+ expect(toastOptions).toBeDefined();
|
|
|
|
|
+ expect(toastOptions).toHaveProperty('duration');
|
|
|
|
|
+ expect(toastOptions.duration).toBeGreaterThan(0);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 验证表单仍然可见,用户可以继续编辑
|
|
|
|
|
+ expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
|
|
|
|
|
+
|
|
|
|
|
+ // 验证表单字段值没有被清除(用户可以继续编辑)
|
|
|
|
|
+ expect((nameInput as HTMLInputElement).value).toBe('李四');
|
|
|
|
|
+ expect((idCardInput as HTMLInputElement).value).toBe('110101199002021234');
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|