Initial commit
This commit is contained in:
97
skills/using-type-guards/references/advanced-patterns.md
Normal file
97
skills/using-type-guards/references/advanced-patterns.md
Normal 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);
|
||||
}
|
||||
```
|
||||
110
skills/using-type-guards/references/nested-validation.md
Normal file
110
skills/using-type-guards/references/nested-validation.md
Normal 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)
|
||||
);
|
||||
}
|
||||
```
|
||||
97
skills/using-type-guards/references/testing-guide.md
Normal file
97
skills/using-type-guards/references/testing-guide.md
Normal 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
|
||||
Reference in New Issue
Block a user