diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0038c1d..354208f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -78,6 +78,20 @@ grunt lint lint-fix # Lint with auto-fix - **var usage**: Allowed (for ES5 compatibility) - **const preference**: Not enforced +### Copyright Headers +- **New files**: Use current year only (e.g., `Copyright (c) 2026 NevWare21 Solutions LLC`) +- **Edited files**: Use original year through current year (e.g., `Copyright (c) 2024-2026 NevWare21 Solutions LLC`) +- All source files must include the standard copyright header: +```typescript +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) YYYY[-YYYY] NevWare21 Solutions LLC + * Licensed under the MIT license. + */ +``` + ### Coding Patterns - Support for both ES5 and ES6+ runtimes - Avoid ES6+ features in core implementation when targeting ES5 diff --git a/core/src/assert/assertClass.ts b/core/src/assert/assertClass.ts index 081055c..468265a 100644 --- a/core/src/assert/assertClass.ts +++ b/core/src/assert/assertClass.ts @@ -35,6 +35,7 @@ import { deepEqualsFunc, deepStrictEqualsFunc, equalsFunc, strictEqualsFunc } fr import { aboveFunc, belowFunc, leastFunc, mostFunc, withinFunc } from "./ops/numericOp"; import { typeOfFunc } from "./funcs/typeOf"; import { instanceOfFunc } from "./funcs/instanceOf"; +import { lengthFunc } from "./funcs/length"; /** * @internal @@ -148,19 +149,19 @@ export function createAssert(): IAssertClass { isNotOk: "not.ok", // The `isNotOk` function is an alias for `not.truthy` equal: { scopeFn: equalsFunc, nArgs: 2 }, - equals: { scopeFn: equalsFunc, nArgs: 2 }, + equals: { alias: "equal" }, strictEqual: { scopeFn: strictEqualsFunc, nArgs: 2 }, - strictEquals: { scopeFn: strictEqualsFunc, nArgs: 2 }, + strictEquals: { alias: "strictEqual" }, notStrictEqual: { scopeFn: createExprAdapter("not", strictEqualsFunc), nArgs: 2 }, deepEqual: { scopeFn: deepEqualsFunc, nArgs: 2 }, - deepEquals: { scopeFn: deepEqualsFunc, nArgs: 2 }, + deepEquals: { alias: "deepEqual" }, deepStrictEqual: { scopeFn: deepStrictEqualsFunc, nArgs: 2 }, - deepStrictEquals: { scopeFn: deepStrictEqualsFunc, nArgs: 2 }, + deepStrictEquals: { alias: "deepStrictEqual" }, notEqual: { scopeFn: createExprAdapter("not", equalsFunc), nArgs: 2 }, - notEquals: { scopeFn: createExprAdapter("not", equalsFunc), nArgs: 2 }, + notEquals: { alias: "notEqual" }, notDeepEqual: { scopeFn: createExprAdapter("not", deepEqualsFunc), nArgs: 2 }, - notDeepEquals: { scopeFn: createExprAdapter("not", deepEqualsFunc), nArgs: 2 }, + notDeepEquals: { alias: "notDeepEqual" }, isTrue: isStrictTrueFunc, isFalse: isStrictFalseFunc, @@ -256,7 +257,13 @@ export function createAssert(): IAssertClass { isAtMost: { scopeFn: mostFunc, nArgs: 2 }, isNotAtMost: { scopeFn: createExprAdapter("not", mostFunc), nArgs: 2 }, isWithin: { scopeFn: withinFunc, nArgs: 3 }, - isNotWithin: { scopeFn: createExprAdapter("not", withinFunc), nArgs: 3 } + isNotWithin: { scopeFn: createExprAdapter("not", withinFunc), nArgs: 3 }, + + // Length checking + lengthOf: { scopeFn: lengthFunc, nArgs: 2 }, + notLengthOf: { scopeFn: createExprAdapter("not", lengthFunc), nArgs: 2 }, + sizeOf: { alias: "lengthOf" }, + notSizeOf: { alias: "notLengthOf" } }; addAssertFuncs(assert, assertFuncs); diff --git a/core/src/assert/funcs/exists.ts b/core/src/assert/funcs/exists.ts index b43d83c..68b4f7e 100644 --- a/core/src/assert/funcs/exists.ts +++ b/core/src/assert/funcs/exists.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/src/assert/funcs/instanceOf.ts b/core/src/assert/funcs/instanceOf.ts index 915e4eb..d70f739 100644 --- a/core/src/assert/funcs/instanceOf.ts +++ b/core/src/assert/funcs/instanceOf.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/src/assert/funcs/length.ts b/core/src/assert/funcs/length.ts new file mode 100644 index 0000000..ebd7c44 --- /dev/null +++ b/core/src/assert/funcs/length.ts @@ -0,0 +1,77 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isNullOrUndefined, isObject, isString } from "@nevware21/ts-utils"; +import { IAssertScope } from "../interface/IAssertScope"; +import { MsgSource } from "../interface/types"; + +/** + * Asserts that the target has a length or size property equal to the given number. + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * @since 0.1.5 + * @param this - The assert scope. + * @param length - The expected length or size. + * @param evalMsg - The message to display if the assertion fails. + * @returns - The assert scope. + * @example + * ```typescript + * import { assert } from "@nevware21/tripwire"; + * + * assert([1, 2, 3]).has.lengthOf(3); // Passes - array length is 3 + * assert("hello").has.lengthOf(5); // Passes - string length is 5 + * assert(new Map([["a", 1]])).has.lengthOf(1); // Passes - Map size is 1 + * assert(new Set([1, 2])).has.lengthOf(2); // Passes - Set size is 2 + * assert([1, 2]).has.lengthOf(3); // Fails - array length is 2, not 3 + * ``` + */ +export function lengthFunc(this: IAssertScope, length: number, evalMsg?: MsgSource): R { + let context = this.context; + let value = context.value; + + context.set("expected", length); + + // Check if the value has a 'length' property + // For strings and objects (includes arrays, functions, etc.) + let hasLength = false; + let hasSize = false; + + if (!isNullOrUndefined(value)) { + // Strings have length as a property + if (isString(value)) { + hasLength = true; + } else if (isObject(value)) { + // Use 'in' operator only for objects + hasLength = "length" in value; + hasSize = "size" in value; + } + } + + if (!hasLength && !hasSize) { + context.eval( + false, + evalMsg || "expected {value} to have property \"length\" or \"size\"" + ); + } else { + let actualLength = hasLength ? (value as any).length : (value as any).size; + context.set("length", actualLength); + + if (hasLength) { + context.eval( + actualLength === length, + evalMsg || "expected {value} to have a length of {expected} but got {length}" + ); + } else { + context.eval( + actualLength === length, + evalMsg || "expected {value} to have a size of {expected} but got {length}" + ); + } + } + + return this.that; +} diff --git a/core/src/assert/funcs/nested.ts b/core/src/assert/funcs/nested.ts index 1e30f3f..a888285 100644 --- a/core/src/assert/funcs/nested.ts +++ b/core/src/assert/funcs/nested.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/src/assert/funcs/typeOf.ts b/core/src/assert/funcs/typeOf.ts index dd5e4da..e918920 100644 --- a/core/src/assert/funcs/typeOf.ts +++ b/core/src/assert/funcs/typeOf.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/src/assert/interface/IAssertClass.ts b/core/src/assert/interface/IAssertClass.ts index 4f2b768..b6caab2 100644 --- a/core/src/assert/interface/IAssertClass.ts +++ b/core/src/assert/interface/IAssertClass.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -1900,6 +1900,104 @@ export interface IAssertClass { * ``` */ isNotWithin(value: number | Date, start: number | Date, finish: number | Date, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the target has a length or size property equal to the given number. + * + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * + * @since 0.1.5 + * @param object - The object to check (array, string, Map, Set, or any object with length/size). + * @param length - The expected length or size. + * @param initMsg - The message to display if the assertion fails. + * @asserts That `object` has a length or size equal to `length` and throws {@link AssertionFailure} if it does not. + * @example + * ```typescript + * assert.lengthOf([1, 2, 3], 3); // Passes + * assert.lengthOf("hello", 5); // Passes + * assert.lengthOf(new Map([["a", 1]]), 1); // Passes + * assert.lengthOf(new Set([1, 2]), 2); // Passes + * assert.lengthOf([1, 2], 3); // Throws AssertionFailure + * ``` + */ + lengthOf( + object: T, + length: number, + initMsg?: MsgSource + ): AssertInst; + + /** + * Asserts that the target does not have a length or size property equal to the given number. + * + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * This is the inverse of {@link lengthOf}. + * + * @since 0.1.5 + * @param object - The object to check (array, string, Map, Set, or any object with length/size). + * @param length - The length or size that the object should not have. + * @param initMsg - The message to display if the assertion fails. + * @asserts That `object` does not have a length or size equal to `length` and throws {@link AssertionFailure} if it does. + * @example + * ```typescript + * assert.notLengthOf([1, 2, 3], 2); // Passes + * assert.notLengthOf("hello", 4); // Passes + * assert.notLengthOf([1, 2], 2); // Throws AssertionFailure + * ``` + */ + notLengthOf( + object: T, + length: number, + initMsg?: MsgSource + ): AssertInst; + + /** + * Asserts that the target has a size or length property equal to the given number. + * + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * + * @since 0.1.5 + * @param object - The object to check (array, string, Map, Set, or any object with length/size). + * @param length - The expected length or size. + * @param initMsg - The message to display if the assertion fails. + * @asserts That `object` has a length or size equal to `length` and throws {@link AssertionFailure} if it does not. + * @example + * ```typescript + * assert.sizeOf([1, 2, 3], 3); // Passes + * assert.sizeOf("hello", 5); // Passes + * assert.sizeOf(new Map([["a", 1]]), 1); // Passes + * assert.sizeOf(new Set([1, 2]), 2); // Passes + * assert.sizeOf([1, 2], 3); // Throws AssertionFailure + * ``` + */ + sizeOf( + object: T, + length: number, + initMsg?: MsgSource + ): AssertInst; + + /** + * Asserts that the target does not have a size or length property equal to the given number. + * + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * This is the inverse of {@link sizeOf}. + * + * @since 0.1.5 + * @param object - The object to check (array, string, Map, Set, or any object with length/size). + * @param length - The length or size that the object should not have. + * @param initMsg - The message to display if the assertion fails. + * @asserts That `object` does not have a length or size equal to `length` and throws {@link AssertionFailure} if it does. + * @example + * ```typescript + * assert.notSizeOf([1, 2, 3], 2); // Passes + * assert.notSizeOf("hello", 4); // Passes + * assert.notSizeOf([1, 2], 2); // Throws AssertionFailure + * ``` + */ + notSizeOf( + object: T, + length: number, + initMsg?: MsgSource + ): AssertInst; } export type IExtendedAssert = IAssertClass & T; \ No newline at end of file diff --git a/core/src/assert/interface/IAssertInstHandlers.ts b/core/src/assert/interface/IAssertInstHandlers.ts index 2aa8536..ba719ab 100644 --- a/core/src/assert/interface/IAssertInstHandlers.ts +++ b/core/src/assert/interface/IAssertInstHandlers.ts @@ -1,3 +1,11 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2024 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + import { IAssertScope } from "./IAssertScope"; /** diff --git a/core/src/assert/interface/ops/IHasOp.ts b/core/src/assert/interface/ops/IHasOp.ts index 3880b2e..0a24e7b 100644 --- a/core/src/assert/interface/ops/IHasOp.ts +++ b/core/src/assert/interface/ops/IHasOp.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -12,6 +12,7 @@ import { IAllOp } from "./IAllOp"; import { IAnyOp } from "./IAnyOp"; import { IOwnOp } from "./IOwnOp"; import { INestedOp } from "./INestedOp"; +import { MsgSource } from "../types"; /** * Provides the 'has' operation for the assert scope. @@ -82,4 +83,36 @@ export interface IHasOp { * ``` */ iterator: SymbolFn; + + /** + * Asserts that the target has a length or size property equal to the given number. + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * @since 0.1.5 + * @param length - The expected length or size. + * @param evalMsg - Optional error message. + * @example + * ```typescript + * expect([1, 2, 3]).has.lengthOf(3); // Passes - array length is 3 + * expect("hello").has.lengthOf(5); // Passes - string length is 5 + * expect(new Map([["a", 1]])).has.lengthOf(1); // Passes - Map size is 1 + * expect(new Set([1, 2])).has.lengthOf(2); // Passes - Set size is 2 + * ``` + */ + lengthOf(n: number, evalMsg?: MsgSource): R; + + /** + * Asserts that the target has a size or length property equal to the given number. + * Works with arrays, strings, Maps, Sets, and any object with a length or size property. + * @since 0.1.5 + * @param size - The expected size or length. + * @param evalMsg - Optional error message. + * @example + * ```typescript + * expect([1, 2, 3]).has.sizeOf(3); // Passes - array length is 3 + * expect("hello").has.sizeOf(5); // Passes - string length is 5 + * expect(new Map([["a", 1]])).has.sizeOf(1); // Passes - Map size is 1 + * expect(new Set([1, 2])).has.sizeOf(2); // Passes - Set size is 2 + * ``` + */ + sizeOf(n: number, evalMsg?: MsgSource): R; } diff --git a/core/src/assert/interface/ops/INestedOp.ts b/core/src/assert/interface/ops/INestedOp.ts index cc328a9..1d1b0b0 100644 --- a/core/src/assert/interface/ops/INestedOp.ts +++ b/core/src/assert/interface/ops/INestedOp.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/src/assert/ops/hasOp.ts b/core/src/assert/ops/hasOp.ts index e2fa24f..02f607e 100644 --- a/core/src/assert/ops/hasOp.ts +++ b/core/src/assert/ops/hasOp.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -14,6 +14,7 @@ import { AssertScopeFuncDefs } from "../interface/IAssertInst"; import { IAssertScope } from "../interface/IAssertScope"; import { hasOwnSymbolFunc, hasSymbolFunc } from "../funcs/hasSymbol"; import { nestedOp } from "./nestedOp"; +import { lengthFunc } from "../funcs/length"; export function hasOp(scope: IAssertScope): IHasOp { let props: AssertScopeFuncDefs> = { @@ -23,7 +24,9 @@ export function hasOp(scope: IAssertScope): IHasOp { propertyDescriptor: { scopeFn: hasPropertyDescriptorFunc}, own: { propFn: ownOp }, nested: { propFn: nestedOp }, - iterator: { scopeFn: hasSymbolFunc(Symbol.iterator) } + iterator: { scopeFn: hasSymbolFunc(Symbol.iterator) }, + lengthOf: { scopeFn: lengthFunc }, + sizeOf: { scopeFn: lengthFunc } }; return scope.createOperation(props); @@ -37,7 +40,9 @@ export function hasOwnOp(scope: IAssertScope): IHasOp { propertyDescriptor: { scopeFn: hasOwnPropertyDescriptorFunc}, own: { propFn: ownOp }, nested: { propFn: nestedOp }, - iterator: { scopeFn: hasOwnSymbolFunc(Symbol.iterator) } + iterator: { scopeFn: hasOwnSymbolFunc(Symbol.iterator) }, + lengthOf: { scopeFn: lengthFunc }, + sizeOf: { scopeFn: lengthFunc } }; return scope.createOperation(props); diff --git a/core/src/assert/ops/nestedOp.ts b/core/src/assert/ops/nestedOp.ts index e5bf8d3..506b102 100644 --- a/core/src/assert/ops/nestedOp.ts +++ b/core/src/assert/ops/nestedOp.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/assert.exists.test.ts b/core/test/src/assert/assert.exists.test.ts index e19c616..4523d22 100644 --- a/core/test/src/assert/assert.exists.test.ts +++ b/core/test/src/assert/assert.exists.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/assert.isFinite.test.ts b/core/test/src/assert/assert.isFinite.test.ts index 7d2a792..5a95741 100644 --- a/core/test/src/assert/assert.isFinite.test.ts +++ b/core/test/src/assert/assert.isFinite.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/assert.isInstanceOf.test.ts b/core/test/src/assert/assert.isInstanceOf.test.ts index f6c662c..ee67ef7 100644 --- a/core/test/src/assert/assert.isInstanceOf.test.ts +++ b/core/test/src/assert/assert.isInstanceOf.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/assert.isNaN.test.ts b/core/test/src/assert/assert.isNaN.test.ts index e57c5cf..cdfe634 100644 --- a/core/test/src/assert/assert.isNaN.test.ts +++ b/core/test/src/assert/assert.isNaN.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/assert.lengthOf.test.ts b/core/test/src/assert/assert.lengthOf.test.ts new file mode 100644 index 0000000..1adcdad --- /dev/null +++ b/core/test/src/assert/assert.lengthOf.test.ts @@ -0,0 +1,285 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "../../../src/assert/assertClass"; +import { expect } from "../../../src/assert/expect"; +import { checkError } from "../support/checkError"; + +describe("assert.lengthOf", () => { + describe("basic functionality", () => { + it("should pass for arrays with correct length", () => { + assert.lengthOf([1, 2, 3], 3); + assert.lengthOf([], 0); + assert.lengthOf([1], 1); + assert.lengthOf([1, 2, 3, 4, 5], 5); + }); + + it("should pass for strings with correct length", () => { + assert.lengthOf("hello", 5); + assert.lengthOf("", 0); + assert.lengthOf("a", 1); + assert.lengthOf("foobar", 6); + }); + + it("should fail for arrays with incorrect length", () => { + checkError( + () => assert.lengthOf([1, 2, 3], 2), + "expected [1,2,3] to have a length of 2 but got 3" + ); + }); + + it("should fail for strings with incorrect length", () => { + checkError( + () => assert.lengthOf("foobar", 5), + "expected \"foobar\" to have a length of 5 but got 6" + ); + }); + + it("should work with custom message", () => { + checkError( + () => assert.lengthOf([1, 2], 3, "Array length mismatch"), + "Array length mismatch" + ); + }); + }); + + describe("Map support", () => { + it("should pass for Maps with correct size", () => { + assert.lengthOf(new Map(), 0); + + const map = new Map(); + map.set("a", 1); + map.set("b", 2); + assert.lengthOf(map, 2); + }); + + it("should fail for Maps with incorrect size", () => { + const map = new Map(); + map.set("a", 1); + map.set("b", 2); + + checkError( + () => assert.lengthOf(map, 3), + /expected .+ to have a size of 3 but got 2/ + ); + }); + + it("should work with custom message for Maps", () => { + const map = new Map(); + map.set("a", 1); + + checkError( + () => assert.lengthOf(map, 2, "Map size mismatch"), + "Map size mismatch" + ); + }); + }); + + describe("Set support", () => { + it("should pass for Sets with correct size", () => { + assert.lengthOf(new Set(), 0); + + const set = new Set(); + set.add(1); + set.add(2); + assert.lengthOf(set, 2); + }); + + it("should fail for Sets with incorrect size", () => { + const set = new Set(); + set.add(1); + set.add(2); + + checkError( + () => assert.lengthOf(set, 3), + /expected .+ to have a size of 3 but got 2/ + ); + }); + + it("should work with custom message for Sets", () => { + const set = new Set(); + set.add(1); + + checkError( + () => assert.lengthOf(set, 2, "Set size mismatch"), + "Set size mismatch" + ); + }); + }); + + describe("expect syntax", () => { + it("should work with expect syntax for arrays", () => { + expect([1, 2, 3]).has.lengthOf(3); + expect([1, 2, 3]).to.have.lengthOf(3); + }); + + it("should work with expect syntax for strings", () => { + expect("hello").has.lengthOf(5); + expect("hello").to.have.lengthOf(5); + }); + + it("should work with expect syntax for Maps", () => { + const map = new Map([["a", 1], ["b", 2]]); + expect(map).has.lengthOf(2); + expect(map).to.have.lengthOf(2); + }); + + it("should work with expect syntax for Sets", () => { + const set = new Set([1, 2, 3]); + expect(set).has.lengthOf(3); + expect(set).to.have.lengthOf(3); + }); + + it("should fail with expect syntax when length doesn't match", () => { + checkError( + () => expect([1, 2]).has.lengthOf(3), + "expected [1,2] to have a length of 3 but got 2" + ); + }); + }); + + describe("edge cases", () => { + it("should fail for objects without length or size property", () => { + checkError( + () => assert.lengthOf({} as any, 0), + "expected {} to have property \"length\" or \"size\"" + ); + + checkError( + () => assert.lengthOf({ a: 1 } as any, 1), + /expected .+ to have property "length" or "size"/ + ); + }); + + it("should fail for primitives without length", () => { + checkError( + () => assert.lengthOf(123 as any, 3), + "expected 123 to have property \"length\" or \"size\"" + ); + + checkError( + () => assert.lengthOf(true as any, 1), + "expected true to have property \"length\" or \"size\"" + ); + }); + + it("should fail for null/undefined", () => { + checkError( + () => assert.lengthOf(null as any, 0), + "expected null to have property \"length\" or \"size\"" + ); + + checkError( + () => assert.lengthOf(undefined as any, 0), + "expected undefined to have property \"length\" or \"size\"" + ); + }); + + it("should handle zero length correctly", () => { + assert.lengthOf([], 0); + assert.lengthOf("", 0); + assert.lengthOf(new Map(), 0); + assert.lengthOf(new Set(), 0); + }); + + it("should work with objects that have a custom length property", () => { + const customObj = { length: 5 }; + assert.lengthOf(customObj, 5); + }); + + it("should work with objects that have a custom size property", () => { + const customObj = { size: 3 }; + assert.lengthOf(customObj, 3); + }); + + it("should prefer length over size when both are present", () => { + const customObj = { length: 5, size: 3 }; + assert.lengthOf(customObj, 5); + + checkError( + () => assert.lengthOf(customObj, 3), + "expected {length:5,size:3} to have a length of 3 but got 5" + ); + }); + + it("should work with typed arrays", () => { + assert.lengthOf(new Uint8Array(5), 5); + assert.lengthOf(new Int32Array([1, 2, 3]), 3); + assert.lengthOf(new Float64Array(10), 10); + }); + + it("should work with array-like objects", () => { + const arrayLike = { 0: "a", 1: "b", length: 2 }; + assert.lengthOf(arrayLike, 2); + }); + }); +}); + +describe("assert.notLengthOf", () => { + describe("basic functionality", () => { + it("should pass when length doesn't match", () => { + assert.notLengthOf([1, 2, 3], 2); + assert.notLengthOf("hello", 4); + assert.notLengthOf(new Map([["a", 1]]), 2); + assert.notLengthOf(new Set([1, 2]), 3); + }); + + it("should fail when length matches", () => { + checkError( + () => assert.notLengthOf([1, 2, 3], 3), + "not expected [1,2,3] to have a length of 3 but got 3" + ); + + checkError( + () => assert.notLengthOf("hello", 5), + "not expected \"hello\" to have a length of 5 but got 5" + ); + }); + + it("should work with custom message", () => { + checkError( + () => assert.notLengthOf([1, 2], 2, "Should not have this length"), + "Should not have this length" + ); + }); + }); + + describe("expect syntax with negation", () => { + it("should work with not operator", () => { + expect([1, 2, 3]).to.not.have.lengthOf(2); + expect("hello").to.not.have.lengthOf(4); + expect(new Map([["a", 1]])).to.not.have.lengthOf(2); + expect(new Set([1, 2])).to.not.have.lengthOf(3); + }); + + it("should fail when length matches with not operator", () => { + checkError( + () => expect([1, 2]).to.not.have.lengthOf(2), + "not expected [1,2] to have a length of 2 but got 2" + ); + }); + }); + + describe("edge cases", () => { + it("should handle zero length correctly", () => { + assert.notLengthOf([1], 0); + assert.notLengthOf("a", 0); + assert.notLengthOf(new Map([["a", 1]]), 0); + assert.notLengthOf(new Set([1]), 0); + }); + + it("should not fail for objects without length or size property", () => { + assert.notLengthOf({} as any, 0); + assert.notLengthOf({ a: 1 } as any, 1); + assert.notLengthOf(123 as any, 3); + assert.notLengthOf(true as any, 1); + assert.notLengthOf(null as any, 0); + assert.notLengthOf(undefined as any, 0); + }); + }); +}); diff --git a/core/test/src/assert/assert.nested.test.ts b/core/test/src/assert/assert.nested.test.ts index b906eb8..274c797 100644 --- a/core/test/src/assert/assert.nested.test.ts +++ b/core/test/src/assert/assert.nested.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/assert.sizeOf.test.ts b/core/test/src/assert/assert.sizeOf.test.ts new file mode 100644 index 0000000..1279706 --- /dev/null +++ b/core/test/src/assert/assert.sizeOf.test.ts @@ -0,0 +1,285 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "../../../src/assert/assertClass"; +import { expect } from "../../../src/assert/expect"; +import { checkError } from "../support/checkError"; + +describe("assert.sizeOf", () => { + describe("basic functionality", () => { + it("should pass for arrays with correct size", () => { + assert.sizeOf([1, 2, 3], 3); + assert.sizeOf([], 0); + assert.sizeOf([1], 1); + assert.sizeOf([1, 2, 3, 4, 5], 5); + }); + + it("should pass for strings with correct size", () => { + assert.sizeOf("hello", 5); + assert.sizeOf("", 0); + assert.sizeOf("a", 1); + assert.sizeOf("foobar", 6); + }); + + it("should fail for arrays with incorrect size", () => { + checkError( + () => assert.sizeOf([1, 2, 3], 2), + "expected [1,2,3] to have a length of 2 but got 3" + ); + }); + + it("should fail for strings with incorrect size", () => { + checkError( + () => assert.sizeOf("foobar", 5), + "expected \"foobar\" to have a length of 5 but got 6" + ); + }); + + it("should work with custom message", () => { + checkError( + () => assert.sizeOf([1, 2], 3, "Array size mismatch"), + "Array size mismatch" + ); + }); + }); + + describe("Map support", () => { + it("should pass for Maps with correct size", () => { + assert.sizeOf(new Map(), 0); + + const map = new Map(); + map.set("a", 1); + map.set("b", 2); + assert.sizeOf(map, 2); + }); + + it("should fail for Maps with incorrect size", () => { + const map = new Map(); + map.set("a", 1); + map.set("b", 2); + + checkError( + () => assert.sizeOf(map, 3), + /expected .+ to have a size of 3 but got 2/ + ); + }); + + it("should work with custom message for Maps", () => { + const map = new Map(); + map.set("a", 1); + + checkError( + () => assert.sizeOf(map, 2, "Map size mismatch"), + "Map size mismatch" + ); + }); + }); + + describe("Set support", () => { + it("should pass for Sets with correct size", () => { + assert.sizeOf(new Set(), 0); + + const set = new Set(); + set.add(1); + set.add(2); + assert.sizeOf(set, 2); + }); + + it("should fail for Sets with incorrect size", () => { + const set = new Set(); + set.add(1); + set.add(2); + + checkError( + () => assert.sizeOf(set, 3), + /expected .+ to have a size of 3 but got 2/ + ); + }); + + it("should work with custom message for Sets", () => { + const set = new Set(); + set.add(1); + + checkError( + () => assert.sizeOf(set, 2, "Set size mismatch"), + "Set size mismatch" + ); + }); + }); + + describe("expect syntax", () => { + it("should work with expect syntax for arrays", () => { + expect([1, 2, 3]).has.sizeOf(3); + expect([1, 2, 3]).to.have.sizeOf(3); + }); + + it("should work with expect syntax for strings", () => { + expect("hello").has.sizeOf(5); + expect("hello").to.have.sizeOf(5); + }); + + it("should work with expect syntax for Maps", () => { + const map = new Map([["a", 1], ["b", 2]]); + expect(map).has.sizeOf(2); + expect(map).to.have.sizeOf(2); + }); + + it("should work with expect syntax for Sets", () => { + const set = new Set([1, 2, 3]); + expect(set).has.sizeOf(3); + expect(set).to.have.sizeOf(3); + }); + + it("should fail with expect syntax when size doesn't match", () => { + checkError( + () => expect([1, 2]).has.sizeOf(3), + "expected [1,2] to have a length of 3 but got 2" + ); + }); + }); + + describe("edge cases", () => { + it("should fail for objects without length or size property", () => { + checkError( + () => assert.sizeOf({} as any, 0), + "expected {} to have property \"length\" or \"size\"" + ); + + checkError( + () => assert.sizeOf({ a: 1 } as any, 1), + /expected .+ to have property "length" or "size"/ + ); + }); + + it("should fail for primitives without size", () => { + checkError( + () => assert.sizeOf(123 as any, 3), + "expected 123 to have property \"length\" or \"size\"" + ); + + checkError( + () => assert.sizeOf(true as any, 1), + "expected true to have property \"length\" or \"size\"" + ); + }); + + it("should fail for null/undefined", () => { + checkError( + () => assert.sizeOf(null as any, 0), + "expected null to have property \"length\" or \"size\"" + ); + + checkError( + () => assert.sizeOf(undefined as any, 0), + "expected undefined to have property \"length\" or \"size\"" + ); + }); + + it("should handle zero size correctly", () => { + assert.sizeOf([], 0); + assert.sizeOf("", 0); + assert.sizeOf(new Map(), 0); + assert.sizeOf(new Set(), 0); + }); + + it("should work with objects that have a custom length property", () => { + const customObj = { length: 5 }; + assert.sizeOf(customObj, 5); + }); + + it("should work with objects that have a custom size property", () => { + const customObj = { size: 3 }; + assert.sizeOf(customObj, 3); + }); + + it("should prefer length over size when both are present", () => { + const customObj = { length: 5, size: 3 }; + assert.sizeOf(customObj, 5); + + checkError( + () => assert.sizeOf(customObj, 3), + "expected {length:5,size:3} to have a length of 3 but got 5" + ); + }); + + it("should work with typed arrays", () => { + assert.sizeOf(new Uint8Array(5), 5); + assert.sizeOf(new Int32Array([1, 2, 3]), 3); + assert.sizeOf(new Float64Array(10), 10); + }); + + it("should work with array-like objects", () => { + const arrayLike = { 0: "a", 1: "b", length: 2 }; + assert.sizeOf(arrayLike, 2); + }); + }); +}); + +describe("assert.notSizeOf", () => { + describe("basic functionality", () => { + it("should pass when length doesn't match", () => { + assert.notSizeOf([1, 2, 3], 2); + assert.notSizeOf("hello", 4); + assert.notSizeOf(new Map([["a", 1]]), 2); + assert.notSizeOf(new Set([1, 2]), 3); + }); + + it("should fail when length matches", () => { + checkError( + () => assert.notSizeOf([1, 2, 3], 3), + "not expected [1,2,3] to have a length of 3 but got 3" + ); + + checkError( + () => assert.notSizeOf("hello", 5), + "not expected \"hello\" to have a length of 5 but got 5" + ); + }); + + it("should work with custom message", () => { + checkError( + () => assert.notSizeOf([1, 2], 2, "Should not have this length"), + "Should not have this length" + ); + }); + }); + + describe("expect syntax with negation", () => { + it("should work with not operator", () => { + expect([1, 2, 3]).to.not.have.sizeOf(2); + expect("hello").to.not.have.sizeOf(4); + expect(new Map([["a", 1]])).to.not.have.sizeOf(2); + expect(new Set([1, 2])).to.not.have.sizeOf(3); + }); + + it("should fail when length matches with not operator", () => { + checkError( + () => expect([1, 2]).to.not.have.sizeOf(2), + "not expected [1,2] to have a length of 2 but got 2" + ); + }); + }); + + describe("edge cases", () => { + it("should handle zero length correctly", () => { + assert.notSizeOf([1], 0); + assert.notSizeOf("a", 0); + assert.notSizeOf(new Map([["a", 1]]), 0); + assert.notSizeOf(new Set([1]), 0); + }); + + it("should not fail for objects without length or size property", () => { + assert.notSizeOf({} as any, 0); + assert.notSizeOf({ a: 1 } as any, 1); + assert.notSizeOf(123 as any, 3); + assert.notSizeOf(true as any, 1); + assert.notSizeOf(null as any, 0); + assert.notSizeOf(undefined as any, 0); + }); + }); +}); diff --git a/core/test/src/assert/assert.typeOf.test.ts b/core/test/src/assert/assert.typeOf.test.ts index 27f2528..a341ae0 100644 --- a/core/test/src/assert/assert.typeOf.test.ts +++ b/core/test/src/assert/assert.typeOf.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/funcs/hasSymbol.test.ts b/core/test/src/assert/funcs/hasSymbol.test.ts index 09c4dc4..3a8cdbb 100644 --- a/core/test/src/assert/funcs/hasSymbol.test.ts +++ b/core/test/src/assert/funcs/hasSymbol.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/ops/numericOp.test.ts b/core/test/src/assert/ops/numericOp.test.ts index 98654df..4218059 100644 --- a/core/test/src/assert/ops/numericOp.test.ts +++ b/core/test/src/assert/ops/numericOp.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/core/test/src/assert/parseNestedPath.test.ts b/core/test/src/assert/parseNestedPath.test.ts index 77e0fc3..23162d8 100644 --- a/core/test/src/assert/parseNestedPath.test.ts +++ b/core/test/src/assert/parseNestedPath.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ diff --git a/shim/chai/src/assert/chaiAssert.ts b/shim/chai/src/assert/chaiAssert.ts index c5777a1..d01999b 100644 --- a/shim/chai/src/assert/chaiAssert.ts +++ b/shim/chai/src/assert/chaiAssert.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -133,7 +133,7 @@ function _createChaiAssert(): IChaiAssert { notDeepPropertyVal: { scopeFn: createExprAdapter("not.deep.property"), nArgs: 3 }, notDeepOwnPropertyVal: { scopeFn: createExprAdapter("not.deep.own.property"), nArgs: 3 }, - lengthOf: notImplemented, + lengthOf: { scopeFn: createExprAdapter("has.lengthOf"), nArgs: 2 }, throw: { scopeFn: createExprAdapter("throws"), nArgs: 3 }, throws: { scopeFn: createExprAdapter("throws"), nArgs: 3 }, Throw: { scopeFn: createExprAdapter("throws"), nArgs: 3 }, diff --git a/shim/chai/src/assert/chaiExpect.ts b/shim/chai/src/assert/chaiExpect.ts index bb2abb9..7b8ea6f 100644 --- a/shim/chai/src/assert/chaiExpect.ts +++ b/shim/chai/src/assert/chaiExpect.ts @@ -1,4 +1,3 @@ - /* * @nevware21/tripwire * https://github.com/nevware21/tripwire diff --git a/shim/chai/test/src/bootstrap/globalErr.ts b/shim/chai/test/src/bootstrap/globalErr.ts index d2d9edb..1cd1da3 100644 --- a/shim/chai/test/src/bootstrap/globalErr.ts +++ b/shim/chai/test/src/bootstrap/globalErr.ts @@ -1,3 +1,11 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2024 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + import { AssertionError, CHECK_INTERNAL_STACK_FRAME_REGEX, expect } from "@nevware21/tripwire"; import { arrSlice, dumpObj, isFunction, strTrim } from "@nevware21/ts-utils"; import { objForEachKey } from "@nevware21/ts-utils"; diff --git a/shim/chai/test/src/chaiAssert.test.ts b/shim/chai/test/src/chaiAssert.test.ts index 3e89ae9..5e623a1 100644 --- a/shim/chai/test/src/chaiAssert.test.ts +++ b/shim/chai/test/src/chaiAssert.test.ts @@ -1,3 +1,11 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2024-2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + /** * This is a modified version of the original Chai library tests to convert it * into TypeScript to ensure that the shim is type-safe and conforms to the @@ -1416,42 +1424,42 @@ describe("assert", function () { }, "blah: not expected any key: [foo,baz], found: [foo,bar] (2 keys)"); }); - // it("lengthOf", function() { - // assert.lengthOf([1,2,3], 3); - // assert.lengthOf("foobar", 6); + it("lengthOf", function() { + assert.lengthOf([1,2,3], 3); + assert.lengthOf("foobar", 6); - // err(function () { - // assert.lengthOf("foobar", 5, "blah"); - // }, "blah: expected \"foobar\" to have a length of 5 but got 6"); + err(function () { + assert.lengthOf("foobar", 5, "blah"); + }, "blah: expected \"foobar\" to have a length of 5 but got 6"); - // err(function () { - // assert.lengthOf(1 as any, 5); - // }, "expected 1 to have property \"length\""); + err(function () { + assert.lengthOf(1 as any, 5); + }, "expected 1 to have property \"length\""); - // assert.lengthOf(new Map(), 0); + assert.lengthOf(new Map(), 0); - // var map = new Map(); - // map.set("a", 1); - // map.set("b", 2); + var map = new Map(); + map.set("a", 1); + map.set("b", 2); - // assert.lengthOf(map, 2); + assert.lengthOf(map, 2); - // err(function(){ - // assert.lengthOf(map, 3, "blah"); - // }, "blah: expected Map{ \"a\" => 1, \"b\" => 2 } to have a size of 3 but got 2"); + err(function(){ + assert.lengthOf(map, 3, "blah"); + }, "blah: expected [Map:{}] to have a size of 3 but got 2"); - // assert.lengthOf(new Set(), 0); + assert.lengthOf(new Set(), 0); - // var set = new Set(); - // set.add(1); - // set.add(2); + var set = new Set(); + set.add(1); + set.add(2); - // assert.lengthOf(set, 2); + assert.lengthOf(set, 2); - // err(function(){ - // assert.lengthOf(set, 3, "blah"); - // }, "blah: expected Set{ 1, 2 } to have a size of 3 but got 2"); - // }); + err(function(){ + assert.lengthOf(set, 3, "blah"); + }, "blah: expected [Set:{}] to have a size of 3 but got 2"); + }); it("match", function () { assert.match("foobar", /^foo/);