error-handling.spec.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import Vue from 'vue'
  2. const components = createErrorTestComponents()
  3. describe('Error handling', () => {
  4. // hooks that prevents the component from rendering, but should not
  5. // break parent component
  6. ;[
  7. ['render', 'render function'],
  8. ['beforeCreate', 'beforeCreate hook'],
  9. ['created', 'created hook'],
  10. ['beforeMount', 'beforeMount hook']
  11. ].forEach(([type, description]) => {
  12. it(`should recover from errors in ${type}`, done => {
  13. const vm = createTestInstance(components[type])
  14. expect(`Error in ${description}`).toHaveBeenWarned()
  15. expect(`Error: ${type}`).toHaveBeenWarned()
  16. assertRootInstanceActive(vm).then(done)
  17. })
  18. })
  19. // error in mounted hook should affect neither child nor parent
  20. it('should recover from errors in mounted hook', done => {
  21. const vm = createTestInstance(components.mounted)
  22. expect(`Error in mounted hook`).toHaveBeenWarned()
  23. expect(`Error: mounted`).toHaveBeenWarned()
  24. assertBothInstancesActive(vm).then(done)
  25. })
  26. // error in beforeUpdate/updated should affect neither child nor parent
  27. ;[
  28. ['beforeUpdate', 'beforeUpdate hook'],
  29. ['updated', 'updated hook']
  30. ].forEach(([type, description]) => {
  31. it(`should recover from errors in ${type} hook`, done => {
  32. const vm = createTestInstance(components[type])
  33. assertBothInstancesActive(vm).then(() => {
  34. expect(`Error in ${description}`).toHaveBeenWarned()
  35. expect(`Error: ${type}`).toHaveBeenWarned()
  36. }).then(done)
  37. })
  38. })
  39. ;[
  40. ['beforeDestroy', 'beforeDestroy hook'],
  41. ['destroyed', 'destroyed hook']
  42. ].forEach(([type, description]) => {
  43. it(`should recover from errors in ${type} hook`, done => {
  44. const vm = createTestInstance(components[type])
  45. vm.ok = false
  46. waitForUpdate(() => {
  47. expect(`Error in ${description}`).toHaveBeenWarned()
  48. expect(`Error: ${type}`).toHaveBeenWarned()
  49. }).thenWaitFor(next => {
  50. assertRootInstanceActive(vm).end(next)
  51. }).then(done)
  52. })
  53. })
  54. it('should recover from errors in user watcher getter', done => {
  55. const vm = createTestInstance(components.userWatcherGetter)
  56. vm.n++
  57. waitForUpdate(() => {
  58. expect(`Error in getter for watcher`).toHaveBeenWarned()
  59. function getErrorMsg () {
  60. try {
  61. this.a.b.c
  62. } catch (e) {
  63. return e.toString()
  64. }
  65. }
  66. const msg = getErrorMsg.call(vm)
  67. expect(msg).toHaveBeenWarned()
  68. }).thenWaitFor(next => {
  69. assertBothInstancesActive(vm).end(next)
  70. }).then(done)
  71. })
  72. it('should recover from errors in user watcher callback', done => {
  73. const vm = createTestInstance(components.userWatcherCallback)
  74. vm.n++
  75. waitForUpdate(() => {
  76. expect(`Error in callback for watcher "n"`).toHaveBeenWarned()
  77. expect(`Error: userWatcherCallback`).toHaveBeenWarned()
  78. }).thenWaitFor(next => {
  79. assertBothInstancesActive(vm).end(next)
  80. }).then(done)
  81. })
  82. it('config.errorHandler should capture errors', done => {
  83. const spy = Vue.config.errorHandler = jasmine.createSpy('errorHandler')
  84. const vm = createTestInstance(components.render)
  85. const args = spy.calls.argsFor(0)
  86. expect(args[0].toString()).toContain('Error: render') // error
  87. expect(args[1]).toBe(vm.$refs.child) // vm
  88. expect(args[2]).toContain('render function') // description
  89. assertRootInstanceActive(vm).then(() => {
  90. Vue.config.errorHandler = null
  91. }).then(done)
  92. })
  93. it('properly format component names', () => {
  94. const format = Vue.util.formatComponentName
  95. const vm = new Vue()
  96. expect(format(vm)).toBe('<Root>')
  97. vm.$root = null
  98. vm.$options.name = 'hello-there'
  99. expect(format(vm)).toBe('<HelloThere>')
  100. vm.$options.name = null
  101. vm.$options._componentTag = 'foo-bar-1'
  102. expect(format(vm)).toBe('<FooBar1>')
  103. vm.$options._componentTag = null
  104. vm.$options.__file = '/foo/bar/baz/SomeThing.vue'
  105. expect(format(vm)).toBe(`<SomeThing> at ${vm.$options.__file}`)
  106. expect(format(vm, false)).toBe('<SomeThing>')
  107. vm.$options.__file = 'C:\\foo\\bar\\baz\\windows_file.vue'
  108. expect(format(vm)).toBe(`<WindowsFile> at ${vm.$options.__file}`)
  109. expect(format(vm, false)).toBe('<WindowsFile>')
  110. })
  111. })
  112. function createErrorTestComponents () {
  113. const components = {}
  114. // render error
  115. components.render = {
  116. render (h) {
  117. throw new Error('render')
  118. }
  119. }
  120. // lifecycle errors
  121. ;['create', 'mount', 'update', 'destroy'].forEach(hook => {
  122. // before
  123. const before = 'before' + hook.charAt(0).toUpperCase() + hook.slice(1)
  124. const beforeComp = components[before] = {
  125. props: ['n'],
  126. render (h) {
  127. return h('div', this.n)
  128. }
  129. }
  130. beforeComp[before] = function () {
  131. throw new Error(before)
  132. }
  133. // after
  134. const after = hook.replace(/e?$/, 'ed')
  135. const afterComp = components[after] = {
  136. props: ['n'],
  137. render (h) {
  138. return h('div', this.n)
  139. }
  140. }
  141. afterComp[after] = function () {
  142. throw new Error(after)
  143. }
  144. })
  145. // user watcher
  146. components.userWatcherGetter = {
  147. props: ['n'],
  148. created () {
  149. this.$watch(function () {
  150. return this.n + this.a.b.c
  151. }, val => {
  152. console.log('user watcher fired: ' + val)
  153. })
  154. },
  155. render (h) {
  156. return h('div', this.n)
  157. }
  158. }
  159. components.userWatcherCallback = {
  160. props: ['n'],
  161. watch: {
  162. n () {
  163. throw new Error('userWatcherCallback error')
  164. }
  165. },
  166. render (h) {
  167. return h('div', this.n)
  168. }
  169. }
  170. return components
  171. }
  172. function createTestInstance (Comp) {
  173. return new Vue({
  174. data: {
  175. n: 0,
  176. ok: true
  177. },
  178. render (h) {
  179. return h('div', [
  180. 'n:' + this.n + '\n',
  181. this.ok
  182. ? h(Comp, { ref: 'child', props: { n: this.n }})
  183. : null
  184. ])
  185. }
  186. }).$mount()
  187. }
  188. function assertRootInstanceActive (vm, chain) {
  189. expect(vm.$el.innerHTML).toContain('n:0\n')
  190. vm.n++
  191. return waitForUpdate(() => {
  192. expect(vm.$el.innerHTML).toContain('n:1\n')
  193. })
  194. }
  195. function assertBothInstancesActive (vm) {
  196. vm.n = 0
  197. return waitForUpdate(() => {
  198. expect(vm.$refs.child.$el.innerHTML).toContain('0')
  199. }).thenWaitFor(next => {
  200. assertRootInstanceActive(vm).then(() => {
  201. expect(vm.$refs.child.$el.innerHTML).toContain('1')
  202. }).end(next)
  203. })
  204. }