Repository: unexpectedjs/unexpected
Pull request #396 (2017-05-07 09:50:51)

Don't allow a compound assertion where (a prefix of) the last half is not an existing assertion
Addresses half of #394

An even better test would be whether there remainder of the string can be "fully covered" by assertions that exist. Implementing that isn't hard, but I think it would have a bad worst case perf -- so I think we should start here.


Patch:
diff --git a/lib/Unexpected.js b/lib/Unexpected.js
index c63ab01f..9f83b189 100644
--- a/lib/Unexpected.js
+++ b/lib/Unexpected.js
@@ -1126,15 +1126,22 @@ Unexpected.prototype._expect = function expect(subject, testDescriptionString) {
 
         if (!assertionRule) {
             var tokens = testDescriptionString.split(' ');
-            for (var n = tokens.length - 1; n > 0 ; n -= 1) {
+            OUTER: for (var n = tokens.length - 1; n > 0 ; n -= 1) {
                 var prefix = tokens.slice(0, n).join(' ');
-                var argsWithAssertionPrepended = [ tokens.slice(n).join(' ') ].concat(args);
+                var remainingTokens = tokens.slice(n);
+                var argsWithAssertionPrepended = [ remainingTokens.join(' ') ].concat(args);
                 assertionRule = that.lookupAssertionRule(subject, prefix, argsWithAssertionPrepended, true);
                 if (assertionRule) {
-                    // Great, found the longest prefix of the string that yielded a suitable assertion for the given subject and args
-                    testDescriptionString = prefix;
-                    args = argsWithAssertionPrepended;
-                    break;
+                    // Found the longest prefix of the string that yielded a suitable assertion for the given subject and args
+                    // To avoid bogus error messages when shifting later (#394) we require some prefix of the remaining tokens
+                    // to be a valid assertion name:
+                    for (var i = 1 ; i < remainingTokens.length ; i += 1) {
+                        if (that.assertions.hasOwnProperty(remainingTokens.slice(0, i + 1).join(' '))) {
+                            testDescriptionString = prefix;
+                            args = argsWithAssertionPrepended;
+                            break OUTER;
+                        }
+                    }
                 }
             }
             if (!assertionRule) {


Test patch:
diff --git a/test/unexpected.spec.js b/test/unexpected.spec.js
index c53bc49a..22aee630 100644
--- a/test/unexpected.spec.js
+++ b/test/unexpected.spec.js
@@ -863,6 +863,20 @@ describe('unexpected', function () {
                 );
             });
         });
+
+        // https://github.com/unexpectedjs/unexpected/issues/394
+        it('should produce a meaningful error message when the last half of a compound assertion is not a valid assertion name', function () {
+            expect(function () {
+                expect(function () {}, 'when called with', 'M1', 'to throw a', SyntaxError);
+            }, 'to throw',
+                "expected function () {}\n" +
+                "when called with 'M1', 'to throw a', function SyntaxError() { /* native code */ }\n" +
+                "  The assertion does not have a matching signature for:\n" +
+                "    <function> when called with <string> <string> <function>\n" +
+                "  did you mean:\n" +
+                "    <function> [when] called with <array-like> <assertion?>"
+            );
+        });
     });
 
     describe('#output', function () {