Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/src/assert/adapters/exprAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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(";")
Expand Down
20 changes: 17 additions & 3 deletions core/src/assert/assertClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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 = {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion core/src/assert/funcs/changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function _getTargetValue<T = any>(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];
Expand Down
6 changes: 3 additions & 3 deletions core/src/assert/funcs/closeTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ export function closeToFunc<R>(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
Expand Down
4 changes: 2 additions & 2 deletions core/src/assert/funcs/equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand Down Expand Up @@ -281,7 +281,7 @@ function _isVisiting<T>(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);
}
Expand Down
10 changes: 5 additions & 5 deletions core/src/assert/funcs/hasProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion core/src/assert/funcs/instanceOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function instanceOfFunc<R>(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";
Expand Down
10 changes: 5 additions & 5 deletions core/src/assert/funcs/isEmpty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand All @@ -26,18 +26,18 @@ export function isEmptyFunc<R>(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<boolean>(() => "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");
Expand Down
53 changes: 19 additions & 34 deletions core/src/assert/funcs/keysFunc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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)) {
Expand Down Expand Up @@ -167,7 +138,10 @@ export function anyKeysFunc<R>(_scope: IAssertScope): KeysFn<R> {
}
});

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;
}
Expand Down Expand Up @@ -196,7 +170,11 @@ export function allKeysFunc<R>(_scope: IAssertScope): KeysFn<R> {
}
});

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;
}
Expand Down Expand Up @@ -264,7 +242,10 @@ export function anyDeepKeysFunc<R>(_scope: IAssertScope): KeysFn<R> {
}
});

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;
}
Expand Down Expand Up @@ -334,7 +315,11 @@ export function allDeepKeysFunc<R>(_scope: IAssertScope): KeysFn<R> {
}
});

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;
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/assert/funcs/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand All @@ -17,7 +17,7 @@ export function matchFunc<R>(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;
Expand Down
4 changes: 2 additions & 2 deletions core/src/assert/funcs/members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/assert/funcs/nested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion core/src/assert/funcs/oneOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function oneOfFunc<R>(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;
Expand Down
2 changes: 1 addition & 1 deletion core/src/assert/funcs/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function operatorFunc<R>(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;
}

Expand Down
Loading
Loading