prop_spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. var _ = require('../../../../src/util')
  2. var Vue = require('../../../../src/vue')
  3. if (_.inBrowser) {
  4. describe('prop', function () {
  5. var el
  6. beforeEach(function () {
  7. el = document.createElement('div')
  8. spyOn(_, 'warn')
  9. })
  10. it('one way binding', function (done) {
  11. var vm = new Vue({
  12. el: el,
  13. data: {
  14. b: 'B'
  15. },
  16. template: '<test b="{{b}}" v-ref="child"></test>',
  17. components: {
  18. test: {
  19. props: ['b'],
  20. template: '{{b}}'
  21. }
  22. }
  23. })
  24. expect(el.innerHTML).toBe('<test>B</test>')
  25. vm.b = 'BB'
  26. _.nextTick(function () {
  27. expect(el.innerHTML).toBe('<test>BB</test>')
  28. vm.$.child.b = 'BBB'
  29. expect(vm.b).toBe('BB')
  30. _.nextTick(function () {
  31. expect(el.innerHTML).toBe('<test>BBB</test>')
  32. done()
  33. })
  34. })
  35. })
  36. it('two-way binding', function (done) {
  37. var vm = new Vue({
  38. el: el,
  39. data: {
  40. b: 'B',
  41. test: {
  42. a: 'A'
  43. }
  44. },
  45. template: '<test testt="{{@test}}" bb="{{@b}}" a="{{@ test.a }}" v-ref="child"></test>',
  46. components: {
  47. test: {
  48. props: ['testt', 'bb', 'a'],
  49. template: '{{testt.a}} {{bb}} {{a}}'
  50. }
  51. }
  52. })
  53. expect(el.firstChild.textContent).toBe('A B A')
  54. vm.test.a = 'AA'
  55. vm.b = 'BB'
  56. _.nextTick(function () {
  57. expect(el.firstChild.textContent).toBe('AA BB AA')
  58. vm.test = { a: 'AAA' }
  59. _.nextTick(function () {
  60. expect(el.firstChild.textContent).toBe('AAA BB AAA')
  61. vm.$data = {
  62. b: 'BBB',
  63. test: {
  64. a: 'AAAA'
  65. }
  66. }
  67. _.nextTick(function () {
  68. expect(el.firstChild.textContent).toBe('AAAA BBB AAAA')
  69. // test two-way
  70. vm.$.child.bb = 'B'
  71. vm.$.child.testt = { a: 'A' }
  72. _.nextTick(function () {
  73. expect(el.firstChild.textContent).toBe('A B A')
  74. expect(vm.test.a).toBe('A')
  75. expect(vm.test).toBe(vm.$.child.testt)
  76. expect(vm.b).toBe('B')
  77. vm.$.child.a = 'Oops'
  78. _.nextTick(function () {
  79. expect(el.firstChild.textContent).toBe('Oops B Oops')
  80. expect(vm.test.a).toBe('Oops')
  81. done()
  82. })
  83. })
  84. })
  85. })
  86. })
  87. })
  88. it('$data as prop', function (done) {
  89. var vm = new Vue({
  90. el: el,
  91. template: '<test $data="{{ok}}"></test>',
  92. data: {
  93. ok: {
  94. msg: 'hihi'
  95. }
  96. },
  97. components: {
  98. test: {
  99. props: ['$data'],
  100. template: '{{msg}}'
  101. }
  102. }
  103. })
  104. expect(el.innerHTML).toBe('<test>hihi</test>')
  105. vm.ok = { msg: 'what' }
  106. _.nextTick(function () {
  107. expect(el.innerHTML).toBe('<test>what</test>')
  108. done()
  109. })
  110. })
  111. it('explicit one time binding', function (done) {
  112. var vm = new Vue({
  113. el: el,
  114. data: {
  115. b: 'B'
  116. },
  117. template: '<test b="{{*b}}" v-ref="child"></test>',
  118. components: {
  119. test: {
  120. props: ['b'],
  121. template: '{{b}}'
  122. }
  123. }
  124. })
  125. expect(el.innerHTML).toBe('<test>B</test>')
  126. vm.b = 'BB'
  127. _.nextTick(function () {
  128. expect(el.innerHTML).toBe('<test>B</test>')
  129. done()
  130. })
  131. })
  132. it('non-settable parent path', function (done) {
  133. var vm = new Vue({
  134. el: el,
  135. data: {
  136. b: 'B'
  137. },
  138. template: '<test b="{{@ b + \'B\' }}" v-ref="child"></test>',
  139. components: {
  140. test: {
  141. props: ['b'],
  142. template: '{{b}}'
  143. }
  144. }
  145. })
  146. expect(hasWarned(_, 'Cannot bind two-way prop with non-settable parent path')).toBe(true)
  147. expect(el.innerHTML).toBe('<test>BB</test>')
  148. vm.b = 'BB'
  149. _.nextTick(function () {
  150. expect(el.innerHTML).toBe('<test>BBB</test>')
  151. vm.$.child.b = 'hahaha'
  152. _.nextTick(function () {
  153. expect(vm.b).toBe('BB')
  154. expect(el.innerHTML).toBe('<test>hahaha</test>')
  155. done()
  156. })
  157. })
  158. })
  159. it('warn invalid keys', function () {
  160. new Vue({
  161. el: el,
  162. template: '<test a.b.c="{{test}}"></test>',
  163. components: {
  164. test: {
  165. props: ['a.b.c']
  166. }
  167. }
  168. })
  169. expect(hasWarned(_, 'Invalid prop key')).toBe(true)
  170. })
  171. it('warn props with no el option', function () {
  172. new Vue({
  173. props: ['a']
  174. })
  175. expect(hasWarned(_, 'Props will not be compiled if no `el`')).toBe(true)
  176. })
  177. it('warn object/array default values', function () {
  178. new Vue({
  179. el: el,
  180. props: {
  181. arr: {
  182. type: Array,
  183. default: []
  184. },
  185. obj: {
  186. type: Object,
  187. default: {}
  188. }
  189. }
  190. })
  191. expect(hasWarned(_, 'Use a factory function to return the default value')).toBe(true)
  192. expect(_.warn.calls.count()).toBe(2)
  193. })
  194. it('teardown', function (done) {
  195. var vm = new Vue({
  196. el: el,
  197. data: {
  198. a: 'A',
  199. b: 'B'
  200. },
  201. template: '<test aa="{{@a}}" bb="{{b}}"></test>',
  202. components: {
  203. test: {
  204. props: ['aa', 'bb'],
  205. template: '{{aa}} {{bb}}'
  206. }
  207. }
  208. })
  209. var child = vm.$children[0]
  210. expect(el.firstChild.textContent).toBe('A B')
  211. child.aa = 'AA'
  212. vm.b = 'BB'
  213. _.nextTick(function () {
  214. expect(el.firstChild.textContent).toBe('AA BB')
  215. expect(vm.a).toBe('AA')
  216. // unbind the two props
  217. child._directives[0].unbind()
  218. child._directives[1].unbind()
  219. child.aa = 'AAA'
  220. vm.b = 'BBB'
  221. _.nextTick(function () {
  222. expect(el.firstChild.textContent).toBe('AAA BB')
  223. expect(vm.a).toBe('AA')
  224. done()
  225. })
  226. })
  227. })
  228. it('block instance with replace:true', function () {
  229. new Vue({
  230. el: el,
  231. template: '<test b="{{a}}" c="{{d}}"></test>',
  232. data: {
  233. a: 'AAA',
  234. d: 'DDD'
  235. },
  236. components: {
  237. test: {
  238. props: ['b', 'c'],
  239. template: '<p>{{b}}</p><p>{{c}}</p>',
  240. replace: true
  241. }
  242. }
  243. })
  244. expect(el.innerHTML).toBe('<p>AAA</p><p>DDD</p>')
  245. })
  246. describe('assertions', function () {
  247. function makeInstance (value, type, validator) {
  248. return new Vue({
  249. el: document.createElement('div'),
  250. template: '<test prop="{{val}}"></test>',
  251. data: {
  252. val: value
  253. },
  254. components: {
  255. test: {
  256. props: [
  257. {
  258. name: 'prop',
  259. type: type,
  260. validator: validator
  261. }
  262. ]
  263. }
  264. }
  265. })
  266. }
  267. it('string', function () {
  268. makeInstance('hello', String)
  269. expect(_.warn).not.toHaveBeenCalled()
  270. makeInstance(123, String)
  271. expect(hasWarned(_, 'Expected String')).toBe(true)
  272. })
  273. it('number', function () {
  274. makeInstance(123, Number)
  275. expect(_.warn).not.toHaveBeenCalled()
  276. makeInstance('123', Number)
  277. expect(hasWarned(_, 'Expected Number')).toBe(true)
  278. })
  279. it('boolean', function () {
  280. makeInstance(true, Boolean)
  281. expect(_.warn).not.toHaveBeenCalled()
  282. makeInstance('123', Boolean)
  283. expect(hasWarned(_, 'Expected Boolean')).toBe(true)
  284. })
  285. it('function', function () {
  286. makeInstance(function () {}, Function)
  287. expect(_.warn).not.toHaveBeenCalled()
  288. makeInstance(123, Function)
  289. expect(hasWarned(_, 'Expected Function')).toBe(true)
  290. })
  291. it('object', function () {
  292. makeInstance({}, Object)
  293. expect(_.warn).not.toHaveBeenCalled()
  294. makeInstance([], Object)
  295. expect(hasWarned(_, 'Expected Object')).toBe(true)
  296. })
  297. it('array', function () {
  298. makeInstance([], Array)
  299. expect(_.warn).not.toHaveBeenCalled()
  300. makeInstance({}, Array)
  301. expect(hasWarned(_, 'Expected Array')).toBe(true)
  302. })
  303. it('custom constructor', function () {
  304. function Class () {}
  305. makeInstance(new Class(), Class)
  306. expect(_.warn).not.toHaveBeenCalled()
  307. makeInstance({}, Class)
  308. expect(hasWarned(_, 'Expected custom type')).toBe(true)
  309. })
  310. it('custom validator', function () {
  311. makeInstance(123, null, function (v) {
  312. return v === 123
  313. })
  314. expect(_.warn).not.toHaveBeenCalled()
  315. makeInstance(123, null, function (v) {
  316. return v === 234
  317. })
  318. expect(hasWarned(_, 'custom validator check failed')).toBe(true)
  319. })
  320. it('type check + custom validator', function () {
  321. makeInstance(123, Number, function (v) {
  322. return v === 123
  323. })
  324. expect(_.warn).not.toHaveBeenCalled()
  325. makeInstance(123, Number, function (v) {
  326. return v === 234
  327. })
  328. expect(hasWarned(_, 'custom validator check failed')).toBe(true)
  329. makeInstance(123, String, function (v) {
  330. return v === 123
  331. })
  332. expect(hasWarned(_, 'Expected String')).toBe(true)
  333. })
  334. it('required', function () {
  335. new Vue({
  336. el: document.createElement('div'),
  337. template: '<test></test>',
  338. components: {
  339. test: {
  340. props: [
  341. {
  342. name: 'prop',
  343. required: true
  344. }
  345. ]
  346. }
  347. }
  348. })
  349. expect(hasWarned(_, 'Missing required prop')).toBe(true)
  350. })
  351. })
  352. it('alternative syntax', function () {
  353. new Vue({
  354. el: el,
  355. template: '<test b="{{a}}" c="{{d}}"></test>',
  356. data: {
  357. a: 'AAA',
  358. d: 'DDD'
  359. },
  360. components: {
  361. test: {
  362. props: {
  363. b: String,
  364. c: {
  365. type: Number
  366. },
  367. d: {
  368. required: true
  369. }
  370. },
  371. template: '<p>{{b}}</p><p>{{c}}</p>'
  372. }
  373. }
  374. })
  375. expect(hasWarned(_, 'Missing required prop')).toBe(true)
  376. expect(hasWarned(_, 'Expected Number')).toBe(true)
  377. expect(el.textContent).toBe('AAA')
  378. })
  379. it('should not overwrite inherit:true properties', function () {
  380. var vm = new Vue({
  381. el: el,
  382. data: {
  383. msg: 'hi!'
  384. },
  385. template: '<test msg="ho!"></test>',
  386. components: {
  387. test: {
  388. props: ['msg'],
  389. inherit: true,
  390. template: '{{msg}}'
  391. }
  392. }
  393. })
  394. expect(vm.msg).toBe('hi!')
  395. expect(el.textContent).toBe('ho!')
  396. })
  397. it('should not overwrite default value for an absent Boolean prop', function () {
  398. var vm = new Vue({
  399. el: el,
  400. template: '<test></test>',
  401. components: {
  402. test: {
  403. props: {
  404. prop: Boolean
  405. },
  406. data: function () {
  407. return {
  408. prop: true
  409. }
  410. },
  411. template: '{{prop}}'
  412. }
  413. }
  414. })
  415. expect(vm.$children[0].prop).toBe(true)
  416. expect(vm.$el.textContent).toBe('true')
  417. expect(JSON.stringify(vm.$children[0].$data)).toBe(JSON.stringify({
  418. prop: true
  419. }))
  420. })
  421. it('should respect default value of a Boolean prop', function () {
  422. var vm = new Vue({
  423. el: el,
  424. template: '<test></test>',
  425. components: {
  426. test: {
  427. props: {
  428. prop: {
  429. type: Boolean,
  430. default: true
  431. }
  432. },
  433. template: '{{prop}}'
  434. }
  435. }
  436. })
  437. expect(vm.$el.textContent).toBe('true')
  438. })
  439. it('should initialize with default value when not provided & has default data', function (done) {
  440. var vm = new Vue({
  441. el: el,
  442. template: '<test></test>',
  443. components: {
  444. test: {
  445. props: {
  446. prop: {
  447. type: String,
  448. default: 'hello'
  449. }
  450. },
  451. data: function () {
  452. return {
  453. other: 'world'
  454. }
  455. },
  456. template: '{{prop}} {{other}}'
  457. }
  458. }
  459. })
  460. expect(vm.$el.textContent).toBe('hello world')
  461. vm.$children[0].prop = 'bye'
  462. _.nextTick(function () {
  463. expect(vm.$el.textContent).toBe('bye world')
  464. done()
  465. })
  466. })
  467. it('should not warn for non-required, absent prop', function () {
  468. new Vue({
  469. el: el,
  470. template: '<test></test>',
  471. components: {
  472. test: {
  473. props: {
  474. prop: {
  475. type: String
  476. }
  477. }
  478. }
  479. }
  480. })
  481. expect(_.warn).not.toHaveBeenCalled()
  482. })
  483. })
  484. }