Remove the outermost folded frame in terse mode. (#21)

We always removed this frame if it was a core library frame,
which (until recent SDK releases) it always was. Now that the
boilerplate core library frames have been removed, we should
remove *any* folded frame.

This partially addresses dart-lang/test#555
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53cdd37..db53c73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 ## 1.7.2
+
+* `Trace.foldFrames()` and `Chain.foldFrames()` now remove the outermost folded
+  frame. This matches the behavior of `.terse` with core frames.
+
 * Fix bug parsing a friendly frame with spaces in the member name.
+
 * Fix bug parsing a friendly frame where the location is a data url.
 
 ## 1.7.1
diff --git a/lib/src/trace.dart b/lib/src/trace.dart
index eb8ecd8..9edee6e 100644
--- a/lib/src/trace.dart
+++ b/lib/src/trace.dart
@@ -285,7 +285,10 @@
         var library = frame.library.replaceAll(_terseRegExp, '');
         return new Frame(Uri.parse(library), null, null, frame.member);
       }).toList();
-      if (newFrames.length > 1 && newFrames.first.isCore) newFrames.removeAt(0);
+
+      if (newFrames.length > 1 && predicate(newFrames.first)) {
+        newFrames.removeAt(0);
+      }
     }
 
     return new Trace(newFrames.reversed);
diff --git a/test/chain/chain_test.dart b/test/chain/chain_test.dart
index 301f338..60137c8 100644
--- a/test/chain/chain_test.dart
+++ b/test/chain/chain_test.dart
@@ -269,7 +269,6 @@
       expect(folded.toString(), equals(
           'dart:async    Zip.zap\n'
           'b.dart 10:11  Bang.qux\n'
-          'a.dart        Zop.zoop\n'
           '===== asynchronous gap ===========================\n'
           'a.dart        Zip.zap\n'
           'b.dart 10:11  Zop.zoop\n'));
@@ -325,4 +324,4 @@
         '$userSlashCode 10:11  Foo.bar\n'
         'dart:core 10:11       Bar.baz\n'));
   });
-}
\ No newline at end of file
+}
diff --git a/test/trace_test.dart b/test/trace_test.dart
index 327eff0..1297932 100644
--- a/test/trace_test.dart
+++ b/test/trace_test.dart
@@ -293,8 +293,9 @@
   });
 
   group("folding", () {
-    test('.terse folds core frames together bottom-up', () {
-      var trace = new Trace.parse('''
+    group(".terse", () {
+      test('folds core frames together bottom-up', () {
+        var trace = new Trace.parse('''
 #1 top (dart:async/future.dart:0:2)
 #2 bottom (dart:core/uri.dart:1:100)
 #0 notCore (foo.dart:42:21)
@@ -303,30 +304,30 @@
 #5 alsoNotCore (bar.dart:10:20)
 ''');
 
-      expect(trace.terse.toString(), equals('''
+        expect(trace.terse.toString(), equals('''
 dart:core       bottom
 foo.dart 42:21  notCore
 dart:async      bottom
 bar.dart 10:20  alsoNotCore
 '''));
-    });
+      });
 
-    test('.terse folds empty async frames', () {
-      var trace = new Trace.parse('''
+      test('folds empty async frames', () {
+        var trace = new Trace.parse('''
 #0 top (dart:async/future.dart:0:2)
 #1 empty.<<anonymous closure>_async_body> (bar.dart)
 #2 bottom (dart:async-patch/future.dart:9:11)
 #3 notCore (foo.dart:42:21)
 ''');
 
-      expect(trace.terse.toString(), equals('''
+        expect(trace.terse.toString(), equals('''
 dart:async      bottom
 foo.dart 42:21  notCore
 '''));
-    });
+      });
 
-    test('.terse removes the bottom-most async frame', () {
-      var trace = new Trace.parse('''
+      test('removes the bottom-most async frame', () {
+        var trace = new Trace.parse('''
 #0 notCore (foo.dart:42:21)
 #1 top (dart:async/future.dart:0:2)
 #2 bottom (dart:core/uri.dart:1:100)
@@ -337,25 +338,27 @@
       expect(trace.terse.toString(), equals('''
 foo.dart 42:21  notCore
 '''));
-    });
+      });
 
-    test(".terse won't make a trace empty", () {
-      var trace = new Trace.parse('''
+      test("won't make a trace empty", () {
+        var trace = new Trace.parse('''
 #1 top (dart:async/future.dart:0:2)
 #2 bottom (dart:core/uri.dart:1:100)
 ''');
 
-      expect(trace.terse.toString(), equals('''
+        expect(trace.terse.toString(), equals('''
 dart:core  bottom
 '''));
+      });
+
+      test("won't panic on an empty trace", () {
+        expect(new Trace.parse("").terse.toString(), equals(""));
+      });
     });
 
