diff --git a/README.md b/README.md index cd960c3..9f0e597 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,8 @@ runner.exec("My Test Name", () => { Assert.equals(1 + 1, 2) }, 2) // The '2' is the indent level for this test (prints if verbosity >= 2) ``` +**Note:** +When using `TestRunner.exec`, always pass the test code as a function reference (e.g., `() => ...` or `function() { ... }`). This ensures the test is executed at the correct time within the `exec` method, preserving the intended order of output and test execution. Passing a direct function call (e.g., `runner.exec("Test", myTestFunction())`) will execute the test immediately—before `exec` can manage output or error handling—leading to unexpected results such as out-of-order titles or missed error reporting. #### Structured Output diff --git a/docs/typedoc/classes/Assert.html b/docs/typedoc/classes/Assert.html index df63d5f..4020205 100644 --- a/docs/typedoc/classes/Assert.html +++ b/docs/typedoc/classes/Assert.html @@ -2,7 +2,7 @@ This class provides a set of static methods to perform common assertions such as checking equality, type, and exceptions, etc. In case of assertion failure, an AssertionError is thrown and the user can provide a descriptive message.

-
Index

Constructors

Index

Constructors

Methods

contains doesNotThrow equals @@ -34,7 +34,7 @@
Assert.contains([1, 2, 3], 2, "Array should contain 2")
Assert.contains("hello world", "world", "String should contain 'world'")
Assert.contains([1, 2, 3], 4) // Fails
Assert.contains("hello world", "test") // Fails
Assert.contains([1,2,3], 2)
Assert.contains("hello world", "world")
-
-
-
+
diff --git a/docs/typedoc/classes/AssertionError.html b/docs/typedoc/classes/AssertionError.html index af97a4e..3e11e25 100644 --- a/docs/typedoc/classes/AssertionError.html +++ b/docs/typedoc/classes/AssertionError.html @@ -12,15 +12,18 @@
  • Sets the error name to "AssertionError" for easier identification.
  • Accepts a message parameter describing the assertion failure.
  • -

    This class is intentionally simple—no extra methods or properties are added, to keep assertion failures clear and unambiguous.

    -

    Hierarchy

    Index

    Constructors

    This class is intentionally simple—no extra methods or properties are added, to keep assertion failures clear and unambiguous.

    +

    Hierarchy

    Index

    Constructors

    Properties

    message: string
    name: string
    stack?: string
    stackTraceLimit: number

    The Error.stackTraceLimit property specifies the number of stack frames +

    Constructors

    • Constructor for AssertionError.

      +

      Parameters

      • message: string

        A descriptive message that explains the assertion failure. +This message will be included in the error stack trace.

        +

      Returns AssertionError

    Properties

    message: string
    name: string
    stack?: string
    stackTraceLimit: number

    The Error.stackTraceLimit property specifies the number of stack frames collected by a stack trace (whether generated by new Error().stack or Error.captureStackTrace(obj)).

    The default value is 10 but may be set to any valid JavaScript number. Changes diff --git a/docs/typedoc/classes/TestRunner.html b/docs/typedoc/classes/TestRunner.html index 6819964..40b903b 100644 --- a/docs/typedoc/classes/TestRunner.html +++ b/docs/typedoc/classes/TestRunner.html @@ -1,49 +1,58 @@ TestRunner | officescripts-unit-testing-framework

    officescripts-unit-testing-framework
      Preparing search index...

      Class TestRunner

      A utility class for managing and running test cases with controlled console output. -TestRunner' supports configurable verbosity levels and allows structured logging +TestRunner supports configurable verbosity levels and allows structured logging with indentation for better test output organization. It is primarily designed for test cases using assertion methods (e.g., Assert.equals, Assert.throws). -Verbosity can be set via the TestRunner.VERBOSITY: constant object (enum pattern)

      +Verbosity can be set via the TestRunner.VERBOSITY in the constructor: constant object (enum pattern)

      • OFF (0): No output
      • HEADER (1): High-level section headers only
      • -
      • SECTION (2): Full nested titles
      • -
      • SUBSECTION (3): Detailed test case titles -Verbosity level is incremental, i.e. allows all logging events with indentation level that is -lower or equal than TestRunner.VERBOSITY.
      • +
      • SECTION (2): First level nested title
      • +
      • SUBSECTION (3): Second level nested title
      -
      const runner = new TestRunner()
      runner.exec("Simple Length Test", () => {
      Assert.equals("test".length, 4)
      })
      function sum(a: number, b: number): number {return a + b}
      const a = 1, b = 2
      runner.exec("Sum Test", () => {
      Assert.equals(sum(a, b), 3) // test passed
      })

      // Example output (if verbosity is set to show headers):
      // ** START: Sum Test **
      // ** END: Sum Test ** +

      Verbosity level is incremental, i.e. allows all logging events with indentation level that is +lower or equal than TestRunner.VERBOSITY.

      +
      const runner = new TestRunner(TestRunner.VERBOSITY.SECTION)
      title("Start Testing") // default indentation is 1 (HEADER)
      runner.exec("Simple Length Test", () => { // default indentation is 2 (SECTION)
      Assert.equals("test".length, 4) // test passed
      })
      function sum(a: number, b: number): number {return a + b}
      const a = 1, b = 2
      runner.exec("Sum Test", () => {
      Assert.equals(sum(a, b), 3) // test passed
      })
      title("End Testing")

      // Output will look like:
      // * Start Testing *
      // ** START: Simple Length Test **
      // ** END: Simple Length Test **
      // ** START: Sum Test **
      // ** END: Sum Test **
      // * End Testing *
      -

      Test functions are expected to use Assert methods internally. If an assertion fails, -the error will be caught and reported with context.

      -
      Index

      Constructors

        +
      • Test functions are expected to use Assert methods internally. If an assertion fails, +the error will be caught and reported with context.
      • +
      • For a better organization, consider to put all test cases withing a single TestCase class +where each test case scenario is defined with a static method.
      • +
      +
      Index

      Constructors

      • Constructs a 'TestRunner' with the specified verbosity level.

        -

        Parameters

        • verbosity: 0 | 1 | 2 | 3 = TestRunner.DEFAULT_VERBOSITY

          One of the values from 'TestRunner.VERBOSITY' (default: 'HEADER')

          -

        Returns TestRunner

      Properties

      VERBOSITY: { HEADER: 1; OFF: 0; SECTION: 2; SUBSECTION: 3 } = ...

      Verbosity level

      -

      Methods

      • See detailed JSDoc in class documentation

        +

      Constructors

      • Constructs a TestRunner with the specified verbosity level.

        +

        Parameters

        • verbosity: 0 | 1 | 2 | 3 = TestRunner.DEFAULT_VERBOSITY

          One of the values from TestRunner.VERBOSITY (default: HEADER(1))

          +

        Returns TestRunner

      Properties

      VERBOSITY: { HEADER: 1; OFF: 0; SECTION: 2; SUBSECTION: 3 } = ...

      Allowed verbosity levels, to control the output of TestRunner.title method.

      +

      Methods

      • Executes a test case with a title and handles assertions. +This method logs the start and end of the test case with titles (using TestRunner.title method) +if the verbosity level allows it. +It executes the provided function which should contain assertions. +If an assertion fails, it will throw an AssertionError. +See detailed JSDoc in class documentation

        Parameters

        • name: string

          The name of the test case.

        • fn: () => void

          The function containing the test logic. It should contain assertions using Assert methods.

          -
        • indent: number = 2

          Indentation level for the title (default: 2). The indentation level is indicated +

        • indent: number = TestRunner.VERBOSITY.SECTION

          Indentation level for the title (default: 2(SECTION)). The indentation level is indicated with the number of suffix *.

        Returns void

        • This method does not return a value.
        -

        AssertionError - If an assertion fails within the test function.

        -

        This method is used to execute a test case, logging the start and end of the test with titles. -It is designed to work with the Assert class for assertions.

        +

        AssertionError - If an assertion fails within the test function fn. +- If the provided fn is not a function.

        +

        Always pass a function reference using () => .... +If you pass a direct function call, the code will execute before +and not at indicated place it should be executed inside this method and it may produce unexpected results.

        const runner = new TestRunner()
        runner.exec("My Test", () => {
        Assert.equals(1 + 1, 2)
        })
        -
      • Returns the current verbosity level. -This is useful for checking the current logging level -and adjusting the output accordingly.

        +
      • Returns the current verbosity level.

        Returns 0 | 1 | 2 | 3

          -
        • The current verbosity level as defined in 'TestRunner.VERBOSITY'.
        • +
        • The current verbosity level as defined in TestRunner.VERBOSITY.
        const runner = new TestRunner(TestRunner.VERBOSITY.HEADER)
        console.log(runner.getVerbosity()) // Outputs: 1
        @@ -52,7 +61,7 @@
      • TestRunner.VERBOSITY for available verbosity levels.
      • TestRunner.getVerbosityLabel for the string label of the verbosity level.
      -
      • Returns the corresonding string label for the verbosity level. This is useful for logging or debugging purposes.

        Returns string

        -
      • Conditionally prints a title message based on the configured verbosity. +

      • Conditionally prints a title message based on the configured verbosity. The title is prefixed and suffixed with * characters for visual structure. The number of * will depend on the indentation level, for 2 it shows ** as prefix and suffix.

        Parameters

        • msg: string

          The message to display

          -
        • indent: number = 1

          Indentation level (default: 1). The indentation level is indicated -with the number of suffix *.

          +
        • indent: number = 1

          Indentation level (default: 1(HEADER)). The indentation level is indicated +with the number of * characters used as prefix and suffix to the title.

        Returns void

        • This method does not return a value.
        @@ -85,4 +94,4 @@
      • TestRunner.getVerbosity for the current verbosity level.
      • TestRunner.getVerbosityLabel for the string label of the verbosity level.
      -
      +
      diff --git a/src/unit-test-framework.ts b/src/unit-test-framework.ts index 1e467a4..c816517 100644 --- a/src/unit-test-framework.ts +++ b/src/unit-test-framework.ts @@ -29,15 +29,17 @@ * throw new AssertionError(`Expected ${expected}, but got ${actual}`) * } * ``` - * * Features: * - Inherits from the built-in Error class. * - Sets the error name to "AssertionError" for easier identification. * - Accepts a message parameter describing the assertion failure. - * - * This class is intentionally simple—no extra methods or properties are added, to keep assertion failures clear and unambiguous. + * @remarks This class is intentionally simple—no extra methods or properties are added, to keep assertion failures clear and unambiguous. */ class AssertionError extends Error { + /** Constructor for `AssertionError`. + * @param message - A descriptive message that explains the assertion failure. + * This message will be included in the error stack trace. + */ constructor(message: string) { super(message) this.name = "AssertionError" @@ -63,7 +65,7 @@ class Assert { * @param expectedErrorType - (Optional) Expected constructor of the thrown error (e.g., `TypeError`). * @param expectedMessage - (Optional) Exact expected error message. * @param message - (Optional) Additional prefix for the error message if the assertion fails. - * @returns {asserts fn is () => never} - Asserts that 'fn' will throw an error if the assertion passes. + * @returns {asserts fn is () => never} - Asserts that fn will throw an error if the assertion passes. * @throws AssertionError - If no error is thrown, or if the thrown error does not match the expected type or message. * @example * ```ts @@ -137,12 +139,12 @@ class Assert { * If the values differ, a detailed error is thrown. * For arrays, mismatches include index, value, and type. * For arrays of objects, a shallow comparison using `JSON.stringify` is performed. - * If a value cannot be stringified (e.g., due to circular references), it is treated as "[unprintable value]" in error messages and object equality checks. + * If a value cannot be stringified (e.g., due to circular references), it is treated as `[unprintable value]` in error messages and object equality checks. * @param actual - The actual value. * @param expected - The expected value. * @param message - (Optional) Prefix message included in the thrown error on failure. - * @returns {asserts actual is T} - Asserts that 'actual' is of type `T` if the assertion passes. - * @throws AssertionError - If 'actual' and 'expected' are not equal. + * @returns {asserts actual is T} - Asserts that actual is of type `T` if the assertion passes. + * @throws AssertionError - If actual and expected are not equal. * @example * ```ts * Assert.equals(2 + 2, 4, "Simple math") @@ -267,10 +269,10 @@ class Assert { * Asserts that the given value is not `null`. * Provides a robust stringification of the value for error messages, * guarding against unsafe or error-throwing `toString()` implementations. - * Narrows the type of 'value' to NonNullable if assertion passes. + * Narrows the type of value to `NonNullable` if assertion passes. * @param value - The value to test. * @param message - Optional message to prefix in case of failure. - * @returns {asserts value is NonNullable} - Narrows the type of 'value' to NonNullable if the assertion passes. + * @returns {asserts value is NonNullable} - Narrows the type of value to `NonNullable` if the assertion passes. * @throws AssertionError if the value is `null`. * @example * ```ts @@ -296,7 +298,7 @@ class Assert { * This method is used to explicitly indicate that a test case has failed, * regardless of any conditions or assertions. * @param message - (Optional) The failure message to display. - * If not provided, a default "Assertion failed" message is used. + * If not provided, a default `Assertion failed` message is used. * @returns {never} - This method never returns, it always throws an error. * @throws AssertionError - Always throws an `AssertionError` with the provided message. * @example @@ -384,7 +386,7 @@ public static isNotType( * Throws `AssertionError` if the value is not truthy. * @param value - The value to test for truthiness. * @param message - (Optional) Message to prefix in case of failure. - * @returns {asserts value} - Narrows the type of 'value' to its original type if the assertion passes. + * @returns {asserts value} - Narrows the type of value to its original type if the assertion passes. * @throws AssertionError - If the value is not truthy. * @example * ```ts @@ -434,7 +436,7 @@ public static isNotType( * Throws `AssertionError` if the value is not exactly `undefined`. * @param value - The value to check. * @param message - (Optional) Message to prefix in case of failure. - * @returns {asserts value is undefined} - Narrows the type of 'value' to `undefined` if the assertion passes. + * @returns {asserts value is undefined} - Narrows the type of value to `undefined` if the assertion passes. * @throws AssertionError - If the value is not `undefined`. * @example * ```ts @@ -456,7 +458,7 @@ public static isNotType( // #region isNotUndefined /** * Asserts that the given value is not `undefined`. - * Narrows the type to exclude undefined. + * Narrows the type to exclude `undefined`. * Throws `AssertionError` if the value is `undefined`. * @param value - The value to check. * @param message - (Optional) Message to prefix in case of failure. @@ -710,43 +712,53 @@ public static isNotType( // #region TestRunner /** * A utility class for managing and running test cases with controlled console output. - * TestRunner' supports configurable verbosity levels and allows structured logging + * `TestRunner` supports configurable verbosity levels and allows structured logging * with indentation for better test output organization. It is primarily designed for * test cases using assertion methods (e.g., `Assert.equals`, `Assert.throws`). - * Verbosity can be set via the `TestRunner.VERBOSITY`: constant object (enum pattern) + * Verbosity can be set via the `TestRunner.VERBOSITY` in the constructor: constant object (enum pattern) * - `OFF` (0): No output * - `HEADER` (1): High-level section headers only - * - `SECTION` (2): Full nested titles - * - `SUBSECTION` (3): Detailed test case titles + * - `SECTION` (2): First level nested title + * - `SUBSECTION` (3): Second level nested title + * * Verbosity level is incremental, i.e. allows all logging events with indentation level that is * lower or equal than `TestRunner.VERBOSITY`. * * @example * ```ts - * const runner = new TestRunner() - * runner.exec("Simple Length Test", () => { - * Assert.equals("test".length, 4) + * const runner = new TestRunner(TestRunner.VERBOSITY.SECTION) + * title("Start Testing") // default indentation is 1 (HEADER) + * runner.exec("Simple Length Test", () => { // default indentation is 2 (SECTION) + * Assert.equals("test".length, 4) // test passed * }) * function sum(a: number, b: number): number {return a + b} * const a = 1, b = 2 * runner.exec("Sum Test", () => { * Assert.equals(sum(a, b), 3) // test passed * }) + * title("End Testing") * - * // Example output (if verbosity is set to show headers): + * // Output will look like: + * // * Start Testing * + * // ** START: Simple Length Test ** + * // ** END: Simple Length Test ** * // ** START: Sum Test ** * // ** END: Sum Test ** + * // * End Testing * * ``` * - * @remarks Test functions are expected to use `Assert` methods internally. If an assertion fails, + * @remarks + * - Test functions are expected to use `Assert` methods internally. If an assertion fails, * the error will be caught and reported with context. + * - For a better organization, consider to put all test cases withing a single `TestCase` class + * where each test case scenario is defined with a static method. */ class TestRunner { - private static readonly START = "START" as const - private static readonly END = "END" as const - private static readonly HEADER_TK = "*" + private static readonly START = "START" as const // Prefix for start of a test case + private static readonly END = "END" as const // Prefix for end of a test case + private static readonly HEADER_TK = "*" // Token for title lines - /**Verbosity level */ + /**Allowed verbosity levels, to control the output of `TestRunner.title` method.*/ public static readonly VERBOSITY = { OFF: 0, HEADER: 1, @@ -760,20 +772,21 @@ class TestRunner { return acc; }, {} as Record) + // Default verbosity level to initialize the TestRunner, if not specified private static readonly DEFAULT_VERBOSITY = TestRunner.VERBOSITY.HEADER + + /** The verbosity level of the TestRunner instance.*/ private readonly _verbosity: typeof TestRunner.VERBOSITY[keyof typeof TestRunner.VERBOSITY] - /**Constructs a 'TestRunner' with the specified verbosity level. - * @param verbosity - One of the values from 'TestRunner.VERBOSITY' (default: 'HEADER') + /**Constructs a `TestRunner` with the specified verbosity level. + * @param verbosity - One of the values from `TestRunner.VERBOSITY` (default: `HEADER(1)`) */ public constructor(verbosity: typeof TestRunner.VERBOSITY[keyof typeof TestRunner.VERBOSITY] = TestRunner.DEFAULT_VERBOSITY) { this._verbosity = verbosity } /** Returns the current verbosity level. - * This is useful for checking the current logging level - * and adjusting the output accordingly. - * @returns {number} - The current verbosity level as defined in 'TestRunner.VERBOSITY'. + * @returns {number} - The current verbosity level as defined in `TestRunner.VERBOSITY`. * @example * ```ts * const runner = new TestRunner(TestRunner.VERBOSITY.HEADER) @@ -807,8 +820,8 @@ class TestRunner { * The number of `*` will depend on the indentation level, for `2` it shows * `**` as prefix and suffix. * @param msg - The message to display - * @param indent - Indentation level (default: `1`). The indentation level is indicated - * with the number of suffix `*`. + * @param indent - Indentation level (default: `1(HEADER)`). The indentation level is indicated + * with the number of `*` characters used as prefix and suffix to the title. * @returns {void} - This method does not return a value. * @example * ```ts @@ -829,15 +842,22 @@ class TestRunner { } } - /** See detailed JSDoc in class documentation + /** Executes a test case with a title and handles assertions. + * This method logs the start and end of the test case with titles (using `TestRunner.title` method) + * if the verbosity level allows it. + * It executes the provided function which should contain assertions. + * If an assertion fails, it will throw an `AssertionError`. + * See detailed JSDoc in class documentation * @param name - The name of the test case. * @param fn - The function containing the test logic. It should contain assertions using `Assert` methods. - * @param indent - Indentation level for the title (default: `2`). The indentation level is indicated + * @param indent - Indentation level for the title (default: `2(SECTION)`). The indentation level is indicated * with the number of suffix `*`. - * @throws AssertionError - If an assertion fails within the test function. + * @throws AssertionError - If an assertion fails within the test function fn. + * - If the provided fn is not a function. * @returns {void} - This method does not return a value. - * @remarks This method is used to execute a test case, logging the start and end of the test with titles. - * It is designed to work with the `Assert` class for assertions. + * @remarks Always pass a function reference using `() => ....` + * If you pass a direct function call, the code will execute before + * and not at indicated place it should be executed inside this method and it may produce unexpected results. * @example * ```ts * const runner = new TestRunner() @@ -846,7 +866,7 @@ class TestRunner { * }) * ``` */ - public exec(name: string, fn: () => void, indent: number = 2): void { + public exec(name: string, fn: () => void, indent: number = TestRunner.VERBOSITY.SECTION): void { this.title(`${TestRunner.START} ${name}`, indent); if (typeof fn !== "function") { throw new AssertionError("TestRunner.exec() expects a function as input."); diff --git a/test/main.ts b/test/main.ts index 4d16a9d..479e724 100644 --- a/test/main.ts +++ b/test/main.ts @@ -1,4 +1,3 @@ -// main test file for the unit test framework // #region main.ts