@qlover/create-app 0.6.3 → 0.7.1

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 (55) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/configs/node-lib/eslint.config.js +3 -3
  3. package/dist/configs/react-app/eslint.config.js +3 -3
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/templates/pack-app/eslint.config.js +3 -3
  7. package/dist/templates/pack-app/package.json +1 -1
  8. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
  9. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
  10. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
  11. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
  12. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
  13. package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
  14. package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
  15. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
  16. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
  17. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
  18. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
  19. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
  20. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
  21. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
  22. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
  23. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
  24. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
  25. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
  26. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
  27. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
  28. package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
  29. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
  30. package/dist/templates/react-app/config/app.router.ts +155 -0
  31. package/dist/templates/react-app/config/common.ts +9 -1
  32. package/dist/templates/react-app/docs/en/test-guide.md +782 -0
  33. package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
  34. package/dist/templates/react-app/package.json +9 -20
  35. package/dist/templates/react-app/public/locales/en/common.json +1 -1
  36. package/dist/templates/react-app/public/locales/zh/common.json +1 -1
  37. package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
  38. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
  39. package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
  40. package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
  41. package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
  42. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
  43. package/dist/templates/react-app/src/core/globals.ts +1 -3
  44. package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
  45. package/dist/templates/react-app/src/main.tsx +6 -1
  46. package/dist/templates/react-app/src/pages/404.tsx +0 -1
  47. package/dist/templates/react-app/src/pages/500.tsx +1 -1
  48. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
  49. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
  50. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
  51. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
  52. package/dist/templates/react-app/tsconfig.json +2 -1
  53. package/dist/templates/react-app/tsconfig.test.json +13 -0
  54. package/dist/templates/react-app/vite.config.ts +3 -2
  55. package/package.json +3 -3
