External LibrariesWriting ComponentsComponent Basics

Component Basics

All external library components follow a common structure and pattern.

Component Structure

Every component has these properties:

interface Component {
  id: string;          // Unique identifier (kebab-case)
  name: string;        // Display name in UI
  description?: string; // What this component does
  enabled: boolean;    // Available in test engine
  deprecated: boolean; // Hidden from UI but still works
  tested: boolean;     // Self-declared quality flag
  function: Function;  // The implementation
}

Component Flags

FlagDefaultPurpose
enabledfalseWhen true, component is available in test engine and UI
deprecatedfalseWhen true, hidden from UI but existing tests still work
testedfalseSelf-declared flag indicating component has been tested

Flag Combinations

enableddeprecatedResult
falsefalseNot available anywhere
truefalseAvailable in UI and engine
truetrueWorks in engine, hidden from UI
falsetrueNot available anywhere

Export Patterns

Each component type uses a specific export name:

// src/index.ts
export { $$AssertionFunctions } from "./assertions";
export { $$ValueFunctions } from "./generators";
export { $$ApiHooks } from "./hooks";
export { $$Utilities } from "./utils";
export { $$TestCases } from "./test-cases";

ID Management

Component IDs must be unique within your library.

// ✅ Good: Descriptive, kebab-case
id: "is-valid-email"
id: "generate-test-user"
id: "auth-before-request"
 
// ❌ Bad: Generic, could conflict
id: "validate"
id: "generate"
id: "hook1"

Tip: Prefix IDs with your domain:

id: "payments-is-valid-card"
id: "auth-generate-jwt"

Naming Conventions

PropertyConventionExample
idkebab-caseis-valid-user
nameTitle CaseIs Valid User
Function namecamelCaseisValidUser

Type Definitions

The template includes type definitions for components:

// src/types.ts
export interface AssertionFunction {
  id: string;
  name: string;
  description?: string;
  enabled: boolean;
  deprecated: boolean;
  tested: boolean;
  function: (value: unknown, expected?: unknown, options?: unknown) => void | Promise<void>;
}
 
export interface ValueFunction {
  id: string;
  name: string;
  description?: string;
  enabled: boolean;
  deprecated: boolean;
  tested: boolean;
  function: (options?: unknown) => unknown | Promise<unknown>;
}
 
export interface ApiHook {
  id: string;
  name: string;
  description?: string;
  enabled: boolean;
  deprecated: boolean;
  tested: boolean;
  type: "beforeRequest" | "afterResponse";
  function: () => void | Promise<void>;
}

Error Handling

Components should handle errors gracefully:

// ✅ Good: Catches errors, reports them
function: async (value) => {
  try {
    // Your logic
    $addAssertionResult({ passed: true, ... });
  } catch (error) {
    $addAssertionResult({
      passed: false,
      message: `Error: ${error.message}`,
      ...
    });
  }
}
 
// ❌ Bad: Throws, crashes the test
function: async (value) => {
  const result = riskyOperation(value); // Might throw
  $addAssertionResult({ passed: result, ... });
}

Accessing Built-in Libraries

All components can use built-in libraries:

function: async (options) => {
  // Lodash
  const filtered = _.filter(data, predicate);
 
  // Faker
  const email = faker.internet.email();
 
  // Zod
  const schema = z.object({ id: z.number() });
 
  // Luxon
  const now = luxon.DateTime.now();
 
  // HTTP requests
  const data = await ky.get(url).json();
}

Next Steps