props.spec.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. import Vue from 'vue'
  2. describe('Options props', () => {
  3. it('array syntax', done => {
  4. const vm = new Vue({
  5. data: {
  6. b: 'bar'
  7. },
  8. template: '<test v-bind:b="b" v-ref:child></test>',
  9. components: {
  10. test: {
  11. props: ['b'],
  12. template: '<div>{{b}}</div>'
  13. }
  14. }
  15. }).$mount()
  16. expect(vm.$el.innerHTML).toBe('bar')
  17. vm.b = 'baz'
  18. waitForUpdate(() => {
  19. expect(vm.$el.innerHTML).toBe('baz')
  20. vm.$refs.child.b = 'qux'
  21. }).then(() => {
  22. expect(vm.$el.innerHTML).toBe('qux')
  23. }).then(done)
  24. })
  25. it('object syntax', done => {
  26. const vm = new Vue({
  27. data: {
  28. b: 'bar'
  29. },
  30. template: '<test v-bind:b="b" v-ref:child></test>',
  31. components: {
  32. test: {
  33. props: { b: String },
  34. template: '<div>{{b}}</div>'
  35. }
  36. }
  37. }).$mount()
  38. expect(vm.$el.innerHTML).toBe('bar')
  39. vm.b = 'baz'
  40. waitForUpdate(() => {
  41. expect(vm.$el.innerHTML).toBe('baz')
  42. vm.$refs.child.b = 'qux'
  43. }).then(() => {
  44. expect(vm.$el.innerHTML).toBe('qux')
  45. }).then(done)
  46. })
  47. it('warn mixed syntax', () => {
  48. new Vue({
  49. props: [{ b: String }]
  50. })
  51. expect('props must be strings when using array syntax').toHaveBeenWarned()
  52. })
  53. it('default values', () => {
  54. const vm = new Vue({
  55. data: {
  56. b: undefined
  57. },
  58. template: '<test :b="b"></test>',
  59. components: {
  60. test: {
  61. props: {
  62. a: {
  63. default: 'A' // absent
  64. },
  65. b: {
  66. default: 'B' // undefined
  67. }
  68. },
  69. template: '<div>{{a}}{{b}}</div>'
  70. }
  71. }
  72. }).$mount()
  73. expect(vm.$el.textContent).toBe('AB')
  74. })
  75. it('default value reactivity', done => {
  76. const vm = new Vue({
  77. props: {
  78. a: {
  79. default: () => ({ b: 1 })
  80. }
  81. },
  82. propsData: {
  83. a: undefined
  84. },
  85. template: '<div>{{ a.b }}</div>'
  86. }).$mount()
  87. expect(vm.$el.textContent).toBe('1')
  88. vm.a.b = 2
  89. waitForUpdate(() => {
  90. expect(vm.$el.textContent).toBe('2')
  91. }).then(done)
  92. })
  93. it('warn object/array default values', () => {
  94. new Vue({
  95. props: {
  96. a: {
  97. default: { b: 1 }
  98. }
  99. },
  100. propsData: {
  101. a: undefined
  102. }
  103. })
  104. expect('Props with type Object/Array must use a factory function').toHaveBeenWarned()
  105. })
  106. it('warn missing required', () => {
  107. new Vue({
  108. template: '<test></test>',
  109. components: {
  110. test: {
  111. props: { a: { required: true }},
  112. template: '<div>{{a}}</div>'
  113. }
  114. }
  115. }).$mount()
  116. expect('Missing required prop: "a"').toHaveBeenWarned()
  117. })
  118. describe('assertions', () => {
  119. function makeInstance (value, type, validator, required) {
  120. return new Vue({
  121. template: '<test :test="val"></test>',
  122. data: {
  123. val: value
  124. },
  125. components: {
  126. test: {
  127. template: '<div></div>',
  128. props: {
  129. test: {
  130. type,
  131. validator,
  132. required
  133. }
  134. }
  135. }
  136. }
  137. }).$mount()
  138. }
  139. it('string', () => {
  140. makeInstance('hello', String)
  141. expect(console.error.calls.count()).toBe(0)
  142. makeInstance(123, String)
  143. expect('Expected String').toHaveBeenWarned()
  144. })
  145. it('number', () => {
  146. makeInstance(123, Number)
  147. expect(console.error.calls.count()).toBe(0)
  148. makeInstance('123', Number)
  149. expect('Expected Number').toHaveBeenWarned()
  150. })
  151. it('boolean', () => {
  152. makeInstance(true, Boolean)
  153. expect(console.error.calls.count()).toBe(0)
  154. makeInstance('123', Boolean)
  155. expect('Expected Boolean').toHaveBeenWarned()
  156. })
  157. it('function', () => {
  158. makeInstance(() => {}, Function)
  159. expect(console.error.calls.count()).toBe(0)
  160. makeInstance(123, Function)
  161. expect('Expected Function').toHaveBeenWarned()
  162. })
  163. it('object', () => {
  164. makeInstance({}, Object)
  165. expect(console.error.calls.count()).toBe(0)
  166. makeInstance([], Object)
  167. expect('Expected Object').toHaveBeenWarned()
  168. })
  169. it('array', () => {
  170. makeInstance([], Array)
  171. expect(console.error.calls.count()).toBe(0)
  172. makeInstance({}, Array)
  173. expect('Expected Array').toHaveBeenWarned()
  174. })
  175. it('custom constructor', () => {
  176. function Class () {}
  177. makeInstance(new Class(), Class)
  178. expect(console.error.calls.count()).toBe(0)
  179. makeInstance({}, Class)
  180. expect('type check failed').toHaveBeenWarned()
  181. })
  182. it('multiple types', () => {
  183. makeInstance([], [Array, Number, Boolean])
  184. expect(console.error.calls.count()).toBe(0)
  185. makeInstance({}, [Array, Number, Boolean])
  186. expect('Expected Array, Number, Boolean, got Object').toHaveBeenWarned()
  187. })
  188. it('custom validator', () => {
  189. makeInstance(123, null, v => v === 123)
  190. expect(console.error.calls.count()).toBe(0)
  191. makeInstance(123, null, v => v === 234)
  192. expect('custom validator check failed').toHaveBeenWarned()
  193. })
  194. it('type check + custom validator', () => {
  195. makeInstance(123, Number, v => v === 123)
  196. expect(console.error.calls.count()).toBe(0)
  197. makeInstance(123, Number, v => v === 234)
  198. expect('custom validator check failed').toHaveBeenWarned()
  199. makeInstance(123, String, v => v === 123)
  200. expect('Expected String').toHaveBeenWarned()
  201. })
  202. it('multiple types + custom validator', () => {
  203. makeInstance(123, [Number, String, Boolean], v => v === 123)
  204. expect(console.error.calls.count()).toBe(0)
  205. makeInstance(123, [Number, String, Boolean], v => v === 234)
  206. expect('custom validator check failed').toHaveBeenWarned()
  207. makeInstance(123, [String, Boolean], v => v === 123)
  208. expect('Expected String, Boolean').toHaveBeenWarned()
  209. })
  210. it('optional with type + null/undefined', () => {
  211. makeInstance(undefined, String)
  212. expect(console.error.calls.count()).toBe(0)
  213. makeInstance(null, String)
  214. expect(console.error.calls.count()).toBe(0)
  215. })
  216. it('required with type + null/undefined', () => {
  217. makeInstance(undefined, String, null, true)
  218. expect(console.error.calls.count()).toBe(1)
  219. expect('Expected String').toHaveBeenWarned()
  220. makeInstance(null, Boolean, null, true)
  221. expect(console.error.calls.count()).toBe(2)
  222. expect('Expected Boolean').toHaveBeenWarned()
  223. })
  224. })
  225. it('should warn data fields already defined as a prop', () => {
  226. new Vue({
  227. template: '<test a="1"></test>',
  228. components: {
  229. test: {
  230. template: '<div></div>',
  231. data: function () {
  232. return { a: 123 }
  233. },
  234. props: {
  235. a: null
  236. }
  237. }
  238. }
  239. }).$mount()
  240. expect('already declared as a prop').toHaveBeenWarned()
  241. })
  242. it('treat boolean props properly', () => {
  243. const vm = new Vue({
  244. template: '<comp v-ref:child prop-a prop-b="prop-b"></comp>',
  245. components: {
  246. comp: {
  247. template: '<div></div>',
  248. props: {
  249. propA: Boolean,
  250. propB: Boolean,
  251. propC: Boolean
  252. }
  253. }
  254. }
  255. }).$mount()
  256. expect(vm.$refs.child.propA).toBe(true)
  257. expect(vm.$refs.child.propB).toBe(true)
  258. expect(vm.$refs.child.propC).toBe(false)
  259. })
  260. it('should respect default value of a Boolean prop', function () {
  261. const vm = new Vue({
  262. template: '<test></test>',
  263. components: {
  264. test: {
  265. props: {
  266. prop: {
  267. type: Boolean,
  268. default: true
  269. }
  270. },
  271. template: '<div>{{prop}}</div>'
  272. }
  273. }
  274. }).$mount()
  275. expect(vm.$el.textContent).toBe('true')
  276. })
  277. it('non reactive values passed down as prop should not be converted', done => {
  278. const a = Object.freeze({
  279. nested: {
  280. msg: 'hello'
  281. }
  282. })
  283. const parent = new Vue({
  284. template: '<comp :a="a.nested"></comp>',
  285. data: {
  286. a: a
  287. },
  288. components: {
  289. comp: {
  290. template: '<div></div>',
  291. props: ['a']
  292. }
  293. }
  294. }).$mount()
  295. const child = parent.$children[0]
  296. expect(child.a.msg).toBe('hello')
  297. expect(child.a.__ob__).toBeUndefined() // should not be converted
  298. parent.a = Object.freeze({
  299. nested: {
  300. msg: 'yo'
  301. }
  302. })
  303. waitForUpdate(() => {
  304. expect(child.a.msg).toBe('yo')
  305. expect(child.a.__ob__).toBeUndefined()
  306. }).then(done)
  307. })
  308. it('should not warn for non-required, absent prop', function () {
  309. new Vue({
  310. template: '<test></test>',
  311. components: {
  312. test: {
  313. template: '<div></div>',
  314. props: {
  315. prop: {
  316. type: String
  317. }
  318. }
  319. }
  320. }
  321. }).$mount()
  322. expect(console.error.calls.count()).toBe(0)
  323. })
  324. })