Compile-time tests for types to make sure types don't regress into being overly permissive as changes go in over time.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Object structure validation, property checking, and type manipulation utilities for testing object types and their properties.
Verify whether objects have specific properties and access their types.
/**
* Check if object has a given property and return its type for further testing
* @param key - Property key to check for
* @returns ExpectTypeOf for the property type if it exists, or true if using .not
*/
toHaveProperty<KeyType extends keyof Actual>(
key: KeyType
): KeyType extends keyof Actual ? PositiveExpectTypeOf<Actual[KeyType]> : true;Usage Examples:
import { expectTypeOf } from "expect-type";
const user = {
name: "John",
age: 30,
email: "john@example.com",
isActive: true
};
// Check property existence
expectTypeOf(user).toHaveProperty("name");
expectTypeOf(user).toHaveProperty("age");
expectTypeOf(user).not.toHaveProperty("address");
// Chain property type checking
expectTypeOf(user).toHaveProperty("name").toBeString();
expectTypeOf(user).toHaveProperty("age").toBeNumber();
expectTypeOf(user).toHaveProperty("isActive").toBeBoolean();
// Nested object properties
const nestedObj = {
data: {
items: [1, 2, 3],
metadata: { count: 3 }
}
};
expectTypeOf(nestedObj).toHaveProperty("data");
expectTypeOf(nestedObj.data).toHaveProperty("items");
expectTypeOf(nestedObj.data).toHaveProperty("metadata");
expectTypeOf(nestedObj.data.items).toBeArray();Select a subset of properties from an object type.
/**
* Select specific properties from an object type
* Equivalent to TypeScript's Pick utility type
* @param keyToPick - Property key to pick (optional for type inference)
* @returns ExpectTypeOf for the picked property subset
*/
pick<KeyToPick extends keyof Actual>(
keyToPick?: KeyToPick
): ExpectTypeOf<Pick<Actual, KeyToPick>, Options>;Usage Examples:
import { expectTypeOf } from "expect-type";
interface Person {
name: string;
age: number;
email: string;
address: {
street: string;
city: string;
};
}
// Pick single property
expectTypeOf<Person>()
.pick<"name">()
.toEqualTypeOf<{ name: string }>();
// Pick multiple properties
expectTypeOf<Person>()
.pick<"name" | "age">()
.toEqualTypeOf<{ name: string; age: number }>();
// Chain with other operations
expectTypeOf<Person>()
.pick<"address">()
.toHaveProperty("address")
.toEqualTypeOf<{ street: string; city: string }>();
// Pick with computed types
type UserKeys = "name" | "email";
expectTypeOf<Person>()
.pick<UserKeys>()
.toEqualTypeOf<{ name: string; email: string }>();Remove specific properties from an object type.
/**
* Remove specific properties from an object type
* Equivalent to TypeScript's Omit utility type
* @param keyToOmit - Property key to omit (optional for type inference)
* @returns ExpectTypeOf for the object type without the omitted properties
*/
omit<KeyToOmit extends keyof Actual | (PropertyKey & Record<never, never>)>(
keyToOmit?: KeyToOmit
): ExpectTypeOf<Omit<Actual, KeyToOmit>, Options>;Usage Examples:
import { expectTypeOf } from "expect-type";
interface Person {
name: string;
age: number;
email: string;
ssn: string; // sensitive data
}
// Omit single property
expectTypeOf<Person>()
.omit<"ssn">()
.toEqualTypeOf<{ name: string; age: number; email: string }>();
// Omit multiple properties
expectTypeOf<Person>()
.omit<"ssn" | "age">()
.toEqualTypeOf<{ name: string; email: string }>();
// Create public user type
type PublicUser = Omit<Person, "ssn">;
expectTypeOf<Person>()
.omit<"ssn">()
.toEqualTypeOf<PublicUser>();
// Omit with union types
expectTypeOf<Person>()
.omit<"ssn" | "email">()
.not.toHaveProperty("ssn");Advanced object structure validation with deep property checking.
Usage Examples:
import { expectTypeOf } from "expect-type";
// Deep object structure
const complexObject = {
user: {
profile: {
name: "Alice",
age: 25,
preferences: {
theme: "dark" as const,
notifications: true
}
},
permissions: ["read", "write"] as const
},
metadata: {
created: new Date(),
version: "1.0.0"
}
};
// Check deep property existence
expectTypeOf(complexObject)
.toHaveProperty("user")
.toHaveProperty("profile")
.toHaveProperty("preferences")
.toHaveProperty("theme")
.toEqualTypeOf<"dark">();
// Verify nested array properties
expectTypeOf(complexObject)
.toHaveProperty("user")
.toHaveProperty("permissions")
.items.toEqualTypeOf<"read" | "write">();
// Partial object matching
expectTypeOf(complexObject).toMatchObjectType<{
user: {
profile: {
name: string;
};
};
metadata: {
version: string;
};
}>();Handle optional properties and distinguish them from required ones.
import { expectTypeOf } from "expect-type";
interface ConfigOptions {
apiUrl: string; // required
timeout?: number; // optional
retries?: number; // optional
debug: boolean; // required
}
// Required properties must exist
expectTypeOf<ConfigOptions>().toHaveProperty("apiUrl");
expectTypeOf<ConfigOptions>().toHaveProperty("debug");
// Optional properties can be checked
expectTypeOf<ConfigOptions>().toHaveProperty("timeout");
expectTypeOf<ConfigOptions>().toHaveProperty("retries");
// Distinguish between optional and required
expectTypeOf<{ a?: number }>().not.toEqualTypeOf<{}>();
expectTypeOf<{ a?: number }>().not.toEqualTypeOf<{ a: number }>();
expectTypeOf<{ a?: number }>().not.toEqualTypeOf<{ a: number | undefined }>();
// Optional vs undefined union
expectTypeOf<{ a?: number | null }>()
.not.toEqualTypeOf<{ a: number | null }>();
// Nested optional properties
expectTypeOf<{ a: { b?: number } }>()
.not.toEqualTypeOf<{ a: {} }>();Distinguish between regular and readonly properties.
import { expectTypeOf } from "expect-type";
type MutableData = {
name: string;
count: number;
};
type ImmutableData = {
readonly name: string;
readonly count: number;
};
type MixedData = {
name: string;
readonly id: number;
};
// Readonly extends mutable (covariance)
expectTypeOf<ImmutableData>().toExtend<MutableData>();
// But they're not equal
expectTypeOf<ImmutableData>().not.toEqualTypeOf<MutableData>();
// Deep readonly differences
type DeepMutable = {
user: { name: string; age: number };
};
type DeepReadonly = {
user: { readonly name: string; readonly age: number };
};
expectTypeOf<DeepReadonly>().toExtend<DeepMutable>();
expectTypeOf<DeepReadonly>().not.toEqualTypeOf<DeepMutable>();
// Mixed readonly properties
expectTypeOf<MixedData>()
.pick<"name">()
.toEqualTypeOf<{ name: string }>();
expectTypeOf<MixedData>()
.pick<"id">()
.toEqualTypeOf<{ readonly id: number }>();Handle objects with index signatures and dynamic properties.
import { expectTypeOf } from "expect-type";
// String index signature
interface StringIndexed {
[key: string]: number;
}
// Number index signature
interface NumberIndexed {
[index: number]: string;
}
// Mixed known and index properties
interface MixedIndex {
name: string;
[key: string]: string | number;
}
// Check index signature behavior
expectTypeOf<StringIndexed>().toHaveProperty("anyKey");
expectTypeOf<NumberIndexed>().toHaveProperty(0);
expectTypeOf<NumberIndexed>().toHaveProperty(999);
// Mixed index validation
const mixedObj: MixedIndex = { name: "test", age: 30, active: 1 };
expectTypeOf(mixedObj).toHaveProperty("name").toBeString();
expectTypeOf(mixedObj).toHaveProperty("age"); // exists via index signatureValidate class instance types and their properties.
import { expectTypeOf } from "expect-type";
class User {
constructor(
public name: string,
private _id: number,
protected role: string = "user"
) {}
getName(): string {
return this.name;
}
private getId(): number {
return this._id;
}
}
// Public properties and methods
expectTypeOf<User>().toHaveProperty("name");
expectTypeOf<User>().toHaveProperty("getName");
// Private/protected members not accessible
expectTypeOf<User>().not.toHaveProperty("_id");
expectTypeOf<User>().not.toHaveProperty("getId");
expectTypeOf<User>().not.toHaveProperty("role");
// Method return types
expectTypeOf(new User("test", 1)).toHaveProperty("getName").returns.toBeString();
// Constructor instance type
expectTypeOf(User).instance.toEqualTypeOf<{
name: string;
getName(): string;
}>();