]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: move mockWarn into setup files
authorEvan You <yyx990803@gmail.com>
Tue, 28 Jul 2020 02:58:37 +0000 (22:58 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 28 Jul 2020 02:58:51 +0000 (22:58 -0400)
35 files changed:
jest.config.js
packages/compiler-sfc/__tests__/compileStyle.spec.ts
packages/global.d.ts
packages/reactivity/__tests__/collections/Map.spec.ts
packages/reactivity/__tests__/collections/Set.spec.ts
packages/reactivity/__tests__/computed.spec.ts
packages/reactivity/__tests__/reactive.spec.ts
packages/reactivity/__tests__/readonly.spec.ts
packages/runtime-core/__tests__/apiCreateApp.spec.ts
packages/runtime-core/__tests__/apiInject.spec.ts
packages/runtime-core/__tests__/apiOptions.spec.ts
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/__tests__/componentEmits.spec.ts
packages/runtime-core/__tests__/componentProps.spec.ts
packages/runtime-core/__tests__/componentProxy.spec.ts
packages/runtime-core/__tests__/errorHandling.spec.ts
packages/runtime-core/__tests__/helpers/renderSlot.spec.ts
packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts
packages/runtime-core/__tests__/helpers/toHandlers.spec.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
packages/runtime-core/__tests__/rendererChildren.spec.ts
packages/runtime-core/__tests__/vnode.spec.ts
packages/runtime-dom/__tests__/helpers/useCssModule.spec.ts
packages/runtime-dom/__tests__/patchProps.spec.ts
packages/runtime-test/__tests__/testRuntime.spec.ts
packages/server-renderer/__tests__/renderToStream.spec.ts
packages/server-renderer/__tests__/renderToString.spec.ts
packages/server-renderer/__tests__/ssrSuspense.spec.ts
packages/shared/src/index.ts
packages/shared/src/mockWarn.ts [deleted file]
packages/vue/__tests__/Transition.spec.ts
packages/vue/__tests__/TransitionGroup.spec.ts
packages/vue/__tests__/index.spec.ts
scripts/setupJestEnv.ts [new file with mode: 0644]

index 380449fa8ffc858ed59851259c924e82ae49cdaa..1b987827ca29a04f02d17c614e8d8f446e5d3036 100644 (file)
@@ -1,5 +1,6 @@
 module.exports = {
   preset: 'ts-jest',
+  setupFilesAfterEnv: ['./scripts/setupJestEnv.ts'],
   globals: {
     __DEV__: true,
     __TEST__: true,
index 328ac9a9811830e39741d8ad7f69adac1aeeaf47..b235273b6415d4cebe201c67f7299824691f0073 100644 (file)
@@ -7,12 +7,9 @@ import {
   compileStyleAsync,
   SFCStyleCompileOptions
 } from '../src/compileStyle'
-import { mockWarn } from '@vue/shared'
 import path from 'path'
 
 describe('SFC scoped CSS', () => {
-  mockWarn()
-
   function compileScoped(
     source: string,
     options?: Partial<SFCStyleCompileOptions>
index 830852217e6762899513641e6dc252fb3b39b748..2957d35acc338dcb658680511baa833588a61bb7 100644 (file)
@@ -13,3 +13,12 @@ declare var __VERSION__: string
 declare var __FEATURE_OPTIONS_API__: boolean
 declare var __FEATURE_PROD_DEVTOOLS__: boolean
 declare var __FEATURE_SUSPENSE__: boolean
+
+// for tests
+declare namespace jest {
+  interface Matchers<R, T> {
+    toHaveBeenWarned(): R
+    toHaveBeenWarnedLast(): R
+    toHaveBeenWarnedTimes(n: number): R
+  }
+}
index a4c69e5b9354cf0ef71989e4fa6c7d3fc897d352..6ee2c81ee4dd8f1ff24fb5cf741504ee5a58131f 100644 (file)
@@ -1,10 +1,7 @@
 import { reactive, effect, toRaw, isReactive } from '../../src'
-import { mockWarn } from '@vue/shared'
 
 describe('reactivity/collections', () => {
   describe('Map', () => {
-    mockWarn()
-
     test('instanceof', () => {
       const original = new Map()
       const observed = reactive(original)
index 8164081df5e9a6911bb887e886f4d9c704435e5d..8161ddf8f108e44eaaa224dc1e45e841401018a0 100644 (file)
@@ -1,10 +1,7 @@
 import { reactive, effect, isReactive, toRaw } from '../../src'
-import { mockWarn } from '@vue/shared'
 
 describe('reactivity/collections', () => {
   describe('Set', () => {
-    mockWarn()
-
     it('instanceof', () => {
       const original = new Set()
       const observed = reactive(original)
index fe6161ff881d40bce0a2851386fbe076825251fe..9d60ad352c41d20b547fc9d3367596156e483ac9 100644 (file)
@@ -7,11 +7,8 @@ import {
   WritableComputedRef,
   isReadonly
 } from '../src'
-import { mockWarn } from '@vue/shared'
 
 describe('reactivity/computed', () => {
-  mockWarn()
-
   it('should return updated value', () => {
     const value = reactive<{ foo?: number }>({})
     const cValue = computed(() => value.foo)
index 8020bbf84c6257bb0660fc01ed269507d620c0a3..c0b10969ba439e26d37fc69fa20431b8ce4fe1be 100644 (file)
@@ -1,11 +1,8 @@
 import { ref, isRef } from '../src/ref'
 import { reactive, isReactive, toRaw, markRaw } from '../src/reactive'
-import { mockWarn } from '@vue/shared'
 import { computed } from '../src/computed'
 
 describe('reactivity/reactive', () => {
-  mockWarn()
-
   test('Object', () => {
     const original = { foo: 1 }
     const observed = reactive(original)
index 6e99c589b52fa56eba0e81d42e8bbc54f42000db..e5eb52d5dff51783c20281603b8151d0afc6055c 100644 (file)
@@ -10,7 +10,6 @@ import {
   shallowReadonly,
   isProxy
 } from '../src'
-import { mockWarn } from '@vue/shared'
 
 /**
  * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html
@@ -18,8 +17,6 @@ import { mockWarn } from '@vue/shared'
 type Writable<T> = { -readonly [P in keyof T]: T[P] }
 
 describe('reactivity/readonly', () => {
-  mockWarn()
-
   describe('Object', () => {
     it('should make nested values readonly', () => {
       const original = { foo: 1, bar: { baz: 2 } }
index fbc784f44d4e2d0339e9da3339f11893b60ead32..c3be3476c2c09bbd08ac2c67eb3e7c2ce1a95bda 100644 (file)
@@ -13,11 +13,8 @@ import {
   getCurrentInstance,
   defineComponent
 } from '@vue/runtime-test'
-import { mockWarn } from '@vue/shared'
 
 describe('api: createApp', () => {
-  mockWarn()
-
   test('mount', () => {
     const Comp = defineComponent({
       props: {
index 4d68901f0a52f72397afd923d4d8a4371c095286..8498e4f848fdf872a4ee9086984ef03977c90c66 100644 (file)
@@ -10,13 +10,9 @@ import {
   reactive
 } from '../src/index'
 import { render, nodeOps, serialize } from '@vue/runtime-test'
-import { mockWarn } from '@vue/shared'
 
 // reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
-
 describe('api: provide/inject', () => {
-  mockWarn()
-
   it('string keys', () => {
     const Provider = {
       setup() {
index 074596bb30f128e2e1fc5ee06505292c59566df3..37fb36357bebd1c93641726a4a35ef34427fd791 100644 (file)
@@ -10,7 +10,6 @@ import {
   ref,
   defineComponent
 } from '@vue/runtime-test'
-import { mockWarn } from '@vue/shared'
 
 describe('api: options', () => {
   test('data', async () => {
@@ -705,8 +704,6 @@ describe('api: options', () => {
   })
 
   describe('warnings', () => {
-    mockWarn()
-
     test('Expected a function as watch handler', () => {
       const Comp = {
         watch: {
index 331b6f3c30558a66e5e8ea0546c4309d71d46135..951f2ba0ab56d7dc55bcb0b9a6bb13f9e94f3829 100644 (file)
@@ -14,13 +14,10 @@ import {
   TrackOpTypes,
   TriggerOpTypes
 } from '@vue/reactivity'
-import { mockWarn } from '@vue/shared'
 
 // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
 
 describe('api: watch', () => {
-  mockWarn()
-
   it('effect', async () => {
     const state = reactive({ count: 0 })
     let dummy
index effe6571f67933a3335798111951a8c5ea364ca0..b47a16cb53382c108e98014121e69a07527b1f70 100644 (file)
@@ -1,13 +1,10 @@
 // Note: emits and listener fallthrough is tested in
 // ./rendererAttrsFallthrough.spec.ts.
 
-import { mockWarn } from '@vue/shared'
 import { render, defineComponent, h, nodeOps } from '@vue/runtime-test'
 import { isEmitListener } from '../src/componentEmits'
 
 describe('component: emit', () => {
-  mockWarn()
-
   test('trigger handlers', () => {
     const Foo = defineComponent({
       render() {},
index ff30044f70baf367cdcf8f1ed0d35bb53be8802c..dee736c4620599b3a5a00419f50f54052d7bcaf8 100644 (file)
@@ -10,11 +10,8 @@ import {
   serializeInner
 } from '@vue/runtime-test'
 import { render as domRender, nextTick } from 'vue'
-import { mockWarn } from '@vue/shared'
 
 describe('component props', () => {
-  mockWarn()
-
   test('stateful', () => {
     let props: any
     let attrs: any
index 706fad4a69a8ab697bbe9f532f8fb98edc07ac67..02cc5f5f4e505e987f3c9d98aad540c05b0f9e9c 100644 (file)
@@ -6,12 +6,9 @@ import {
   createApp,
   shallowReadonly
 } from '@vue/runtime-test'
-import { mockWarn } from '@vue/shared'
 import { ComponentInternalInstance } from '../src/component'
 
 describe('component: proxy', () => {
-  mockWarn()
-
   test('data', () => {
     let instance: ComponentInternalInstance
     let instanceProxy: any
index 4830b65eff5d89f8830cda2fc8437919cd5a655e..56ae8b26a0d796fe3baba8eda6f0be61fe9c6b8b 100644 (file)
@@ -11,11 +11,8 @@ import {
   watchEffect
 } from '@vue/runtime-test'
 import { setErrorRecovery } from '../src/errorHandling'
-import { mockWarn } from '@vue/shared'
 
 describe('error handling', () => {
-  mockWarn()
-
   beforeEach(() => {
     setErrorRecovery(true)
   })
index f07c777283e8bdb0339e9944325e4ca218cd1263..a2c39f5b53457f34befe23fb8dcd6098ec3223f8 100644 (file)
@@ -1,9 +1,7 @@
 import { renderSlot } from '../../src/helpers/renderSlot'
 import { h } from '../../src/h'
-import { mockWarn } from '@vue/shared'
 
 describe('renderSlot', () => {
-  mockWarn()
   it('should render slot', () => {
     let child
     const vnode = renderSlot(
index 6f8957d064ca094d9430332793ab4ae376332622..5eee77f4d2d066ed404d5d6ee74f6b2438484538 100644 (file)
@@ -12,11 +12,8 @@ import {
   Comment,
   VNode
 } from '@vue/runtime-test'
-import { mockWarn } from '@vue/shared'
 
 describe('resolveAssets', () => {
-  mockWarn()
-
   test('should work', () => {
     const FooBar = () => null
     const BarBaz = { mounted: () => null }
index 44351b76871e61f6a0b9b09f96f639298e92b313..3dc5bb5172b6e611189a5caa2c025bb2021db858 100644 (file)
@@ -1,9 +1,6 @@
 import { toHandlers } from '../../src/helpers/toHandlers'
-import { mockWarn } from '@vue/shared'
 
 describe('toHandlers', () => {
-  mockWarn()
-
   it('should not accept non-objects', () => {
     toHandlers(null as any)
     toHandlers(undefined as any)
index 007ceec925ad433db9ff76a8e9cdd8cf97aa3004..fa89631d2017e655e252e631438c09239dfc933a 100644 (file)
@@ -12,7 +12,6 @@ import {
   defineComponent
 } from '@vue/runtime-dom'
 import { renderToString, SSRContext } from '@vue/server-renderer'
-import { mockWarn } from '@vue/shared'
 
 function mountWithHydration(html: string, render: () => any) {
   const container = document.createElement('div')
@@ -34,8 +33,6 @@ const triggerEvent = (type: string, el: Element) => {
 }
 
 describe('SSR hydration', () => {
-  mockWarn()
-
   beforeEach(() => {
     document.body.innerHTML = ''
   })
index ab18c842601814cf5ea823564238095aece3ed2e..e2031eb66bb8d7e91c2c757f52ada1df7960d1d0 100644 (file)
@@ -13,11 +13,8 @@ import {
   createCommentVNode,
   Fragment
 } from '@vue/runtime-dom'
-import { mockWarn } from '@vue/shared'
 
 describe('attribute fallthrough', () => {
-  mockWarn()
-
   it('should allow attrs to fallthrough', async () => {
     const click = jest.fn()
     const childUpdated = jest.fn()
index 22c2d101b62a55c8b876874dd70baa11795f2970..90e88c4b06b93ed7b2b0099f7e8b058dc392e7b4 100644 (file)
@@ -8,10 +8,6 @@ import {
   serialize,
   serializeInner
 } from '@vue/runtime-test'
-import { mockWarn } from '@vue/shared'
-
-mockWarn()
-
 function toSpan(content: any) {
   if (typeof content === 'string') {
     return h('span', content.toString())
index 4e6c207b716de9d4490ca35877d35ed4f6e3581b..2cab3215d40bc24bc7b92a3b4b7eb50a85ac9655 100644 (file)
@@ -11,14 +11,12 @@ import {
   transformVNodeArgs
 } from '../src/vnode'
 import { Data } from '../src/component'
-import { ShapeFlags, PatchFlags, mockWarn } from '@vue/shared'
+import { ShapeFlags, PatchFlags } from '@vue/shared'
 import { h, reactive, isReactive } from '../src'
 import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
 import { setCurrentRenderingInstance } from '../src/componentRenderUtils'
 
 describe('vnode', () => {
-  mockWarn()
-
   test('create with just tag', () => {
     const vnode = createVNode('p')
     expect(vnode.type).toBe('p')
index b92c9c161fdf79dd8e1b301dfc15dfd3634bdb5a..bb65082add1255a1dd252d37c347f70d0f9399c3 100644 (file)
@@ -1,10 +1,7 @@
 import { render, h, nodeOps } from '@vue/runtime-test'
 import { useCssModule } from '../../src/helpers/useCssModule'
-import { mockWarn } from '@vue/shared'
 
 describe('useCssModule', () => {
-  mockWarn()
-
   function mountWithModule(modules: any, name?: string) {
     let res
     render(
index f2f3dfdf15e53ec54514455cda873739d7173394..f8c3c06b344d35abb5c7e0330190278733988117 100644 (file)
@@ -1,10 +1,7 @@
 import { patchProp } from '../src/patchProp'
 import { render, h } from '../src'
-import { mockWarn } from '@vue/shared'
 
 describe('runtime-dom: props patching', () => {
-  mockWarn()
-
   test('basic', () => {
     const el = document.createElement('div')
     patchProp(el, 'id', null, 'foo')
index 8774faffa6f1311df2db7c399466b27381309b9a..f3ca6eb2025c8d0b369b8e7c2518575e57e41a62 100644 (file)
@@ -14,11 +14,8 @@ import {
   serialize,
   triggerEvent
 } from '../src'
-import { mockWarn } from '@vue/shared'
 
 describe('test renderer', () => {
-  mockWarn()
-
   it('should work', () => {
     const root = nodeOps.createElement('div')
     render(
index a679e9615345a8eac717e404016af4f0009bdf91..5f7ecf6795a96d31a017fb340adbf65b400aace3 100644 (file)
@@ -10,14 +10,11 @@ import {
   createTextVNode,
   createStaticVNode
 } from 'vue'
-import { escapeHtml, mockWarn } from '@vue/shared'
+import { escapeHtml } from '@vue/shared'
 import { renderToStream as _renderToStream } from '../src/renderToStream'
 import { Readable } from 'stream'
 import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
 import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
-
-mockWarn()
-
 const promisifyStream = (stream: Readable) => {
   return new Promise((resolve, reject) => {
     let result = ''
index f7985632fb3551692f7a502edebf26fbe70dcc7b..3e2ff0cc5280d7f6c64b8535e5845869a4fc7de4 100644 (file)
@@ -10,13 +10,10 @@ import {
   createTextVNode,
   createStaticVNode
 } from 'vue'
-import { escapeHtml, mockWarn } from '@vue/shared'
+import { escapeHtml } from '@vue/shared'
 import { renderToString } from '../src/renderToString'
 import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
 import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'
-
-mockWarn()
-
 describe('ssr: renderToString', () => {
   test('should apply app context', async () => {
     const app = createApp({
index 89009ac1a254de66c34e59dc279799743dde9333..a7d1078783328527724bf078e63158db4169ae78 100644 (file)
@@ -1,10 +1,7 @@
 import { createApp, h, Suspense } from 'vue'
 import { renderToString } from '../src/renderToString'
-import { mockWarn } from '@vue/shared'
 
 describe('SSR Suspense', () => {
-  mockWarn()
-
   const ResolvingAsync = {
     async setup() {
       return () => h('div', 'async')
index c0655ba5a51ac1dc0199bc7f9443607119e32ac3..bd7abd127b02a3727c4b8af43f4ff2b278bee8c9 100644 (file)
@@ -6,7 +6,6 @@ export * from './shapeFlags'
 export * from './slotFlags'
 export * from './globalsWhitelist'
 export * from './codeframe'
-export * from './mockWarn'
 export * from './normalizeProp'
 export * from './domTagConfig'
 export * from './domAttrConfig'
diff --git a/packages/shared/src/mockWarn.ts b/packages/shared/src/mockWarn.ts
deleted file mode 100644 (file)
index abd5db5..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-declare global {
-  namespace jest {
-    interface Matchers<R, T> {
-      toHaveBeenWarned(): R
-      toHaveBeenWarnedLast(): R
-      toHaveBeenWarnedTimes(n: number): R
-    }
-  }
-}
-
-export const mockError = () => mockWarn(true)
-
-export function mockWarn(asError = false) {
-  expect.extend({
-    toHaveBeenWarned(received: string) {
-      asserted.add(received)
-      const passed = warn.mock.calls.some(
-        args => args[0].indexOf(received) > -1
-      )
-      if (passed) {
-        return {
-          pass: true,
-          message: () => `expected "${received}" not to have been warned.`
-        }
-      } else {
-        const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
-        return {
-          pass: false,
-          message: () =>
-            `expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`
-        }
-      }
-    },
-
-    toHaveBeenWarnedLast(received: string) {
-      asserted.add(received)
-      const passed =
-        warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1
-      if (passed) {
-        return {
-          pass: true,
-          message: () => `expected "${received}" not to have been warned last.`
-        }
-      } else {
-        const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
-        return {
-          pass: false,
-          message: () =>
-            `expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`
-        }
-      }
-    },
-
-    toHaveBeenWarnedTimes(received: string, n: number) {
-      asserted.add(received)
-      let found = 0
-      warn.mock.calls.forEach(args => {
-        if (args[0].indexOf(received) > -1) {
-          found++
-        }
-      })
-
-      if (found === n) {
-        return {
-          pass: true,
-          message: () =>
-            `expected "${received}" to have been warned ${n} times.`
-        }
-      } else {
-        return {
-          pass: false,
-          message: () =>
-            `expected "${received}" to have been warned ${n} times but got ${found}.`
-        }
-      }
-    }
-  })
-
-  let warn: jest.SpyInstance
-  const asserted: Set<string> = new Set()
-
-  beforeEach(() => {
-    asserted.clear()
-    warn = jest.spyOn(console, asError ? 'error' : 'warn')
-    warn.mockImplementation(() => {})
-  })
-
-  afterEach(() => {
-    const assertedArray = Array.from(asserted)
-    const nonAssertedWarnings = warn.mock.calls
-      .map(args => args[0])
-      .filter(received => {
-        return !assertedArray.some(assertedMsg => {
-          return received.indexOf(assertedMsg) > -1
-        })
-      })
-    warn.mockRestore()
-    if (nonAssertedWarnings.length) {
-      nonAssertedWarnings.forEach(warning => {
-        console.warn(warning)
-      })
-      throw new Error(`test case threw unexpected warnings.`)
-    }
-  })
-}
index 112e7400cf378eec42ca29e51a4734ba4fba245a..0c124d182c44507981054971b318d7386f9d50a1 100644 (file)
@@ -1,10 +1,8 @@
 import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
 import path from 'path'
-import { mockWarn } from '@vue/shared'
 import { h, createApp, Transition } from 'vue'
 
 describe('e2e: Transition', () => {
-  mockWarn()
   const {
     page,
     html,
index 6e4233db28d1303b91d74da86d66cfa39fb20f7d..dee092ff67769a059ab5d2507e13a44213c6db0a 100644 (file)
@@ -1,10 +1,8 @@
 import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
 import path from 'path'
-import { mockWarn } from '@vue/shared'
 import { createApp, ref } from 'vue'
 
 describe('e2e: TransitionGroup', () => {
-  mockWarn()
   const { page, html, nextFrame, timeout } = setupPuppeteer()
   const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
 
index 304bc79b60d4785ea0d88bbe25215ff03615b761..974fffc0442467f8d9ce8ec7341602efeb9f55b9 100644 (file)
@@ -1,9 +1,6 @@
 import { createApp, ref, nextTick } from '../src'
-import { mockWarn } from '@vue/shared'
 
 describe('compiler + runtime integration', () => {
-  mockWarn()
-
   it('should support runtime template compilation', () => {
     const container = document.createElement('div')
     const App = {
@@ -55,7 +52,7 @@ describe('compiler + runtime integration', () => {
     expect(one.deactivated).toHaveBeenCalledTimes(0)
     expect(one.destroyed).toHaveBeenCalledTimes(0)
 
-    toggle.value = false;
+    toggle.value = false
     await nextTick()
     expect(container.innerHTML).toBe(`<!--v-if-->`)
     expect(one.created).toHaveBeenCalledTimes(1)
@@ -64,7 +61,7 @@ describe('compiler + runtime integration', () => {
     expect(one.deactivated).toHaveBeenCalledTimes(1)
     expect(one.destroyed).toHaveBeenCalledTimes(0)
 
-    toggle.value = true;
+    toggle.value = true
     await nextTick()
     expect(container.innerHTML).toBe(`one`)
     expect(one.created).toHaveBeenCalledTimes(1)
diff --git a/scripts/setupJestEnv.ts b/scripts/setupJestEnv.ts
new file mode 100644 (file)
index 0000000..95f1261
--- /dev/null
@@ -0,0 +1,88 @@
+expect.extend({
+  toHaveBeenWarned(received: string) {
+    asserted.add(received)
+    const passed = warn.mock.calls.some(args => args[0].indexOf(received) > -1)
+    if (passed) {
+      return {
+        pass: true,
+        message: () => `expected "${received}" not to have been warned.`
+      }
+    } else {
+      const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
+      return {
+        pass: false,
+        message: () =>
+          `expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`
+      }
+    }
+  },
+
+  toHaveBeenWarnedLast(received: string) {
+    asserted.add(received)
+    const passed =
+      warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1
+    if (passed) {
+      return {
+        pass: true,
+        message: () => `expected "${received}" not to have been warned last.`
+      }
+    } else {
+      const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
+      return {
+        pass: false,
+        message: () =>
+          `expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`
+      }
+    }
+  },
+
+  toHaveBeenWarnedTimes(received: string, n: number) {
+    asserted.add(received)
+    let found = 0
+    warn.mock.calls.forEach(args => {
+      if (args[0].indexOf(received) > -1) {
+        found++
+      }
+    })
+
+    if (found === n) {
+      return {
+        pass: true,
+        message: () => `expected "${received}" to have been warned ${n} times.`
+      }
+    } else {
+      return {
+        pass: false,
+        message: () =>
+          `expected "${received}" to have been warned ${n} times but got ${found}.`
+      }
+    }
+  }
+})
+
+let warn: jest.SpyInstance
+const asserted: Set<string> = new Set()
+
+beforeEach(() => {
+  asserted.clear()
+  warn = jest.spyOn(console, 'warn')
+  warn.mockImplementation(() => {})
+})
+
+afterEach(() => {
+  const assertedArray = Array.from(asserted)
+  const nonAssertedWarnings = warn.mock.calls
+    .map(args => args[0])
+    .filter(received => {
+      return !assertedArray.some(assertedMsg => {
+        return received.indexOf(assertedMsg) > -1
+      })
+    })
+  warn.mockRestore()
+  if (nonAssertedWarnings.length) {
+    nonAssertedWarnings.forEach(warning => {
+      console.warn(warning)
+    })
+    throw new Error(`test case threw unexpected warnings.`)
+  }
+})