componentEmits.spec.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Note: emits and listener fallthrough is tested in
  2. // ./rendererAttrsFallthrough.spec.ts.
  3. import { render, defineComponent, h, nodeOps } from '@vue/runtime-test'
  4. import { isEmitListener } from '../src/componentEmits'
  5. describe('component: emit', () => {
  6. test('trigger handlers', () => {
  7. const Foo = defineComponent({
  8. render() {},
  9. created() {
  10. // the `emit` function is bound on component instances
  11. this.$emit('foo')
  12. this.$emit('bar')
  13. this.$emit('!baz')
  14. }
  15. })
  16. const onfoo = jest.fn()
  17. const onBar = jest.fn()
  18. const onBaz = jest.fn()
  19. const Comp = () => h(Foo, { onfoo, onBar, ['on!baz']: onBaz })
  20. render(h(Comp), nodeOps.createElement('div'))
  21. expect(onfoo).not.toHaveBeenCalled()
  22. // only capitalized or special chars are considered event listeners
  23. expect(onBar).toHaveBeenCalled()
  24. expect(onBaz).toHaveBeenCalled()
  25. })
  26. test('trigger camelize event', () => {
  27. const Foo = defineComponent({
  28. render() {},
  29. created() {
  30. this.$emit('test-event')
  31. }
  32. })
  33. const fooSpy = jest.fn()
  34. const Comp = () =>
  35. h(Foo, {
  36. onTestEvent: fooSpy
  37. })
  38. render(h(Comp), nodeOps.createElement('div'))
  39. expect(fooSpy).toHaveBeenCalled()
  40. })
  41. // for v-model:foo-bar usage in DOM templates
  42. test('trigger hyphenated events for update:xxx events', () => {
  43. const Foo = defineComponent({
  44. render() {},
  45. created() {
  46. this.$emit('update:fooProp')
  47. this.$emit('update:barProp')
  48. }
  49. })
  50. const fooSpy = jest.fn()
  51. const barSpy = jest.fn()
  52. const Comp = () =>
  53. h(Foo, {
  54. 'onUpdate:fooProp': fooSpy,
  55. 'onUpdate:bar-prop': barSpy
  56. })
  57. render(h(Comp), nodeOps.createElement('div'))
  58. expect(fooSpy).toHaveBeenCalled()
  59. expect(barSpy).toHaveBeenCalled()
  60. })
  61. test('should trigger array of listeners', async () => {
  62. const Child = defineComponent({
  63. setup(_, { emit }) {
  64. emit('foo', 1)
  65. return () => h('div')
  66. }
  67. })
  68. const fn1 = jest.fn()
  69. const fn2 = jest.fn()
  70. const App = {
  71. setup() {
  72. return () =>
  73. h(Child, {
  74. onFoo: [fn1, fn2]
  75. })
  76. }
  77. }
  78. render(h(App), nodeOps.createElement('div'))
  79. expect(fn1).toHaveBeenCalledTimes(1)
  80. expect(fn1).toHaveBeenCalledWith(1)
  81. expect(fn2).toHaveBeenCalledTimes(1)
  82. expect(fn1).toHaveBeenCalledWith(1)
  83. })
  84. test('warning for undeclared event (array)', () => {
  85. const Foo = defineComponent({
  86. emits: ['foo'],
  87. render() {},
  88. created() {
  89. // @ts-ignore
  90. this.$emit('bar')
  91. }
  92. })
  93. render(h(Foo), nodeOps.createElement('div'))
  94. expect(
  95. `Component emitted event "bar" but it is neither declared`
  96. ).toHaveBeenWarned()
  97. })
  98. test('warning for undeclared event (object)', () => {
  99. const Foo = defineComponent({
  100. emits: {
  101. foo: null
  102. },
  103. render() {},
  104. created() {
  105. // @ts-ignore
  106. this.$emit('bar')
  107. }
  108. })
  109. render(h(Foo), nodeOps.createElement('div'))
  110. expect(
  111. `Component emitted event "bar" but it is neither declared`
  112. ).toHaveBeenWarned()
  113. })
  114. test('should not warn if has equivalent onXXX prop', () => {
  115. const Foo = defineComponent({
  116. props: ['onFoo'],
  117. emits: [],
  118. render() {},
  119. created() {
  120. // @ts-ignore
  121. this.$emit('foo')
  122. }
  123. })
  124. render(h(Foo), nodeOps.createElement('div'))
  125. expect(
  126. `Component emitted event "foo" but it is neither declared`
  127. ).not.toHaveBeenWarned()
  128. })
  129. test('validator warning', () => {
  130. const Foo = defineComponent({
  131. emits: {
  132. foo: (arg: number) => arg > 0
  133. },
  134. render() {},
  135. created() {
  136. this.$emit('foo', -1)
  137. }
  138. })
  139. render(h(Foo), nodeOps.createElement('div'))
  140. expect(`event validation failed for event "foo"`).toHaveBeenWarned()
  141. })
  142. test('merging from mixins', () => {
  143. const mixin = {
  144. emits: {
  145. foo: (arg: number) => arg > 0
  146. }
  147. }
  148. const Foo = defineComponent({
  149. mixins: [mixin],
  150. render() {},
  151. created() {
  152. this.$emit('foo', -1)
  153. }
  154. })
  155. render(h(Foo), nodeOps.createElement('div'))
  156. expect(`event validation failed for event "foo"`).toHaveBeenWarned()
  157. })
  158. test('.once', () => {
  159. const Foo = defineComponent({
  160. render() {},
  161. emits: {
  162. foo: null
  163. },
  164. created() {
  165. this.$emit('foo')
  166. this.$emit('foo')
  167. }
  168. })
  169. const fn = jest.fn()
  170. render(
  171. h(Foo, {
  172. onFooOnce: fn
  173. }),
  174. nodeOps.createElement('div')
  175. )
  176. expect(fn).toHaveBeenCalledTimes(1)
  177. })
  178. test('.once with normal listener of the same name', () => {
  179. const Foo = defineComponent({
  180. render() {},
  181. emits: {
  182. foo: null
  183. },
  184. created() {
  185. this.$emit('foo')
  186. this.$emit('foo')
  187. }
  188. })
  189. const onFoo = jest.fn()
  190. const onFooOnce = jest.fn()
  191. render(
  192. h(Foo, {
  193. onFoo,
  194. onFooOnce
  195. }),
  196. nodeOps.createElement('div')
  197. )
  198. expect(onFoo).toHaveBeenCalledTimes(2)
  199. expect(onFooOnce).toHaveBeenCalledTimes(1)
  200. })
  201. test('.number modifier should work with v-model on component', () => {
  202. const Foo = defineComponent({
  203. render() {},
  204. created() {
  205. this.$emit('update:modelValue', '1')
  206. this.$emit('update:foo', '2')
  207. }
  208. })
  209. const fn1 = jest.fn()
  210. const fn2 = jest.fn()
  211. const Comp = () =>
  212. h(Foo, {
  213. modelValue: null,
  214. modelModifiers: { number: true },
  215. 'onUpdate:modelValue': fn1,
  216. foo: null,
  217. fooModifiers: { number: true },
  218. 'onUpdate:foo': fn2
  219. })
  220. render(h(Comp), nodeOps.createElement('div'))
  221. expect(fn1).toHaveBeenCalledTimes(1)
  222. expect(fn1).toHaveBeenCalledWith(1)
  223. expect(fn2).toHaveBeenCalledTimes(1)
  224. expect(fn2).toHaveBeenCalledWith(2)
  225. })
  226. test('.trim modifier should work with v-model on component', () => {
  227. const Foo = defineComponent({
  228. render() {},
  229. created() {
  230. this.$emit('update:modelValue', ' one ')
  231. this.$emit('update:foo', ' two ')
  232. }
  233. })
  234. const fn1 = jest.fn()
  235. const fn2 = jest.fn()
  236. const Comp = () =>
  237. h(Foo, {
  238. modelValue: null,
  239. modelModifiers: { trim: true },
  240. 'onUpdate:modelValue': fn1,
  241. foo: null,
  242. fooModifiers: { trim: true },
  243. 'onUpdate:foo': fn2
  244. })
  245. render(h(Comp), nodeOps.createElement('div'))
  246. expect(fn1).toHaveBeenCalledTimes(1)
  247. expect(fn1).toHaveBeenCalledWith('one')
  248. expect(fn2).toHaveBeenCalledTimes(1)
  249. expect(fn2).toHaveBeenCalledWith('two')
  250. })
  251. test('isEmitListener', () => {
  252. const options = {
  253. click: null,
  254. 'test-event': null,
  255. fooBar: null,
  256. FooBaz: null
  257. }
  258. expect(isEmitListener(options, 'onClick')).toBe(true)
  259. expect(isEmitListener(options, 'onclick')).toBe(false)
  260. expect(isEmitListener(options, 'onBlick')).toBe(false)
  261. // .once listeners
  262. expect(isEmitListener(options, 'onClickOnce')).toBe(true)
  263. expect(isEmitListener(options, 'onclickOnce')).toBe(false)
  264. // kebab-case option
  265. expect(isEmitListener(options, 'onTestEvent')).toBe(true)
  266. // camelCase option
  267. expect(isEmitListener(options, 'onFooBar')).toBe(true)
  268. // PascalCase option
  269. expect(isEmitListener(options, 'onFooBaz')).toBe(true)
  270. })
  271. })