@qlover/create-app 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/react-app/README.en.md +257 -0
  5. package/dist/templates/react-app/README.md +29 -231
  6. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
  7. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
  8. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
  9. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
  10. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
  11. package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
  12. package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
  13. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
  14. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
  15. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
  16. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
  17. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
  18. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
  19. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
  20. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
  21. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
  22. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
  23. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
  24. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
  25. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
  26. package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
  27. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
  28. package/dist/templates/react-app/config/app.router.ts +155 -0
  29. package/dist/templates/react-app/config/common.ts +9 -1
  30. package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
  31. package/dist/templates/react-app/docs/en/development-guide.md +523 -0
  32. package/dist/templates/react-app/docs/en/env.md +482 -0
  33. package/dist/templates/react-app/docs/en/global.md +509 -0
  34. package/dist/templates/react-app/docs/en/i18n.md +268 -0
  35. package/dist/templates/react-app/docs/en/index.md +173 -0
  36. package/dist/templates/react-app/docs/en/ioc.md +424 -0
  37. package/dist/templates/react-app/docs/en/project-structure.md +434 -0
  38. package/dist/templates/react-app/docs/en/request.md +425 -0
  39. package/dist/templates/react-app/docs/en/router.md +404 -0
  40. package/dist/templates/react-app/docs/en/store.md +321 -0
  41. package/dist/templates/react-app/docs/en/test-guide.md +782 -0
  42. package/dist/templates/react-app/docs/en/theme.md +424 -0
  43. package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
  44. package/dist/templates/react-app/docs/zh/bootstrap.md +7 -0
  45. package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
  46. package/dist/templates/react-app/docs/zh/env.md +24 -25
  47. package/dist/templates/react-app/docs/zh/global.md +28 -27
  48. package/dist/templates/react-app/docs/zh/i18n.md +268 -0
  49. package/dist/templates/react-app/docs/zh/index.md +173 -0
  50. package/dist/templates/react-app/docs/zh/ioc.md +44 -32
  51. package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
  52. package/dist/templates/react-app/docs/zh/request.md +429 -0
  53. package/dist/templates/react-app/docs/zh/router.md +408 -0
  54. package/dist/templates/react-app/docs/zh/store.md +321 -0
  55. package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
  56. package/dist/templates/react-app/docs/zh/theme.md +424 -0
  57. package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
  58. package/dist/templates/react-app/package.json +9 -20
  59. package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
  60. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
  61. package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
  62. package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
  63. package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
  64. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
  65. package/dist/templates/react-app/src/core/globals.ts +1 -3
  66. package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
  67. package/dist/templates/react-app/src/main.tsx +6 -1
  68. package/dist/templates/react-app/src/pages/404.tsx +0 -1
  69. package/dist/templates/react-app/src/pages/500.tsx +1 -1
  70. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
  71. package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
  72. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
  73. package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
  74. package/dist/templates/react-app/src/styles/css/page.css +1 -1
  75. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
  76. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
  77. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
  78. package/dist/templates/react-app/tsconfig.json +2 -1
  79. package/dist/templates/react-app/tsconfig.test.json +13 -0
  80. package/dist/templates/react-app/vite.config.ts +3 -2
  81. package/package.json +1 -1
@@ -2,6 +2,8 @@
2
2
 
3
3
  一个现代化的 React 前端项目模板,集成了多项实用功能和最佳实践。
4
4
 
5
+ [English](./README.en.md)
6
+
5
7
  ## 🌟 特性亮点
6
8
 
7
9
  - 🚀 基于 Vite 的快速开发体验
@@ -64,6 +66,29 @@ pnpm build
64
66
  pnpm test
