props.spec.js 9.0 KB

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