Просмотр исходного кода

test: shim done interface for tests

Evan You 4 лет назад
Родитель
Сommit
5ec77f32be

+ 10 - 16
test/helpers/classlist.ts

@@ -1,18 +1,12 @@
-beforeEach(() => {
-  jasmine.addMatchers({
-    // since classList may not be supported in all browsers
-    toHaveClass: () => {
-      return {
-        compare: (el, cls) => {
-          const pass = el.classList
-            ? el.classList.contains(cls)
-            : el.getAttribute('class').split(/\s+/g).indexOf(cls) > -1
-          return {
-            pass,
-            message: `Expected element${pass ? ' ' : ' not '}to have class ${cls}`
-          }
-        }
-      }
+expect.extend({
+  toHaveClass(el: Element, cls: string) {
+    const pass = el.classList
+      ? el.classList.contains(cls)
+      : (el.getAttribute('class') || '').split(/\s+/g).indexOf(cls) > -1
+    return {
+      pass,
+      message: () =>
+        `Expected element${pass ? ' ' : ' not '}to have class ${cls}`
     }
-  })
+  }
 })

+ 33 - 0
test/helpers/shim-done.ts

@@ -0,0 +1,33 @@
+// wrap tests to support test('foo', done => {...}) interface
+const _test = test
+
+const wait = (): [() => void, Promise<void>] => {
+  let done
+  const p = new Promise<void>((resolve, reject) => {
+    done = resolve
+    done.fail = reject
+  })
+  return [done, p]
+}
+
+;(global as any).it = (global as any).test = (
+  desc: string,
+  fn?: any,
+  timeout?: number
+) => {
+  if (fn && fn.length > 0) {
+    _test(
+      desc,
+      () => {
+        const [done, p] = wait()
+        fn(done)
+        return p
+      },
+      timeout
+    )
+  } else {
+    _test(desc, fn, timeout)
+  }
+}
+
+export {}

+ 0 - 19
test/helpers/to-equal.ts

@@ -1,19 +0,0 @@
-import { isEqual } from 'lodash'
-
-beforeEach(() => {
-  jasmine.addMatchers({
-    // override built-in toEqual because it behaves incorrectly
-    // on Vue-observed arrays in Safari
-    toEqual: () => {
-      return {
-        compare: (a, b) => {
-          const pass = isEqual(a, b)
-          return {
-            pass,
-            message: `Expected ${a} to equal ${b}`
-          }
-        }
-      }
-    }
-  })
-})

+ 94 - 0
test/helpers/to-have-warned.ts

@@ -0,0 +1,94 @@
+import { SpyInstance } from 'vitest'
+
+expect.extend({
+  toHaveBeenWarned(received: string) {
+    asserted.add(received)
+    const passed = warn.mock.calls.some((args) => args[0].includes(received))
+    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` +
+          (msgs.length
+            ? `.\n\nActual messages:\n\n - ${msgs}`
+            : ` but no warning was recorded.`)
+      }
+    }
+  },
+
+  toHaveBeenWarnedLast(received: string) {
+    asserted.add(received)
+    const passed =
+      warn.mock.calls[warn.mock.calls.length - 1][0].includes(received)
+    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].includes(received)) {
+        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: SpyInstance
+const asserted: Set<string> = new Set()
+
+beforeEach(() => {
+  asserted.clear()
+  warn = vi.spyOn(console, 'error')
+  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.includes(assertedMsg)
+      })
+    })
+  warn.mockRestore()
+  if (nonAssertedWarnings.length) {
+    throw new Error(
+      `test case threw unexpected warnings:\n - ${nonAssertedWarnings.join(
+        '\n - '
+      )}`
+    )
+  }
+})

+ 1 - 1
test/helpers/trigger-event.ts

@@ -1,4 +1,4 @@
-window.triggerEvent = function triggerEvent (target, event, process) {
+global.triggerEvent = function triggerEvent (target, event, process) {
   const e = document.createEvent('HTMLEvents')
   e.initEvent(event, true, true)
   if (event === 'click') {

+ 13 - 8
test/helpers/wait-for-update.ts

@@ -13,16 +13,21 @@ import Vue from 'vue'
 // })
 // .then(done)
 
-window.waitForUpdate = initialCb => {
+interface Job extends Function {
+  wait?: boolean
+  fail?: (e: any) => void
+}
+
+global.waitForUpdate = (initialCb: Job) => {
   let end
-  const queue = initialCb ? [initialCb] : []
+  const queue: Job[] = initialCb ? [initialCb] : []
 
-  function shift () {
+  function shift() {
     const job = queue.shift()
     if (queue.length) {
       let hasError = false
       try {
-        job.wait ? job(shift) : job()
+        job!.wait ? job!(shift) : job!()
       } catch (e) {
         hasError = true
         const done = queue[queue.length - 1]
@@ -48,7 +53,7 @@ window.waitForUpdate = initialCb => {
   })
 
   const chainer = {
-    then: nextCb => {
+    then: (nextCb) => {
       queue.push(nextCb)
       return chainer
     },
@@ -60,7 +65,7 @@ window.waitForUpdate = initialCb => {
       queue.push(wait)
       return chainer
     },
-    end: endFn => {
+    end: (endFn) => {
       queue.push(endFn)
       end = endFn
     }
@@ -69,6 +74,6 @@ window.waitForUpdate = initialCb => {
   return chainer
 }
 
-function timeout (n) {
-  return next => setTimeout(next, n)
+function timeout(n) {
+  return (next) => setTimeout(next, n)
 }

+ 25 - 0
test/test-env.d.ts

@@ -0,0 +1,25 @@
+interface Chainer {
+  then(next: Function): this
+  thenWaitFor(n: number): this
+  end(endFn: Function): void
+}
+
+declare function waitForUpdate(cb: Function): Chainer
+
+declare function createTextVNode(arg?: string): any
+
+declare function triggerEvent(
+  target: Element,
+  event: string,
+  process?: (e: Event) => void
+): void
+
+// vitest extends jest namespace so we can just extend jest.Matchers
+declare namespace jest {
+  interface Matchers<R, T> {
+    toHaveBeenWarned(): R
+    toHaveBeenWarnedLast(): R
+    toHaveBeenWarnedTimes(n: number): R
+    toHaveClass(cls: string): R
+  }
+}

+ 15 - 48
test/unit/features/component/component-async.spec.ts

@@ -1,14 +1,5 @@
 import Vue from 'vue'
 
-function wait(): [() => void, Promise<void>] {
-  let done
-  const p = new Promise<void>((resolve, reject) => {
-    done = resolve
-    done.fail = reject
-  })
-  return [done, p]
-}
-
 describe('Component async', () => {
   const oldSetTimeout = setTimeout
   const oldClearTimeout = clearTimeout
@@ -55,7 +46,7 @@ describe('Component async', () => {
     expect(JSON.stringify(timeoutsPending)).toEqual(JSON.stringify({}))
   })
 
-  it('normal', () => {
+  it('normal', (done) => {
     const vm = new Vue({
       template: '<div><test></test></div>',
       components: {
@@ -73,16 +64,14 @@ describe('Component async', () => {
     expect(vm.$el.innerHTML).toBe('<!---->')
     expect(vm.$children.length).toBe(0)
 
-    const [done, p] = wait()
     function next() {
       expect(vm.$el.innerHTML).toBe('<div>hi</div>')
       expect(vm.$children.length).toBe(1)
       done()
     }
-    return p
   })
 
-  it('resolve ES module default', () => {
+  it('resolve ES module default', (done) => {
     const vm = new Vue({
       template: '<div><test></test></div>',
       components: {
@@ -103,16 +92,14 @@ describe('Component async', () => {
     expect(vm.$el.innerHTML).toBe('<!---->')
     expect(vm.$children.length).toBe(0)
 
-    const [done, p] = wait()
     function next() {
       expect(vm.$el.innerHTML).toBe('<div>hi</div>')
       expect(vm.$children.length).toBe(1)
       done()
     }
-    return p
   })
 
-  it('as root', () => {
+  it('as root', (done) => {
     const vm = new Vue({
       template: '<test></test>',
       components: {
@@ -130,17 +117,15 @@ describe('Component async', () => {
     expect(vm.$el.nodeType).toBe(8)
     expect(vm.$children.length).toBe(0)
 
-    const [done, p] = wait()
     function next() {
       expect(vm.$el.nodeType).toBe(1)
       expect(vm.$el.outerHTML).toBe('<div>hi</div>')
       expect(vm.$children.length).toBe(1)
       done()
     }
-    return p
   })
 
-  it('dynamic', () => {
+  it('dynamic', (done) => {
     const vm = new Vue({
       template: '<component :is="view"></component>',
       data: {
@@ -174,7 +159,7 @@ describe('Component async', () => {
       expect(vm.$el.textContent).toBe('A')
       vm.view = 'view-b'
     }
-    const [done, p] = wait()
+
     function step2() {
       expect(vm.$el.tagName).toBe('P')
       expect(vm.$el.textContent).toBe('B')
@@ -185,7 +170,6 @@ describe('Component async', () => {
         expect(vm.$el.textContent).toBe('A')
       }).then(done)
     }
-    return p
   })
 
   it('warn reject', () => {
@@ -200,7 +184,7 @@ describe('Component async', () => {
     expect('Reason: nooooo').toHaveBeenWarned()
   })
 
-  it('with v-for', () => {
+  it('with v-for', (done) => {
     const vm = new Vue({
       template: '<div><test v-for="n in list" :key="n" :n="n"></test></div>',
       data: {
@@ -218,15 +202,14 @@ describe('Component async', () => {
         }
       }
     }).$mount()
-    const [done, p] = wait()
+
     function next() {
       expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
       done()
     }
-    return p
   })
 
-  it('returning Promise', () => {
+  it('returning Promise', (done) => {
     const vm = new Vue({
       template: '<div><test></test></div>',
       components: {
@@ -248,17 +231,15 @@ describe('Component async', () => {
     expect(vm.$el.innerHTML).toBe('<!---->')
     expect(vm.$children.length).toBe(0)
 
-    const [done, p] = wait()
     function next() {
       expect(vm.$el.innerHTML).toBe('<div>hi</div>')
       expect(vm.$children.length).toBe(1)
       done()
     }
-    return p
   })
 
   describe('loading/error/timeout', () => {
-    it('with loading component', () => {
+    it('with loading component', (done) => {
       const vm = new Vue({
         template: `<div><test/></div>`,
         components: {
@@ -288,16 +269,14 @@ describe('Component async', () => {
         })
       }, 1)
 
-      const [done, p] = wait()
       function next() {
         expect(loadingAsserted).toBe(true)
         expect(vm.$el.textContent).toBe('hi')
         done()
       }
-      return p
     })
 
-    it('with loading component (0 delay)', () => {
+    it('with loading component (0 delay)', (done) => {
       const vm = new Vue({
         template: `<div><test/></div>`,
         components: {
@@ -319,15 +298,13 @@ describe('Component async', () => {
 
       expect(vm.$el.textContent).toBe('loading')
 
-      const [done, p] = wait()
       function next() {
         expect(vm.$el.textContent).toBe('hi')
         done()
       }
-      return p
     })
 
-    it('with error component', () => {
+    it('with error component', (done) => {
       const vm = new Vue({
         template: `<div><test/></div>`,
         components: {
@@ -350,16 +327,14 @@ describe('Component async', () => {
 
       expect(vm.$el.textContent).toBe('loading')
 
-      const [done, p] = wait()
       function next() {
         expect(`Failed to resolve async component`).toHaveBeenWarned()
         expect(vm.$el.textContent).toBe('error')
         done()
       }
-      return p
     })
 
-    it('with error component + timeout', () => {
+    it('with error component + timeout', (done) => {
       const vm = new Vue({
         template: `<div><test/></div>`,
         components: {
@@ -390,15 +365,13 @@ describe('Component async', () => {
         })
       }, 1)
 
-      const [done, p] = wait()
       function next() {
         expect(vm.$el.textContent).toBe('error') // late resolve ignored
         done()
       }
-      return p
     })
 
-    it('should not trigger timeout if resolved', () => {
+    it('should not trigger timeout if resolved', (done) => {
       const vm = new Vue({
         template: `<div><test/></div>`,
         components: {
@@ -414,16 +387,14 @@ describe('Component async', () => {
         }
       }).$mount()
 
-      const [done, p] = wait()
       setTimeout(() => {
         expect(vm.$el.textContent).toBe('hi')
         expect(`Failed to resolve async component`).not.toHaveBeenWarned()
         done()
       }, 50)
-      return p
     })
 
-    it('should not have running timeout/loading if resolved', () => {
+    it('should not have running timeout/loading if resolved', (done) => {
       const vm = new Vue({
         template: `<div><test/></div>`,
         components: {
@@ -444,17 +415,15 @@ describe('Component async', () => {
         }
       }).$mount()
 
-      const [done, p] = wait()
       function next() {
         expect(vm.$el.textContent).toBe('hi')
         // the afterEach() will ensure that the timeouts for delay and timeout have been cleared
         done()
       }
-      return p
     })
 
     // #7107
-    it(`should work when resolving sync in sibling component's mounted hook`, () => {
+    it(`should work when resolving sync in sibling component's mounted hook`, (done) => {
       let resolveTwo
 
       const vm = new Vue({
@@ -478,11 +447,9 @@ describe('Component async', () => {
 
       expect(vm.$el.textContent).toBe('one ')
 
-      const [done, p] = wait()
       waitForUpdate(() => {
         expect(vm.$el.textContent).toBe('one two')
       }).then(done)
-      return p
     })
   })
 })

+ 0 - 16
test/unit/global.d.ts

@@ -1,16 +0,0 @@
-declare function waitForUpdate(
-  cb: Function
-): any;
-
-declare function createTextVNode(
-  arg?: any
-): any;
-
-// vitest extends jest namespace so we can just extend jest.Matchers
-declare namespace jest {
-  interface Matchers<R, T> {
-    toHaveBeenWarned(): R
-    toHaveBeenWarnedLast(): R
-    toHaveBeenWarnedTimes(n: number): R
-  }
-}

+ 6 - 185
test/vitest.setup.ts

@@ -1,185 +1,6 @@
-import { SpyInstance } from 'vitest'
-
-expect.extend({
-  toHaveBeenWarned(received: string) {
-    asserted.add(received)
-    const passed = warn.mock.calls.some((args) => args[0].includes(received))
-    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` +
-          (msgs.length
-            ? `.\n\nActual messages:\n\n - ${msgs}`
-            : ` but no warning was recorded.`)
-      }
-    }
-  },
-
-  toHaveBeenWarnedLast(received: string) {
-    asserted.add(received)
-    const passed =
-      warn.mock.calls[warn.mock.calls.length - 1][0].includes(received)
-    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].includes(received)) {
-        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: SpyInstance
-const asserted: Set<string> = new Set()
-
-beforeEach(() => {
-  asserted.clear()
-  warn = vi.spyOn(console, 'error')
-  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.includes(assertedMsg)
-      })
-    })
-  warn.mockRestore()
-  if (nonAssertedWarnings.length) {
-    throw new Error(
-      `test case threw unexpected warnings:\n - ${nonAssertedWarnings.join(
-        '\n - '
-      )}`
-    )
-  }
-})
-
-import Vue from 'vue'
-
-// helper for async assertions.
-// Use like this:
-//
-// vm.a = 123
-// waitForUpdate(() => {
-//   expect(vm.$el.textContent).toBe('123')
-//   vm.a = 234
-// })
-// .then(() => {
-//   // more assertions...
-// })
-// .then(done)
-
-interface Job extends Function {
-  wait?: boolean
-  fail?: (e: any) => void
-}
-
-global.waitForUpdate = (initialCb: Job) => {
-  let end
-  let reject
-  const queue: Job[] = initialCb ? [initialCb] : []
-
-  function shift() {
-    const job = queue.shift()
-    if (queue.length) {
-      let hasError = false
-      try {
-        job!.wait ? job!(shift) : job!()
-      } catch (e) {
-        hasError = true
-        if (reject) {
-          reject()
-        } else {
-          const done = queue[queue.length - 1]
-          if (done && done.fail) {
-            done.fail(e)
-          }
-        }
-      }
-      if (!hasError && !job!.wait) {
-        if (queue.length) {
-          Vue.nextTick(shift)
-        }
-      }
-    } else if (job && (job.fail || job === end)) {
-      job() // done
-    }
-  }
-
-  Vue.nextTick(() => {
-    if (!queue.length || (!end && !queue[queue.length - 1]!.fail)) {
-      throw new Error('waitForUpdate chain is missing .then(done)')
-    }
-    shift()
-  })
-
-  const chainer = {
-    then: (nextCb) => {
-      queue.push(nextCb)
-      return chainer
-    },
-    thenWaitFor: (wait) => {
-      if (typeof wait === 'number') {
-        wait = timeout(wait)
-      }
-      wait.wait = true
-      queue.push(wait)
-      return chainer
-    },
-    end: (endFn) => {
-      queue.push(endFn)
-      end = endFn
-    },
-    promise() {
-      return new Promise((resolve, rej) => {
-        end = resolve
-        reject = rej
-      })
-    }
-  }
-
-  return chainer
-}
-
-function timeout(n) {
-  return (next) => setTimeout(next, n)
-}
+import './helpers/shim-done'
+import './helpers/to-have-warned'
+import './helpers/wait-for-update'
+import './helpers/trigger-event'
+import './helpers/vdom'
+import './helpers/classlist'