Skip to content

Commit 67ec97a

Browse files
BridgeARMylesBorins
authored andcommitted
assert: implement assert.match() and assert.doesNotMatch()
This adds a new functionality to the assertion module: a dedicated check for regular expressions. So far it's possible to use `assert.ok(regexp.test(string))`. This is not ideal though when it comes to the error message, since it's not possible to know how either of the input values look like. It's just known that the assertion failed. This allows to pass through the regular expression and the input string. The string is then matched against the regular expression and reports a expressive error message in case of a failure. Backport-PR-URL: #31431 PR-URL: #30929 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent 5c6a6b8 commit 67ec97a

File tree

3 files changed

+220
-4
lines changed

3 files changed

+220
-4
lines changed

doc/api/assert.md

+72
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,42 @@ parameter is undefined, a default error message is assigned. If the `message`
425425
parameter is an instance of an [`Error`][] then it will be thrown instead of the
426426
`AssertionError`.
427427

428+
## `assert.doesNotMatch(string, regexp[, message])`
429+
<!-- YAML
430+
added: REPLACEME
431+
-->
432+
433+
* `string` {string}
434+
* `regexp` {RegExp}
435+
* `message` {string|Error}
436+
437+
> Stability: 1 - Experimental
438+
439+
Expects the `string` input not to match the regular expression.
440+
441+
This feature is currently experimental and the name might change or it might be
442+
completely removed again.
443+
444+
```js
445+
const assert = require('assert').strict;
446+
447+
assert.doesNotMatch('I will fail', /fail/);
448+
// AssertionError [ERR_ASSERTION]: The input was expected to not match the ...
449+
450+
assert.doesNotMatch(123, /pass/);
451+
// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
452+
453+
assert.doesNotMatch('I will pass', /different/);
454+
// OK
455+
```
456+
457+
If the values do match, or if the `string` argument is of another type than
458+
`string`, an [`AssertionError`][] is thrown with a `message` property set equal
459+
to the value of the `message` parameter. If the `message` parameter is
460+
undefined, a default error message is assigned. If the `message` parameter is an
461+
instance of an [`Error`][] then it will be thrown instead of the
462+
[`AssertionError`][].
463+
428464
## `assert.doesNotReject(asyncFn[, error][, message])`
429465
<!-- YAML
430466
added: v10.0.0
@@ -728,6 +764,42 @@ let err;
728764
// at errorFrame
729765
```
730766

767+
## `assert.match(string, regexp[, message])`
768+
<!-- YAML
769+
added: REPLACEME
770+
-->
771+
772+
* `string` {string}
773+
* `regexp` {RegExp}
774+
* `message` {string|Error}
775+
776+
> Stability: 1 - Experimental
777+
778+
Expects the `string` input to match the regular expression.
779+
780+
This feature is currently experimental and the name might change or it might be
781+
completely removed again.
782+
783+
```js
784+
const assert = require('assert').strict;
785+
786+
assert.match('I will fail', /pass/);
787+
// AssertionError [ERR_ASSERTION]: The input did not match the regular ...
788+
789+
assert.match(123, /pass/);
790+
// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
791+
792+
assert.match('I will pass', /pass/);
793+
// OK
794+
```
795+
796+
If the values do not match, or if the `string` argument is of another type than
797+
`string`, an [`AssertionError`][] is thrown with a `message` property set equal
798+
to the value of the `message` parameter. If the `message` parameter is
799+
undefined, a default error message is assigned. If the `message` parameter is an
800+
instance of an [`Error`][] then it will be thrown instead of the
801+
[`AssertionError`][].
802+
731803
## `assert.notDeepEqual(actual, expected[, message])`
732804
<!-- YAML
733805
added: v0.1.21

lib/assert.js

+48-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const {
2727
ObjectKeys,
2828
ObjectPrototypeIsPrototypeOf,
2929
Map,
30+
RegExpPrototypeTest,
3031
} = primordials;
3132

3233
const { Buffer } = require('buffer');
@@ -533,7 +534,7 @@ class Comparison {
533534
if (actual !== undefined &&
534535
typeof actual[key] === 'string' &&
535536
isRegExp(obj[key]) &&
536-
obj[key].test(actual[key])) {
537+
RegExpPrototypeTest(obj[key], actual[key])) {
537538
this[key] = actual[key];
538539
} else {
539540
this[key] = obj[key];
@@ -579,7 +580,7 @@ function expectedException(actual, expected, message, fn) {
579580
// Handle regular expressions.
580581
if (isRegExp(expected)) {
581582
const str = String(actual);
582-
if (expected.test(str))
583+
if (RegExpPrototypeTest(expected, str))
583584
return;
584585

585586
if (!message) {
@@ -614,7 +615,7 @@ function expectedException(actual, expected, message, fn) {
614615
for (const key of keys) {
615616
if (typeof actual[key] === 'string' &&
616617
isRegExp(expected[key]) &&
617-
expected[key].test(actual[key])) {
618+
RegExpPrototypeTest(expected[key], actual[key])) {
618619
continue;
619620
}
620621
compareExceptionKey(actual, expected, key, message, keys, fn);
@@ -751,7 +752,7 @@ function hasMatchingError(actual, expected) {
751752
if (typeof expected !== 'function') {
752753
if (isRegExp(expected)) {
753754
const str = String(actual);
754-
return expected.test(str);
755+
return RegExpPrototypeTest(expected, str);
755756
}
756757
throw new ERR_INVALID_ARG_TYPE(
757758
'expected', ['Function', 'RegExp'], expected
@@ -856,6 +857,49 @@ assert.ifError = function ifError(err) {
856857
}
857858
};
858859

860+
function internalMatch(string, regexp, message, fn) {
861+
if (!isRegExp(regexp)) {
862+
throw new ERR_INVALID_ARG_TYPE(
863+
'regexp', 'RegExp', regexp
864+
);
865+
}
866+
const match = fn.name === 'match';
867+
if (typeof string !== 'string' ||
868+
RegExpPrototypeTest(regexp, string) !== match) {
869+
if (message instanceof Error) {
870+
throw message;
871+
}
872+
873+
const generatedMessage = !message;
874+
875+
// 'The input was expected to not match the regular expression ' +
876+
message = message || (typeof string !== 'string' ?
877+
'The "string" argument must be of type string. Received type ' +
878+
`${typeof string} (${inspect(string)})` :
879+
(match ?
880+
'The input did not match the regular expression ' :
881+
'The input was expected to not match the regular expression ') +
882+
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
883+
const err = new AssertionError({
884+
actual: string,
885+
expected: regexp,
886+
message,
887+
operator: fn.name,
888+
stackStartFn: fn
889+
});
890+
err.generatedMessage = generatedMessage;
891+
throw err;
892+
}
893+
}
894+
895+
assert.match = function match(string, regexp, message) {
896+
internalMatch(string, regexp, message, match);
897+
};
898+
899+
assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
900+
internalMatch(string, regexp, message, doesNotMatch);
901+
};
902+
859903
// Expose a strict only variant of assert
860904
function strict(...args) {
861905
innerOk(strict, args.length, ...args);

test/parallel/test-assert.js

+100
Original file line numberDiff line numberDiff line change
@@ -1301,3 +1301,103 @@ assert.throws(
13011301
assert(!err2.stack.includes('hidden'));
13021302
})();
13031303
}
1304+
1305+
// Multiple assert.match() tests.
1306+
{
1307+
assert.throws(
1308+
() => assert.match(/abc/, 'string'),
1309+
{
1310+
code: 'ERR_INVALID_ARG_TYPE',
1311+
message: 'The "regexp" argument must be an instance of RegExp. ' +
1312+
"Received type string ('string')"
1313+
}
1314+
);
1315+
assert.throws(
1316+
() => assert.match('string', /abc/),
1317+
{
1318+
actual: 'string',
1319+
expected: /abc/,
1320+
operator: 'match',
1321+
message: 'The input did not match the regular expression /abc/. ' +
1322+
"Input:\n\n'string'\n",
1323+
generatedMessage: true
1324+
}
1325+
);
1326+
assert.throws(
1327+
() => assert.match('string', /abc/, 'foobar'),
1328+
{
1329+
actual: 'string',
1330+
expected: /abc/,
1331+
operator: 'match',
1332+
message: 'foobar',
1333+
generatedMessage: false
1334+
}
1335+
);
1336+
const errorMessage = new RangeError('foobar');
1337+
assert.throws(
1338+
() => assert.match('string', /abc/, errorMessage),
1339+
errorMessage
1340+
);
1341+
assert.throws(
1342+
() => assert.match({ abc: 123 }, /abc/),
1343+
{
1344+
actual: { abc: 123 },
1345+
expected: /abc/,
1346+
operator: 'match',
1347+
message: 'The "string" argument must be of type string. ' +
1348+
'Received type object ({ abc: 123 })',
1349+
generatedMessage: true
1350+
}
1351+
);
1352+
assert.match('I will pass', /pass$/);
1353+
}
1354+
1355+
// Multiple assert.doesNotMatch() tests.
1356+
{
1357+
assert.throws(
1358+
() => assert.doesNotMatch(/abc/, 'string'),
1359+
{
1360+
code: 'ERR_INVALID_ARG_TYPE',
1361+
message: 'The "regexp" argument must be an instance of RegExp. ' +
1362+
"Received type string ('string')"
1363+
}
1364+
);
1365+
assert.throws(
1366+
() => assert.doesNotMatch('string', /string/),
1367+
{
1368+
actual: 'string',
1369+
expected: /string/,
1370+
operator: 'doesNotMatch',
1371+
message: 'The input was expected to not match the regular expression ' +
1372+
"/string/. Input:\n\n'string'\n",
1373+
generatedMessage: true
1374+
}
1375+
);
1376+
assert.throws(
1377+
() => assert.doesNotMatch('string', /string/, 'foobar'),
1378+
{
1379+
actual: 'string',
1380+
expected: /string/,
1381+
operator: 'doesNotMatch',
1382+
message: 'foobar',
1383+
generatedMessage: false
1384+
}
1385+
);
1386+
const errorMessage = new RangeError('foobar');
1387+
assert.throws(
1388+
() => assert.doesNotMatch('string', /string/, errorMessage),
1389+
errorMessage
1390+
);
1391+
assert.throws(
1392+
() => assert.doesNotMatch({ abc: 123 }, /abc/),
1393+
{
1394+
actual: { abc: 123 },
1395+
expected: /abc/,
1396+
operator: 'doesNotMatch',
1397+
message: 'The "string" argument must be of type string. ' +
1398+
'Received type object ({ abc: 123 })',
1399+
generatedMessage: true
1400+
}
1401+
);
1402+
assert.doesNotMatch('I will pass', /different$/);
1403+
}

0 commit comments

Comments
 (0)