65
67
  ```
66
68
 
69
+ ## 📚 文档指南
70
+
71
+ 项目提供了详细的开发文档,涵盖了所有主要功能和最佳实践:
72
+
73
+ ### 基础文档
74
+ - [项目概述](./docs/zh/index.md) - 项目整体介绍和快速开始指南
75
+ - [项目结构](./docs/zh/project-structure.md) - 详细的项目目录结构说明
76
+ - [开发指南](./docs/zh/development-guide.md) - 项目开发规范和最佳实践
77
+ - [环境配置](./docs/zh/env.md) - 环境变量和配置管理说明
78
+ - [全局配置](./docs/zh/global.md) - 应用全局配置和设置说明
79
+
80
+ ### 核心功能
81
+ - [启动流程](./docs/zh/bootstrap.md) - 应用启动流程和生命周期管理
82
+ - [IOC容器](./docs/zh/ioc.md) - 依赖注入系统的使用说明
83
+ - [路由管理](./docs/zh/router.md) - 路由配置和页面导航说明
84
+ - [状态管理](./docs/zh/store.md) - 应用状态管理方案说明
85
+ - [请求处理](./docs/zh/request.md) - API 请求处理机制说明
86
+
87
+ ### 功能扩展
88
+ - [国际化](./docs/zh/i18n.md) - 多语言支持和翻译管理
89
+ - [主题系统](./docs/zh/theme.md) - 主题配置和暗色模式支持
90
+ - [TypeScript指南](./docs/zh/typescript-guide.md) - TypeScript 使用规范和最佳实践
91
+
67
92
  ## 🔨 核心功能
68
93
 
69
94
  ### IOC 容器
@@ -72,15 +97,15 @@ pnpm test
72
97
  - 支持服务自动注册和依赖管理
73
98
  - 提供完整的类型推导
74
99
 
75
- ### env 配置注入
100
+ ### 环境配置
76
101
 
77
102
  [vite 环境变量和模式](https://cn.vite.dev/guide/env-and-mode#env-variables-and-modes)
78
103
 
79
104
  `vite dev` 默认 NODE_ENV 表示为 development, 他会加载可能的 `.env[mode]` 文件, 比如 .env.local -> .env
80
105
 
81
- `vite build`默认 NODE_ENV 表示为 production, 他会加载可能的 `.env[mode]` 文件, 比如 .env.production -> .env
106
+ `vite build` 默认 NODE_ENV 表示为 production, 他会加载可能的 `.env[mode]` 文件, 比如 .env.production -> .env
82
107
 
83
- Nodejs NODE_ENV 只支持 development,production,test
108
+ Node.js NODE_ENV 只支持 development,production,test
84
109
 
85
110
  这个和 vite 中的 mode 是完全不一样的, mode 可以根据 `--mode` 指定不同模式,来加载不同的 env 配置
86
111
 
@@ -99,127 +124,6 @@ vite dev --mode local # 加载 .env.local
99
124
  - URL 路径语言检测和切换
100
125
  - 内置语言切换组件
101
126
 
102
- #### 国际化配置
103
-
104
- 项目使用 `@brain-toolkit/ts2locales` 插件从 TypeScript 注释自动生成国际化资源:
105
-
106
- ```typescript
107
- // config/Identifier/I18n.ts
108
- export const Messages = {
109
- /**
110
- * @description Home page welcome message
111
- * @localZh 欢迎来到主页
112
- * @localEn Welcome to the home page
113
- */
114
- HOME_WELCOME: 'home.welcome',
115
-
116
- /**
117
- * @description Get started button text
118
- * @localZh 开始使用
119
- * @localEn Get Started
120
- */
121
- HOME_GET_STARTED: 'home.get_started'
122
- };
123
- ```
124
-
125
- Vite 插件配置:
126
-
127
- ```typescript
128
- // vite.config.ts
129
- import ts2Locales from '@brain-toolkit/ts2locales/vite';
130
- import i18nConfig from './config/i18n';
131
-
132
- export default defineConfig({
133
- plugins: [
134
- ts2Locales({
135
- locales: i18nConfig.supportedLngs,
136
- options: [
137
- {
138
- source: './config/Identifier/error.ts',
139
- target: './public/locales/{{lng}}/common.json'
140
- },
141
- {
142
- source: './config/Identifier/index.ts',
143
- target: './public/locales/{{lng}}/common.json'
144
- }
145
- ]
146
- })
147
- ]
148
- });
149
- ```
150
-
151
- #### 国际化服务
152
-
153
- 项目提供了 `I18nService` 用于管理国际化状态和语言切换:
154
-
155
- ```typescript
156
- import { I18nService } from '@/base/services/I18nService';
157
-
158
- // 获取当前语言
159
- const currentLang = I18nService.getCurrentLanguage();
160
-
161
- // 切换语言
162
- await i18nService.changeLanguage('zh');
163
- ```
164
-
165
- #### 在组件中使用
166
-
167
- ```typescript
168
- import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
169
- import * as i18nKeys from '@config/Identifier';
170
-
171
- function MyComponent() {
172
- const { t } = useBaseRoutePage();
173
-
174
- return (
175
- <div>
176
- <h1>{t(i18nKeys.HOME_WELCOME)}</h1>
177
- <button>{t(i18nKeys.HOME_GET_STARTED)}</button>
178
- </div>
179
- );
180
- }
181
- ```
182
-
183
- #### 语言切换组件
184
-
185
- 项目提供了开箱即用的语言切换组件:
186
-
187
- ```typescript
188
- import LanguageSwitcher from '@/uikit/components/LanguageSwitcher';
189
-
190
- function Header() {
191
- return (
192
- <header>
193
- <LanguageSwitcher />
194
- </header>
195
- );
196
- }
197
- ```
198
-
199
- #### 最佳实践
200
-
201
- 1. 国际化标识符管理:
202
-
203
- - 在 `config/Identifier/index.ts` 中集中管理UI文本
204
- - 在 `config/Identifier/error.ts` 中集中管理错误信息
205
- - 使用有意义的 key 命名(如:'page.home.title')
206
-
207
- 2. TypeScript 注释规范:
208
-
209
- - 使用 `@description` 描述文本用途
210
- - 使用 `@localZh` 定义中文文本
211
- - 使用 `@localEn` 定义英文文本
212
-
213
- 3. 路由配置:
214
-
215
- - 使用 `LocaleLink` 组件进行页面跳转
216
- - URL 格式:`/{lang}/path`(如:'/zh/about')
217
-
218
- 4. 组件开发:
219
- - 使用 `useBaseRoutePage` hook 获取翻译函数
220
- - 从 `@config/Identifier` 引入国际化 key
221
- - 避免硬编码文本,始终使用国际化 key
222
-
223
127
  ### 主题系统
224
128
 
225
129
  - 基于 Tailwind CSS 的主题配置
@@ -266,106 +170,6 @@ export class RequestController extends StoreInterface<RequestControllerState> {
266
170
  }
267
171
  ```
