summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAlan Pearce2017-07-02 15:30:20 +0200
committerAlan Pearce2017-07-02 15:30:20 +0200
commitb7bf162e6c3bc834097e65936704e3eac59eb4bd (patch)
tree9f080d9c67d6ee17a506e5ab52761f22b649137c
parent2d931962b74fe06c1bfbc2454fa166d24e8e2f59 (diff)
downloadhomestead-b7bf162e6c3bc834097e65936704e3eac59eb4bd.tar.lz
homestead-b7bf162e6c3bc834097e65936704e3eac59eb4bd.tar.zst
homestead-b7bf162e6c3bc834097e65936704e3eac59eb4bd.zip
feat: Use microformats classes
Add snapshot-based tests to ensure microformats data can be extracted
correctly.
-rw-r--r--package.json3
-rw-r--r--src/app.js36
-rw-r--r--src/domain/posts.js73
-rw-r--r--src/responders.js31
-rw-r--r--src/templates/home.html8
-rw-r--r--src/templates/post.html8
-rw-r--r--src/templates/taxon.html8
-rw-r--r--test/app.test.js227
-rw-r--r--test/domain/posts.test.js18
-rw-r--r--test/snapshots/app.test.js.md163
-rw-r--r--test/snapshots/app.test.js.snapbin0 -> 1236 bytes
-rw-r--r--yarn.lock272
12 files changed, 518 insertions, 329 deletions
diff --git a/package.json b/package.json
index 6cb7921..d324fdf 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
   },
   "devDependencies": {
     "auto-install": "^1.7.4",
-    "ava": "^0.19.1",
+    "ava": "^0.20.0",
     "cheerio": "^1.0.0-rc.1",
     "eslint": "^4.1.1",
     "eslint-config-prettier": "^2.2.0",
@@ -38,6 +38,7 @@
     "eslint-plugin-standard": "^3.0.1",
     "husky": "^0.13.4",
     "lint-staged": "^3.6.1",
+    "microformat-node": "^2.0.1",
     "node-dev": "^3.1.3",
     "prettier-standard": "^5.1.0",
     "standard": "^10.0.2",
diff --git a/src/app.js b/src/app.js
index fa0d5c2..1a0c5cb 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,31 +1,33 @@
-'use strict'
+"use strict";
 
-const Koa = require('koa')
-const app = new Koa()
+const Koa = require("koa");
+const app = new Koa();
 
-const helmet = require('koa-helmet')
+const helmet = require("koa-helmet");
 
-const actions = require('./actions.js')
+const actions = require("./actions.js");
 
-const config = require('./modules/config.js')
+const config = require("./modules/config.js");
 
-const Router = require('koa-router')
-const router = new Router()
+const Router = require("koa-router");
+const router = new Router();
 
-app.context.getURL = router.url.bind(router)
+app.context.getURL = router.url.bind(router);
 
-const Posts = require('./domain/posts.js')(config.posts)
+const Posts = require("./domain/posts.js")(config.posts, basename =>
+  router.url("post", basename)
+);
 
-router.get('home', '/', actions.home(config, Posts.posts))
+router.get("home", "/", actions.home(config, Posts.posts));
 
-router.get('post', '/post/:filename', actions.post(config, Posts.posts))
+router.get("post", "/post/:filename", actions.post(config, Posts.posts));
 
 for (let [term, items] of Posts.taxonomies) {
   router.get(
     `taxon-${term}`,
     `/${term}/:value`,
     actions.taxonGenerator(config, term, items)
-  )
+  );
 }
 
 app.use(
@@ -34,10 +36,10 @@ app.use(
       setIf: ctx => ctx.secure
     }
   })
-)
+);
 
-app.use(router.routes()).use(router.allowedMethods())
+app.use(router.routes()).use(router.allowedMethods());
 
-app.use(actions.serveFiles)
+app.use(actions.serveFiles);
 
-module.exports = app
+module.exports = app;
diff --git a/src/domain/posts.js b/src/domain/posts.js
index ea81729..fd4fb3d 100644
--- a/src/domain/posts.js
+++ b/src/domain/posts.js
@@ -1,81 +1,82 @@
-'use strict'
+"use strict";
 
-const fs = require('fs')
-const path = require('path')
-const matter = require('gray-matter')
-const markdown = require('../modules/markdown.js')
-const { indentForTemplate, postIndentLevel } = require('../responders.js')
+const fs = require("fs");
+const path = require("path");
+const matter = require("gray-matter");
+const markdown = require("../modules/markdown.js");
+const { indentForTemplate, postIndentLevel } = require("../responders.js");
 
 const grayMatterOptions = {
-  lang: 'toml',
-  delims: '+++'
-}
+  lang: "toml",
+  delims: "+++"
+};
 
