Skip to content

Commit e33f773

Browse files
committed
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. PR-URL: #30929 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent a1d307f commit e33f773

File tree

3 files changed

+221
-5
lines changed

3 files changed

+221
-5
lines changed

doc/api/assert.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,42 @@ parameter is undefined, a default error message is assigned. If the `message`
430430
parameter is an instance of an [`Error`][] then it will be thrown instead of the
431431
`AssertionError`.
432432

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

780+
## `assert.match(string, regexp[, message])`
781+
<!-- YAML
782+
added: REPLACEME
783+
-->
784+
785+
* `string` {string}
786+
* `regexp` {RegExp}
787+
* `message` {string|Error}
788+
789+
> Stability: 1 - Experimental
790+
791+
Expects the `string` input to match the regular expression.
792+
793+
This feature is currently experimental and the name might change or it might be
794+
completely removed again.
795+
796+
```js
797+
const assert = require('assert').strict;
798+
799+
assert.match('I will fail', /pass/);
800+
// AssertionError [ERR_ASSERTION]: The input did not match the regular ...
801+
802+
assert.match(123, /pass/);
803+
// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
804+
805+
assert.match('I will pass', /pass/);
806+
// OK
807+
```
808+
809+
If the values do not match, or if the `string` argument is of another type than
810+
`string`, an [`AssertionError`][] is thrown with a `message` property set equal
811+
to the value of the `message` parameter. If the `message` parameter is
812+
undefined, a default error message is assigned. If the `message` parameter is an
813+
instance of an [`Error`][] then it will be thrown instead of the
814+
[`AssertionError`][].
815+
744816
## `assert.notDeepEqual(actual, expected[, message])`
745817
<!-- YAML
746818
added: v0.1.21

lib/assert.js

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const {
2525
ObjectIs,
2626
ObjectKeys,
2727
ObjectPrototypeIsPrototypeOf,
28-
NumberIsNaN
28+
NumberIsNaN,
29+
RegExpPrototypeTest,
2930
} = primordials;
3031