268
172
 
269
- #### 请求插件系统
270
-
271
- 项目内置多个实用的请求插件:
272
-
273
- 1. `FetchURLPlugin`: URL 处理和构建
274
- 2. `RequestCommonPlugin`: 通用请求配置
275
- 3. `ApiMockPlugin`: Mock 数据支持
276
- 4. `FetchAbortPlugin`: 请求中断控制
277
- 5. `RequestLogger`: 请求日志记录
278
- 6. `ApiCatchPlugin`: 统一错误处理
279
-
280
- 使用示例:
281
-
282
- ```typescript
283
- // 配置请求适配器
284
- const adapter = new RequestAdapter({
285
- plugins: [
286
- new FetchURLPlugin(),
287
- new RequestCommonPlugin(),
288
- new ApiMockPlugin(),
289
- new FetchAbortPlugin(),
290
- new RequestLogger()
291
- ]
292
- });
293
-
294
- // 发起请求
295
- const response = await adapter.request({
296
- url: '/api/users',
297
- method: 'GET',
298
- requestId: 'uniqueId', // 用于中断请求
299
- mock: true // 启用 mock 数据
300
- });
301
- ```
302
-
303
- #### 请求调度器
304
-
305
- 使用 `RequestScheduler` 管理复杂的请求流程:
306
-
307
- ```typescript
308
- export class FeApi extends RequestScheduler<RequestConfig> {
309
- async getIpInfo() {
310
- return this.request<void, IpInfo>({
311
- url: '/api/ip',
312
- method: 'GET'
313
- });
314
- }
315
- }
316
- ```
317
-
318
- #### Mock 数据支持
319
-
320
- 项目支持灵活的 Mock 数据配置:
321
-
322
- ```typescript
323
- // 配置 Mock 数据
324
- const mockConfig = {
325
- '/api/users': {
326
- GET: () => ({
327
- code: 200,
328
- data: {
329
- id: 1,
330
- name: 'John Doe'
331
- }
332
- })
333
- }
334
- };
335
-
336
- // 在请求中使用
337
- const response = await api.request({
338
- url: '/api/users',
339
- method: 'GET',
340
- mock: true // 启用 mock
341
- });
342
- ```
343
-
344
- #### 最佳实践
345
-
346
- 1. API 定义:
347
-
348
- - 在 `src/base/apis` 中集中管理 API 定义
349
- - 使用 TypeScript 接口定义请求和响应类型
350
- - 遵循 RESTful API 设计规范
351
-
352
- 2. 错误处理:
353
-
354
- - 使用 `ApiCatchPlugin` 统一处理错误
355
- - 定义清晰的错误类型和错误码
356
- - 提供友好的错误提示
357
-
358
- 3. 请求状态管理:
359
-
360
- - 使用 `SliceStore` 管理请求状态
361
- - 处理加载、成功、错误等状态
362
- - 实现请求防抖和节流
363
-
364
- 4. Mock 数据:
365
- - 提供合理的 Mock 数据结构
366
- - 支持动态 Mock 数据生成
367
- - 便于本地开发和测试
368
-
369
173
  ### 控制器模式
