Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7248f70804 | |||
| 0054271de6 | |||
| dbec1d3e4a | |||
| ff9170ab67 | |||
| b68011b79d | |||
| ff795f6fe0 | |||
| 62cf7f5db5 | |||
| 0351da2878 |
29
changelog.md
29
changelog.md
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-05-23 - 2.5.0 - feat(Assertion)
|
||||
Add missing alias methods for length and emptiness checks and update documentation
|
||||
|
||||
- Updated readme.hints.md with detailed project hints, feature overview, and common patterns
|
||||
- Added direct alias methods in smartexpect.classes.assertion.ts for string/array length checks (toHaveLength, toBeEmpty)
|
||||
- Introduced additional delegations for numeric and array namespace methods (toHaveLengthGreaterThan, toHaveLengthLessThan)
|
||||
- Enhanced function namespace with direct matchers for error checking (toThrowErrorMatching, toThrowErrorWithMessage)
|
||||
|
||||
## 2025-05-01 - 2.4.2 - fix(cleanup)
|
||||
Remove unused scratch files
|
||||
|
||||
- Deleted scratch-alias.js, scratch-alias2.js, scratch-alias3.js, scratch-alias4.js, scratch-alias5.js, and scratch.js
|
||||
- Clean up temporary alias and scratch test files
|
||||
|
||||
## 2025-04-30 - 2.4.1 - fix(Assertion)
|
||||
Improve toHaveProperty alias by forwarding arguments correctly for intuitive object property assertions
|
||||
|
||||
- Updated the toHaveProperty method in the Assertion class to check the number of arguments and call the appropriate object matcher
|
||||
- Added several scratch alias files to demonstrate and test the alias usage
|
||||
- Enhanced test cases in test/propertypath to cover alias behavior
|
||||
|
||||
## 2025-04-30 - 2.4.0 - feat(object)
|
||||
add toHaveOwnProperty method and improve property-path matching in object assertions
|
||||
|
||||
- Added 'toHaveOwnProperty' as a direct method on Assertion to check for own properties
|
||||
- Enhanced property path evaluation in 'toHaveProperty' to handle nested keys more robustly
|
||||
- Renamed test file to maintain consistent naming for expect.any tests
|
||||
- Introduced scratch.js for manual testing and debugging of property matchers
|
||||
|
||||
## 2025-04-30 - 2.3.3 - fix(tests)
|
||||
Fix test file naming inconsistencies
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartexpect",
|
||||
"version": "2.3.3",
|
||||
"version": "2.5.0",
|
||||
"private": false,
|
||||
"description": "A testing library to manage expectations in code, offering both synchronous and asynchronous assertion methods.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
||||
@@ -1 +1,55 @@
|
||||
|
||||
# SmartExpect - Project Hints
|
||||
|
||||
## Project Overview
|
||||
- **Name**: @push.rocks/smartexpect
|
||||
- **Purpose**: A minimal, promise-first assertion library for testing with TypeScript support
|
||||
- **License**: MIT
|
||||
- **Version**: 2.4.2
|
||||
|
||||
## Architecture
|
||||
1. **Core Assertion Class** (`ts/smartexpect.classes.assertion.ts`):
|
||||
- Central `Assertion<T, M>` class handles all assertion logic
|
||||
- Supports sync and async execution modes
|
||||
- Property drilling with `.property()` and `.arrayItem()`
|
||||
- Custom matcher extension via `expect.extend()`
|
||||
|
||||
2. **Namespace Organization** (`ts/namespaces/`):
|
||||
- Matchers grouped by type: string, number, array, object, boolean, function, date, type
|
||||
- Each namespace provides type-specific assertions
|
||||
- All namespaces extend the base Assertion class
|
||||
|
||||
3. **Entry Point** (`ts/index.ts`):
|
||||
- Exports the main `expect()` function
|
||||
- Auto-detects promises for async mode
|
||||
- Provides `expect.any()` and `expect.anything()` utility matchers
|
||||
|
||||
## Key Features
|
||||
- **Async-first**: `.resolves` and `.rejects` modifiers for promise assertions
|
||||
- **Timeout support**: `.withTimeout(ms)` for async assertions
|
||||
- **Negation**: `.not` modifier inverts any assertion
|
||||
- **Property navigation**: Chain `.property()` and `.arrayItem()` for nested assertions
|
||||
- **Custom matchers**: Extend with `expect.extend({ matcherName: fn })`
|
||||
- **Debugging**: `.log()` method to inspect values during assertion chains
|
||||
|
||||
## Dependencies
|
||||
- `@push.rocks/smartdelay`: For async timeout handling
|
||||
- `@push.rocks/smartpromise`: For promise utilities
|
||||
- `fast-deep-equal`: For deep equality comparisons
|
||||
|
||||
## Testing
|
||||
- Tests use `@git.zone/tstest` with tap
|
||||
- Test files import from compiled `dist_ts/` directory
|
||||
- Test naming: `*.both.ts` (browser+node), `*.node.ts`, `*.browser.ts`
|
||||
- Run with `pnpm test`
|
||||
|
||||
## Recent Changes
|
||||
- v2.4.2: General maintenance
|
||||
- v2.4.1: Fixed toHaveProperty alias to forward arguments correctly
|
||||
- v2.4.0: Major improvements (details not specified)
|
||||
|
||||
## Common Patterns
|
||||
1. **Basic assertions**: `expect(value).toEqual(expected)`
|
||||
2. **Async assertions**: `expect(promise).resolves.toEqual(value)`
|
||||
3. **Property drilling**: `expect(obj).property('nested').property('value').toEqual(x)`
|
||||
4. **Array navigation**: `expect(arr).arrayItem(0).toEqual(firstItem)`
|
||||
5. **Custom messages**: `expect(x).setFailMessage('Custom error').toEqual(y)`
|
||||
@@ -2,7 +2,10 @@ import { tap, expect as tExpect } from '@push.rocks/tapbundle';
|
||||
import * as smartexpect from '../dist_ts/index.js';
|
||||
|
||||
tap.test('toHaveProperty nested path via dot notation', async () => {
|
||||
const testObject = { level1: { level2: { level3: 'value' } } };
|
||||
const testObject = { level1: { level2: { level3: 'value' }}, publicTest: 'hi' };
|
||||
|
||||
smartexpect.expect(testObject).object.toHaveProperty('publicTest');
|
||||
smartexpect.expect(testObject).toHaveProperty('publicTest');
|
||||
// Existence check
|
||||
smartexpect.expect(testObject).object.toHaveProperty('level1.level2.level3');
|
||||
// Value check
|
||||
@@ -11,4 +14,4 @@ tap.test('toHaveProperty nested path via dot notation', async () => {
|
||||
smartexpect.expect(testObject).not.object.toHaveProperty('level1.level2.missing');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
export default tap.start();
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartexpect',
|
||||
version: '2.3.3',
|
||||
version: '2.5.0',
|
||||
description: 'A testing library to manage expectations in code, offering both synchronous and asynchronous assertion methods.'
|
||||
}
|
||||
|
||||
@@ -62,13 +62,21 @@ export class ObjectMatchers<T extends object, M extends TExecutionType> {
|
||||
return this.assertion.customAssertion(
|
||||
(v) => {
|
||||
const obj = v as any;
|
||||
// first check for a literal property (including inherited)
|
||||
if (property in obj) {
|
||||
if (arguments.length === 2) {
|
||||
return plugins.fastDeepEqual(obj[property], value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// no direct key, try nested path via dot notation
|
||||
const path = property.split('.');
|
||||
let current = obj;
|
||||
for (const key of path) {
|
||||
if (current == null || !(key in current)) {
|
||||
return false;
|
||||
}
|
||||
current = current[key];
|
||||
current = (current as any)[key];
|
||||
}
|
||||
if (arguments.length === 2) {
|
||||
return plugins.fastDeepEqual(current, value);
|
||||
|
||||
@@ -355,7 +355,14 @@ export class Assertion<T = unknown, M extends TExecutionType = 'sync'> {
|
||||
public toInclude(substring: string) { return this.string.toInclude(substring); }
|
||||
public toMatch(regex: RegExp) { return this.string.toMatch(regex); }
|
||||
public toBeOneOf(values: any[]) { return this.string.toBeOneOf(values as string[]); }
|
||||
public toHaveProperty(property: string, value?: any) { return this.object.toHaveProperty(property, value); }
|
||||
public toHaveProperty(property: string, value?: any) {
|
||||
// Forward only provided arguments to object matcher to preserve argument count
|
||||
if (arguments.length === 2) {
|
||||
return this.object.toHaveProperty(property, value);
|
||||
}
|
||||
return this.object.toHaveProperty(property);
|
||||
}
|
||||
public toHaveOwnProperty(property: string, value?: any) { return this.object.toHaveOwnProperty(property, value); }
|
||||
public toMatchObject(expected: object) { return this.object.toMatchObject(expected); }
|
||||
public toBeInstanceOf(constructor: any) { return this.object.toBeInstanceOf(constructor); }
|
||||
public toHaveDeepProperty(path: string[]) { return this.object.toHaveDeepProperty(path); }
|
||||
@@ -371,6 +378,54 @@ export class Assertion<T = unknown, M extends TExecutionType = 'sync'> {
|
||||
public toBeTypeOf(typeName: string) { return this.type.toBeTypeOf(typeName); }
|
||||
public toBeDefined() { return this.type.toBeDefined(); }
|
||||
|
||||
// Additional missing direct aliases for completeness
|
||||
// String/Array namespace - intelligently delegate based on value type
|
||||
public toHaveLength(length: number) {
|
||||
// Determine if value is string or array and delegate accordingly
|
||||
const value = this.getObjectToTestReference();
|
||||
if (typeof value === 'string') {
|
||||
return this.string.toHaveLength(length);
|
||||
} else if (Array.isArray(value)) {
|
||||
return this.array.toHaveLength(length);
|
||||
} else {
|
||||
return this.customAssertion(
|
||||
() => false,
|
||||
'Expected value to be string or array to check length'
|
||||
);
|
||||
}
|
||||
}
|
||||
public toBeEmpty() {
|
||||
// Determine if value is string or array and delegate accordingly
|
||||
const value = this.getObjectToTestReference();
|
||||
if (typeof value === 'string') {
|
||||
return this.string.toBeEmpty();
|
||||
} else if (Array.isArray(value)) {
|
||||
return this.array.toBeEmpty();
|
||||
} else {
|
||||
return this.customAssertion(
|
||||
() => false,
|
||||
'Expected value to be string or array to check if empty'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Number namespace
|
||||
public toBeNaN() { return this.number.toBeNaN(); }
|
||||
public toBeFinite() { return this.number.toBeFinite(); }
|
||||
public toBeWithinRange(min: number, max: number) { return this.number.toBeWithinRange(min, max); }
|
||||
|
||||
// Array namespace length comparisons
|
||||
public toHaveLengthGreaterThan(length: number) { return this.array.toHaveLengthGreaterThan(length); }
|
||||
public toHaveLengthLessThan(length: number) { return this.array.toHaveLengthLessThan(length); }
|
||||
|
||||
// Object namespace
|
||||
public toHaveKeys(keys: string[]) { return this.object.toHaveKeys(keys); }
|
||||
public toHaveOwnKeys(keys: string[]) { return this.object.toHaveOwnKeys(keys); }
|
||||
|
||||
// Function namespace
|
||||
public toThrowErrorMatching(regex: RegExp) { return this.function.toThrowErrorMatching(regex); }
|
||||
public toThrowErrorWithMessage(message: string) { return this.function.toThrowErrorWithMessage(message); }
|
||||
|
||||
// Namespaced matcher accessors
|
||||
/** String-specific matchers */
|
||||
public get string(): StringMatchers<M> {
|
||||
|
||||
Reference in New Issue
Block a user