3132
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);
@@ -780,7 +781,7 @@ function hasMatchingError(actual, expected) {
780781
if (typeof expected !== 'function') {
781782
if (isRegExp(expected)) {
782783
const str = String(actual);
783-
return expected.test(str);
784+
return RegExpPrototypeTest(expected, str);
784785
}
785786
throw new ERR_INVALID_ARG_TYPE(
786787
'expected', ['Function', 'RegExp'], expected
@@ -885,6 +886,49 @@ assert.ifError = function ifError(err) {
885886
}
886887
};
887888

889+
function internalMatch(string, regexp, message, fn) {
890+
if (!isRegExp(regexp)) {
891+
throw new ERR_INVALID_ARG_TYPE(
892+
'regexp', 'RegExp', regexp
893+
);
894+
}
895+
const match = fn.name === 'match';
896+
if (typeof string !== 'string' ||
897+
RegExpPrototypeTest(regexp, string) !== match) {
898+
if (message instanceof Error) {
899+
throw message;
900+
}
901+
902+
const generatedMessage = !message;
903+
904+
// 'The input was expected to not match the regular expression ' +
905+
message = message || (typeof string !== 'string' ?
906+
'The "string" argument must be of type string. Received type ' +
907+
`${typeof string} (${inspect(string)})` :
908+
(match ?
909+
'The input did not match the regular expression ' :
910+
'The input was expected to not match the regular expression ') +
911+
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
912+
const err = new AssertionError({
913+
actual: string,
914+
expected: regexp,
915+
message,
916+
operator: fn.name,
917+
stackStartFn: fn
918+
});
919+
err.generatedMessage = generatedMessage;
920+
throw err;
921+
}
922+
}
923+
924+
assert.match = function match(string, regexp, message) {
925+
internalMatch(string, regexp, message, match);
926+
};
927+
928+
assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
929+
internalMatch(string, regexp, message, doesNotMatch);
930+
};
931+
888932
// Expose a strict only variant of assert
889933
function strict(...args) {
890934
innerOk(strict, args.length, ...args);

test/parallel/test-assert.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,3 +1367,103 @@ assert.throws(
13671367
'prototype.\n\nError message:\n\nfoobar'
13681368
}
13691369
);
1370+
1371+
// Multiple assert.match() tests.
1372+
{
1373+
assert.throws(
1374+
() => assert.match(/abc/, 'string'),
1375+
{
1376+
code: 'ERR_INVALID_ARG_TYPE',
1377+
message: 'The "regexp" argument must be an instance of RegExp. ' +
1378+
"Received type string ('string')"
1379+
}
1380+
);
1381+
assert.throws(
1382+
() => assert.match('string', /abc/),
1383+
{
1384+
actual: 'string',
1385+
expected: /abc/,
1386+
operator: 'match',
1387+
message: 'The input did not match the regular expression /abc/. ' +
1388+
"Input:\n\n'string'\n",
1389+
generatedMessage: true
1390+
}
1391+
);
1392+
assert.throws(
1393+
() => assert.match('string', /abc/, 'foobar'),
1394+
{
1395+
actual: 'string',
1396+
expected: /abc/,
1397+
operator: 'match',
1398+
message: 'foobar',
1399+
generatedMessage: false
1400+
}
1401+
);
1402+
const errorMessage = new RangeError('foobar');
1403+
assert.throws(
1404+
() => assert.match('string', /abc/, errorMessage),
1405+
errorMessage
1406+
);
1407+
assert.throws(
1408+
() => assert.match({ abc: 123 }, /abc/),
1409+
{
1410+
actual: { abc: 123 },
1411+
expected: /abc/,
1412+
operator: 'match',
1413+
message: 'The "string" argument must be of type string. ' +
1414+
'Received type object ({ abc: 123 })',
1415+
generatedMessage: true
1416+
}
1417+
);
1418+
assert.match('I will pass', /pass$/);
1419+
}
1420+
1421+
// Multiple assert.doesNotMatch() tests.
1422+
{
1423+
assert.throws(
1424+
() => assert.doesNotMatch(/abc/, 'string'),
1425+
{
1426+
code: 'ERR_INVALID_ARG_TYPE',
1427+
message: 'The "regexp" argument must be an instance of RegExp. ' +
1428+
"Received type string ('string')"
1429+
}
1430+
);
1431+
assert.throws(
1432+
() => assert.doesNotMatch('string', /string/),
1433+
{
1434+
actual: 'string',
1435+
expected: /string/,
1436+
operator: 'doesNotMatch',
1437+
message: 'The input was expected to not match the regular expression ' +
1438+
"/string/. Input:\n\n'string'\n",
1439+
generatedMessage: true
1440+
}
1441+
);
1442+
assert.throws(
1443+
() => assert.doesNotMatch('string', /string/, 'foobar'),
1444+
{
1445+
actual: 'string',
1446+
expected: /string/,
1447+
operator: 'doesNotMatch',
1448+
message: 'foobar',
1449+
generatedMessage: false
1450+
}
1451+
);
1452+
const errorMessage = new RangeError('foobar');
1453+
assert.throws(
1454+
() => assert.doesNotMatch('string', /string/, errorMessage),
1455+
errorMessage
1456+
);
1457+
assert.throws(
1458+
() => assert.doesNotMatch({ abc: 123 }, /abc/),
1459+
{
1460+
actual: { abc: 123 },
1461+
expected: /abc/,
1462+
operator: 'doesNotMatch',
1463+
message: 'The "string" argument must be of type string. ' +
1464+
'Received type object ({ abc: 123 })',
1465+
generatedMessage: true
1466+
}
1467+
);
1468+
assert.doesNotMatch('I will pass', /different$/);
1469+
}

0 commit comments

Comments
 (0)