@qlover/create-app 0.10.5 → 0.11.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.
@@ -627,7 +627,20 @@ describe('AsyncService', () => {
627
627
 
628
628
  ### Type Safety Testing
629
629
 
630
+ TypeScript project tests should not only verify runtime behavior but also ensure the correctness of the type system. Vitest provides the `expectTypeOf` utility for compile-time type checking.
631
+
632
+ #### Why Type Testing?
633
+
634
+ 1. **Type Inference Validation**: Ensure TypeScript correctly infers complex types
635
+ 2. **Generic Constraint Checking**: Verify generic parameter constraints
636
+ 3. **Type Compatibility**: Ensure type definitions match actual usage
637
+ 4. **API Contract Guarantee**: Prevent breaking changes in type definitions
638
+
639
+ #### Basic Type Testing
640
+
630
641
  ```typescript
642
+ import { describe, it, expectTypeOf } from 'vitest';
643
+
631
644
  describe('TypeSafetyTests', () => {
632
645
  it('should maintain type safety', () => {
633
646
  const processor = new DataProcessor<User>();
@@ -636,9 +649,190 @@ describe('TypeSafetyTests', () => {
636
649
  expectTypeOf(processor.process).parameter(0).toEqualTypeOf<User>();
637
650
  expectTypeOf(processor.process).returns.toEqualTypeOf<ProcessedUser>();
638
651
  });
652
+
653
+ it('should infer correct return types', () => {
654
+ const result = getData();
655
+
656
+ // Verify return type
657
+ expectTypeOf(result).toEqualTypeOf<{ id: number; name: string }>();
658
+ expectTypeOf(result).not.toEqualTypeOf<{ id: string; name: string }>();
659
+ });
660
+
661
+ it('should validate parameter types', () => {
662
+ function processUser(user: User): void {
663
+ // implementation
664
+ }
665
+
666
+ // Verify parameter type
667
+ expectTypeOf(processUser).parameter(0).toMatchTypeOf<{ id: number }>();
668
+ expectTypeOf(processUser).parameter(0).toHaveProperty('id');
669
+ });
670
+ });
671
+ ```
672
+
673
+ #### Generic Type Testing
674
+
675
+ ```typescript
676
+ describe('Generic Type Tests', () => {
677
+ it('should work with generic constraints', () => {
678
+ class Storage<T extends { id: number }> {
679
+ store(item: T): T {
680
+ return item;
681
+ }
682
+ }
683
+
684
+ const storage = new Storage<User>();
685
+
686
+ // Verify generic type
687
+ expectTypeOf(storage.store).parameter(0).toMatchTypeOf<User>();
688
+ expectTypeOf(storage.store).returns.toMatchTypeOf<User>();
689
+ });
690
+
691
+ it('should validate complex generic types', () => {
692
+ type ApiResponse<T> = {
693
+ data: T;
694
+ status: number;
695
+ message?: string;
696
+ };
697
+
698
+ const response: ApiResponse<User[]> = {
699
+ data: [],
700
+ status: 200
701
+ };
702
+
703
+ // Verify nested generic type
704
+ expectTypeOf(response).toMatchTypeOf<ApiResponse<User[]>>();
705
+ expectTypeOf(response.data).toEqualTypeOf<User[]>();
706
+ });
707
+ });
708
+ ```
709
+
710
+ #### Union and Intersection Type Testing
711
+
712
+ ```typescript
713
+ describe('Union and Intersection Types', () => {
714
+ it('should handle union types correctly', () => {
715
+ type Result = Success | Error;
716
+ type Success = { status: 'success'; data: string };
717
+ type Error = { status: 'error'; message: string };
718
+
719
+ function handleResult(result: Result): void {
720
+ // implementation
721
+ }
722
+
723
+ // Verify union type
724
+ expectTypeOf(handleResult).parameter(0).toMatchTypeOf<Success>();
725
+ expectTypeOf(handleResult).parameter(0).toMatchTypeOf<Error>();
726
+ });
727
+
728
+ it('should handle intersection types correctly', () => {
729
+ type Timestamped = { createdAt: Date; updatedAt: Date };
730
+ type UserWithTimestamp = User & Timestamped;
731
+
732
+ const user: UserWithTimestamp = {
733
+ id: 1,
734
+ name: 'John',
735
+ createdAt: new Date(),
736
+ updatedAt: new Date()
737
+ };
738
+
739
+ // Verify intersection type contains all properties
740
+ expectTypeOf(user).toHaveProperty('id');
741
+ expectTypeOf(user).toHaveProperty('name');
742
+ expectTypeOf(user).toHaveProperty('createdAt');
743
+ expectTypeOf(user).toHaveProperty('updatedAt');
744
+ });
639
745
  });
640
746
  ```
641
747
 
748
+ #### Type Narrowing Testing
749
+
750
+ ```typescript
751
+ describe('Type Narrowing Tests', () => {
752
+ it('should validate type guards', () => {
753
+ function isString(value: unknown): value is string {
754
+ return typeof value === 'string';
755
+ }
756
+
757
+ const value: unknown = 'test';
758
+
759
+ if (isString(value)) {
760
+ // Within this scope, value should be narrowed to string type
761
+ expectTypeOf(value).toEqualTypeOf<string>();
762
+ }
763
+ });
764
+
765
+ it('should validate discriminated unions', () => {
766
+ type Shape =
767
+ | { kind: 'circle'; radius: number }
768
+ | { kind: 'rectangle'; width: number; height: number };
769
+
770
+ function getArea(shape: Shape): number {
771
+ if (shape.kind === 'circle') {
772
+ // In this branch, shape should be narrowed to circle type
773
+ expectTypeOf(shape).toHaveProperty('radius');
774
+ expectTypeOf(shape).not.toHaveProperty('width');
775
+ return Math.PI * shape.radius ** 2;
776
+ } else {
777
+ // In this branch, shape should be narrowed to rectangle type
778
+ expectTypeOf(shape).toHaveProperty('width');
779
+ expectTypeOf(shape).toHaveProperty('height');
780
+ return shape.width * shape.height;
781
+ }
782
+ }
783
+ });
784
+ });
785
+ ```
786
+
787
+ #### Practical Recommendations
788
+
789
+ 1. **Combine with Runtime Tests**: Type tests should complement runtime tests
790
+
791
+ ```typescript
792
+ describe('Combined Runtime and Type Tests', () => {
793
+ it('should validate both runtime behavior and types', () => {
794
+ function add(a: number, b: number): number {
795
+ return a + b;
796
+ }
797
+
798
+ // Type test
799
+ expectTypeOf(add).parameter(0).toEqualTypeOf<number>();
800
+ expectTypeOf(add).returns.toEqualTypeOf<number>();
801
+
802
+ // Runtime test
803
+ expect(add(1, 2)).toBe(3);
804
+ expect(add(-1, 1)).toBe(0);
805
+ });
806
+ });
807
+ ```
808
+
809
+ 2. **Test Type Inference**: Ensure TypeScript correctly infers types, avoid overusing `any`
810
+
811
+ ```typescript
812
+ describe('Type Inference Tests', () => {
813
+ it('should infer types correctly', () => {
814
+ const data = { id: 1, name: 'John' };
815
+
816
+ // Verify inferred type
817
+ expectTypeOf(data).toEqualTypeOf<{ id: number; name: string }>();
818
+ expectTypeOf(data.id).toEqualTypeOf<number>();
819
+ expectTypeOf(data.name).toEqualTypeOf<string>();
820
+ });
821
+ });
822
+ ```
823
+
824
+ 3. **Use TypeScript Compiler Checks**: Run `tsc --noEmit` in CI to ensure no type errors
825
+
826
+ ```bash
827
+ # Add script in package.json
828
+ {
829
+ "scripts": {
830
+ "type-check": "tsc --noEmit",
831
+ "test": "pnpm type-check && vitest run"
832
+ }
833
+ }
834
+ ```
835
+
642
836
  ---
643
837
 
644
838
  ## Performance Testing
@@ -627,7 +627,20 @@ describe('AsyncService', () => {
627
627
 
628
628
  ### 类型安全测试
629
629
 
630
+ TypeScript 项目的测试不仅要验证运行时行为,还应该确保类型系统的正确性。Vitest 提供了 `expectTypeOf` 工具来进行编译时类型检查。
631
+
632
+ #### 为什么需要类型测试?
633
+
634
+ 1. **类型推断验证**:确保 TypeScript 能正确推断复杂类型
635
+ 2. **泛型约束检查**:验证泛型参数的约束条件
636
+ 3. **类型兼容性**:确保类型定义与实际使用匹配
637
+ 4. **API 契约保证**:防止类型定义的破坏性变更
638
+
639
+ #### 基础类型测试
640
+
630
641
  ```typescript
642
+ import { describe, it, expectTypeOf } from 'vitest';
643
+
631
644
  describe('TypeSafetyTests', () => {
632
645
  it('should maintain type safety', () => {
633
646
  const processor = new DataProcessor<User>();
@@ -636,9 +649,190 @@ describe('TypeSafetyTests', () => {
636
649
  expectTypeOf(processor.process).parameter(0).toEqualTypeOf<User>();
637
650
  expectTypeOf(processor.process).returns.toEqualTypeOf<ProcessedUser>();
638
651
  });
652
+
653
+ it('should infer correct return types', () => {
654
+ const result = getData();
655
+
656
+ // 验证返回值类型
657
+ expectTypeOf(result).toEqualTypeOf<{ id: number; name: string }>();
658
+ expectTypeOf(result).not.toEqualTypeOf<{ id: string; name: string }>();
659
+ });
660
+
661
+ it('should validate parameter types', () => {
662
+ function processUser(user: User): void {
663
+ // implementation
664
+ }
665
+
666
+ // 验证参数类型
667
+ expectTypeOf(processUser).parameter(0).toMatchTypeOf<{ id: number }>();
668
+ expectTypeOf(processUser).parameter(0).toHaveProperty('id');
669
+ });
670
+ });
671
+ ```
672
+
673
+ #### 泛型类型测试
674
+
675
+ ```typescript
676
+ describe('Generic Type Tests', () => {
677
+ it('should work with generic constraints', () => {
678
+ class Storage<T extends { id: number }> {
679
+ store(item: T): T {
680
+ return item;
681
+ }
682
+ }
683
+
684
+ const storage = new Storage<User>();
685
+
686
+ // 验证泛型类型
687
+ expectTypeOf(storage.store).parameter(0).toMatchTypeOf<User>();
688
+ expectTypeOf(storage.store).returns.toMatchTypeOf<User>();
689
+ });
690
+
691
+ it('should validate complex generic types', () => {
692
+ type ApiResponse<T> = {
693
+ data: T;
694
+ status: number;
695
+ message?: string;
696
+ };
697
+
698
+ const response: ApiResponse<User[]> = {
699
+ data: [],
700
+ status: 200
701
+ };
702
+
703
+ // 验证嵌套泛型类型
704
+ expectTypeOf(response).toMatchTypeOf<ApiResponse<User[]>>();
705
+ expectTypeOf(response.data).toEqualTypeOf<User[]>();
706
+ });
707
+ });
708
+ ```
709
+
710
+ #### 联合类型与交叉类型测试
711
+
712
+ ```typescript
713
+ describe('Union and Intersection Types', () => {
714
+ it('should handle union types correctly', () => {
715
+ type Result = Success | Error;
716
+ type Success = { status: 'success'; data: string };
717
+ type Error = { status: 'error'; message: string };
718
+
719
+ function handleResult(result: Result): void {
720
+ // implementation
721
+ }
722
+
723
+ // 验证联合类型
724
+ expectTypeOf(handleResult).parameter(0).toMatchTypeOf<Success>();
725
+ expectTypeOf(handleResult).parameter(0).toMatchTypeOf<Error>();
726
+ });
727
+
728
+ it('should handle intersection types correctly', () => {
729
+ type Timestamped = { createdAt: Date; updatedAt: Date };
730
+ type UserWithTimestamp = User & Timestamped;
731
+
732
+ const user: UserWithTimestamp = {
733
+ id: 1,
734
+ name: 'John',
735
+ createdAt: new Date(),
736
+ updatedAt: new Date()
737
+ };
738
+
739
+ // 验证交叉类型包含所有属性
740
+ expectTypeOf(user).toHaveProperty('id');
741
+ expectTypeOf(user).toHaveProperty('name');
742
+ expectTypeOf(user).toHaveProperty('createdAt');
743
+ expectTypeOf(user).toHaveProperty('updatedAt');
744
+ });
639
745
  });
640
746
  ```
641
747
 
748
+ #### 类型窄化测试
749
+
750
+ ```typescript
751
+ describe('Type Narrowing Tests', () => {
752
+ it('should validate type guards', () => {
753
+ function isString(value: unknown): value is string {
754
+ return typeof value === 'string';
755
+ }
756
+
757
+ const value: unknown = 'test';
758
+
759
+ if (isString(value)) {
760
+ // 在此作用域内,value 应该被窄化为 string 类型
761
+ expectTypeOf(value).toEqualTypeOf<string>();
762
+ }
763
+ });
764
+
765
+ it('should validate discriminated unions', () => {
766
+ type Shape =
767
+ | { kind: 'circle'; radius: number }
768
+ | { kind: 'rectangle'; width: number; height: number };
769
+
770
+ function getArea(shape: Shape): number {
771
+ if (shape.kind === 'circle') {
772
+ // 在此分支,shape 应该被窄化为 circle 类型
773
+ expectTypeOf(shape).toHaveProperty('radius');
774
+ expectTypeOf(shape).not.toHaveProperty('width');
775
+ return Math.PI * shape.radius ** 2;
776
+ } else {
777
+ // 在此分支,shape 应该被窄化为 rectangle 类型
778
+ expectTypeOf(shape).toHaveProperty('width');
779
+ expectTypeOf(shape).toHaveProperty('height');
780
+ return shape.width * shape.height;
781
+ }
782
+ }
783
+ });
784
+ });
785
+ ```
786
+
787
+ #### 实用建议
788
+
789
+ 1. **结合运行时测试**:类型测试应该与运行时测试相辅相成
790
+
791
+ ```typescript
792
+ describe('Combined Runtime and Type Tests', () => {
793
+ it('should validate both runtime behavior and types', () => {
794
+ function add(a: number, b: number): number {
795
+ return a + b;
796
+ }
797
+
798
+ // 类型测试
799
+ expectTypeOf(add).parameter(0).toEqualTypeOf<number>();
800
+ expectTypeOf(add).returns.toEqualTypeOf<number>();
801
+
802
+ // 运行时测试
803
+ expect(add(1, 2)).toBe(3);
804
+ expect(add(-1, 1)).toBe(0);
805
+ });
806
+ });
807
+ ```
808
+
809
+ 2. **测试类型推断**:确保 TypeScript 能正确推断类型,避免过度使用 `any`
810
+
811
+ ```typescript
812
+ describe('Type Inference Tests', () => {
813
+ it('should infer types correctly', () => {
814
+ const data = { id: 1, name: 'John' };
815
+
816
+ // 验证推断的类型
817
+ expectTypeOf(data).toEqualTypeOf<{ id: number; name: string }>();
818
+ expectTypeOf(data.id).toEqualTypeOf<number>();
819
+ expectTypeOf(data.name).toEqualTypeOf<string>();
820
+ });
821
+ });
822
+ ```
823
+
824
+ 3. **使用 TypeScript 编译器检查**:在 CI 中运行 `tsc --noEmit` 确保没有类型错误
825
+
826
+ ```bash
827
+ # 在 package.json 中添加脚本
828
+ {
829
+ "scripts": {
830
+ "type-check": "tsc --noEmit",
831
+ "test": "pnpm type-check && vitest run"
832
+ }
833
+ }
834
+ ```
835
+
642
836
  ---
643
837
 
644
838
  ## 性能测试
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlover/create-app",
3
- "version": "0.10.5",
3
+ "version": "0.11.0",
4
4
  "description": "Create a new app with a single command",
5
5
  "private": false,
6
6
  "type": "module",