| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ea2df2a commit fce2930
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -265,11 +265,12 @@ added: | |||
| 265 | 265 | - v25.5.0 | |
| 266 | 266 | --> | |
| 267 | 267 | ||
| 268 | - This flips the pass/fail reporting for a specific test or suite: A flagged test/test-case must throw | ||
| 269 | - in order to "pass"; a test/test-case that does not throw, fails. | ||
| 268 | + This flips the pass/fail reporting for a specific test or suite: a flagged test | ||
| 269 | + case must throw in order to pass, and a flagged test case that does not throw | ||
| 270 | + fails. | ||
| 270 | 271 | ||
| 271 | - In the following, `doTheThing()` returns _currently_ `false` (`false` does not equal `true`, causing | ||
| 272 | - `strictEqual` to throw, so the test-case passes). | ||
| 272 | + In each of the following, `doTheThing()` fails to return `true`, but since the | ||
| 273 | + tests are flagged `expectFailure`, they pass. | ||
| 273 | 274 | ||
| 274 | 275 | ```js | |
| 275 | 276 | it.expectFailure('should do the thing', () => { | |
@@ -279,6 +280,50 @@ it.expectFailure('should do the thing', () => { | |||
| 279 | 280 | it('should do the thing', { expectFailure: true }, () => { | |
| 280 | 281 | assert.strictEqual(doTheThing(), true); | |
| 281 | 282 | }); | |
| 283 | + | ||
| 284 | + it('should do the thing', { expectFailure: 'feature not implemented' }, () => { | ||
| 285 | + assert.strictEqual(doTheThing(), true); | ||
| 286 | + }); | ||
| 287 | + ``` | ||
| 288 | + | ||
| 289 | + If the value of `expectFailure` is a | ||
| 290 | + [<RegExp>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) | | ||
| 291 | + [<Function>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) | | ||
| 292 | + [<Object>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) | | ||
| 293 | + [<Error>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error), | ||
| 294 | + the tests will pass only if they throw a matching value. | ||
| 295 | + See [`assert.throws`][] for how each value type is handled. | ||
| 296 | + | ||
| 297 | + Each of the following tests fails _despite_ being flagged `expectFailure` | ||
| 298 | + because the failure does not match the specific **expected** failure. | ||
| 299 | + | ||
| 300 | + ```js | ||
| 301 | + it('fails because regex does not match', { | ||
| 302 | + expectFailure: /expected message/, | ||
| 303 | + }, () => { | ||
| 304 | + throw new Error('different message'); | ||
| 305 | + }); | ||
| 306 | + | ||
| 307 | + it('fails because object matcher does not match', { | ||
| 308 | + expectFailure: { code: 'ERR_EXPECTED' }, | ||
| 309 | + }, () => { | ||
| 310 | + const err = new Error('boom'); | ||
| 311 | + err.code = 'ERR_ACTUAL'; | ||
| 312 | + throw err; | ||
| 313 | + }); | ||
| 314 | + ``` | ||
| 315 | + | ||
| 316 | + To supply both a reason and specific error for `expectFailure`, use `{ label, match }`. | ||
| 317 | + | ||
| 318 | + ```js | ||
| 319 | + it('should fail with specific error and reason', { | ||
| 320 | + expectFailure: { | ||
| 321 | + label: 'reason for failure', | ||
| 322 | + match: /error message/, | ||
| 323 | + }, | ||
| 324 | + }, () => { | ||
| 325 | + assert.strictEqual(doTheThing(), true); | ||
| 326 | + }); | ||
| 282 | 327 | ``` | |
| 283 | 328 | ||
| 284 | 329 | `skip` and/or `todo` are mutually exclusive to `expectFailure`, and `skip` or `todo` | |
@@ -1684,6 +1729,18 @@ changes: | |||
| 1684 | 1729 | thread. If `false`, only one test runs at a time. | |
| 1685 | 1730 | If unspecified, subtests inherit this value from their parent. | |
| 1686 | 1731 | **Default:** `false`. | |
| 1732 | + * `expectFailure` {boolean|string|RegExp|Function|Object|Error} If truthy, the | ||
| 1733 | + test is expected to fail. If a non-empty string is provided, that string is displayed | ||
| 1734 | + in the test results as the reason why the test is expected to fail. If a | ||
| 1735 | + [<RegExp>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp), | ||
| 1736 | + [<Function>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function), | ||
| 1737 | + [<Object>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object), or | ||
| 1738 | + [<Error>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) | ||
| 1739 | + is provided directly (without wrapping in `{ match: … }`), the test passes | ||
| 1740 | + only if the thrown error matches, following the behavior of | ||
| 1741 | + [`assert.throws`][]. To provide both a reason and validation, pass an object | ||
| 1742 | + with `label` (string) and `match` (RegExp, Function, Object, or Error). | ||
| 1743 | + **Default:** `false`. | ||
| 1687 | 1744 | * `only` {boolean} If truthy, and the test context is configured to run | |
| 1688 | 1745 | `only` tests, then this test will be run. Otherwise, the test is skipped. | |
| 1689 | 1746 | **Default:** `false`. | |
@@ -4181,6 +4238,7 @@ Can be used to abort test subtasks when the test has been aborted. | |||
| 4181 | 4238 | [`NODE_V8_COVERAGE`]: cli.md#node_v8_coveragedir | |
| 4182 | 4239 | [`SuiteContext`]: #class-suitecontext | |
| 4183 | 4240 | [`TestContext`]: #class-testcontext | |
| 4241 | + [`assert.throws`]: assert.md#assertthrowsfn-error-message | ||
| 4184 | 4242 | [`context.diagnostic`]: #contextdiagnosticmessage | |
| 4185 | 4243 | [`context.skip`]: #contextskipmessage | |
| 4186 | 4244 | [`context.todo`]: #contexttodomessage | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -87,7 +87,7 @@ function reportTest(nesting, testNumber, status, name, skip, todo, expectFailure | |||
| 87 | 87 | } else if (todo !== undefined) { | |
| 88 | 88 | line += ` # TODO${typeof todo === 'string' && todo.length ? ` ${tapEscape(todo)}` : ''}`; | |
| 89 | 89 | } else if (expectFailure !== undefined) { | |
| 90 | - line += ' # EXPECTED FAILURE'; | ||
| 90 | + line += ` # EXPECTED FAILURE${typeof expectFailure === 'string' ? ` ${tapEscape(expectFailure)}` : ''}`; | ||
| 91 | 91 | } | |
| 92 | 92 | ||
| 93 | 93 | line += '\n'; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | const { | |
| 3 | + ArrayPrototypeEvery, | ||
| 3 | 4 | ArrayPrototypePush, | |
| 4 | 5 | ArrayPrototypePushApply, | |
| 5 | 6 | ArrayPrototypeShift, | |
@@ -13,6 +14,7 @@ const { | |||
| 13 | 14 | MathMax, | |
| 14 | 15 | Number, | |
| 15 | 16 | NumberPrototypeToFixed, | |
| 17 | + ObjectKeys, | ||
| 16 | 18 | ObjectSeal, | |
| 17 | 19 | Promise, | |
| 18 | 20 | PromisePrototypeThen, | |
@@ -40,6 +42,7 @@ const { | |||
| 40 | 42 | AbortError, | |
| 41 | 43 | codes: { | |
| 42 | 44 | ERR_INVALID_ARG_TYPE, | |
| 45 | + ERR_INVALID_ARG_VALUE, | ||
| 43 | 46 | ERR_TEST_FAILURE, | |
| 44 | 47 | }, | |
| 45 | 48 | } = require('internal/errors'); | |
@@ -56,7 +59,8 @@ const { | |||
| 56 | 59 | once: runOnce, | |
| 57 | 60 | setOwnProperty, | |
| 58 | 61 | } = require('internal/util'); | |
| 59 | - const { isPromise } = require('internal/util/types'); | ||
| 62 | + const assert = require('assert'); | ||
| 63 | + const { isPromise, isRegExp } = require('internal/util/types'); | ||
| 60 | 64 | const { | |
| 61 | 65 | validateAbortSignal, | |
| 62 | 66 | validateFunction, | |
@@ -492,6 +496,39 @@ class SuiteContext { | |||
| 492 | 496 | } | |
| 493 | 497 | } | |
| 494 | 498 | ||
| 499 | + function parseExpectFailure(expectFailure) { | ||
| 500 | + if (expectFailure === undefined || expectFailure === false) { | ||
| 501 | + return false; | ||
| 502 | + } | ||
| 503 | + | ||
| 504 | + if (typeof expectFailure === 'string') { | ||
| 505 | + return { __proto__: null, label: expectFailure, match: undefined }; | ||
| 506 | + } | ||
| 507 | + | ||
| 508 | + if (typeof expectFailure === 'function' || isRegExp(expectFailure)) { | ||
| 509 | + return { __proto__: null, label: undefined, match: expectFailure }; | ||
| 510 | + } | ||
| 511 | + | ||
| 512 | + if (typeof expectFailure !== 'object') { | ||
| 513 | + return { __proto__: null, label: undefined, match: undefined }; | ||
| 514 | + } | ||
| 515 | + | ||
| 516 | + const keys = ObjectKeys(expectFailure); | ||
| 517 | + if (keys.length === 0) { | ||
| 518 | + throw new ERR_INVALID_ARG_VALUE('options.expectFailure', expectFailure, 'must not be an empty object'); | ||
| 519 | + } | ||
| 520 | + | ||
| 521 | + if (ArrayPrototypeEvery(keys, (k) => k === 'match' || k === 'label')) { | ||
| 522 | + return { | ||
| 523 | + __proto__: null, | ||
| 524 | + label: expectFailure.label, | ||
| 525 | + match: expectFailure.match, | ||
| 526 | + }; | ||
| 527 | + } | ||
| 528 | + | ||
| 529 | + return { __proto__: null, label: undefined, match: expectFailure }; | ||
| 530 | + } | ||
| 531 | + | ||
| 495 | 532 | class Test extends AsyncResource { | |
| 496 | 533 | reportedType = 'test'; | |
| 497 | 534 | abortController; | |
@@ -641,7 +678,7 @@ class Test extends AsyncResource { | |||
| 641 | 678 | this.plan = null; | |
| 642 | 679 | this.expectedAssertions = plan; | |
| 643 | 680 | this.cancelled = false; | |
| 644 | - this.expectFailure = expectFailure !== undefined && expectFailure !== false; | ||
| 681 | + this.expectFailure = parseExpectFailure(expectFailure) || this.parent?.expectFailure; | ||
| 645 | 682 | this.skipped = skip !== undefined && skip !== false; | |
| 646 | 683 | this.isTodo = (todo !== undefined && todo !== false) || this.parent?.isTodo; | |
| 647 | 684 | this.startTime = null; | |
@@ -955,7 +992,30 @@ class Test extends AsyncResource { | |||
| 955 | 992 | return; | |
| 956 | 993 | } | |
| 957 | 994 | ||
| 958 | - if (this.expectFailure === true) { | ||
| 995 | + if (this.expectFailure) { | ||
| 996 | + if (typeof this.expectFailure === 'object' && | ||
| 997 | + this.expectFailure.match !== undefined) { | ||
| 998 | + const { match: validation } = this.expectFailure; | ||
| 999 | + try { | ||
| 1000 | + const errorToCheck = ( | ||
| 1001 | + err?.code === 'ERR_TEST_FAILURE' && | ||
| 1002 | + err?.failureType === kTestCodeFailure && | ||
| 1003 | + err.cause | ||
| 1004 | + ) ? | ||
| 1005 | + err.cause : | ||
| 1006 | + err; | ||
| 1007 | + // eslint-disable-next-line no-restricted-syntax | ||
| 1008 | + assert.throws(() => { throw errorToCheck; }, validation); | ||
| 1009 | + } catch (e) { | ||
| 1010 | + this.passed = false; | ||
| 1011 | + this.error = new ERR_TEST_FAILURE( | ||
| 1012 | + 'The test failed, but the error did not match the expected validation', | ||
| 1013 | + kTestCodeFailure, | ||
| 1014 | + ); | ||
| 1015 | + this.error.cause = e; | ||
| 1016 | + return; | ||
| 1017 | + } | ||
| 1018 | + } | ||
| 959 | 1019 | this.passed = true; | |
| 960 | 1020 | } else { | |
| 961 | 1021 | this.passed = false; | |
@@ -965,7 +1025,7 @@ class Test extends AsyncResource { | |||
| 965 | 1025 | } | |
| 966 | 1026 | ||
| 967 | 1027 | pass() { | |
| 968 | - if (this.error == null && this.expectFailure === true && !this.skipped) { | ||
| 1028 | + if (this.error == null && this.expectFailure && !this.skipped) { | ||
| 969 | 1029 | this.passed = false; | |
| 970 | 1030 | this.error = new ERR_TEST_FAILURE( | |
| 971 | 1031 | 'test was expected to fail but passed', | |
@@ -977,6 +1037,20 @@ class Test extends AsyncResource { | |||
| 977 | 1037 | return; | |
| 978 | 1038 | } | |
| 979 | 1039 | ||
| 1040 | + if (this.skipped || this.isTodo) { | ||
| 1041 | + this.passed = true; | ||
| 1042 | + return; | ||
| 1043 | + } | ||
| 1044 | + | ||
| 1045 | + if (this.expectFailure) { | ||
| 1046 | + this.passed = false; | ||
| 1047 | + this.error = new ERR_TEST_FAILURE( | ||
| 1048 | + 'Test passed but was expected to fail', | ||
| 1049 | + kTestCodeFailure, | ||
| 1050 | + ); | ||
| 1051 | + return; | ||
| 1052 | + } | ||
| 1053 | + | ||
| 980 | 1054 | this.passed = true; | |
| 981 | 1055 | } | |
| 982 | 1056 | ||
@@ -1366,7 +1440,10 @@ class Test extends AsyncResource { | |||
| 1366 | 1440 | } else if (this.isTodo) { | |
| 1367 | 1441 | directive = this.reporter.getTodo(this.message); | |
| 1368 | 1442 | } else if (this.expectFailure) { | |
| 1369 | - directive = this.reporter.getXFail(this.expectFailure); // TODO(@JakobJingleheimer): support specifying failure | ||
| 1443 | + const message = typeof this.expectFailure === 'object' ? | ||
| 1444 | + this.expectFailure.label : | ||
| 1445 | + this.expectFailure; | ||
| 1446 | + directive = this.reporter.getXFail(message); | ||
| 1370 | 1447 | } | |
| 1371 | 1448 | ||
| 1372 | 1449 | if (this.reportedType) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,10 +8,9 @@ if (!process.env.NODE_TEST_CONTEXT) { | |||
| 8 | 8 | ||
| 9 | 9 | stream.on('test:pass', common.mustNotCall()); | |
| 10 | 10 | stream.on('test:fail', common.mustCall((event) => { | |
| 11 | - assert.strictEqual(event.expectFailure, true); | ||
| 12 | 11 | assert.strictEqual(event.details.error.code, 'ERR_TEST_FAILURE'); | |
| 13 | 12 | assert.strictEqual(event.details.error.failureType, 'expectedFailure'); | |
| 14 | - assert.strictEqual(event.details.error.cause, 'test was expected to fail but passed'); | ||
| 13 | + assert.strictEqual(event.details.error.message, 'test was expected to fail but passed'); | ||
| 15 | 14 | }, 1)); | |
| 16 | 15 | } else { | |
| 17 | 16 | test('passing test', { expectFailure: true }, () => {}); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments