Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:22:35 +08:00
commit 6fcffca9b0
35 changed files with 8235 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
# Advanced Type Guard Patterns
This reference provides detailed examples of advanced type guard patterns.
## Pattern 1: Optional Property Guard
```typescript
interface Config {
apiKey: string;
timeout?: number;
}
function isConfig(value: unknown): value is Config {
if (typeof value !== "object" || value === null) {
return false;
}
const obj = value as Record<string, unknown>;
if (typeof obj.apiKey !== "string") {
return false;
}
if ("timeout" in obj && typeof obj.timeout !== "number") {
return false;
}
return true;
}
```
## Pattern 2: Array Element Guard
```typescript
function everyElementIs<T>(
arr: unknown[],
guard: (item: unknown) => item is T
): arr is T[] {
return arr.every(guard);
}
const data: unknown = ["a", "b", "c"];
if (Array.isArray(data) && everyElementIs(data, (item): item is string => typeof item === "string")) {
const lengths = data.map(str => str.length);
}
```
## Pattern 3: Tuple Guard
```typescript
function isTuple<T, U>(
value: unknown,
guard1: (item: unknown) => item is T,
guard2: (item: unknown) => item is U
): value is [T, U] {
return (
Array.isArray(value) &&
value.length === 2 &&
guard1(value[0]) &&
guard2(value[1])
);
}
const isStringNumberPair = (value: unknown): value is [string, number] =>
isTuple(
value,
(item): item is string => typeof item === "string",
(item): item is number => typeof item === "number"
);
```
## Pattern 4: Record Guard
```typescript
function isStringRecord(value: unknown): value is Record<string, string> {
if (typeof value !== "object" || value === null) {
return false;
}
return Object.values(value).every(val => typeof val === "string");
}
```
## Pattern 5: Enum Guard
```typescript
enum Status {
Active = "active",
Inactive = "inactive",
Pending = "pending"
}
function isStatus(value: unknown): value is Status {
return Object.values(Status).includes(value as Status);
}
```

View File

@@ -0,0 +1,110 @@
# Nested Type Guard Validation
Examples of validating complex nested object structures.
## Basic Nested Validation
```typescript
interface Address {
street: string;
city: string;
zipCode: string;
}
interface UserWithAddress {
id: string;
name: string;
address: Address;
}
function isAddress(value: unknown): value is Address {
return (
typeof value === "object" &&
value !== null &&
"street" in value &&
"city" in value &&
"zipCode" in value &&
typeof (value as Address).street === "string" &&
typeof (value as Address).city === "string" &&
typeof (value as Address).zipCode === "string"
);
}
function isUserWithAddress(value: unknown): value is UserWithAddress {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
"address" in value &&
typeof (value as UserWithAddress).id === "string" &&
typeof (value as UserWithAddress).name === "string" &&
isAddress((value as UserWithAddress).address)
);
}
```
## Composable Guards Pattern
Build complex guards by composing simpler ones:
```typescript
function hasStringProperty(obj: unknown, key: string): boolean {
return (
typeof obj === "object" &&
obj !== null &&
key in obj &&
typeof (obj as Record<string, unknown>)[key] === "string"
);
}
function isUserWithAddress(value: unknown): value is UserWithAddress {
return (
hasStringProperty(value, "id") &&
hasStringProperty(value, "name") &&
typeof value === "object" &&
value !== null &&
"address" in value &&
isAddress((value as UserWithAddress).address)
);
}
```
## Deep Nesting Example
```typescript
interface Company {
name: string;
address: Address;
}
interface Employee {
id: string;
name: string;
company: Company;
}
function isCompany(value: unknown): value is Company {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
"address" in value &&
typeof (value as Company).name === "string" &&
isAddress((value as Company).address)
);
}
function isEmployee(value: unknown): value is Employee {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
"company" in value &&
typeof (value as Employee).id === "string" &&
typeof (value as Employee).name === "string" &&
isCompany((value as Employee).company)
);
}
```

View File

@@ -0,0 +1,97 @@
# Testing Type Guards
Complete guide to unit testing type guards with comprehensive test cases.
## Testing Strategy
When testing type guards, ensure coverage for:
1. Valid inputs (happy path)
2. Missing required properties
3. Wrong property types
4. Null and undefined
5. Non-object primitives
6. Edge cases specific to your type
## Complete Test Suite Example
```typescript
describe("isUser", () => {
it("returns true for valid user", () => {
expect(isUser({ id: "1", name: "Alice", email: "alice@example.com" })).toBe(true);
});
it("returns false for missing property", () => {
expect(isUser({ id: "1", name: "Alice" })).toBe(false);
});
it("returns false for wrong property type", () => {
expect(isUser({ id: 1, name: "Alice", email: "alice@example.com" })).toBe(false);
});
it("returns false for null", () => {
expect(isUser(null)).toBe(false);
});
it("returns false for undefined", () => {
expect(isUser(undefined)).toBe(false);
});
it("returns false for non-object", () => {
expect(isUser("not an object")).toBe(false);
});
});
```
## Testing Array Guards
```typescript
describe("isStringArray", () => {
it("returns true for array of strings", () => {
expect(isStringArray(["a", "b", "c"])).toBe(true);
});
it("returns true for empty array", () => {
expect(isStringArray([])).toBe(true);
});
it("returns false for mixed types", () => {
expect(isStringArray(["a", 1, "c"])).toBe(false);
});
it("returns false for non-array", () => {
expect(isStringArray("not array")).toBe(false);
});
});
```
## Testing Assertion Functions
```typescript
describe("assertIsUser", () => {
it("does not throw for valid user", () => {
expect(() => assertIsUser({ id: "1", name: "Alice", email: "alice@example.com" })).not.toThrow();
});
it("throws for invalid user", () => {
expect(() => assertIsUser({ id: "1" })).toThrow("Invalid user data");
});
it("throws for null", () => {
expect(() => assertIsUser(null)).toThrow();
});
});
```
## Test Coverage Checklist
- [ ] Valid inputs
- [ ] Each required property missing
- [ ] Each property with wrong type
- [ ] Null input
- [ ] Undefined input
- [ ] Non-object primitives
- [ ] Empty objects/arrays
- [ ] Nested object validation
- [ ] Optional properties (present and absent)
- [ ] Edge cases for your domain