or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assert-interface.mdconfiguration.mdcore-assertions.mdexpect-interface.mdindex.mdplugin-system.mdshould-interface.mdutilities.md
tile.json

plugin-system.mddocs/

Plugin System

Extensible architecture for adding custom assertion methods, properties, and functionality to all assertion styles. Chai's plugin system allows developers to create reusable assertion extensions that integrate seamlessly with the existing API.

Capabilities

use Function

Registers a plugin function that extends Chai's functionality across all assertion interfaces.

/**
 * Registers a plugin that extends Chai functionality
 * @param plugin - Plugin function that receives chai and utils parameters
 * @returns ChaiStatic instance for chaining
 */
function use(plugin: (chai: ChaiStatic, utils: ChaiUtils) => void): ChaiStatic;

Plugin Function Interface

Plugin functions receive the chai instance and utilities for extending functionality.

interface PluginFunction {
  /**
   * @param chai - The main Chai export object
   * @param utils - Chai utility functions for plugin development
   */
  (chai: ChaiStatic, utils: ChaiUtils): void;
}

interface ChaiStatic {
  expect: ExpectStatic;
  assert: AssertStatic;
  should(): Should;
  Should: ShouldStatic;
  use: (plugin: PluginFunction) => ChaiStatic;
  util: ChaiUtils;
  config: Config;
  AssertionError: typeof AssertionError;
  Assertion: typeof Assertion;
}

Plugin Development

Adding Properties

Add chainable properties to the Assertion prototype.

import { use } from "chai";

// Plugin that adds custom properties
use(function(chai, utils) {
  const { Assertion } = chai;
  
  // Add a simple property
  Assertion.addProperty('positive', function() {
    const obj = utils.flag(this, 'object');
    
    this.assert(
      obj > 0,
      'expected #{this} to be positive',
      'expected #{this} to not be positive'
    );
  });
  
  // Add property with modifier behavior
  Assertion.addProperty('api', function() {
    utils.flag(this, 'api', true);
  });
});

// Usage
expect(5).to.be.positive;
expect(-1).to.not.be.positive;

Adding Methods

Add chainable methods that accept parameters.

use(function(chai, utils) {
  const { Assertion } = chai;
  
  // Add method with parameters
  Assertion.addMethod('divisibleBy', function(divisor) {
    const obj = utils.flag(this, 'object');
    
    this.assert(
      obj % divisor === 0,
      'expected #{this} to be divisible by #{exp}',
      'expected #{this} to not be divisible by #{exp}',
      divisor
    );
  });
  
  // Add method with complex logic
  Assertion.addMethod('between', function(min, max) {
    const obj = utils.flag(this, 'object');
    
    this.assert(
      obj >= min && obj <= max,
      'expected #{this} to be between #{exp} and #{act}',
      'expected #{this} to not be between #{exp} and #{act}',
      min,
      max
    );
  });
});

// Usage
expect(10).to.be.divisibleBy(5);
expect(7).to.be.between(5, 10);

Adding Chainable Methods

Add methods that can be chained and also act as properties.

use(function(chai, utils) {
  const { Assertion } = chai;
  
  // Chainable method with both method and property behavior
  Assertion.addChainableMethod(
    'length',
    function(expectedLength) {
      // Method behavior: expect(array).to.have.length(5)
      const obj = utils.flag(this, 'object');
      
      this.assert(
        obj.length === expectedLength,
        'expected #{this} to have length #{exp}',
        'expected #{this} to not have length #{exp}',
        expectedLength
      );
    },
    function() {
      // Property behavior: expect(array).to.have.length.above(3)
      const obj = utils.flag(this, 'object');
      utils.flag(this, 'object', obj.length);
    }
  );
});

// Usage
expect([1, 2, 3]).to.have.length(3);          // Method usage
expect([1, 2, 3]).to.have.length.above(2);    // Property usage

Extending Assert Interface

Add functions to the assert interface for TDD-style testing.

use(function(chai, utils) {
  const { assert } = chai;
  
  // Add assert-style function
  assert.isPositive = function(value, message) {
    assert(
      value > 0,
      message || `expected ${value} to be positive`
    );
  };
  
  assert.isDivisibleBy = function(value, divisor, message) {
    assert(
      value % divisor === 0,
      message || `expected ${value} to be divisible by ${divisor}`
    );
  };
});

// Usage
assert.isPositive(5);
assert.isDivisibleBy(10, 2);

Extending Should Interface

Add functionality to the should interface.

use(function(chai, utils) {
  const should = chai.should();
  
  // Add should-style static methods
  should.bePositive = function(value, message) {
    new chai.Assertion(value, message).to.be.positive;
  };
  
  // Methods are automatically available on objects after should() is called
});

// Usage (after calling should())
should.bePositive(5);
(5).should.be.positive;

Utility Functions

Chai provides utility functions for plugin development.

interface ChaiUtils {
  /**
   * Get or set flags on assertion instance
   * @param assertion - Assertion instance
   * @param key - Flag key
   * @param value - Flag value (optional, for setting)
   */
  flag(assertion: Assertion, key: string, value?: any): any;
  
  /**
   * Execute an assertion with proper error handling
   * @param assertion - Assertion instance
   * @param expression - Boolean expression to test
   */
  test(assertion: Assertion, expression: boolean): void;
  
