Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
b68011b79d | |||
ff795f6fe0 | |||
62cf7f5db5 | |||
0351da2878 | |||
0d3d498240 | |||
30604dc77b | |||
84fd23d6a4 | |||
e7941e7b99 |
28
changelog.md
28
changelog.md
@ -1,5 +1,33 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- Rename 'test/test.diffOutput.ts' to 'test/test.diffoutput.ts' to standardize filename casing
|
||||||
|
- Rename 'test/test.propertyPath.ts' to 'test/test.propertypath.ts' for consistent file naming
|
||||||
|
|
||||||
|
## 2025-04-30 - 2.3.2 - fix(object)
|
||||||
|
Update toHaveProperty matcher to support nested property paths using dot notation
|
||||||
|
|
||||||
|
- Changed toHaveProperty implementation to split property strings on '.' and traverse nested objects
|
||||||
|
- Fixed value comparison for nested properties by comparing the final drilled value instead of direct property access
|
||||||
|
- Added tests for nested property access in test/propertyPath.ts
|
||||||
|
|
||||||
## 2025-04-30 - 2.3.1 - fix(readme)
|
## 2025-04-30 - 2.3.1 - fix(readme)
|
||||||
Improve README documentation with detailed 'Why SmartExpect' benefits section
|
Improve README documentation with detailed 'Why SmartExpect' benefits section
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartexpect",
|
"name": "@push.rocks/smartexpect",
|
||||||
"version": "2.3.1",
|
"version": "2.4.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A testing library to manage expectations in code, offering both synchronous and asynchronous assertion methods.",
|
"description": "A testing library to manage expectations in code, offering both synchronous and asynchronous assertion methods.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
14
scratch-alias.js
Normal file
14
scratch-alias.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import * as smartexpect from './dist_ts/index.js';
|
||||||
|
const obj = { topLevel: 'hello' };
|
||||||
|
try {
|
||||||
|
smartexpect.expect(obj).toHaveProperty('topLevel');
|
||||||
|
console.log('alias toHaveProperty succeeded');
|
||||||
|
} catch(err) {
|
||||||
|
console.error('alias toHaveProperty failed:', err.message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
smartexpect.expect(obj).not.toHaveProperty('missing');
|
||||||
|
console.log('alias not.toHaveProperty succeeded');
|
||||||
|
} catch(err) {
|
||||||
|
console.error('alias not.toHaveProperty failed:', err.message);
|
||||||
|
}
|
20
scratch-alias2.js
Normal file
20
scratch-alias2.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import * as smartexpect from './dist_ts/index.js';
|
||||||
|
console.log('script start');
|
||||||
|
const obj = { topLevel: 'hello' };
|
||||||
|
console.log('script: after obj');
|
||||||
|
console.log('script: test for alias toHaveProperty');
|
||||||
|
try {
|
||||||
|
smartexpect.expect(obj).toHaveProperty('topLevel');
|
||||||
|
console.log('alias toHaveProperty succeeded');
|
||||||
|
} catch(err) {
|
||||||
|
console.error('alias toHaveProperty failed:', err.message);
|
||||||
|
}
|
||||||
|
console.log('script: after first try');
|
||||||
|
console.log('script: test for alias not.toHaveProperty');
|
||||||
|
try {
|
||||||
|
smartexpect.expect(obj).not.toHaveProperty('missing');
|
||||||
|
console.log('alias not.toHaveProperty succeeded');
|
||||||
|
} catch(err) {
|
||||||
|
console.error('alias not.toHaveProperty failed:', err.message);
|
||||||
|
}
|
||||||
|
console.log('script end');
|
8
scratch-alias3.js
Normal file
8
scratch-alias3.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as smartexpect from './dist_ts/index.js';
|
||||||
|
console.log('script start');
|
||||||
|
const obj = { topLevel: 'hello' };
|
||||||
|
console.log('script: test alias toHaveProperty');
|
||||||
|
const ret = smartexpect.expect(obj).toHaveProperty('topLevel');
|
||||||
|
console.log('got ret:', ret, 'typeof', typeof ret);
|
||||||
|
console.log('ret.then?', ret && typeof (ret as any).then);
|
||||||
|
console.log('script end');
|
8
scratch-alias4.js
Normal file
8
scratch-alias4.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as smartexpect from './dist_ts/index.js';
|
||||||
|
console.log('script start');
|
||||||
|
const obj = { topLevel: 'hello' };
|
||||||
|
console.log('script: test alias toHaveProperty');
|
||||||
|
const ret = smartexpect.expect(obj).toHaveProperty('topLevel');
|
||||||
|
console.log('got ret:', ret, 'typeof', typeof ret);
|
||||||
|
console.log('ret.then?', ret && ret.then);
|
||||||
|
console.log('script end');
|
10
scratch-alias5.js
Normal file
10
scratch-alias5.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as smartexpect from './dist_ts/index.js';
|
||||||
|
console.log('start');
|
||||||
|
const obj = { topLevel: 'hello' };
|
||||||
|
try {
|
||||||
|
smartexpect.expect(obj).toHaveProperty('topLevel');
|
||||||
|
console.log('success');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('caught in catch:', e, e.message);
|
||||||
|
}
|
||||||
|
console.log('end');
|
16
scratch.js
Normal file
16
scratch.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as smartexpect from './dist_ts/index.js';
|
||||||
|
class Foo { constructor(){ this.foo='bar'; } }
|
||||||
|
console.log('foo in instance:', 'foo' in new Foo());
|
||||||
|
console.log('hasOwn foo:', Object.prototype.hasOwnProperty.call(new Foo(), 'foo'));
|
||||||
|
try {
|
||||||
|
smartexpect.expect(new Foo()).object.toHaveProperty('foo');
|
||||||
|
console.log('toHaveProperty passed');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('toHaveProperty failed:', err.message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
smartexpect.expect(new Foo()).object.toHaveOwnProperty('foo');
|
||||||
|
console.log('toHaveOwnProperty passed');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('toHaveOwnProperty failed:', err.message);
|
||||||
|
}
|
17
test/test.propertypath.ts
Normal file
17
test/test.propertypath.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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' }}, 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
|
||||||
|
smartexpect.expect(testObject).object.toHaveProperty('level1.level2.level3', 'value');
|
||||||
|
// Negation for missing deep property
|
||||||
|
smartexpect.expect(testObject).not.object.toHaveProperty('level1.level2.missing');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartexpect',
|
name: '@push.rocks/smartexpect',
|
||||||
version: '2.3.1',
|
version: '2.4.1',
|
||||||
description: 'A testing library to manage expectations in code, offering both synchronous and asynchronous assertion methods.'
|
description: 'A testing library to manage expectations in code, offering both synchronous and asynchronous assertion methods.'
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,24 @@ export class ObjectMatchers<T extends object, M extends TExecutionType> {
|
|||||||
return this.assertion.customAssertion(
|
return this.assertion.customAssertion(
|
||||||
(v) => {
|
(v) => {
|
||||||
const obj = v as any;
|
const obj = v as any;
|
||||||
if (!(property in obj)) {
|
// first check for a literal property (including inherited)
|
||||||
return false;
|
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 as any)[key];
|
||||||
}
|
}
|
||||||
if (arguments.length === 2) {
|
if (arguments.length === 2) {
|
||||||
return plugins.fastDeepEqual(obj[property], value);
|
return plugins.fastDeepEqual(current, value);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -355,7 +355,14 @@ export class Assertion<T = unknown, M extends TExecutionType = 'sync'> {
|
|||||||
public toInclude(substring: string) { return this.string.toInclude(substring); }
|
public toInclude(substring: string) { return this.string.toInclude(substring); }
|
||||||
public toMatch(regex: RegExp) { return this.string.toMatch(regex); }
|
public toMatch(regex: RegExp) { return this.string.toMatch(regex); }
|
||||||
public toBeOneOf(values: any[]) { return this.string.toBeOneOf(values as string[]); }
|
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 toMatchObject(expected: object) { return this.object.toMatchObject(expected); }
|
||||||
public toBeInstanceOf(constructor: any) { return this.object.toBeInstanceOf(constructor); }
|
public toBeInstanceOf(constructor: any) { return this.object.toBeInstanceOf(constructor); }
|
||||||
public toHaveDeepProperty(path: string[]) { return this.object.toHaveDeepProperty(path); }
|
public toHaveDeepProperty(path: string[]) { return this.object.toHaveDeepProperty(path); }
|
||||||
|
Reference in New Issue
Block a user