Skip to content

Commit

Permalink
[New] t.match/`t.doesNotMatch: fail the test instead of throw on wr…
Browse files Browse the repository at this point in the history
…ong input types.

Turns out throwing is much less useful than failing the test.
  • Loading branch information
ljharb committed Dec 26, 2021
1 parent d3b4f46 commit a1c266b
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 76 deletions.
90 changes: 57 additions & 33 deletions lib/test.js
Expand Up @@ -14,6 +14,7 @@ var inspect = require('object-inspect');
var isEnumerable = callBound('Object.prototype.propertyIsEnumerable');
var toLowerCase = callBound('String.prototype.toLowerCase');
var $test = callBound('RegExp.prototype.test');
var objectToString = callBound('Object.prototype.toString');

module.exports = Test;

Expand Down Expand Up @@ -593,45 +594,68 @@ Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {

Test.prototype.match = function match(string, regexp, msg, extra) {
if (!isRegExp(regexp)) {
throw new TypeError('The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')');
}
if (typeof string !== 'string') {
throw new TypeError('The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')');
this._assert(false, {
message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
operator: 'match',
actual: objectToString(regexp),
expected: '[object RegExp]',
extra: extra
});
} else if (typeof string !== 'string') {
this._assert(false, {
message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
operator: 'match',
actual: string === null ? null : typeof string,
expected: 'string',
extra: extra
});
} else {
var matches = $test(regexp, string);
var message = defined(
msg,
'The input ' + (matches ? 'matched' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
);
this._assert(matches, {
message: message,
operator: 'match',
actual: string,
expected: regexp,
extra: extra
});
}

var matches = $test(regexp, string);
var message = defined(
msg,
'The input ' + (matches ? 'matched' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
);
this._assert(matches, {
message: message,
operator: 'match',
actual: string,
expected: regexp,
extra: extra
});
};

Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) {
if (!isRegExp(regexp)) {
throw new TypeError('The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')');
}
if (typeof string !== 'string') {
throw new TypeError('The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')');
this._assert(false, {
message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
operator: 'doesNotMatch',
actual: objectToString(regexp),
expected: '[object RegExp]',
extra: extra
});
} else if (typeof string !== 'string') {
this._assert(false, {
message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
operator: 'doesNotMatch',
actual: string === null ? null : typeof string,
expected: 'string',
extra: extra
});
} else {
var matches = $test(regexp, string);
var message = defined(
msg,
'The input ' + (matches ? 'was expected to not match' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
);
this._assert(!matches, {
message: message,
operator: 'doesNotMatch',
actual: string,
expected: regexp,
extra: extra
});
}
var matches = $test(regexp, string);
var message = defined(
msg,
'The input ' + (matches ? 'was expected to not match' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
);
this._assert(!matches, {
message: message,
operator: 'doesNotMatch',
actual: string,
expected: regexp,
extra: extra
});
};

// eslint-disable-next-line no-unused-vars
Expand Down
4 changes: 2 additions & 2 deletions readme.markdown
Expand Up @@ -331,11 +331,11 @@ Multiline output will be split by `\n` characters, and each one printed as a com

## t.match(string, regexp, message)

Assert that `string` matches the RegExp `regexp`. Will throw (not just fail) when the first two arguments are the wrong type.
Assert that `string` matches the RegExp `regexp`. Will fail when the first two arguments are the wrong type.

## t.doesNotMatch(string, regexp, message)

Assert that `string` does not match the RegExp `regexp`. Will throw (not just fail) when the first two arguments are the wrong type.
Assert that `string` does not match the RegExp `regexp`. Will fail when the first two arguments are the wrong type.

## var htest = test.createHarness()

Expand Down
167 changes: 126 additions & 41 deletions test/match.js
Expand Up @@ -14,9 +14,55 @@ tap.test('match', function (tt) {
tt.same(stripFullStack(rows.toString('utf8')), [
'TAP version 13',
'# match',
'ok 1 regex arg must be a regex',
'ok 2 string arg must be a string',
'not ok 3 The input did not match the regular expression /abc/. Input: \'string\'',
'not ok 1 The "regexp" argument must be an instance of RegExp. Received type string (\'string\')',
' ---',
' operator: match',
' expected: \'[object RegExp]\'',
' actual: \'[object String]\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: The "regexp" argument must be an instance of RegExp. Received type string (\'string\')',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 2 regex arg must not be a string',
' ---',
' operator: match',
' expected: \'[object RegExp]\'',
' actual: \'[object String]\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: regex arg must not be a string',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 3 The "string" argument must be of type string. Received type object ({ abc: 123 })',
' ---',
' operator: match',
' expected: \'string\'',
' actual: \'object\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: The "string" argument must be of type string. Received type object ({ abc: 123 })',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 4 string arg must not be an object',
' ---',
' operator: match',
' expected: \'string\'',
' actual: \'object\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: string arg must not be an object',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 5 The input did not match the regular expression /abc/. Input: \'string\'',
' ---',
' operator: match',
' expected: /abc/',
Expand All @@ -28,7 +74,7 @@ tap.test('match', function (tt) {
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 4 "string" does not match /abc/',
'not ok 6 "string" does not match /abc/',
' ---',
' operator: match',
' expected: /abc/',
Expand All @@ -40,33 +86,27 @@ tap.test('match', function (tt) {
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'ok 5 The input matched the regular expression /pass$/. Input: \'I will pass\'',
'ok 6 "I will pass" matches /pass$/',
'ok 7 The input matched the regular expression /pass$/. Input: \'I will pass\'',
'ok 8 "I will pass" matches /pass$/',
'',
'1..6',
'# tests 6',
'# pass 4',
'# fail 2',
'1..8',
'# tests 8',
'# pass 2',
'# fail 6',
''
]);
};

test.createStream().pipe(concat(tc));

test('match', function (t) {
t.plan(6);
t.plan(8);

t['throws'](
function () { t.match(/abc/, 'string'); },
TypeError,
'regex arg must be a regex'
);
t.match(/abc/, 'string');
t.match(/abc/, 'string', 'regex arg must not be a string');

t['throws'](
function () { t.match({ abc: 123 }, /abc/); },
TypeError,
'string arg must be a string'
);
t.match({ abc: 123 }, /abc/);
t.match({ abc: 123 }, /abc/, 'string arg must not be an object');

t.match('string', /abc/);
t.match('string', /abc/, '"string" does not match /abc/');
Expand All @@ -86,9 +126,55 @@ tap.test('doesNotMatch', function (tt) {
tt.same(stripFullStack(rows.toString('utf8')), [
'TAP version 13',
'# doesNotMatch',
'ok 1 regex arg must be a regex',
'ok 2 string arg must be a string',
'not ok 3 The input was expected to not match the regular expression /string/. Input: \'string\'',
'not ok 1 The "regexp" argument must be an instance of RegExp. Received type string (\'string\')',
' ---',
' operator: doesNotMatch',
' expected: \'[object RegExp]\'',
' actual: \'[object String]\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: The "regexp" argument must be an instance of RegExp. Received type string (\'string\')',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 2 regex arg must not be a string',
' ---',
' operator: doesNotMatch',
' expected: \'[object RegExp]\'',
' actual: \'[object String]\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: regex arg must not be a string',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 3 The "string" argument must be of type string. Received type object ({ abc: 123 })',
' ---',
' operator: doesNotMatch',
' expected: \'string\'',
' actual: \'object\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: The "string" argument must be of type string. Received type object ({ abc: 123 })',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 4 string arg must not be an object',
' ---',
' operator: doesNotMatch',
' expected: \'string\'',
' actual: \'object\'',
' at: Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' stack: |-',
' Error: string arg must not be an object',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 5 The input was expected to not match the regular expression /string/. Input: \'string\'',
' ---',
' operator: doesNotMatch',
' expected: /string/',
Expand All @@ -100,7 +186,7 @@ tap.test('doesNotMatch', function (tt) {
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 4 "string" should not match /string/',
'not ok 6 "string" should not match /string/',
' ---',
' operator: doesNotMatch',
' expected: /string/',
Expand All @@ -112,7 +198,7 @@ tap.test('doesNotMatch', function (tt) {
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 5 The input was expected to not match the regular expression /pass$/. Input: \'I will pass\'',
'not ok 7 The input was expected to not match the regular expression /pass$/. Input: \'I will pass\'',
' ---',
' operator: doesNotMatch',
' expected: /pass$/',
Expand All @@ -124,7 +210,7 @@ tap.test('doesNotMatch', function (tt) {
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'not ok 6 "I will pass" should not match /pass$/',
'not ok 8 "I will pass" should not match /pass$/',
' ---',
' operator: doesNotMatch',
' expected: /pass$/',
Expand All @@ -136,38 +222,37 @@ tap.test('doesNotMatch', function (tt) {
' at Test.<anonymous> ($TEST/match.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'ok 9 The input did not match the regular expression /pass$/. Input: \'I will fail\'',
'ok 10 "I will fail" does not match /pass$/',
'',
'1..6',
'# tests 6',
'1..10',
'# tests 10',
'# pass 2',
'# fail 4',
'# fail 8',
''
]);
};

test.createStream().pipe(concat(tc));

test('doesNotMatch', function (t) {
t.plan(6);
t.plan(10);

t['throws'](
function () { t.doesNotMatch(/abc/, 'string'); },
TypeError,
'regex arg must be a regex'
);
t.doesNotMatch(/abc/, 'string');
t.doesNotMatch(/abc/, 'string', 'regex arg must not be a string');

t['throws'](
function () { t.doesNotMatch({ abc: 123 }, /abc/); },
TypeError,
'string arg must be a string'
);
t.doesNotMatch({ abc: 123 }, /abc/);
t.doesNotMatch({ abc: 123 }, /abc/, 'string arg must not be an object');

t.doesNotMatch('string', /string/);
t.doesNotMatch('string', /string/, '"string" should not match /string/');

t.doesNotMatch('I will pass', /pass$/);
t.doesNotMatch('I will pass', /pass$/, '"I will pass" should not match /pass$/');

t.doesNotMatch('I will fail', /pass$/);
t.doesNotMatch('I will fail', /pass$/, '"I will fail" does not match /pass$/');

t.end();
});
});

0 comments on commit a1c266b

Please sign in to comment.