2
0

pages_know_info.test.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { JSDOM } from 'jsdom'
  2. import React from 'react'
  3. import {render, fireEvent, within, screen, waitFor} from '@testing-library/react'
  4. import { KnowInfoPage } from "./pages_know_info.tsx"
  5. import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
  6. import {
  7. assertEquals,
  8. assertExists,
  9. assertNotEquals,
  10. assertRejects,
  11. assert,
  12. } from "https://deno.land/std@0.217.0/assert/mod.ts";
  13. const queryClient = new QueryClient()
  14. const dom = new JSDOM(`<body></body>`, {
  15. runScripts: "dangerously",
  16. pretendToBeVisual: true,
  17. url: "http://localhost"
  18. });
  19. // 模拟浏览器环境
  20. globalThis.window = dom.window;
  21. globalThis.document = dom.window.document;
  22. // 定义浏览器环境所需的类
  23. globalThis.Element = dom.window.Element;
  24. globalThis.HTMLElement = dom.window.HTMLElement;
  25. globalThis.ShadowRoot = dom.window.ShadowRoot;
  26. globalThis.SVGElement = dom.window.SVGElement;
  27. // 模拟 getComputedStyle
  28. globalThis.getComputedStyle = (elt) => {
  29. const style = new dom.window.CSSStyleDeclaration();
  30. style.getPropertyValue = () => '';
  31. return style;
  32. };
  33. // 模拟matchMedia函数
  34. globalThis.matchMedia = (query) => ({
  35. matches: query.includes('max-width'),
  36. media: query,
  37. onchange: null,
  38. addListener: () => {},
  39. removeListener: () => {},
  40. addEventListener: () => {},
  41. removeEventListener: () => {},
  42. dispatchEvent: () => false,
  43. });
  44. // 模拟动画相关API
  45. globalThis.AnimationEvent = globalThis.AnimationEvent || dom.window.Event;
  46. globalThis.TransitionEvent = globalThis.TransitionEvent || dom.window.Event;
  47. // 模拟requestAnimationFrame
  48. globalThis.requestAnimationFrame = globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 0));
  49. globalThis.cancelAnimationFrame = globalThis.cancelAnimationFrame || clearTimeout;
  50. // 设置浏览器尺寸相关方法
  51. window.resizeTo = (width, height) => {
  52. window.innerWidth = width || window.innerWidth;
  53. window.innerHeight = height || window.innerHeight;
  54. window.dispatchEvent(new Event('resize'));
  55. };
  56. window.scrollTo = () => {};
  57. const customScreen = within(document.body);
  58. // 使用异步测试处理组件渲染
  59. Deno.test({
  60. name: '知识库管理页面测试',
  61. fn: async (t) => {
  62. // 存储所有需要清理的定时器
  63. const timers: number[] = [];
  64. const originalSetTimeout = globalThis.setTimeout;
  65. const originalSetInterval = globalThis.setInterval;
  66. // 重写定时器方法以跟踪所有创建的定时器
  67. globalThis.setTimeout = ((callback, delay, ...args) => {
  68. const id = originalSetTimeout(callback, delay, ...args);
  69. timers.push(id);
  70. return id;
  71. }) as typeof setTimeout;
  72. globalThis.setInterval = ((callback, delay, ...args) => {
  73. const id = originalSetInterval(callback, delay, ...args);
  74. timers.push(id);
  75. return id;
  76. }) as typeof setInterval;
  77. // 清理函数
  78. const cleanup = () => {
  79. for (const id of timers) {
  80. clearTimeout(id);
  81. clearInterval(id);
  82. }
  83. // 恢复原始定时器方法
  84. globalThis.setTimeout = originalSetTimeout;
  85. globalThis.setInterval = originalSetInterval;
  86. };
  87. try {
  88. // 渲染组件
  89. const {
  90. findByText, findByPlaceholderText, queryByText,
  91. findByRole, findAllByRole, findByLabelText, findAllByText
  92. } = render(
  93. <QueryClientProvider client={queryClient}>
  94. <KnowInfoPage />
  95. </QueryClientProvider>
  96. );
  97. // 测试1: 基本渲染
  98. await t.step('应正确渲染页面元素', async () => {
  99. const title = await findByText(/知识库管理/i);
  100. assertExists(title, '未找到知识库管理标题');
  101. });
  102. // 测试2: 搜索表单功能
  103. await t.step('搜索表单应正常工作', async () => {
  104. const searchInput = await findByPlaceholderText(/请输入文章标题/i) as HTMLInputElement;
  105. const searchButton = await findByText(/搜 索/i);
  106. // 输入搜索内容
  107. fireEvent.change(searchInput, { target: { value: '数据分析' } });
  108. assertEquals(searchInput.value, '数据分析', '搜索输入框值未更新');
  109. // 提交搜索
  110. fireEvent.click(searchButton);
  111. // 验证是否触发了搜索
  112. await waitFor(() => {
  113. const loading = queryByText(/加载中/i);
  114. assertNotEquals(loading, null, '搜索未触发加载状态');
  115. });
  116. // 等待搜索结果并验证
  117. await waitFor(async () => {
  118. const table = await findByRole('table');
  119. const rows = await within(table).findAllByRole('row');
  120. // 检查至少有一行包含"数据分析"
  121. const hasMatch = rows.some(async row => {
  122. const cells = await within(row).findAllByRole('cell');
  123. return cells.some(cell => cell.textContent?.includes('数据分析'));
  124. });
  125. assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章');
  126. }, {
  127. timeout: 5000,
  128. onTimeout: () => new Error('等待搜索结果超时')
  129. });
  130. });
  131. // 测试3: 表格数据加载
  132. await t.step('表格应加载并显示数据', async () => {
  133. // 等待数据加载完成或表格出现,最多等待5秒
  134. await waitFor(async () => {
  135. // 检查加载状态是否消失
  136. const loading = queryByText(/加载中/i);
  137. if (loading) {
  138. throw new Error('数据仍在加载中');
  139. }
  140. // 检查表格是否出现
  141. const table = await findByRole('table');
  142. assertExists(table, '未找到数据表格');
  143. // 检查表格是否有数据行
  144. const rows = await within(table).findAllByRole('row');
  145. assertNotEquals(rows.length, 1, '表格没有数据行'); // 1是表头行
  146. }, {
  147. timeout: 5000, // 5秒超时
  148. onTimeout: (error) => {
  149. return new Error(`数据加载超时: ${error.message}`);
  150. }
  151. });
  152. });
  153. // 测试4: 添加文章功能
  154. await t.step('应能打开添加文章模态框', async () => {
  155. const addButton = await findByText(/添加文章/i);
  156. fireEvent.click(addButton);
  157. const modalTitle = await findByText(/添加知识库文章/i);
  158. assertExists(modalTitle, '未找到添加文章模态框');
  159. // 验证表单字段
  160. const titleInput = await findByLabelText(/文章标题/i);
  161. assertExists(titleInput, '未找到标题输入框');
  162. });
  163. // 测试5: 完整添加文章流程
  164. await t.step('应能完整添加一篇文章', async () => {
  165. // 打开添加模态框
  166. const addButton = await findByText(/添加文章/i);
  167. fireEvent.click(addButton);
  168. // 填写表单
  169. const titleInput = await findByLabelText(/文章标题/i) as HTMLInputElement;
  170. const contentInput = await findByLabelText(/文章内容/i) as HTMLTextAreaElement;
  171. const submitButton = await findByText(/确 定/i);
  172. fireEvent.change(titleInput, { target: { value: '测试文章标题' } });
  173. fireEvent.change(contentInput, { target: { value: '这是测试文章内容' } });
  174. // 提交表单
  175. fireEvent.click(submitButton);
  176. // 验证提交后状态
  177. await waitFor(() => {
  178. const successMessage = queryByText(/添加成功/i);
  179. assertExists(successMessage, '未显示添加成功提示');
  180. });
  181. // 验证模态框已关闭
  182. await waitFor(() => {
  183. const modalTitle = queryByText(/添加知识库文章/i);
  184. assertEquals(modalTitle, null, '添加模态框未关闭');
  185. });
  186. // 验证表格中是否出现新添加的文章
  187. await waitFor(async () => {
  188. const table = await findByRole('table');
  189. const rows = await within(table).findAllByRole('row');
  190. const hasNewArticle = rows.some(row => {
  191. // 使用更通用的选择器来查找包含文本的单元格
  192. const cells = within(row).queryAllByRole('cell') ||
  193. within(row).queryAllByRole('gridcell') ||
  194. within(row).queryAllByRole('columnheader');
  195. return cells.some(cell => cell.textContent?.includes('测试文章标题'));
  196. });
  197. assert(hasNewArticle, '新添加的文章未出现在表格中');
  198. }, {
  199. timeout: 5000,
  200. onTimeout: () => new Error('等待新文章出现在表格中超时')
  201. });
  202. });
  203. // // 测试5: 分页功能
  204. // await t.step('应显示分页控件', async () => {
  205. // const pagination = await findByRole('navigation');
  206. // assertExists(pagination, '未找到分页控件');
  207. // const pageItems = await findAllByRole('button', { name: /1|2|3|下一页|上一页/i });
  208. // assertNotEquals(pageItems.length, 0, '未找到分页按钮');
  209. // });
  210. // // 测试6: 操作按钮
  211. // await t.step('应显示操作按钮', async () => {
  212. // const editButtons = await findAllByText(/编辑/i);
  213. // assertNotEquals(editButtons.length, 0, '未找到编辑按钮');
  214. // const deleteButtons = await findAllByText(/删除/i);
  215. // assertNotEquals(deleteButtons.length, 0, '未找到删除按钮');
  216. // });
  217. } finally {
  218. // 确保清理所有定时器
  219. cleanup();
  220. }
  221. },
  222. sanitizeOps: false, // 禁用操作清理检查
  223. sanitizeResources: false, // 禁用资源清理检查
  224. });