Skip to content

Commit 6e1e4a8

Browse files
committed
docs: update expectFailure proposal with direct matchers and rationale
1 parent abb5912 commit 6e1e4a8

File tree

1 file changed

+35
-34
lines changed

1 file changed

+35
-34
lines changed

proposals/expect-failure-enhancements.md

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,25 @@ test('fails with a specific reason', {
2121
- **Validation**: None. It accepts *any* error.
2222
- **Output**: The reporter displays the string (e.g., `# EXPECTED FAILURE Bug #123...`).
2323

24-
### 2. RegExp: Error Matcher (via Object)
25-
Use the object form with the `with` property.
24+
### 2. Matcher: RegExp, Class, or Error Object
25+
When a **RegExp**, **Class** (Function), or **Error Object** is provided directly, it acts as the validation logic. This leverages `assert.throws` behavior directly.
2626

2727
```js
28-
test('fails with matching error', {
29-
expectFailure: { with: /expected error message/ }
28+
test('fails with matching error (RegExp)', {
29+
expectFailure: /expected error message/
3030
}, () => {
3131
throw new Error('this is the expected error message');
3232
});
33+
34+
test('fails with matching error (Class)', {
35+
expectFailure: RangeError
36+
}, () => {
37+
throw new RangeError('Index out of bounds');
38+
});
3339
```
3440

35-
### 3. Object: Reason & Validation
36-
When an **Object** is provided, it allows specifying both a failure reason and validation logic simultaneously.
41+
### 3. Configuration Object: Reason & Validation
42+
When a **Plain Object** with specific properties (`with`, `message`) is provided, it allows specifying both a failure reason and validation logic simultaneously.
3743

3844
```js
3945
test('fails with reason and specific error', {
@@ -51,46 +57,41 @@ test('fails with reason and specific error', {
5157
- **Behavior**: The test passes **only if** the error matches the `with` criteria.
5258
- **Output**: The reporter displays the `message`.
5359

60+
### Equivalence
61+
The following configurations are equivalent in behavior (both set a failure reason without validation):
62+
```js
63+
expectFailure: 'reason'
64+
expectFailure: { message: 'reason' }
65+
```
66+
5467
## Ambiguity Resolution
55-
Potential ambiguity is resolved by strict type separation:
56-
* `typeof value === 'string'`**Reason**
57-
* `typeof value === 'object'`**Configuration Object** (`message` and/or `with`)
68+
Potential ambiguity between a **Matcher Object** and a **Configuration Object** is resolved as follows:
69+
70+
1. **String** → Reason.
71+
2. **RegExp** or **Function** → Matcher (Validation).
72+
3. **Object**:
73+
* If the object contains `with` or `message` properties → **Configuration Object**.
74+
* Otherwise → **Matcher Object** (passed to `assert.throws` for property matching).
5875

5976
## Alternatives Considered
6077

6178
### Flat Options (`expectFailureError`)
6279
It was proposed to split the options into `expectFailure` (reason) and `expectFailureError` (validation).
63-
```js
64-
{
65-
expectFailure: 'reason',
66-
expectFailureError: /error/
67-
}
68-
```
69-
This was rejected in favor of the nested object structure to:
70-
1. Keep related configuration grouped.
71-
2. Avoid polluting the top-level options namespace.
72-
3. Allow future extensibility within the `expectFailure` object.
80+
This was rejected in favor of the nested/polymorphic structure using `with` and `message` properties. This syntax was selected as the preferred choice for its readability and clarity:
81+
* `with`: Clearly indicates "fails **with** this error" (Validation).
82+
* `message`: Clearly indicates the **reason** or label for the expected failure.
83+
This approach keeps related configuration grouped without polluting the top-level options namespace.
7384

7485
## Implementation Details
7586

7687
### Validation Logic
77-
The implementation leverages `assert.throws` internally to perform error validation. This ensures consistency with the existing assertion ecosystem and supports advanced validation (Classes, Custom Functions) out of the box without code duplication.
88+
The implementation leverages `assert.throws` internally to perform error validation.
89+
- If `expectFailure` is a Matcher (RegExp, Class, Object), it is passed as the second argument to `assert.throws(fn, expectFailure)`.
90+
- If `expectFailure` is a Configuration Object, `expectFailure.with` is passed to `assert.throws`.
7891

79-
## Edge Cases & Implementation Details
92+
## Edge Cases
8093

8194
### Empty String (`expectFailure: ''`)
8295
Following standard JavaScript truthiness rules, an empty string should be treated as **falsy**.
8396
* `expectFailure: ''` behaves exactly like `expectFailure: false`.
84-
* The feature is **disabled**, and the test is expected to pass normally.
85-
86-
### Type Safety for `this.passed`
87-
The implementation must ensure that `this.passed` remains a strict `boolean`.
88-
Assigning a string directly (e.g., `this.passed = this.expectFailure`) is unsafe as it introduces type pollution.
89-
90-
**Recommended Implementation Logic:**
91-
```javascript
92-
// When an error is caught:
93-
this.passed = !!this.expectFailure; // Forces conversion to boolean
94-
```
95-
* If `expectFailure` is `"reason"``true` (Test Passes)
96-
* If `expectFailure` is `""``false` (Test Fails, as expected failure was not active)
97+
* The feature is **disabled**, and the test is expected to pass normally.

0 commit comments

Comments
 (0)