From 03cdbf210853a2338aa11b5e2ad7bc3908fb4df0 Mon Sep 17 00:00:00 2001 From: Nev Date: Sun, 25 Jan 2026 20:13:20 -0800 Subject: [PATCH] feat(chai-shim): Enable ownInclude, deepOwnInclude, and Map/Set keys assertions Add support for ownInclude, notOwnInclude, deepOwnInclude, and notDeepOwnInclude assertions to the Chai compatibility shim. Changes: - Implement own.include operation for checking own properties only - Add deep.own.include for deep equality comparison of own properties - Enable previously commented tests for include, deepInclude, keys operations with Map/Set collections - Improve error messages for include operations on unsupported types - Update keys operation error messages to quote string keys consistently - Fix Map/Set formatting in error messages to show contents Core changes: - Update includeOp error messages for null/undefined to be more descriptive - Update keysOp error messages to wrap string keys in quotes Error Message Improvements - Updated includeOp.test.ts error messages for null/undefined to use descriptive format: "argument null (\"null\") is not a supported collection type" Copyright Updates - Updated copyright years to 2024-2026 for modified files --- core/src/assert/adapters/exprAdapter.ts | 6 +- core/src/assert/assertClass.ts | 20 +- core/src/assert/funcs/changes.ts | 2 +- core/src/assert/funcs/closeTo.ts | 6 +- core/src/assert/funcs/equal.ts | 4 +- core/src/assert/funcs/hasProperty.ts | 10 +- core/src/assert/funcs/instanceOf.ts | 2 +- core/src/assert/funcs/isEmpty.ts | 10 +- core/src/assert/funcs/keysFunc.ts | 53 +- core/src/assert/funcs/match.ts | 4 +- core/src/assert/funcs/members.ts | 4 +- core/src/assert/funcs/nested.ts | 4 +- core/src/assert/funcs/oneOf.ts | 2 +- core/src/assert/funcs/operator.ts | 2 +- core/src/assert/funcs/valuesFunc.ts | 47 +- core/src/assert/interface/IAssertClass.ts | 246 +++++++ core/src/assert/interface/IAssertInst.ts | 10 +- core/src/assert/interface/IFormatter.ts | 3 +- core/src/assert/interface/IScopeContext.ts | 14 +- core/src/assert/interface/ops/IToOp.ts | 10 +- .../assert/internal/_addAssertInstFuncs.ts | 4 +- core/src/assert/internal/_formatValue.ts | 76 ++- core/src/assert/ops/changeResultOp.ts | 4 +- core/src/assert/ops/includeOp.ts | 300 +++++++- core/src/assert/ops/numericOp.ts | 6 +- core/src/assert/ops/ownOp.ts | 20 +- core/src/assert/scopeContext.ts | 144 +++- .../assert/assert.doesNotHaveAllKeys.test.ts | 112 +++ .../assert/assert.doesNotHaveAnyKeys.test.ts | 108 +++ .../src/assert/assert.doesNotThrow.test.ts | 117 ++++ core/test/src/assert/assert.equals.test.ts | 8 +- .../test/src/assert/assert.hasAllKeys.test.ts | 116 ++++ .../test/src/assert/assert.hasAnyKeys.test.ts | 106 +++ core/test/src/assert/assert.ifError.test.ts | 1 + core/test/src/assert/assert.includes.test.ts | 118 +++- .../test/src/assert/assert.isIterable.test.ts | 4 +- core/test/src/assert/assert.isObject.test.ts | 2 +- core/test/src/assert/assert.notMatch.test.ts | 84 +++ core/test/src/assert/assert.ok.test.ts | 10 +- .../test/src/assert/assert.ownInclude.test.ts | 318 +++++++++ core/test/src/assert/expect.has.test.ts | 6 +- core/test/src/assert/expect.include.test.ts | 82 ++- core/test/src/assert/expect.isIterable.ts | 8 +- .../test/src/assert/expect.ownInclude.test.ts | 340 +++++++++ core/test/src/assert/expect.to.test.ts | 6 +- .../src/assert/funcs/allValuesFunc.test.ts | 54 +- .../src/assert/funcs/anyValuesFunc.test.ts | 10 +- core/test/src/assert/members.test.ts | 133 ++++ .../test/src/operations/deepIncludeOp.test.ts | 14 +- core/test/src/operations/includeOp.test.ts | 6 +- core/test/src/operations/keysOp.test.ts | 18 +- shim/chai/README.md | 2 +- shim/chai/src/assert/chaiAssert.ts | 17 +- shim/chai/test/src/chaiAssert.test.ts | 643 +++++++++--------- 54 files changed, 2853 insertions(+), 603 deletions(-) create mode 100644 core/test/src/assert/assert.doesNotHaveAllKeys.test.ts create mode 100644 core/test/src/assert/assert.doesNotHaveAnyKeys.test.ts create mode 100644 core/test/src/assert/assert.doesNotThrow.test.ts create mode 100644 core/test/src/assert/assert.hasAllKeys.test.ts create mode 100644 core/test/src/assert/assert.hasAnyKeys.test.ts create mode 100644 core/test/src/assert/assert.notMatch.test.ts create mode 100644 core/test/src/assert/assert.ownInclude.test.ts create mode 100644 core/test/src/assert/expect.ownInclude.test.ts diff --git a/core/src/assert/adapters/exprAdapter.ts b/core/src/assert/adapters/exprAdapter.ts index 9a64f4f..6b56339 100644 --- a/core/src/assert/adapters/exprAdapter.ts +++ b/core/src/assert/adapters/exprAdapter.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -34,7 +34,7 @@ function _throwInvalidExpr(isValid: boolean, expr: string | string[], internalEr expr = expr.join("."); } - throw new AssertionError(`Invalid expression: ${expr}`, null, internalErrorStackStart); + throw new AssertionError("Invalid expression: " + expr, null, internalErrorStackStart); } } @@ -136,7 +136,7 @@ function _runExpr(theAssert: any, scope: IAssertScope, steps: IStepDef[], scopeF if (!(step.name in scope.that)) { throw new AssertionError( - `${idx} Invalid step: ${step.name} for [${steps.map(s => s.name).join("->")}] available steps: [${objKeys(scope.that).join(";")}] - ${_formatValue(scope.context, scope.that)}`, + "" + idx + " Invalid step: " + step.name + " for [" + steps.map((s: any) => s.name).join("->") + "] available steps: [" + objKeys(scope.that).join(";") + "] - " + _formatValue(scope.context, scope.that), { expected: step.name, actual: objKeys(scope.that).join(";") diff --git a/core/src/assert/assertClass.ts b/core/src/assert/assertClass.ts index 066dcc4..f895ca4 100644 --- a/core/src/assert/assertClass.ts +++ b/core/src/assert/assertClass.ts @@ -244,8 +244,10 @@ export function createAssert(): IAssertClass { isNotInstanceOf: { scopeFn: createExprAdapter("not", instanceOfFunc), nArgs: 2 }, throws: { scopeFn: throwsFunc, nArgs: 3 }, + doesNotThrow: { scopeFn: createExprAdapter("not", throwsFunc), nArgs: 3 }, match: { scopeFn: matchFunc, nArgs: 2 }, + notMatch: { scopeFn: createExprAdapter("not", matchFunc), nArgs: 2 }, hasProperty: { scopeFn: hasPropertyFunc, nArgs: 3 }, hasOwnProperty: { scopeFn: hasOwnPropertyFunc, nArgs: 3 }, @@ -266,6 +268,12 @@ export function createAssert(): IAssertClass { deepNestedInclude: { scopeFn: deepNestedIncludeFunc, nArgs: 2 }, notDeepNestedInclude: { scopeFn: createExprAdapter("not", deepNestedIncludeFunc), nArgs: 2 }, + // Own include operations (checks only own properties, not inherited) + ownInclude: { scopeFn: createExprAdapter("own.include"), nArgs: 2 }, + notOwnInclude: { scopeFn: createExprAdapter("not.own.include"), nArgs: 2 }, + deepOwnInclude: { scopeFn: createExprAdapter("deep.own.include"), nArgs: 2 }, + notDeepOwnInclude: { scopeFn: createExprAdapter("not.deep.own.include"), nArgs: 2 }, + // Numeric comparison operations isAbove: { scopeFn: aboveFunc, nArgs: 2 }, isNotAbove: { scopeFn: createExprAdapter("not", aboveFunc), nArgs: 2 }, @@ -344,6 +352,12 @@ export function createAssert(): IAssertClass { deepSubsequence: { scopeFn: deepSubsequenceFunc, nArgs: 2 }, notDeepSubsequence: { scopeFn: createExprAdapter("not", deepSubsequenceFunc), nArgs: 2 }, + // Keys operations (non-deep, uses strict equality) + hasAnyKeys: { scopeFn: createExprAdapter("has.any.keys"), nArgs: 2 }, + hasAllKeys: { scopeFn: createExprAdapter("has.all.keys"), nArgs: 2 }, + doesNotHaveAnyKeys: { scopeFn: createExprAdapter("not.has.any.keys"), nArgs: 2 }, + doesNotHaveAllKeys: { scopeFn: createExprAdapter("not.has.all.keys"), nArgs: 2 }, + // Deep keys operations hasAnyDeepKeys: { scopeFn: createExprAdapter("has.any.deep.keys"), nArgs: 2 }, hasAllDeepKeys: { scopeFn: createExprAdapter("has.all.deep.keys"), nArgs: 2 }, @@ -419,7 +433,7 @@ export function addAssertFuncs(target: any, funcs: { [key: string]: AssertClassD scopeFn: createExprAdapter(def) }; } else { - throw new AssertionError(`Invalid definition for ${name}: ${def}`, null, addAssertFuncs); + throw new AssertionError("Invalid definition for " + name + ": " + def, null, addAssertFuncs); } } else if (isString(def)) { theDef = { @@ -430,7 +444,7 @@ export function addAssertFuncs(target: any, funcs: { [key: string]: AssertClassD scopeFn: def }; } else if (!def) { - throw new AssertionError(`Invalid definition for ${name}: ${def}`, null, addAssertFuncs); + throw new AssertionError("Invalid definition for " + name + ": " + def, null, addAssertFuncs); } else { theDef = def; } @@ -494,7 +508,7 @@ function _createProxyFunc(theAssert: IAssertClass, assertName: string, def: IAss let numArgs: number = isNullOrUndefined(def.nArgs) ? 1 : def.nArgs; if (!isFunction(scopeFn)) { - throw new AssertionError(`Invalid definition for ${assertName}: ${dumpObj(def)}`, null, internalErrorStackStart); + throw new AssertionError("Invalid definition for " + assertName + ": " + dumpObj(def), null, internalErrorStackStart); } return function _assertFunc(): any { diff --git a/core/src/assert/funcs/changes.ts b/core/src/assert/funcs/changes.ts index 89e3d07..4ea460e 100644 --- a/core/src/assert/funcs/changes.ts +++ b/core/src/assert/funcs/changes.ts @@ -28,7 +28,7 @@ export function _getTargetValue(context: IScopeContext, name: string, t } else if (isObject(target as any)) { if (!(prop in (target as any))) { context.set("target", target); - context.fail("expected {target} to have {property} property"); + context.fatal("expected {target} to have {property} property"); } result = (target as any)[prop]; diff --git a/core/src/assert/funcs/closeTo.ts b/core/src/assert/funcs/closeTo.ts index bd8deac..8f4e585 100644 --- a/core/src/assert/funcs/closeTo.ts +++ b/core/src/assert/funcs/closeTo.ts @@ -39,17 +39,17 @@ export function closeToFunc(this: IAssertScope, expected: number, delta: numb // Validate that actual value is a number (and not NaN) if (!isNumber(value) || isNaN(value)) { - context.fail(evalMsg || "expected {value} to be a number"); + context.fatal(evalMsg || "expected {value} to be a number"); } // Validate that expected is a number (and not NaN) if (!isNumber(expected) || isNaN(expected)) { - context.fail(evalMsg || "the expected argument ({expected}) must be a number"); + context.fatal(evalMsg || "the expected argument ({expected}) must be a number"); } // Validate that delta is a number and not null/undefined or NaN if (isNullOrUndefined(delta) || !isNumber(delta) || isNaN(delta)) { - context.fail(evalMsg || "the delta argument ({delta}) is required and must be a number"); + context.fatal(evalMsg || "the delta argument ({delta}) is required and must be a number"); } // Calculate the absolute difference diff --git a/core/src/assert/funcs/equal.ts b/core/src/assert/funcs/equal.ts index ffcc2b6..3ca16f5 100644 --- a/core/src/assert/funcs/equal.ts +++ b/core/src/assert/funcs/equal.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -281,7 +281,7 @@ function _isVisiting(value: any, options: IEqualOptions, cb: () => T): T { if (visitCount > 10) { let errorMsg = "Unresolvable Circular reference detected for " + _formatValue(options.context, options.visiting[0]) + " @ depth " + options.visiting.length + " reference count: " + visitCount; if (options.context) { - options.context.fail(errorMsg); + options.context.fatal(errorMsg); } else { throw new Error(errorMsg); } diff --git a/core/src/assert/funcs/hasProperty.ts b/core/src/assert/funcs/hasProperty.ts index b71a60d..6b1014e 100644 --- a/core/src/assert/funcs/hasProperty.ts +++ b/core/src/assert/funcs/hasProperty.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -66,7 +66,7 @@ export function _assertHasProperty(context: IScopeContext, name: string | symbol context.set("property", name); if (!(isString(name) || isSymbol(name) || isNumber(name))) { - context.fail("expected {property} to be a string, symbol, or number"); + context.fatal("expected {property} to be a string, symbol, or number"); } if (isArray(value)) { @@ -87,7 +87,7 @@ function _assertHasOwnPropertyDescriptor(context: IScopeContext, name: string | context.set("propertyDescriptor", name); if (!(isString(name) || isSymbol(name) || isNumber(name))) { - context.fail("expected {propertyDescriptor} to be a string, symbol, or number"); + context.fatal("expected {propertyDescriptor} to be a string, symbol, or number"); } let desc = !isStrictNullOrUndefined(value) ? objGetOwnPropertyDescriptor(value, name) : null; @@ -141,7 +141,7 @@ export function hasPropertyFunc(this: IAssertScope, name: string | symbol | numb // - Negated: property doesn't exist OR value doesn't equal expected context.set("property", name); if (!(isString(name) || isSymbol(name) || isNumber(name))) { - context.fail("expected {property} to be a string, symbol, or number"); + context.fatal("expected {property} to be a string, symbol, or number"); } // First check if property exists (but don't fail the assertion yet) @@ -217,7 +217,7 @@ export function hasDeepPropertyFunc(this: IAssertScope, name: string | symbol | // When a value is provided, check property existence first without failing context.set("property", name); if (!(isString(name) || isSymbol(name) || isNumber(name))) { - context.fail("expected {property} to be a string, symbol, or number"); + context.fatal("expected {property} to be a string, symbol, or number"); } let target = context.value; diff --git a/core/src/assert/funcs/instanceOf.ts b/core/src/assert/funcs/instanceOf.ts index 49f229a..ee4552d 100644 --- a/core/src/assert/funcs/instanceOf.ts +++ b/core/src/assert/funcs/instanceOf.ts @@ -27,7 +27,7 @@ export function instanceOfFunc(this: IAssertScope, constructor: Function, eva // Validate that the constructor is a function if (!isFunction(constructor)) { - context.fail("The instanceOf check requires the constructor to be a function, got " + typeof constructor); + context.fatal("The instanceOf check requires the constructor to be a function, got " + typeof constructor); } let constructorName = constructor.name || "Anonymous"; diff --git a/core/src/assert/funcs/isEmpty.ts b/core/src/assert/funcs/isEmpty.ts index 0f508d8..4504e17 100644 --- a/core/src/assert/funcs/isEmpty.ts +++ b/core/src/assert/funcs/isEmpty.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -26,18 +26,18 @@ export function isEmptyFunc(this: IAssertScope, evalMsg: MsgSource): R { if (isArray(value) || isString(value)) { isEmpty = getLength(value) === 0; } else if (isPrimitive(value)) { - this.fail(evalMsg || "unsupported primitive {value}"); + this.fatal(evalMsg || "unsupported primitive {value}"); } else if (isFunction(value)) { - this.fail(evalMsg || "unsupported {value}"); + this.fatal(evalMsg || "unsupported {value}"); } else if (safeGet(() => "size" in value, false)) { isEmpty = value.size === 0; } else if (isWeakMap(value) || isWeakSet(value)) { // WeakMap and WeakSet do not have a size property, so we cannot check for emptiness directly. - this.fail(evalMsg || "unsupported type {value}"); + this.fatal(evalMsg || "unsupported type {value}"); } else if (isObject(value)) { isEmpty = objKeys(value).length === 0; } else { - this.fail(evalMsg || "unsupported value {value}"); + this.fatal(evalMsg || "unsupported value {value}"); } context.eval(isEmpty, evalMsg || "expected {value} to be empty"); diff --git a/core/src/assert/funcs/keysFunc.ts b/core/src/assert/funcs/keysFunc.ts index 2834dae..308855e 100644 --- a/core/src/assert/funcs/keysFunc.ts +++ b/core/src/assert/funcs/keysFunc.ts @@ -6,10 +6,9 @@ * Licensed under the MIT license. */ -import { arrForEach, arrFrom, arrSlice, asString, dumpObj, isArray, isMapLike, isObject, isSetLike, isStrictNullOrUndefined, isString, objGetOwnPropertyDescriptor, objGetOwnPropertySymbols, objKeys } from "@nevware21/ts-utils"; +import { arrForEach, arrFrom, arrSlice, isArray, isMapLike, isObject, isSetLike, isStrictNullOrUndefined, isString, objGetOwnPropertyDescriptor, objGetOwnPropertySymbols, objKeys } from "@nevware21/ts-utils"; import { IAssertScope } from "../interface/IAssertScope"; import { KeysFn } from "../interface/funcs/KeysFn"; -import { EMPTY_STRING } from "../internal/const"; import { _deepEqual } from "./equal"; import { _isArrayLikeOrIterable, _iterateForEachItem } from "../internal/_isArrayLikeOrIterable"; import { _isMsgSource } from "../internal/_isMsgSource"; @@ -30,34 +29,6 @@ function _objKeys(value: any): any[] { return keys; } -function _formatKeys(keys: any[]): string { - let formattedKeys: string = EMPTY_STRING; - arrForEach(keys, (key) => { - if (formattedKeys.length > 0) { - formattedKeys += ","; - } - if (key || isStrictNullOrUndefined(key)) { - try { - formattedKeys += asString(key); - } catch (e) { - // Handle objects that can't be converted to string (e.g., null prototype) - formattedKeys += dumpObj(key); - } - } else if (!isString(key)) { - try { - formattedKeys += asString(key); - } catch (e) { - formattedKeys += dumpObj(key); - } - } else { - // special case for empty string keys - formattedKeys += "\"\""; - } - }); - - return formattedKeys; -} - function _getValueKeys(value: any): any[] { let theKeys: any[] = []; if (!isStrictNullOrUndefined(value)) { @@ -167,7 +138,10 @@ export function anyKeysFunc(_scope: IAssertScope): KeysFn { } }); - context.eval(found, `expected any key: [${_formatKeys(expectedKeys)}], found: [${_formatKeys(valueKeys)}] (${valueKeys.length} keys)`); + context.set("expectedKeys", expectedKeys); + context.set("valueKeys", valueKeys); + + context.eval(found, "expected any key: {expectedKeys}, found: {valueKeys} (" + valueKeys.length + " keys)"); return scope.that; } @@ -196,7 +170,11 @@ export function allKeysFunc(_scope: IAssertScope): KeysFn { } }); - context.eval(missingKeys.length === 0, `expected all keys: [${_formatKeys(theKeys)}], missing: [${_formatKeys(missingKeys)}], found: [${_formatKeys(valueKeys)}]`); + context.set("theKeys", theKeys); + context.set("missingKeys", missingKeys); + context.set("valueKeys", valueKeys); + + context.eval(missingKeys.length === 0, "expected all keys: {theKeys}, missing: {missingKeys}, found: {valueKeys}"); return scope.that; } @@ -264,7 +242,10 @@ export function anyDeepKeysFunc(_scope: IAssertScope): KeysFn { } }); - context.eval(found, `expected any deep key: [${_formatKeys(expectedKeys)}], found: [${_formatKeys(valueKeys)}] (${valueKeys.length} keys)`); + context.set("expectedKeys", expectedKeys); + context.set("valueKeys", valueKeys); + + context.eval(found, "expected any deep key: {expectedKeys}, found: {valueKeys} (" + valueKeys.length + " keys)"); return scope.that; } @@ -334,7 +315,11 @@ export function allDeepKeysFunc(_scope: IAssertScope): KeysFn { } }); - context.eval(missingKeys.length === 0, `expected all deep keys: [${_formatKeys(theKeys)}], missing: [${_formatKeys(missingKeys)}], found: [${_formatKeys(valueKeys)}]`); + context.set("theKeys", theKeys); + context.set("missingKeys", missingKeys); + context.set("valueKeys", valueKeys); + + context.eval(missingKeys.length === 0, "expected all deep keys: {theKeys}, missing: {missingKeys}, found: {valueKeys}"); return scope.that; } diff --git a/core/src/assert/funcs/match.ts b/core/src/assert/funcs/match.ts index a0fd44b..a87567e 100644 --- a/core/src/assert/funcs/match.ts +++ b/core/src/assert/funcs/match.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. */ @@ -17,7 +17,7 @@ export function matchFunc(this: IAssertScope, match: RegExp, evalMsg?: MsgSou if (isRegExp(match)) { context.eval(match.test(context.value), evalMsg || "expected {value} to match {match}"); } else { - context.fail("expected {match} to be a Regexp"); + context.fatal("expected {match} to be a Regexp"); } return this.that; diff --git a/core/src/assert/funcs/members.ts b/core/src/assert/funcs/members.ts index 3dba8a9..ef20616 100644 --- a/core/src/assert/funcs/members.ts +++ b/core/src/assert/funcs/members.ts @@ -19,11 +19,11 @@ function _checkActualAndExpected(context: IScopeContext, value: any, expected: a // Validate that both values conform to ArrayLikeOrSizedIterable if (!_isArrayLikeOrIterable(value)) { - context.fail(evalMsg || "expected {value} to be an array-like or sized iterable"); + context.fatal(evalMsg || "expected {value} to be an array-like or sized iterable"); } if (!_isArrayLikeOrIterable(expected)) { - context.fail(evalMsg || "expected argument ({expected}) to be an array-like or sized iterable"); + context.fatal(evalMsg || "expected argument ({expected}) to be an array-like or sized iterable"); } } diff --git a/core/src/assert/funcs/nested.ts b/core/src/assert/funcs/nested.ts index a7f0b27..6573eda 100644 --- a/core/src/assert/funcs/nested.ts +++ b/core/src/assert/funcs/nested.ts @@ -116,7 +116,7 @@ export function hasNestedPropertyFunc(this: IAssertScope, path: string, value?: context.set("property", path); if (!isString(path)) { - context.fail(evalMsg || "expected {property} to be a string using nested syntax"); + context.fatal(evalMsg || "expected {property} to be a string using nested syntax"); } let valueResult = _getNestedProperty(context.value, path); @@ -157,7 +157,7 @@ export function hasDeepNestedPropertyFunc(this: IAssertScope, path: string, valu context.set("property", path); if (!isString(path)) { - context.fail(evalMsg || "expected {property} to be a string using nested syntax"); + context.fatal(evalMsg || "expected {property} to be a string using nested syntax"); } let valueResult = _getNestedProperty(context.value, path); diff --git a/core/src/assert/funcs/oneOf.ts b/core/src/assert/funcs/oneOf.ts index 3cc29d2..2b28f36 100644 --- a/core/src/assert/funcs/oneOf.ts +++ b/core/src/assert/funcs/oneOf.ts @@ -38,7 +38,7 @@ export function oneOfFunc(this: IAssertScope, list: ArrayLikeOrSizedIterable< // Validate that list is ArrayLike or iterable if (!_isArrayLikeOrIterable(list)) { - context.fail(evalMsg || "the list argument ({list}) must be an array, ArrayLike, or iterable with size"); + context.fatal(evalMsg || "the list argument ({list}) must be an array, ArrayLike, or iterable with size"); } let found = false; diff --git a/core/src/assert/funcs/operator.ts b/core/src/assert/funcs/operator.ts index bba60e8..5f99f82 100644 --- a/core/src/assert/funcs/operator.ts +++ b/core/src/assert/funcs/operator.ts @@ -83,7 +83,7 @@ export function operatorFunc(this: IAssertScope, operator: string, expected: break; default: context.set("operator", operator); - context.fail(evalMsg || "Invalid operator {operator}"); + context.fatal(evalMsg || "Invalid operator {operator}"); return this.that; } diff --git a/core/src/assert/funcs/valuesFunc.ts b/core/src/assert/funcs/valuesFunc.ts index c65c646..bf19e60 100644 --- a/core/src/assert/funcs/valuesFunc.ts +++ b/core/src/assert/funcs/valuesFunc.ts @@ -2,48 +2,18 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2025 NevWare21 Solutions LLC + * Copyright (c) 2025-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ import { - arrForEach, arrIndexOf, arrSlice, asString, isArray, isIterable, isIterator, isObject, + arrForEach, arrIndexOf, arrSlice, isArray, isIterable, isIterator, isObject, isStrictNullOrUndefined, isString, iterForOf, objForEachKey, strIndexOf } from "@nevware21/ts-utils"; import { IAssertScope } from "../interface/IAssertScope"; import { EMPTY_STRING } from "../internal/const"; import { ValuesFn } from "../interface/funcs/ValuesFn"; -function _formatValues(values: any): string { - let formattedValues: string = EMPTY_STRING; - if (isArray(values)) { - arrForEach(values, (key) => { - if (formattedValues.length > 0) { - formattedValues += ","; - } - if (key || isStrictNullOrUndefined(key)) { - formattedValues += asString(key); - } else if (!isString(key)) { - formattedValues += asString(key); - } else { - // special case for empty string - formattedValues += "\"\""; - } - }); - } else { - if (values || isStrictNullOrUndefined(values)) { - formattedValues += asString(values); - } else if (!isString(values)) { - formattedValues += asString(values); - } else { - // special case for empty string - formattedValues += "\"\""; - } - } - - return formattedValues; -} - function _getCtxValues(scope: IAssertScope, value: any): any[] { let theValues: any[] = []; if (!isStrictNullOrUndefined(value)) { @@ -117,7 +87,11 @@ export function anyValuesFunc(_scope: IAssertScope): ValuesFn { } }); - context.eval(found, `expected any value: [${_formatValues(expectedValues)}], found: [${_formatValues(checkValue)}] (${checkLength} value${checkLength > 1 ? "s" : EMPTY_STRING})`); + context.set("expectedValues", expectedValues); + context.set("checkValues", checkValue); + context.set("checkLength", checkLength); + + context.eval(found, "expected any value: {expectedValues}, found: {checkValues} ({checkLength} value" + (checkLength > 1 ? "s" : EMPTY_STRING) + ")"); return scope.that; } @@ -161,7 +135,12 @@ export function allValuesFunc(_scope: IAssertScope): ValuesFn { } }); - context.eval(missingValues.length === 0, `expected all values: [${_formatValues(expectedValues)}], missing: [${_formatValues(missingValues)}], found: [${_formatValues(checkValue)}] (${checkLength} value${checkLength > 1 ? "s" : EMPTY_STRING})`); + context.set("expectedValues", expectedValues); + context.set("missingValues", missingValues); + context.set("checkValue", checkValue); + context.set("checkLength", checkLength); + + context.eval(missingValues.length === 0, "expected all values: {expectedValues}, missing: {missingValues}, found: {checkValue} ({checkLength} value" + (checkLength > 1 ? "s" : EMPTY_STRING) + ")"); return scope.that; } diff --git a/core/src/assert/interface/IAssertClass.ts b/core/src/assert/interface/IAssertClass.ts index 8cbf5d2..3677476 100644 --- a/core/src/assert/interface/IAssertClass.ts +++ b/core/src/assert/interface/IAssertClass.ts @@ -1443,6 +1443,45 @@ export interface IAssertClass { * ``` */ throws(fn: () => void, msgMatch?: string | RegExp | null, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the given function does NOT throw an error. Optionally checks that if an error + * is thrown, it does not match the specified error constructor or message pattern. + * If the function throws an error (that matches the optional criteria), it throws an + * {@link AssertionFailure} with the given message. This is the inverse of {@link throws}. + * @param fn - The function to execute and check for thrown errors. + * @param errorLike - Optional. The error constructor or error instance that should NOT be thrown. + * @param msgMatch - Optional. The partial message or pattern that should NOT match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the function does not throw an error (or does not throw a matching error) and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * assert.doesNotThrow(() => {}); // Passes - no error thrown + * assert.doesNotThrow(() => { return 42; }); // Passes - no error thrown + * assert.doesNotThrow(() => { throw new Error("test"); }); // Throws AssertionFailure + * assert.doesNotThrow(() => { throw new TypeError("test"); }); // Throws AssertionFailure + * ``` + */ + doesNotThrow(fn: () => void, errorLike?: ErrorConstructor | Error | null, msgMatch?: string | RegExp | null, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the given function does NOT throw an error with a message matching the pattern. + * If the function throws an error with a matching message, it throws an {@link AssertionFailure} + * with the given message. This is the inverse of {@link throws}. + * @param fn - The function to execute and check for thrown errors. + * @param msgMatch - Optional. The partial message or pattern that should NOT match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the function does not throw an error with a matching message and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * assert.doesNotThrow(() => {}, /test/); // Passes - no error thrown + * assert.doesNotThrow(() => { throw new Error("other"); }, /test/); // Passes - message doesn't match + * assert.doesNotThrow(() => { throw new Error("test"); }, /test/); // Throws AssertionFailure + * ``` + */ + doesNotThrow(fn: () => void, msgMatch?: string | RegExp | null, initMsg?: MsgSource): AssertInst; /** * Asserts that the provided value matches the specified regular expression. @@ -1463,6 +1502,26 @@ export interface IAssertClass { */ match(value: T, regexp: RegExp, initMsg?: MsgSource): AssertInst; + /** + * Asserts that the provided value does NOT match the specified regular expression. + * If the value matches the regular expression, it throws an {@link AssertionFailure} + * with the given message. This is the inverse of {@link match}. + * + * @param value - The value to evaluate. + * @param regexp - The regular expression that should not match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `value` does not match the regular expression and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * assert.notMatch("hello world", /nomatch/); // Passes + * assert.notMatch("hello world", /goodbye/); // Passes + * assert.notMatch("hello world", /hello/); // Throws AssertionFailure + * assert.notMatch("hello world", /world$/); // Throws AssertionFailure + * ``` + */ + notMatch(value: T, regexp: RegExp, initMsg?: MsgSource): AssertInst; + /** * Asserts that the provided value includes the specified `match`. The value can be * a string, array, or any other type that supports the `includes` method. If the @@ -1797,6 +1856,100 @@ export interface IAssertClass { */ notDeepNestedInclude(value: T, expected: any, initMsg?: MsgSource): AssertInst; + /** + * Asserts that the value contains the expected using own property matching only. + * For objects, checks that all keys in the expected are own properties (not inherited) + * and that values match using strict equality (===). + * + * @param value - The object to search in. + * @param expected - The value or object with properties to match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `value` contains the `expected` using own property matching and throws {@link AssertionFailure} if it does not. + * @since 0.1.5 + * @example + * ```typescript + * assert.ownInclude({ a: 1, b: 2 }, { a: 1 }); // Passes + * assert.ownInclude({ a: 1, b: 2 }, { a: 1, b: 2 }); // Passes + * assert.ownInclude({ a: 1 }, { a: 2 }); // Throws AssertionFailure + * // Inherited properties are not matched: + * const obj = Object.create({ inherited: 1 }); + * obj.own = 2; + * assert.ownInclude(obj, { own: 2 }); // Passes + * assert.ownInclude(obj, { inherited: 1 }); // Throws AssertionFailure + * ``` + */ + ownInclude(value: T, expected: any, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the value does NOT contain the expected using own property matching. + * For objects, checks that not all keys in the expected are own properties (not inherited) + * or that values don't match using strict equality (===). + * + * @param value - The object to search in. + * @param expected - The value or object with properties to match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `value` does not contain the `expected` using own property matching and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * assert.notOwnInclude({ a: 1 }, { a: 2 }); // Passes + * assert.notOwnInclude({ a: 1 }, { b: 1 }); // Passes + * assert.notOwnInclude({ a: 1 }, { a: 1 }); // Throws AssertionFailure + * // Inherited properties are not matched: + * const obj = Object.create({ inherited: 1 }); + * obj.own = 2; + * assert.notOwnInclude(obj, { inherited: 1 }); // Passes + * ``` + */ + notOwnInclude(value: T, expected: any, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the value contains the expected using deep own property matching. + * For objects, checks that all keys in the expected are own properties (not inherited) + * and that values match using deep equality. + * + * @param value - The object to search in. + * @param expected - The value or object with properties to match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `value` deeply contains the `expected` using own property matching and throws {@link AssertionFailure} if it does not. + * @since 0.1.5 + * @example + * ```typescript + * assert.deepOwnInclude({ a: { b: 1 } }, { a: { b: 1 } }); // Passes + * assert.deepOwnInclude({ a: [1, 2, 3] }, { a: [1, 2, 3] }); // Passes + * assert.deepOwnInclude({ a: { b: 1 } }, { a: { b: 2 } }); // Throws AssertionFailure + * // Inherited properties are not matched: + * const obj = Object.create({ inherited: { deep: 1 } }); + * obj.own = { deep: 2 }; + * assert.deepOwnInclude(obj, { own: { deep: 2 } }); // Passes + * assert.deepOwnInclude(obj, { inherited: { deep: 1 } }); // Throws AssertionFailure + * ``` + */ + deepOwnInclude(value: T, expected: any, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the value does NOT contain the expected using deep own property matching. + * For objects, checks that not all keys in the expected are own properties (not inherited) + * or that values don't match using deep equality. + * + * @param value - The object to search in. + * @param expected - The value or object with properties to match. + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `value` does not deeply contain the `expected` using own property matching and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * assert.notDeepOwnInclude({ a: { b: 1 } }, { a: { b: 2 } }); // Passes + * assert.notDeepOwnInclude({ a: 1 }, { b: 1 }); // Passes + * assert.notDeepOwnInclude({ a: { b: 1 } }, { a: { b: 1 } }); // Throws AssertionFailure + * // Inherited properties are not matched: + * const obj = Object.create({ inherited: { deep: 1 } }); + * obj.own = { deep: 2 }; + * assert.notDeepOwnInclude(obj, { inherited: { deep: 1 } }); // Passes + * ``` + */ + notDeepOwnInclude(value: T, expected: any, initMsg?: MsgSource): AssertInst; + /** * Asserts that the given value is greater than the expected value. * @@ -3386,6 +3539,99 @@ export interface IAssertClass { */ decreasesButNotBy(fn: () => void, target: T, prop: keyof T, delta: number, initMsg?: MsgSource): AssertInst; + /** + * Asserts that the target has any of the specified keys. + * This method checks if at least one of the given keys exists in the target. + * Works with objects, Maps, and Sets. + * + * @param target - The object, Map, or Set to check for keys. + * @param keys - The keys to search for (array, Set, Map, or other iterable of keys, or a single key). + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `target` has at least one of the specified keys and throws {@link AssertionFailure} if it does not. + * @since 0.1.5 + * @example + * ```typescript + * const obj = { greeting: "hello", subject: "friend" }; + * assert.hasAnyKeys(obj, "greeting"); // Passes - has "greeting" + * assert.hasAnyKeys(obj, ["greeting", "message"]); // Passes - has "greeting" + * assert.hasAnyKeys(obj, ["unknown", "missing"]); // Throws AssertionFailure + * + * const map = new Map([["a", 1], ["b", 2]]); + * assert.hasAnyKeys(map, "a"); // Passes - has "a" + * assert.hasAnyKeys(map, ["a", "c"]); // Passes - has "a" + * ``` + */ + hasAnyKeys(target: any, keys: ArrayLikeOrSizedIterable | any, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the target has all of the specified keys. + * This method checks if all of the given keys exist in the target. + * Works with objects, Maps, and Sets. + * + * @param target - The object, Map, or Set to check for keys. + * @param keys - The keys to search for (array, Set, Map, or other iterable of keys, or a single key). + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `target` has all of the specified keys and throws {@link AssertionFailure} if it does not. + * @since 0.1.5 + * @example + * ```typescript + * const obj = { greeting: "hello", subject: "friend", message: "darkness" }; + * assert.hasAllKeys(obj, "greeting"); // Passes - has the key + * assert.hasAllKeys(obj, ["greeting", "subject"]); // Passes - has both keys + * assert.hasAllKeys(obj, ["greeting", "unknown"]); // Throws AssertionFailure - missing "unknown" + * + * const map = new Map([["a", 1], ["b", 2]]); + * assert.hasAllKeys(map, ["a", "b"]); // Passes - has both keys + * ``` + */ + hasAllKeys(target: any, keys: ArrayLikeOrSizedIterable | any, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the target does NOT have any of the specified keys. + * This method checks that none of the given keys exist in the target. + * This is the inverse of {@link hasAnyKeys}. + * + * @param target - The object, Map, or Set to check for keys. + * @param keys - The keys to search for (array, Set, Map, or other iterable of keys, or a single key). + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `target` does not have any of the specified keys and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * const obj = { greeting: "hello", subject: "friend" }; + * assert.doesNotHaveAnyKeys(obj, "unknown"); // Passes - does not have the key + * assert.doesNotHaveAnyKeys(obj, ["unknown", "missing"]); // Passes - has neither key + * assert.doesNotHaveAnyKeys(obj, ["greeting", "unknown"]); // Throws AssertionFailure - has "greeting" + * + * const map = new Map([["a", 1], ["b", 2]]); + * assert.doesNotHaveAnyKeys(map, ["c", "d"]); // Passes - has neither key + * ``` + */ + doesNotHaveAnyKeys(target: any, keys: ArrayLikeOrSizedIterable | any, initMsg?: MsgSource): AssertInst; + + /** + * Asserts that the target does NOT have all of the specified keys. + * This method checks that at least one of the given keys does not exist in the target. + * This is the inverse of {@link hasAllKeys}. + * + * @param target - The object, Map, or Set to check for keys. + * @param keys - The keys to search for (array, Set, Map, or other iterable of keys, or a single key). + * @param initMsg - The message to display if the assertion fails. + * @asserts That the `target` does not have all of the specified keys and throws {@link AssertionFailure} if it does. + * @since 0.1.5 + * @example + * ```typescript + * const obj = { greeting: "hello", subject: "friend" }; + * assert.doesNotHaveAllKeys(obj, "unknown"); // Passes - missing "unknown" + * assert.doesNotHaveAllKeys(obj, ["greeting", "unknown"]); // Passes - missing "unknown" + * assert.doesNotHaveAllKeys(obj, ["greeting", "subject"]); // Throws AssertionFailure - has both keys + * + * const map = new Map([["a", 1], ["b", 2]]); + * assert.doesNotHaveAllKeys(map, ["a", "c"]); // Passes - missing "c" + * ``` + */ + doesNotHaveAllKeys(target: any, keys: ArrayLikeOrSizedIterable | any, initMsg?: MsgSource): AssertInst; + /** * Asserts that the target has any of the specified keys using deep equality comparison. * This method checks if at least one of the given keys exists in the target using deep equality diff --git a/core/src/assert/interface/IAssertInst.ts b/core/src/assert/interface/IAssertInst.ts index 303f526..e0bd71f 100644 --- a/core/src/assert/interface/IAssertInst.ts +++ b/core/src/assert/interface/IAssertInst.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. */ @@ -144,7 +144,7 @@ export interface IAssertInst extends INotOp, IAssertInstCore, IEqua * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).include(2); // Passes @@ -170,7 +170,7 @@ export interface IAssertInst extends INotOp, IAssertInstCore, IEqua * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).includes(2); // Passes @@ -195,7 +195,7 @@ export interface IAssertInst extends INotOp, IAssertInstCore, IEqua * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).contain(2); // Passes @@ -220,7 +220,7 @@ export interface IAssertInst extends INotOp, IAssertInstCore, IEqua * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).contains(2); // Passes diff --git a/core/src/assert/interface/IFormatter.ts b/core/src/assert/interface/IFormatter.ts index 9216a28..86a2dad 100644 --- a/core/src/assert/interface/IFormatter.ts +++ b/core/src/assert/interface/IFormatter.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2025 NevWare21 Solutions LLC + * Copyright (c) 2025-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -258,6 +258,7 @@ export interface IFormatterOptions { finalizeFn?: (value: string) => string; /** + * @deprecated This property is deprecated and will be replaced and removed in future releases. * Custom value formatter functions to be used for formatting values in error messages. * These formatters will be checked before the default formatters. Each formatter should * implement `value` which returns an {@link IFormattedValue} object indicating whether diff --git a/core/src/assert/interface/IScopeContext.ts b/core/src/assert/interface/IScopeContext.ts index 99be3de..96d13ef 100644 --- a/core/src/assert/interface/IScopeContext.ts +++ b/core/src/assert/interface/IScopeContext.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. */ @@ -82,6 +82,18 @@ export interface IScopeContext { */ fail(msg: MsgSource, details?: any, stackStart?: Function | Function[]): never; + /** + * Throws an {@link AssertionFatal} exception with the given message and optional + * details which are obtained via the `getDetails` function. This indicates a fatal + * error that should stop further processing, it also bypasses any child scopes for + * message processing. + * @param msg - The message to display. + * @param details - The details (props) to include in the error + * @param stackStart - The optional stack start function + * @throws {@link AssertionFatal} always + */ + fatal(msg: MsgSource, details?: any, stackStart?: Function | Function[]): never; + /** * Sets the current operation name and adds it to the context execution * order, which is used to display the order of operations in the error message diff --git a/core/src/assert/interface/ops/IToOp.ts b/core/src/assert/interface/ops/IToOp.ts index 59d1e0f..e5a05ef 100644 --- a/core/src/assert/interface/ops/IToOp.ts +++ b/core/src/assert/interface/ops/IToOp.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. */ @@ -101,7 +101,7 @@ export interface IToOp extends INotOp> { * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).include(2); // Passes @@ -127,7 +127,7 @@ export interface IToOp extends INotOp> { * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).includes(2); // Passes @@ -152,7 +152,7 @@ export interface IToOp extends INotOp> { * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).contain(2); // Passes @@ -177,7 +177,7 @@ export interface IToOp extends INotOp> { * import { assert } from "@nevware21/tripwire"; * * const arr = [1, 2, 3]; - * const str = "hello darkness my old friend"; + * const str = "hello darkness welcome back again"; * const obj = { a: 1, b: 2, c: 3 }; * * assert(arr).contains(2); // Passes diff --git a/core/src/assert/internal/_addAssertInstFuncs.ts b/core/src/assert/internal/_addAssertInstFuncs.ts index ef0b5ce..84fa2a6 100644 --- a/core/src/assert/internal/_addAssertInstFuncs.ts +++ b/core/src/assert/internal/_addAssertInstFuncs.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. */ @@ -34,7 +34,7 @@ export function _addAssertInstFuncs(target: any, funcs: AssertScopeFuncDefs { + let result: IFormattedValue; + if (value && isSetLike(value)) { + let resultStr = "Set:{"; + let first = true; + try { + iterForOf(value, (item: any) => { + resultStr += (first ? EMPTY_STRING : ",") + ctx.format(item); + first = false; + }); + } catch (e) { + resultStr += "..."; + } + + resultStr += "}"; + + result = { + res: eFormatResult.Ok, + val: resultStr + }; + } + + return result; + } +}; + +/** + * @internal + * @ignore + * Default formatter for plain object values + */ +const _defaultMapFormatter: IFormatter = { + name: "Map", + value: (ctx: IFormatCtx, value: any) => { + let result: IFormattedValue; + if (isMapLike(value)) { + let theValue = "Map:{"; + let idx = 0; + iterForOf(value.keys(), (key: any) => { + let formattedValue = ctx.format(value.get(key)); + + if (isSymbol(key)) { + theValue += (idx > 0 ? "," : EMPTY_STRING) + "[" + asString(key) + "]:" + formattedValue; + } else if (isString(key) && strIndexOf(key, " ") >= 0) { + theValue += (idx > 0 ? "," : EMPTY_STRING) + "\"" + key + "\":" + formattedValue; + } else { + theValue += (idx > 0 ? "," : EMPTY_STRING) + asString(key) + ":" + formattedValue; + } + + idx++; + }); + + theValue += "}"; + + result = { + res: eFormatResult.Ok, + val: theValue + }; + } + + return result; + } +}; + + /** * @internal * @ignore @@ -295,6 +363,8 @@ const _defaultFormatters: IFormatter[] = [ _defaultErrorFormatter, _defaultErrorTypeFormatter, _defaultFunctionFormatter, + _defaultSetFormatter, + _defaultMapFormatter, _defaultConstructorFormatter, _defaultToStringFormatter, _defaultFallbackFormatter diff --git a/core/src/assert/ops/changeResultOp.ts b/core/src/assert/ops/changeResultOp.ts index 7ac4412..f8a1603 100644 --- a/core/src/assert/ops/changeResultOp.ts +++ b/core/src/assert/ops/changeResultOp.ts @@ -29,11 +29,11 @@ function byOpFn(this: IAssertScope, delta: number, evalMsg?: MsgSource): R { context.set("delta", actualDelta); if (!isNumber(delta)) { - context.fail("expected delta ({expectedDelta}) to be a number"); + context.fatal("expected delta ({expectedDelta}) to be a number"); } if (!isNumber(actualDelta)) { - context.fail("expected actual delta ({delta}) to be a number"); + context.fatal("expected actual delta ({delta}) to be a number"); } context.eval( diff --git a/core/src/assert/ops/includeOp.ts b/core/src/assert/ops/includeOp.ts index fac65bf..610592d 100644 --- a/core/src/assert/ops/includeOp.ts +++ b/core/src/assert/ops/includeOp.ts @@ -6,15 +6,44 @@ * Licensed under the MIT license. */ -import { arrIndexOf, isArray, isFunction, isString, objKeys } from "@nevware21/ts-utils"; +import { + arrIndexOf, isArray, isFunction, isString, objKeys, objHasOwnProperty, isObject, arrForEach, isMap, isSet, isPrimitive, + iterForOf, isWeakSet, isWeakMap +} from "@nevware21/ts-utils"; import { MsgSource } from "../type/MsgSource"; import { allOp, anyOp } from "./allOp"; -import { IAssertInst, AssertScopeFuncDefs } from "../interface/IAssertInst"; +import { IAssertInst, AssertScopeFuncDefs as AssertScopeDefs } from "../interface/IAssertInst"; import { IIncludeOp } from "../interface/ops/IIncludeOp"; import { IAssertScope } from "../interface/IAssertScope"; -import { includeDeepMembersFunc, includeDeepOrderedMembersFunc, includeMembersFunc, includeOrderedMembersFunc, sameDeepMembersFunc, sameDeepOrderedMembersFunc, sameMembersFunc, sameOrderedMembersFunc, startsWithMembersFunc, startsWithDeepMembersFunc, endsWithMembersFunc, endsWithDeepMembersFunc, subsequenceFunc, deepSubsequenceFunc } from "../funcs/members"; +import { + includeDeepMembersFunc, includeDeepOrderedMembersFunc, includeMembersFunc, includeOrderedMembersFunc, sameDeepMembersFunc, + sameDeepOrderedMembersFunc, sameMembersFunc, sameOrderedMembersFunc, startsWithMembersFunc, startsWithDeepMembersFunc, + endsWithMembersFunc, endsWithDeepMembersFunc, subsequenceFunc, deepSubsequenceFunc +} from "../funcs/members"; import { _deepEqual } from "../funcs/equal"; +/** + * Helper function to compare values for Map include checks. + * Uses === for regular values but special-cases NaN (NaN should equal NaN). + * Does NOT special-case 0/-0 (they should be treated as equal per Chai behavior). + */ +function _mapValueEquals(a: any, b: any): boolean { + // Special case for NaN + if (a !== a && b !== b) { // Both are NaN + return true; + } + + return a === b; +} + +function _checkSupportedValueType(scope: IAssertScope, value: any): void { + + if (!isString(value) && isPrimitive(value)) { + let context = scope.context; + context.fatal("argument {value} ({typeof(value)}) is not a supported collection type for the operation."); + } +} + /** * Creates the include assertion operation using the given scope. * This includes the primary include function and any additional properties @@ -28,25 +57,54 @@ export function includeOp(scope: IAssertScope): IIncludeOp { let scope = this; let context = scope.context; let value = context.value; + let found = false; + + _checkSupportedValueType(scope, value); context.set("match", match); if (isString(value)) { - context.eval(value.indexOf(match) > -1, evalMsg || "expected {value} to include {match}"); + found = value.indexOf(match) > -1; } else if (isArray(value)) { - context.eval(arrIndexOf(value, match) > -1, evalMsg || "expected {value} to include {match}"); - } else if (value && isFunction(value.has)) { - // Looks like a set or map - context.eval(value.has(match), evalMsg || "expected {value} to include {match}"); + found = arrIndexOf(value, match) > -1; + } else if (isMap(value)) { + // For Maps, check if any value in the map equals match + iterForOf(value.keys(), (v: any) => { + if (_mapValueEquals(value.get(v), match)) { + found = true; + return -1; + } + }); + } else if (isSet(value) || (value && isFunction(value.has))) { + // For Sets and other collections with .has(), check membership + found = value.has(match); + } else if (match && isObject(match) && value && isObject(value)) { + // For objects, check if all properties in match exist in value with same values + let matchKeys = objKeys(match); + found = true; + + arrForEach(matchKeys, (key) => { + if (!objHasOwnProperty(value, key) && !(key in value)) { + found = false; + return -1; // break + } + if (value[key] !== match[key]) { + found = false; + return -1; // break + } + }); } else { let keys = value ? objKeys(value) : []; context.set("keys", keys); - context.eval(arrIndexOf(keys, match) > -1, evalMsg || "expected {value} to have a {match} property"); + found = arrIndexOf(keys, match) > -1; + evalMsg = evalMsg || "expected {value} to have a {match} property"; } + context.eval(found, evalMsg || "expected {value} to include {match}"); + return scope.newInst(); } - const props: AssertScopeFuncDefs> = { + const props: AssertScopeDefs> = { any: { propFn: anyOp }, all: { propFn: allOp }, members: { scopeFn: includeMembersFunc }, @@ -74,35 +132,73 @@ export function deepIncludeOp(scope: IAssertScope): IIncludeOp { function _deepIncludes(this: IAssertScope, match: any, evalMsg?: MsgSource): R { let scope = this; let context = scope.context; - let value = context.value; + let found = false; + + _checkSupportedValueType(scope, value); context.set("match", match); + if (isString(context.value)) { - context.eval(value.indexOf(match) > -1, evalMsg || "expected {value} to include {match}"); + found = value.indexOf(match) > -1; } else if (isArray(context.value)) { // Check if any item in the array deeply equals match - let found = false; - for (let i = 0; i < value.length; i++) { - if (_deepEqual(value[i], match)) { + arrForEach(value, (item) => { + if (_deepEqual(item, match)) { found = true; - break; + return -1; // break } - } - context.eval(found, evalMsg || "expected {value} to include {match}"); + }); + } else if (isMap(value)) { + // For Maps, check if any value in the map deeply equals match + iterForOf(value.keys(), (v: any) => { + if (_deepEqual(value.get(v), match)) { + found = true; + return -1; + } + }); + } else if (isSet(value)) { + // For Sets, iterate and check deep equality for each item + iterForOf(value, (v: any) => { + if (_deepEqual(v, match)) { + found = true; + return -1; + } + }); + } else if (isWeakSet(value) || isWeakMap(value)) { + // Cannot iterate WeakSet, so we cannot perform deep include + context.fatal("argument {value} ({typeof(value)}) cannot be used for deep include operation."); } else if (value && isFunction(value.has)) { - // Looks like a set or map - context.eval(value.has(match), evalMsg || "expected {value} to include {match}"); + // For other collections with .has(), check membership + found = value.has(match); + } else if (match && isObject(match) && (!value || !isFunction(value.has))) { + // For object matching, check if all properties in match exist in value with deep equality + let matchKeys = objKeys(match); + found = true; + + arrForEach(matchKeys, (key) => { + if (!objHasOwnProperty(value, key) && !(key in value)) { + found = false; + return -1; // break + } + if (!_deepEqual(value[key], match[key])) { + found = false; + return -1; // break + } + }); } else { let keys = value ? objKeys(value) : []; context.set("keys", keys); - context.eval(arrIndexOf(keys, match) > -1, evalMsg || "expected {value} to have a {match} property"); + found = arrIndexOf(keys, match) > -1; + evalMsg = evalMsg || "expected {value} to have a deep {match} property"; } + context.eval(found, evalMsg || "expected {value} to deep include {match}"); + return scope.that; } - const props: AssertScopeFuncDefs> = { + const props: AssertScopeDefs> = { any: { propFn: anyOp }, all: { propFn: allOp }, members: { scopeFn: includeDeepMembersFunc }, @@ -115,4 +211,164 @@ export function deepIncludeOp(scope: IAssertScope): IIncludeOp { }; return scope.createOperation(props, _deepIncludes); +} + +/** + * Creates the own property include assertion operation using the given scope. + * This checks if an object contains the specified properties as own properties (not inherited). + * For objects, checks that all keys in the match are own properties and values match using strict equality. + * @since 0.1.5 + * @param scope - The current scope of the assert + * @returns - The {@link IIncludeOp} operation instance. + */ +export function ownIncludeOp(scope: IAssertScope): IIncludeOp { + + function _ownIncludes(this: IAssertScope, match: any, evalMsg?: MsgSource): IAssertInst { + let scope = this; + let context = scope.context; + let value = context.value; + let found = false; + + _checkSupportedValueType(scope, value); + + context.set("match", match); + if (isString(value)) { + found = value.indexOf(match) > -1; + } else if (isArray(value)) { + found = arrIndexOf(value, match) > -1; + } else if (isMap(value)) { + // For Maps, check if any value in the map equals match + iterForOf(value.keys(), (v: any) => { + if (_mapValueEquals(value.get(v), match)) { + found = true; + return -1; + } + }); + } else if (isSet(value) || (value && isFunction(value.has))) { + // For Sets and other collections with .has(), check membership + found = value.has(match); + } else if (match && isObject(match)) { + // For object matching, check own properties only + let matchKeys = objKeys(match); + found = true; + + arrForEach(matchKeys, (key) => { + if (!objHasOwnProperty(value, key) || value[key] !== match[key]) { + found = false; + return -1; // break + } + }); + + evalMsg = evalMsg || "expected {value} to have own properties matching {match}"; + } else { + let keys = value ? objKeys(value) : []; + + context.set("keys", keys); + found = objHasOwnProperty(value, match); + evalMsg = evalMsg || "expected {value} to have own {match} property"; + } + + context.eval(found, evalMsg || "expected {value} to include own {match}"); + + return scope.newInst(); + } + + const props: AssertScopeDefs> = { + any: { propFn: anyOp }, + all: { propFn: allOp }, + members: { scopeFn: includeMembersFunc }, + orderedMembers: { scopeFn: includeOrderedMembersFunc }, + sameMembers: { scopeFn: sameMembersFunc }, + sameOrderedMembers: { scopeFn: sameOrderedMembersFunc }, + startsWithMembers: { scopeFn: startsWithMembersFunc }, + endsWithMembers: { scopeFn: endsWithMembersFunc }, + subsequence: { scopeFn: subsequenceFunc } + }; + + return scope.createOperation(props, _ownIncludes); +} + +/** + * Creates the deep own property include assertion operation using the given scope. + * This checks if an object contains the specified properties as own properties (not inherited). + * For objects, checks that all keys in the match are own properties and values match using deep equality. + * @since 0.1.5 + * @param scope - The current scope of the assert + * @returns - The {@link IIncludeOp} operation instance. + */ +export function deepOwnIncludeOp(scope: IAssertScope): IIncludeOp { + + function _deepOwnIncludes(this: IAssertScope, match: any, evalMsg?: MsgSource): R { + let scope = this; + let context = scope.context; + let value = context.value; + let found = false; + + _checkSupportedValueType(scope, value); + + context.set("match", match); + if (isString(context.value)) { + found = value.indexOf(match) > -1; + } else if (isArray(context.value)) { + // Check if any item in the array deeply equals match + arrForEach(value, (item) => { + if (_deepEqual(item, match)) { + found = true; + return -1; // break + } + }); + } else if (isMap(value)) { + // For Maps, check if any value in the map deeply equals match + iterForOf(value.keys(), (v: any) => { + if (_deepEqual(value.get(v), match)) { + found = true; + return -1; + } + }); + } else if (isSet(value) || (value && isFunction(value.has))) { + // For Sets, deeply compare each entry against match + iterForOf(value, (entry: any) => { + if (_deepEqual(entry, match)) { + found = true; + return -1; // break + } + }); + } else if (match && typeof match === "object") { + // For object matching, check own properties only with deep equality + let matchKeys = objKeys(match); + found = true; + + arrForEach(matchKeys, (key) => { + if (!objHasOwnProperty(value, key) || !_deepEqual(value[key], match[key])) { + found = false; + return -1; // break + } + }); + + evalMsg = evalMsg || "expected {value} to have own properties deeply matching {match}"; + } else { + let keys = value ? objKeys(value) : []; + context.set("keys", keys); + found = objHasOwnProperty(value, match); + evalMsg = evalMsg || "expected {value} to have own deep {match} property"; + } + + context.eval(found, evalMsg || "expected {value} to deep include own {match}"); + + return scope.that; + } + + const props: AssertScopeDefs> = { + any: { propFn: anyOp }, + all: { propFn: allOp }, + members: { scopeFn: includeDeepMembersFunc }, + orderedMembers: { scopeFn: includeDeepOrderedMembersFunc }, + sameMembers: { scopeFn: sameDeepMembersFunc }, + sameOrderedMembers: { scopeFn: sameDeepOrderedMembersFunc }, + startsWithMembers: { scopeFn: startsWithDeepMembersFunc }, + endsWithMembers: { scopeFn: endsWithDeepMembersFunc }, + subsequence: { scopeFn: deepSubsequenceFunc } + }; + + return scope.createOperation(props, _deepOwnIncludes); } \ No newline at end of file diff --git a/core/src/assert/ops/numericOp.ts b/core/src/assert/ops/numericOp.ts index 44e6265..cc32cb6 100644 --- a/core/src/assert/ops/numericOp.ts +++ b/core/src/assert/ops/numericOp.ts @@ -29,14 +29,14 @@ function _checkTypes(context: IScopeContext, value: unknown, expected: unknown, if (isNumber(value)) { if (!isNumber(expected)) { - context.fail(evalMsg || "expected {expected} to be a number"); + context.fatal(evalMsg || "expected {expected} to be a number"); } } else if (isDate(value)) { if (!isDate(expected)) { - context.fail(evalMsg || "expected {expected} to be a date"); + context.fatal(evalMsg || "expected {expected} to be a date"); } } else { - context.fail(evalMsg || "expected {value} to be a number or date"); + context.fatal(evalMsg || "expected {value} to be a number or date"); } } diff --git a/core/src/assert/ops/ownOp.ts b/core/src/assert/ops/ownOp.ts index 6f71ef9..619deeb 100644 --- a/core/src/assert/ops/ownOp.ts +++ b/core/src/assert/ops/ownOp.ts @@ -2,13 +2,13 @@ * @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. */ import { OWN } from "../internal/const"; import { IOwnOp } from "../interface/ops/IOwnOp"; -import { includeOp } from "./includeOp"; +import { ownIncludeOp, deepOwnIncludeOp } from "./includeOp"; import { hasDeepOwnPropertyFunc, hasOwnPropertyDescriptorFunc, hasOwnPropertyFunc } from "../funcs/hasProperty"; import { IAssertScope } from "../interface/IAssertScope"; import { AssertScopeFuncDefs } from "../interface/IAssertInst"; @@ -18,10 +18,10 @@ export function ownOp(scope: IAssertScope): IOwnOp { scope.context.set(OWN, true); let props: AssertScopeFuncDefs> = { - contain: { propFn: includeOp }, - contains: { propFn: includeOp }, - include: { propFn: includeOp }, - includes: { propFn: includeOp }, + contain: { propFn: ownIncludeOp }, + contains: { propFn: ownIncludeOp }, + include: { propFn: ownIncludeOp }, + includes: { propFn: ownIncludeOp }, property: { scopeFn: hasOwnPropertyFunc }, propertyDescriptor: { scopeFn: hasOwnPropertyDescriptorFunc }, iterator: { scopeFn: hasOwnSymbolFunc(Symbol.iterator) } @@ -34,10 +34,10 @@ export function ownDeepOp(scope: IAssertScope): IOwnOp { scope.context.set(OWN, true); let props: AssertScopeFuncDefs> = { - contain: { propFn: includeOp }, - contains: { propFn: includeOp }, - include: { propFn: includeOp }, - includes: { propFn: includeOp }, + contain: { propFn: deepOwnIncludeOp }, + contains: { propFn: deepOwnIncludeOp }, + include: { propFn: deepOwnIncludeOp }, + includes: { propFn: deepOwnIncludeOp }, property: { scopeFn: hasDeepOwnPropertyFunc }, propertyDescriptor: { scopeFn: hasOwnPropertyDescriptorFunc }, iterator: { scopeFn: hasOwnSymbolFunc(Symbol.iterator) } diff --git a/core/src/assert/scopeContext.ts b/core/src/assert/scopeContext.ts index 746f547..31decf3 100644 --- a/core/src/assert/scopeContext.ts +++ b/core/src/assert/scopeContext.ts @@ -2,17 +2,17 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ import { - arrForEach, arrIndexOf, arrSlice, fnApply, getDeferred, getLazy, isArray, isFunction, isPlainObject, + arrForEach, arrIndexOf, arrSlice, fnApply, getDeferred, getLazy, getLength, isArray, isFunction, isObject, isPlainObject, newSymbol, objAssign, objDefine, objDefineProps, objForEachKey, objHasOwnProperty, - objKeys, strIndexOf, strLeft, strSubstring + objKeys, strEndsWith, strIndexOf, strLeft, strSubstring } from "@nevware21/ts-utils"; import { IScopeContext, IScopeContextOverrides } from "./interface/IScopeContext"; -import { AssertionFailure } from "./assertionError"; +import { AssertionFailure, AssertionFatal } from "./assertionError"; import { MsgSource } from "./type/MsgSource"; import { EMPTY_STRING } from "./internal/const"; import { assertConfig } from "./config"; @@ -72,6 +72,84 @@ function _createStackTracker(parentstack?: Function[]): Function[] { return theStack; } +function _getTokenValue(context: IScopeContext, details: any, token: string, opName?: string): { has: boolean, value: any } { + let value: any; + let hasToken = true; + + let idx = strIndexOf(token, "("); + if (idx != -1 && strEndsWith(token, ")")) { + // Find the closing parenthesis, there may be nested ones so we need to track them + let openParens = 1; + let lp = idx + 1; + while (openParens > 0 && lp < token.length) { + if (token[lp] == "(") { + openParens++; + } else if (token[lp] == ")") { + openParens--; + if (openParens == 0) { + let result = _getTokenValue(context, details, strSubstring(token, idx + 1, lp), strSubstring(token, 0, idx)); + if (result.has) { + return result; + } else { + // Just break out and treat it as a normal token + break; + } + } + } + + lp++; + } + } + + // Try and resolve the token value (name) as a property on the details object + if (objHasOwnProperty(details, token)) { + value = details[token]; + } else { + switch (token) { + case "value": + value = details["actual"]; + break; + case "path": + let path = context.get("opPath"); + value = path.join(" "); + break; + default: + hasToken = false; + } + } + + if (opName) { + if (opName === "len" || opName === "length") { + value = getLength(value); + hasToken = true; + } else if (opName === "typeof") { + if (value === null) { + value = "null"; + } else if (value === undefined) { + value = "undefined"; + } else if (isObject(value)) { + if (value.constructor && value.constructor.name) { + value = value.constructor.name; + } else { + value = typeof value; + } + } else { + value = typeof value; + } + + hasToken = true; + } else { + // not a supported operation + hasToken = false; + } + } + + return { + has: hasToken, + value: value + }; +} + /** * Returns the scope context for the given value, if it is an {@link IAssertInst} * instance it will return its context, if it's a {@link IAssertScope} it will @@ -129,30 +207,13 @@ export function createContext(value?: T, initMsg?: MsgSource, stackStart?: Fu break; } - let hasToken = true; - let token = strSubstring(message, open + 1, close); - let value: any; - if (objHasOwnProperty(details, token)) { - value = details[token]; - } else { - switch (token) { - case "value": - value = details["actual"]; - break; - case "path": - let path = context.get("opPath"); - value = path.join(" "); - break; - default: - hasToken = false; - } - } - - if (hasToken) { - let prefix = strLeft(message, open) + _formatValue(context, value); + let result = _getTokenValue(context, details, strSubstring(message, open + 1, close)); + if (result.has) { + let prefix = strLeft(message, open) + _formatValue(context, result.value); message = prefix + strSubstring(message, close + 1); start = prefix.length; } else { + // Unresolved token, so just skip it and continue start = open + 1; } } @@ -227,6 +288,38 @@ export function createContext(value?: T, initMsg?: MsgSource, stackStart?: Fu throw new AssertionFailure(_this.getMessage(msg), causedBy, theDetails, theStack); }); }, + fatal: function (msg: MsgSource, details: any, stackStart?: Function | Function[], causedBy?: Error): never { + let _this = this; + let theStack: Function[] = null; + + if (!theOptions.v.fullStack) { + theStack = _createStackTracker(_this._$stackFn); + if (stackStart) { + if (!isArray(stackStart)) { + theStack.push(stackStart); + } else { + arrForEach(stackStart, (fn) => { + theStack.push(fn); + }); + } + } + } + + // Use the scope context during error throwing + return useScope(_this, () => { + // As this always throws we don't need to resolve the message via _this.getMessage + // as we don't want the "not" prefix if this is used within a "not" operation. + let theInitMsg = _resolveMessage(_this, initMsg); + let path = _this.get("opPath"); + let message = _resolveMessage(_this, msg) || (path ? path.join(" ") : EMPTY_STRING); + + if (theInitMsg) { + message = theInitMsg + (message ? ": " + message : EMPTY_STRING); + } + + throw new AssertionFatal(message, causedBy, details || _this.getDetails(), theStack); + }); + }, setOp: function (opName: string) { let _this = this; _this.set("operation", opName); @@ -352,6 +445,7 @@ function _childContext(parent: IScopeContext, value: V, overrides?: IScopeCon getDetails: _proxyFn(parent, "getDetails", overrides), eval: _proxyFn(parent, "eval", overrides), fail: _proxyFn(parent, "fail", overrides), + fatal: _proxyFn(parent, "fatal", overrides), setOp: _proxyFn(parent, "setOp", null), get: function (name: string) { let _this = this; diff --git a/core/test/src/assert/assert.doesNotHaveAllKeys.test.ts b/core/test/src/assert/assert.doesNotHaveAllKeys.test.ts new file mode 100644 index 0000000..ad8b287 --- /dev/null +++ b/core/test/src/assert/assert.doesNotHaveAllKeys.test.ts @@ -0,0 +1,112 @@ +/* + * @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 { AssertionFailure } from "../../../src/assert/assertionError"; + +describe("assert.doesNotHaveAllKeys", () => { + describe("positive cases - should pass", () => { + it("should pass when object is missing at least one key", () => { + assert.doesNotHaveAllKeys({ a: 1, b: 2 }, ["a", "b", "c"]); + }); + + it("should pass when object does not have the single key", () => { + assert.doesNotHaveAllKeys({ greeting: "hello" }, "unknown"); + }); + + it("should pass when object is missing all keys", () => { + assert.doesNotHaveAllKeys({ a: 1 }, ["x", "y"]); + }); + + it("should pass with Map missing at least one key", () => { + const map = new Map([["key1", "value1"]]); + assert.doesNotHaveAllKeys(map, ["key1", "key2"]); + }); + + it("should pass with Set missing at least one key", () => { + const set = new Set(["a", "b"]); + assert.doesNotHaveAllKeys(set, ["a", "b", "c"]); + }); + + it("should pass with keys as array when missing a key", () => { + assert.doesNotHaveAllKeys({ a: 1 }, ["a", "b"]); + }); + }); + + describe("negative cases - should fail with correct error message", () => { + it("should fail when object has all the keys", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAllKeys({ a: 1, b: 2, c: 3 }, ["a", "b"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + const msg = error.message; + const hasPattern = msg.includes("not") || msg.includes("key") || msg.includes("Key"); + assert.isTrue(hasPattern, "Error message should contain 'not', 'key', or 'Key' but got: " + msg); + } + }); + + it("should fail when object has the single key", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAllKeys({ a: 1 }, "a"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Map has all the keys", () => { + let error: AssertionFailure | null = null; + try { + const map = new Map([["key1", "value1"], ["key2", "value2"]]); + assert.doesNotHaveAllKeys(map, ["key1", "key2"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Set has all the keys", () => { + let error: AssertionFailure | null = null; + try { + const set = new Set(["a", "b", "c"]); + assert.doesNotHaveAllKeys(set, ["a", "b"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when checking subset of keys that all exist", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAllKeys({ a: 1, b: 2, c: 3, d: 4 }, ["a", "c"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should include custom message when provided", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAllKeys({ a: 1, b: 2 }, ["a"], "Custom does not have all keys error"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + assert.isTrue(error.message.includes("Custom does not have all keys error"), "Error should contain custom message"); + } + }); + }); +}); diff --git a/core/test/src/assert/assert.doesNotHaveAnyKeys.test.ts b/core/test/src/assert/assert.doesNotHaveAnyKeys.test.ts new file mode 100644 index 0000000..19e7bc4 --- /dev/null +++ b/core/test/src/assert/assert.doesNotHaveAnyKeys.test.ts @@ -0,0 +1,108 @@ +/* + * @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 { AssertionFailure } from "../../../src/assert/assertionError"; + +describe("assert.doesNotHaveAnyKeys", () => { + describe("positive cases - should pass", () => { + it("should pass when object has none of the keys", () => { + assert.doesNotHaveAnyKeys({ a: 1, b: 2 }, ["c", "d"]); + }); + + it("should pass when object does not have the single key", () => { + assert.doesNotHaveAnyKeys({ greeting: "hello" }, "unknown"); + }); + + it("should pass with Map not containing any of the keys", () => { + const map = new Map([["key1", "value1"]]); + assert.doesNotHaveAnyKeys(map, ["key2", "key3"]); + }); + + it("should pass with Set not containing any of the keys", () => { + const set = new Set(["a", "b", "c"]); + assert.doesNotHaveAnyKeys(set, ["x", "y", "z"]); + }); + + it("should pass with keys as array", () => { + assert.doesNotHaveAnyKeys({ a: 1, b: 2 }, ["c", "d"]); + }); + }); + + describe("negative cases - should fail with correct error message", () => { + it("should fail when object has one of the keys", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAnyKeys({ a: 1, b: 2 }, ["a", "c"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + const msg = error.message; + const hasPattern = msg.includes("not") || msg.includes("key") || msg.includes("Key"); + assert.isTrue(hasPattern, "Error message should contain 'not', 'key', or 'Key' but got: " + msg); + } + }); + + it("should fail when object has the single key", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAnyKeys({ a: 1 }, "a"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Map has one of the keys", () => { + let error: AssertionFailure | null = null; + try { + const map = new Map([["key1", "value1"]]); + assert.doesNotHaveAnyKeys(map, ["key1", "key2"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Set has one of the keys", () => { + let error: AssertionFailure | null = null; + try { + const set = new Set(["a", "b", "c"]); + assert.doesNotHaveAnyKeys(set, ["a", "x", "y"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when object has all the specified keys", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAnyKeys({ a: 1, b: 2 }, ["a", "b"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should include custom message when provided", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotHaveAnyKeys({ a: 1 }, ["a"], "Custom does not have any keys error"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + assert.isTrue(error.message.includes("Custom does not have any keys error"), "Error should contain custom message"); + } + }); + }); +}); diff --git a/core/test/src/assert/assert.doesNotThrow.test.ts b/core/test/src/assert/assert.doesNotThrow.test.ts new file mode 100644 index 0000000..02089f2 --- /dev/null +++ b/core/test/src/assert/assert.doesNotThrow.test.ts @@ -0,0 +1,117 @@ +/* + * @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 { AssertionFailure } from "../../../src/assert/assertionError"; + +describe("assert.doesNotThrow", () => { + describe("positive cases - should pass", () => { + it("should pass when function does not throw", () => { + assert.doesNotThrow(() => { + // No error + }); + }); + + it("should pass when function returns a value", () => { + assert.doesNotThrow(() => { + return 42; + }); + }); + + it("should pass when function does not throw specified error type", () => { + assert.doesNotThrow(() => { + // No throw + }, TypeError); + }); + + it("should pass when function performs operations without error", () => { + assert.doesNotThrow(() => { + const arr = [1, 2, 3]; + arr.push(4); + return arr.length; + }); + }); + + it("should pass with async-like synchronous code", () => { + assert.doesNotThrow(() => { + const promise = Promise.resolve(42); + return promise; + }); + }); + }); + + describe("negative cases - should fail with correct error message", () => { + it("should fail when function throws an error", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotThrow(() => { + throw new Error("test error"); + }); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + const msg = error.message; + const hasPattern = msg.includes("not") || msg.includes("throw") || msg.includes("Throw"); + assert.isTrue(hasPattern, "Error message should contain 'not', 'throw', or 'Throw' but got: " + msg); + } + }); + + it("should fail when function throws matching error type", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotThrow(() => { + throw new TypeError("type error"); + }, TypeError); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when function throws with matching message", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotThrow(() => { + throw new Error("specific message"); + }, /specific/); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when function throws generic Error", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotThrow(() => { + throw new Error("generic error"); + }, Error); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should include custom message when provided", () => { + let error: AssertionFailure | null = null; + try { + assert.doesNotThrow(() => { + throw new Error("test"); + }, null, null, "Custom error message"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + assert.isTrue(error.message.includes("Custom error message"), "Error should contain custom message"); + } + }); + }); +}); diff --git a/core/test/src/assert/assert.equals.test.ts b/core/test/src/assert/assert.equals.test.ts index f9265ee..8673e34 100644 --- a/core/test/src/assert/assert.equals.test.ts +++ b/core/test/src/assert/assert.equals.test.ts @@ -410,7 +410,7 @@ describe("assert.equals", function () { checkError(function () { assert.equals(value, expected); - }, "expected [Map:{}] to equal [Map:{}]"); + }, "expected Map:{key1:\"value1\",key2:\"value2\"} to equal Map:{key1:\"value1\",key2:\"value2\"}"); assert.deepEqual(value, expected); }); @@ -421,7 +421,7 @@ describe("assert.equals", function () { checkError(function () { assert.equals(value, expected); - }, "expected [Map:{}] to equal [Map:{}]"); + }, "expected Map:{key1:\"value1\",key2:\"value2\"} to equal Map:{key1:\"value1\",key2:\"differentValue\"}"); }); it("should deeply equal when valueEntries are empty", function () { @@ -430,7 +430,7 @@ describe("assert.equals", function () { checkError(function () { assert.equals(value, expected); - }, "expected [Map:{}] to equal [Map:{}]"); + }, "expected Map:{} to equal Map:{}"); assert.deepEqual(value, expected); }); @@ -441,7 +441,7 @@ describe("assert.equals", function () { checkError(function () { assert.equals(value, expected); - }, "expected [Map:{}] to equal [Map:{}]"); + }, "expected Map:{key1:\"value1\"} to equal Map:{key1:\"value1\",key2:\"value2\"}"); }); }); diff --git a/core/test/src/assert/assert.hasAllKeys.test.ts b/core/test/src/assert/assert.hasAllKeys.test.ts new file mode 100644 index 0000000..bd31a42 --- /dev/null +++ b/core/test/src/assert/assert.hasAllKeys.test.ts @@ -0,0 +1,116 @@ +/* + * @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 { AssertionFailure } from "../../../src/assert/assertionError"; + +describe("assert.hasAllKeys", () => { + describe("positive cases - should pass", () => { + it("should pass when object has all the keys", () => { + assert.hasAllKeys({ a: 1, b: 2, c: 3 }, ["a", "b"]); + }); + + it("should pass when object has the single key", () => { + assert.hasAllKeys({ greeting: "hello" }, "greeting"); + }); + + it("should pass when object has exactly the specified keys", () => { + assert.hasAllKeys({ a: 1, b: 2 }, ["a", "b"]); + }); + + it("should pass with Map containing all keys", () => { + const map = new Map([["key1", "value1"], ["key2", "value2"]]); + assert.hasAllKeys(map, ["key1", "key2"]); + }); + + it("should pass with Set containing all keys", () => { + const set = new Set(["a", "b", "c"]); + assert.hasAllKeys(set, ["a", "b"]); + }); + + it("should pass with keys as array", () => { + assert.hasAllKeys({ a: 1, b: 2, c: 3 }, ["a", "b"]); + }); + + it("should pass with single key in object with multiple keys", () => { + assert.hasAllKeys({ x: 1, y: 2, z: 3 }, "x"); + }); + }); + + describe("negative cases - should fail with correct error message", () => { + it("should fail when object is missing a key", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAllKeys({ a: 1, b: 2 }, ["a", "b", "c"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + const msg = error.message; + const hasPattern = msg.includes("key") || msg.includes("Key"); + assert.isTrue(hasPattern, "Error message should contain 'key' or 'Key' but got: " + msg); + } + }); + + it("should fail when object does not have the single key", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAllKeys({ a: 1 }, "b"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Map is missing some keys", () => { + let error: AssertionFailure | null = null; + try { + const map = new Map([["key1", "value1"]]); + assert.hasAllKeys(map, ["key1", "key2"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Set is missing some keys", () => { + let error: AssertionFailure | null = null; + try { + const set = new Set(["a"]); + assert.hasAllKeys(set, ["a", "b"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when object is missing all keys", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAllKeys({ a: 1 }, ["x", "y"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should include custom message when provided", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAllKeys({ a: 1 }, ["a", "b"], "Custom all keys error"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + assert.isTrue(error.message.includes("Custom all keys error"), "Error should contain custom message"); + } + }); + }); +}); diff --git a/core/test/src/assert/assert.hasAnyKeys.test.ts b/core/test/src/assert/assert.hasAnyKeys.test.ts new file mode 100644 index 0000000..84ba610 --- /dev/null +++ b/core/test/src/assert/assert.hasAnyKeys.test.ts @@ -0,0 +1,106 @@ +/* + * @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 { AssertionFailure } from "../../../src/assert/assertionError"; + +describe("assert.hasAnyKeys", () => { + describe("positive cases - should pass", () => { + it("should pass when object has one of the keys", () => { + assert.hasAnyKeys({ a: 1, b: 2 }, ["a", "c"]); + }); + + it("should pass when object has the single key", () => { + assert.hasAnyKeys({ greeting: "hello" }, "greeting"); + }); + + it("should pass when object has multiple matching keys", () => { + assert.hasAnyKeys({ a: 1, b: 2, c: 3 }, ["a", "b"]); + }); + + it("should pass with Map containing the key", () => { + const map = new Map([["key1", "value1"]]); + assert.hasAnyKeys(map, ["key1", "key2"]); + }); + + it("should pass with Set containing the key", () => { + const set = new Set(["a", "b", "c"]); + assert.hasAnyKeys(set, ["a", "z"]); + }); + + it("should pass when checking multiple keys and one exists", () => { + assert.hasAnyKeys({ x: 1, y: 2, z: 3 }, ["a", "b", "x"]); + }); + + it("should pass with keys as array", () => { + assert.hasAnyKeys({ a: 1, b: 2 }, ["a", "c"]); + }); + }); + + describe("negative cases - should fail with correct error message", () => { + it("should fail when object has none of the keys", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAnyKeys({ a: 1, b: 2 }, ["c", "d"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + const msg = error.message; + const hasPattern = msg.includes("key") || msg.includes("Key"); + assert.isTrue(hasPattern, "Error message should contain 'key' or 'Key' but got: " + msg); + } + }); + + it("should fail when object does not have the single key", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAnyKeys({ a: 1 }, "b"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Map has none of the keys", () => { + let error: AssertionFailure | null = null; + try { + const map = new Map([["key1", "value1"]]); + assert.hasAnyKeys(map, ["key2", "key3"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when Set has none of the keys", () => { + let error: AssertionFailure | null = null; + try { + const set = new Set(["a", "b"]); + assert.hasAnyKeys(set, ["x", "y", "z"]); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should include custom message when provided", () => { + let error: AssertionFailure | null = null; + try { + assert.hasAnyKeys({ a: 1 }, ["b", "c"], "Custom key error"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + assert.isTrue(error.message.includes("Custom key error"), "Error should contain custom message"); + } + }); + }); +}); diff --git a/core/test/src/assert/assert.ifError.test.ts b/core/test/src/assert/assert.ifError.test.ts index f197f47..9f69363 100644 --- a/core/test/src/assert/assert.ifError.test.ts +++ b/core/test/src/assert/assert.ifError.test.ts @@ -288,6 +288,7 @@ describe("assert.ifError", () => { try { // Some operation that doesn't throw let x = 1 + 1; + assert.equal(x, 2); } catch (e: any) { err = e; } diff --git a/core/test/src/assert/assert.includes.test.ts b/core/test/src/assert/assert.includes.test.ts index d133d22..65cfc4e 100644 --- a/core/test/src/assert/assert.includes.test.ts +++ b/core/test/src/assert/assert.includes.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -45,4 +45,120 @@ describe("assert.includes", () => { expect(() => assert.includes("hello darkness", "nomatch", customMessage)).toThrowError(new AssertionFailure(customMessage)); }); + + describe("Error object property matching", () => { + it("should pass when Error includes the specified message property", () => { + const err = new Error("foo"); + assert.includes(err, { message: "foo" }); + }); + + it("should pass when Error includes multiple specified properties", () => { + const err: any = new Error("test error"); + err.code = 123; + err.status = "failed"; + assert.includes(err, { message: "test error", code: 123 }); + }); + + it("should throw AssertionFailure when Error property value does not match", () => { + const err = new Error("foo"); + checkError(() => { + assert.includes(err, { message: "bar" }); + }, "expected"); + + expect(() => assert.includes(err, { message: "bar" })).toThrow(AssertionFailure); + }); + + it("should throw AssertionFailure when Error is missing a specified property", () => { + const err = new Error("foo"); + checkError(() => { + assert.includes(err, { message: "foo", code: 123 }); + }, "expected"); + + expect(() => assert.includes(err, { message: "foo", code: 123 })).toThrow(AssertionFailure); + }); + + it("should work with custom Error properties", () => { + const err: any = new Error("custom"); + err.customProp = "value"; + err.count = 42; + assert.includes(err, { customProp: "value", count: 42 }); + }); + + it("should work with objects having Symbol.toStringTag", () => { + const customObj: any = { a: 1, b: 2 }; + customObj[Symbol.toStringTag] = "CustomObject"; + assert.includes(customObj, { a: 1 }); + }); + }); + + describe("Map and Set support", () => { + it("should pass when Map contains the specified value", () => { + const map = new Map(); + const val = { a: 1 }; + map.set("key", val); + assert.includes(map, val); + }); + + it("should pass when Map contains primitive values", () => { + const map = new Map(); + map.set("a", 1); + map.set("b", 2); + assert.includes(map, 1); + assert.includes(map, 2); + }); + + it("should handle NaN in Map values", () => { + const map = new Map(); + map.set("nan", NaN); + assert.includes(map, NaN); + }); + + it("should handle -0 and 0 as equal in Map values", () => { + const map = new Map(); + map.set("zero", -0); + assert.includes(map, 0); + }); + + it("should pass when Set contains the specified value", () => { + const set = new Set(); + const val = { a: 1 }; + set.add(val); + assert.includes(set, val); + }); + + it("should pass when Set contains primitive values", () => { + const set = new Set(); + set.add(1); + set.add(2); + assert.includes(set, 1); + assert.includes(set, 2); + }); + + it("should handle NaN in Set values", () => { + const set = new Set(); + set.add(NaN); + assert.includes(set, NaN); + }); + + it("should throw AssertionFailure when Map does not contain the value", () => { + const map = new Map(); + map.set("a", 1); + checkError(() => { + assert.includes(map, 999); + }, "expected"); + + expect(() => assert.includes(map, 999)).toThrow(AssertionFailure); + }); + + it("should throw AssertionFailure when Set does not contain the value", () => { + const set = new Set(); + set.add(1); + set.add(2); + checkError(() => { + assert.includes(set, 999); + }, "expected"); + + expect(() => assert.includes(set, 999)).toThrow(AssertionFailure); + }); + }); }); diff --git a/core/test/src/assert/assert.isIterable.test.ts b/core/test/src/assert/assert.isIterable.test.ts index 35a6879..6256c31 100644 --- a/core/test/src/assert/assert.isIterable.test.ts +++ b/core/test/src/assert/assert.isIterable.test.ts @@ -60,11 +60,11 @@ describe("assert.isNotIterable", function () { checkError(function () { assert.isNotIterable(new Map()); - }, "not expected [Map:{}] to be an iterable"); + }, "not expected Map:{} to be an iterable"); checkError(function () { assert.isNotIterable(new Set()); - }, "not expected [Set:{}] to be an iterable"); + }, "not expected Set:{} to be an iterable"); checkError(function () { let symIterator = getKnownSymbol(WellKnownSymbols.iterator); diff --git a/core/test/src/assert/assert.isObject.test.ts b/core/test/src/assert/assert.isObject.test.ts index 20895cf..655615a 100644 --- a/core/test/src/assert/assert.isObject.test.ts +++ b/core/test/src/assert/assert.isObject.test.ts @@ -139,6 +139,6 @@ describe("assert.isNotObject", () => { it("should fail when the value is a map", () => { checkError(() => { assert.isNotObject(new Map()); - }, "not expected [Map:{}] to be an Object"); + }, "not expected Map:{} to be an Object"); }); }); \ No newline at end of file diff --git a/core/test/src/assert/assert.notMatch.test.ts b/core/test/src/assert/assert.notMatch.test.ts new file mode 100644 index 0000000..6dd1fa7 --- /dev/null +++ b/core/test/src/assert/assert.notMatch.test.ts @@ -0,0 +1,84 @@ +/* + * @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 { AssertionFailure } from "../../../src/assert/assertionError"; + +describe("assert.notMatch", () => { + describe("positive cases - should pass", () => { + it("should pass when string does not match regex", () => { + assert.notMatch("hello world", /goodbye/); + }); + + it("should pass when value does not match pattern", () => { + assert.notMatch("abc", /xyz/); + }); + + it("should pass with full string non-match", () => { + assert.notMatch("hello", /^goodbye$/); + }); + + it("should pass when numeric pattern does not match", () => { + assert.notMatch("abc", /\d+/); + }); + + it("should pass with case-sensitive non-match", () => { + assert.notMatch("HELLO", /hello/); + }); + }); + + describe("negative cases - should fail with correct error message", () => { + it("should fail when string matches regex", () => { + let error: AssertionFailure | null = null; + try { + assert.notMatch("hello world", /hello/); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + const msg = error.message; + const hasPattern = msg.includes("not") || msg.includes("match") || msg.includes("Match"); + assert.isTrue(hasPattern, "Error message should contain 'not', 'match', or 'Match' but got: " + msg); + } + }); + + it("should fail when pattern matches", () => { + let error: AssertionFailure | null = null; + try { + assert.notMatch("test123", /\d+/); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should fail when entire string matches", () => { + let error: AssertionFailure | null = null; + try { + assert.notMatch("abc", /^abc$/); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + }); + + it("should include custom message when provided", () => { + let error: AssertionFailure | null = null; + try { + assert.notMatch("abc", /abc/, "Custom error message"); + } catch (e) { + error = e as AssertionFailure; + } + assert.isNotNull(error, "Expected assertion to fail"); + if (error) { + assert.isTrue(error.message.includes("Custom error message"), "Error should contain custom message"); + } + }); + }); +}); diff --git a/core/test/src/assert/assert.ok.test.ts b/core/test/src/assert/assert.ok.test.ts index 0f398f4..9e18bdc 100644 --- a/core/test/src/assert/assert.ok.test.ts +++ b/core/test/src/assert/assert.ok.test.ts @@ -271,19 +271,19 @@ describe("assert.isNotOk", function () { checkError(function () { assert.isNotOk(new Map()); // Throws AssertionError - }, /not expected \[.*Map.*\] to be truthy/); - + }, /not expected .*Map.* to be truthy/); + checkError(function () { assert.isNotOk(new Map([["a", 1]])); // Throws AssertionError - }, /not expected \[.*Map.*\] to be truthy/); + }, /not expected .*Map.* to be truthy/); checkError(function () { assert.isNotOk(new Set()); // Throws AssertionError - }, /not expected \[.*Set.*\] to be truthy/); + }, /not expected .*Set.* to be truthy/); checkError(function () { assert.isNotOk(new Set([1, 2])); // Throws AssertionError - }, /not expected \[.*Set.*\] to be truthy/); + }, /not expected .*Set.* to be truthy/); let dt = new Date(); checkError(function () { diff --git a/core/test/src/assert/assert.ownInclude.test.ts b/core/test/src/assert/assert.ownInclude.test.ts new file mode 100644 index 0000000..68105f6 --- /dev/null +++ b/core/test/src/assert/assert.ownInclude.test.ts @@ -0,0 +1,318 @@ +/* + * @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("Own Include Operations", () => { + + describe("ownInclude", () => { + it("should pass when object has own property with matching value", () => { + assert.ownInclude({ a: 1 }, { a: 1 }); + assert.ownInclude({ a: 1, b: 2 }, { a: 1 }); + assert.ownInclude({ a: 1, b: 2 }, { a: 1, b: 2 }); + }); + + it("should fail when object has own property with different value", () => { + checkError(() => { + assert.ownInclude({ a: 1 }, { a: 3 }); + }, "to have own properties matching"); + }); + + it("should pass when checking that inherited properties are not included", () => { + const obj = { a: 1 }; + assert.notOwnInclude(obj, { toString: Object.prototype.toString }); + }); + + it("should fail when trying to match nested objects with strict equality", () => { + checkError(() => { + assert.ownInclude({ a: { b: 2 } }, { a: { b: 2 } }); + }, "to have own properties matching"); + }); + + it("should work with custom message", () => { + checkError(() => { + assert.ownInclude({ a: 1 }, { a: 3 }, "custom error"); + }, "custom error"); + }); + + it("should work with expect syntax", () => { + expect({ a: 1, b: 2 }).own.include({ a: 1 }); + }); + + it("should handle empty objects", () => { + assert.ownInclude({}, {}); + assert.ownInclude({ a: 1 }, {}); + }); + + it("should handle objects with multiple properties", () => { + const obj = { a: 1, b: 2, c: 3 }; + assert.ownInclude(obj, { a: 1 }); + assert.ownInclude(obj, { a: 1, b: 2 }); + assert.ownInclude(obj, { a: 1, b: 2, c: 3 }); + }); + + it("should handle objects with undefined values", () => { + assert.ownInclude({ a: undefined }, { a: undefined }); + }); + + it("should handle objects with null values", () => { + assert.ownInclude({ a: null }, { a: null }); + }); + + it("should handle objects with boolean values", () => { + assert.ownInclude({ a: true, b: false }, { a: true }); + }); + + it("should handle objects with string values", () => { + assert.ownInclude({ name: "test" }, { name: "test" }); + }); + + it("should handle objects with number values including zero", () => { + assert.ownInclude({ count: 0 }, { count: 0 }); + }); + + it("should not match inherited properties from prototype", () => { + function Parent(this: any) {} + Parent.prototype.inheritedProp = "inherited"; + + function Child(this: any) { + this.ownProp = "own"; + } + Child.prototype = new (Parent as any)(); + + const instance = new (Child as any)(); + assert.ownInclude(instance, { ownProp: "own" }); + assert.notOwnInclude(instance, { inheritedProp: "inherited" }); + }); + }); + + describe("notOwnInclude", () => { + it("should pass when object does not have matching own property value", () => { + assert.notOwnInclude({ a: 1 }, { a: 3 }); + assert.notOwnInclude({ a: 1 }, { b: 1 }); + }); + + it("should pass when property is inherited not own", () => { + assert.notOwnInclude({ a: 1 }, { toString: Object.prototype.toString }); + }); + + it("should pass when nested objects don't match with strict equality", () => { + assert.notOwnInclude({ a: { b: 2 } }, { a: { b: 2 } }); + }); + + it("should fail when object has matching own property", () => { + checkError(() => { + assert.notOwnInclude({ a: 1 }, { a: 1 }); + }, "not expected {a:1} to have own properties matching {a:1}"); + }); + + it("should work with custom message", () => { + checkError(() => { + assert.notOwnInclude({ a: 1 }, { a: 1 }, "custom error"); + }, "custom error"); + }); + + it("should work with expect syntax", () => { + expect({ a: 1 }).not.own.include({ a: 3 }); + }); + }); + + describe("ownInclude - strings and arrays", () => { + it("should work with strings", () => { + assert.ownInclude("hello world", "hello"); + assert.ownInclude("hello world", "world"); + }); + + it("should work with arrays using strict equality", () => { + assert.ownInclude([1, 2, 3], 2); + assert.ownInclude(["a", "b", "c"], "b"); + }); + + it("should not match array items with different references", () => { + assert.notOwnInclude([{ a: 1 }, { b: 2 }], { a: 1 }); + }); + }); + + describe("ownInclude - error messages", () => { + it("should provide clear error message for property mismatch", () => { + checkError(() => { + assert.ownInclude({ a: 1, b: 2 }, { a: 1, b: 3 }); + }, "to have own properties matching"); + }); + + it("should provide clear error message for missing property", () => { + checkError(() => { + assert.ownInclude({ a: 1 }, { b: 2 }); + }, "to have own properties matching"); + }); + + it("should provide clear error message when negation fails", () => { + checkError(() => { + assert.notOwnInclude({ a: 1 }, { a: 1 }); + }, "not expected"); + }); + }); + + describe("deepOwnInclude", () => { + it("should pass when object has own property with deeply equal value", () => { + assert.deepOwnInclude({ a: { b: 2 } }, { a: { b: 2 } }); + assert.deepOwnInclude({ x: [1, 2, 3] }, { x: [1, 2, 3] }); + }); + + it("should fail when nested values don't match", () => { + checkError(() => { + assert.deepOwnInclude({ a: { b: 2 } }, { a: { c: 3 } }); + }, "to have own properties deeply matching"); + }); + + it("should pass when checking that inherited properties are not included", () => { + const obj = { a: { b: 2 } }; + assert.notDeepOwnInclude(obj, { toString: Object.prototype.toString }); + }); + + it("should work with custom message", () => { + checkError(() => { + assert.deepOwnInclude({ a: { b: 2 } }, { a: { c: 3 } }, "custom error"); + }, "custom error"); + }); + + it("should work with expect syntax", () => { + expect({ a: { b: 2 } }).deep.own.include({ a: { b: 2 } }); + }); + + it("should handle deeply nested objects", () => { + const obj = { a: { b: { c: { d: 1 } } } }; + assert.deepOwnInclude(obj, { a: { b: { c: { d: 1 } } } }); + }); + + it("should handle arrays with objects", () => { + const obj = { items: [{ id: 1 }, { id: 2 }] }; + assert.deepOwnInclude(obj, { items: [{ id: 1 }, { id: 2 }] }); + }); + + it("should handle objects with array values", () => { + assert.deepOwnInclude({ nums: [1, 2, 3] }, { nums: [1, 2, 3] }); + }); + + it("should handle mixed nested structures", () => { + const obj = { + user: { + name: "John", + roles: ["admin", "user"], + settings: { + theme: "dark", + notifications: true + } + } + }; + assert.deepOwnInclude(obj, { + user: { + name: "John", + roles: ["admin", "user"], + settings: { + theme: "dark", + notifications: true + } + } + }); + }); + + it("should handle objects with null and undefined", () => { + assert.deepOwnInclude({ a: null }, { a: null }); + assert.deepOwnInclude({ b: undefined }, { b: undefined }); + }); + + it("should pass when subset of properties match", () => { + const obj = { a: { b: 2 }, c: { d: 3 }, e: 5 }; + assert.deepOwnInclude(obj, { a: { b: 2 } }); + assert.deepOwnInclude(obj, { a: { b: 2 }, c: { d: 3 } }); + }); + + it("should fail when any property doesn't match", () => { + const obj = { a: { b: 2 }, c: { d: 3 } }; + checkError(() => { + assert.deepOwnInclude(obj, { a: { b: 2 }, c: { d: 4 } }); + }, "to have own properties deeply matching"); + }); + }); + + describe("notDeepOwnInclude", () => { + it("should pass when nested values don't match", () => { + assert.notDeepOwnInclude({ a: { b: 2 } }, { a: { c: 3 } }); + }); + + it("should pass when property is inherited not own", () => { + assert.notDeepOwnInclude({ a: { b: 2 } }, { toString: Object.prototype.toString }); + }); + + it("should fail when nested values match", () => { + checkError(() => { + assert.notDeepOwnInclude({ a: { b: 2 } }, { a: { b: 2 } }); + }, "not expected {a:{b:2}} to have own properties deeply matching {a:{b:2}}"); + }); + + it("should work with custom message", () => { + checkError(() => { + assert.notDeepOwnInclude({ a: { b: 2 } }, { a: { b: 2 } }, "custom error"); + }, "custom error"); + }); + + it("should work with expect syntax", () => { + expect({ a: { b: 2 } }).to.not.deep.own.include({ a: { c: 3 } }); + }); + }); + + describe("deepOwnInclude - inherited properties", () => { + it("should not match inherited properties", () => { + function Parent(this: any) {} + Parent.prototype.inheritedObj = { value: "inherited" }; + + function Child(this: any) { + this.ownObj = { value: "own" }; + } + Child.prototype = new (Parent as any)(); + + const instance = new (Child as any)(); + assert.deepOwnInclude(instance, { ownObj: { value: "own" } }); + assert.notDeepOwnInclude(instance, { inheritedObj: { value: "inherited" } }); + }); + }); + + describe("deepOwnInclude - strings and arrays", () => { + it("should work with strings", () => { + assert.deepOwnInclude("hello world", "hello"); + }); + + it("should work with arrays using deep equality", () => { + assert.deepOwnInclude([{ a: 1 }, { b: 2 }], { a: 1 }); + assert.deepOwnInclude([{ a: 1 }, { b: 2 }], { b: 2 }); + }); + + it("should fail when object is not in array", () => { + checkError(() => { + assert.deepOwnInclude([{ a: 1 }, { b: 2 }], { c: 3 }); + }, "to deep include own"); + }); + }); + + describe("deepOwnInclude - error messages", () => { + it("should provide clear error message for deep property mismatch", () => { + checkError(() => { + assert.deepOwnInclude({ a: { b: 2 } }, { a: { b: 3 } }); + }, "to have own properties deeply matching"); + }); + + it("should provide clear error message when negation fails", () => { + checkError(() => { + assert.notDeepOwnInclude({ a: { b: 2 } }, { a: { b: 2 } }); + }, "not expected"); + }); + }); +}); diff --git a/core/test/src/assert/expect.has.test.ts b/core/test/src/assert/expect.has.test.ts index ee6fe0a..dfc89cf 100644 --- a/core/test/src/assert/expect.has.test.ts +++ b/core/test/src/assert/expect.has.test.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. */ @@ -16,7 +16,7 @@ describe("expect.has", () => { checkError(() => { expect({ a: 1, b: 2 }).has.all.keys("a", "c"); - }, "expected all keys: [a,c], missing: [c], found: [a,b]"); + }, "expected all keys: [\"a\",\"c\"], missing: [\"c\"], found: [\"a\",\"b\"]"); expect(() => expect({ a: 1, b: 2 }).has.all.keys("a", "c")).toThrow(); }); @@ -26,7 +26,7 @@ describe("expect.has", () => { checkError(() => { expect({ a: 1, b: 2 }).has.any.keys("c", "d"); - }, "expected any key: [c,d], found: [a,b]"); + }, "expected any key: [\"c\",\"d\"], found: [\"a\",\"b\"]"); expect(() => expect({ a: 1, b: 2 }).has.any.keys("c", "d")).toThrow(); }); diff --git a/core/test/src/assert/expect.include.test.ts b/core/test/src/assert/expect.include.test.ts index 44e57fe..34a3ab5 100644 --- a/core/test/src/assert/expect.include.test.ts +++ b/core/test/src/assert/expect.include.test.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. */ @@ -26,17 +26,17 @@ describe("expect.include operation", () => { }); it("should pass when string includes the specified substring", () => { - const str = "hello darkness my old friend"; + const str = "walking through darkness and saying hello"; expect(str).include("darkness"); expect(() => expect(str).include("darkness")).to.not.throw(); }); it("should fail when string does not include the specified substring", () => { - const str = "hello darkness my old friend"; + const str = "walking through darkness and saying hello"; checkError(() => { expect(str).include("planet"); - }, "expected \"hello darkness my old friend\" to include \"planet\""); + }, "expected \"walking through darkness and saying hello\" to include \"planet\""); expect(() => expect(str).include("planet")).to.throw(); }); @@ -53,7 +53,7 @@ describe("expect.include operation", () => { checkError(() => { expect(obj).include.all.keys("a", "d"); - }, "expected all keys: [a,d], missing: [d], found: [a,b,c]"); + }, "expected all keys: [\"a\",\"d\"], missing: [\"d\"], found: [\"a\",\"b\",\"c\"]"); expect(() => expect(obj).include.all.keys("a", "d")).to.throw(); }); @@ -77,9 +77,79 @@ describe("expect.include operation", () => { checkError(() => { expect(obj).include.any.keys("x", "d"); - }, "expected any key: [x,d], found: [a,b,c]"); + }, "expected any key: [\"x\",\"d\"], found: [\"a\",\"b\",\"c\"]"); expect(() => expect(obj).include.any.keys("x", "d")).to.throw(); }); + + describe("object property matching", () => { + it("should pass when Error includes the specified properties", () => { + const err = new Error("foo"); + expect(err).include({ message: "foo" }); + expect(() => expect(err).include({ message: "foo" })).to.not.throw(); + }); + + it("should fail when Error does not include the specified properties", () => { + const err = new Error("foo"); + checkError(() => { + expect(err).include({ message: "bar" }); + }, "expected"); + expect(() => expect(err).include({ message: "bar" })).to.throw(); + }); + + it("should pass when Error includes multiple specified properties", () => { + const err: any = new Error("foo"); + err.code = 123; + expect(err).include({ message: "foo", code: 123 }); + expect(() => expect(err).include({ message: "foo", code: 123 })).to.not.throw(); + }); + + it("should fail when Error is missing one of the specified properties", () => { + const err = new Error("foo"); + checkError(() => { + expect(err).include({ message: "foo", code: 123 }); + }, "expected"); + expect(() => expect(err).include({ message: "foo", code: 123 })).to.throw(); + }); + + it("should work with nested Error objects", () => { + const err: any = new Error("outer"); + err.inner = new Error("inner"); + expect(err).include({ message: "outer" }); + expect(() => expect(err).include({ message: "outer" })).to.not.throw(); + }); + }); + + describe("deep include with object property matching", () => { + it("should pass when Error includes the specified properties with deep equality", () => { + const err: any = new Error("foo"); + err.data = { code: 123 }; + expect(err).deep.include({ data: { code: 123 } }); + expect(() => expect(err).deep.include({ data: { code: 123 } })).to.not.throw(); + }); + + it("should fail when nested property values don't match", () => { + const err: any = new Error("foo"); + err.data = { code: 123 }; + checkError(() => { + expect(err).deep.include({ data: { code: 456 } }); + }, "expected"); + expect(() => expect(err).deep.include({ data: { code: 456 } })).to.throw(); + }); + + it("should pass when object includes nested properties with deep equality", () => { + const obj = { a: 1, b: { c: 2, d: 3 } }; + expect(obj).deep.include({ b: { c: 2, d: 3 } }); + expect(() => expect(obj).deep.include({ b: { c: 2, d: 3 } })).to.not.throw(); + }); + + it("should fail when nested object structure doesn't match", () => { + const obj = { a: 1, b: { c: 2, d: 3 } }; + checkError(() => { + expect(obj).deep.include({ b: { c: 2, d: 99 } }); + }, "expected"); + expect(() => expect(obj).deep.include({ b: { c: 2, d: 99 } })).to.throw(); + }); + }); }); diff --git a/core/test/src/assert/expect.isIterable.ts b/core/test/src/assert/expect.isIterable.ts index 0f4cc5d..12c7086 100644 --- a/core/test/src/assert/expect.isIterable.ts +++ b/core/test/src/assert/expect.isIterable.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. */ @@ -17,7 +17,7 @@ describe("expect.is.iterable", () => { }); it("should pass when string is iterable", () => { - const str = "hello darkness my old friend"; + const str = "darkness fades when we say hello"; expect(str).is.iterable(); expect(() => expect(str).is.iterable()).to.not.throw(); @@ -95,10 +95,10 @@ describe("expect.is.not.iterable", () => { }); it("should fail when string is iterable", () => { - const str = "hello darkness my old friend"; + const str = "darkness fades when we say hello"; checkError(() => { expect(str).is.not.iterable(); - }, "not expected \"hello darkness my old friend\" to be an iterable"); + }, "not expected \"darkness fades when we say hello\" to be an iterable"); }); it("should fail when object is iterable", () => { diff --git a/core/test/src/assert/expect.ownInclude.test.ts b/core/test/src/assert/expect.ownInclude.test.ts new file mode 100644 index 0000000..c9fe6a3 --- /dev/null +++ b/core/test/src/assert/expect.ownInclude.test.ts @@ -0,0 +1,340 @@ +/* + * @nevware21/tripwire + * https://github.com/nevware21/tripwire + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { expect } from "../../../src/assert/expect"; + +describe("ownInclude and notOwnInclude", () => { + + describe("ownInclude - basic functionality", () => { + it("should pass when object has own property with matching value", () => { + expect({ a: 1 }).own.include({ a: 1 }); + expect({ a: 1, b: 2 }).own.include({ a: 1 }); + expect({ a: 1, b: 2 }).own.include({ a: 1, b: 2 }); + }); + + it("should fail when object has own property with different value", () => { + expect(() => { + expect({ a: 1 }).own.include({ a: 3 }); + }).to.throw("expected {a:1} to have own properties matching {a:3}"); + }); + + it("should pass when checking that inherited properties are not included", () => { + const obj = { a: 1 }; + expect(obj).not.own.include({ toString: Object.prototype.toString }); + }); + + it("should fail when trying to match nested objects with strict equality", () => { + expect(() => { + expect({ a: { b: 2 } }).own.include({ a: { b: 2 } }); + }).to.throw("expected {a:{b:2}} to have own properties matching {a:{b:2}}"); + }); + + it("should work with custom message", () => { + expect(() => { + expect({ a: 1 }).own.include({ a: 3 }, "custom error"); + }).to.throw(/custom error/); + }); + }); + + describe("notOwnInclude - basic functionality", () => { + it("should pass when object does not have matching own property value", () => { + expect({ a: 1 }).not.own.include({ a: 3 }); + expect({ a: 1 }).not.own.include({ b: 1 }); + }); + + it("should pass when property is inherited not own", () => { + expect({ a: 1 }).not.own.include({ toString: Object.prototype.toString }); + }); + + it("should pass when nested objects don't match with strict equality", () => { + expect({ a: { b: 2 } }).not.own.include({ a: { b: 2 } }); + }); + + it("should fail when object has matching own property", () => { + expect(() => { + expect({ a: 1 }).not.own.include({ a: 1 }); + }).to.throw("not expected {a:1} to have own properties matching {a:1}"); + }); + + it("should work with custom message", () => { + expect(() => { + expect({ a: 1 }).not.own.include({ a: 1 }, "custom error"); + }).to.throw(/custom error/); + }); + }); + + describe("ownInclude - edge cases", () => { + it("should handle empty objects", () => { + expect({}).own.include({}); + expect({ a: 1 }).own.include({}); + }); + + it("should handle objects with multiple properties", () => { + const obj = { a: 1, b: 2, c: 3 }; + expect(obj).own.include({ a: 1 }); + expect(obj).own.include({ a: 1, b: 2 }); + expect(obj).own.include({ a: 1, b: 2, c: 3 }); + }); + + it("should handle objects with undefined values", () => { + expect({ a: undefined }).own.include({ a: undefined }); + }); + + it("should handle objects with null values", () => { + expect({ a: null }).own.include({ a: null }); + }); + + it("should handle objects with boolean values", () => { + expect({ a: true, b: false }).own.include({ a: true }); + }); + + it("should handle objects with string values", () => { + expect({ name: "test" }).own.include({ name: "test" }); + }); + + it("should handle objects with number values including zero", () => { + expect({ count: 0 }).own.include({ count: 0 }); + }); + + it("should handle objects with symbol keys", () => { + const sym = Symbol("test"); + const obj = { [sym]: "value" }; + expect(obj).own.include({ [sym]: "value" }); + }); + + it("should not match inherited properties from prototype", () => { + function Parent(this: any) {} + Parent.prototype.inheritedProp = "inherited"; + + function Child(this: any) { + this.ownProp = "own"; + } + Child.prototype = new (Parent as any)(); + + const instance = new (Child as any)(); + expect(instance).own.include({ ownProp: "own" }); + expect(instance).not.own.include({ inheritedProp: "inherited" }); + }); + }); + + describe("ownInclude with strings and arrays", () => { + it("should work with strings", () => { + expect("hello world").own.include("hello"); + expect("hello world").own.include("world"); + }); + + it("should work with arrays using strict equality", () => { + expect([1, 2, 3]).own.include(2); + expect(["a", "b", "c"]).own.include("b"); + }); + + it("should not match array items with different references", () => { + expect([{ a: 1 }, { b: 2 }]).not.own.include({ a: 1 }); + }); + }); + + describe("error messages", () => { + it("should provide clear error message for property mismatch", () => { + try { + expect({ a: 1, b: 2 }).own.include({ a: 1, b: 3 }); + throw new Error("Should have thrown"); + } catch (err: any) { + expect(err.message).to.include("expected"); + expect(err.message).to.include("to have own properties matching"); + } + }); + + it("should provide clear error message for missing property", () => { + try { + expect({ a: 1 }).own.include({ b: 2 }); + throw new Error("Should have thrown"); + } catch (err: any) { + expect(err.message).to.include("expected"); + expect(err.message).to.include("to have own properties matching"); + } + }); + + it("should provide clear error message when negation fails", () => { + try { + expect({ a: 1 }).not.own.include({ a: 1 }); + throw new Error("Should have thrown"); + } catch (err: any) { + expect(err.message).to.include("not expected"); + expect(err.message).to.include("to have own properties matching"); + } + }); + }); +}); + +describe("deepOwnInclude and notDeepOwnInclude", () => { + + describe("deepOwnInclude - basic functionality", () => { + it("should pass when object has own property with deeply equal value", () => { + expect({ a: { b: 2 } }).deep.own.include({ a: { b: 2 } }); + expect({ x: [1, 2, 3] }).deep.own.include({ x: [1, 2, 3] }); + }); + + it("should fail when nested values don't match", () => { + expect(() => { + expect({ a: { b: 2 } }).deep.own.include({ a: { c: 3 } }); + }).to.throw("expected {a:{b:2}} to have own properties deeply matching {a:{c:3}}"); + }); + + it("should pass when checking that inherited properties are not included", () => { + const obj = { a: { b: 2 } }; + expect(obj).not.deep.own.include({ toString: Object.prototype.toString }); + }); + + it("should work with custom message", () => { + expect(() => { + expect({ a: { b: 2 } }).deep.own.include({ a: { c: 3 } }, "custom error"); + }).to.throw(/custom error/); + }); + }); + + describe("notDeepOwnInclude - basic functionality", () => { + it("should pass when nested values don't match", () => { + expect({ a: { b: 2 } }).not.deep.own.include({ a: { c: 3 } }); + }); + + it("should pass when property is inherited not own", () => { + expect({ a: { b: 2 } }).not.deep.own.include({ toString: Object.prototype.toString }); + }); + + it("should fail when nested values match", () => { + expect(() => { + expect({ a: { b: 2 } }).not.deep.own.include({ a: { b: 2 } }); + }).to.throw("not expected {a:{b:2}} to have own properties deeply matching {a:{b:2}}"); + }); + + it("should work with custom message", () => { + expect(() => { + expect({ a: { b: 2 } }).not.deep.own.include({ a: { b: 2 } }, "custom error"); + }).to.throw(/custom error/); + }); + }); + + describe("deepOwnInclude - deep equality scenarios", () => { + it("should handle deeply nested objects", () => { + const obj = { a: { b: { c: { d: 1 } } } }; + expect(obj).deep.own.include({ a: { b: { c: { d: 1 } } } }); + }); + + it("should handle arrays with objects", () => { + const obj = { items: [{ id: 1 }, { id: 2 }] }; + expect(obj).deep.own.include({ items: [{ id: 1 }, { id: 2 }] }); + }); + + it("should handle objects with array values", () => { + expect({ nums: [1, 2, 3] }).deep.own.include({ nums: [1, 2, 3] }); + }); + + it("should handle mixed nested structures", () => { + const obj = { + user: { + name: "John", + roles: ["admin", "user"], + settings: { + theme: "dark", + notifications: true + } + } + }; + expect(obj).deep.own.include({ + user: { + name: "John", + roles: ["admin", "user"], + settings: { + theme: "dark", + notifications: true + } + } + }); + }); + + it("should handle objects with null and undefined", () => { + expect({ a: null }).deep.own.include({ a: null }); + expect({ b: undefined }).deep.own.include({ b: undefined }); + }); + + it("should handle objects with dates", () => { + const date = new Date("2024-01-01"); + expect({ timestamp: date }).deep.own.include({ timestamp: date }); + }); + }); + + describe("deepOwnInclude - partial matching", () => { + it("should pass when subset of properties match", () => { + const obj = { a: { b: 2 }, c: { d: 3 }, e: 5 }; + expect(obj).deep.own.include({ a: { b: 2 } }); + expect(obj).deep.own.include({ a: { b: 2 }, c: { d: 3 } }); + }); + + it("should fail when any property doesn't match", () => { + const obj = { a: { b: 2 }, c: { d: 3 } }; + expect(() => { + expect(obj).deep.own.include({ a: { b: 2 }, c: { d: 4 } }); + }).to.throw(); + }); + }); + + describe("deepOwnInclude - inherited properties", () => { + it("should not match inherited properties", () => { + function Parent(this: any) {} + Parent.prototype.inheritedObj = { value: "inherited" }; + + function Child(this: any) { + this.ownObj = { value: "own" }; + } + Child.prototype = new (Parent as any)(); + + const instance = new (Child as any)(); + expect(instance).deep.own.include({ ownObj: { value: "own" } }); + expect(instance).not.deep.own.include({ inheritedObj: { value: "inherited" } }); + }); + }); + + describe("deepOwnInclude with strings and arrays", () => { + it("should work with strings", () => { + expect("hello world").deep.own.include("hello"); + }); + + it("should work with arrays using deep equality", () => { + expect([{ a: 1 }, { b: 2 }]).deep.own.include({ a: 1 }); + expect([{ a: 1 }, { b: 2 }]).deep.own.include({ b: 2 }); + }); + + it("should fail when object is not in array", () => { + expect(() => { + expect([{ a: 1 }, { b: 2 }]).deep.own.include({ c: 3 }); + }).to.throw(); + }); + }); + + describe("error messages", () => { + it("should provide clear error message for deep property mismatch", () => { + try { + expect({ a: { b: 2 } }).deep.own.include({ a: { b: 3 } }); + throw new Error("Should have thrown"); + } catch (err: any) { + expect(err.message).to.include("expected"); + expect(err.message).to.include("to have own properties deeply matching"); + } + }); + + it("should provide clear error message when negation fails", () => { + try { + expect({ a: { b: 2 } }).to.not.deep.own.include({ a: { b: 2 } }); + throw new Error("Should have thrown"); + } catch (err: any) { + expect(err.message).to.include("not expected"); + expect(err.message).to.include("to have own properties deeply matching"); + } + }); + }); +}); diff --git a/core/test/src/assert/expect.to.test.ts b/core/test/src/assert/expect.to.test.ts index ee5bb24..87e2052 100644 --- a/core/test/src/assert/expect.to.test.ts +++ b/core/test/src/assert/expect.to.test.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. */ @@ -16,7 +16,7 @@ describe("expect.to.have", () => { checkError(() => { expect({ a: 1, b: 2 }).to.have.all.keys("a", "c"); - }, "expected all keys: [a,c], missing: [c], found: [a,b]"); + }, "expected all keys: [\"a\",\"c\"], missing: [\"c\"], found: [\"a\",\"b\"]"); expect(() => expect({ a: 1, b: 2 }).to.have.all.keys("a", "c")).toThrow(); }); @@ -26,7 +26,7 @@ describe("expect.to.have", () => { checkError(() => { expect({ a: 1, b: 2 }).to.have.any.keys("c", "d"); - }, "expected any key: [c,d], found: [a,b]"); + }, "expected any key: [\"c\",\"d\"], found: [\"a\",\"b\"]"); expect(() => expect({ a: 1, b: 2 }).to.have.any.keys("c", "d")).toThrow(); }); diff --git a/core/test/src/assert/funcs/allValuesFunc.test.ts b/core/test/src/assert/funcs/allValuesFunc.test.ts index 0a53f54..92f2f56 100644 --- a/core/test/src/assert/funcs/allValuesFunc.test.ts +++ b/core/test/src/assert/funcs/allValuesFunc.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2025 NevWare21 Solutions LLC + * Copyright (c) 2025-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -71,11 +71,11 @@ describe("allValuesFunc", () => { checkError(() => { valuesFn.call(scope, "h", "e", "l", "o", "z"); - }, /expected all values: \[h,e,l,o,z\], missing: \[z\], found: \[hello\] \(1 value\)/); + }, /expected all values: \["h","e","l","o","z"\], missing: \["z"\], found: "hello" \(1 value\)/); checkError(() => { valuesFn.call(scope, ["h", "e", "l", "o", "z"]); - }, /expected all values: \[h,e,l,o,z\], missing: \[z\], found: \[hello\] \(1 value\)/); + }, /expected all values: \["h","e","l","o","z"\], missing: \["z"\], found: "hello" \(1 value\)/); }); it("should fail when not all keys are in an object", () => { @@ -85,11 +85,11 @@ describe("allValuesFunc", () => { checkError(() => { valuesFn.call(scope, "a", "b", "c", "d"); - }, /expected all values: \[a,b,c,d\], missing: \[d\], found: \[a,b,c\] \(3 values\)/); + }, /expected all values: \["a","b","c","d"\], missing: \["d"\], found: \["a","b","c"\] \(3 values\)/); checkError(() => { valuesFn.call(scope, ["a", "b", "c", "d"]); - }, /expected all values: \[a,b,c,d\], missing: \[d\], found: \[a,b,c\] \(3 values\)/); + }, /expected all values: \["a","b","c","d"\], missing: \["d"\], found: \["a","b","c"\] \(3 values\)/); }); it("should handle multiple missing values", () => { @@ -112,7 +112,7 @@ describe("allValuesFunc", () => { checkError(() => { valuesFn.call(scope, null, undefined, 0, "", "a"); - }, /expected all values: \[null,undefined,0,"",a\], missing: \[a\], found: \[null,undefined,0,""\] \(4 values\)/); + }, /expected all values: \[null,undefined,0,"",\"a\"\], missing: \[\"a\"\], found: \[null,undefined,0,""\] \(4 values\)/); }); it("should handle empty string context", () => { @@ -124,7 +124,7 @@ describe("allValuesFunc", () => { checkError(() => { valuesFn.call(scope, "", "a"); - }, /expected all values: \["",a\], missing: \[a\], found: \[""\] \(1 value\)/); + }, /expected all values: \["","a"\], missing: \["a"\], found: "" \(1 value\)/); }); it("should handle null context", () => { @@ -136,7 +136,7 @@ describe("allValuesFunc", () => { checkError(() => { valuesFn.call(scope, null, "a"); - }, /expected all values: \[null,a\], missing: \[a\], found: \[null\] \(1 value\)/); + }, /expected all values: \[null,"a"\], missing: \["a"\], found: null \(1 value\)/); }); it("should handle undefined context", () => { @@ -148,50 +148,50 @@ describe("allValuesFunc", () => { checkError(() => { valuesFn.call(scope, undefined, "a"); - }, /expected all values: \[undefined,a\], missing: \[a\], found: \[undefined\] \(1 value\)/); + }, /expected all values: \[undefined,"a"\], missing: \["a"\], found: undefined \(1 value\)/); }); it("should check for all substrings in a longer text", () => { - const context = "hello darkness my old friend"; + const context = "walking and talking into the darkness"; const scope = createAssertScope(createContext(context)); const valuesFn = allValuesFunc(scope); // These words are all in the string - expect(() => valuesFn.call(scope, "hello", "darkness", "my", "old", "friend")).to.not.throw(); - expect(() => valuesFn.call(scope, ["hello", "darkness", "my", "old", "friend"])).to.not.throw(); + expect(() => valuesFn.call(scope, "walking", "and", "talking", "into", "darkness")).to.not.throw(); + expect(() => valuesFn.call(scope, ["walking", "and", "talking", "into", "darkness"])).to.not.throw(); // These partial substrings are all in the string - expect(() => valuesFn.call(scope, "hello", "dark", "my", "old", "fri")).to.not.throw(); + expect(() => valuesFn.call(scope, "walk", "and", "talk", "into", "dark")).to.not.throw(); }); it("should detect missing substrings in a longer text", () => { - const context = "hello darkness my old friend"; + const context = "say hello while running and jumping"; const scope = createAssertScope(createContext(context)); const valuesFn = allValuesFunc(scope); // Missing words: silence, sound checkError(() => { - valuesFn.call(scope, "hello", "darkness", "silence", "sound"); - }, /expected all values: \[hello,darkness,silence,sound\], missing: \[silence,sound\], found: \[hello darkness my old friend\] \(1 value\)/); + valuesFn.call(scope, "hello", "running", "silence", "sound"); + }, /expected all values: \[\"hello\",\"running\",\"silence\",\"sound\"\], missing: \[\"silence\",\"sound\"\], found: "say hello while running and jumping" \(1 value\)/); - // Missing words: vision, talking, hearing + // Missing words: walking, talking, seeing checkError(() => { - valuesFn.call(scope, ["darkness", "my", "old", "vision", "talking", "hearing"]); - }, /expected all values: \[darkness,my,old,vision,talking,hearing\], missing: \[vision,talking,hearing\], found: \[hello darkness my old friend\] \(1 value\)/); + valuesFn.call(scope, ["hello", "running", "and", "walking", "talking", "seeing"]); + }, /expected all values: \[\"hello\",\"running\",\"and\",\"walking\",\"talking\",\"seeing\"\], missing: \[\"walking\",\"talking\",\"seeing\"\], found: "say hello while running and jumping" \(1 value\)/); }); - it("should handle known Sound of Silence lyric combinations", () => { - // Full verse lyric line - const context = "hello darkness my old friend I've come to talk with you again"; + it("should handle longer phrase combinations", () => { + // Full phrase with hello and darkness spread out + const context = "we said hello while walking through the darkness and listening"; const scope = createAssertScope(createContext(context)); const valuesFn = allValuesFunc(scope); - // These parts are all in the verse - expect(() => valuesFn.call(scope, "hello darkness", "my old friend", "talk with you")).to.not.throw(); + // These parts are all in the phrase + expect(() => valuesFn.call(scope, "said hello", "walking through", "darkness and")).to.not.throw(); - // Missing parts: whispers, restless dreams, remains + // Missing parts: swimming, climbing, flying checkError(() => { - valuesFn.call(scope, "hello darkness", "whispers", "restless dreams", "remains"); - }, /expected all values: \[hello darkness,whispers,restless dreams,remains\], missing: \[whispers,restless dreams,remains\], found: \[hello darkness my old friend I've come to talk with you again\] \(1 value\)/); + valuesFn.call(scope, "said hello", "swimming", "climbing", "flying"); + }, /expected all values: \[\"said hello\",\"swimming\",\"climbing\",\"flying\"\], missing: \[\"swimming\",\"climbing\",\"flying\"\], found: "we said hello while walking through the darkness and listening" \(1 value\)/); }); }); \ No newline at end of file diff --git a/core/test/src/assert/funcs/anyValuesFunc.test.ts b/core/test/src/assert/funcs/anyValuesFunc.test.ts index 3ef3126..aa4dcc1 100644 --- a/core/test/src/assert/funcs/anyValuesFunc.test.ts +++ b/core/test/src/assert/funcs/anyValuesFunc.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2025 NevWare21 Solutions LLC + * Copyright (c) 2025-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -104,7 +104,7 @@ describe("anyValuesFunc", () => { checkError(() => { valuesFn.call(scope, "a"); - }, /expected any value: \[a\], found: \[""\] \(1 value\)/); + }, /expected any value: \["a"\], found: "" \(1 value\)/); }); it("should handle null context", () => { @@ -116,7 +116,7 @@ describe("anyValuesFunc", () => { checkError(() => { valuesFn.call(scope, "a"); - }, /expected any value: \[a\], found: \[null\] \(1 value\)/); + }, /expected any value: \["a"\], found: null \(1 value\)/); }); it("should handle undefined context", () => { @@ -128,7 +128,7 @@ describe("anyValuesFunc", () => { checkError(() => { valuesFn.call(scope, "a"); - }, /expected any value: \[a\], found: \[undefined\] \(1 value\)/); + }, /expected any value: \["a"\], found: undefined \(1 value\)/); }); it("should find substrings in a longer text string", () => { @@ -154,7 +154,7 @@ describe("anyValuesFunc", () => { checkError(() => { valuesFn.call(scope, "talking", "hearing", "whisper"); - }, /expected any value: \[talking,hearing,whisper\], found: \[hello darkness old friend\] \(1 value\)/); + }, /expected any value: \["talking","hearing","whisper"\], found: "hello darkness old friend" \(1 value\)/); }); it("should find at least one substring from a list of options", () => { diff --git a/core/test/src/assert/members.test.ts b/core/test/src/assert/members.test.ts index 3b59541..8fe5287 100644 --- a/core/test/src/assert/members.test.ts +++ b/core/test/src/assert/members.test.ts @@ -122,6 +122,18 @@ describe("Member Comparison Tests", () => { expect([{a: 1}]).to.not.deep.include.sameMembers([{a: 2}]); }); + it("should work with assert.notSameDeepMembers", () => { + assert.notSameDeepMembers([{a: 1}], [{a: 2}]); + assert.notSameDeepMembers([{a: 1}, {b: 2}], [{a: 1}, {b: 3}]); + assert.notSameDeepMembers([{a: 1}], [{a: 1}, {b: 2}]); + }); + + it("should fail assert.notSameDeepMembers when arrays have same deep members", () => { + expect(() => { + assert.notSameDeepMembers([{a: 1}, {b: 2}], [{b: 2}, {a: 1}]); + }).to.throw(); + }); + it("should handle mixed types", () => { assert.sameDeepMembers([1, "a", {b: 2}], [{b: 2}, "a", 1]); }); @@ -182,6 +194,36 @@ describe("Member Comparison Tests", () => { it("should work with not operator", () => { assert.notSameOrderedMembers([1, 2, 3], [3, 2, 1]); }); + + it("should fail assert.notSameOrderedMembers when arrays have same ordered members", () => { + expect(() => { + assert.notSameOrderedMembers([1, 2, 3], [1, 2, 3]); + }).to.throw(); + }); + }); + + describe("sameDeepOrderedMembers", () => { + it("should pass when arrays have same deep members in same order", () => { + assert.sameDeepOrderedMembers([{a: 1}, {b: 2}], [{a: 1}, {b: 2}]); + assert.sameDeepOrderedMembers([[1, 2], [3, 4]], [[1, 2], [3, 4]]); + }); + + it("should fail when arrays have same deep members in different order", () => { + expect(() => { + assert.sameDeepOrderedMembers([{a: 1}, {b: 2}], [{b: 2}, {a: 1}]); + }).to.throw(); + }); + + it("should work with assert.notSameDeepOrderedMembers", () => { + assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}], [{b: 2}, {a: 1}]); + assert.notSameDeepOrderedMembers([{a: 1}], [{a: 2}]); + }); + + it("should fail assert.notSameDeepOrderedMembers when arrays have same deep ordered members", () => { + expect(() => { + assert.notSameDeepOrderedMembers([{a: 1}, {b: 2}], [{a: 1}, {b: 2}]); + }).to.throw(); + }); }); describe("includeMembers", () => { @@ -290,6 +332,17 @@ describe("Member Comparison Tests", () => { expect([{a: 1}]).to.not.deep.include.members([{a: 2}]); }); + it("should work with assert.notIncludeDeepMembers", () => { + assert.notIncludeDeepMembers([{a: 1}], [{a: 2}]); + assert.notIncludeDeepMembers([{a: 1}, {a: 2}], [{a: 3}]); + }); + + it("should fail assert.notIncludeDeepMembers when superset includes all subset deep members", () => { + expect(() => { + assert.notIncludeDeepMembers([{a: 1}, {b: 2}], [{a: 1}]); + }).to.throw(); + }); + it("should handle nested objects", () => { assert.includeDeepMembers( [{a: {b: 1}}, {c: {d: 2}}, {e: 3}], @@ -376,6 +429,22 @@ describe("Member Comparison Tests", () => { it("should work with not operator", () => { expect([1, 2, 3, 4]).to.not.include.orderedMembers([2, 4]); }); + + it("should work with assert.notIncludeOrderedMembers", () => { + assert.notIncludeOrderedMembers([1, 2, 3, 4], [2, 4]); + assert.notIncludeOrderedMembers([1, 2, 3, 4], [3, 2]); + assert.notIncludeOrderedMembers([1, 2, 3], [4, 5]); + }); + + it("should fail assert.notIncludeOrderedMembers when subset appears consecutively in order", () => { + expect(() => { + assert.notIncludeOrderedMembers([1, 2, 3, 4], [1, 2, 3]); + }).to.throw(); + + expect(() => { + assert.notIncludeOrderedMembers([1, 2, 3, 4], [2, 3]); + }).to.throw(); + }); }); describe("includeDeepOrderedMembers", () => { @@ -1130,6 +1199,22 @@ describe("Member Comparison Tests", () => { expect([1, 2, 3, 4]).to.not.include.endsWithMembers([1, 2]); }); + it("should work with assert.notEndsWithMembers", () => { + assert.notEndsWithMembers([1, 2, 3, 4], [1, 2]); + assert.notEndsWithMembers([1, 2, 3, 4], [2, 3]); + assert.notEndsWithMembers([1, 2, 3], [4, 5]); + }); + + it("should fail assert.notEndsWithMembers when array ends with the expected sequence", () => { + expect(() => { + assert.notEndsWithMembers([1, 2, 3, 4], [3, 4]); + }).to.throw(); + + expect(() => { + assert.notEndsWithMembers([1, 2, 3, 4], [4]); + }).to.throw(); + }); + it("should fail with correct error message for invalid types", () => { expect(() => { assert.endsWithMembers("not an array" as any, [1, 2]); @@ -1183,6 +1268,22 @@ describe("Member Comparison Tests", () => { expect([{a: 1}, {b: 2}, {c: 3}]).to.not.deep.include.endsWithMembers([{a: 1}]); }); + it("should work with assert.notEndsWithDeepMembers", () => { + assert.notEndsWithDeepMembers([{a: 1}, {a: 2}, {a: 3}], [{a: 1}]); + assert.notEndsWithDeepMembers([{a: 1}, {a: 2}, {a: 3}], [{a: 1}, {a: 2}]); + assert.notEndsWithDeepMembers([{a: 1}, {a: 2}], [{a: 3}]); + }); + + it("should fail assert.notEndsWithDeepMembers when array ends with the expected deep sequence", () => { + expect(() => { + assert.notEndsWithDeepMembers([{a: 1}, {b: 2}, {c: 3}], [{b: 2}, {c: 3}]); + }).to.throw(); + + expect(() => { + assert.notEndsWithDeepMembers([{a: 1}, {b: 2}], [{b: 2}]); + }).to.throw(); + }); + it("should handle nested arrays", () => { assert.endsWithDeepMembers([[1, 2], [3, 4], [5, 6]], [[3, 4], [5, 6]]); }); @@ -1284,6 +1385,22 @@ describe("Member Comparison Tests", () => { expect([1, 2, 3, 4, 5]).to.not.include.subsequence([5, 1]); }); + it("should work with assert.notSubsequence", () => { + assert.notSubsequence([1, 2, 3, 4, 5], [5, 3, 1]); + assert.notSubsequence([1, 2, 3, 4, 5], [5, 1]); + assert.notSubsequence([1, 2, 3], [4, 5]); + }); + + it("should fail assert.notSubsequence when members do appear in order", () => { + expect(() => { + assert.notSubsequence([1, 2, 3, 4, 5], [1, 3, 5]); + }).to.throw(); + + expect(() => { + assert.notSubsequence([1, 2, 3, 4, 5], [2, 4]); + }).to.throw(); + }); + it("should fail with correct error message for invalid types", () => { expect(() => { assert.subsequence("not an array" as any, [1, 2]); @@ -1387,6 +1504,22 @@ describe("Member Comparison Tests", () => { expect([{a: 1}, {b: 2}, {c: 3}]).to.not.deep.include.subsequence([{c: 3}, {a: 1}]); }); + it("should work with assert.notDeepSubsequence", () => { + assert.notDeepSubsequence([{a: 1}, {a: 2}, {a: 3}], [{a: 3}, {a: 1}]); + assert.notDeepSubsequence([{a: 1}, {a: 2}], [{a: 2}, {a: 1}]); + assert.notDeepSubsequence([{a: 1}, {a: 2}], [{a: 3}]); + }); + + it("should fail assert.notDeepSubsequence when deep members do appear in order", () => { + expect(() => { + assert.notDeepSubsequence([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {c: 3}]); + }).to.throw(); + + expect(() => { + assert.notDeepSubsequence([{a: 1}, {b: 2}, {c: 3}], [{a: 1}, {b: 2}]); + }).to.throw(); + }); + it("should fail with correct error message for invalid types", () => { expect(() => { assert.deepSubsequence("not an array" as any, [{a: 1}]); diff --git a/core/test/src/operations/deepIncludeOp.test.ts b/core/test/src/operations/deepIncludeOp.test.ts index b39c490..2e4e9e6 100644 --- a/core/test/src/operations/deepIncludeOp.test.ts +++ b/core/test/src/operations/deepIncludeOp.test.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. */ @@ -21,7 +21,7 @@ describe("deepIncludeOp", () => { checkError(() => { op.call(scope, "key"); - }, "expected null to have a \"key\" property"); + }, "argument null (\"null\") is not a supported collection type for the operation."); expect(() => op.call(scope, "key")).to.throw(); }); @@ -33,7 +33,7 @@ describe("deepIncludeOp", () => { checkError(() => { op.call(scope, "key"); - }, "expected undefined to have a \"key\" property"); + }, "argument undefined (\"undefined\") is not a supported collection type for the operation."); expect(() => op.call(scope, "key")).to.throw(); }); @@ -45,7 +45,7 @@ describe("deepIncludeOp", () => { checkError(() => { op.call(scope, "key"); - }, "expected {} to have a \"key\" property"); + }, "expected {} to have a deep \"key\" property"); expect(() => op.call(scope, "key")).to.throw(); }); @@ -58,7 +58,7 @@ describe("deepIncludeOp", () => { op.call(scope, "hello"); checkError(() => { op.call(scope, "my"); - }, "expected {hello:\"darkness\"} to have a \"my\" property"); + }, "expected {hello:\"darkness\"} to have a deep \"my\" property"); expect(() => op.call(scope, "hello")).to.not.throw(); expect(() => op.call(scope, "friend")).to.throw(); @@ -71,7 +71,7 @@ describe("deepIncludeOp", () => { checkError(() => { op.call(scope, "key"); - }, "expected {hello:{}} to have a \"key\" property"); + }, "expected {hello:{}} to have a deep \"key\" property"); }); it("should handle non-object inputs gracefully", () => { @@ -80,6 +80,6 @@ describe("deepIncludeOp", () => { const op = deepIncludeOp(scope); checkError(() => { op.call(scope, "key"); - }, "expected null to have a \"key\" property"); + }, "argument null (\"null\") is not a supported collection type for the operation."); }); }); \ No newline at end of file diff --git a/core/test/src/operations/includeOp.test.ts b/core/test/src/operations/includeOp.test.ts index 23227cc..3658d6d 100644 --- a/core/test/src/operations/includeOp.test.ts +++ b/core/test/src/operations/includeOp.test.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. */ @@ -20,7 +20,7 @@ describe("includeOp", () => { checkError(() => { op.call(scope, "hello"); - }, "expected null to have a \"hello\" property"); + }, "argument null (\"null\") is not a supported collection type for the operation."); expect(() => op.call(scope, "hello")).to.throw(); }); @@ -32,7 +32,7 @@ describe("includeOp", () => { checkError(() => { op.call(scope, "hello"); - }, "expected undefined to have a \"hello\" property"); + }, "argument undefined (\"undefined\") is not a supported collection type for the operation."); expect(() => op.call(scope, "hello")).to.throw(); }); diff --git a/core/test/src/operations/keysOp.test.ts b/core/test/src/operations/keysOp.test.ts index 3ba9590..9e9d724 100644 --- a/core/test/src/operations/keysOp.test.ts +++ b/core/test/src/operations/keysOp.test.ts @@ -2,7 +2,7 @@ * @nevware21/tripwire * https://github.com/nevware21/tripwire * - * Copyright (c) 2024-2025 NevWare21 Solutions LLC + * Copyright (c) 2024-2026 NevWare21 Solutions LLC * Licensed under the MIT license. */ @@ -27,7 +27,7 @@ describe("anyKeyFilter", () => { checkError(() => { keysOp.keys.call(scope, "hello"); - }, "expected any key: [hello], found: []"); + }, "expected any key: [\"hello\"], found: []"); expect(() => expect(null).has.any.keys("hello")).to.throw(); }); @@ -39,7 +39,7 @@ describe("anyKeyFilter", () => { checkError(() => { keysOp.keys.call(scope, "hello"); - }, "expected any key: [hello], found: []"); + }, "expected any key: [\"hello\"], found: []"); expect(() => expect(undefined).has.any.keys("hello")).to.throw(); }); @@ -51,7 +51,7 @@ describe("anyKeyFilter", () => { checkError(() => { keysOp.keys.call(scope, "hello"); - }, "expected any key: [hello], found: []"); + }, "expected any key: [\"hello\"], found: []"); expect(() => expect({}).has.any.keys("hello")).to.throw(); }); @@ -124,7 +124,7 @@ describe("anyKeyFilter", () => { // Failing cases checkError(() => { keysOp.keys.call(scope, "darkness"); - }, /expected any key: \[darkness\], found: \[.*\]/); + }, /expected any key: \["darkness"\], found: \[.*\]/); expect(() => expect({}).has.any.keys("darkness")).to.throw(); }); @@ -159,7 +159,7 @@ describe("allKeyFilter", () => { checkError(() => { keysOp.keys.call(scope, "hello"); - }, "expected all keys: [hello], missing: [hello], found: []"); + }, "expected all keys: [\"hello\"], missing: [\"hello\"], found: []"); expect(() => expect(null).has.any.keys("hello")).to.throw(); }); @@ -171,7 +171,7 @@ describe("allKeyFilter", () => { checkError(() => { keysOp.keys.call(scope, "hello"); - }, "expected all keys: [hello], missing: [hello], found: []"); + }, "expected all keys: [\"hello\"], missing: [\"hello\"], found: []"); expect(() => expect(undefined).has.any.keys("hello")).to.throw(); }); @@ -183,7 +183,7 @@ describe("allKeyFilter", () => { checkError(() => { keysOp.keys.call(scope, "hello"); - }, "expected all keys: [hello], missing: [hello], found: []"); + }, "expected all keys: [\"hello\"], missing: [\"hello\"], found: []"); expect(() => expect({}).has.any.keys("hello")).to.throw(); }); @@ -255,7 +255,7 @@ describe("allKeyFilter", () => { // Failing cases checkError(() => { keysOp.keys.call(scope, "darkness"); - }, /expected all keys: \[darkness\], missing: \[darkness\], found: \[.*\]/); + }, /expected all keys: \["darkness"\], missing: \["darkness"\], found: \[.*\]/); expect(() => expect({}).has.any.keys("darkness")).to.throw(); }); diff --git a/shim/chai/README.md b/shim/chai/README.md index 4de36bc..f8950e4 100644 --- a/shim/chai/README.md +++ b/shim/chai/README.md @@ -66,7 +66,7 @@ Error messages differ from Chai. Use regex instead of exact strings: - **Types**: `isObject`, `isArray`, `isString`, `isNumber`, `isBoolean`, `isFunction`, `isNull`, `isUndefined`, `isNaN`, `isFinite`, etc. - **Comparisons**: `isAbove`, `isAtLeast`, `isBelow`, `isAtMost`, `closeTo`, `approximately`, `operator` - **Properties**: `property`, `deepProperty`, `nestedProperty`, `ownProperty` with value validation -- **Collections**: `include`, `deepInclude`, `members`, `sameMembers`, `keys` +- **Collections**: `include`, `deepInclude`, `nestedInclude`, `ownInclude`, `deepOwnInclude`, `members`, `sameMembers`, `keys` - **Deep Keys**: `hasAnyDeepKeys`, `hasAllDeepKeys`, `containsAllDeepKeys`, `doesNotHaveAnyDeepKeys`, `doesNotHaveAllDeepKeys` for Maps/Sets with object keys - **Size**: `lengthOf`, `sizeOf` - **Changes**: `changes`, `increases`, `decreases` with delta tracking diff --git a/shim/chai/src/assert/chaiAssert.ts b/shim/chai/src/assert/chaiAssert.ts index b626005..6b7a71e 100644 --- a/shim/chai/src/assert/chaiAssert.ts +++ b/shim/chai/src/assert/chaiAssert.ts @@ -13,15 +13,6 @@ import { isFunction } from "@nevware21/ts-utils"; export const chaiAssert: IChaiAssert = _createChaiAssert(); -/** - * @internal - * @ignore - * Internal function which throws an error indicating that a function is not yet implemented. - */ -function notImplemented(this: IAssertScope): void { - this.fatal("Not implemented"); -} - /** * @internal * @ignore @@ -111,10 +102,10 @@ function _createChaiAssert(): IChaiAssert { notNestedInclude: { scopeFn: createExprAdapter("not.has.nested.include"), nArgs: 2 }, deepNestedInclude: { scopeFn: createExprAdapter("deep.nested.include"), nArgs: 2 }, notDeepNestedInclude: { scopeFn: createExprAdapter("not.deep.nested.include"), nArgs: 2 }, - ownInclude: notImplemented, //{ scopeFn: createExprAdapter("own.include"), nArgs: 2 }, - notOwnInclude: notImplemented, //{ scopeFn: createExprAdapter("not.own.include"), nArgs: 2 }, - deepOwnInclude: notImplemented, //{ scopeFn: createExprAdapter("deep.own.include"), nArgs: 2 }, - notDeepOwnInclude: notImplemented, //{ scopeFn: createExprAdapter("not.deep.own.include"), nArgs: 2 }, + ownInclude: { scopeFn: createExprAdapter("own.include"), nArgs: 2 }, + notOwnInclude: { scopeFn: createExprAdapter("not.own.include"), nArgs: 2 }, + deepOwnInclude: { scopeFn: createExprAdapter("deep.own.include"), nArgs: 2 }, + notDeepOwnInclude: { scopeFn: createExprAdapter("not.deep.own.include"), nArgs: 2 }, match: { scopeFn: createExprAdapter("match"), nArgs: 2 }, notMatch: { scopeFn: createExprAdapter("not.match"), nArgs: 2 }, property: { scopeFn: createExprAdapter("has.property"), nArgs: 2 }, diff --git a/shim/chai/test/src/chaiAssert.test.ts b/shim/chai/test/src/chaiAssert.test.ts index 7fe2f62..f537149 100644 --- a/shim/chai/test/src/chaiAssert.test.ts +++ b/shim/chai/test/src/chaiAssert.test.ts @@ -625,7 +625,7 @@ describe("assert", function () { it("isString", function() { assert.isString("Foo"); - // assert.isString(new String("foo")); + //assert.isString(new String("foo")); err(function () { assert.isString(1, "blah"); @@ -712,47 +712,47 @@ describe("assert", function () { // .include should work with Error objects and objects with a custom // `@@toStringTag`. - // assert.include(new Error("foo"), {message: "foo"}); - // var customObj = {a: 1}; - // customObj[Symbol.toStringTag] = "foo"; - - // assert.include(customObj, {a: 1}); - - // var obj1 = {a: 1} - // , obj2 = {b: 2}; - // assert.include([obj1, obj2], obj1); - // assert.include({foo: obj1, bar: obj2}, {foo: obj1}); - // assert.include({foo: obj1, bar: obj2}, {foo: obj1, bar: obj2}); - - // var map = new Map(); - // var val = [{a: 1}]; - // map.set("a", val); - // map.set("b", 2); - // map.set("c", -0); - // map.set("d", NaN); - - // assert.include(map, val); - // assert.include(map, 2); - // assert.include(map, 0); - // assert.include(map, NaN); - - // var val = [{a: 1}]; - // var set = new Set(); - // set.add(val); - // set.add(2); - // set.add(-0); - // set.add(NaN); - - // assert.include(set, val); - // assert.include(set, 2); - // assert.include(set, 0); - // assert.include(set, NaN); - - // var ws = new WeakSet(); - // var val = [{a: 1}]; - // ws.add(val); - - // assert.include(ws, val); + assert.include(new Error("foo"), {message: "foo"}); + var customObj: any = {a: 1}; + customObj[Symbol.toStringTag] = "foo"; + + assert.include(customObj, {a: 1}); + + var obj1 = {a: 1} + , obj2 = {b: 2}; + assert.include([obj1, obj2], obj1); + assert.include({foo: obj1, bar: obj2}, {foo: obj1}); + assert.include({foo: obj1, bar: obj2}, {foo: obj1, bar: obj2}); + + var map = new Map(); + var val = [{a: 1}]; + map.set("a", val); + map.set("b", 2); + map.set("c", -0); + map.set("d", NaN); + + assert.include(map, val); + assert.include(map, 2); + assert.include(map, 0); + assert.include(map, NaN); + + var val = [{a: 1}]; + var set = new Set(); + set.add(val); + set.add(2); + set.add(-0); + set.add(NaN); + + assert.include(set, val); + assert.include(set, 2); + assert.include(set, 0); + assert.include(set, NaN); + + var ws = new WeakSet(); + var val = [{a: 1}]; + ws.add(val); + + assert.include(ws, val); var sym1 = Symbol() , sym2 = Symbol(); @@ -768,35 +768,31 @@ describe("assert", function () { err(function () { assert.include({foo: {a: 1}, bar: {b: 2}}, {foo: {a: 1}}, "blah"); - }, "blah: expected {foo:{a:1},bar:{b:2}} to have a {foo:{a:1}} property"); + }, "blah: expected {foo:{a:1},bar:{b:2}} to include {foo:{a:1}}"); + + err(function(){ + assert.include(true, true, "blah"); + }, + "blah: argument true (\"boolean\") is not a supported collection type for the operation" + ); - // err(function(){ - // assert.include(true, true, "blah"); - // }, - // "blah: the given combination of arguments (boolean and boolean) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a boolean" - // ); - - // err(function () { - // assert.include(42, "bar" as any); - // }, - // "the given combination of arguments (number and string) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a string" - // ); + err(function () { + assert.include(42, "bar" as any); + }, + "argument 42 (\"number\") is not a supported collection type for the operation" + ); - // err(function(){ - // assert.include(null, 42 as any); - // }, - // "the given combination of arguments (null and number) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a number" - // ); + err(function(){ + assert.include(null, 42 as any); + }, + "argument null (\"null\") is not a supported collection type for the operation" + ); - // err(function () { - // assert.include(undefined, "bar" as any); - // }, - // "the given combination of arguments (undefined and string) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a string" - // ); + err(function () { + assert.include(undefined, "bar" as any); + }, + "argument undefined (\"undefined\") is not a supported collection type for the operation" + ); }); it("notInclude", function () { @@ -840,94 +836,90 @@ describe("assert", function () { , sym3 = Symbol(); assert.notInclude([sym1, sym2], sym3); - // err(function () { - // var obj1 = {a: 1} - // , obj2 = {b: 2}; - // assert.notInclude([obj1, obj2], obj1, "blah"); - // }, "blah: expected [ { a: 1 }, { b: 2 } ] to not include { a: 1 }"); + err(function () { + var obj1 = {a: 1} + , obj2 = {b: 2}; + assert.notInclude([obj1, obj2], obj1, "blah"); + }, "blah: not expected [{a:1},{b:2}] to include {a:1}"); - // err(function () { - // var obj1 = {a: 1} - // , obj2 = {b: 2}; - // assert.notInclude({foo: obj1, bar: obj2}, {foo: obj1, bar: obj2}, "blah"); - // }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to not have property \"foo\" of { a: 1 }"); + err(function () { + var obj1 = {a: 1} + , obj2 = {b: 2}; + assert.notInclude({foo: obj1, bar: obj2}, {foo: obj1, bar: obj2}, "blah"); + }, "blah: not expected {foo:{a:1},bar:{b:2}} to include {foo:{a:1},bar:{b:2}}"); - // err(function(){ - // assert.notInclude(true, true, "blah"); - // }, - // "blah: the given combination of arguments (boolean and boolean) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a boolean" - // ); - - // err(function () { - // assert.notInclude(42, "bar" as any); - // }, - // "the given combination of arguments (number and string) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a string" - // ); + err(function(){ + assert.notInclude(true, true, "blah"); + }, + "blah: argument true (\"boolean\") is not a supported collection type for the operation" + ); - // err(function(){ - // assert.notInclude(null, 42 as any); - // }, - // "the given combination of arguments (null and number) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a number" - // ); + err(function () { + assert.notInclude(42, "bar" as any); + }, + "argument 42 (\"number\") is not a supported collection type for the operation" + ); - // err(function () { - // assert.notInclude(undefined, "bar" as any); - // }, - // "the given combination of arguments (undefined and string) is invalid for this assertion. " + - // "You can use an array, a map, an object, a set, a string, or a weakset instead of a string" - // ); + err(function(){ + assert.notInclude(null, 42 as any); + }, + "argument null (\"null\") is not a supported collection type for the operation" + ); + + err(function () { + assert.notInclude(undefined, "bar" as any); + }, + "argument undefined (\"undefined\") is not a supported collection type for the operation" + ); err(function () { assert.notInclude("foobar", "bar"); }, "not expected \"foobar\" to include \"bar\""); }); - // it("deepInclude and notDeepInclude", function () { - // var obj1 = {a: 1} - // , obj2 = {b: 2}; - // assert.deepInclude([obj1, obj2], {a: 1}); - // assert.notDeepInclude([obj1, obj2], {a: 9}); - // assert.notDeepInclude([obj1, obj2], {z: 1} as any); - // assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}}); - // assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}}); - // assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 9}}); - // assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {z: 1}} as any); - // assert.notDeepInclude({foo: obj1, bar: obj2}, {baz: {a: 1}} as any); - // assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}}); - - // var map = new Map(); - // map.set(1, [{a: 1}]); + it("deepInclude and notDeepInclude", function () { + var obj1 = {a: 1} + , obj2 = {b: 2}; + assert.deepInclude([obj1, obj2], {a: 1}); + assert.notDeepInclude([obj1, obj2], {a: 9}); + assert.notDeepInclude([obj1, obj2], {z: 1} as any); + assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}}); + assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}}); + assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 9}}); + assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {z: 1}} as any); + assert.notDeepInclude({foo: obj1, bar: obj2}, {baz: {a: 1}} as any); + assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}}); - // assert.deepInclude(map, [{a: 1}]); + var map = new Map(); + map.set(1, [{a: 1}]); - // var set = new Set(); - // set.add([{a: 1}]); + assert.deepInclude(map, [{a: 1}]); - // assert.deepInclude(set, [{a: 1}]); + var set = new Set(); + set.add([{a: 1}]); - // err(function() { - // assert.deepInclude(new WeakSet() as any, {}, "foo"); - // }, "foo: unable to use .deep.include with WeakSet"); + assert.deepInclude(set, [{a: 1}]); - // err(function () { - // assert.deepInclude([obj1, obj2], {a: 9}, "blah"); - // }, "blah: expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }"); + err(function() { + assert.deepInclude(new WeakSet() as any, {}, "foo"); + }, "foo: argument [WeakSet:{}] (\"WeakSet\") cannot be used for deep include operation"); + + err(function () { + assert.deepInclude([obj1, obj2], {a: 9}, "blah"); + }, "blah: expected [{a:1},{b:2}] to deep include {a:9}"); - // err(function () { - // assert.notDeepInclude([obj1, obj2], {a: 1}); - // }, "expected [ { a: 1 }, { b: 2 } ] to not deep include { a: 1 }"); + err(function () { + assert.notDeepInclude([obj1, obj2], {a: 1}); + }, "not expected [{a:1},{b:2}] to deep include {a:1}"); - // err(function () { - // assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}}, "blah"); - // }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to have deep property \"bar\" of { b: 9 }, but got { b: 2 }"); + err(function () { + assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}}, "blah"); + }, "blah: expected {foo:{a:1},bar:{b:2}} to deep include {foo:{a:1},bar:{b:9}}"); - // err(function () { - // assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}}, "blah"); - // }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property \"foo\" of { a: 1 }"); - // }); + err(function () { + assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}}, "blah"); + }, "blah: not expected {foo:{a:1},bar:{b:2}} to deep include {foo:{a:1},bar:{b:2}}"); + }); it("nestedInclude and notNestedInclude", function() { assert.nestedInclude({a: {b: ["x", "y"]}}, {"a.b[1]": "y"}); @@ -978,51 +970,43 @@ describe("assert", function () { }, "blah: not expected {a:{b:[{x:1}]}} to have a nested property \"a.b[0]\" deeply equal {x:1}"); }); - // it("ownInclude and notOwnInclude", function() { - // assert.ownInclude({a: 1}, {a: 1}); - // assert.notOwnInclude({a: 1}, {a: 3}); - // assert.notOwnInclude({a: 1}, {"toString": Object.prototype.toString}); - - // assert.notOwnInclude({a: {b: 2}}, {a: {b: 2}}); - - // err(function () { - // assert.ownInclude({a: 1}, {a: 3}, "blah"); - // }, "blah: expected {a:1} to have own property \"a\" of 3, but got 1"); + it("ownInclude and notOwnInclude", function() { + assert.ownInclude({a: 1}, {a: 1}); + assert.notOwnInclude({a: 1}, {a: 3}); + assert.notOwnInclude({a: 1}, {"toString": Object.prototype.toString}); - // err(function () { - // assert.ownInclude({a: 1}, {a: 3}, "blah"); - // }, "blah: expected {a:1} to have own property \"a\" of 3, but got 1"); + assert.notOwnInclude({a: {b: 2}}, {a: {b: 2}}); - // err(function () { - // assert.ownInclude({a: 1}, {"toString": Object.prototype.toString}); - // }, "expected {a:1} to have own property \"toString\""); + err(function () { + assert.ownInclude({a: 1}, {a: 3}, "blah"); + }, "blah: expected {a:1} to have own properties matching {a:3}"); - // err(function () { - // assert.notOwnInclude({a: 1}, {a: 1}, "blah"); - // }, "blah: expected {a:1} to not have own property \"a\" of 1"); - // }); + err(function () { + assert.ownInclude({a: 1}, {"toString": Object.prototype.toString}); + }, "expected {a:1} to have own properties matching {toString:"); - // it("deepOwnInclude and notDeepOwnInclude", function() { - // assert.deepOwnInclude({a: {b: 2}}, {a: {b: 2}}); - // assert.notDeepOwnInclude({a: {b: 2}}, {a: {c: 3}}); - // assert.notDeepOwnInclude({a: {b: 2}}, {"toString": Object.prototype.toString}); + err(function () { + assert.notOwnInclude({a: 1}, {a: 1}, "blah"); + }, "blah: not expected {a:1} to have own properties matching {a:1}"); + }); - // err(function () { - // assert.deepOwnInclude({a: {b: 2}}, {a: {c: 3}}, "blah"); - // }, "blah: expected {a:{b:2}} to have deep own property \"a\" of {c:3}, but got {b:2}"); + it("deepOwnInclude and notDeepOwnInclude", function() { + assert.deepOwnInclude({a: {b: 2}}, {a: {b: 2}}); + assert.notDeepOwnInclude({a: {b: 2}}, {a: {c: 3}}); + assert.notDeepOwnInclude({a: {b: 2}}, {"toString": Object.prototype.toString}); - // err(function () { - // assert.deepOwnInclude({a: {b: 2}}, {a: {c: 3}}, "blah"); - // }, "blah: expected {a:{b:2}} to have deep own property \"a\" of {c:3}, but got {b:2}"); + err(function () { + assert.deepOwnInclude({a: {b: 2}}, {a: {c: 3}}, "blah"); + }, "blah: expected {a:{b:2}} to have own properties deeply matching {a:{c:3}}"); - // err(function () { - // assert.deepOwnInclude({a: {b: 2}}, {"toString": Object.prototype.toString}); - // }, "expected {a:{b:2}} to have deep own property \"toString\""); + err(function () { + assert.deepOwnInclude({a: {b: 2}}, {"toString": Object.prototype.toString}); + }, "expected {a:{b:2}} to have own properties deeply matching {toString:"); - // err(function () { - // assert.notDeepOwnInclude({a: {b: 2}}, {a: {b: 2}}, "blah"); - // }, "blah: expected {a:{b:2}} to not have deep own property \"a\" of {b:2}"); - // }); + err(function () { + assert.notDeepOwnInclude({a: {b: 2}}, {a: {b: 2}}, "blah"); + }, "blah: not expected {a:{b:2}} to have own properties deeply matching {a:{b:2}}"); + }); it("keys(array|Object|arguments)", function(){ assert.hasAllKeys({ foo: 1 }, [ "foo" ]); @@ -1104,36 +1088,36 @@ describe("assert", function () { testMap.set(aKey, "aValue"); testMap.set(anotherKey, "anotherValue"); - // assert.hasAnyKeys(testMap, [ aKey ]); - // assert.hasAnyKeys(testMap, [ "thisDoesNotExist", "thisToo", aKey ]); - // assert.hasAllKeys(testMap, [ aKey, anotherKey ]); + assert.hasAnyKeys(testMap, [ aKey ]); + assert.hasAnyKeys(testMap, [ "thisDoesNotExist", "thisToo", aKey ]); + assert.hasAllKeys(testMap, [ aKey, anotherKey ]); - // assert.containsAllKeys(testMap, [ aKey ]); - // assert.doesNotHaveAllKeys(testMap, [ aKey, {iDoNot: "exist"} ]); + assert.containsAllKeys(testMap, [ aKey ]); + assert.doesNotHaveAllKeys(testMap, [ aKey, {iDoNot: "exist"} ]); - // assert.doesNotHaveAnyKeys(testMap, [ {iDoNot: "exist"} ]); - // assert.doesNotHaveAnyKeys(testMap, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); - // assert.doesNotHaveAllKeys(testMap, [ "thisDoesNotExist", "thisToo", anotherKey ]); + assert.doesNotHaveAnyKeys(testMap, [ {iDoNot: "exist"} ]); + assert.doesNotHaveAnyKeys(testMap, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); + assert.doesNotHaveAllKeys(testMap, [ "thisDoesNotExist", "thisToo", anotherKey ]); - // assert.doesNotHaveAnyKeys(testMap, [ {iDoNot: "exist"}, "thisDoesNotExist" ]); - // assert.doesNotHaveAnyKeys(testMap, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); - // assert.doesNotHaveAllKeys(testMap, [ aKey, {iDoNot: "exist"} ]); + assert.doesNotHaveAnyKeys(testMap, [ {iDoNot: "exist"}, "thisDoesNotExist" ]); + assert.doesNotHaveAnyKeys(testMap, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); + assert.doesNotHaveAllKeys(testMap, [ aKey, {iDoNot: "exist"} ]); - // // Ensure the assertions above use strict equality - // assert.doesNotHaveAnyKeys(testMap, {thisIs: "anExampleObject"}); - // assert.doesNotHaveAllKeys(testMap, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); + // Ensure the assertions above use strict equality + assert.doesNotHaveAnyKeys(testMap, {thisIs: "anExampleObject"}); + assert.doesNotHaveAllKeys(testMap, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); - // err(function(){ - // assert.hasAnyKeys(testMap, [ {thisIs: "anExampleObject"} ]); - // }); + err(function(){ + assert.hasAnyKeys(testMap, [ {thisIs: "anExampleObject"} ]); + }); - // err(function(){ - // assert.hasAllKeys(testMap, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); - // }); + err(function(){ + assert.hasAllKeys(testMap, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); + }); - // err(function(){ - // assert.containsAllKeys(testMap, [ {thisIs: "anExampleObject"} ]); - // }); + err(function(){ + assert.containsAllKeys(testMap, [ {thisIs: "anExampleObject"} ]); + }); // Tests for the deep variations of the keys assertion assert.hasAnyDeepKeys(testMap, {thisIs: "anExampleObject"}); @@ -1152,65 +1136,64 @@ describe("assert", function () { assert.doesNotHaveAllDeepKeys(testMap, [{twenty: "twenty"}, {thisIs: "anExampleObject"}]); - // var weirdMapKey1 = Object.create(null) - // , weirdMapKey2 = {toString: NaN} - // , weirdMapKey3: any[] = [] - // , weirdMap = new Map(); + var weirdMapKey1 = Object.create(null) + , weirdMapKey2 = {toString: NaN} + , weirdMapKey3: any[] = [] + , weirdMap = new Map(); - // weirdMap.set(weirdMapKey1, "val1"); - // weirdMap.set(weirdMapKey2, "val2"); + weirdMap.set(weirdMapKey1, "val1"); + weirdMap.set(weirdMapKey2, "val2"); - // assert.hasAllKeys(weirdMap, [weirdMapKey1, weirdMapKey2]); - // assert.doesNotHaveAllKeys(weirdMap, [weirdMapKey1, weirdMapKey3]); + assert.hasAllKeys(weirdMap, [weirdMapKey1, weirdMapKey2]); + assert.doesNotHaveAllKeys(weirdMap, [weirdMapKey1, weirdMapKey3]); - // var symMapKey1 = Symbol() - // , symMapKey2 = Symbol() - // , symMapKey3 = Symbol() - // , symMap = new Map(); + var symMapKey1 = Symbol() + , symMapKey2 = Symbol() + , symMapKey3 = Symbol() + , symMap = new Map(); - // symMap.set(symMapKey1, "val1"); - // symMap.set(symMapKey2, "val2"); + symMap.set(symMapKey1, "val1"); + symMap.set(symMapKey2, "val2"); - // assert.hasAllKeys(symMap, [symMapKey1, symMapKey2]); - // assert.hasAnyKeys(symMap, [symMapKey1, symMapKey3]); - // assert.containsAllKeys(symMap, [symMapKey2, symMapKey1]); + assert.hasAllKeys(symMap, [symMapKey1, symMapKey2]); + assert.hasAnyKeys(symMap, [symMapKey1, symMapKey3]); + assert.containsAllKeys(symMap, [symMapKey2, symMapKey1]); - // assert.doesNotHaveAllKeys(symMap, [symMapKey1, symMapKey3]); - // assert.doesNotHaveAnyKeys(symMap, [symMapKey3]); + assert.doesNotHaveAllKeys(symMap, [symMapKey1, symMapKey3]); + assert.doesNotHaveAnyKeys(symMap, [symMapKey3]); - // var errMap = new Map(); + var errMap = new Map(); - // errMap.set({1: 20}, "number"); - - // err(function(){ - // assert.hasAllKeys(errMap, [], "blah"); - // }, "blah: keys required"); + errMap.set({1: 20}, "number"); - // err(function(){ - // assert.containsAllKeys(errMap, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.hasAllKeys(errMap, [], "blah"); + }, "blah: expected at least one key to be provided []"); - // err(function(){ - // assert.doesNotHaveAllKeys(errMap, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.containsAllKeys(errMap, [], "blah"); + }, "blah: expected at least one key to be provided []"); + err(function(){ + assert.doesNotHaveAllKeys(errMap, [], "blah"); + }, "blah: expected at least one key to be provided []"); - // err(function(){ - // assert.hasAnyKeys(errMap, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.hasAnyKeys(errMap, [], "blah"); + }, "blah: expected at least one key to be provided []"); - // err(function(){ - // assert.doesNotHaveAnyKeys(errMap, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.doesNotHaveAnyKeys(errMap, [], "blah"); + }, "blah: expected at least one key to be provided []"); // Uncomment this after solving https://github.com/chaijs/chai/issues/662 // This should fail because of referential equality (this is a strict comparison) - // err(function(){ - // assert.containsAllKeys(new Map([[{foo: 1}, \"bar\"]]), { foo: 1 }); - // }, 'expected [ [ { foo: 1 }, \"bar\" ] ] to contain key { foo: 1 }'); + err(function(){ + assert.containsAllKeys(new Map([[{foo: 1}, "bar"]]), { foo: 1 }); + }, "expected all keys: [\"foo\"], missing: [\"foo\"], found: [{foo:1}]"); - // err(function(){ - // assert.containsAllDeepKeys(new Map([[{foo: 1}, \"bar\"]]), { iDoNotExist: 0 }) - // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }'); + err(function(){ + assert.containsAllDeepKeys(new Map([[{foo: 1}, "bar"]]), { iDoNotExist: 0 }); + }, "expected all deep keys: [{iDoNotExist:0}], missing: [{iDoNotExist:0}], found: [{foo:1}]"); var aKey = {thisIs: "anExampleObject"} , anotherKey = {doingThisBecauseOf: "referential equality"} @@ -1219,36 +1202,36 @@ describe("assert", function () { testSet.add(aKey); testSet.add(anotherKey); - // assert.hasAnyKeys(testSet, [ aKey ]); - // assert.hasAnyKeys(testSet, [ 20, 1, aKey ]); - // assert.hasAllKeys(testSet, [ aKey, anotherKey ]); + assert.hasAnyKeys(testSet, [ aKey ]); + assert.hasAnyKeys(testSet, [ 20, 1, aKey ]); + assert.hasAllKeys(testSet, [ aKey, anotherKey ]); - // assert.containsAllKeys(testSet, [ aKey ]); - // assert.doesNotHaveAllKeys(testSet, [ aKey, {iDoNot: "exist"} ]); + assert.containsAllKeys(testSet, [ aKey ]); + assert.doesNotHaveAllKeys(testSet, [ aKey, {iDoNot: "exist"} ]); - // assert.doesNotHaveAnyKeys(testSet, [ {iDoNot: "exist"} ]); - // assert.doesNotHaveAnyKeys(testSet, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); - // assert.doesNotHaveAllKeys(testSet, [ "thisDoesNotExist", "thisToo", anotherKey ]); + assert.doesNotHaveAnyKeys(testSet, [ {iDoNot: "exist"} ]); + assert.doesNotHaveAnyKeys(testSet, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); + assert.doesNotHaveAllKeys(testSet, [ "thisDoesNotExist", "thisToo", anotherKey ]); - // assert.doesNotHaveAnyKeys(testSet, [ {iDoNot: "exist"}, "thisDoesNotExist" ]); - // assert.doesNotHaveAnyKeys(testSet, [ 20, 1, {iDoNot: "exist"} ]); - // assert.doesNotHaveAllKeys(testSet, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); + assert.doesNotHaveAnyKeys(testSet, [ {iDoNot: "exist"}, "thisDoesNotExist" ]); + assert.doesNotHaveAnyKeys(testSet, [ 20, 1, {iDoNot: "exist"} ]); + assert.doesNotHaveAllKeys(testSet, [ "thisDoesNotExist", "thisToo", {iDoNot: "exist"} ]); - // // Ensure the assertions above use strict equality - // assert.doesNotHaveAnyKeys(testSet, {thisIs: "anExampleObject"}); - // assert.doesNotHaveAllKeys(testSet, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); + // Ensure the assertions above use strict equality + assert.doesNotHaveAnyKeys(testSet, {thisIs: "anExampleObject"}); + assert.doesNotHaveAllKeys(testSet, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); - // err(function(){ - // assert.hasAnyKeys(testSet, [ {thisIs: "anExampleObject"} ]); - // }); + err(function(){ + assert.hasAnyKeys(testSet, [ {thisIs: "anExampleObject"} ]); + }); - // err(function(){ - // assert.hasAllKeys(testSet, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); - // }); + err(function(){ + assert.hasAllKeys(testSet, [ {thisIs: "anExampleObject"}, {doingThisBecauseOf: "referential equality"} ]); + }); - // err(function(){ - // assert.containsAllKeys(testSet, [ {thisIs: "anExampleObject"} ]); - // }); + err(function(){ + assert.containsAllKeys(testSet, [ {thisIs: "anExampleObject"} ]); + }); // Tests for the deep variations of the keys assertion assert.hasAnyDeepKeys(testSet, {thisIs: "anExampleObject"}); @@ -1268,66 +1251,65 @@ describe("assert", function () { assert.doesNotHaveAllDeepKeys(testSet, [{thisIs: "anExampleObject"}, {fifty: "fifty"}]); - // var weirdSetKey1 = Object.create(null) - // , weirdSetKey2 = {toString: NaN} - // , weirdSetKey3: any[] = [] - // , weirdSet = new Set(); - - // weirdSet.add(weirdSetKey1); - // weirdSet.add(weirdSetKey2); + var weirdSetKey1 = Object.create(null) + , weirdSetKey2 = {toString: NaN} + , weirdSetKey3: any[] = [] + , weirdSet = new Set(); - // assert.hasAllKeys(weirdSet, [weirdSetKey1, weirdSetKey2]); - // assert.doesNotHaveAllKeys(weirdSet, [weirdSetKey1, weirdSetKey3]); + weirdSet.add(weirdSetKey1); + weirdSet.add(weirdSetKey2); - // var symSetKey1 = Symbol() - // , symSetKey2 = Symbol() - // , symSetKey3 = Symbol() - // , symSet = new Set(); + assert.hasAllKeys(weirdSet, [weirdSetKey1, weirdSetKey2]); + assert.doesNotHaveAllKeys(weirdSet, [weirdSetKey1, weirdSetKey3]); - // symSet.add(symSetKey1); - // symSet.add(symSetKey2); + var symSetKey1 = Symbol() + , symSetKey2 = Symbol() + , symSetKey3 = Symbol() + , symSet = new Set(); - // assert.hasAllKeys(symSet, [symSetKey1, symSetKey2]); - // assert.hasAnyKeys(symSet, [symSetKey1, symSetKey3]); - // assert.containsAllKeys(symSet, [symSetKey2, symSetKey1]); + symSet.add(symSetKey1); + symSet.add(symSetKey2); - // assert.doesNotHaveAllKeys(symSet, [symSetKey1, symSetKey3]); - // assert.doesNotHaveAnyKeys(symSet, [symSetKey3]); + assert.hasAllKeys(symSet, [symSetKey1, symSetKey2]); + assert.hasAnyKeys(symSet, [symSetKey1, symSetKey3]); + assert.containsAllKeys(symSet, [symSetKey2, symSetKey1]); - // var errSet = new Set(); + assert.doesNotHaveAllKeys(symSet, [symSetKey1, symSetKey3]); + assert.doesNotHaveAnyKeys(symSet, [symSetKey3]); - // errSet.add({1: 20}); - // errSet.add("number"); + var errSet = new Set(); - // err(function(){ - // assert.hasAllKeys(errSet, [], "blah"); - // }, "blah: keys required"); + errSet.add({1: 20}); + errSet.add("number"); - // err(function(){ - // assert.containsAllKeys(errSet, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.hasAllKeys(errSet, [], "blah"); + }, "blah: expected at least one key to be provided []"); - // err(function(){ - // assert.doesNotHaveAllKeys(errSet, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.containsAllKeys(errSet, [], "blah"); + }, "blah: expected at least one key to be provided []"); + err(function(){ + assert.doesNotHaveAllKeys(errSet, [], "blah"); + }, "blah: expected at least one key to be provided []"); - // err(function(){ - // assert.hasAnyKeys(errSet, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.hasAnyKeys(errSet, [], "blah"); + }, "blah: expected at least one key to be provided []"); - // err(function(){ - // assert.doesNotHaveAnyKeys(errSet, [], "blah"); - // }, "blah: keys required"); + err(function(){ + assert.doesNotHaveAnyKeys(errSet, [], "blah"); + }, "blah: expected at least one key to be provided []"); // Uncomment this after solving https://github.com/chaijs/chai/issues/662 // This should fail because of referential equality (this is a strict comparison) - // err(function(){ - // assert.containsAllKeys(new Set([{foo: 1}]), { foo: 1 }); - // }, 'expected [ [ { foo: 1 }, \"bar\" ] ] to contain key { foo: 1 }'); + err(function(){ + assert.containsAllKeys(new Set([{foo: 1}]), { foo: 1 }); + }, "expected all keys: [\"foo\"], missing: [\"foo\"], found: [{foo:1}]"); - // err(function(){ - // assert.containsAllDeepKeys(new Set([{foo: 1}]), { iDoNotExist: 0 }) - // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }'); + err(function(){ + assert.containsAllDeepKeys(new Set([{foo: 1}]), { iDoNotExist: 0 }); + }, "expected all deep keys: [{iDoNotExist:0}], missing: [{iDoNotExist:0}], found: [{foo:1}]"); err(function(){ assert.hasAllKeys({ foo: 1 }, [], "blah"); @@ -1351,23 +1333,23 @@ describe("assert", function () { err(function(){ assert.hasAllKeys({ foo: 1 }, ["bar"], "blah"); - }, "blah: expected all keys: [bar], missing: [bar], found: [foo]"); + }, "blah: expected all keys: [\"bar\"], missing: [\"bar\"], found: [\"foo\"]"); err(function(){ assert.hasAllKeys({ foo: 1 }, ["bar", "baz"]); - }, "expected all keys: [bar,baz], missing: [bar,baz], found: [foo]"); + }, "expected all keys: [\"bar\",\"baz\"], missing: [\"bar\",\"baz\"], found: [\"foo\"]"); err(function(){ assert.hasAllKeys({ foo: 1 }, ["foo", "bar", "baz"]); - }, "expected all keys: [foo,bar,baz], missing: [bar,baz], found: [foo]"); + }, "expected all keys: [\"foo\",\"bar\",\"baz\"], missing: [\"bar\",\"baz\"], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1 }, ["foo"], "blah"); - }, "blah: not expected all keys: [foo], missing: [], found: [foo]"); + }, "blah: not expected all keys: [\"foo\"], missing: [], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1, bar: 2 }, ["foo", "bar"]); - }, "not expected all keys: [foo,bar], missing: [], found: [foo,bar]"); + }, "not expected all keys: [\"foo\",\"bar\"], missing: [], found: [\"foo\",\"bar\"]"); // err(function(){ assert.hasAllKeys({ foo: 1, bar: 2 }, ["foo"]); @@ -1375,56 +1357,55 @@ describe("assert", function () { err(function(){ assert.containsAllKeys({ foo: 1 }, ["foo", "bar"], "blah"); - }, "blah: expected all keys: [foo,bar], missing: [bar], found: [foo]", true); + }, "blah: expected all keys: [\"foo\",\"bar\"], missing: [\"bar\"], found: [\"foo\"]", true); err(function() { assert.hasAnyKeys({ foo: 1 }, ["baz"], "blah"); - }, "blah: expected any key: [baz], found: [foo]"); + }, "blah: expected any key: [\"baz\"], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1, bar: 2 }, ["foo", "bar"]); - }, "not expected all keys: [foo,bar], missing: [], found: [foo,bar]"); + }, "not expected all keys: [\"foo\",\"bar\"], missing: [], found: [\"foo\",\"bar\"]"); err(function(){ assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, ["foo", "baz"], "blah"); - }, "blah: not expected any key: [foo,baz], found: [foo,bar] (2 keys)"); + }, "blah: not expected any key: [\"foo\",\"baz\"], found: [\"foo\",\"bar\"]"); // repeat previous tests with Object as arg. err(function(){ assert.hasAllKeys({ foo: 1 }, { "bar": 1 }, "blah"); - }, "blah: expected all keys: [bar], missing: [bar], found: [foo]"); + }, "blah: expected all keys: [\"bar\"], missing: [\"bar\"], found: [\"foo\"]"); err(function(){ assert.hasAllKeys({ foo: 1 }, { "bar": 1, "baz": 1}); - }, "expected all keys: [bar,baz], missing: [bar,baz], found: [foo]"); - + }, "expected all keys: [\"bar\",\"baz\"], missing: [\"bar\",\"baz\"], found: [\"foo\"]"); err(function(){ assert.hasAllKeys({ foo: 1 }, { "foo": 1, "bar": 1, "baz": 1}); - }, "expected all keys: [foo,bar,baz], missing: [bar,baz], found: [foo]"); + }, "expected all keys: [\"foo\",\"bar\",\"baz\"], missing: [\"bar\",\"baz\"], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1 }, { "foo": 1 }, "blah"); - }, "blah: not expected all keys: [foo], missing: [], found: [foo]"); + }, "blah: not expected all keys: [\"foo\"], missing: [], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1 }, { "foo": 1 }); - }, "not expected all keys: [foo], missing: [], found: [foo]"); + }, "not expected all keys: [\"foo\"], missing: [], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1, bar: 2 }, { "foo": 1, "bar": 1}); - }, "not expected all keys: [foo,bar], missing: [], found: [foo,bar]"); + }, "not expected all keys: [\"foo\",\"bar\"], missing: [], found: [\"foo\",\"bar\"]"); err(function() { assert.hasAnyKeys({ foo: 1 }, "baz" as any, "blah"); - }, "blah: expected any key: [baz], found: [foo] (1 keys)"); + }, "blah: expected any key: [\"baz\"], found: [\"foo\"]"); err(function(){ assert.doesNotHaveAllKeys({ foo: 1, bar: 2 }, { "foo": 1, "bar": 1}); - }, "not expected all keys: [foo,bar], missing: [], found: [foo,bar]"); + }, "not expected all keys: [\"foo\",\"bar\"], missing: [], found: [\"foo\",\"bar\"]"); err(function(){ assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, { "foo": 1, "baz": 1}, "blah"); - }, "blah: not expected any key: [foo,baz], found: [foo,bar] (2 keys)"); + }, "blah: not expected any key: [\"foo\",\"baz\"], found: [\"foo\",\"bar\"]"); }); it("lengthOf", function() { @@ -1449,7 +1430,7 @@ describe("assert", function () { err(function(){ assert.lengthOf(map, 3, "blah"); - }, "blah: expected [Map:{}] to have a size of 3 but got 2"); + }, "blah: expected Map:{a:1,b:2} to have a size of 3 but got 2"); assert.lengthOf(new Set(), 0); @@ -1461,7 +1442,7 @@ describe("assert", function () { err(function(){ assert.lengthOf(set, 3, "blah"); - }, "blah: expected [Set:{}] to have a size of 3 but got 2"); + }, "blah: expected Set:{1,2} to have a size of 3 but got 2"); }); it("match", function () { @@ -2975,7 +2956,7 @@ describe("assert", function () { err(function(){ (assert as any)[isNotEmpty](new Map()); - }, "not expected [Map:{}] to be empty"); + }, "not expected Map:{} to be empty"); var set = new Set(); set.add(1); @@ -2983,7 +2964,7 @@ describe("assert", function () { err(function(){ (assert as any)[isNotEmpty](new Set()); - }, "not expected [Set:{}] to be empty"); + }, "not expected Set:{} to be empty"); err(function(){ (assert as any)[isNotEmpty]("", "blah");