-function* lowercaseKeys (iterator) {
+function* lowercaseKeys(iterator) {
   for (let [k, v] of iterator) {
-    yield [String(k).toLowerCase(), v]
+    yield [String(k).toLowerCase(), v];
   }
 }
 
-function canonicaliseMetadata (meta) {
+function canonicaliseMetadata(meta) {
   if (meta.data) {
-    meta.data = new Map(lowercaseKeys(Object.entries(meta.data)))
+    meta.data = new Map(lowercaseKeys(Object.entries(meta.data)));
   } else {
-    meta.data = new Map()
+    meta.data = new Map();
   }
-  return meta
+  return meta;
 }
 
-function getTitle (file) {
-  return path.basename(file.path, path.extname(file.path))
+function getTitle(file) {
+  return path.basename(file.path, path.extname(file.path));
 }
 
-function get (filename) {
-  const fileMatter = matter.read(filename, grayMatterOptions)
-  fileMatter.basename = getTitle(fileMatter)
-  delete fileMatter.orig
+function get(getURL, filename) {
+  const fileMatter = matter.read(filename, grayMatterOptions);
+  fileMatter.basename = getTitle(fileMatter);
+  delete fileMatter.orig;
   fileMatter.body = indentForTemplate(
     markdown(fileMatter.content),
     postIndentLevel
-  )
-  return canonicaliseMetadata(fileMatter)
+  );
+  fileMatter.url = getURL(fileMatter.basename);
+  return canonicaliseMetadata(fileMatter);
 }
 
-function getFolder (folder) {
+function getFolder(folder, getURL) {
   return new Map(
     fs
       .readdirSync(folder)
       .map(f => path.resolve(folder, f))
-      .map(get)
+      .map(get.bind(this, getURL))
       .map(f => [getTitle(f), f])
-  )
+  );
 }
 
-function taxonomise (taxonomies, posts) {
-  const taxons = new Map(Object.keys(taxonomies).map(t => [t, new Map()]))
+function taxonomise(taxonomies, posts) {
+  const taxons = new Map(Object.keys(taxonomies).map(t => [t, new Map()]));
 
   for (let [, post] of posts) {
     for (let [singularName, pluralName] of Object.entries(taxonomies)) {
       if (post.data.has(pluralName)) {
         for (let term of post.data.get(pluralName)) {
-          const current = taxons.get(singularName).get(term)
+          const current = taxons.get(singularName).get(term);
           taxons
             .get(singularName)
-            .set(term, current ? current.concat(post) : [post])
+            .set(term, current ? current.concat(post) : [post]);
         }
       }
     }
   }
 
-  return taxons
+  return taxons;
 }
 
-module.exports = function (config) {
-  const posts = getFolder(config.folder)
-  const taxonomies = taxonomise(config.taxonomies, posts)
+module.exports = function(config, getURL) {
+  const posts = getFolder(config.folder, getURL);
+  const taxonomies = taxonomise(config.taxonomies, posts);
   return {
     posts,
     taxonomies,
     get
-  }
-}
+  };
+};
diff --git a/src/responders.js b/src/responders.js
index 4049ebf..867310a 100644
--- a/src/responders.js
+++ b/src/responders.js
@@ -21,7 +21,7 @@ function getTemplateIndent(re, template) {
 }
 const findMain = /^(\s+)<main/m;
 const baseIndentLevel = getTemplateIndent(findMain, "layout");
-const findPostContent = /^(\s+)<div class="post-content/m;
+const findPostContent = /^(\s+)<div class="e-content/m;
 const postIndentLevel =
   baseIndentLevel + getTemplateIndent(findPostContent, "post");
 
@@ -49,12 +49,15 @@ const makeTime = date => ({
   _text: postDateFormatter.format(date)
 });
 
-const renderPostListItem = ctx => post => {
+const renderPost = ctx => post => {
   return {
-    time: makeTime(post.data.get("date")),
-    a: {
-      href: ctx.getURL("post", post.basename),
-      _text: post.data.get("title")
+    ".dt-published": makeTime(post.data.get("date")),
+    ".p-name": post.data.get("title"),
+    ".u-url": {
+      href: ctx.getURL("post", post.basename)
+    },
+    ".e-content": {
+      _html: post.body
     }
   };
 };
@@ -62,8 +65,8 @@ const renderPostListItem = ctx => post => {
 function layout(config, pageTitle, pageElement) {
   return hyperfast(templates.layout, {
     title: title(config.site.author.name, pageTitle),
-    "body > header .p-name": config.site.author.name,
-    "body > header .u-photo": {
+    ".h-card .p-name": config.site.author.name,
+    ".h-card .u-photo": {
       alt: config.site.author.name,
       src: config.site.author.photo
     },
@@ -83,7 +86,7 @@ module.exports = {
       config,
       null,
       hyperfast(templates.home, {
-        ".post": posts.map(renderPostListItem(ctx))
+        ".h-entry": posts.map(renderPost(ctx))
       })
     );
   },
@@ -94,13 +97,7 @@ module.exports = {
     ctx.body = layout(
       config,
       post.data.get("title"),
-      hyperfast(templates.post, {
-        "article h1": post.data.get("title"),
-        "article time": makeTime(post.data.get("date")),
-        "article .post-content": {
-          _html: post.body
-        }
-      })
+      hyperfast(templates.post, renderPost(ctx)(post))
     );
   },
 
@@ -111,7 +108,7 @@ module.exports = {
       config,
       Case.title(value),
       hyperfast(templates.taxon, {
-        ".post": taxonItems.map(renderPostListItem(ctx))
+        ".h-entry": taxonItems.map(renderPost(ctx))
       })
     );
   }
diff --git a/src/templates/home.html b/src/templates/home.html
index a3597a1..44244e5 100644
--- a/src/templates/home.html
+++ b/src/templates/home.html
@@ -1,6 +1,6 @@
-<ul class="posts">
-  <li class="post">
-    <a href="/">Test post please ignore</a>
-    <time></time>
+<ul class="h-feed">
+  <li class="h-entry">
+    <a class="u-url p-name" href="/">Test post please ignore</a>
+    <time class="dt-published"></time>
   </li>
 </ul>
diff --git a/src/templates/post.html b/src/templates/post.html
index 27bcdc3..a047aa4 100644
--- a/src/templates/post.html
+++ b/src/templates/post.html
@@ -1,7 +1,7 @@
-<article>
-  <h1>post title</h1>
-  <time></time>
-  <div class="post-content">
+<article class="h-entry">
+  <h1 class="p-name">post title</h1>
+  <time class="dt-published"></time>
+  <div class="e-content">
     Fringilla ut morbi tincidunt augue interdum velit euismod!
     Interdum velit laoreet id donec ultrices tincidunt arcu, non
     sodales neque sodales ut etiam sit amet nisl purus, in
diff --git a/src/templates/taxon.html b/src/templates/taxon.html
index a3597a1..44244e5 100644
--- a/src/templates/taxon.html
+++ b/src/templates/taxon.html
@@ -1,6 +1,6 @@
-<ul class="posts">
-  <li class="post">
-    <a href="/">Test post please ignore</a>
-    <time></time>
+<ul class="h-feed">
+  <li class="h-entry">
+    <a class="u-url p-name" href="/">Test post please ignore</a>
+    <time class="dt-published"></time>
   </li>
 </ul>
diff --git a/test/app.test.js b/test/app.test.js
index 6810089..69cea86 100644
--- a/test/app.test.js
+++ b/test/app.test.js
@@ -22,137 +22,110 @@ const toMicroformatsOptions = node => ({
   textFormat: "normalised"
 });
 
-test("homepage", t => {
-  return request(app.listen())
-    .get("/")
-    .expect(200)
-    .expect("Content-Type", "text/html; charset=utf-8")
-    .expect(/^<!DOCTYPE html>/)
-    .then(parseResponse)
-    .then($ => {
-      t.is($("head > title").text(), "John Doe", "head title is site author");
-      t.is($("h1").text(), "John Doe", "h1 is site author");
-      t.is($("main").length, 1, "only one <main> tag");
-      t.is($("main .posts").length, 1, "contains one posts listing");
-      t.is(
-        $(".post:first-of-type a").attr("href"),
-        "/post/testfile",
-        "first post url"
-      );
-      t.is(
-        $(".post:first-of-type time").text(),
-        "Sunday, January 1, 2017",
-        "first post date"
-      );
-      t.is(
-        $(".post:first-of-type time").attr("datetime"),
-        new Date("2017-01-01").toISOString()
-      );
-      return $;
-    })
-    .then(toMicroformatsOptions)
-    .then(options =>
-      Promise.all([
-        mf.countAsync(options).then(count =>
-          t.deepEqual(count, {
-            "h-card": 1
-          })
-        ),
-        mf.getAsync(options).then(data => {
-          t.deepEqual(data.items, [
-            {
-              properties: {
-                name: ["John Doe"],
-                url: ["/"],
-                photo: ["/static/johndoe.jpg"]
-              },
-              type: ["h-card"]
-            }
-          ]);
-        })
-      ])
-    );
-});
+async function notFound(t, url, bodyRegex) {
+  const res = await request(app.listen()).get(url);
+  const body = res.text;
+  t.is(res.statusCode, 404);
+  t.regex(body, bodyRegex);
+}
+notFound.title = (providedTitle, input, expected) =>
+  `${input} = 404 ${expected.toString()}`;
+
+test("homepage", async function(t) {
+  const res = await request(app.listen()).get("/");
+
+  t.is(res.statusCode, 200);
+  t.is(res.type, "text/html");
+  t.is(res.charset, "utf-8");
+  t.regex(res.text, /^<!DOCTYPE html>/);
+
+  const $ = parseResponse(res);
 
-test("post", t => {
-  return request(app.listen())
-    .get("/post/testfile")
-    .expect(200)
-    .expect("Content-Type", "text/html; charset=utf-8")
-    .expect(/^<!DOCTYPE html>/)
-    .then(parseResponse)
-    .then($ => {
-      t.is(
-        $("head > title").text(),
-        "This is a test · " + "John Doe",
-        "head title contains post and site author"
-      );
-      t.is(
-        $("article h1").text(),
-        "This is a test",
-        "article header is post title"
-      );
-      t.is(
-        $("article time").text(),
-        "Sunday, January 1, 2017",
-        "first post date"
-      );
-      t.is(
-        $("article time").attr("datetime"),
-        new Date("2017-01-01").toISOString()
-      );
-      t.is(
-        $("article p").text(),
-        `Ut enim blandit volutpat maecenas? Volutpat blandit aliquam etiam erat \
-velit, scelerisque in dictum non, consectetur a erat nam at lectus \
-urna duis convallis convallis tellus, id interdum velit laoreet!`,
-        "article has text"
-      );
-    });
+  t.is($("head > title").text(), "John Doe", "head title is site author");
+  t.is($("h1").text(), "John Doe", "h1 is site author");
+  t.is($("main").length, 1, "only one <main> tag");
+  t.is(
+    $(".h-entry:first-of-type time").text(),
+    "Sunday, January 1, 2017",
+    "first post date"
+  );
+  t.is(
+    $(".h-entry:first-of-type time").attr("datetime"),
+    new Date("2017-01-01").toISOString()
+  );
+
+  const options = toMicroformatsOptions($);
+  const count = await mf.countAsync(options);
+
+  t.deepEqual(count, {
+    "h-card": 1,
+    "h-feed": 1,
+    "h-entry": 1
+  });
+
+  const data = await mf.getAsync(options);
+
+  t.snapshot(data, "should contain relevant microformats data");
 });
 
-test("post not found", t => {
-  return request(app.listen())
-    .get("/post/non-existant")
-    .expect(404)
-    .expect(/Post not found/)
-    .then(() => t.pass());
+test("post", async function(t) {
+  const res = await request(app.listen()).get("/post/testfile");
+
+  t.is(res.statusCode, 200);
+  t.is(res.type, "text/html");
+  t.is(res.charset, "utf-8");
+  t.regex(res.text, /^<!DOCTYPE html>/);
+
+  const $ = parseResponse(res);
+
+  t.is(
+    $("head > title").text(),
+    "This is a test · " + "John Doe",
+    "head title contains post and site author"
+  );
+
+  const options = toMicroformatsOptions($);
+
+  const count = await mf.countAsync(options);
+
+  t.deepEqual(count, {
+    "h-card": 1,
+    "h-entry": 1
+  });
+
+  const data = await mf.getAsync(options);
+
+  t.snapshot(data, "should contain relevant microformats data");
 });
 
-test("tags", t => {
-  return request(app.listen())
-    .get("/tag/a")
-    .expect(200)
-    .expect("Content-Type", "text/html; charset=utf-8")
-    .expect(/^<!DOCTYPE html>/)
-    .then(parseResponse)
-    .then($ => {
-      t.is(
-        $("head > title").text(),
-        "A · John Doe",
-        "head title contains title-cased tag and site name"
-      );
-      t.is(
-        $(".post a").text(),
-        "This is a test",
-        "post link text is post title"
-      );
-      t.is(
-        $(".post:first-of-type a").attr("href"),
-        "/post/testfile",
-        "post url"
-      );
-      t.is(
-        $(".post:first-of-type time").text(),
-        "Sunday, January 1, 2017",
-        "first post date"
-      );
-    });
+test("tags", async function(t) {
+  const res = await request(app.listen()).get("/tag/a");
+
+  t.is(res.statusCode, 200);
+  t.is(res.type, "text/html");
+  t.is(res.charset, "utf-8");
+  t.regex(res.text, /^<!DOCTYPE html>/);
+
+  const $ = parseResponse(res);
+
+  t.is(
+    $("head > title").text(),
+    "A · John Doe",
+    "head title contains title-cased tag and site name"
+  );
+  const options = toMicroformatsOptions($);
+  const count = await mf.countAsync(options);
+
+  t.deepEqual(count, {
+    "h-card": 1,
+    "h-feed": 1,
+    "h-entry": 1
+  });
+
+  const data = await mf.getAsync(options);
+
+  t.snapshot(data, "should contain relevant microformats data");
 });
 
-test("tags not found", t =>
-  request(app.listen())
-    .get("/tag/non-existant")
-    .expect(404)
-    .expect(/tag non-existant not found/)
-    .then(() => t.pass()));
+test(notFound, "/post/non-existent", /Post not found/);
+test(notFound, "/tag/non-existent", /tag non-existent not found/);
diff --git a/test/domain/posts.test.js b/test/domain/posts.test.js
index cc236c7..8b27698 100644
--- a/test/domain/posts.test.js
+++ b/test/domain/posts.test.js
@@ -1,13 +1,16 @@
 const test = require("ava");
 const path = require("path");
 
-const Posts = require("../../src/domain/posts.js")({
-  folder: path.resolve(__dirname, "../testsite/posts/"),
-  taxonomies: {
-    tag: "tags",
-    category: "categories"
-  }
-});
+const Posts = require("../../src/domain/posts.js")(
+  {
+    folder: path.resolve(__dirname, "../testsite/posts/"),
+    taxonomies: {
+      tag: "tags",
+      category: "categories"
+    }
+  },
+  basename => basename
+);
 
 test("get", t => {
   const expected = new Map(
@@ -19,6 +22,7 @@ test("get", t => {
     })
   );
   const post = Posts.get(
+    basename => basename,
     path.resolve(__dirname, "../testsite/posts/testfile.md")
   );
   t.deepEqual(post.data, expected);
diff --git a/test/snapshots/app.test.js.md b/test/snapshots/app.test.js.md
new file mode 100644
index 0000000..99811f9
--- /dev/null
+++ b/test/snapshots/app.test.js.md
@@ -0,0 +1,163 @@
+# Snapshot report for `test/app.test.js`
+
+The actual snapshot is saved in `app.test.js.snap`.
+
+Generated by [AVA](https://ava.li).
+
+## homepage
+
+> should contain relevant microformats data
+
+    {
+      items: [
+        {
+          properties: {
+            name: [
+              'John Doe',
+            ],
+            photo: [
+              '/static/johndoe.jpg',
+            ],
+            url: [
+              '/',
+            ],
+          },
+          type: [
+            'h-card',
+          ],
+        },
+        {
+          children: [
+            {
+              properties: {
+                name: [
+                  'This is a test',
+                ],
+                published: [
+                  '2017-01-01T00:00:00.000Z',
+                ],
+                url: [
+                  '/post/testfile',
+                ],
+              },
+              type: [
+                'h-entry',
+              ],
+              value: 'This is a test Sunday, January 1, 2017',
+            },
+          ],
+          properties: {
+            name: [
+              'John Doe',
+            ],
+          },
+          type: [
+            'h-feed',
+          ],
+        },
+      ],
+      'rel-urls': {},
+      rels: {},
+    }
+
+## post
+
+> should contain relevant microformats data
+
+    {
+      items: [
+        {
+          properties: {
+            name: [
+              'John Doe',
+            ],
+            photo: [
+              '/static/johndoe.jpg',
+            ],
+            url: [
+              '/',
+            ],
+          },
+          type: [
+            'h-card',
+          ],
+        },
+        {
+          properties: {
+            content: [
+              {
+                html: '<p>Ut enim blandit volutpat maecenas? Volutpat blandit aliquam etiam erat velit, scelerisque in dictum non, consectetur a erat nam at lectus urna duis convallis convallis tellus, id interdum velit laoreet!</p>',
+                value: 'Ut enim blandit volutpat maecenas? Volutpat blandit aliquam etiam erat velit, scelerisque in dictum non, consectetur a erat nam at lectus urna duis convallis convallis tellus, id interdum velit laoreet!',
+              },
+            ],
+            name: [
+              'This is a test',
+            ],
+            published: [
+              '2017-01-01T00:00:00.000Z',
+            ],
+          },
+          type: [
+            'h-entry',
+          ],
+        },
+      ],
+      'rel-urls': {},
+      rels: {},
+    }
+
+## tags
+
+> should contain relevant microformats data
+
+    {
+      items: [
+        {
+          properties: {
+            name: [
+              'John Doe',
+            ],
+            photo: [
+              '/static/johndoe.jpg',
+            ],
+            url: [
+              '/',
+            ],
+          },
+          type: [
+            'h-card',
+          ],
+        },
+        {
+          children: [
+            {
+              properties: {
+                name: [
+                  'This is a test',
+                ],
+                published: [
+                  '2017-01-01T00:00:00.000Z',
+                ],
+                url: [
+                  '/post/testfile',
+                ],
+              },
+              type: [
+                'h-entry',
+              ],
+              value: 'This is a test Sunday, January 1, 2017',
+            },
+          ],
+          properties: {
+            name: [
+              'A · John Doe',
+            ],
+          },
+          type: [
+            'h-feed',
+          ],
+        },
+      ],
+      'rel-urls': {},
+      rels: {},
+    }
diff --git a/test/snapshots/app.test.js.snap b/test/snapshots/app.test.js.snap
new file mode 100644
index 0000000..bbc5c61
--- /dev/null
+++ b/test/snapshots/app.test.js.snap
Binary files differdiff --git a/yarn.lock b/yarn.lock
index 48d071d..a43ee0a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6,7 +6,7 @@
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-2.0.0.tgz#2fc1fe3c211a71071a4eca7b8f7af5842cd1ae7c"
 
-"@ava/babel-preset-stage-4@^1.0.0":
+"@ava/babel-preset-stage-4@^1.1.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@ava/babel-preset-stage-4/-/babel-preset-stage-4-1.1.0.tgz#ae60be881a0babf7d35f52aba770d1f6194f76bd"
   dependencies:
@@ -30,12 +30,19 @@
     "@ava/babel-plugin-throws-helper" "^2.0.0"
     babel-plugin-espower "^2.3.2"
 
-"@ava/pretty-format@^1.1.0":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@ava/pretty-format/-/pretty-format-1.1.0.tgz#d0a57d25eb9aeab9643bdd1a030642b91c123e28"
+"@ava/write-file-atomic@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@ava/write-file-atomic/-/write-file-atomic-2.2.0.tgz#d625046f3495f1f5e372135f473909684b429247"
   dependencies:
-    ansi-styles "^2.2.1"
-    esutils "^2.0.2"
+    graceful-fs "^4.1.11"
+    imurmurhash "^0.1.4"
+    slide "^1.1.5"
+
+"@concordance/react@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@concordance/react/-/react-1.0.0.tgz#fcf3cad020e5121bfd1c61d05bc3516aac25f734"
+  dependencies:
+    arrify "^1.0.1"
 
 "@types/node@^6.0.46":
   version "6.0.79"
@@ -105,7 +112,7 @@ ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
-ansi-styles@^3.0.0:
+ansi-styles@^3.0.0, ansi-styles@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750"
   dependencies:
@@ -259,20 +266,22 @@ ava-init@^0.2.0:
     read-pkg-up "^2.0.0"
     write-pkg "^2.0.0"
 
-ava@^0.19.1:
-  version "0.19.1"
-  resolved "https://registry.yarnpkg.com/ava/-/ava-0.19.1.tgz#43dd82435ad19b3980ffca2488f05daab940b273"
+ava@^0.20.0:
+  version "0.20.0"
+  resolved "https://registry.yarnpkg.com/ava/-/ava-0.20.0.tgz#bdc0dd36453d7255e9f733305ab370c248381e41"
   dependencies:
-    "@ava/babel-preset-stage-4" "^1.0.0"
+    "@ava/babel-preset-stage-4" "^1.1.0"
     "@ava/babel-preset-transform-test-files" "^3.0.0"
-    "@ava/pretty-format" "^1.1.0"
+    "@ava/write-file-atomic" "^2.2.0"
+    "@concordance/react" "^1.0.0"
+    ansi-escapes "^2.0.0"
+    ansi-styles "^3.1.0"
     arr-flatten "^1.0.1"
     array-union "^1.0.1"
     array-uniq "^1.0.2"
     arrify "^1.0.0"
     auto-bind "^1.1.0"
     ava-init "^0.2.0"
-    babel-code-frame "^6.16.0"
     babel-core "^6.17.0"
     bluebird "^3.0.0"
     caching-transform "^1.0.0"
@@ -286,12 +295,11 @@ ava@^0.19.1:
     co-with-promise "^4.6.0"
     code-excerpt "^2.1.0"
     common-path-prefix "^1.0.0"
+    concordance "^2.0.0"
     convert-source-map "^1.2.0"
     core-assert "^0.2.0"
     currently-unhandled "^0.4.1"
     debug "^2.2.0"
-    diff "^3.0.1"
-    diff-match-patch "^1.0.0"
     dot-prop "^4.1.0"
     empower-core "^0.6.1"
     equal-length "^1.0.0"
@@ -301,28 +309,27 @@ ava@^0.19.1:
     get-port "^3.0.0"
     globby "^6.0.0"
     has-flag "^2.0.0"
-    hullabaloo-config-manager "^1.0.0"
+    hullabaloo-config-manager "^1.1.0"
     ignore-by-default "^1.0.0"
+    import-local "^0.1.1"
     indent-string "^3.0.0"
     is-ci "^1.0.7"
     is-generator-fn "^1.0.0"
     is-obj "^1.0.0"
     is-observable "^0.2.0"
     is-promise "^2.1.0"
-    jest-diff "19.0.0"
-    jest-snapshot "19.0.2"
     js-yaml "^3.8.2"
     last-line-stream "^1.0.0"
+    lodash.clonedeepwith "^4.5.0"
     lodash.debounce "^4.0.3"
     lodash.difference "^4.3.0"
     lodash.flatten "^4.2.0"
-    lodash.isequal "^4.5.0"
     loud-rejection "^1.2.0"
+    make-dir "^1.0.0"
     matcher "^0.1.1"
     md5-hex "^2.0.0"
     meow "^3.7.0"
-    mkdirp "^0.5.1"
-    ms "^0.7.1"
+    ms "^1.0.0"
     multimatch "^2.1.0"
     observable-to-promise "^0.5.0"
     option-chain "^0.1.0"
@@ -339,6 +346,7 @@ ava@^0.19.1:
     strip-bom-buf "^1.0.0"
     supports-color "^3.2.3"
     time-require "^0.1.2"
+    trim-off-newlines "^1.0.1"
     unique-temp-dir "^1.0.0"
     update-notifier "^2.1.0"
 
@@ -675,9 +683,9 @@ block-stream@*:
   dependencies:
     inherits "~2.0.0"
 
-bluebird@^3.0.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
+bluebird@3.4.x, bluebird@^3.0.0:
+  version "3.4.7"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
 
 boolbase@~1.0.0:
   version "1.0.0"
@@ -823,6 +831,27 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
+cheerio@0.22.x:
+  version "0.22.0"
+  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
+  dependencies:
+    css-select "~1.2.0"
+    dom-serializer "~0.1.0"
+    entities "~1.1.1"
+    htmlparser2 "^3.9.1"
+    lodash.assignin "^4.0.9"
+    lodash.bind "^4.1.4"
+    lodash.defaults "^4.0.1"
+    lodash.filter "^4.4.0"
+    lodash.flatten "^4.2.0"
+    lodash.foreach "^4.3.0"
+    lodash.map "^4.4.0"
+    lodash.merge "^4.4.0"
+    lodash.pick "^4.2.1"
+    lodash.reduce "^4.4.0"
+    lodash.reject "^4.4.0"
+    lodash.some "^4.4.0"
+
 cheerio@^1.0.0-rc.1:
   version "1.0.0-rc.1"
   resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.1.tgz#2af37339eab713ef6b72cde98cefa672b87641fe"
@@ -1020,6 +1049,22 @@ concat-stream@^1.5.2, concat-stream@^1.6.0:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
+concordance@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/concordance/-/concordance-2.0.0.tgz#c3c5dbffa83c29537df202bded8fa1d6aa94e805"
+  dependencies:
+    esutils "^2.0.2"
+    fast-diff "^1.1.1"
+    function-name-support "^0.2.0"
+    js-string-escape "^1.0.1"
+    lodash.clonedeep "^4.5.0"
+    lodash.flattendeep "^4.4.0"
+    lodash.merge "^4.6.0"
+    md5-hex "^2.0.0"
+    moment "^2.18.1"
+    semver "^5.3.0"
+    well-known-symbols "^1.0.0"
+
 configly@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/configly/-/configly-4.1.0.tgz#48a6af73cddd1e4d98d44fe264b78db2b0880bce"
@@ -1339,14 +1384,6 @@ detective@4.3.2:
     acorn "^3.1.0"
     defined "^1.0.0"
 
-diff-match-patch@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.0.tgz#1cc3c83a490d67f95d91e39f6ad1f2e086b63048"
-
-diff@^3.0.0, diff@^3.0.1:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
-
 dlv@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.0.tgz#fee1a7c43f63be75f3f679e85262da5f102764a7"
@@ -2079,6 +2116,10 @@ function-bind@^1.0.2, function-bind@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
 
+function-name-support@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/function-name-support/-/function-name-support-0.2.0.tgz#55d3bfaa6eafd505a50f9bc81fdf57564a0bb071"
+
 gauge@~2.7.3:
   version "2.7.4"
   resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -2242,7 +2283,7 @@ got@^6.7.1:
     unzip-response "^2.0.1"
     url-parse-lax "^1.0.0"
 
-graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+graceful-fs@^4.1.11, graceful-fs@^4.1.2:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
@@ -2425,7 +2466,7 @@ http-signature@~1.1.0:
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
-hullabaloo-config-manager@^1.0.0:
+hullabaloo-config-manager@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/hullabaloo-config-manager/-/hullabaloo-config-manager-1.1.1.tgz#1d9117813129ad035fd9e8477eaf066911269fe3"
   dependencies:
@@ -2482,6 +2523,13 @@ import-lazy@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
 
+import-local@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8"
+  dependencies:
+    pkg-dir "^2.0.0"
+    resolve-cwd "^2.0.0"
+
 import-modules@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/import-modules/-/import-modules-1.1.0.tgz#748db79c5cc42bb9701efab424f894e72600e9dc"
@@ -2801,74 +2849,13 @@ isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
-jest-diff@19.0.0, jest-diff@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-19.0.0.tgz#d1563cfc56c8b60232988fbc05d4d16ed90f063c"
-  dependencies:
-    chalk "^1.1.3"
-    diff "^3.0.0"
-    jest-matcher-utils "^19.0.0"
-    pretty-format "^19.0.0"
-
 jest-docblock@^20.0.1:
   version "20.0.3"
   resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712"
 
-jest-file-exists@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-19.0.0.tgz#cca2e587a11ec92e24cfeab3f8a94d657f3fceb8"
-
-jest-matcher-utils@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-19.0.0.tgz#5ecd9b63565d2b001f61fbf7ec4c7f537964564d"
-  dependencies:
-    chalk "^1.1.3"
-    pretty-format "^19.0.0"
-
-jest-message-util@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-19.0.0.tgz#721796b89c0e4d761606f9ba8cb828a3b6246416"
-  dependencies:
-    chalk "^1.1.1"
-    micromatch "^2.3.11"
-
-jest-mock@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-19.0.0.tgz#67038641e9607ab2ce08ec4a8cb83aabbc899d01"
-
-jest-snapshot@19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-19.0.2.tgz#9c1b216214f7187c38bfd5c70b1efab16b0ff50b"
-  dependencies:
-    chalk "^1.1.3"
-    jest-diff "^19.0.0"
-    jest-file-exists "^19.0.0"
-    jest-matcher-utils "^19.0.0"
-    jest-util "^19.0.2"
-    natural-compare "^1.4.0"
-    pretty-format "^19.0.0"
-
-jest-util@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-19.0.2.tgz#e0a0232a2ab9e6b2b53668bdb3534c2b5977ed41"
-  dependencies:
-    chalk "^1.1.1"
-    graceful-fs "^4.1.6"
-    jest-file-exists "^19.0.0"
-    jest-message-util "^19.0.0"
-    jest-mock "^19.0.0"
-    jest-validate "^19.0.2"
-    leven "^2.0.0"
-    mkdirp "^0.5.1"
-
-jest-validate@^19.0.2:
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c"
-  dependencies:
-    chalk "^1.1.1"
-    jest-matcher-utils "^19.0.0"
-    leven "^2.0.0"
-    pretty-format "^19.0.0"
+js-string-escape@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
 
 js-tokens@^3.0.0:
   version "3.0.1"
@@ -3041,10 +3028,6 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-leven@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
-
 levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -3191,6 +3174,14 @@ lodash.assign@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
 
+lodash.assignin@^4.0.9:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
+
+lodash.bind@^4.1.4:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35"
+
 lodash.chunk@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc"
@@ -3218,10 +3209,18 @@ lodash.debounce@^4.0.3:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
 
+lodash.defaults@^4.0.1:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
+
 lodash.difference@^4.3.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
 
+lodash.filter@^4.4.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
+
 lodash.flatten@^4.2.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
@@ -3230,6 +3229,10 @@ lodash.flattendeep@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
 
+lodash.foreach@^4.3.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
+
 lodash.isarguments@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
@@ -3250,18 +3253,34 @@ lodash.keys@^3.0.0:
     lodash.isarguments "^3.0.0"
     lodash.isarray "^3.0.0"
 
+lodash.map@^4.4.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
 
-lodash.merge@^4.6.0:
+lodash.merge@^4.4.0, lodash.merge@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5"
 
-lodash.reduce@4.6.0:
+lodash.pick@^4.2.1:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
+
+lodash.reduce@4.6.0, lodash.reduce@^4.4.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b"
 
+lodash.reject@^4.4.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415"
+
+lodash.some@^4.4.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
+
 lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -3417,7 +3436,20 @@ methods@^1.0.1, methods@^1.1.1, methods@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
 
-micromatch@^2.1.5, micromatch@^2.3.11:
+microformat-node@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/microformat-node/-/microformat-node-2.0.1.tgz#0849dcfb29f4f9251a4c6523d9a873cf45b8e942"
+  dependencies:
+    bluebird "3.4.x"
+    cheerio "0.22.x"
+    ent "^2.2.0"
+    microformat-shiv "^2.0.0"
+
+microformat-shiv@^2.0.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/microformat-shiv/-/microformat-shiv-2.0.3.tgz#0350dab6da2c517f8f4cef1ab287fe6beeaec725"
+
+micromatch@^2.1.5:
   version "2.3.11"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
   dependencies:
@@ -3473,7 +3505,11 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
   dependencies:
     minimist "0.0.8"
 
-ms@0.7.1, ms@^0.7.1:
+moment@^2.18.1:
+  version "2.18.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
+
+ms@0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
 
@@ -3481,6 +3517,10 @@ ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
 
+ms@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-1.0.0.tgz#59adcd22edc543f7b5381862d31387b1f4bc9473"
+
 multimatch@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b"
@@ -4060,12 +4100,6 @@ prettier@^1.4.2, prettier@^1.4.4:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.4.4.tgz#a8d1447b14c9bf67e6d420dcadd10fb9a4fad65a"
 
-pretty-format@^19.0.0:
-  version "19.0.0"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-19.0.0.tgz#56530d32acb98a3fa4851c4e2b9d37b420684c84"
-  dependencies:
-    ansi-styles "^3.0.0"
-
 pretty-format@^20.0.3:
   version "20.0.3"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14"
@@ -4407,6 +4441,12 @@ resolve-cwd@^1.0.0:
   dependencies:
     resolve-from "^2.0.0"
 
+resolve-cwd@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
+  dependencies:
+    resolve-from "^3.0.0"
+
 resolve-from@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
@@ -4891,6 +4931,10 @@ trim-newlines@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
 
+trim-off-newlines@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
+
 trim-right@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
@@ -5026,6 +5070,10 @@ verror@1.3.6:
   dependencies:
     extsprintf "1.0.2"
 
+well-known-symbols@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-1.0.0.tgz#73c78ae81a7726a8fa598e2880801c8b16225518"
+
 which-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"