diff --git a/client/admin/pages_know_info.test.tsx b/client/admin/pages_know_info.test.tsx index 68fed05..9f76896 100644 --- a/client/admin/pages_know_info.test.tsx +++ b/client/admin/pages_know_info.test.tsx @@ -1,9 +1,9 @@ import { JSDOM } from 'jsdom' import React from 'react' -import {render, waitFor, within} from '@testing-library/react' +import {render, waitFor, within, fireEvent} from '@testing-library/react' import {userEvent} from '@testing-library/user-event' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { createBrowserRouter, RouterProvider, Navigate } from 'react-router' +import { createBrowserRouter, RouterProvider } from 'react-router' import { assertEquals, assertExists, @@ -34,93 +34,6 @@ console.error = (...args) => { originalError(...args); }; -// // 配置Testing Library的eventWrapper来处理这个问题 -// configure({ -// eventWrapper: (cb) => { -// try { -// return cb(); -// } catch (error) { -// console.log('eventWrapper', cb) -// // 忽略attachEvent和detachEvent相关的错误 -// if (error instanceof Error && -// (error.message?.includes('attachEvent is not a function') || -// error.message?.includes('detachEvent is not a function'))) { -// // 忽略这个错误并返回一个默认值 -// return undefined; -// } -// // 其他错误正常抛出 -// throw error; -// } -// } -// }); - -const queryClient = new QueryClient() - -const dom = new JSDOM(``, { - runScripts: "dangerously", - pretendToBeVisual: true, - url: "http://localhost", -}); - -// 模拟浏览器环境 -globalThis.window = dom.window; -globalThis.document = dom.window.document; - -// 添加必要的 DOM 配置 -globalThis.Node = dom.window.Node; -globalThis.Document = dom.window.Document; -globalThis.HTMLInputElement = dom.window.HTMLInputElement; -globalThis.HTMLButtonElement = dom.window.HTMLButtonElement; - -// 定义浏览器环境所需的类 -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 = () => {}; - -localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsInNlc3Npb25JZCI6Ijk4T2lzTW5SMm0zQ0dtNmo4SVZrNyIsInJvbGVJbmZvIjpudWxsLCJpYXQiOjE3NDQzNjIzNTUsImV4cCI6MTc0NDQ0ODc1NX0.k1Ld7qWAZmdzsbjmrl_0ec1FqF_GimaOuQIic4znRtc'); - -axios.defaults.baseURL = 'https://23957.dev.d8dcloud.com' - -const customScreen = within(document.body); - // 应用入口组件 const App = () => { // 路由配置 @@ -136,10 +49,329 @@ const App = () => { ]); return }; +// setup function +function setup() { + + const dom = new JSDOM(``, { + runScripts: "dangerously", + pretendToBeVisual: true, + url: "http://localhost", + }); + + // 模拟浏览器环境 + globalThis.window = dom.window; + globalThis.document = dom.window.document; + + // 添加必要的 DOM 配置 + globalThis.Node = dom.window.Node; + globalThis.Document = dom.window.Document; + globalThis.HTMLInputElement = dom.window.HTMLInputElement; + globalThis.HTMLButtonElement = dom.window.HTMLButtonElement; + + // 定义浏览器环境所需的类 + 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); + + const user = userEvent.setup({ + document: dom.window.document, + delay: 10, + skipAutoClose: true, + }); + + localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsInNlc3Npb25JZCI6Ijk4T2lzTW5SMm0zQ0dtNmo4SVZrNyIsInJvbGVJbmZvIjpudWxsLCJpYXQiOjE3NDQzNjIzNTUsImV4cCI6MTc0NDQ0ODc1NX0.k1Ld7qWAZmdzsbjmrl_0ec1FqF_GimaOuQIic4znRtc'); + + axios.defaults.baseURL = 'https://23957.dev.d8dcloud.com' + + const queryClient = new QueryClient() + + return { + user, + // Import `render` from the framework library of your choice. + // See https://testing-library.com/docs/dom-testing-library/install#wrappers + ...render( + + + + + + ), + } +} + +// // 使用异步测试处理组件渲染 +// 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, debug, +// // queryByRole + +// // } = render( +// // +// // +// // +// // +// // +// // ); + +// // 测试1: 基本渲染 +// await t.step('应正确渲染页面元素', async () => { +// const { findByText } = setup() +// await waitFor(async () => { +// const title = await findByText(/知识库管理/i); +// assertExists(title, '未找到知识库管理标题'); +// }, { +// timeout: 1000 * 5, +// }); +// }); + +// // 初始加载表格数据 +// await t.step('初始加载表格数据', async () => { +// const { findByRole } = setup() +// await waitFor(async () => { +// const table = await findByRole('table'); +// const rows = await within(table).findAllByRole('row'); + +// // 应该大于2行 +// assert(rows.length > 2, '表格没有数据'); // 1是表头行 2是数据行 + +// }, { +// timeout: 1000 * 5, +// }); +// }); + +// // 测试2: 搜索表单功能 +// await t.step('搜索表单应正常工作', async () => { +// const {findByPlaceholderText, findByText, findByRole, user} = setup() + +// // 等待知识库管理标题出现 +// await waitFor(async () => { +// const title = await findByText(/知识库管理/i); +// assertExists(title, '未找到知识库管理标题'); +// }, { +// timeout: 1000 * 5, +// }); + +// // 直接查找标题搜索输入框和搜索按钮 +// const searchInput = await findByPlaceholderText(/请输入文章标题/i) as HTMLInputElement; +// const searchButton = await findByText(/搜 索/i); + +// assertExists(searchInput, '未找到搜索输入框'); +// assertExists(searchButton, '未找到搜索按钮'); + +// // 输入搜索内容 +// await user.type(searchInput, '数据分析') +// assertEquals(searchInput.value, '数据分析', '搜索输入框值未更新'); + +// // 提交搜索 +// await user.click(searchButton); + + +// let rows: HTMLElement[] = []; + + +// const table = await findByRole('table'); +// assertExists(table, '未找到数据表格'); + +// // 等待表格刷新并验证 +// await waitFor(async () => { +// rows = await within(table).findAllByRole('row'); +// assert(rows.length === 2, '表格未刷新'); +// }, { +// timeout: 1000 * 5, +// onTimeout: () => new Error('等待表格刷新超时') +// }); + +// // 等待搜索结果并验证 +// await waitFor(async () => { +// rows = await within(table).findAllByRole('row'); +// assert(rows.length > 2, '表格没有数据'); +// }, { +// timeout: 1000 * 5, +// onTimeout: () => new Error('等待搜索结果超时') +// }); + + + +// // 检查至少有一行包含"数据分析" +// const matchResults = await Promise.all(rows.map(async row => { +// try{ +// const cells = await within(row).findAllByRole('cell'); +// return cells.some(cell => { +// return cell.textContent?.includes('数据分析') +// }); +// } catch (error: unknown) { +// // console.error('搜索结果获取失败', error) +// return false +// } +// })) +// // console.log('matchResults', matchResults) +// const hasMatch = matchResults.some(result => result); + +// assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章'); +// }); + +// // 测试3: 表格数据加载 +// await t.step('表格应加载并显示数据', async () => { +// const {findByRole, queryByText} = setup() +// // 等待数据加载完成或表格出现,最多等待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 {findByText, findByRole, user} = setup() +// // 等待知识库管理标题出现 +// await waitFor(async () => { +// const title = await findByText(/知识库管理/i); +// assertExists(title, '未找到知识库管理标题'); +// }, { +// timeout: 1000 * 5, +// }); + +// const addButton = await findByText(/添加文章/i); +// assertExists(addButton, '未找到添加文章按钮'); + +// await user.click(addButton); + +// // 找到模态框 +// const modal = await findByRole('dialog'); +// assertExists(modal, '未找到模态框'); + +// const modalTitle = await within(modal).findByText(/添加知识库文章/i); +// assertExists(modalTitle, '未找到添加文章模态框'); + +// // 验证表单字段 +// const titleInput = await within(modal).findByPlaceholderText(/请输入文章标题/i); +// assertExists(titleInput, '未找到标题输入框'); + +// const contentInput = await within(modal).findByPlaceholderText(/请输入文章内容/i); +// assertExists(contentInput, '未找到文章内容输入框'); + +// // 取消 +// const cancelButton = await within(modal).findByText(/取 消/i); +// assertExists(cancelButton, '未找到取消按钮'); +// await user.click(cancelButton); + +// // 验证模态框是否关闭 +// await waitFor(async () => { + +// const modalTitle = await within(modal).findByText(/添加知识库文章/i); +// assertExists(!modalTitle, '模态框未关闭'); +// }, { +// timeout: 1000 * 5, +// onTimeout: () => new Error('等待模态框关闭超时') +// }); +// }); + +// } finally { +// // 确保清理所有定时器 +// cleanup(); +// } +// }, +// sanitizeOps: false, // 禁用操作清理检查 +// sanitizeResources: false, // 禁用资源清理检查 +// }); -// 使用异步测试处理组件渲染 Deno.test({ - name: '知识库管理页面测试', + name: '知识库管理页面新增测试', fn: async (t) => { // 存储所有需要清理的定时器 const timers: number[] = []; @@ -175,187 +407,31 @@ Deno.test({ try { - // 渲染组件 - const { - findByText, findByPlaceholderText, queryByText, - findByRole, findAllByRole, findByLabelText, findAllByText, debug, - queryByRole + // // 渲染组件 + // const { + // findByText, findByPlaceholderText, queryByText, + // findByRole, findAllByRole, findByLabelText, findAllByText, debug, + // queryByRole - } = render( - - - - - - ); + // } = render( + // + // + // + // + // + // ); - // 测试1: 基本渲染 - await t.step('应正确渲染页面元素', async () => { + + // 测试5: 完整添加文章流程 + await t.step('应能完整添加一篇文章', async () => { + const {findByText, findByRole, debug, user} = setup() + // 等待知识库管理标题出现 await waitFor(async () => { const title = await findByText(/知识库管理/i); assertExists(title, '未找到知识库管理标题'); }, { timeout: 1000 * 5, }); - }); - - // 初始加载表格数据 - await t.step('初始加载表格数据', async () => { - await waitFor(async () => { - const table = await findByRole('table'); - const rows = await within(table).findAllByRole('row'); - - // 应该大于2行 - assert(rows.length > 2, '表格没有数据'); // 1是表头行 2是数据行 - - }, { - timeout: 1000 * 5, - }); - }); - - // 测试2: 搜索表单功能 - await t.step('搜索表单应正常工作', async () => { - - // 确保在正确的测试环境中设置 userEvent - const user = userEvent.setup({ - document: dom.window.document, - delay: 0 - }); - // 直接查找标题搜索输入框和搜索按钮 - const searchInput = await findByPlaceholderText(/请输入文章标题/i) as HTMLInputElement; - const searchButton = await findByText(/搜 索/i); - - assertExists(searchInput, '未找到搜索输入框'); - assertExists(searchButton, '未找到搜索按钮'); - - // 输入搜索内容 - await user.type(searchInput, '数据分析') - assertEquals(searchInput.value, '数据分析', '搜索输入框值未更新'); - - // 提交搜索 - await user.click(searchButton); - - - let rows: HTMLElement[] = []; - - - const table = await findByRole('table'); - assertExists(table, '未找到数据表格'); - - // 等待表格刷新并验证 - await waitFor(async () => { - rows = await within(table).findAllByRole('row'); - assert(rows.length === 2, '表格未刷新'); - }, { - timeout: 1000 * 5, - onTimeout: () => new Error('等待表格刷新超时') - }); - - // 等待搜索结果并验证 - await waitFor(async () => { - rows = await within(table).findAllByRole('row'); - assert(rows.length > 2, '表格没有数据'); - }, { - timeout: 1000 * 5, - onTimeout: () => new Error('等待搜索结果超时') - }); - - - - // 检查至少有一行包含"数据分析" - const matchResults = await Promise.all(rows.map(async row => { - try{ - const cells = await within(row).findAllByRole('cell'); - return cells.some(cell => { - return cell.textContent?.includes('数据分析') - }); - } catch (error: unknown) { - // console.error('搜索结果获取失败', error) - return false - } - })) - // console.log('matchResults', matchResults) - const hasMatch = matchResults.some(result => result); - - assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章'); - }); - - // 测试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 () => { - // 确保在正确的测试环境中设置 userEvent - const user = userEvent.setup({ - document: dom.window.document, - delay: 0 - }); - - const addButton = await findByText(/添加文章/i); - assertExists(addButton, '未找到添加文章按钮'); - - await user.click(addButton); - - // 找到模态框 - const modal = await findByRole('dialog'); - assertExists(modal, '未找到模态框'); - - const modalTitle = await within(modal).findByText(/添加知识库文章/i); - assertExists(modalTitle, '未找到添加文章模态框'); - - // 验证表单字段 - const titleInput = await within(modal).findByPlaceholderText(/请输入文章标题/i); - assertExists(titleInput, '未找到标题输入框'); - - const contentInput = await within(modal).findByPlaceholderText(/请输入文章内容/i); - assertExists(contentInput, '未找到文章内容输入框'); - - // 取消 - const cancelButton = await within(modal).findByText(/取 消/i); - assertExists(cancelButton, '未找到取消按钮'); - await user.click(cancelButton); - - // 验证模态框是否关闭 - await waitFor(async () => { - - const modalTitle = await within(modal).findByText(/添加知识库文章/i); - assertExists(!modalTitle, '模态框未关闭'); - }, { - timeout: 1000 * 5, - onTimeout: () => new Error('等待模态框关闭超时') - }); - }); - - // 测试5: 完整添加文章流程 - await t.step('应能完整添加一篇文章', async () => { - - // 确保在正确的测试环境中设置 userEvent - const user = userEvent.setup({ - document: dom.window.document, - delay: 0 - }); // 打开添加模态框 const addButton = await findByText(/添加文章/i); assertExists(addButton, '未找到添加文章按钮'); @@ -391,12 +467,13 @@ Deno.test({ debug(contentInput); debug(submitButton); + // 提交表单 + await user.click(submitButton); + // 验证表单字段 assertEquals(titleInput.value, '测试文章标题', '模态框中标题输入框值未更新'); assertEquals(contentInput.value, '这是测试文章内容', '内容输入框值未更新'); - // 提交表单 - await user.click(submitButton); let rows: HTMLElement[] = []; @@ -463,4 +540,4 @@ Deno.test({ }, sanitizeOps: false, // 禁用操作清理检查 sanitizeResources: false, // 禁用资源清理检查 -}); +}); \ No newline at end of file diff --git a/client/admin/pages_know_info.tsx b/client/admin/pages_know_info.tsx index 35217f5..fdaaaf9 100644 --- a/client/admin/pages_know_info.tsx +++ b/client/admin/pages_know_info.tsx @@ -59,6 +59,7 @@ export const KnowInfoPage = () => { const [formMode, setFormMode] = useState<'create' | 'edit'>('create'); const [editingId, setEditingId] = useState(null); const [form] = Form.useForm(); + const [searchForm] = Form.useForm(); const [searchParams, setSearchParams] = useState({ title: '', category: '', @@ -270,6 +271,7 @@ export const KnowInfoPage = () => {
{ 搜索