diff --git a/client/admin/components_protected_route.tsx b/client/admin/components_protected_route.tsx
new file mode 100644
index 0000000..18b09f4
--- /dev/null
+++ b/client/admin/components_protected_route.tsx
@@ -0,0 +1,36 @@
+import React, { useEffect } from 'react';
+import {
+ useNavigate,
+} from 'react-router';
+
+
+import { useAuth } from './hooks_sys.tsx';
+
+
+export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
+ const { isAuthenticated, isLoading } = useAuth();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ // 只有在加载完成且未认证时才重定向
+ if (!isLoading && !isAuthenticated) {
+ navigate('/admin/login', { replace: true });
+ }
+ }, [isAuthenticated, isLoading, navigate]);
+
+ // 显示加载状态,直到认证检查完成
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ // 如果未认证且不再加载中,不显示任何内容(等待重定向)
+ if (!isAuthenticated) {
+ return null;
+ }
+
+ return children;
+};
\ No newline at end of file
diff --git a/client/admin/pages_know_info.test.tsx b/client/admin/pages_know_info.test.tsx
index a7cd5e8..6288420 100644
--- a/client/admin/pages_know_info.test.tsx
+++ b/client/admin/pages_know_info.test.tsx
@@ -1,7 +1,9 @@
import { JSDOM } from 'jsdom'
import React from 'react'
-import {render, fireEvent, within, screen, waitFor} from '@testing-library/react'
+import {render, fireEvent, within, screen, waitFor, configure} 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 {
assertEquals,
assertExists,
@@ -12,25 +14,72 @@ import {
import axios from 'axios';
import { KnowInfoPage } from "./pages_know_info.tsx"
import { AuthProvider } from './hooks_sys.tsx'
+import { ProtectedRoute } from './components_protected_route.tsx'
+
+// 拦截React DOM中的attachEvent和detachEvent错误
+const originalError = console.error;
+console.error = (...args) => {
+ // 过滤掉attachEvent和detachEvent相关的错误
+ if (args[0] instanceof Error) {
+ if (args[0].message?.includes('attachEvent is not a function') ||
+ args[0].message?.includes('detachEvent is not a function')) {
+ return; // 不输出这些错误
+ }
+ } else if (typeof args[0] === 'string') {
+ if (args[0].includes('attachEvent is not a function') ||
+ args[0].includes('detachEvent is not a function')) {
+ return; // 不输出这些错误
+ }
+ }
+ 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"
+ 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();
@@ -72,6 +121,22 @@ axios.defaults.baseURL = 'https://23957.dev.d8dcloud.com'
const customScreen = within(document.body);
+// 应用入口组件
+const App = () => {
+ // 路由配置
+ const router = createBrowserRouter([
+ {
+ path: '/',
+ element: (
+
+
+
+ )
+ },
+ ]);
+ return
+};
+
// 使用异步测试处理组件渲染
Deno.test({
name: '知识库管理页面测试',
@@ -105,6 +170,8 @@ Deno.test({
globalThis.setInterval = originalSetInterval;
};
+
+
try {
// 渲染组件
const {
@@ -113,79 +180,116 @@ Deno.test({
} = render(
-
+
);
// 测试1: 基本渲染
await t.step('应正确渲染页面元素', async () => {
- const title = await findByText(/知识库管理/i);
- assertExists(title, '未找到知识库管理标题');
+ await waitFor(async () => {
+ const title = await findByText(/知识库管理/i);
+ assertExists(title, '未找到知识库管理标题');
+ }, {
+ timeout: 1000 * 5,
+ });
});
- let i = 0
-
// 初始加载表格数据
- await waitFor(async () => {
- const table = await findByRole('table');
- const rows = await within(table).findAllByRole('row');
-
- // debug(rows[1])
- i++
- console.log('i', i)
- console.log('rows', rows.length)
-
- // 应该大于2行
- // assert(rows.length > 2, '表格没有数据'); // 1是表头行 2是数据行
-
- if (rows.length <= 2) {
- throw new Error('表格没有数据');
- }
- }, {
- timeout: 1000 * 10,
- });
-
- // 测试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 t.step('初始加载表格数据', async () => {
await waitFor(async () => {
const table = await findByRole('table');
const rows = await within(table).findAllByRole('row');
- debug(rows)
-
- console.log('rows', rows.length);
+ // 应该大于2行
+ assert(rows.length > 2, '表格没有数据'); // 1是表头行 2是数据行
- // 检查至少有一行包含"数据分析"
- const hasMatch = rows.some(async row => {
- const cells = await within(row).findAllByRole('cell');
- return cells.some(cell => cell.textContent?.includes('数据分析'));
- });
-
- console.log('hasMatch', hasMatch);
-
- assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章');
}, {
- timeout: 5000,
+ 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, '未找到搜索按钮');
+
+ // 输入搜索内容
+ try {
+ await user.type(searchInput, '数据分析')
+ } catch (error: unknown) {
+ // console.error('输入搜索内容失败', error)
+ }
+ assertEquals(searchInput.value, '数据分析', '搜索输入框值未更新');
+
+ console.log('searchInput', searchInput.value)
+
+ debug(searchInput)
+ debug(searchButton)
+
+ // 提交搜索
+ try {
+ await user.click(searchButton);
+ } catch (error: unknown) {
+ // console.error('点击搜索按钮失败', error)
+ }
+
+
+ let rows: HTMLElement[] = [];
+
+
+ const table = await findByRole('table');
+ assertExists(table, '未找到数据表格');
+
+ // 等待表格刷新并验证
+ await waitFor(async () => {
+ rows = await within(table).findAllByRole('row');
+ console.log('等待表格刷新并验证', rows.length)
+ assert(rows.length === 2, '表格未刷新');
+ }, {
+ timeout: 1000 * 5,
+ onTimeout: () => new Error('等待表格刷新超时')
+ });
+
+ // 等待搜索结果并验证
+ await waitFor(async () => {
+ rows = await within(table).findAllByRole('row');
+ console.log('等待搜索结果并验证', rows.length)
+ 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);
+
+ console.log('hasMatch', hasMatch)
+
+ assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章');
});
// 测试3: 表格数据加载
diff --git a/client/admin/pages_know_info.tsx b/client/admin/pages_know_info.tsx
index ba37fbf..7839478 100644
--- a/client/admin/pages_know_info.tsx
+++ b/client/admin/pages_know_info.tsx
@@ -148,6 +148,7 @@ export const KnowInfoPage = () => {
// 处理搜索
const handleSearch = async (values: any) => {
try {
+ console.log('handleSearch', values)
queryClient.removeQueries({ queryKey: ['knowInfos'] });
setSearchParams({
title: values.title || '',
@@ -286,7 +287,7 @@ export const KnowInfoPage = () => {
-