Files
d8d-admin-mobile-starter-pu…/client/admin/pages_know_info.test.tsx

267 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { JSDOM } from 'jsdom'
import React from 'react'
import {render, fireEvent, within, screen, waitFor} from '@testing-library/react'
import { KnowInfoPage } from "./pages_know_info.tsx"
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
assertEquals,
assertExists,
assertNotEquals,
assertRejects,
assert,
} from "https://deno.land/std@0.217.0/assert/mod.ts";
const queryClient = new QueryClient()
const dom = new JSDOM(`<body></body>`, {
runScripts: "dangerously",
pretendToBeVisual: true,
url: "http://localhost"
});
// 模拟浏览器环境
globalThis.window = dom.window;
globalThis.document = dom.window.document;
// 定义浏览器环境所需的类
globalThis.Element = dom.window.Element;
globalThis.HTMLElement = dom.window.HTMLElement;
globalThis.ShadowRoot = dom.window.ShadowRoot;
globalThis.SVGElement = dom.window.SVGElement;
// 模拟 getComputedStyle
globalThis.getComputedStyle = (elt) => {
const style = new dom.window.CSSStyleDeclaration();
style.getPropertyValue = () => '';
return style;
};
// 模拟matchMedia函数
globalThis.matchMedia = (query) => ({
matches: query.includes('max-width'),
media: query,
onchange: null,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => false,
});
// 模拟动画相关API
globalThis.AnimationEvent = globalThis.AnimationEvent || dom.window.Event;
globalThis.TransitionEvent = globalThis.TransitionEvent || dom.window.Event;
// 模拟requestAnimationFrame
globalThis.requestAnimationFrame = globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 0));
globalThis.cancelAnimationFrame = globalThis.cancelAnimationFrame || clearTimeout;
// 设置浏览器尺寸相关方法
window.resizeTo = (width, height) => {
window.innerWidth = width || window.innerWidth;
window.innerHeight = height || window.innerHeight;
window.dispatchEvent(new Event('resize'));
};
window.scrollTo = () => {};
const customScreen = within(document.body);
// 使用异步测试处理组件渲染
Deno.test({
name: '知识库管理页面测试',
fn: async (t) => {
// 存储所有需要清理的定时器
const timers: number[] = [];
const originalSetTimeout = globalThis.setTimeout;
const originalSetInterval = globalThis.setInterval;
// 重写定时器方法以跟踪所有创建的定时器
globalThis.setTimeout = ((callback, delay, ...args) => {
const id = originalSetTimeout(callback, delay, ...args);
timers.push(id);
return id;
}) as typeof setTimeout;
globalThis.setInterval = ((callback, delay, ...args) => {
const id = originalSetInterval(callback, delay, ...args);
timers.push(id);
return id;
}) as typeof setInterval;
// 清理函数
const cleanup = () => {
for (const id of timers) {
clearTimeout(id);
clearInterval(id);
}
// 恢复原始定时器方法
globalThis.setTimeout = originalSetTimeout;
globalThis.setInterval = originalSetInterval;
};
try {
// 渲染组件
const {
findByText, findByPlaceholderText, queryByText,
findByRole, findAllByRole, findByLabelText, findAllByText
} = render(
<QueryClientProvider client={queryClient}>
<KnowInfoPage />
</QueryClientProvider>
);
// 测试1: 基本渲染
await t.step('应正确渲染页面元素', async () => {
const title = await findByText(/知识库管理/i);
assertExists(title, '未找到知识库管理标题');
});
// 测试2: 搜索表单功能
await t.step('搜索表单应正常工作', async () => {
const searchInput = await findByPlaceholderText(/请输入文章标题/i) as HTMLInputElement;
const searchButton = await findByText(/搜 索/i);
// 输入搜索内容
fireEvent.change(searchInput, { target: { value: '数据分析' } });
assertEquals(searchInput.value, '数据分析', '搜索输入框值未更新');
// 提交搜索
fireEvent.click(searchButton);
// 验证是否触发了搜索
await waitFor(() => {
const loading = queryByText(/加载中/i);
assertNotEquals(loading, null, '搜索未触发加载状态');
});
// 等待搜索结果并验证
await waitFor(async () => {
const table = await findByRole('table');
const rows = await within(table).findAllByRole('row');
// 检查至少有一行包含"数据分析"
const hasMatch = rows.some(async row => {
const cells = await within(row).findAllByRole('cell');
return cells.some(cell => cell.textContent?.includes('数据分析'));
});
assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章');
}, {
timeout: 5000,
onTimeout: () => new Error('等待搜索结果超时')
});
});
// 测试3: 表格数据加载
await t.step('表格应加载并显示数据', async () => {
// 等待数据加载完成或表格出现最多等待5秒
await waitFor(async () => {
// 检查加载状态是否消失
const loading = queryByText(/加载中/i);
if (loading) {
throw new Error('数据仍在加载中');
}
// 检查表格是否出现
const table = await findByRole('table');
assertExists(table, '未找到数据表格');
// 检查表格是否有数据行
const rows = await within(table).findAllByRole('row');
assertNotEquals(rows.length, 1, '表格没有数据行'); // 1是表头行
}, {
timeout: 5000, // 5秒超时
onTimeout: (error) => {
return new Error(`数据加载超时: ${error.message}`);
}
});
});
// 测试4: 添加文章功能
await t.step('应能打开添加文章模态框', async () => {
const addButton = await findByText(/添加文章/i);
fireEvent.click(addButton);
const modalTitle = await findByText(/添加知识库文章/i);
assertExists(modalTitle, '未找到添加文章模态框');
// 验证表单字段
const titleInput = await findByLabelText(/文章标题/i);
assertExists(titleInput, '未找到标题输入框');
});
// 测试5: 完整添加文章流程
await t.step('应能完整添加一篇文章', async () => {
// 打开添加模态框
const addButton = await findByText(/添加文章/i);
fireEvent.click(addButton);
// 填写表单
const titleInput = await findByLabelText(/文章标题/i) as HTMLInputElement;
const contentInput = await findByLabelText(/文章内容/i) as HTMLTextAreaElement;
const submitButton = await findByText(/确 定/i);
fireEvent.change(titleInput, { target: { value: '测试文章标题' } });
fireEvent.change(contentInput, { target: { value: '这是测试文章内容' } });
// 提交表单
fireEvent.click(submitButton);
// 验证提交后状态
await waitFor(() => {
const successMessage = queryByText(/添加成功/i);
assertExists(successMessage, '未显示添加成功提示');
});
// 验证模态框已关闭
await waitFor(() => {
const modalTitle = queryByText(/添加知识库文章/i);
assertEquals(modalTitle, null, '添加模态框未关闭');
});
// 验证表格中是否出现新添加的文章
await waitFor(async () => {
const table = await findByRole('table');
const rows = await within(table).findAllByRole('row');
const hasNewArticle = rows.some(row => {
// 使用更通用的选择器来查找包含文本的单元格
const cells = within(row).queryAllByRole('cell') ||
within(row).queryAllByRole('gridcell') ||
within(row).queryAllByRole('columnheader');
return cells.some(cell => cell.textContent?.includes('测试文章标题'));
});
assert(hasNewArticle, '新添加的文章未出现在表格中');
}, {
timeout: 5000,
onTimeout: () => new Error('等待新文章出现在表格中超时')
});
});
// // 测试5: 分页功能
// await t.step('应显示分页控件', async () => {
// const pagination = await findByRole('navigation');
// assertExists(pagination, '未找到分页控件');
// const pageItems = await findAllByRole('button', { name: /1|2|3|下一页|上一页/i });
// assertNotEquals(pageItems.length, 0, '未找到分页按钮');
// });
// // 测试6: 操作按钮
// await t.step('应显示操作按钮', async () => {
// const editButtons = await findAllByText(/编辑/i);
// assertNotEquals(editButtons.length, 0, '未找到编辑按钮');
// const deleteButtons = await findAllByText(/删除/i);
// assertNotEquals(deleteButtons.length, 0, '未找到删除按钮');
// });
} finally {
// 确保清理所有定时器
cleanup();
}
},
sanitizeOps: false, // 禁用操作清理检查
sanitizeResources: false, // 禁用资源清理检查
});