2
0

pages_know_info.test.tsx 11 KB

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