  /**
   * Get the type of a value as a string
   * @param obj - Value to get type of
   */
  type(obj: any): string;
  
  /**
   * Generate error message for assertion
   * @param assertion - Assertion instance
   * @param args - Message template arguments
   */
  getMessage(assertion: Assertion, args: any[]): string;
  
  /**
   * Get the actual value being asserted against
   * @param assertion - Assertion instance
   * @param args - Additional arguments
   */
  getActual(assertion: Assertion, args: any[]): any;
  
  /**
   * Inspect/stringify a value for display
   * @param obj - Value to inspect
   * @param showHidden - Show hidden properties
   * @param depth - Inspection depth
   * @param colors - Use colors in output
   */
  inspect(obj: any, showHidden?: boolean, depth?: number, colors?: boolean): string;
  
  /**
   * Deep equality comparison
   * @param a - First value
   * @param b - Second value
   */
  eql(a: any, b: any): boolean;
  
  /**
   * Transfer flags from one assertion to another
   * @param assertion - Source assertion
   * @param target - Target assertion
   * @param includeAll - Include all flags or just specific ones
   */
  transferFlags(assertion: Assertion, target: Assertion, includeAll?: boolean): void;
}

Real-World Plugin Examples

JSON Schema Validation Plugin

use(function(chai, utils) {
  const { Assertion } = chai;
  
  Assertion.addMethod('jsonSchema', function(schema) {
    const obj = utils.flag(this, 'object');
    
    // Simple validation (real plugin would use ajv or similar)
    const isValid = validateJsonSchema(obj, schema);
    const errors = getValidationErrors(obj, schema);
    
    this.assert(
      isValid,
      'expected #{this} to match JSON schema',
      'expected #{this} to not match JSON schema'
    );
  });
  
  function validateJsonSchema(obj, schema) {
    // Implementation details...
    return true; // Simplified
  }
  
  function getValidationErrors(obj, schema) {
    // Implementation details...
    return [];
  }
});

// Usage
expect(userData).to.match.jsonSchema(userSchema);

HTTP Response Testing Plugin

use(function(chai, utils) {
  const { Assertion } = chai;
  
  // Status code assertion
  Assertion.addMethod('status', function(expectedStatus) {
    const response = utils.flag(this, 'object');
    
    this.assert(
      response.status === expectedStatus,
      'expected response to have status #{exp} but got #{act}',
      'expected response to not have status #{exp}',
      expectedStatus,
      response.status
    );
  });
  
  // Header assertion
  Assertion.addMethod('header', function(headerName, expectedValue) {
    const response = utils.flag(this, 'object');
    const headerValue = response.headers[headerName.toLowerCase()];
    
    if (arguments.length === 1) {
      // Check header existence
      this.assert(
        headerValue !== undefined,
        'expected response to have header #{exp}',
        'expected response to not have header #{exp}',
        headerName
      );
    } else {
      // Check header value
      this.assert(
        headerValue === expectedValue,
        'expected header #{exp} to have value #{act}',
        'expected header #{exp} to not have value #{act}',
        headerName,
        expectedValue
      );
    }
  });
});

// Usage
expect(response).to.have.status(200);
expect(response).to.have.header('content-type');
expect(response).to.have.header('content-type', 'application/json');

DOM Testing Plugin

use(function(chai, utils) {
  const { Assertion } = chai;
  
  // Element existence
  Assertion.addMethod('element', function(selector) {
    const context = utils.flag(this, 'object') || document;
    const element = context.querySelector(selector);
    
    this.assert(
      element !== null,
      'expected to find element #{exp}',
      'expected to not find element #{exp}',
      selector
    );
    
    utils.flag(this, 'object', element);
  });
  
  // Text content
  Assertion.addMethod('text', function(expectedText) {
    const element = utils.flag(this, 'object');
    const actualText = element.textContent.trim();
    
    this.assert(
      actualText === expectedText,
      'expected element to have text #{exp} but got #{act}',
      'expected element to not have text #{exp}',
      expectedText,
      actualText
    );
  });
  
  // CSS class
  Assertion.addMethod('class', function(className) {
    const element = utils.flag(this, 'object');
    
    this.assert(
      element.classList.contains(className),
      'expected element to have class #{exp}',
      'expected element to not have class #{exp}',
      className
    );
  });
});

// Usage
expect(document).to.have.element('#my-button');
expect('#my-button').to.have.text('Click me');
expect('#my-button').to.have.class('btn-primary');

Plugin Distribution

Publishing Plugins

// my-chai-plugin.js
export default function(chai, utils) {
  // Plugin implementation
}

// Usage
import chai, { expect } from 'chai';
import myPlugin from 'my-chai-plugin';

chai.use(myPlugin);

TypeScript Plugin Support

// types.d.ts
declare namespace Chai {
  interface Assertion {
    positive: Assertion;
    divisibleBy(divisor: number): Assertion;
    between(min: number, max: number): Assertion;
  }
}

// plugin.ts
export default function(chai: any, utils: any): void {
  // Implementation with TypeScript types
}

Plugin Best Practices

  1. Namespace your assertions to avoid conflicts
  2. Provide TypeScript definitions for better developer experience
  3. Use semantic assertion names that read naturally
  4. Handle edge cases and provide helpful error messages
  5. Test your plugins thoroughly across all assertion styles
  6. Document usage examples clearly
  7. Follow semver for plugin versioning