370
174
 
371
175
  提供多个开箱即用的控制器:
@@ -376,7 +180,7 @@ const response = await api.request({
376
180
  - UserController
377
181
  - ThemeController
378
182
 
379
- ## 📚 开发指南
183
+ ## 🛠️ 开发指南
380
184
 
381
185
  ### API 开发规范
382
186
 
@@ -389,12 +193,6 @@ const response = await api.request({
389
193
  2. 更新 `config/app.router.json`
390
194
  3. 添加对应的控制器(如需要)
391
195
 
392
- ### 国际化开发
393
-
394
- 1. 在 `public/locales` 添加翻译文件
395
- 2. 使用 `useTranslation` hook 进行调用
396
- 3. 支持按需加载语言包
397
-
398
196
  ### 构建优化
399
197
 
400
198
  项目使用 Vite 进行构建,并进行了以下优化:
@@ -0,0 +1,13 @@
1
+ import { vi } from 'vitest';
2
+ import { I18nService } from '@/base/services/I18nService';
3
+
4
+ export class MockI18nService extends I18nService {
5
+ constructor() {
6
+ super('/');
7
+ }
8
+
9
+ t = vi.fn((key: string) => key);
10
+ changeLanguage = vi.fn();
11
+ changeLoading = vi.fn();
12
+ onBefore = vi.fn();
13
+ }
@@ -0,0 +1,48 @@
1
+ import { EnvConfigInterface } from '@qlover/corekit-bridge';
2
+ import { name, version } from '../../package.json';
3
+
4
+ export class MockAppConfig implements EnvConfigInterface {
5
+ appName = name;
6
+
7
+ appVersion = version;
8
+
9
+ env: string = import.meta.env.MODE;
10
+
11
+ userTokenStorageKey = '__fe_user_token__';
12
+
13
+ userInfoStorageKey = '__fe_user_info__';
14
+
15
+ openAiModels = [
16
+ 'gpt-4o-mini',
17
+ 'gpt-3.5-turbo',
18
+ 'gpt-3.5-turbo-2',
19
+ 'gpt-4',
20
+ 'gpt-4-32k'
21
+ ];
22
+
23
+ openAiBaseUrl = '';
24
+
25
+ openAiToken = '';
26
+
27
+ openAiTokenPrefix = '';
28
+
29
+ openAiRequireToken = true;
30
+
31
+ loginUser = '';
32
+
33
+ loginPassword = '';
34
+
35
+ feApiBaseUrl = '';
36
+
37
+ userApiBaseUrl = '';
38
+
39
+ aiApiBaseUrl = 'https://api.openai.com/v1';
40
+
41
+ aiApiToken = '';
42
+
43
+ aiApiTokenPrefix = 'Bearer';
44
+
45
+ aiApiRequireToken = true;
46
+
47
+ bootHref = '';
48
+ }
@@ -0,0 +1,16 @@
1
+ import { InteractionHubInterface } from '@/base/port/InteractionHubInterface';
2
+ import { AntdStaticApiInterface } from '@brain-toolkit/antd-theme-override/react';
3
+ import { vi } from 'vitest';
4
+
5
+ export class MockDialogHandler
6
+ implements InteractionHubInterface, AntdStaticApiInterface
7
+ {
8
+ setMessage = vi.fn();
9
+ setModal = vi.fn();
10
+ setNotification = vi.fn();
11
+ success = vi.fn();
12
+ error = vi.fn();
13
+ info = vi.fn();
14
+ warn = vi.fn();
15
+ confirm = vi.fn();
16
+ }
@@ -0,0 +1,14 @@
1
+ import { LoggerInterface } from '@qlover/logger';
2
+ import { vi } from 'vitest';
3
+
4
+ export class MockLogger implements LoggerInterface {
5
+ info = vi.fn();
6
+ error = vi.fn();
7
+ debug = vi.fn();
8
+ warn = vi.fn();
9
+ trace = vi.fn();
10
+ log = vi.fn();
11
+ fatal = vi.fn();
12
+ addAppender = vi.fn();
13
+ context = vi.fn();
14
+ }
@@ -0,0 +1,92 @@
1
+ import { vi } from 'vitest';
2
+ import { MockLogger } from './MockLogger';
3
+ import { MockAppConfig } from './MockAppConfit';
4
+ import { MockDialogHandler } from './MockDialogHandler';
5
+
6
+ export function createMockGlobals() {
7
+ const mockLogger = new MockLogger();
8
+ const mockAppConfig = new MockAppConfig();
9
+ const mockDialogHandler = new MockDialogHandler();
10
+
11
+ // Mock JSON serializer
12
+ const mockJSON = {
13
+ serialize: vi.fn((data) => JSON.stringify(data)),
14
+ deserialize: vi.fn((data) => JSON.parse(data)),
15
+ stringify: vi.fn((data) => JSON.stringify(data)),
16
+ parse: vi.fn((data) => JSON.parse(data))
17
+ };
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ const storageOptions: Record<string, any> = {
21
+ localStorage: {},
22
+ cookieStorage: {},
23
+ localStorageEncrypt: {}
24
+ };
25
+
26
+ // Mock localStorage with actual storage simulation
27
+ const mockLocalStorageData = storageOptions.localStorage ?? {};
28
+ const mockLocalStorage = {
29
+ get: vi.fn((key: string) => mockLocalStorageData[key]),
30
+ set: vi.fn((key: string, value: unknown) => {
31
+ mockLocalStorageData[key] = value;
32
+ }),
33
+ getItem: vi.fn((key: string) => mockLocalStorageData[key]),
34
+ setItem: vi.fn((key: string, value: unknown) => {
35
+ mockLocalStorageData[key] = value;
36
+ }),
37
+ remove: vi.fn((key: string) => {
38
+ delete mockLocalStorageData[key];
39
+ }),
40
+ removeItem: vi.fn((key: string) => {
41
+ delete mockLocalStorageData[key];
42
+ }),
43
+ clear: vi.fn(() => {
44
+ Object.keys(mockLocalStorageData).forEach((key) => {
45
+ delete mockLocalStorageData[key];
46
+ });
47
+ }),
48
+ has: vi.fn((key: string) => key in mockLocalStorageData),
49
+ keys: vi.fn(() => Object.keys(mockLocalStorageData)),
50
+ size: vi.fn(() => Object.keys(mockLocalStorageData).length)
51
+ };
52
+
53
+ // Mock localStorageEncrypt (same as localStorage for now)
54
+ const mockLocalStorageEncrypt = { ...mockLocalStorage };
55
+
56
+ // Mock cookieStorage
57
+ const mockCookieStorageData = storageOptions.cookieStorage ?? {};
58
+ const mockCookieStorage = {
59
+ get: vi.fn((key: string) => mockCookieStorageData[key]),
60
+ set: vi.fn((key: string, value: unknown, _options?: unknown) => {
61
+ mockCookieStorageData[key] = value;
62
+ }),
63
+ getItem: vi.fn((key: string) => mockCookieStorageData[key]),
64
+ setItem: vi.fn((key: string, value: unknown, _options?: unknown) => {
65
+ mockCookieStorageData[key] = value;
66
+ }),
67
+ remove: vi.fn((key: string) => {
68
+ delete mockCookieStorageData[key];
69
+ }),
70
+ removeItem: vi.fn((key: string) => {
71
+ delete mockCookieStorageData[key];
72
+ }),
73
+ clear: vi.fn(() => {
74
+ Object.keys(mockCookieStorageData).forEach((key) => {
75
+ delete mockCookieStorageData[key];
76
+ });
77
+ }),
78
+ has: vi.fn((key: string) => key in mockCookieStorageData),
79
+ keys: vi.fn(() => Object.keys(mockCookieStorageData)),
80
+ size: vi.fn(() => Object.keys(mockCookieStorageData).length)
81
+ };
82
+
83
+ return {
84
+ logger: mockLogger,
85
+ appConfig: mockAppConfig,
86
+ dialogHandler: mockDialogHandler,
87
+ JSON: mockJSON,
88
+ localStorage: mockLocalStorage,
89
+ localStorageEncrypt: mockLocalStorageEncrypt,
90
+ cookieStorage: mockCookieStorage
91
+ };
92
+ }
@@ -0,0 +1,51 @@
1
+ import { createMockGlobals } from '../__mocks__/createMockGlobals';
2
+
3
+ // 设置测试环境
4
+ beforeEach(() => {
5
+ // 清理DOM
6
+ document.body.innerHTML = '';
7
+
8
+ // 创建root元素(如果不存在)
9
+ if (!document.getElementById('root')) {
10
+ const rootElement = document.createElement('div');
11
+ rootElement.id = 'root';
12
+ document.body.appendChild(rootElement);
13
+ }
14
+ });
15
+
16
+ afterEach(() => {
17
+ // 清理所有mock
18
+ vi.clearAllMocks();
19
+ });
20
+
21
+ // 全局测试配置
22
+ global.ResizeObserver = vi.fn().mockImplementation(() => ({
23
+ observe: vi.fn(),
24
+ unobserve: vi.fn(),
25
+ disconnect: vi.fn()
26
+ }));
27
+
28
+ // Mock window.matchMedia
29
+ Object.defineProperty(window, 'matchMedia', {
30
+ writable: true,
31
+ value: vi.fn().mockImplementation((query) => ({
32
+ matches: false,
33
+ media: query,
34
+ onchange: null,
35
+ addListener: vi.fn(), // deprecated
36
+ removeListener: vi.fn(), // deprecated
37
+ addEventListener: vi.fn(),
38
+ removeEventListener: vi.fn(),
39
+ dispatchEvent: vi.fn()
40
+ }))
41
+ });
42
+
43
+ // Mock IntersectionObserver
44
+ global.IntersectionObserver = vi.fn().mockImplementation(() => ({
45
+ observe: vi.fn(),
46
+ unobserve: vi.fn(),
47
+ disconnect: vi.fn()
48
+ }));
49
+
50
+ // Mock globals
51
+ vi.mock('@/core/globals', () => createMockGlobals());
@@ -0,0 +1,139 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import App from '@/App';
3
+ import { routerPrefix } from '@config/common';
4
+
5
+ // Mock the dependencies
6
+ vi.mock('react-router-dom', async () => {
7
+ const actual = await vi.importActual('react-router-dom');
8
+ return {
9
+ ...actual,
10
+ createBrowserRouter: vi.fn(() => ({
11
+ routes: [],
12
+ basename: routerPrefix
13
+ })),
14
+ RouterProvider: vi.fn(({ router }) => (
15
+ <div data-testid="router-provider">
16
+ <div data-testid="router-basename">{router.basename}</div>
17
+ <div data-testid="router-routes-count">
18
+ {router.routes?.length || 0}
19
+ </div>
20
+ </div>
21
+ ))
22
+ };
23
+ });
24
+
25
+ vi.mock('@brain-toolkit/antd-theme-override/react', () => ({
26
+ AntdThemeProvider: vi.fn(({ children, theme }) => (
27
+ <div data-testid="antd-theme-provider">
28
+ <div data-testid="theme-key">{theme?.cssVar?.key}</div>
29
+ <div data-testid="theme-prefix">{theme?.cssVar?.prefix}</div>
30
+ {children}
31
+ </div>
32
+ ))
33
+ }));
34
+
35
+ vi.mock('@/core/IOC', () => ({
36
+ IOC: vi.fn((service) => {
37
+ if (service === 'RouteService') {
38
+ return {
39
+ routes: [
40
+ { path: '/', component: 'HomePage' },
41
+ { path: '/about', component: 'AboutPage' }
42
+ ]
43
+ };
44
+ }
45
+ if (service === 'DialogHandler') {
46
+ return {};
47
+ }
48
+ return {};
49
+ })
50
+ }));
51
+
52
+ vi.mock('@/uikit/hooks/useStore', () => ({
53
+ useStore: vi.fn((service, selector) => {
54
+ if (service && selector) {
55
+ return [
56
+ { path: '/', component: 'HomePage' },
57
+ { path: '/about', component: 'AboutPage' }
58
+ ];
59
+ }
60
+ return [];
61
+ })
62
+ }));
63
+
64
+ vi.mock('@/base/cases/RouterLoader', () => ({
65
+ RouterLoader: vi.fn().mockImplementation(() => ({
66
+ toRoute: vi.fn((route) => ({
67
+ path: route.path,
68
+ element: <div data-testid={`route-${route.path}`}>{route.component}</div>
69
+ }))
70
+ }))
71
+ }));
72
+
73
+ // Mock the CSS import
74
+ vi.mock('@/styles/css/index.css', () => ({}));
75
+
76
+ describe('App Component', () => {
77
+ beforeEach(() => {
78
+ vi.clearAllMocks();
79
+ });
80
+
81
+ it('should render without crashing', () => {
82
+ render(<App />);
83
+
84
+ // Check if the main app structure is rendered
85
+ expect(screen.getByTestId('antd-theme-provider')).toBeDefined();
86
+ expect(screen.getByTestId('router-provider')).toBeDefined();
87
+ });
88
+
89
+ it('should configure theme provider correctly', () => {
90
+ render(<App />);
91
+
92
+ // Check theme configuration
93
+ expect(screen.getByTestId('theme-key').textContent).toBe('fe-theme');
94
+ expect(screen.getByTestId('theme-prefix').textContent).toBe('fe');
95
+ });
96
+
97
+ it('should configure router with correct basename', () => {
98
+ render(<App />);
99
+
100
+ // Check router configuration
101
+ expect(screen.getByTestId('router-basename')).toBeDefined();
102
+ expect(screen.getByTestId('router-basename').textContent).toBe(
103
+ routerPrefix
104
+ );
105
+ });
106
+
107
+ it('should render router provider with routes', () => {
108
+ render(<App />);
109
+
110
+ // Check that router provider is rendered
111
+ expect(screen.getByTestId('router-provider')).toBeDefined();
112
+ expect(screen.getByTestId('router-routes-count')).toBeDefined();
113
+ });
114
+
115
+ it('should have proper component structure', () => {
116
+ const { container } = render(<App />);
117
+
118
+ // Check the overall structure
119
+ expect(container.firstChild).toBeDefined();
120
+
121
+ // Check that theme provider wraps router provider
122
+ const themeProvider = screen.getByTestId('antd-theme-provider');
123
+ const routerProvider = screen.getByTestId('router-provider');
124
+
125
+ expect(themeProvider.contains(routerProvider)).toBe(true);
126
+ });
127
+
128
+ it('should handle empty routes gracefully', async () => {
129
+ // Mock useStore to return empty routes
130
+ const { useStore } = await import('@/uikit/hooks/useStore');
131
+ vi.mocked(useStore).mockReturnValueOnce([]);
132
+
133
+ render(<App />);
134
+
135
+ // Should still render without crashing
136
+ expect(screen.getByTestId('antd-theme-provider')).toBeDefined();
137
+ expect(screen.getByTestId('router-provider')).toBeDefined();
138
+ });
139
+ });