@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.
- package/CHANGELOG.md +53 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/react-app/README.en.md +257 -0
- package/dist/templates/react-app/README.md +29 -231
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
- package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
- package/dist/templates/react-app/config/app.router.ts +155 -0
- package/dist/templates/react-app/config/common.ts +9 -1
- package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
- package/dist/templates/react-app/docs/en/development-guide.md +523 -0
- package/dist/templates/react-app/docs/en/env.md +482 -0
- package/dist/templates/react-app/docs/en/global.md +509 -0
- package/dist/templates/react-app/docs/en/i18n.md +268 -0
- package/dist/templates/react-app/docs/en/index.md +173 -0
- package/dist/templates/react-app/docs/en/ioc.md +424 -0
- package/dist/templates/react-app/docs/en/project-structure.md +434 -0
- package/dist/templates/react-app/docs/en/request.md +425 -0
- package/dist/templates/react-app/docs/en/router.md +404 -0
- package/dist/templates/react-app/docs/en/store.md +321 -0
- package/dist/templates/react-app/docs/en/test-guide.md +782 -0
- package/dist/templates/react-app/docs/en/theme.md +424 -0
- package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +7 -0
- package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
- package/dist/templates/react-app/docs/zh/env.md +24 -25
- package/dist/templates/react-app/docs/zh/global.md +28 -27
- package/dist/templates/react-app/docs/zh/i18n.md +268 -0
- package/dist/templates/react-app/docs/zh/index.md +173 -0
- package/dist/templates/react-app/docs/zh/ioc.md +44 -32
- package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
- package/dist/templates/react-app/docs/zh/request.md +429 -0
- package/dist/templates/react-app/docs/zh/router.md +408 -0
- package/dist/templates/react-app/docs/zh/store.md +321 -0
- package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
- package/dist/templates/react-app/docs/zh/theme.md +424 -0
- package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
- package/dist/templates/react-app/package.json +9 -20
- package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
- package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
- package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
- package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
- package/dist/templates/react-app/src/core/globals.ts +1 -3
- package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
- package/dist/templates/react-app/src/main.tsx +6 -1
- package/dist/templates/react-app/src/pages/404.tsx +0 -1
- package/dist/templates/react-app/src/pages/500.tsx +1 -1
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
- package/dist/templates/react-app/src/styles/css/page.css +1 -1
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
- package/dist/templates/react-app/tsconfig.json +2 -1
- package/dist/templates/react-app/tsconfig.test.json +13 -0
- package/dist/templates/react-app/vite.config.ts +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { IOC, type IOCContainer, type IOCIdentifierMap } from '@/core/IOC';
|
|
2
|
+
import { InversifyContainer } from '@/base/cases/InversifyContainer';
|
|
3
|
+
import { IocRegisterImpl } from '@/core/registers/IocRegisterImpl';
|
|
4
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
5
|
+
import type { AppConfig } from '@/base/cases/AppConfig';
|
|
6
|
+
|
|
7
|
+
describe('IOC Container Tests', () => {
|
|
8
|
+
let container: IOCContainer;
|
|
9
|
+
let mockAppConfig: AppConfig;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
container = new InversifyContainer();
|
|
13
|
+
mockAppConfig = {
|
|
14
|
+
appName: '',
|
|
15
|
+
appVersion: '',
|
|
16
|
+
env: 'test',
|
|
17
|
+
userInfoStorageKey: '__fe_user_info__',
|
|
18
|
+
userTokenStorageKey: '__fe_user_token__',
|
|
19
|
+
openAiModels: [
|
|
20
|
+
'gpt-4o-mini',
|
|
21
|
+
'gpt-3.5-turbo',
|
|
22
|
+
'gpt-3.5-turbo-2',
|
|
23
|
+
'gpt-4',
|
|
24
|
+
'gpt-4-32k'
|
|
25
|
+
],
|
|
26
|
+
openAiBaseUrl: '',
|
|
27
|
+
openAiToken: '',
|
|
28
|
+
openAiTokenPrefix: '',
|
|
29
|
+
openAiRequireToken: true,
|
|
30
|
+
loginUser: '',
|
|
31
|
+
loginPassword: '',
|
|
32
|
+
feApiBaseUrl: '',
|
|
33
|
+
userApiBaseUrl: '',
|
|
34
|
+
aiApiBaseUrl: 'https://api.openai.com/v1',
|
|
35
|
+
aiApiToken: '',
|
|
36
|
+
aiApiTokenPrefix: 'Bearer',
|
|
37
|
+
aiApiRequireToken: true,
|
|
38
|
+
bootHref: ''
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('InversifyContainer', () => {
|
|
43
|
+
it('should create container with correct configuration', () => {
|
|
44
|
+
expect(container).toBeInstanceOf(InversifyContainer);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should bind and get services correctly', () => {
|
|
48
|
+
const mockService = { test: 'service' };
|
|
49
|
+
const mockKey = 'testKey';
|
|
50
|
+
|
|
51
|
+
container.bind(mockKey, mockService);
|
|
52
|
+
const result = container.get(mockKey);
|
|
53
|
+
|
|
54
|
+
expect(result).toBe(mockService);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle string identifiers correctly', () => {
|
|
58
|
+
const mockLogger = { info: vi.fn() };
|
|
59
|
+
container.bind(IOCIdentifier.Logger, mockLogger);
|
|
60
|
+
|
|
61
|
+
const result = container.get(IOCIdentifier.Logger);
|
|
62
|
+
expect(result).toBe(mockLogger);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('IOC Function', () => {
|
|
67
|
+
it('should be a function', () => {
|
|
68
|
+
expect(typeof IOC).toBe('function');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should accept string identifiers', () => {
|
|
72
|
+
// This test verifies that IOC function can be called with string identifiers
|
|
73
|
+
// The function should accept the identifier, but will throw if service is not bound
|
|
74
|
+
expect(() => {
|
|
75
|
+
IOC(IOCIdentifier.Logger);
|
|
76
|
+
}).toThrow();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should accept class constructors', () => {
|
|
80
|
+
// Mock class for testing
|
|
81
|
+
class TestService {
|
|
82
|
+
constructor() {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// if service is not bound, it will return the class itself
|
|
86
|
+
const result = IOC(TestService);
|
|
87
|
+
expect(result).toBeInstanceOf(TestService);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('IocRegisterImpl', () => {
|
|
92
|
+
let registerImpl: IocRegisterImpl;
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
registerImpl = new IocRegisterImpl({
|
|
96
|
+
pathname: '/test',
|
|
97
|
+
appConfig: mockAppConfig
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should create register implementation correctly', () => {
|
|
102
|
+
expect(registerImpl).toBeInstanceOf(IocRegisterImpl);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return register list', () => {
|
|
106
|
+
const registerList = registerImpl.getRegisterList();
|
|
107
|
+
expect(Array.isArray(registerList)).toBe(true);
|
|
108
|
+
expect(registerList.length).toBeGreaterThan(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should register services without throwing', () => {
|
|
112
|
+
const mockManager = {
|
|
113
|
+
get: vi.fn(),
|
|
114
|
+
bind: vi.fn(),
|
|
115
|
+
implement: vi.fn(),
|
|
116
|
+
implemention: container
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
expect(() => {
|
|
120
|
+
registerImpl.register(container, mockManager);
|
|
121
|
+
}).not.toThrow();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('IOCIdentifierMap Type Safety', () => {
|
|
126
|
+
it('should have correct type mapping for JSON', () => {
|
|
127
|
+
// This test verifies that the type mapping is correct
|
|
128
|
+
const identifierMap: IOCIdentifierMap = {} as IOCIdentifierMap;
|
|
129
|
+
|
|
130
|
+
// TypeScript should enforce that these keys exist
|
|
131
|
+
expect(IOCIdentifier.JSON in identifierMap).toBeDefined();
|
|
132
|
+
expect(IOCIdentifier.LocalStorage in identifierMap).toBeDefined();
|
|
133
|
+
expect(IOCIdentifier.Logger in identifierMap).toBeDefined();
|
|
134
|
+
expect(IOCIdentifier.AppConfig in identifierMap).toBeDefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should have all required identifiers', () => {
|
|
138
|
+
const requiredIdentifiers = [
|
|
139
|
+
'JSON',
|
|
140
|
+
'LocalStorage',
|
|
141
|
+
'LocalStorageEncrypt',
|
|
142
|
+
'CookieStorage',
|
|
143
|
+
'Logger',
|
|
144
|
+
'FeApiToken',
|
|
145
|
+
'FeApiCommonPlugin',
|
|
146
|
+
'AppConfig',
|
|
147
|
+
'ApiMockPlugin',
|
|
148
|
+
'ApiCatchPlugin',
|
|
149
|
+
'DialogHandler'
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
requiredIdentifiers.forEach((identifier) => {
|
|
153
|
+
expect(IOCIdentifier).toHaveProperty(identifier);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('Service Registration Integration', () => {
|
|
159
|
+
it('should register global services correctly', async () => {
|
|
160
|
+
const registerImpl = new IocRegisterImpl({
|
|
161
|
+
pathname: '/test',
|
|
162
|
+
appConfig: mockAppConfig
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const mockManager = {
|
|
166
|
+
get: vi.fn(),
|
|
167
|
+
bind: vi.fn(),
|
|
168
|
+
implement: vi.fn(),
|
|
169
|
+
implemention: container
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// This should not throw and should register services
|
|
173
|
+
expect(() => {
|
|
174
|
+
registerImpl.register(container, mockManager);
|
|
175
|
+
}).not.toThrow();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should handle app config registration', () => {
|
|
179
|
+
container.bind(IOCIdentifier.AppConfig, mockAppConfig);
|
|
180
|
+
const result = container.get(IOCIdentifier.AppConfig);
|
|
181
|
+
|
|
182
|
+
expect(result).toBe(mockAppConfig);
|
|
183
|
+
expect(result.appName).toBe('');
|
|
184
|
+
expect(result.env).toBe('test');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should handle logger registration', () => {
|
|
188
|
+
const mockLogger = {
|
|
189
|
+
info: vi.fn(),
|
|
190
|
+
warn: vi.fn(),
|
|
191
|
+
error: vi.fn(),
|
|
192
|
+
debug: vi.fn()
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
container.bind(IOCIdentifier.Logger, mockLogger);
|
|
196
|
+
const result = container.get(IOCIdentifier.Logger);
|
|
197
|
+
|
|
198
|
+
expect(result).toBe(mockLogger);
|
|
199
|
+
expect(typeof result.info).toBe('function');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('Error Handling', () => {
|
|
204
|
+
it('should handle missing service gracefully', () => {
|
|
205
|
+
expect(() => {
|
|
206
|
+
container.get('NonExistentService');
|
|
207
|
+
}).toThrow();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle invalid identifier types', () => {
|
|
211
|
+
expect(() => {
|
|
212
|
+
container.get(null as never);
|
|
213
|
+
}).toThrow();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Dependency Injection', () => {
|
|
218
|
+
it('should support injectable decorators', () => {
|
|
219
|
+
// This test verifies that the container supports @injectable decorators
|
|
220
|
+
// The autobind configuration should allow this
|
|
221
|
+
expect(container).toBeDefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should support singleton scope', () => {
|
|
225
|
+
// This test verifies that the container uses singleton scope by default
|
|
226
|
+
const service1 = { id: 'test' };
|
|
227
|
+
const service2 = { id: 'test' };
|
|
228
|
+
|
|
229
|
+
container.bind('TestService1', service1);
|
|
230
|
+
container.bind('TestService2', service2);
|
|
231
|
+
|
|
232
|
+
const result1 = container.get('TestService1');
|
|
233
|
+
const result2 = container.get('TestService2');
|
|
234
|
+
|
|
235
|
+
expect(result1).toBe(service1);
|
|
236
|
+
expect(result2).toBe(service2);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('IOCIdentifier Constants', () => {
|
|
241
|
+
it('should be frozen object', () => {
|
|
242
|
+
expect(Object.isFrozen(IOCIdentifier)).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should have string values', () => {
|
|
246
|
+
Object.values(IOCIdentifier).forEach((value) => {
|
|
247
|
+
expect(typeof value).toBe('string');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should have unique values', () => {
|
|
252
|
+
const values = Object.values(IOCIdentifier);
|
|
253
|
+
const uniqueValues = new Set(values);
|
|
254
|
+
expect(uniqueValues.size).toBe(values.length);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { InversifyContainer } from '@/base/cases/InversifyContainer';
|
|
2
|
+
import { BootstrapApp } from '@/core/bootstraps/BootstrapApp';
|
|
3
|
+
import type { BootstrapAppArgs } from '@/core/bootstraps/BootstrapApp';
|
|
4
|
+
import { createIOCFunction } from '@qlover/corekit-bridge';
|
|
5
|
+
import type { IOCIdentifierMap } from '@/core/IOC';
|
|
6
|
+
import { name, version } from '../../../../package.json';
|
|
7
|
+
import { browserGlobalsName } from '@config/common';
|
|
8
|
+
|
|
9
|
+
// Mock IocRegisterImpl to properly handle registration
|
|
10
|
+
vi.mock('@/core/registers/IocRegisterImpl', () => ({
|
|
11
|
+
IocRegisterImpl: vi.fn().mockImplementation((_options) => ({
|
|
12
|
+
getRegisterList: vi.fn().mockReturnValue([]),
|
|
13
|
+
register: vi.fn().mockImplementation((_container, _manager) => {
|
|
14
|
+
// Mock the actual registration process to avoid I18nService constructor issues
|
|
15
|
+
// This prevents the real registration from happening which causes the error
|
|
16
|
+
})
|
|
17
|
+
}))
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Mock BootstrapsRegistry to avoid I18nService instantiation
|
|
21
|
+
vi.mock('@/core/bootstraps/BootstrapsRegistry', () => ({
|
|
22
|
+
BootstrapsRegistry: vi.fn().mockImplementation(() => ({
|
|
23
|
+
register: vi.fn().mockReturnValue([])
|
|
24
|
+
}))
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe('BootstrapApp', () => {
|
|
28
|
+
let mockArgs: BootstrapAppArgs;
|
|
29
|
+
let mockIOC: ReturnType<typeof createIOCFunction<IOCIdentifierMap>>;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
// Reset all mocks
|
|
33
|
+
vi.clearAllMocks();
|
|
34
|
+
|
|
35
|
+
// Setup mock IOC with proper typing
|
|
36
|
+
const container = new InversifyContainer();
|
|
37
|
+
mockIOC = createIOCFunction<IOCIdentifierMap>(container);
|
|
38
|
+
|
|
39
|
+
// Setup test arguments
|
|
40
|
+
mockArgs = {
|
|
41
|
+
root: {},
|
|
42
|
+
bootHref: 'http://localhost:3000',
|
|
43
|
+
IOC: mockIOC
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('main', () => {
|
|
48
|
+
it('should initialize bootstrap successfully', async () => {
|
|
49
|
+
const result = await BootstrapApp.main(mockArgs);
|
|
50
|
+
|
|
51
|
+
expect(result.bootHref).toBe('http://localhost:3000');
|
|
52
|
+
// default inject env,globals var, ioc
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
(mockArgs.root as Record<string, unknown>)[browserGlobalsName]
|
|
56
|
+
).toBeDefined();
|
|
57
|
+
|
|
58
|
+
// Verify that the globals object is injected into root
|
|
59
|
+
const injectedGlobals = (mockArgs.root as Record<string, unknown>)[
|
|
60
|
+
browserGlobalsName
|
|
61
|
+
] as Record<string, unknown>;
|
|
62
|
+
expect(injectedGlobals).toHaveProperty('logger');
|
|
63
|
+
expect(injectedGlobals).toHaveProperty('appConfig');
|
|
64
|
+
expect(
|
|
65
|
+
(injectedGlobals.appConfig as Record<string, unknown>).appName
|
|
66
|
+
).toBe(name);
|
|
67
|
+
expect(
|
|
68
|
+
(injectedGlobals.appConfig as Record<string, unknown>).appVersion
|
|
69
|
+
).toBe(version);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
describe('main.tsx Integration Tests', () => {
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
// Setup test environment
|
|
8
|
+
if (!document.getElementById('root')) {
|
|
9
|
+
const rootElement = document.createElement('div');
|
|
10
|
+
rootElement.id = 'root';
|
|
11
|
+
document.body.appendChild(rootElement);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
// Clean up DOM
|
|
17
|
+
const rootElement = document.getElementById('root');
|
|
18
|
+
if (rootElement) {
|
|
19
|
+
rootElement.remove();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Clean up root element content
|
|
25
|
+
const rootElement = document.getElementById('root');
|
|
26
|
+
if (rootElement) {
|
|
27
|
+
rootElement.innerHTML = '';
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should have main.tsx file', () => {
|
|
32
|
+
// Test if file exists
|
|
33
|
+
const mainPath = join(process.cwd(), 'src/main.tsx');
|
|
34
|
+
expect(existsSync(mainPath)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should have main.tsx with correct structure', () => {
|
|
38
|
+
// Test file content structure
|
|
39
|
+
const mainPath = join(process.cwd(), 'src/main.tsx');
|
|
40
|
+
const content = readFileSync(mainPath, 'utf-8');
|
|
41
|
+
|
|
42
|
+
// Check if it contains necessary imports
|
|
43
|
+
expect(content).toContain('import');
|
|
44
|
+
expect(content).toContain('BootstrapApp');
|
|
45
|
+
expect(content).toContain('createRoot');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should have App.tsx file', () => {
|
|
49
|
+
// Test if App.tsx file exists
|
|
50
|
+
const appPath = join(process.cwd(), 'src/App.tsx');
|
|
51
|
+
expect(existsSync(appPath)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should have BootstrapApp.ts file', () => {
|
|
55
|
+
// Test if BootstrapApp.ts file exists
|
|
56
|
+
const bootstrapPath = join(
|
|
57
|
+
process.cwd(),
|
|
58
|
+
'src/core/bootstraps/BootstrapApp.ts'
|
|
59
|
+
);
|
|
60
|
+
expect(existsSync(bootstrapPath)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BootstrapApp } from '@/core/bootstraps/BootstrapApp';
|
|
2
|
+
|
|
3
|
+
// Mock BootstrapApp
|
|
4
|
+
vi.mock('@/core/bootstraps/BootstrapApp', () => ({
|
|
5
|
+
BootstrapApp: {
|
|
6
|
+
main: vi.fn()
|
|
7
|
+
}
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
// Mock react-dom
|
|
11
|
+
vi.mock('react-dom/client', () => ({
|
|
12
|
+
createRoot: vi.fn(() => ({
|
|
13
|
+
render: vi.fn(),
|
|
14
|
+
unmount: vi.fn()
|
|
15
|
+
}))
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock App component
|
|
19
|
+
vi.mock('@/App', () => ({
|
|
20
|
+
default: vi.fn(() => 'MockedApp')
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
describe('main.tsx', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
vi.resetModules();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
vi.restoreAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('BootstrapApp initialization', () => {
|
|
34
|
+
it('should call BootstrapApp.main()', async () => {
|
|
35
|
+
await import('@/main');
|
|
36
|
+
expect(BootstrapApp.main).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('Module loading', () => {
|
|
41
|
+
it('should load main module without errors', async () => {
|
|
42
|
+
const mainModule = await import('@/main');
|
|
43
|
+
expect(mainModule).toBeDefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { vi, expect } from 'vitest';
|
|
3
|
+
import BaseHeader from '@/uikit/components/BaseHeader';
|
|
4
|
+
import { PublicAssetsPath } from '@/base/cases/PublicAssetsPath';
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
const mockPublicAssetsPath = new PublicAssetsPath('');
|
|
8
|
+
|
|
9
|
+
vi.mock('@/core/IOC', () => ({
|
|
10
|
+
IOC: vi.fn((key) => {
|
|
11
|
+
if (key === 'AppConfig') {
|
|
12
|
+
return { appName: 'Test App' };
|
|
13
|
+
}
|
|
14
|
+
if (key === PublicAssetsPath) {
|
|
15
|
+
return mockPublicAssetsPath;
|
|
16
|
+
}
|
|
17
|
+
return {};
|
|
18
|
+
})
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Mock child components
|
|
22
|
+
vi.mock('@/uikit/components/ThemeSwitcher', () => ({
|
|
23
|
+
default: () => <div data-testid="theme-switcher">Theme Switcher</div>
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
interface LocaleLinkProps {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
href: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
vi.mock('@/uikit/components/LocaleLink', () => ({
|
|
33
|
+
default: ({ children, href, className }: LocaleLinkProps) => (
|
|
34
|
+
<a href={href} className={className} data-testid="locale-link">
|
|
35
|
+
{children}
|
|
36
|
+
</a>
|
|
37
|
+
)
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
vi.mock('@/uikit/components/LanguageSwitcher', () => ({
|
|
41
|
+
default: () => <div data-testid="language-switcher">Language Switcher</div>
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
vi.mock('@/uikit/components/LogoutButton', () => ({
|
|
45
|
+
default: () => <div data-testid="logout-button">Logout</div>
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
describe('BaseHeader', () => {
|
|
49
|
+
it('renders header with correct structure', () => {
|
|
50
|
+
render(<BaseHeader />);
|
|
51
|
+
expect(screen.getByTestId('base-header')).toBeDefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders logo and app name correctly', () => {
|
|
55
|
+
render(<BaseHeader />);
|
|
56
|
+
const logo = screen.getByTestId('base-header-logo');
|
|
57
|
+
const appName = screen.getByTestId('base-header-app-name');
|
|
58
|
+
|
|
59
|
+
expect(logo).toBeDefined();
|
|
60
|
+
expect(logo.getAttribute('src')).toBe('/logo.svg');
|
|
61
|
+
expect(logo.getAttribute('alt')).toBe('logo');
|
|
62
|
+
expect(appName).toBeDefined();
|
|
63
|
+
expect(appName.textContent).toBe('Test App');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders theme and language switchers', () => {
|
|
67
|
+
render(<BaseHeader />);
|
|
68
|
+
expect(screen.getByTestId('theme-switcher')).toBeDefined();
|
|
69
|
+
expect(screen.getByTestId('language-switcher')).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders logout button when showLogoutButton is true', () => {
|
|
73
|
+
render(<BaseHeader showLogoutButton />);
|
|
74
|
+
expect(screen.getByTestId('logout-button')).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('does not render logout button when showLogoutButton is false', () => {
|
|
78
|
+
render(<BaseHeader showLogoutButton={false} />);
|
|
79
|
+
expect(screen.queryByTestId('logout-button')).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('renders home link correctly', () => {
|
|
83
|
+
render(<BaseHeader />);
|
|
84
|
+
const link = screen.getByTestId('locale-link');
|
|
85
|
+
expect(link).toBeDefined();
|
|
86
|
+
expect(link.getAttribute('href')).toBe('/');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -161,3 +161,158 @@ export const baseRoutes: RouteConfigValue[] = [
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
];
|
|
164
|
+
|
|
165
|
+
export const baseNoLocaleRoutes: RouteConfigValue[] = [
|
|
166
|
+
{
|
|
167
|
+
path: '/',
|
|
168
|
+
element: 'base/Layout',
|
|
169
|
+
meta: {
|
|
170
|
+
category: 'main'
|
|
171
|
+
},
|
|
172
|
+
children: [
|
|
173
|
+
{
|
|
174
|
+
index: true,
|
|
175
|
+
element: 'base/HomePage',
|
|
176
|
+
meta: {
|
|
177
|
+
title: identifier.PAGE_HOME_TITLE,
|
|
178
|
+
description: identifier.PAGE_HOME_DESCRIPTION,
|
|
179
|
+
icon: 'home',
|
|
180
|
+
localNamespace: 'common'
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
path: 'about',
|
|
185
|
+
element: 'base/AboutPage',
|
|
186
|
+
meta: {
|
|
187
|
+
title: identifier.PAGE_ABOUT_TITLE,
|
|
188
|
+
description: identifier.PAGE_ABOUT_DESCRIPTION,
|
|
189
|
+
icon: 'info',
|
|
190
|
+
localNamespace: 'common'
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
path: 'jsonStorage',
|
|
195
|
+
element: 'base/JSONStoragePage',
|
|
196
|
+
meta: {
|
|
197
|
+
title: identifier.PAGE_JSONSTORAGE_TITLE,
|
|
198
|
+
description: identifier.PAGE_JSONSTORAGE_DESCRIPTION,
|
|
199
|
+
icon: 'info',
|
|
200
|
+
localNamespace: 'common'
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
path: 'request',
|
|
205
|
+
element: 'base/RequestPage',
|
|
206
|
+
meta: {
|
|
207
|
+
title: identifier.PAGE_REQUEST_TITLE,
|
|
208
|
+
description: identifier.PAGE_REQUEST_DESCRIPTION,
|
|
209
|
+
icon: 'info',
|
|
210
|
+
localNamespace: 'common'
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
path: 'executor',
|
|
215
|
+
element: 'base/ExecutorPage',
|
|
216
|
+
meta: {
|
|
217
|
+
title: identifier.PAGE_EXECUTOR_TITLE,
|
|
218
|
+
description: identifier.PAGE_EXECUTOR_DESCRIPTION,
|
|
219
|
+
icon: 'info',
|
|
220
|
+
localNamespace: 'common'
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
path: 'errorIdentifier',
|
|
225
|
+
element: 'base/ErrorIdentifierPage',
|
|
226
|
+
meta: {
|
|
227
|
+
title: identifier.PAGE_ERROR_IDENTIFIER_TITLE,
|
|
228
|
+
description: identifier.PAGE_ERROR_IDENTIFIER_DESCRIPTION,
|
|
229
|
+
icon: 'info',
|
|
230
|
+
localNamespace: 'common'
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
path: '404',
|
|
235
|
+
element: '404',
|
|
236
|
+
meta: {
|
|
237
|
+
category: 'common',
|
|
238
|
+
title: identifier.PAGE_404_TITLE,
|
|
239
|
+
description: identifier.PAGE_404_DESCRIPTION,
|
|
240
|
+
localNamespace: 'common'
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
path: '500',
|
|
245
|
+
element: '500',
|
|
246
|
+
meta: {
|
|
247
|
+
category: 'common',
|
|
248
|
+
title: identifier.PAGE_500_TITLE,
|
|
249
|
+
description: identifier.PAGE_500_DESCRIPTION,
|
|
250
|
+
localNamespace: 'common'
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
path: '/',
|
|
257
|
+
element: 'auth/Layout',
|
|
258
|
+
meta: {
|
|
259
|
+
category: 'auth'
|
|
260
|
+
},
|
|
261
|
+
children: [
|
|
262
|
+
{
|
|
263
|
+
index: true,
|
|
264
|
+
element: 'auth/LoginPage'
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
path: 'login',
|
|
268
|
+
element: 'auth/LoginPage',
|
|
269
|
+
meta: {
|
|
270
|
+
title: identifier.PAGE_LOGIN_TITLE,
|
|
271
|
+
description: identifier.PAGE_LOGIN_DESCRIPTION,
|
|
272
|
+
icon: 'info',
|
|
273
|
+
localNamespace: 'common'
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
path: 'register',
|
|
278
|
+
element: 'auth/RegisterPage',
|
|
279
|
+
meta: {
|
|
280
|
+
title: identifier.PAGE_REGISTER_TITLE,
|
|
281
|
+
description: identifier.PAGE_REGISTER_DESCRIPTION,
|
|
282
|
+
icon: 'info',
|
|
283
|
+
localNamespace: 'common'
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
path: '404',
|
|
290
|
+
element: '404',
|
|
291
|
+
meta: {
|
|
292
|
+
category: 'common',
|
|
293
|
+
title: identifier.PAGE_404_TITLE,
|
|
294
|
+
description: identifier.PAGE_404_DESCRIPTION,
|
|
295
|
+
localNamespace: 'common'
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
path: '500',
|
|
300
|
+
element: '500',
|
|
301
|
+
meta: {
|
|
302
|
+
category: 'common',
|
|
303
|
+
title: identifier.PAGE_500_TITLE,
|
|
304
|
+
description: identifier.PAGE_500_DESCRIPTION,
|
|
305
|
+
localNamespace: 'common'
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
path: '*',
|
|
310
|
+
element: '404',
|
|
311
|
+
meta: {
|
|
312
|
+
category: 'common',
|
|
313
|
+
title: identifier.PAGE_404_TITLE,
|
|
314
|
+
description: identifier.PAGE_404_DESCRIPTION,
|
|
315
|
+
localNamespace: 'common'
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
];
|
|
@@ -16,7 +16,7 @@ export const overrideAntdThemeMode: ViteDeprecatedAntdOptions['mode'] =
|
|
|
16
16
|
'noGlobals';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* 启动器环境变量注入黑名单
|
|
20
20
|
*/
|
|
21
21
|
export const envBlackList = ['env', 'userNodeEnv'];
|
|
22
22
|
|
|
@@ -37,3 +37,11 @@ export const loggerStyles = {
|
|
|
37
37
|
* - 但是不能只有 /
|
|
38
38
|
*/
|
|
39
39
|
export const routerPrefix = '/router-root';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 是否使用本地化路由
|
|
43
|
+
*
|
|
44
|
+
* - true: 使用本地化路由,路由会带有语言前缀 (例如: /en/home)
|
|
45
|
+
* - false: 不使用本地化路由,直接使用路径 (例如: /home)
|
|
46
|
+
*/
|
|
47
|
+
export const useLocaleRoutes = true;
|