Repository: unexpectedjs/unexpected (js)

Issue:
not to match diff: Highlight special chars and newlines in the matched part
``` js
var text = "foobar\n";
expect(text, 'not to match', /\s+/);
```

When the above assertion fails it would be nice to mark up the special characters in the part of the diff that highlights the match that shouldn't have been there. Right now just looks like:

```
expected 'foobar\n' not to match /\s+$/

foobar

```

... which isn't that easy to decipher.


Pull request description:
removedHighlight style: Render newlines as \n
Fixes #342

Affects:

* `<string> not to (contain|begin with|end with) <string>`
* `<string> not to match <regexp>`

Before (text mode):

```
expected 'barfo\noquuxfoobaz' not to match /fo\no/

barfo
   ^^
oquuxfoobaz
^

expected 'foobar\n' not to match /\s+/

foobar
```

After:

```
expected 'foobar\n' not to match /\s+/

foobar\n
      ^

expected 'barfo\noquuxfoobaz' not to match /fo\no/

barfo\n
   ^^^
oquuxfoobaz
^
```


Before (ansi mode):
![before1](https://user-images.githubusercontent.com/373545/50743558-ba43d200-1219-11e9-91ec-5b04c6fcf0e8.png)
![before2](https://user-images.githubusercontent.com/373545/50743561-bf088600-1219-11e9-9cb6-8813a9b1ac12.png)

After:
![after1](https://user-images.githubusercontent.com/373545/50743564-c7f95780-1219-11e9-85ee-08365c8c1a4a.png)
![after2](https://user-images.githubusercontent.com/373545/50743565-cb8cde80-1219-11e9-89ca-5bc7499f0337.png)


Patch:
diff --git a/lib/styles.js b/lib/styles.js
index 32efe916..d9f82a9d 100644
--- a/lib/styles.js
+++ b/lib/styles.js
@@ -206,19 +206,24 @@ module.exports = expect => {
     this.alt({
       text() {
         content.split(/(\n)/).forEach(function(fragment) {
+          this.block(function() {
+            this.text(fragment.replace(/\n/g, '\\n'))
+              .nl()
+              .text(fragment.replace(/[\s\S]/g, '^'));
+          });
           if (fragment === '\n') {
             this.nl();
-          } else {
-            this.block(function() {
-              this.text(fragment)
-                .nl()
-                .text(fragment.replace(/[\s\S]/g, '^'));
-            });
           }
         }, this);
       },
       fallback() {
-        this.diffRemovedHighlight(content);
+        content.split(/(\n)/).forEach(fragment => {
+          if (fragment === '\n') {
+            this.diffRemovedSpecialChar('\\n').nl();
+          } else {
+            this.diffRemovedHighlight(fragment);
+          }
+        });
       }
     });
   });


Test patch:
diff --git a/test/assertions/to-begin-with.spec.js b/test/assertions/to-begin-with.spec.js
index dce3adca..6c2b751c 100644
--- a/test/assertions/to-begin-with.spec.js
+++ b/test/assertions/to-begin-with.spec.js
@@ -158,10 +158,10 @@ describe('to begin with assertion', () => {
           'to throw',
           "expected 'f\\no\\nobarquuxfoo' not to begin with 'f\\no\\no'\n" +
             '\n' +
-            'f\n' +
-            '^\n' +
-            'o\n' +
-            '^\n' +
+            'f\\n\n' +
+            '^^\n' +
+            'o\\n\n' +
+            '^^\n' +
             'obarquuxfoo\n' +
             '^'
         );
diff --git a/test/assertions/to-contain.spec.js b/test/assertions/to-contain.spec.js
index e2d10002..3d5eda19 100644
--- a/test/assertions/to-contain.spec.js
+++ b/test/assertions/to-contain.spec.js
@@ -125,10 +125,10 @@ describe('to contain assertion', () => {
             'to have message',
             "expected 'blahfoo\\nbar\\nquux' not to contain 'foo\\nbar\\nq'\n" +
               '\n' +
-              'blahfoo\n' +
-              '    ^^^\n' +
-              'bar\n' +
-              '^^^\n' +
+              'blahfoo\\n\n' +
+              '    ^^^^\n' +
+              'bar\\n\n' +
+              '^^^^\n' +
               'quux\n' +
               '^'
           );
diff --git a/test/assertions/to-end-with.spec.js b/test/assertions/to-end-with.spec.js
index be6e56de..7863f3c6 100644
--- a/test/assertions/to-end-with.spec.js
+++ b/test/assertions/to-end-with.spec.js
@@ -157,10 +157,10 @@ describe('to end with assertion', () => {
         'to throw',
         "expected 'foobarquuxf\\no\\no' not to end with 'f\\no\\no'\n" +
           '\n' +
-          'foobarquuxf\n' +
-          '          ^\n' +
-          'o\n' +
-          '^\n' +
+          'foobarquuxf\\n\n' +
+          '          ^^\n' +
+          'o\\n\n' +
+          '^^\n' +
           'o\n' +
           '^'
       );
diff --git a/test/assertions/to-match.spec.js b/test/assertions/to-match.spec.js
index d467e552..28df755a 100644
--- a/test/assertions/to-match.spec.js
+++ b/test/assertions/to-match.spec.js
@@ -85,11 +85,25 @@ describe('to match assertion', () => {
         'to throw',
         "expected 'barfo\\noquuxfoobaz' not to match /fo\\no/\n" +
           '\n' +
-          'barfo\n' +
-          '   ^^\n' +
+          'barfo\\n\n' +
+          '   ^^^\n' +
           'oquuxfoobaz\n' +
           '^'
       );
     });
+
+    it('highlights a newline at the end of the match', function() {
+      expect(
+        function() {
+          expect('foobar\n', 'not to match', /\s+/);
+        },
+        'to throw',
+        "expected 'foobar\\n' not to match /\\s+/\n" +
+          '\n' +
+          'foobar\\n\n' +
+          '      ^\n' +
+          '\n'
+      );
+    });
   });
 });
diff --git a/test/styles/removedHighlight.spec.js b/test/styles/removedHighlight.spec.js
new file mode 100644
index 00000000..efe86525
--- /dev/null
+++ b/test/styles/removedHighlight.spec.js
@@ -0,0 +1,51 @@
+/*global expectWithUnexpectedMagicPen*/
+describe('removedHighlight', () => {
+  const expect = expectWithUnexpectedMagicPen;
+
+  var text = 'foo\nbar';
+
+  describe('in text mode', () => {
+    it('escapes the newlines', () => {
+      var pen = expect.createOutput('text').removedHighlight(text);
+      expect(pen.toString(), 'to equal', 'foo\\n\n^^^^\nbar\n^^^');
+
+      expect(
+        pen,
+        'to equal',
+        expect
+          .createOutput('text')
+          .block(function() {
+            this.text('foo')
+              .nl()
+              .text('^^^');
+          })
+          .block(function() {
+            this.diffRemovedHighlight('\\n')
+              .nl()
+              .text('^');
+          })
+          .nl()
+          .block(function() {
+            this.text('bar')
+              .nl()
+              .text('^^^');
+          })
+      );
+    });
+  });
+
+  describe('in ansi mode', () => {
+    it('does not output leading + and -', () => {
+      expect(
+        expect.createOutput('ansi').removedHighlight(text),
+        'to equal',
+        expect
+          .createOutput('ansi')
+          .diffRemovedHighlight('foo')
+          .diffRemovedSpecialChar('\\n')
+          .nl()
+          .diffRemovedHighlight('bar')
+      );
+    });
+  });
+});