Compare commits
No commits in common. "master" and "v1.2.1" have entirely different histories.
@ -119,6 +119,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npmci node install stable
|
npmci node install stable
|
||||||
npmci npm install
|
npmci npm install
|
||||||
pnpm install -g @git.zone/tsdoc
|
pnpm install -g @gitzone/tsdoc
|
||||||
npmci command tsdoc
|
npmci command tsdoc
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
93
changelog.md
93
changelog.md
@ -1,98 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2025-04-29 - 2.2.2 - fix(license-files)
|
|
||||||
Remove legacy license file and add license.md to update file naming.
|
|
||||||
|
|
||||||
- Removed the old 'license' file.
|
|
||||||
- Added 'license.md' with updated file structure.
|
|
||||||
|
|
||||||
## 2025-04-29 - 2.2.1 - fix(readme)
|
|
||||||
Update usage examples and full matcher reference in README
|
|
||||||
|
|
||||||
- Removed deprecated 'expectAsync' from import example
|
|
||||||
- Fixed formatting in documentation
|
|
||||||
- Added comprehensive full matcher reference section
|
|
||||||
|
|
||||||
## 2025-04-29 - 2.2.0 - feat(generics)
|
|
||||||
Improve assertion and matcher type definitions by adding execution mode generics for better async/sync support
|
|
||||||
|
|
||||||
- Updated ts/index.ts to import and use TExecutionType in the expect function
|
|
||||||
- Modified Assertion class to use a generic execution mode (M) for improved type inference
|
|
||||||
- Revised all matcher namespaces (array, boolean, date, function, number, object, string, type) to accept the new generic parameter
|
|
||||||
- Enhanced async/sync distinction for assertion methods like resolves and rejects
|
|
||||||
|
|
||||||
## 2025-04-29 - 2.1.2 - fix(ts/index.ts)
|
|
||||||
Remove deprecated expectAsync function and advise using .resolves/.rejects on expect for async assertions
|
|
||||||
|
|
||||||
- Deleted the redundant expectAsync export in ts/index.ts
|
|
||||||
- Users should now call expect(...).resolves or expect(...).rejects for asynchronous assertions
|
|
||||||
|
|
||||||
## 2025-04-29 - 2.1.1 - fix(Assertion)
|
|
||||||
Improve chainability by fixing return types in assertion methods
|
|
||||||
|
|
||||||
- Update runCheck method to explicitly return the correct chainable type for both async and sync assertions
|
|
||||||
- Ensure customAssertion propagates the chainable Assertion instance
|
|
||||||
- Refactor internal promise handling for clarity and consistency
|
|
||||||
|
|
||||||
## 2025-04-28 - 2.1.0 - feat(core)
|
|
||||||
Add new matchers and improve negation messaging
|
|
||||||
|
|
||||||
- Added expect.any() and expect.anything() matchers for enhanced object pattern matching
|
|
||||||
- Introduced new number matchers: toBeNaN(), toBeFinite(), and toBeWithinRange()
|
|
||||||
- Implemented alias toBeEmpty() for both string and array matchers
|
|
||||||
- Enhanced function matchers with toThrowErrorMatching() and toThrowErrorWithMessage()
|
|
||||||
- Improved negation messaging to provide clearer failure messages (e.g. 'Expected 5 not to be greater than 3')
|
|
||||||
- Enhanced object assertions with a toHaveOwnProperty() shorthand that outputs unified diff-style messages
|
|
||||||
|
|
||||||
## 2025-04-28 - 2.0.1 - fix(assertion-matchers)
|
|
||||||
Refactor matcher implementations to consistently use customAssertion for improved consistency and clarity.
|
|
||||||
|
|
||||||
- Updated ArrayMatchers, BooleanMatchers, DateMatchers, FunctionMatchers, NumberMatchers, ObjectMatchers, StringMatchers, and TypeMatchers to use customAssertion directly.
|
|
||||||
- Aligned Assertion class aliases to delegate to the namespaced matchers with the new customAssertion pattern.
|
|
||||||
|
|
||||||
## 2025-04-28 - 2.0.0 - BREAKING CHANGE(docs)
|
|
||||||
Update documentation and examples to unify async and sync assertions, add custom matcher guides, and update package configuration
|
|
||||||
|
|
||||||
- Added packageManager field in package.json
|
|
||||||
- Revised documentation in readme.md to use .resolves/.rejects instead of expectAsync
|
|
||||||
- Included detailed examples for custom matchers and updated API usage
|
|
||||||
- Added readme.plan.md outlining the future roadmap
|
|
||||||
- Updated tests to import the built library from dist_ts
|
|
||||||
|
|
||||||
## 2025-03-04 - 1.6.1 - fix(build)
|
|
||||||
Corrected package.json and workflow dependencies and resolved formatting issues in tests.
|
|
||||||
|
|
||||||
- Fixed incorrect global npm package reference for tsdoc installation in workflow file.
|
|
||||||
- Updated dependencies in package.json for consistency in package naming.
|
|
||||||
- Resolved inconsistent formatting and spacing in test files.
|
|
||||||
|
|
||||||
## 2025-03-04 - 1.6.0 - feat(assertion)
|
|
||||||
Enhanced the assertion error messaging and added new test cases.
|
|
||||||
|
|
||||||
- Improved error messages by incorporating path and value/placeholders in assertions.
|
|
||||||
- Added detailed testing of new assertion functionalities.
|
|
||||||
- Additional test cases for comprehensive coverage of new features.
|
|
||||||
|
|
||||||
## 2025-03-04 - 1.5.0 - feat(Assertion)
|
|
||||||
Add toBeTypeOf assertion method
|
|
||||||
|
|
||||||
- Introduced a new assertion method `toBeTypeOf` allowing checks for expected data types.
|
|
||||||
- Updated devDependencies and dependencies to their latest versions.
|
|
||||||
|
|
||||||
## 2024-12-30 - 1.4.0 - feat(Assertion)
|
|
||||||
Add log method to Assertion class
|
|
||||||
|
|
||||||
- Introduced a log method in the Assertion class to output assertion context.
|
|
||||||
|
|
||||||
## 2024-12-30 - 1.3.0 - feat(Assertion)
|
|
||||||
Refactor Assertion class for better error handling and code clarity
|
|
||||||
|
|
||||||
- Improved method runCheck to better handle async and sync execution
|
|
||||||
- Enhanced getObjectToTestReference to handle undefined or null values gracefully
|
|
||||||
- Refactored error message logic for clarity and added more descriptive fail messages
|
|
||||||
- Added arrayItem method for better handling of array index access
|
|
||||||
- Improved structure by integrating consistent error handling in assertion methods
|
|
||||||
|
|
||||||
## 2024-08-24 - 1.2.1 - fix(Assertion)
|
## 2024-08-24 - 1.2.1 - fix(Assertion)
|
||||||
Refactor methods for setting failure and success messages
|
Refactor methods for setting failure and success messages
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2022 Task Venture Capital GmbH (hello@lossless.com)
|
Copyright (c) 2022 Lossless GmbH (hello@lossless.com)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
26
package.json
26
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartexpect",
|
"name": "@push.rocks/smartexpect",
|
||||||
"version": "2.2.2",
|
"version": "1.2.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",
|
||||||
@ -10,20 +10,20 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/ --web)",
|
"test": "(tstest test/ --web)",
|
||||||
"build": "(tsbuild tsfolders)",
|
"build": "(tsbuild --web)",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.2.1",
|
"@gitzone/tsbuild": "^2.1.66",
|
||||||
"@git.zone/tsbundle": "^2.2.5",
|
"@gitzone/tsbundle": "^2.0.8",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@gitzone/tsrun": "^1.2.44",
|
||||||
"@git.zone/tstest": "^1.0.96",
|
"@gitzone/tstest": "^1.0.77",
|
||||||
"@push.rocks/tapbundle": "^5.5.6",
|
"@push.rocks/tapbundle": "^5.0.23",
|
||||||
"@types/node": "^22.13.9"
|
"@types/node": "^22.4.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.0.4",
|
||||||
"fast-deep-equal": "^3.1.3"
|
"fast-deep-equal": "^3.1.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
@ -57,11 +57,5 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.foss.global/push.rocks/smartexpect.git"
|
"url": "https://code.foss.global/push.rocks/smartexpect.git"
|
||||||
},
|
}
|
||||||
"pnpm": {
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"mongodb-memory-server"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
|
||||||
}
|
}
|
||||||
|
8029
pnpm-lock.yaml
generated
8029
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
326
readme.md
326
readme.md
@ -1,5 +1,5 @@
|
|||||||
# @push.rocks/smartexpect
|
# @push.rocks/smartexpect
|
||||||
Manage expectations in code with precise, readable assertions
|
manage expectations in code
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -17,10 +17,10 @@ This will add `@push.rocks/smartexpect` to your project's dependencies. Make sur
|
|||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
|
||||||
First, import `@push.rocks/smartexpect` into your TypeScript file:
|
First, import `@push.rocks/smartexpect` into your TypeScript file:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { expect } from '@push.rocks/smartexpect';
|
import { expect, expectAsync } from '@push.rocks/smartexpect';
|
||||||
```
|
```
|
||||||
|
|
||||||
### Synchronous Expectations
|
### Synchronous Expectations
|
||||||
@ -30,16 +30,14 @@ You can employ `expect` to create synchronous assertions:
|
|||||||
```typescript
|
```typescript
|
||||||
import { expect } from '@push.rocks/smartexpect';
|
import { expect } from '@push.rocks/smartexpect';
|
||||||
|
|
||||||
// Type assertions
|
// String type assertion
|
||||||
expect('hello').toBeTypeofString();
|
expect('hello').toBeTypeofString();
|
||||||
expect(42).toBeTypeofNumber();
|
|
||||||
expect(true).toBeTypeofBoolean();
|
|
||||||
expect(() => {}).toBeTypeOf('function');
|
|
||||||
expect({}).toBeTypeOf('object');
|
|
||||||
|
|
||||||
// Negated assertions
|
// Negated String type assertion
|
||||||
expect(1).not.toBeTypeofString();
|
expect(1).not.toBeTypeofString();
|
||||||
expect('string').not.toBeTypeofNumber();
|
|
||||||
|
// Boolean type assertion
|
||||||
|
expect(true).toBeTypeofBoolean();
|
||||||
|
|
||||||
// Equality assertion
|
// Equality assertion
|
||||||
expect('hithere').toEqual('hithere');
|
expect('hithere').toEqual('hithere');
|
||||||
@ -53,308 +51,78 @@ expect('hithere').toMatch(/hi/);
|
|||||||
|
|
||||||
### Asynchronous Expectations
|
### Asynchronous Expectations
|
||||||
|
|
||||||
For asynchronous code, use the same `expect` function with the `.resolves` or `.rejects` modifier:
|
For asynchronous operations, use `expectAsync` to return a promise:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { expect } from '@push.rocks/smartexpect';
|
import { expectAsync } from '@push.rocks/smartexpect';
|
||||||
|
|
||||||
const asyncStringFetcher = async (): Promise<string> => {
|
const asyncStringFetcher = async (): Promise<string> => {
|
||||||
return 'async string';
|
return 'async string';
|
||||||
};
|
};
|
||||||
|
|
||||||
const asyncTest = async () => {
|
const asyncTest = async () => {
|
||||||
// Add a timeout to prevent hanging tests
|
await expectAsync(asyncStringFetcher()).toBeTypeofString();
|
||||||
await expect(asyncStringFetcher()).resolves.withTimeout(5000).type.toBeTypeofString();
|
await expectAsync(asyncStringFetcher()).toEqual('async string');
|
||||||
await expect(asyncStringFetcher()).resolves.toEqual('async string');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
asyncTest();
|
asyncTest();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Navigating Complex Objects
|
### Advanced Usage
|
||||||
|
|
||||||
You can navigate complex objects using the `property()` and `arrayItem()` methods:
|
- **Properties and Deep Properties:** Assert the existence of properties and their values.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const complexObject = {
|
const testObject = { level1: { level2: 'value' } };
|
||||||
users: [
|
|
||||||
{ id: 1, name: 'Alice', permissions: { admin: true } },
|
|
||||||
{ id: 2, name: 'Bob', permissions: { admin: false } }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Navigate to a nested property
|
// Property existence
|
||||||
expect(complexObject)
|
expect(testObject).toHaveProperty('level1');
|
||||||
.property('users')
|
|
||||||
.arrayItem(0)
|
|
||||||
.property('name')
|
|
||||||
.toEqual('Alice');
|
|
||||||
|
|
||||||
// Check nested permission
|
// Deep Property existence
|
||||||
expect(complexObject)
|
expect(testObject).toHaveDeepProperty(['level1', 'level2']);
|
||||||
.property('users')
|
```
|
||||||
.arrayItem(0)
|
|
||||||
.property('permissions')
|
|
||||||
.property('admin')
|
|
||||||
.toBeTrue();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Assertions
|
- **Conditions and Comparisons:** Allow more intricate assertions like greater than, less than, or matching specific conditions.
|
||||||
|
|
||||||
#### Properties and Deep Properties
|
```typescript
|
||||||
|
// Greater Than
|
||||||
|
expect(5).toBeGreaterThan(3);
|
||||||
|
|
||||||
Assert the existence of properties and their values:
|
// Less Than
|
||||||
|
expect(3).toBeLessThan(5);
|
||||||
|
|
||||||
```typescript
|
// Custom conditions
|
||||||
const testObject = { level1: { level2: 'value' } };
|
expect(7).customAssertion(value => value > 5, 'Value is not greater than 5');
|
||||||
|
```
|
||||||
|
|
||||||
// Property existence
|
- **Arrays and Objects:** Work seamlessly with arrays and objects, checking for containment, length, or specific values.
|
||||||
expect(testObject).toHaveProperty('level1');
|
|
||||||
|
|
||||||
// Property with specific value
|
```typescript
|
||||||
expect(testObject).toHaveProperty('level1.level2', 'value');
|
const testArray = [1, 2, 3];
|
||||||
|
|
||||||
// Deep Property existence
|
// Containment
|
||||||
expect(testObject).toHaveDeepProperty(['level1', 'level2']);
|
expect(testArray).toContain(2);
|
||||||
```
|
|
||||||
|
|
||||||
#### Conditions and Comparisons
|
// Array length
|
||||||
|
expect(testArray).toHaveLength(3);
|
||||||
|
|
||||||
Perform more intricate assertions:
|
// Object matching
|
||||||
|
expect({ name: 'Test', value: 123 }).toMatchObject({ name: 'Test' });
|
||||||
|
```
|
||||||
|
|
||||||
```typescript
|
### Handling Promises and Async Operations
|
||||||
// Numeric comparisons
|
|
||||||
expect(5).toBeGreaterThan(3);
|
|
||||||
expect(3).toBeLessThan(5);
|
|
||||||
expect(5).toBeGreaterThanOrEqual(5);
|
|
||||||
expect(5).toBeLessThanOrEqual(5);
|
|
||||||
expect(0.1 + 0.2).toBeCloseTo(0.3, 10); // Floating point comparison with precision
|
|
||||||
|
|
||||||
// Truthiness checks
|
`@push.rocks/smartexpect` gracefully integrates with asynchronous operations, providing a `expectAsync` function that handles promise-based assertions. This keeps your tests clean and readable, irrespective of the nature of the code being tested.
|
||||||
expect(true).toBeTrue();
|
|
||||||
expect(false).toBeFalse();
|
|
||||||
expect('non-empty').toBeTruthy();
|
|
||||||
expect(0).toBeFalsy();
|
|
||||||
|
|
||||||
// Null/Undefined checks
|
### Best Practices
|
||||||
expect(null).toBeNull();
|
|
||||||
expect(undefined).toBeUndefined();
|
|
||||||
expect(null).toBeNullOrUndefined();
|
|
||||||
|
|
||||||
// Custom conditions
|
- **Readability:** Favor clarity and readability by explicitly stating your expectations. `@push.rocks/smartexpect`'s API is designed to be fluent and expressive, making your tests easy to write and, more importantly, easy to read.
|
||||||
expect(7).customAssertion(value => value % 2 === 1, 'Value is not odd');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Arrays and Collections
|
- **Comprehensive Coverage:** Utilize the full spectrum of assertions provided to cover a broad set of use cases, ensuring your code behaves as expected not just in ideal conditions but across various edge cases.
|
||||||
|
|
||||||
Work seamlessly with arrays and collections:
|
- **Maintainability:** Group related assertions together to improve test maintainability. This makes it easier to update tests as your codebase evolves.
|
||||||
|
|
||||||
```typescript
|
Through judicious use of `@push.rocks/smartexpect`, you can enhance the reliability and maintainability of your test suite, making your codebase more robust and your development workflow more efficient.
|
||||||
const testArray = [1, 2, 3];
|
|
||||||
|
|
||||||
// Array checks
|
|
||||||
expect(testArray).toBeArray();
|
|
||||||
expect(testArray).toHaveLength(3);
|
|
||||||
expect(testArray).toContain(2);
|
|
||||||
expect(testArray).toContainAll([1, 3]);
|
|
||||||
expect(testArray).toExclude(4);
|
|
||||||
expect([]).toBeEmptyArray();
|
|
||||||
expect(testArray).toHaveLengthGreaterThan(2);
|
|
||||||
expect(testArray).toHaveLengthLessThan(4);
|
|
||||||
|
|
||||||
// Deep equality in arrays
|
|
||||||
expect([{ id: 1 }, { id: 2 }]).toContainEqual({ id: 1 });
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Strings
|
|
||||||
|
|
||||||
String-specific checks:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
expect('hello world').toStartWith('hello');
|
|
||||||
expect('hello world').toEndWith('world');
|
|
||||||
expect('hello world').toInclude('lo wo');
|
|
||||||
expect('options').toBeOneOf(['choices', 'options', 'alternatives']);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Functions and Exceptions
|
|
||||||
|
|
||||||
Test function behavior and exceptions:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const throwingFn = () => { throw new Error('test error'); };
|
|
||||||
expect(throwingFn).toThrow();
|
|
||||||
expect(throwingFn).toThrow(Error);
|
|
||||||
|
|
||||||
const safeFn = () => 'result';
|
|
||||||
expect(safeFn).not.toThrow();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Date Assertions
|
|
||||||
|
|
||||||
Work with dates:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const now = new Date();
|
|
||||||
const past = new Date(Date.now() - 10000);
|
|
||||||
const future = new Date(Date.now() + 10000);
|
|
||||||
|
|
||||||
expect(now).toBeDate();
|
|
||||||
expect(now).toBeAfterDate(past);
|
|
||||||
expect(now).toBeBeforeDate(future);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debugging Assertions
|
|
||||||
|
|
||||||
The `log()` method is useful for debugging complex assertions:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
expect(complexObject)
|
|
||||||
.property('users')
|
|
||||||
.log() // Logs the current value in the assertion chain
|
|
||||||
.arrayItem(0)
|
|
||||||
.log() // Logs the first user
|
|
||||||
.property('permissions')
|
|
||||||
.log() // Logs the permissions object
|
|
||||||
.property('admin')
|
|
||||||
.toBeTrue();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customizing Error Messages
|
|
||||||
|
|
||||||
You can provide custom error messages for more meaningful test failures:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
expect(user.age)
|
|
||||||
.setFailMessage('User age must be at least 18 for adult content')
|
|
||||||
.toBeGreaterThanOrEqual(18);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Matchers
|
|
||||||
|
|
||||||
You can define your own matchers via `expect.extend()`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
expect.extend({
|
|
||||||
toBeOdd(received: number) {
|
|
||||||
const pass = received % 2 === 1;
|
|
||||||
return {
|
|
||||||
pass,
|
|
||||||
message: () =>
|
|
||||||
`Expected ${received} ${pass ? 'not ' : ''}to be odd`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then use your custom matcher in tests:
|
|
||||||
expect(3).toBeOdd();
|
|
||||||
expect(4).not.toBeOdd();
|
|
||||||
```
|
|
||||||
|
|
||||||
- Matcher functions receive the value under test (`received`) plus any arguments.
|
|
||||||
- Must return an object with `pass` (boolean) and `message` (string or function) for failure messages.
|
|
||||||
|
|
||||||
### Full Matcher Reference
|
|
||||||
|
|
||||||
Below is a comprehensive list of all matchers and utility functions available in `@push.rocks/smartexpect`.
|
|
||||||
|
|
||||||
#### Modifiers and Utilities
|
|
||||||
- `.not` Negates the next matcher in the chain.
|
|
||||||
- `.resolves` Switches to async mode, expecting the promise to resolve.
|
|
||||||
- `.rejects` Switches to async mode, expecting the promise to reject.
|
|
||||||
- `.withTimeout(ms)` Sets a timeout (in milliseconds) for async assertions.
|
|
||||||
- `.timeout(ms)` (deprecated) Alias for `.withTimeout(ms)`.
|
|
||||||
- `.property(name)` Drill into a property of an object.
|
|
||||||
- `.arrayItem(index)` Drill into an array element by index.
|
|
||||||
- `.log()` Logs the current value and assertion path for debugging.
|
|
||||||
- `.setFailMessage(message)` Override the failure message for the current assertion.
|
|
||||||
- `.setSuccessMessage(message)` Override the success message for the current assertion.
|
|
||||||
- `.customAssertion(fn, message)` Execute a custom assertion function with a message.
|
|
||||||
- `expect.extend(matchers)` Register custom matchers globally.
|
|
||||||
- `expect.any(constructor)` Matcher for values that are instances of the given constructor.
|
|
||||||
- `expect.anything()` Matcher for any defined value (not null or undefined).
|
|
||||||
|
|
||||||
#### Basic Matchers
|
|
||||||
- `.toEqual(expected)` Deep (or strict for primitives) equality.
|
|
||||||
- `.toBeTrue()` Value is strictly `true`.
|
|
||||||
- `.toBeFalse()` Value is strictly `false`.
|
|
||||||
- `.toBeTruthy()` Value is truthy.
|
|
||||||
- `.toBeFalsy()` Value is falsy.
|
|
||||||
- `.toBeNull()` Value is `null`.
|
|
||||||
- `.toBeUndefined()` Value is `undefined`.
|
|
||||||
- `.toBeNullOrUndefined()` Value is `null` or `undefined`.
|
|
||||||
- `.toBeDefined()` Value is not `undefined`.
|
|
||||||
|
|
||||||
#### Number Matchers
|
|
||||||
- `.toBeGreaterThan(value)`
|
|
||||||
- `.toBeLessThan(value)`
|
|
||||||
- `.toBeGreaterThanOrEqual(value)`
|
|
||||||
- `.toBeLessThanOrEqual(value)`
|
|
||||||
- `.toBeCloseTo(value, precision?)`
|
|
||||||
- `.toEqual(value)` Strict equality for numbers.
|
|
||||||
- `.toBeNaN()`
|
|
||||||
- `.toBeFinite()`
|
|
||||||
- `.toBeWithinRange(min, max)`
|
|
||||||
|
|
||||||
#### String Matchers
|
|
||||||
- `.toStartWith(prefix)`
|
|
||||||
- `.toEndWith(suffix)`
|
|
||||||
- `.toInclude(substring)`
|
|
||||||
- `.toMatch(regex)`
|
|
||||||
- `.toBeOneOf(arrayOfValues)`
|
|
||||||
- `.toHaveLength(length)`
|
|
||||||
- `.toBeEmpty()` Alias for empty string.
|
|
||||||
|
|
||||||
#### Array Matchers
|
|
||||||
- `.toBeArray()`
|
|
||||||
- `.toHaveLength(length)`
|
|
||||||
- `.toContain(value)`
|
|
||||||
- `.toContainEqual(value)`
|
|
||||||
- `.toContainAll(arrayOfValues)`
|
|
||||||
- `.toExclude(value)`
|
|
||||||
- `.toBeEmptyArray()`
|
|
||||||
- `.toBeEmpty()` Alias for empty array.
|
|
||||||
- `.toHaveLengthGreaterThan(length)`
|
|
||||||
- `.toHaveLengthLessThan(length)`
|
|
||||||
|
|
||||||
#### Object Matchers
|
|
||||||
- `.toEqual(expected)` Deep equality for objects.
|
|
||||||
- `.toMatchObject(partialObject)` Partial deep matching (supports `expect.any` and `expect.anything`).
|
|
||||||
- `.toBeInstanceOf(constructor)`
|
|
||||||
- `.toHaveProperty(propertyName, value?)`
|
|
||||||
- `.toHaveDeepProperty(pathArray)`
|
|
||||||
- `.toHaveOwnProperty(propertyName, value?)`
|
|
||||||
- `.toBeNull()`
|
|
||||||
- `.toBeUndefined()`
|
|
||||||
- `.toBeNullOrUndefined()`
|
|
||||||
|
|
||||||
#### Function Matchers
|
|
||||||
- `.toThrow(expectedError?)`
|
|
||||||
- `.toThrowErrorMatching(regex)`
|
|
||||||
- `.toThrowErrorWithMessage(message)`
|
|
||||||
|
|
||||||
#### Date Matchers
|
|
||||||
- `.toBeDate()`
|
|
||||||
- `.toBeBeforeDate(date)`
|
|
||||||
- `.toBeAfterDate(date)`
|
|
||||||
|
|
||||||
#### Type Matchers
|
|
||||||
- `.toBeTypeofString()`
|
|
||||||
- `.toBeTypeofNumber()`
|
|
||||||
- `.toBeTypeofBoolean()`
|
|
||||||
- `.toBeTypeOf(typeName)`
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
- **Human-readable assertions**: The fluent API is designed to create tests that read like natural language sentences.
|
|
||||||
|
|
||||||
- **Precise error messages**: When tests fail, the error messages provide detailed information about what went wrong, including expected vs. actual values.
|
|
||||||
|
|
||||||
- **Property path navigation**: Use the property path methods to navigate complex objects without creating temporary variables.
|
|
||||||
|
|
||||||
- **Comprehensive testing**: Take advantage of the wide range of assertion methods to test various aspects of your code.
|
|
||||||
|
|
||||||
- **Debugging with log()**: Use the `log()` method to see intermediate values in the assertion chain during test development.
|
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
# Plan for Improving @push.rocks/smartexpect API
|
|
||||||
|
|
||||||
This document captures the roadmap for evolving the `expect` / `expectAsync` API.
|
|
||||||
|
|
||||||
## Phase 1: Unify Sync + Async
|
|
||||||
- [x] Consolidate `expect` and `expectAsync` into a single `expect()` entry point.
|
|
||||||
- [x] Introduce `.resolves` and `.rejects` chainable helpers for Promises.
|
|
||||||
- [x] Deprecate `expectAsync`, provide migration guidance.
|
|
||||||
|
|
||||||
## Phase 2: Timeout Helper
|
|
||||||
- [x] Rename or wrap the existing `.timeout(ms)` to a more intuitive `.withTimeout(ms)`.
|
|
||||||
|
|
||||||
## Phase 3: Custom Matchers
|
|
||||||
- [x] Implement `expect.extend()` API for user-defined matchers.
|
|
||||||
|
|
||||||
## Phase 4: TypeScript Typings
|
|
||||||
- [ ] Enhance generic matcher types to infer narrow types after `.property()` / `.arrayItem()`.
|
|
||||||
- [ ] Provide matcher overloads for primitive categories (string, number, array, etc.).
|
|
||||||
|
|
||||||
## Phase 5: Namespaced Matchers
|
|
||||||
- [ ] Group matchers under `.string`, `.array`, `.number`, etc. for discoverability.
|
|
||||||
|
|
||||||
## Phase 6: Jest-Style Convenience
|
|
||||||
- [x] Add `expect.any()` and `expect.anything()` matchers for use in `.toMatchObject()` patterns
|
|
||||||
(Snapshot matchers still TBD)
|
|
||||||
|
|
||||||
The next items to tackle:
|
|
||||||
|
|
||||||
3. Improve negation (`.not`) messaging
|
|
||||||
- Today `.not` simply flips pass/fail, but the failure message isn’t very descriptive. We should capture positive/negative message templates so e.g.
|
|
||||||
> expect(5).not.toBeGreaterThan(3)
|
|
||||||
emits:
|
|
||||||
"Expected 5 not to be greater than 3"
|
|
||||||
|
|
||||||
4. Richer error output for objects/arrays
|
|
||||||
- Integrate a diff library (or extend `fast-deep-equal`) to show unified diffs between expected and actual values
|
|
||||||
|
|
||||||
5. More built-in matchers
|
|
||||||
- toBeNaN(), toBeFinite()
|
|
||||||
- toBeWithinRange(min, max)
|
|
||||||
- toHaveKeys(...), toHaveOwnKeys(...)
|
|
||||||
- toThrowErrorMatching(/regex/), toThrowErrorWithMessage('…')
|
|
||||||
- string/array: toBeEmpty() alias
|
|
||||||
- object: toHaveOwnProperty() shorthand
|
|
||||||
|
|
||||||
6. TypeScript-friendliness
|
|
||||||
- Enhance `.d.ts` so editors autocomplete namespace methods (e.g. `expect(x).string.`)
|
|
||||||
- Statically type matcher arguments to catch wrong types at compile time
|
|
||||||
|
|
||||||
7. Async assertions and timeouts improvements
|
|
||||||
- Support `.not.resolves`, `.rejects.toThrow()`
|
|
||||||
- Provide clearer timeout errors (e.g. "Expected promise to resolve within …")
|
|
||||||
|
|
||||||
8. Plugin/extension API
|
|
||||||
- Formalize `Assertion.extend()` plugin API for shipping matcher bundles
|
|
@ -1,221 +0,0 @@
|
|||||||
import { tap } from '@push.rocks/tapbundle';
|
|
||||||
// Import the built library (dist_ts) so all matcher implementations are available
|
|
||||||
import * as smartexpect from '../dist_ts/index.js';
|
|
||||||
|
|
||||||
tap.test('basic type assertions', async () => {
|
|
||||||
// String type checks
|
|
||||||
smartexpect.expect('hello').type.toBeTypeofString();
|
|
||||||
smartexpect.expect(1).not.type.toBeTypeofString();
|
|
||||||
|
|
||||||
// Boolean type checks
|
|
||||||
smartexpect.expect(true).type.toBeTypeofBoolean();
|
|
||||||
smartexpect.expect(false).type.toBeTypeofBoolean();
|
|
||||||
smartexpect.expect(1).not.type.toBeTypeofBoolean();
|
|
||||||
|
|
||||||
// Number type checks
|
|
||||||
smartexpect.expect(42).type.toBeTypeofNumber();
|
|
||||||
smartexpect.expect(true).not.type.toBeTypeofNumber();
|
|
||||||
|
|
||||||
// Generic type checks with new method
|
|
||||||
smartexpect.expect(() => {}).type.toBeTypeOf('function');
|
|
||||||
smartexpect.expect(class Test {}).type.toBeTypeOf('function');
|
|
||||||
smartexpect.expect({}).type.toBeTypeOf('object');
|
|
||||||
smartexpect.expect(Symbol('test')).type.toBeTypeOf('symbol');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('async tests', async (toolsArg) => {
|
|
||||||
const deferred = toolsArg.defer();
|
|
||||||
toolsArg.delayFor(1000).then(() => {
|
|
||||||
deferred.resolve('hello');
|
|
||||||
});
|
|
||||||
// Using .resolves to test promise resolution with timeout
|
|
||||||
await smartexpect.expect(deferred.promise).resolves.withTimeout(2000).type.toBeTypeofString();
|
|
||||||
await smartexpect.expect(deferred.promise).resolves.not.type.toBeTypeofBoolean();
|
|
||||||
|
|
||||||
// Test async timeout handling
|
|
||||||
const longOperation = toolsArg.defer();
|
|
||||||
toolsArg.delayFor(3000).then(() => {
|
|
||||||
longOperation.resolve('completed');
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
// Assert that resolution must occur within timeout
|
|
||||||
await smartexpect.expect(longOperation.promise).resolves.withTimeout(1000).toBeDefined();
|
|
||||||
throw new Error('Should have timed out');
|
|
||||||
} catch (err) {
|
|
||||||
// Successfully caught timeout error from .withTimeout
|
|
||||||
console.log('Successfully caught timeout:', err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('equality and matching assertions', async () => {
|
|
||||||
// Basic equality
|
|
||||||
smartexpect.expect('hithere').object.toEqual('hithere');
|
|
||||||
smartexpect.expect('hithere').not.object.toEqual('hithere2');
|
|
||||||
|
|
||||||
// Object equality
|
|
||||||
const obj1 = { a: 1, b: { c: true } };
|
|
||||||
const obj2 = { a: 1, b: { c: true } };
|
|
||||||
const obj3 = { a: 1, b: { c: false } };
|
|
||||||
smartexpect.expect(obj1).object.toEqual(obj2);
|
|
||||||
smartexpect.expect(obj1).not.object.toEqual(obj3);
|
|
||||||
|
|
||||||
// RegExp matching
|
|
||||||
smartexpect.expect('hithere').string.toMatch(/hi/);
|
|
||||||
smartexpect.expect('hithere').string.toMatch(/^hithere$/);
|
|
||||||
smartexpect.expect('hithere').not.string.toMatch(/ho/);
|
|
||||||
|
|
||||||
// String inclusion
|
|
||||||
smartexpect.expect('hithere').string.toInclude('hit');
|
|
||||||
smartexpect.expect('hithere').not.string.toInclude('missing');
|
|
||||||
|
|
||||||
// String start/end
|
|
||||||
smartexpect.expect('hithere').string.toStartWith('hi');
|
|
||||||
smartexpect.expect('hithere').string.toEndWith('ere');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('object property assertions', async () => {
|
|
||||||
const testObject = {
|
|
||||||
topLevel: 'hello',
|
|
||||||
nested: {
|
|
||||||
prop: 42,
|
|
||||||
deeplyNested: {
|
|
||||||
array: [1, 2, 3],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Basic property checks
|
|
||||||
smartexpect.expect(testObject).object.toHaveProperty('topLevel');
|
|
||||||
smartexpect.expect(testObject).object.toHaveProperty('topLevel', 'hello');
|
|
||||||
smartexpect.expect(testObject).not.object.toHaveProperty('missing');
|
|
||||||
|
|
||||||
// Drill-down property navigation
|
|
||||||
smartexpect.expect(testObject).property('nested').object.toHaveProperty('prop', 42);
|
|
||||||
smartexpect
|
|
||||||
.expect(testObject)
|
|
||||||
.property('nested')
|
|
||||||
.property('deeplyNested')
|
|
||||||
.property('array')
|
|
||||||
.array.toBeArray();
|
|
||||||
|
|
||||||
// Deep property checks
|
|
||||||
smartexpect.expect(testObject).object.toHaveDeepProperty(['nested', 'deeplyNested', 'array']);
|
|
||||||
|
|
||||||
// Array item navigation
|
|
||||||
smartexpect
|
|
||||||
.expect(testObject)
|
|
||||||
.property('nested')
|
|
||||||
.property('deeplyNested')
|
|
||||||
.property('array')
|
|
||||||
.arrayItem(0)
|
|
||||||
.number.toEqual(1); // numeric equality via number namespace
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('numeric comparison assertions', async () => {
|
|
||||||
// Greater/less than
|
|
||||||
smartexpect.expect(4).number.toBeGreaterThan(3);
|
|
||||||
smartexpect.expect(4).number.toBeLessThan(5);
|
|
||||||
smartexpect.expect(4).number.toBeGreaterThanOrEqual(4);
|
|
||||||
smartexpect.expect(4).number.toBeLessThanOrEqual(4);
|
|
||||||
|
|
||||||
// Approximate equality
|
|
||||||
smartexpect.expect(0.1 + 0.2).number.toBeCloseTo(0.3, 10);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('array assertions', async () => {
|
|
||||||
const obj1 = { id: 1 };
|
|
||||||
const obj2 = { id: 2 };
|
|
||||||
const testArray = [1, 'two', obj1, true];
|
|
||||||
|
|
||||||
// Basic array checks
|
|
||||||
smartexpect.expect(testArray).array.toBeArray();
|
|
||||||
smartexpect.expect(testArray).array.toHaveLength(4);
|
|
||||||
|
|
||||||
// Content checks
|
|
||||||
smartexpect.expect(testArray).array.toContain('two');
|
|
||||||
smartexpect.expect(testArray).array.toContain(obj1);
|
|
||||||
smartexpect.expect(testArray).not.array.toContain(obj2);
|
|
||||||
|
|
||||||
// Array with equal items (not same reference)
|
|
||||||
smartexpect.expect([{ a: 1 }, { b: 2 }]).array.toContainEqual({ a: 1 });
|
|
||||||
|
|
||||||
// Multiple values
|
|
||||||
smartexpect.expect(testArray).array.toContainAll([1, 'two']);
|
|
||||||
smartexpect.expect(testArray).array.toExclude('missing');
|
|
||||||
|
|
||||||
// Empty array
|
|
||||||
smartexpect.expect([]).array.toBeEmptyArray();
|
|
||||||
|
|
||||||
// Length comparisons
|
|
||||||
smartexpect.expect(testArray).array.toHaveLengthGreaterThan(3);
|
|
||||||
smartexpect.expect(testArray).array.toHaveLengthLessThan(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('boolean assertions', async () => {
|
|
||||||
// True/False
|
|
||||||
smartexpect.expect(true).boolean.toBeTrue();
|
|
||||||
smartexpect.expect(false).boolean.toBeFalse();
|
|
||||||
|
|
||||||
// Truthy/Falsy
|
|
||||||
smartexpect.expect('something').boolean.toBeTruthy();
|
|
||||||
smartexpect.expect(0).boolean.toBeFalsy();
|
|
||||||
|
|
||||||
// Null/Undefined
|
|
||||||
smartexpect.expect(null).object.toBeNull();
|
|
||||||
smartexpect.expect(undefined).object.toBeUndefined();
|
|
||||||
smartexpect.expect(null).object.toBeNullOrUndefined();
|
|
||||||
smartexpect.expect(undefined).object.toBeNullOrUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('function assertions', async () => {
|
|
||||||
// Function that throws
|
|
||||||
const throwingFn = () => {
|
|
||||||
throw new Error('test error');
|
|
||||||
};
|
|
||||||
smartexpect.expect(throwingFn).function.toThrow();
|
|
||||||
smartexpect.expect(throwingFn).function.toThrow(Error);
|
|
||||||
|
|
||||||
// Function that doesn't throw
|
|
||||||
const nonThrowingFn = () => 'safe';
|
|
||||||
smartexpect.expect(nonThrowingFn).not.function.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('date assertions', async () => {
|
|
||||||
const now = new Date();
|
|
||||||
const past = new Date(Date.now() - 10000);
|
|
||||||
const future = new Date(Date.now() + 10000);
|
|
||||||
|
|
||||||
smartexpect.expect(now).date.toBeDate();
|
|
||||||
smartexpect.expect(now).date.toBeAfterDate(past);
|
|
||||||
smartexpect.expect(now).date.toBeBeforeDate(future);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('custom assertions', async () => {
|
|
||||||
// Custom validation logic
|
|
||||||
smartexpect.expect(42).customAssertion((value) => value % 2 === 0, 'Expected number to be even');
|
|
||||||
|
|
||||||
// With fail message
|
|
||||||
smartexpect.expect('test').setFailMessage('Custom fail message for assertion').string.toHaveLength(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('logging and debugging', async () => {
|
|
||||||
// Using log() for debugging
|
|
||||||
const complexObject = {
|
|
||||||
level1: {
|
|
||||||
level2: {
|
|
||||||
value: 'nested value',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// This logs the current value in the chain for debugging
|
|
||||||
smartexpect
|
|
||||||
.expect(complexObject)
|
|
||||||
.property('level1')
|
|
||||||
.property('level2')
|
|
||||||
.log()
|
|
||||||
.property('value')
|
|
||||||
.object.toEqual('nested value');
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
|
@ -1,32 +0,0 @@
|
|||||||
import { tap, expect as tExpect } from '@push.rocks/tapbundle';
|
|
||||||
import * as smartexpect from '../dist_ts/index.js';
|
|
||||||
|
|
||||||
tap.test('diff-like output for object.toEqual mismatch', async () => {
|
|
||||||
const a = { x: 1, y: 2 };
|
|
||||||
const b = { x: 1, y: 3 };
|
|
||||||
try {
|
|
||||||
smartexpect.expect(a).object.toEqual(b);
|
|
||||||
throw new Error('Assertion did not throw');
|
|
||||||
} catch (err: any) {
|
|
||||||
const msg: string = err.message;
|
|
||||||
tExpect(msg.includes('Expected objects to be deeply equal')).toBeTrue();
|
|
||||||
tExpect(msg.includes('Received:')).toBeTrue();
|
|
||||||
tExpect(msg.includes('"y": 2')).toBeTrue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('diff-like output for array.toContainEqual mismatch', async () => {
|
|
||||||
const arr = [{ id: 1 }, { id: 2 }];
|
|
||||||
const item = { id: 3 };
|
|
||||||
try {
|
|
||||||
smartexpect.expect(arr).array.toContainEqual(item);
|
|
||||||
throw new Error('Assertion did not throw');
|
|
||||||
} catch (err: any) {
|
|
||||||
const msg: string = err.message;
|
|
||||||
tExpect(msg.includes('Expected array to contain equal to')).toBeTrue();
|
|
||||||
tExpect(msg.includes('Received:')).toBeTrue();
|
|
||||||
tExpect(msg.includes('"id": 1')).toBeTrue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
|
@ -1,38 +0,0 @@
|
|||||||
import { tap } from '@push.rocks/tapbundle';
|
|
||||||
import * as smartexpect from '../dist_ts/index.js';
|
|
||||||
|
|
||||||
tap.test('expect.any and expect.anything basic usage', async () => {
|
|
||||||
const obj = { a: 1, b: 'two', d: new Date() };
|
|
||||||
// Using expect.any to match types
|
|
||||||
smartexpect.expect(obj).object.toMatchObject({
|
|
||||||
a: smartexpect.expect.any(Number),
|
|
||||||
b: smartexpect.expect.any(String),
|
|
||||||
d: smartexpect.expect.any(Date),
|
|
||||||
});
|
|
||||||
// Using expect.anything to match any defined value
|
|
||||||
smartexpect.expect(obj).object.toMatchObject({
|
|
||||||
a: smartexpect.expect.anything(),
|
|
||||||
b: smartexpect.expect.anything(),
|
|
||||||
d: smartexpect.expect.anything(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('expect.any mismatch and anything null/undefined rejection', async () => {
|
|
||||||
const obj = { a: 1, b: null };
|
|
||||||
// Mismatch for expect.any
|
|
||||||
try {
|
|
||||||
smartexpect.expect(obj).object.toMatchObject({ a: smartexpect.expect.any(String) });
|
|
||||||
throw new Error('Expected mismatch for expect.any did not throw');
|
|
||||||
} catch (err) {
|
|
||||||
// success: thrown on mismatch
|
|
||||||
}
|
|
||||||
// anything should reject null or undefined
|
|
||||||
try {
|
|
||||||
smartexpect.expect(obj).object.toMatchObject({ b: smartexpect.expect.anything() });
|
|
||||||
throw new Error('Expected anything() to reject null or undefined');
|
|
||||||
} catch (err) {
|
|
||||||
// success: thrown on null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
|
@ -1,22 +0,0 @@
|
|||||||
import { tap, expect as tExpect } from '@push.rocks/tapbundle';
|
|
||||||
import * as smartexpect from '../dist_ts/index.js';
|
|
||||||
|
|
||||||
tap.test('negation message for numeric matcher', async () => {
|
|
||||||
try {
|
|
||||||
smartexpect.expect(5).not.toBeGreaterThan(3);
|
|
||||||
throw new Error('Assertion did not throw');
|
|
||||||
} catch (err: any) {
|
|
||||||
tExpect(err.message).toEqual('Expected number not to be greater than 3');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('negation message for string matcher', async () => {
|
|
||||||
try {
|
|
||||||
smartexpect.expect('hello').not.string.toInclude('he');
|
|
||||||
throw new Error('Assertion did not throw');
|
|
||||||
} catch (err: any) {
|
|
||||||
tExpect(err.message).toEqual('Expected string not to include "he"');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
|
52
test/test.ts
Normal file
52
test/test.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { tap } from '@push.rocks/tapbundle';
|
||||||
|
import * as smartexpect from '../ts/index.js';
|
||||||
|
|
||||||
|
tap.test('sync tests', async () => {
|
||||||
|
smartexpect.expect('hello').toBeTypeofString();
|
||||||
|
smartexpect.expect(1).not.toBeTypeofString();
|
||||||
|
smartexpect.expect(true).toBeTypeofBoolean();
|
||||||
|
smartexpect.expect(true).not.toBeTypeofNumber();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('async tests', async (toolsArg) => {
|
||||||
|
const deferred = toolsArg.defer();
|
||||||
|
toolsArg.delayFor(4000).then(() => {
|
||||||
|
deferred.resolve('hello');
|
||||||
|
});
|
||||||
|
await smartexpect.expectAsync(deferred.promise).timeout(5000).toBeTypeofString();
|
||||||
|
await smartexpect.expectAsync(deferred.promise).not.toBeTypeofBoolean();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should check equality', async () => {
|
||||||
|
smartexpect.expect('hithere').toEqual('hithere');
|
||||||
|
smartexpect.expect('hithere').not.toEqual('hithere2');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should check for regexp matching', async () => {
|
||||||
|
smartexpect.expect('hithere').toMatch(/hi/);
|
||||||
|
smartexpect.expect('hithere').not.toMatch(/ho/);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should correctly state property presence', async () => {
|
||||||
|
const testObject = {
|
||||||
|
aprop: 'hello',
|
||||||
|
};
|
||||||
|
|
||||||
|
smartexpect.expect(testObject).toHaveProperty('aprop');
|
||||||
|
smartexpect.expect(testObject).not.toHaveProperty('aprop2');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should be greater than', async () => {
|
||||||
|
smartexpect.expect(4).toBeGreaterThan(3);
|
||||||
|
smartexpect.expect(4).toBeLessThan(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should correctly determine toContain', async () => {
|
||||||
|
const hello = {
|
||||||
|
socool: 'yes',
|
||||||
|
};
|
||||||
|
const testArray = [hello];
|
||||||
|
smartexpect.expect(testArray).toContain(hello);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartexpect',
|
name: '@push.rocks/smartexpect',
|
||||||
version: '2.2.2',
|
version: '1.2.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.'
|
||||||
}
|
}
|
||||||
|
49
ts/index.ts
49
ts/index.ts
@ -1,41 +1,12 @@
|
|||||||
import { Assertion, AnyMatcher, AnythingMatcher } from './smartexpect.classes.assertion.js';
|
import { Assertion } from './smartexpect.classes.assertion.js';
|
||||||
import type { TExecutionType } from './types.js';
|
|
||||||
// import type { TMatcher } from './smartexpect.classes.assertion.js'; // unused
|
|
||||||
|
|
||||||
/**
|
export const expect = (baseArg: any) => {
|
||||||
* Primary entry point for assertions.
|
const assertion = new Assertion(baseArg, 'sync');
|
||||||
* Automatically detects Promises to support async assertions.
|
return assertion;
|
||||||
*/
|
};
|
||||||
/**
|
|
||||||
* The `expect` function interface. Supports custom matchers via .extend.
|
export const expectAsync = (baseArg: any) => {
|
||||||
*/
|
const assertion = new Assertion(baseArg, 'async');
|
||||||
/**
|
return assertion;
|
||||||
* Entry point for assertions.
|
};
|
||||||
* Automatically detects Promises to support async assertions.
|
|
||||||
*/
|
|
||||||
export function expect<T>(value: Promise<T>): Assertion<T, 'async'>;
|
|
||||||
export function expect<T>(value: T): Assertion<T, 'sync'>;
|
|
||||||
export function expect<T>(value: any): Assertion<T, TExecutionType> {
|
|
||||||
const isThenable = value != null && typeof (value as any).then === 'function';
|
|
||||||
const mode: 'sync' | 'async' = isThenable ? 'async' : 'sync';
|
|
||||||
return new Assertion<T, TExecutionType>(value, mode);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Register custom matchers.
|
|
||||||
*/
|
|
||||||
export namespace expect {
|
|
||||||
export const extend = Assertion.extend;
|
|
||||||
/**
|
|
||||||
* Matcher for a specific constructor. Passes if value is instance of given constructor.
|
|
||||||
*/
|
|
||||||
export function any(constructor: any) {
|
|
||||||
return new AnyMatcher(constructor);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Matcher for any defined value (not null or undefined).
|
|
||||||
*/
|
|
||||||
export function anything() {
|
|
||||||
return new AnythingMatcher();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import * as plugins from '../plugins.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for array-specific matchers
|
|
||||||
*/
|
|
||||||
export class ArrayMatchers<T, M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<T[], M>) {}
|
|
||||||
|
|
||||||
toBeArray() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => Array.isArray(value),
|
|
||||||
`Expected value to be array`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toHaveLength(length: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as T[]).length === length,
|
|
||||||
`Expected array to have length ${length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toContain(item: T) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as T[]).includes(item),
|
|
||||||
`Expected array to contain ${JSON.stringify(item)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toContainEqual(item: T) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as T[]).some((e) => plugins.fastDeepEqual(e, item)),
|
|
||||||
(value) =>
|
|
||||||
`Expected array to contain equal to ${JSON.stringify(item)}` +
|
|
||||||
`\nReceived: ${JSON.stringify(value, null, 2)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toContainAll(items: T[]) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => items.every((i) => (value as T[]).includes(i)),
|
|
||||||
`Expected array to contain all ${JSON.stringify(items)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toExclude(item: T) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => !(value as T[]).includes(item),
|
|
||||||
`Expected array to exclude ${JSON.stringify(item)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeEmptyArray() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => Array.isArray(value) && (value as T[]).length === 0,
|
|
||||||
`Expected array to be empty`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Alias for empty array check
|
|
||||||
*/
|
|
||||||
toBeEmpty() {
|
|
||||||
return this.toBeEmptyArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
toHaveLengthGreaterThan(length: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as T[]).length > length,
|
|
||||||
`Expected array to have length greater than ${length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toHaveLengthLessThan(length: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as T[]).length < length,
|
|
||||||
`Expected array to have length less than ${length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for boolean-specific matchers
|
|
||||||
*/
|
|
||||||
export class BooleanMatchers<M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<boolean, M>) {}
|
|
||||||
|
|
||||||
toBeTrue() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v === true,
|
|
||||||
`Expected value to be true`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeFalse() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v === false,
|
|
||||||
`Expected value to be false`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeTruthy() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => Boolean(v),
|
|
||||||
`Expected value to be truthy`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeFalsy() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => !v,
|
|
||||||
`Expected value to be falsy`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for date-specific matchers
|
|
||||||
*/
|
|
||||||
export class DateMatchers<M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<Date, M>) {}
|
|
||||||
|
|
||||||
toBeDate() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v instanceof Date,
|
|
||||||
`Expected value to be a Date instance`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeBeforeDate(date: Date) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v instanceof Date && (v as Date).getTime() < date.getTime(),
|
|
||||||
`Expected date to be before ${date.toISOString()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeAfterDate(date: Date) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v instanceof Date && (v as Date).getTime() > date.getTime(),
|
|
||||||
`Expected date to be after ${date.toISOString()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for function-specific matchers
|
|
||||||
*/
|
|
||||||
export class FunctionMatchers<M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<Function, M>) {}
|
|
||||||
|
|
||||||
toThrow(expectedError?: any) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => {
|
|
||||||
let threw = false;
|
|
||||||
try {
|
|
||||||
(value as Function)();
|
|
||||||
} catch (e: any) {
|
|
||||||
threw = true;
|
|
||||||
if (expectedError) {
|
|
||||||
if (typeof expectedError === 'function') {
|
|
||||||
return e instanceof expectedError;
|
|
||||||
}
|
|
||||||
return e === expectedError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return threw;
|
|
||||||
},
|
|
||||||
`Expected function to throw${expectedError ? ` ${expectedError}` : ''}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Assert thrown error message matches the given regex
|
|
||||||
*/
|
|
||||||
toThrowErrorMatching(regex: RegExp) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => {
|
|
||||||
try {
|
|
||||||
(value as Function)();
|
|
||||||
} catch (e: any) {
|
|
||||||
return regex.test(e && e.message);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
`Expected function to throw error matching ${regex}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Assert thrown error message equals the given string
|
|
||||||
*/
|
|
||||||
toThrowErrorWithMessage(expectedMessage: string) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => {
|
|
||||||
try {
|
|
||||||
(value as Function)();
|
|
||||||
} catch (e: any) {
|
|
||||||
return e && e.message === expectedMessage;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
`Expected function to throw error with message "${expectedMessage}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
export { StringMatchers } from './string.js';
|
|
||||||
export { ArrayMatchers } from './array.js';
|
|
||||||
export { NumberMatchers } from './number.js';
|
|
||||||
export { BooleanMatchers } from './boolean.js';
|
|
||||||
export { ObjectMatchers } from './object.js';
|
|
||||||
export { FunctionMatchers } from './function.js';
|
|
||||||
export { DateMatchers } from './date.js';
|
|
||||||
export { TypeMatchers } from './type.js';
|
|
@ -1,84 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for number-specific matchers
|
|
||||||
*/
|
|
||||||
export class NumberMatchers<M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<number, M>) {}
|
|
||||||
|
|
||||||
toBeGreaterThan(value: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as number) > value,
|
|
||||||
`Expected number to be greater than ${value}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeLessThan(value: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as number) < value,
|
|
||||||
`Expected number to be less than ${value}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeGreaterThanOrEqual(value: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as number) >= value,
|
|
||||||
`Expected number to be greater than or equal to ${value}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeLessThanOrEqual(value: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as number) <= value,
|
|
||||||
`Expected number to be less than or equal to ${value}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeCloseTo(value: number, precision?: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => {
|
|
||||||
const num = v as number;
|
|
||||||
const p = precision !== undefined ? precision : 2;
|
|
||||||
const diff = Math.abs(num - value);
|
|
||||||
const tolerance = 0.5 * Math.pow(10, -p);
|
|
||||||
return diff <= tolerance;
|
|
||||||
},
|
|
||||||
`Expected number to be close to ${value} within precision ${precision ?? 2}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/** Equality check for numbers */
|
|
||||||
toEqual(value: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as number) === value,
|
|
||||||
`Expected number to equal ${value}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks for NaN
|
|
||||||
*/
|
|
||||||
toBeNaN() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => Number.isNaN(v as number),
|
|
||||||
`Expected number to be NaN`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks for finite number
|
|
||||||
*/
|
|
||||||
toBeFinite() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => Number.isFinite(v as number),
|
|
||||||
`Expected number to be finite`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks if number is within inclusive range
|
|
||||||
*/
|
|
||||||
toBeWithinRange(min: number, max: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as number) >= min && (v as number) <= max,
|
|
||||||
`Expected number to be within range ${min} - ${max}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
import { Assertion, AnyMatcher, AnythingMatcher } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
import * as plugins from '../plugins.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for object-specific matchers
|
|
||||||
*/
|
|
||||||
export class ObjectMatchers<T extends object, M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<T, M>) {}
|
|
||||||
|
|
||||||
toEqual(expected: any) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => plugins.fastDeepEqual(v, expected),
|
|
||||||
(v) =>
|
|
||||||
`Expected objects to be deeply equal to ${JSON.stringify(expected, null, 2)}` +
|
|
||||||
`\nReceived: ${JSON.stringify(v, null, 2)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toMatchObject(expected: object) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => {
|
|
||||||
const obj = v as any;
|
|
||||||
for (const key of Object.keys(expected)) {
|
|
||||||
const expectedVal = (expected as any)[key];
|
|
||||||
const actualVal = obj[key];
|
|
||||||
if (expectedVal instanceof AnyMatcher) {
|
|
||||||
const ctor = expectedVal.expectedConstructor;
|
|
||||||
if (ctor === Number) {
|
|
||||||
if (typeof actualVal !== 'number') return false;
|
|
||||||
} else if (ctor === String) {
|
|
||||||
if (typeof actualVal !== 'string') return false;
|
|
||||||
} else if (ctor === Boolean) {
|
|
||||||
if (typeof actualVal !== 'boolean') return false;
|
|
||||||
} else {
|
|
||||||
if (!(actualVal instanceof ctor)) return false;
|
|
||||||
}
|
|
||||||
} else if (expectedVal instanceof AnythingMatcher) {
|
|
||||||
if (actualVal === null || actualVal === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!plugins.fastDeepEqual(actualVal, expectedVal)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(v) =>
|
|
||||||
`Expected object to match properties ${JSON.stringify(expected, null, 2)}` +
|
|
||||||
`\nReceived: ${JSON.stringify(v, null, 2)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeInstanceOf(constructor: any) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => (v as any) instanceof constructor,
|
|
||||||
`Expected object to be instance of ${constructor.name || constructor}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toHaveProperty(property: string, value?: any) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => {
|
|
||||||
const obj = v as any;
|
|
||||||
if (!(property in obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
return plugins.fastDeepEqual(obj[property], value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
`Expected object to have property ${property}${value !== undefined ? ` with value ${JSON.stringify(value)}` : ''}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toHaveDeepProperty(path: string[]) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => {
|
|
||||||
let obj: any = v;
|
|
||||||
for (const key of path) {
|
|
||||||
if (obj == null || !(key in obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
obj = obj[key];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
`Expected object to have deep property path ${JSON.stringify(path)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
toBeNull() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v === null,
|
|
||||||
`Expected value to be null`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeUndefined() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v === undefined,
|
|
||||||
`Expected value to be undefined`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeNullOrUndefined() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v === null || v === undefined,
|
|
||||||
`Expected value to be null or undefined`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks own property only (not inherited)
|
|
||||||
*/
|
|
||||||
toHaveOwnProperty(property: string, value?: any) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => {
|
|
||||||
const obj = v as any;
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(obj, property)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
return plugins.fastDeepEqual(obj[property], value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(v) =>
|
|
||||||
`Expected object to have own property ${property}` +
|
|
||||||
(value !== undefined ? ` with value ${JSON.stringify(value)}` : ``) +
|
|
||||||
`\nReceived: ${JSON.stringify(v, null, 2)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for string-specific matchers
|
|
||||||
*/
|
|
||||||
export class StringMatchers<M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<string, M>) {}
|
|
||||||
|
|
||||||
toStartWith(prefix: string) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as string).startsWith(prefix),
|
|
||||||
`Expected string to start with "${prefix}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toEndWith(suffix: string) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as string).endsWith(suffix),
|
|
||||||
`Expected string to end with "${suffix}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toInclude(substring: string) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as string).includes(substring),
|
|
||||||
`Expected string to include "${substring}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toMatch(regex: RegExp) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => regex.test(value as string),
|
|
||||||
`Expected string to match ${regex}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeOneOf(values: string[]) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (values as string[]).includes(value as string),
|
|
||||||
`Expected string to be one of ${JSON.stringify(values)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/** Length check for strings */
|
|
||||||
toHaveLength(length: number) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as string).length === length,
|
|
||||||
`Expected string to have length ${length}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Alias for empty string check
|
|
||||||
*/
|
|
||||||
toBeEmpty() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(value) => (value as string).length === 0,
|
|
||||||
`Expected string to be empty`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import { Assertion } from '../smartexpect.classes.assertion.js';
|
|
||||||
import type { TExecutionType } from '../types.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Namespace for type-based matchers
|
|
||||||
*/
|
|
||||||
export class TypeMatchers<M extends TExecutionType> {
|
|
||||||
constructor(private assertion: Assertion<any, M>) {}
|
|
||||||
|
|
||||||
toBeTypeofString() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => typeof v === 'string',
|
|
||||||
`Expected type to be 'string'`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeTypeofNumber() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => typeof v === 'number',
|
|
||||||
`Expected type to be 'number'`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeTypeofBoolean() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => typeof v === 'boolean',
|
|
||||||
`Expected type to be 'boolean'`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeTypeOf(typeName: string) {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => typeof v === typeName,
|
|
||||||
`Expected type to be '${typeName}'`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeDefined() {
|
|
||||||
return this.assertion.customAssertion(
|
|
||||||
(v) => v !== undefined,
|
|
||||||
`Expected value to be defined`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,407 +1,572 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './smartexpect.plugins.js';
|
||||||
import {
|
|
||||||
StringMatchers,
|
|
||||||
ArrayMatchers,
|
|
||||||
NumberMatchers,
|
|
||||||
BooleanMatchers,
|
|
||||||
ObjectMatchers,
|
|
||||||
FunctionMatchers,
|
|
||||||
DateMatchers,
|
|
||||||
TypeMatchers,
|
|
||||||
} from './namespaces/index.js';
|
|
||||||
/**
|
|
||||||
* Definition of a custom matcher function.
|
|
||||||
* Should return an object with `pass` and optional `message`.
|
|
||||||
*/
|
|
||||||
import type { TMatcher, TExecutionType } from './types.js';
|
|
||||||
|
|
||||||
/**
|
export type TExecutionType = 'sync' | 'async';
|
||||||
* Core assertion class. Generic over the current value type T.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Internal matcher classes for expect.any and expect.anything
|
|
||||||
*/
|
|
||||||
export class AnyMatcher {
|
|
||||||
constructor(public expectedConstructor: any) {}
|
|
||||||
}
|
|
||||||
export class AnythingMatcher {}
|
|
||||||
|
|
||||||
export class Assertion<T = unknown, M extends TExecutionType = 'sync'> {
|
export class Assertion {
|
||||||
executionMode: M;
|
executionMode: TExecutionType;
|
||||||
baseReference: any;
|
baseReference: any;
|
||||||
propertyDrillDown: Array<string | number> = [];
|
propertyDrillDown: string[] = [];
|
||||||
|
|
||||||
private notSetting = false;
|
private notSetting = false;
|
||||||
private timeoutSetting = 0;
|
private timeoutSetting = 0;
|
||||||
/** Registry of user-defined custom matchers */
|
constructor(baseReferenceArg: any, executionModeArg: TExecutionType) {
|
||||||
private static customMatchers: Record<string, TMatcher> = {};
|
|
||||||
/** Flag for Promise rejection assertions */
|
|
||||||
private isRejects = false;
|
|
||||||
/** Flag for Promise resolution assertions (default for async) */
|
|
||||||
private isResolves = false;
|
|
||||||
private failMessage: string;
|
|
||||||
private successMessage: string;
|
|
||||||
/** Computed negation failure message for the current assertion */
|
|
||||||
private negativeMessage: string;
|
|
||||||
|
|
||||||
constructor(baseReferenceArg: any, executionModeArg: M) {
|
|
||||||
this.baseReference = baseReferenceArg;
|
this.baseReference = baseReferenceArg;
|
||||||
this.executionMode = executionModeArg;
|
this.executionMode = executionModeArg;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Register custom matchers to be available on all assertions.
|
|
||||||
* @param matchers An object whose keys are matcher names and values are matcher functions.
|
|
||||||
*/
|
|
||||||
public static extend(matchers: Record<string, TMatcher>): void {
|
|
||||||
for (const [name, fn] of Object.entries(matchers)) {
|
|
||||||
if ((Assertion.prototype as any)[name]) {
|
|
||||||
throw new Error(`Cannot extend. Matcher '${name}' already exists on Assertion.`);
|
|
||||||
}
|
|
||||||
// store in registry
|
|
||||||
Assertion.customMatchers[name] = fn;
|
|
||||||
// add method to prototype
|
|
||||||
(Assertion.prototype as any)[name] = function (...args: any[]) {
|
|
||||||
return this.runCheck(() => {
|
|
||||||
const received = this.getObjectToTestReference();
|
|
||||||
const result = fn(received, ...args);
|
|
||||||
const pass = result.pass;
|
|
||||||
const msg = result.message;
|
|
||||||
if (!pass) {
|
|
||||||
const message = typeof msg === 'function' ? msg() : msg;
|
|
||||||
throw new Error(message || `Custom matcher '${name}' failed`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getObjectToTestReference() {
|
private getObjectToTestReference() {
|
||||||
let returnObjectToTestReference = this.baseReference;
|
let returnObjectToTestReference = this.baseReference;
|
||||||
for (const property of this.propertyDrillDown) {
|
for (const property of this.propertyDrillDown) {
|
||||||
if (returnObjectToTestReference == null) {
|
|
||||||
// if it's null or undefined, stop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We just directly access with bracket notation.
|
|
||||||
// If property is a string, it's like obj["someProp"];
|
|
||||||
// If property is a number, it's like obj[0].
|
|
||||||
returnObjectToTestReference = returnObjectToTestReference[property];
|
returnObjectToTestReference = returnObjectToTestReference[property];
|
||||||
}
|
}
|
||||||
return returnObjectToTestReference;
|
return returnObjectToTestReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatDrillDown(): string {
|
|
||||||
if (!this.propertyDrillDown || this.propertyDrillDown.length === 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = this.propertyDrillDown.map(prop => {
|
|
||||||
if (typeof prop === 'number') {
|
|
||||||
return `[${prop}]`;
|
|
||||||
} else {
|
|
||||||
return `.${prop}`;
|
|
||||||
}
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatValue(value: any): string {
|
|
||||||
if (value === null) {
|
|
||||||
return 'null';
|
|
||||||
} else if (value === undefined) {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof value === 'object') {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(value);
|
|
||||||
} catch (e) {
|
|
||||||
return `[Object ${value.constructor.name}]`;
|
|
||||||
}
|
|
||||||
} else if (typeof value === 'function') {
|
|
||||||
return `[Function${value.name ? ': ' + value.name : ''}]`;
|
|
||||||
} else if (typeof value === 'string') {
|
|
||||||
return `"${value}"`;
|
|
||||||
} else {
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createErrorMessage(message: string): string {
|
|
||||||
if (this.failMessage) {
|
|
||||||
return this.failMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
const testValue = this.getObjectToTestReference();
|
|
||||||
const formattedValue = this.formatValue(testValue);
|
|
||||||
const drillDown = this.formatDrillDown();
|
|
||||||
|
|
||||||
// Replace placeholders in the message
|
|
||||||
return message
|
|
||||||
.replace('{value}', formattedValue)
|
|
||||||
.replace('{path}', drillDown || '');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Compute a negated failure message by inserting 'not' into the positive message.
|
|
||||||
*/
|
|
||||||
private computeNegationMessage(message: string): string {
|
|
||||||
const idx = message.indexOf(' to ');
|
|
||||||
if (idx !== -1) {
|
|
||||||
return message.slice(0, idx) + ' not' + message.slice(idx);
|
|
||||||
}
|
|
||||||
return 'Negated: ' + message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get not() {
|
public get not() {
|
||||||
this.notSetting = true;
|
this.notSetting = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Assert that a Promise resolves.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Switch to async (resolve) mode. Subsequent matchers return Promises.
|
|
||||||
*/
|
|
||||||
public get resolves(): Assertion<T, 'async'> {
|
|
||||||
return new Assertion<T, 'async'>(this.baseReference, 'async');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Assert that a Promise rejects.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Switch to async (reject) mode. Subsequent matchers return Promises.
|
|
||||||
*/
|
|
||||||
public get rejects(): Assertion<T, 'async'> {
|
|
||||||
const a = new Assertion<T, 'async'>(this.baseReference, 'async');
|
|
||||||
// mark to expect rejection
|
|
||||||
(a as any).isRejects = true;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use `.withTimeout(ms)` instead for clarity
|
|
||||||
* Set a timeout (in ms) for async assertions (Promise must settle before timeout).
|
|
||||||
*/
|
|
||||||
public timeout(millisArg: number) {
|
public timeout(millisArg: number) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn('[DEPRECATED] .timeout() is deprecated. Use .withTimeout(ms)');
|
|
||||||
this.timeoutSetting = millisArg;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set a timeout (in ms) for async assertions (Promise must settle before timeout).
|
|
||||||
*/
|
|
||||||
public withTimeout(millisArg: number) {
|
|
||||||
this.timeoutSetting = millisArg;
|
this.timeoutSetting = millisArg;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private failMessage: string;
|
||||||
public setFailMessage(failMessageArg: string) {
|
public setFailMessage(failMessageArg: string) {
|
||||||
this.failMessage = failMessageArg;
|
this.failMessage = failMessageArg;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private successMessage: string;
|
||||||
public setSuccessMessage(successMessageArg: string) {
|
public setSuccessMessage(successMessageArg: string) {
|
||||||
this.successMessage = successMessageArg;
|
this.successMessage = successMessageArg;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal check runner: returns Promise in async mode, else sync Assertion
|
private runCheck(checkFunction: () => any) {
|
||||||
// Internal check runner; returns Promise or this at runtime, but typed via customAssertion
|
|
||||||
private runCheck(checkFunction: () => any): any {
|
|
||||||
const runDirectOrNegated = (checkFunction: () => any) => {
|
const runDirectOrNegated = (checkFunction: () => any) => {
|
||||||
if (!this.notSetting) {
|
if (!this.notSetting) {
|
||||||
return checkFunction();
|
return checkFunction();
|
||||||
} else {
|
} else {
|
||||||
let isOk = false;
|
let isOk = false;
|
||||||
try {
|
try {
|
||||||
// attempt positive assertion and expect it to throw
|
runDirectOrNegated(checkFunction());
|
||||||
checkFunction();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isOk = true;
|
isOk = true;
|
||||||
}
|
}
|
||||||
if (!isOk) {
|
if (!isOk) {
|
||||||
const msg = this.failMessage || this.negativeMessage || 'Negated assertion failed';
|
throw new Error(this.failMessage || 'Negated assertion is not ok!');
|
||||||
throw new Error(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.executionMode === 'async') {
|
if (this.executionMode === 'async') {
|
||||||
const done = plugins.smartpromise.defer<Assertion<T, M>>();
|
const done = plugins.smartpromise.defer();
|
||||||
const isThenable = this.baseReference && typeof (this.baseReference as any).then === 'function';
|
if (!(this.baseReference instanceof Promise)) {
|
||||||
if (!isThenable) {
|
done.reject(new Error(`${this.baseReference} is not of type promise.`));
|
||||||
done.reject(new Error(`Expected a Promise but received: ${this.formatValue(this.baseReference)}`));
|
} else {
|
||||||
return done.promise;
|
if (this.timeoutSetting) {
|
||||||
}
|
plugins.smartdelay.delayFor(this.timeoutSetting).then(() => {
|
||||||
if (this.timeoutSetting) {
|
if (done.status === 'pending') {
|
||||||
plugins.smartdelay.delayFor(this.timeoutSetting).then(() => {
|
done.reject(new Error(`${this.baseReference} timed out at ${this.timeoutSetting}!`));
|
||||||
if (done.status === 'pending') {
|
}
|
||||||
done.reject(new Error(`Promise timed out after ${this.timeoutSetting}ms`));
|
});
|
||||||
}
|
}
|
||||||
|
this.baseReference.then((promiseResultArg) => {
|
||||||
|
this.baseReference = promiseResultArg;
|
||||||
|
done.resolve(runDirectOrNegated(checkFunction));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.isRejects) {
|
return done.promise;
|
||||||
(this.baseReference as Promise<any>).then(
|
} else {
|
||||||
(res: any) => {
|
return runDirectOrNegated(checkFunction);
|
||||||
done.reject(new Error(`Expected Promise to reject but it resolved with ${this.formatValue(res)}`));
|
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
this.baseReference = err;
|
|
||||||
try {
|
|
||||||
runDirectOrNegated(checkFunction);
|
|
||||||
done.resolve(this);
|
|
||||||
} catch (e: any) {
|
|
||||||
done.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
(this.baseReference as Promise<any>).then(
|
|
||||||
(res: any) => {
|
|
||||||
this.baseReference = res;
|
|
||||||
try {
|
|
||||||
runDirectOrNegated(checkFunction);
|
|
||||||
done.resolve(this);
|
|
||||||
} catch (e: any) {
|
|
||||||
done.reject(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
done.reject(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// return a promise resolving to this for chaining
|
|
||||||
return done.promise.then(() => this) as any;
|
|
||||||
}
|
}
|
||||||
// sync: run and return this for chaining
|
|
||||||
runDirectOrNegated(checkFunction);
|
|
||||||
return this as any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a custom assertion. Returns a Promise in async mode, else returns this.
|
* checks if the given object is defined
|
||||||
*/
|
*/
|
||||||
public customAssertion(
|
public toBeDefined() {
|
||||||
assertionFunction: (value: any) => boolean,
|
|
||||||
errorMessage: string | ((value: any) => string)
|
|
||||||
): M extends 'async' ? Promise<Assertion<T, M>> : Assertion<T, M> {
|
|
||||||
// Prepare negation message based on the positive error template, if static
|
|
||||||
if (typeof errorMessage === 'string') {
|
|
||||||
this.negativeMessage = this.computeNegationMessage(errorMessage);
|
|
||||||
}
|
|
||||||
return this.runCheck(() => {
|
return this.runCheck(() => {
|
||||||
const value = this.getObjectToTestReference();
|
if (this.getObjectToTestReference() === undefined) {
|
||||||
if (!assertionFunction(value)) {
|
throw new Error(
|
||||||
const msg = this.failMessage
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not defined`
|
||||||
|| (typeof errorMessage === 'function' ? errorMessage(value) : errorMessage);
|
);
|
||||||
throw new Error(msg);
|
|
||||||
}
|
}
|
||||||
}) as any;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drill into a property of an object.
|
* checks if the given object is not defined
|
||||||
* @param propertyName Name of the property to navigate into.
|
|
||||||
* @returns Assertion of the property type.
|
|
||||||
*/
|
*/
|
||||||
public property<K extends keyof NonNullable<T>>(propertyName: K): Assertion<NonNullable<T>[K], M> {
|
public toBeTypeofString() {
|
||||||
this.propertyDrillDown.push(propertyName as string);
|
return this.runCheck(() => {
|
||||||
return this as unknown as Assertion<NonNullable<T>[K], M>;
|
if (typeof this.getObjectToTestReference() !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `Assertion failed: ${this.baseReference} with drill down ${
|
||||||
|
this.propertyDrillDown
|
||||||
|
} is not of type string, but typeof ${typeof this.baseReference}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public toBeTypeofNumber() {
|
||||||
* Drill into an array element by index.
|
return this.runCheck(() => {
|
||||||
* @param index Index of the array item.
|
if (typeof this.getObjectToTestReference() !== 'number') {
|
||||||
* @returns Assertion of the element type.
|
throw new Error(
|
||||||
*/
|
this.failMessage || `Assertion failed: ${this.baseReference} with drill down ${
|
||||||
public arrayItem(index: number): Assertion<T extends Array<infer U> ? U : unknown, M> {
|
this.propertyDrillDown
|
||||||
this.propertyDrillDown.push(index);
|
} is not of type string, but typeof ${typeof this.baseReference}`
|
||||||
return this as unknown as Assertion<T extends Array<infer U> ? U : unknown, M>;
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public log() {
|
public toBeTypeofBoolean() {
|
||||||
console.log(`Current value:`);
|
return this.runCheck(() => {
|
||||||
console.log(JSON.stringify(this.getObjectToTestReference(), null, 2));
|
if (typeof this.getObjectToTestReference() !== 'boolean') {
|
||||||
console.log(`Path: ${this.formatDrillDown() || '(root)'}`);
|
throw new Error(
|
||||||
|
this.failMessage || `Assertion failed: ${this.baseReference} with drill down ${
|
||||||
|
this.propertyDrillDown
|
||||||
|
} is not of type string, but typeof ${typeof this.baseReference}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toEqual(comparisonObject: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = plugins.fastDeepEqual(this.getObjectToTestReference(), comparisonObject);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not equal ${comparisonObject}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toMatch(comparisonObject: RegExp) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = comparisonObject.test(this.getObjectToTestReference());
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not equal ${comparisonObject}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeTrue() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result =
|
||||||
|
typeof this.getObjectToTestReference() === 'boolean' &&
|
||||||
|
this.getObjectToTestReference() === true;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not true or not of type boolean`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeFalse() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result =
|
||||||
|
typeof this.getObjectToTestReference() === 'boolean' &&
|
||||||
|
this.getObjectToTestReference() === false;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not false or not of type boolean`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeInstanceOf(classArg: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = this.getObjectToTestReference() instanceof classArg;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not an instance of ${classArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toHaveProperty(propertyArg: string, equalsArg?: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = !!this.getObjectToTestReference()[propertyArg];
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have property ${propertyArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (equalsArg) {
|
||||||
|
if (result !== equalsArg) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does have property ${propertyArg}, but it does not equal ${equalsArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toHaveDeepProperty(properties: string[]) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
let obj = this.getObjectToTestReference();
|
||||||
|
let currentPath = '';
|
||||||
|
|
||||||
|
for (const property of properties) {
|
||||||
|
if (currentPath) {
|
||||||
|
currentPath += `.${property}`;
|
||||||
|
} else {
|
||||||
|
currentPath = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obj || !(property in obj)) {
|
||||||
|
throw new Error(this.failMessage || `Missing property at path "${currentPath}" in ${this.baseReference}`);
|
||||||
|
}
|
||||||
|
obj = obj[property];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeGreaterThan(numberArg: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = this.getObjectToTestReference() > numberArg;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not greater than ${numberArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeLessThan(numberArg: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = this.getObjectToTestReference() < numberArg;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not less than ${numberArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeNull() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = this.getObjectToTestReference() === null;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not null`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeUndefined() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = this.getObjectToTestReference() === undefined;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not undefined`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeNullOrUndefined() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result =
|
||||||
|
this.getObjectToTestReference() === null || this.getObjectToTestReference() === undefined;
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not null or undefined`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array
|
||||||
|
|
||||||
|
public toContain(itemArg: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result =
|
||||||
|
this.getObjectToTestReference() instanceof Array &&
|
||||||
|
this.getObjectToTestReference().includes(itemArg);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not contain ${itemArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeEmptyArray() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const arrayRef = this.getObjectToTestReference();
|
||||||
|
if (!Array.isArray(arrayRef) || arrayRef.length !== 0) {
|
||||||
|
throw new Error(this.failMessage || `Expected ${this.baseReference} to be an empty array, but it was not.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toContainAll(values: any[]) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const arrayRef = this.getObjectToTestReference();
|
||||||
|
if (!Array.isArray(arrayRef)) {
|
||||||
|
throw new Error(this.failMessage || `Expected ${this.baseReference} to be an array.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
if (!arrayRef.includes(value)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `Expected ${this.baseReference} to include value "${value}", but it did not.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toExclude(value: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const arrayRef = this.getObjectToTestReference();
|
||||||
|
if (!Array.isArray(arrayRef)) {
|
||||||
|
throw new Error(this.failMessage || `Expected ${this.baseReference} to be an array.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arrayRef.includes(value)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `Expected ${this.baseReference} to exclude value "${value}", but it included it.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toStartWith(itemArg: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const testObject = this.getObjectToTestReference();
|
||||||
|
const result = typeof testObject === 'string' && testObject.startsWith(itemArg);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not contain ${itemArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toEndWith(itemArg: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const testObject = this.getObjectToTestReference();
|
||||||
|
const result = typeof testObject === 'string' && testObject.endsWith(itemArg);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not contain ${itemArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... previous code ...
|
||||||
|
|
||||||
|
public toBeOneOf(values: any[]) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const result = values.includes(this.getObjectToTestReference());
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not one of ${values}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toHaveLength(length: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const obj = this.getObjectToTestReference();
|
||||||
|
if (typeof obj.length !== 'number' || obj.length !== length) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have a length of ${length}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeCloseTo(value: number, precision = 2) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const difference = Math.abs(this.getObjectToTestReference() - value);
|
||||||
|
if (difference > Math.pow(10, -precision) / 2) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not close to ${value} up to ${precision} decimal places`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toThrow(expectedError?: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
this.getObjectToTestReference()();
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
if (expectedError && !(e instanceof expectedError)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `Expected function to throw ${expectedError.name}, but it threw ${e.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!thrown) {
|
||||||
|
throw new Error(`Expected function to throw, but it didn't.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeTruthy() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!this.getObjectToTestReference()) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not truthy`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeFalsy() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (this.getObjectToTestReference()) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not falsy`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeGreaterThanOrEqual(numberArg: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (this.getObjectToTestReference() <= numberArg) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not greater than or equal to ${numberArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeLessThanOrEqual(numberArg: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (this.getObjectToTestReference() >= numberArg) {
|
||||||
|
throw new Error(
|
||||||
|
`${this.baseReference} with drill down ${this.propertyDrillDown} is not less than or equal to ${numberArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toMatchObject(objectArg: object) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const partialMatch = plugins.fastDeepEqual(this.getObjectToTestReference(), objectArg); // Note: Implement a deep comparison function or use one from a library
|
||||||
|
if (!partialMatch) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not match the object ${objectArg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toContainEqual(value: any) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const arr = this.getObjectToTestReference();
|
||||||
|
const found = arr.some((item: any) => plugins.fastDeepEqual(item, value)); // Assuming fastDeepEqual checks deep equality
|
||||||
|
if (!found) {
|
||||||
|
throw new Error(
|
||||||
|
`${this.baseReference} with drill down ${this.propertyDrillDown} does not contain the value ${value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeArray() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!Array.isArray(this.getObjectToTestReference())) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not an array`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toInclude(substring: string) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!this.getObjectToTestReference().includes(substring)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not include the substring ${substring}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toHaveLengthGreaterThan(length: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const obj = this.getObjectToTestReference();
|
||||||
|
if (typeof obj.length !== 'number' || obj.length <= length) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have a length greater than ${length}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toHaveLengthLessThan(length: number) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
const obj = this.getObjectToTestReference();
|
||||||
|
if (typeof obj.length !== 'number' || obj.length >= length) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have a length less than ${length}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeDate() {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!(this.getObjectToTestReference() instanceof Date)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not a date`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeBeforeDate(date: Date) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!(this.getObjectToTestReference() < date)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not before ${date}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toBeAfterDate(date: Date) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!(this.getObjectToTestReference() > date)) {
|
||||||
|
throw new Error(
|
||||||
|
this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not after ${date}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public customAssertion(assertionFunction: (value: any) => boolean, errorMessage: string) {
|
||||||
|
return this.runCheck(() => {
|
||||||
|
if (!assertionFunction(this.getObjectToTestReference())) {
|
||||||
|
throw new Error(this.failMessage || errorMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public property(propertyNameArg: string) {
|
||||||
|
this.propertyDrillDown.push(propertyNameArg);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
// Direct (flat) matcher aliases
|
|
||||||
public toEqual(expected: any) {
|
|
||||||
return this.customAssertion(
|
|
||||||
(v) => plugins.fastDeepEqual(v, expected),
|
|
||||||
`Expected value to equal ${JSON.stringify(expected)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public toBeTrue() { return this.boolean.toBeTrue(); }
|
|
||||||
public toBeFalse() { return this.boolean.toBeFalse(); }
|
|
||||||
public toBeTruthy() { return this.boolean.toBeTruthy(); }
|
|
||||||
public toBeFalsy() { return this.boolean.toBeFalsy(); }
|
|
||||||
public toThrow(expectedError?: any) { return this.function.toThrow(expectedError); }
|
|
||||||
public toBeGreaterThan(value: number) { return this.number.toBeGreaterThan(value); }
|
|
||||||
public toBeLessThan(value: number) { return this.number.toBeLessThan(value); }
|
|
||||||
public toBeGreaterThanOrEqual(value: number) { return this.number.toBeGreaterThanOrEqual(value); }
|
|
||||||
public toBeLessThanOrEqual(value: number) { return this.number.toBeLessThanOrEqual(value); }
|
|
||||||
public toBeCloseTo(value: number, precision?: number) { return this.number.toBeCloseTo(value, precision); }
|
|
||||||
public toBeArray() { return this.array.toBeArray(); }
|
|
||||||
public toContain(item: any) { return this.array.toContain(item); }
|
|
||||||
public toContainEqual(item: any) { return this.array.toContainEqual(item); }
|
|
||||||
public toContainAll(items: any[]) { return this.array.toContainAll(items); }
|
|
||||||
public toExclude(item: any) { return this.array.toExclude(item); }
|
|
||||||
public toBeEmptyArray() { return this.array.toBeEmptyArray(); }
|
|
||||||
public toStartWith(prefix: string) { return this.string.toStartWith(prefix); }
|
|
||||||
public toEndWith(suffix: string) { return this.string.toEndWith(suffix); }
|
|
||||||
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 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); }
|
|
||||||
public toBeNull() { return this.object.toBeNull(); }
|
|
||||||
public toBeUndefined() { return this.object.toBeUndefined(); }
|
|
||||||
public toBeNullOrUndefined() { return this.object.toBeNullOrUndefined(); }
|
|
||||||
public toBeDate() { return this.date.toBeDate(); }
|
|
||||||
public toBeBeforeDate(date: Date) { return this.date.toBeBeforeDate(date); }
|
|
||||||
public toBeAfterDate(date: Date) { return this.date.toBeAfterDate(date); }
|
|
||||||
public toBeTypeofString() { return this.type.toBeTypeofString(); }
|
|
||||||
public toBeTypeofNumber() { return this.type.toBeTypeofNumber(); }
|
|
||||||
public toBeTypeofBoolean() { return this.type.toBeTypeofBoolean(); }
|
|
||||||
public toBeTypeOf(typeName: string) { return this.type.toBeTypeOf(typeName); }
|
|
||||||
public toBeDefined() { return this.type.toBeDefined(); }
|
|
||||||
|
|
||||||
// Namespaced matcher accessors
|
|
||||||
/** String-specific matchers */
|
|
||||||
public get string(): StringMatchers<M> {
|
|
||||||
return new StringMatchers<M>(this as Assertion<string, M>);
|
|
||||||
}
|
|
||||||
/** Array-specific matchers */
|
|
||||||
public get array(): ArrayMatchers<any, M> {
|
|
||||||
return new ArrayMatchers<any, M>(this as Assertion<any[], M>);
|
|
||||||
}
|
|
||||||
/** Number-specific matchers */
|
|
||||||
public get number(): NumberMatchers<M> {
|
|
||||||
return new NumberMatchers<M>(this as Assertion<number, M>);
|
|
||||||
}
|
|
||||||
/** Boolean-specific matchers */
|
|
||||||
public get boolean(): BooleanMatchers<M> {
|
|
||||||
return new BooleanMatchers<M>(this as Assertion<boolean, M>);
|
|
||||||
}
|
|
||||||
/** Object-specific matchers */
|
|
||||||
public get object(): ObjectMatchers<any, M> {
|
|
||||||
return new ObjectMatchers<any, M>(this as Assertion<object, M>);
|
|
||||||
}
|
|
||||||
/** Function-specific matchers */
|
|
||||||
public get function(): FunctionMatchers<M> {
|
|
||||||
return new FunctionMatchers<M>(this as Assertion<Function, M>);
|
|
||||||
}
|
|
||||||
/** Date-specific matchers */
|
|
||||||
public get date(): DateMatchers<M> {
|
|
||||||
return new DateMatchers<M>(this as Assertion<Date, M>);
|
|
||||||
}
|
|
||||||
/** Type-based matchers */
|
|
||||||
public get type(): TypeMatchers<M> {
|
|
||||||
return new TypeMatchers<M>(this as Assertion<any, M>);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
|
||||||
export { smartdelay, smartpromise };
|
export { smartdelay, smartpromise };
|
||||||
|
|
||||||
// third party utilities
|
// third party scope
|
||||||
import fastDeepEqual from 'fast-deep-equal';
|
import fastDeepEqual from 'fast-deep-equal';
|
||||||
|
|
||||||
export { fastDeepEqual };
|
export { fastDeepEqual };
|
13
ts/types.ts
13
ts/types.ts
@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Common types for smartexpect
|
|
||||||
*/
|
|
||||||
/** Execution mode: sync or async */
|
|
||||||
export type TExecutionType = 'sync' | 'async';
|
|
||||||
/**
|
|
||||||
* Definition of a custom matcher function.
|
|
||||||
* Should return an object with `pass` and optional `message`.
|
|
||||||
*/
|
|
||||||
export type TMatcher = (
|
|
||||||
received: any,
|
|
||||||
...args: any[]
|
|
||||||
) => { pass: boolean; message?: string | (() => string) };
|
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user