@@ -0,0 +1,782 @@
1
+ # Testing Guide
2
+
3
+ > This document briefly introduces the testing strategies and best practices for the **fe-base** project in a monorepo scenario, using [Vitest](https://vitest.dev/) as the unified testing framework.
4
+
5
+ ---
6
+
7
+ ## Why Choose Vitest
8
+
9
+ 1. **Perfect Integration with Vite Ecosystem**: Shares Vite configuration, TypeScript & ESM work out of the box.
10
+ 2. **Modern Features**: Parallel execution, HMR, built-in coverage statistics.
11
+ 3. **Jest Compatible API**: `describe / it / expect` APIs with no learning curve.
12
+ 4. **Monorepo Friendly**: Can filter execution by workspace, easy to run package-level tests in parallel in CI.
13
+
14
+ ---
15
+
16
+ ## Test Types
17
+
18
+ - **Unit Tests**: Verify the minimal behavior of functions, classes, or components.
19
+ - **Integration Tests**: Verify collaboration and boundaries between multiple modules.
20
+ - **End-to-End (E2E, introduce Playwright/Cypress as needed)**: Verify complete user workflows.
21
+
22
+ > ⚡️ In most cases, prioritize writing unit tests; only add integration tests when cross-module interactions are complex.
23
+
24
+ ---
25
+
26
+ ## Test File Organization Standards
27
+
28
+ ### File Naming and Location
29
+
30
+ ```
31
+ packages/
32
+ ├── package-name/
33
+ │ ├── __tests__/ # Test files directory
34
+ │ │ ├── Class.test.ts # Class tests
35
+ │ │ ├── utils/ # Utility function tests
36
+ │ │ │ └── helper.test.ts
37
+ │ │ └── integration/ # Integration tests
38
+ │ ├── __mocks__/ # Mock files directory
39
+ │ │ └── index.ts
40
+ │ └── src/ # Source code directory
41
+ ```
42
+
43
+ ### Test File Structure
44
+
45
+ ```typescript
46
+ // Standard test file header comment
47
+ /**
48
+ * ClassName test-suite
49
+ *
50
+ * Coverage:
51
+ * 1. constructor – Constructor tests
52
+ * 2. methodName – Method functionality tests
53
+ * 3. edge cases – Edge case tests
54
+ * 4. error handling – Error handling tests
55
+ */
56
+
57
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
58
+ import { ClassName } from '../src/ClassName';
59
+
60
+ describe('ClassName', () => {
61
+ // Test data and mock objects
62
+ let instance: ClassName;
63
+ let mockDependency: MockType;
64
+
65
+ // Setup and cleanup
66
+ beforeEach(() => {
67
+ // Initialize test environment
68
+ mockDependency = createMockDependency();
69
+ instance = new ClassName(mockDependency);
70
+ });
71
+
72
+ afterEach(() => {
73
+ // Clean up test environment
74
+ vi.clearAllMocks();
75
+ });
76
+
77
+ // Constructor tests
78
+ describe('constructor', () => {
79
+ it('should create instance with valid parameters', () => {
80
+ expect(instance).toBeInstanceOf(ClassName);
81
+ });
82
+
83
+ it('should throw error with invalid parameters', () => {
84
+ expect(() => new ClassName(null)).toThrow();
85
+ });
86
+ });
87
+
88
+ // Method test grouping
89
+ describe('methodName', () => {
90
+ it('should handle normal case', () => {
91
+ // Test normal scenarios
92
+ });
93
+
94
+ it('should handle edge cases', () => {
95
+ // Test edge cases
96
+ });
97
+
98
+ it('should handle error cases', () => {
99
+ // Test error scenarios
100
+ });
101
+ });
102
+
103
+ // Integration tests
104
+ describe('integration tests', () => {
105
+ it('should work with dependent modules', () => {
106
+ // Test module collaboration
107
+ });
108
+ });
109
+ });
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Vitest Global Configuration Example
115
+
116
+ ```typescript
117
+ // vitest.config.ts
118
+ import { defineConfig } from 'vitest/config';
119
+ import { resolve } from 'path';
120
+
121
+ export default defineConfig({
122
+ test: {
123
+ globals: true,
124
+ environment: 'jsdom',
125
+ setupFiles: ['./test/setup.ts'],
126
+ alias: {
127
+ // Automatically mock certain packages in test environment, pointing to __mocks__ directory
128
+ '@qlover/fe-corekit': resolve(__dirname, 'packages/fe-corekit/__mocks__'),
129
+ '@qlover/logger': resolve(__dirname, 'packages/logger/__mocks__')
130
+ }
131
+ }
132
+ });
133
+ ```
134
+
135
+ ### Package-level Scripts
136
+
137
+ ```jsonc
138
+ // packages/xxx/package.json (example)
139
+ {
140
+ "scripts": {
141
+ "test": "vitest run",
142
+ "test:watch": "vitest",
143
+ "test:coverage": "vitest run --coverage"
144
+ }
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Test Strategy
151
+
152
+ ### Test Grouping
153
+
154
+ The entire file is a test file, with test content organized into groups. For example, describe represents a group of tests. Typically, a test file has only one root describe.
155
+
156
+ The content is tested from "small to large." For example, if the source file contains a class, the tests are grouped by the constructor parameters, the constructor itself, and each member method.
157
+
158
+ - From covering various parameter types for each method to the overall flow affected by method calls.
159
+ - As well as comprehensive flow testing and boundary testing.
160
+
161
+ Source file (TestClass.ts):
162
+
163
+ ```ts
164
+ type TestClassOptions = {
165
+ name: string;
166
+ };
167
+
168
+ class TestClass {
169
+ constructor(options: TestClassOptions) {}
170
+
171
+ getName(): string {
172
+ return this.options.name;
173
+ }
174
+
175
+ setName(name: string): void {
176
+ this.options.name = name;
177
+ }
178
+ }
179
+ ```
180
+
181
+ Test file (TestClass.test.ts):
182
+
183
+ ```ts
184
+ describe('TestClass', () => {
185
+ describe('TestClass.constructor', () => {
186
+ // ...
187
+ });
188
+ describe('TestClass.getName', () => {
189
+ // ...
190
+ });
191
+
192
+ describe('Overall flow or boundary testing', () => {
193
+ it('should keep getName consistent after modifying the name', () => {
194
+ const testClass = new TestClass({ name: 'test' });
195
+ testClass.setName('test2');
196
+ expect(testClass.getName()).toBe('test2');
197
+ });
198
+ });
199
+ });
200
+ ```
201
+
202
+ ### Test Case Naming Conventions
203
+
204
+ ```typescript
205
+ describe('ClassName', () => {
206
+ describe('methodName', () => {
207
+ // Positive test cases
208
+ it('should return expected result when given valid input', () => {});
209
+ it('should handle multiple parameters correctly', () => {});
210
+
211
+ // Boundary test cases
212
+ it('should handle empty input', () => {});
213
+ it('should handle null/undefined input', () => {});
214
+ it('should handle maximum/minimum values', () => {});
215
+
216
+ // Error test cases
217
+ it('should throw error when given invalid input', () => {});
218
+ it('should handle network failure gracefully', () => {});
219
+
220
+ // Behavioral test cases
221
+ it('should call dependency method with correct parameters', () => {});
222
+ it('should not call dependency when condition is false', () => {});
223
+ });
224
+ });
225
+ ```
226
+
227
+ ### Function Testing
228
+
229
+ Function tests should cover the following aspects:
230
+
231
+ 1. **Parameter Combination Testing**
232
+
233
+ ```typescript
234
+ interface TestParams {
235
+ key1?: string;
236
+ key2?: number;
237
+ key3?: boolean;
238
+ }
239
+
240
+ function testFunction({ key1, key2, key3 }: TestParams): string {
241
+ // Implementation...
242
+ }
243
+
244
+ describe('testFunction', () => {
245
+ describe('Parameter Combination Testing', () => {
246
+ it('should handle case with all parameters present', () => {
247
+ expect(testFunction({ key1: 'test', key2: 1, key3: true })).toBe(
248
+ 'expected result'
249
+ );
250
+ });
251
+
252
+ it('should handle case with key1, key2 only', () => {
253
+ expect(testFunction({ key1: 'test', key2: 1 })).toBe('expected result');
254
+ });
255
+
256
+ it('should handle case with key1, key3 only', () => {
257
+ expect(testFunction({ key1: 'test', key3: true })).toBe(
258
+ 'expected result'
259
+ );
260
+ });
261
+
262
+ it('should handle case with key2, key3 only', () => {
263
+ expect(testFunction({ key2: 1, key3: true })).toBe('expected result');
264
+ });
265
+
266
+ it('should handle case with key1 only', () => {
267
+ expect(testFunction({ key1: 'test' })).toBe('expected result');
268
+ });
269
+
270
+ it('should handle case with key2 only', () => {
271
+ expect(testFunction({ key2: 1 })).toBe('expected result');
272
+ });
273
+
274
+ it('should handle case with key3 only', () => {
275
+ expect(testFunction({ key3: true })).toBe('expected result');
276
+ });
277
+
278
+ it('should handle empty object case', () => {
279
+ expect(testFunction({})).toBe('expected result');
280
+ });
281
+ });
282
+
283
+ describe('Boundary Value Testing', () => {
284
+ it('should handle extreme values', () => {
285
+ expect(
286
+ testFunction({
287
+ key1: '', // Empty string
288
+ key2: Number.MAX_SAFE_INTEGER, // Maximum safe integer
289
+ key3: false
290
+ })
291
+ ).toBe('expected result');
292
+
293
+ expect(
294
+ testFunction({
295
+ key1: 'a'.repeat(1000), // Very long string
296
+ key2: Number.MIN_SAFE_INTEGER, // Minimum safe integer
297
+ key3: true
298
+ })
299
+ ).toBe('expected result');
300
+ });
301
+
302
+ it('should handle special values', () => {
303
+ expect(
304
+ testFunction({
305
+ key1: ' ', // All spaces string
306
+ key2: 0, // Zero value
307
+ key3: false
308
+ })
309
+ ).toBe('expected result');
310
+
311
+ expect(
312
+ testFunction({
313
+ key1: null as any, // null value
314
+ key2: NaN, // NaN value
315
+ key3: undefined as any // undefined value
316
+ })
317
+ ).toBe('expected result');
318
+ });
319
+
320
+ it('should handle invalid values', () => {
321
+ expect(() =>
322
+ testFunction({
323
+ key1: Symbol() as any, // Invalid type
324
+ key2: {} as any, // Invalid type
325
+ key3: 42 as any // Invalid type
326
+ })
327
+ ).toThrow();
328
+ });
329
+ });
330
+ });
331
+ ```
332
+
333
+ This test suite demonstrates:
334
+
335
+ 1. **Complete Parameter Combination Coverage**:
336
+ - Tests all possible parameter combinations (2^n combinations, where n is the number of parameters)
337
+ - Includes cases with all parameters present, partial parameters, and empty object
338
+
339
+ 2. **Boundary Value Testing**:
340
+ - Tests parameter limit values (maximum, minimum)
341
+ - Tests special values (empty string, null, undefined, NaN)
342
+ - Tests invalid values (type errors)
343
+
344
+ 3. **Test Case Organization**:
345
+ - Uses nested describe blocks to clearly organize test scenarios
346
+ - Each test case has a clear description
347
+ - Related test cases are grouped together
348
+
349
+ ### Test Data Management
350
+
351
+ ```typescript
352
+ describe('DataProcessor', () => {
353
+ // Test data constants
354
+ const VALID_DATA = {
355
+ id: 1,
356
+ name: 'test',
357
+ items: ['a', 'b', 'c']
358
+ };
359
+
360
+ const INVALID_DATA = {
361
+ id: null,
362
+ name: '',
363
+ items: []
364
+ };
365
+
366
+ // Test data factory functions
367
+ const createTestUser = (overrides = {}) => ({
368
+ id: 1,
369
+ name: 'Test User',
370
+ email: 'test@example.com',
371
+ ...overrides
372
+ });
373
+
374
+ // Complex data structures
375
+ const createComplexTestData = () => ({
376
+ metadata: {
377
+ version: '1.0.0',
378
+ created: Date.now(),
379
+ tags: ['test', 'data']
380
+ },
381
+ users: [
382
+ createTestUser({ id: 1 }),
383
+ createTestUser({ id: 2, name: 'Another User' })
384
+ ]
385
+ });
386
+ });
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Mock Strategy
392
+
393
+ ### 1. Global Mock Directory
394
+
395
+ Each package can expose a subdirectory with the same name, providing persistent mocks for automatic use by other packages during testing.
396
+
397
+ ```typescript
398
+ // packages/fe-corekit/__mocks__/index.ts
399
+ import { vi } from 'vitest';
400
+
401
+ export const MyUtility = {
402
+ doSomething: vi.fn(() => 'mocked'),
403
+ processData: vi.fn((input: string) => `processed-${input}`)
404
+ };
405
+ export default MyUtility;
406
+ ```
407
+
408
+ ### 2. File-level Mock
409
+
410
+ ```typescript
411
+ // At the top of test file
412
+ vi.mock('../src/Util', () => ({
413
+ Util: {
414
+ ensureDir: vi.fn(),
415
+ readFile: vi.fn()
416
+ }
417
+ }));
418
+
419
+ vi.mock('js-cookie', () => {
420
+ let store: Record<string, string> = {};
421
+
422
+ const get = vi.fn((key?: string) => {
423
+ if (typeof key === 'string') return store[key];
424
+ return { ...store };
425
+ });
426
+
427
+ const set = vi.fn((key: string, value: string) => {
428
+ store[key] = value;
429
+ });
430
+
431
+ const remove = vi.fn((key: string) => {
432
+ delete store[key];
433
+ });
434
+
435
+ const __resetStore = () => {
436
+ store = {};
437
+ };
438
+
439
+ return {
440
+ default: { get, set, remove, __resetStore }
441
+ };
442
+ });
443
+ ```
444
+
445
+ ### 3. Dynamic Mock
446
+
447
+ ```typescript
448
+ describe('ServiceClass', () => {
449
+ it('should handle API failure', async () => {
450
+ // Temporarily mock API call failure
451
+ vi.spyOn(apiClient, 'request').mockRejectedValue(
452
+ new Error('Network error')
453
+ );
454
+
455
+ await expect(service.fetchData()).rejects.toThrow('Network error');
456
+
457
+ // Restore original implementation
458
+ vi.restoreAllMocks();
459
+ });
460
+ });
461
+ ```
462
+
463
+ ### 4. Mock Class Instances
464
+
465
+ ```typescript
466
+ class MockStorage<Key = string> implements SyncStorageInterface<Key> {
467
+ public data = new Map<string, string>();
468
+ public calls: {
469
+ setItem: Array<{ key: Key; value: unknown; options?: unknown }>;
470
+ getItem: Array<{ key: Key; defaultValue?: unknown; options?: unknown }>;
471
+ removeItem: Array<{ key: Key; options?: unknown }>;
472
+ clear: number;
473
+ } = {
474
+ setItem: [],
475
+ getItem: [],
476
+ removeItem: [],
477
+ clear: 0
478
+ };
479
+
480
+ setItem<T>(key: Key, value: T, options?: unknown): void {
481
+ this.calls.setItem.push({ key, value, options });
482
+ this.data.set(String(key), String(value));
483
+ }
484
+
485
+ getItem<T>(key: Key, defaultValue?: T, options?: unknown): T | null {
486
+ this.calls.getItem.push({ key, defaultValue, options });
487
+ const value = this.data.get(String(key));
488
+ return (value ?? defaultValue ?? null) as T | null;
489
+ }
490
+
491
+ reset(): void {
492
+ this.data.clear();
493
+ this.calls = {
494
+ setItem: [],
495
+ getItem: [],
496
+ removeItem: [],
497
+ clear: 0
498
+ };
499
+ }
500
+ }
501
+ ```
502
+
503
+ ---
504
+
505
+ ## Test Environment Management
506
+
507
+ ### Lifecycle Hooks
508
+
509
+ ```typescript
510
+ describe('ComponentTest', () => {
511
+ let component: Component;
512
+ let mockDependency: MockDependency;
513
+
514
+ beforeAll(() => {
515
+ // One-time setup before entire test suite
516
+ setupGlobalTestEnvironment();
517
+ });
518
+
519
+ afterAll(() => {
520
+ // One-time cleanup after entire test suite
521
+ cleanupGlobalTestEnvironment();
522
+ });
523
+
524
+ beforeEach(() => {
525
+ // Setup before each test case
526
+ vi.useFakeTimers();
527
+ mockDependency = new MockDependency();
528
+ component = new Component(mockDependency);
529
+ });
530
+
531
+ afterEach(() => {
532
+ // Cleanup after each test case
533
+ vi.useRealTimers();
534
+ vi.clearAllMocks();
535
+ mockDependency.reset();
536
+ });
537
+ });
538
+ ```
539
+
540
+ ### File System Testing
541
+
542
+ ```typescript
543
+ describe('FileProcessor', () => {
544
+ const testDir = './test-files';
545
+ const testFilePath = path.join(testDir, 'test.json');
546
+
547
+ beforeAll(() => {
548
+ // Create test directories and files
549
+ if (!fs.existsSync(testDir)) {
550
+ fs.mkdirSync(testDir, { recursive: true });
551
+ }
552
+ fs.writeFileSync(testFilePath, JSON.stringify({ test: 'data' }));
553
+ });
554
+
555
+ afterAll(() => {
556
+ // Clean up test files
557
+ if (fs.existsSync(testDir)) {
558
+ fs.rmSync(testDir, { recursive: true, force: true });
559
+ }
560
+ });
561
+
562
+ it('should process file correctly', () => {
563
+ const processor = new FileProcessor();
564
+ const result = processor.processFile(testFilePath);
565
+ expect(result).toEqual({ test: 'data' });
566
+ });
567
+ });
568
+ ```
569
+
570
+ ---
571
+
572
+ ## Boundary Testing and Error Handling
573
+
574
+ ### Boundary Value Testing
575
+
576
+ ```typescript
577
+ describe('ValidationUtils', () => {
578
+ describe('validateAge', () => {
579
+ it('should handle boundary values', () => {
580
+ // Boundary value testing
581
+ expect(validateAge(0)).toBe(true); // Minimum value
582
+ expect(validateAge(150)).toBe(true); // Maximum value
583
+ expect(validateAge(-1)).toBe(false); // Below minimum
584
+ expect(validateAge(151)).toBe(false); // Above maximum
585
+ });
586
+
587
+ it('should handle edge cases', () => {
588
+ // Edge case testing
589
+ expect(validateAge(null)).toBe(false);
590
+ expect(validateAge(undefined)).toBe(false);
591
+ expect(validateAge(NaN)).toBe(false);
592
+ expect(validateAge(Infinity)).toBe(false);
593
+ });
594
+ });
595
+ });
596
+ ```
597
+
598
+ ### Async Operation Testing
599
+
600
+ ```typescript
601
+ describe('AsyncService', () => {
602
+ it('should handle successful async operation', async () => {
603
+ const service = new AsyncService();
604
+ const result = await service.fetchData();
605
+ expect(result).toBeDefined();
606
+ });
607
+
608
+ it('should handle async operation failure', async () => {
609
+ const service = new AsyncService();
610
+ vi.spyOn(service, 'apiCall').mockRejectedValue(new Error('API Error'));
611
+
612
+ await expect(service.fetchData()).rejects.toThrow('API Error');
613
+ });
614
+
615
+ it('should handle timeout', async () => {
616
+ vi.useFakeTimers();
617
+ const service = new AsyncService();
618
+
619
+ const promise = service.fetchDataWithTimeout(1000);
620
+ vi.advanceTimersByTime(1001);
621
+
622
+ await expect(promise).rejects.toThrow('Timeout');
623
+ vi.useRealTimers();
624
+ });
625
+ });
626
+ ```
627
+
628
+ ### Type Safety Testing
629
+
630
+ ```typescript
631
+ describe('TypeSafetyTests', () => {
632
+ it('should maintain type safety', () => {
633
+ const processor = new DataProcessor<User>();
634
+
635
+ // Use expectTypeOf for type checking
636
+ expectTypeOf(processor.process).parameter(0).toEqualTypeOf<User>();
637
+ expectTypeOf(processor.process).returns.toEqualTypeOf<ProcessedUser>();
638
+ });
639
+ });
640
+ ```
641
+
642
+ ---
643
+
644
+ ## Performance Testing
645
+
646
+ ```typescript
647
+ describe('PerformanceTests', () => {
648
+ it('should complete operation within time limit', async () => {
649
+ const startTime = Date.now();
650
+ const processor = new DataProcessor();
651
+
652
+ await processor.processLargeDataset(largeDataset);
653
+
654
+ const endTime = Date.now();
655
+ const duration = endTime - startTime;
656
+
657
+ expect(duration).toBeLessThan(1000); // Should complete within 1 second
658
+ });
659
+ });
660
+ ```
661
+
662
+ ---
663
+
664
+ ## Running Tests
665
+
666
+ ```bash
667
+ # Run all package tests
668
+ pnpm test
669
+
670
+ # Run tests for specific package only
671
+ pnpm --filter @qlover/fe-corekit test
672
+
673
+ # Watch mode
674
+ pnpm test:watch
675
+
676
+ # Generate coverage report
677
+ pnpm test:coverage
678
+ ```
679
+
680
+ In CI, you can leverage GitHub Actions:
681
+
682
+ ```yaml
683
+ # .github/workflows/test.yml (truncated)
684
+ - run: pnpm install
685
+ - run: pnpm test:coverage
686
+ - uses: codecov/codecov-action@v3
687
+ ```
688
+
689
+ ---
690
+
691
+ ## Coverage Targets
692
+
693
+ | Metric | Target |
694
+ | ---------- | ------ |
695
+ | Statements | ≥ 80% |
696
+ | Branches | ≥ 75% |
697
+ | Functions | ≥ 85% |
698
+ | Lines | ≥ 80% |
699
+
700
+ Coverage reports are output to the `coverage/` directory by default, with `index.html` available for local browsing.
701
+
702
+ ---
703
+
704
+ ## Debugging
705
+
706
+ ### VS Code Launch Configuration
707
+
708
+ ```jsonc
709
+ {
710
+ "version": "0.2.0",
711
+ "configurations": [
712
+ {
713
+ "name": "Debug Vitest",
714
+ "type": "node",
715
+ "request": "launch",
716
+ "program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
717
+ "args": ["run", "--reporter=verbose"],
718
+ "console": "integratedTerminal",
719
+ "internalConsoleOptions": "neverOpen"
720
+ }
721
+ ]
722
+ }
723
+ ```
724
+
725
+ > You can use `console.log` / `debugger` in test code to assist with troubleshooting.
726
+
727
+ ---
728
+
729
+ ## Frequently Asked Questions (FAQ)
730
+
731
+ ### Q1: How to Mock Browser APIs?
732
+
733
+ Use `vi.mock()` or globally override in `setupFiles`, for example:
734
+
735
+ ```typescript
736
+ globalThis.requestAnimationFrame = (cb) => setTimeout(cb, 16);
737
+ ```
738
+
739
+ ### Q2: What to do when tests are slow?
740
+
741
+ - Use `vi.useFakeTimers()` to accelerate time-related logic.
742
+ - Break down long integration processes into independent unit tests.
743
+
744
+ ### Q3: How to test TypeScript types?
745
+
746
+ Use `expectTypeOf`:
747
+
748
+ ```typescript
749
+ import { expectTypeOf } from 'vitest';
750
+
751
+ expectTypeOf(MyUtility.doSomething).returns.toEqualTypeOf<string>();
752
+ ```
753
+
754
+ ### Q4: How to test private methods?
755
+
756
+ ```typescript
757
+ // Access private methods through type assertion
758
+ it('should test private method', () => {
759
+ const instance = new MyClass();
760
+ const result = (instance as any).privateMethod();
761
+ expect(result).toBe('expected');
762
+ });
763
+ ```
764
+
765
+ ### Q5: How to handle dependency injection testing?
766
+
767
+ ```typescript
768
+ describe('ServiceWithDependencies', () => {
769
+ let mockRepository: MockRepository;
770
+ let service: UserService;
771
+
772
+ beforeEach(() => {
773
+ mockRepository = new MockRepository();
774
+ service = new UserService(mockRepository);
775
+ });
776
+
777
+ it('should use injected dependency', () => {
778
+ service.getUser(1);
779
+ expect(mockRepository.findById).toHaveBeenCalledWith(1);
780
+ });
781
+ });
782
+ ```