-    test(".terse won't panic on an empty trace", () {
-      expect(new Trace.parse("").terse.toString(), equals(""));
-    });
-
-    test('.foldFrames folds frames together bottom-up', () {
-      var trace = new Trace.parse('''
+    group(".foldFrames", () {
+      test('folds frames together bottom-up', () {
+        var trace = new Trace.parse('''
 #0 notFoo (foo.dart:42:21)
 #1 fooTop (bar.dart:0:2)
 #2 fooBottom (foo.dart:1:100)
@@ -364,17 +367,32 @@
 #5 fooBottom (dart:async-patch/future.dart:9:11)
 ''');
 
-      var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'));
-      expect(folded.toString(), equals('''
+        var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'));
+        expect(folded.toString(), equals('''
 foo.dart 42:21                     notFoo
 foo.dart 1:100                     fooBottom
 bar.dart 10:20                     alsoNotFoo
 dart:async-patch/future.dart 9:11  fooBottom
 '''));
-    });
+      });
 
-    test('.foldFrames with terse: true folds core frames as well', () {
-      var trace = new Trace.parse('''
+      test('will never fold unparsed frames', () {
+        var trace = new Trace.parse(r'''
+.g"cs$#:b";a#>sw{*{ul$"$xqwr`p
+%+j-?uppx<([j@#nu{{>*+$%x-={`{
+!e($b{nj)zs?cgr%!;bmw.+$j+pfj~
+''');
+
+        expect(trace.foldFrames((frame) => true).toString(), equals(r'''
+.g"cs$#:b";a#>sw{*{ul$"$xqwr`p
+%+j-?uppx<([j@#nu{{>*+$%x-={`{
+!e($b{nj)zs?cgr%!;bmw.+$j+pfj~
+'''));
+      });
+
+      group("with terse: true", () {
+        test('folds core frames as well', () {
+          var trace = new Trace.parse('''
 #0 notFoo (foo.dart:42:21)
 #1 fooTop (bar.dart:0:2)
 #2 coreBottom (dart:async/future.dart:0:2)
@@ -383,47 +401,52 @@
 #5 coreBottom (dart:async-patch/future.dart:9:11)
 ''');
 
-      var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'),
-          terse: true);
-      expect(folded.toString(), equals('''
+          var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'),
+              terse: true);
+          expect(folded.toString(), equals('''
 foo.dart 42:21  notFoo
 dart:async      coreBottom
 bar.dart 10:20  alsoNotFoo
 '''));
-    });
+      });
 
-    test('.foldFrames with terse: true shortens folded frames', () {
-      var trace = new Trace.parse('''
+        test('shortens folded frames', () {
+          var trace = new Trace.parse('''
 #0 notFoo (foo.dart:42:21)
 #1 fooTop (bar.dart:0:2)
 #2 fooBottom (package:foo/bar.dart:0:2)
 #3 alsoNotFoo (bar.dart:10:20)
 #4 fooTop (foo.dart:9:11)
 #5 fooBottom (foo/bar.dart:9:11)
+#6 againNotFoo (bar.dart:20:20)
 ''');
 
-      var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'),
-          terse: true);
-      expect(folded.toString(), equals('''
+          var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'),
+              terse: true);
+          expect(folded.toString(), equals('''
 foo.dart 42:21  notFoo
 package:foo     fooBottom
 bar.dart 10:20  alsoNotFoo
 foo             fooBottom
+bar.dart 20:20  againNotFoo
 '''));
-    });
+        });
 
-    test('.foldFrames will never fold unparsed frames', () {
-      var trace = new Trace.parse(r'''
-.g"cs$#:b";a#>sw{*{ul$"$xqwr`p
-%+j-?uppx<([j@#nu{{>*+$%x-={`{
-!e($b{nj)zs?cgr%!;bmw.+$j+pfj~
+        test('removes the bottom-most folded frame', () {
+          var trace = new Trace.parse('''
+#2 fooTop (package:foo/bar.dart:0:2)
+#3 notFoo (bar.dart:10:20)
+#5 fooBottom (foo/bar.dart:9:11)
 ''');
 
-      expect(trace.foldFrames((frame) => true).toString(), equals(r'''
-.g"cs$#:b";a#>sw{*{ul$"$xqwr`p
-%+j-?uppx<([j@#nu{{>*+$%x-={`{
-!e($b{nj)zs?cgr%!;bmw.+$j+pfj~
+          var folded = trace.foldFrames((frame) => frame.member.startsWith('foo'),
+              terse: true);
+          expect(folded.toString(), equals('''
+package:foo     fooTop
+bar.dart 10:20  notFoo
 '''));
+        });
+      });
     });
   });
 }