prop_spec.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. var Vue = require('src')
  2. describe('prop', function () {
  3. var el
  4. beforeEach(function () {
  5. el = document.createElement('div')
  6. })
  7. it('one way binding', function (done) {
  8. var vm = new Vue({
  9. el: el,
  10. data: {
  11. b: 'bar'
  12. },
  13. template: '<test v-bind:b="b" v-ref:child></test>',
  14. components: {
  15. test: {
  16. props: ['b'],
  17. template: '{{b}}'
  18. }
  19. }
  20. })
  21. expect(el.innerHTML).toBe('<test>bar</test>')
  22. vm.b = 'baz'
  23. Vue.nextTick(function () {
  24. expect(el.innerHTML).toBe('<test>baz</test>')
  25. vm.$refs.child.b = 'qux'
  26. expect(vm.b).toBe('baz')
  27. Vue.nextTick(function () {
  28. expect(el.innerHTML).toBe('<test>qux</test>')
  29. done()
  30. })
  31. })
  32. })
  33. it('with filters', function (done) {
  34. var vm = new Vue({
  35. el: el,
  36. template: '<test :name="a | test"></test>',
  37. data: {
  38. a: 123
  39. },
  40. filters: {
  41. test: function (v) {
  42. return v * 2
  43. }
  44. },
  45. components: {
  46. test: {
  47. props: ['name'],
  48. template: '{{name}}'
  49. }
  50. }
  51. })
  52. expect(el.textContent).toBe('246')
  53. vm.a = 234
  54. Vue.nextTick(function () {
  55. expect(el.textContent).toBe('468')
  56. done()
  57. })
  58. })
  59. it('two-way binding', function (done) {
  60. var vm = new Vue({
  61. el: el,
  62. data: {
  63. b: 'B',
  64. test: {
  65. a: 'A'
  66. }
  67. },
  68. template: '<test v-bind:testt.sync="test" :bb.sync="b" :a.sync=" test.a " v-ref:child></test>',
  69. components: {
  70. test: {
  71. props: ['testt', 'bb', 'a'],
  72. template: '{{testt.a}} {{bb}} {{a}}'
  73. }
  74. }
  75. })
  76. expect(el.firstChild.textContent).toBe('A B A')
  77. vm.test.a = 'AA'
  78. vm.b = 'BB'
  79. Vue.nextTick(function () {
  80. expect(el.firstChild.textContent).toBe('AA BB AA')
  81. vm.test = { a: 'foo' }
  82. Vue.nextTick(function () {
  83. expect(el.firstChild.textContent).toBe('foo BB foo')
  84. vm.$data = {
  85. b: 'bar',
  86. test: {
  87. a: 'fooA'
  88. }
  89. }
  90. Vue.nextTick(function () {
  91. expect(el.firstChild.textContent).toBe('fooA bar fooA')
  92. // test two-way
  93. vm.$refs.child.bb = 'B'
  94. vm.$refs.child.testt = { a: 'A' }
  95. Vue.nextTick(function () {
  96. expect(el.firstChild.textContent).toBe('A B A')
  97. expect(vm.test.a).toBe('A')
  98. expect(vm.test).toBe(vm.$refs.child.testt)
  99. expect(vm.b).toBe('B')
  100. vm.$refs.child.a = 'Oops'
  101. Vue.nextTick(function () {
  102. expect(el.firstChild.textContent).toBe('Oops B Oops')
  103. expect(vm.test.a).toBe('Oops')
  104. done()
  105. })
  106. })
  107. })
  108. })
  109. })
  110. })
  111. it('explicit one time binding', function (done) {
  112. var vm = new Vue({
  113. el: el,
  114. data: {
  115. b: 'foo'
  116. },
  117. template: '<test :b.once="b" v-ref:child></test>',
  118. components: {
  119. test: {
  120. props: ['b'],
  121. template: '{{b}}'
  122. }
  123. }
  124. })
  125. expect(el.innerHTML).toBe('<test>foo</test>')
  126. vm.b = 'bar'
  127. Vue.nextTick(function () {
  128. expect(el.innerHTML).toBe('<test>foo</test>')
  129. done()
  130. })
  131. })
  132. it('warn non-settable parent path', function (done) {
  133. var vm = new Vue({
  134. el: el,
  135. data: {
  136. b: 'foo'
  137. },
  138. template: '<test :b.sync=" b + \'bar\'" v-ref:child></test>',
  139. components: {
  140. test: {
  141. props: ['b'],
  142. template: '{{b}}'
  143. }
  144. }
  145. })
  146. expect('Cannot bind two-way prop with non-settable parent path').toHaveBeenWarned()
  147. expect(el.innerHTML).toBe('<test>foobar</test>')
  148. vm.b = 'baz'
  149. Vue.nextTick(function () {
  150. expect(el.innerHTML).toBe('<test>bazbar</test>')
  151. vm.$refs.child.b = 'qux'
  152. Vue.nextTick(function () {
  153. expect(vm.b).toBe('baz')
  154. expect(el.innerHTML).toBe('<test>qux</test>')
  155. done()
  156. })
  157. })
  158. })
  159. it('warn expect two-way', function () {
  160. new Vue({
  161. el: el,
  162. template: '<test :test="foo"></test>',
  163. data: {
  164. foo: 'bar'
  165. },
  166. components: {
  167. test: {
  168. props: {
  169. test: {
  170. twoWay: true
  171. }
  172. }
  173. }
  174. }
  175. })
  176. expect('expects a two-way binding type').toHaveBeenWarned()
  177. })
  178. it('warn $data as prop', function () {
  179. new Vue({
  180. el: el,
  181. template: '<test></test>',
  182. data: {
  183. foo: 'bar'
  184. },
  185. components: {
  186. test: {
  187. props: ['$data']
  188. }
  189. }
  190. })
  191. expect('Do not use $data as prop').toHaveBeenWarned()
  192. })
  193. it('warn invalid keys', function () {
  194. new Vue({
  195. el: el,
  196. template: '<test :a.b.c="test"></test>',
  197. components: {
  198. test: {
  199. props: ['a.b.c']
  200. }
  201. }
  202. })
  203. expect('Invalid prop key').toHaveBeenWarned()
  204. })
  205. it('warn props with no el option', function () {
  206. new Vue({
  207. props: ['a']
  208. })
  209. expect('Props will not be compiled if no `el`').toHaveBeenWarned()
  210. })
  211. it('warn object/array default values', function () {
  212. new Vue({
  213. el: el,
  214. props: {
  215. arr: {
  216. type: Array,
  217. default: []
  218. },
  219. obj: {
  220. type: Object,
  221. default: {}
  222. }
  223. }
  224. })
  225. expect('use a factory function to return the default value').toHaveBeenWarned()
  226. expect(getWarnCount()).toBe(2)
  227. })
  228. it('teardown', function (done) {
  229. var vm = new Vue({
  230. el: el,
  231. data: {
  232. a: 'A',
  233. b: 'B'
  234. },
  235. template: '<test :aa.sync="a" :bb="b"></test>',
  236. components: {
  237. test: {
  238. props: ['aa', 'bb'],
  239. template: '{{aa}} {{bb}}'
  240. }
  241. }
  242. })
  243. var child = vm.$children[0]
  244. expect(el.firstChild.textContent).toBe('A B')
  245. child.aa = 'AA'
  246. vm.b = 'BB'
  247. Vue.nextTick(function () {
  248. expect(el.firstChild.textContent).toBe('AA BB')
  249. expect(vm.a).toBe('AA')
  250. // unbind the two props
  251. child._directives[0].unbind()
  252. child._directives[1].unbind()
  253. child.aa = 'foo'
  254. vm.b = 'BBB'
  255. Vue.nextTick(function () {
  256. expect(el.firstChild.textContent).toBe('foo BB')
  257. expect(vm.a).toBe('AA')
  258. done()
  259. })
  260. })
  261. })
  262. it('block instance with replace:true', function () {
  263. new Vue({
  264. el: el,
  265. template: '<test :b="a" :c="d"></test>',
  266. data: {
  267. a: 'foo',
  268. d: 'bar'
  269. },
  270. components: {
  271. test: {
  272. props: ['b', 'c'],
  273. template: '<p>{{b}}</p><p>{{c}}</p>',
  274. replace: true
  275. }
  276. }
  277. })
  278. expect(el.innerHTML).toBe('<p>foo</p><p>bar</p>')
  279. })
  280. describe('assertions', function () {
  281. function makeInstance (value, type, validator, coerce, required) {
  282. return new Vue({
  283. el: document.createElement('div'),
  284. template: '<test :test="val"></test>',
  285. data: {
  286. val: value
  287. },
  288. components: {
  289. test: {
  290. props: {
  291. test: {
  292. type: type,
  293. validator: validator,
  294. coerce: coerce,
  295. required: required
  296. }
  297. }
  298. }
  299. }
  300. })
  301. }
  302. it('string', function () {
  303. makeInstance('hello', String)
  304. expect(getWarnCount()).toBe(0)
  305. makeInstance(123, String)
  306. expect('Expected String').toHaveBeenWarned()
  307. })
  308. it('number', function () {
  309. makeInstance(123, Number)
  310. expect(getWarnCount()).toBe(0)
  311. makeInstance('123', Number)
  312. expect('Expected Number').toHaveBeenWarned()
  313. })
  314. it('boolean', function () {
  315. makeInstance(true, Boolean)
  316. expect(getWarnCount()).toBe(0)
  317. makeInstance('123', Boolean)
  318. expect('Expected Boolean').toHaveBeenWarned()
  319. })
  320. it('function', function () {
  321. makeInstance(function () {}, Function)
  322. expect(getWarnCount()).toBe(0)
  323. makeInstance(123, Function)
  324. expect('Expected Function').toHaveBeenWarned()
  325. })
  326. it('object', function () {
  327. makeInstance({}, Object)
  328. expect(getWarnCount()).toBe(0)
  329. makeInstance([], Object)
  330. expect('Expected Object').toHaveBeenWarned()
  331. })
  332. it('array', function () {
  333. makeInstance([], Array)
  334. expect(getWarnCount()).toBe(0)
  335. makeInstance({}, Array)
  336. expect('Expected Array').toHaveBeenWarned()
  337. })
  338. it('custom constructor', function () {
  339. function Class () {}
  340. makeInstance(new Class(), Class)
  341. expect(getWarnCount()).toBe(0)
  342. makeInstance({}, Class)
  343. expect('Expected custom type').toHaveBeenWarned()
  344. })
  345. it('multiple types', function () {
  346. makeInstance([], [Array, Number, Boolean])
  347. expect(getWarnCount()).toBe(0)
  348. makeInstance({}, [Array, Number, Boolean])
  349. expect('Expected Array, Number, Boolean').toHaveBeenWarned()
  350. })
  351. it('custom validator', function () {
  352. makeInstance(123, null, function (v) {
  353. return v === 123
  354. })
  355. expect(getWarnCount()).toBe(0)
  356. makeInstance(123, null, function (v) {
  357. return v === 234
  358. })
  359. expect('custom validator check failed').toHaveBeenWarned()
  360. })
  361. it('type check + custom validator', function () {
  362. makeInstance(123, Number, function (v) {
  363. return v === 123
  364. })
  365. expect(getWarnCount()).toBe(0)
  366. makeInstance(123, Number, function (v) {
  367. return v === 234
  368. })
  369. expect('custom validator check failed').toHaveBeenWarned()
  370. makeInstance(123, String, function (v) {
  371. return v === 123
  372. })
  373. expect('Expected String').toHaveBeenWarned()
  374. })
  375. it('multiple types + custom validator', function () {
  376. makeInstance(123, [Number, String, Boolean], function (v) {
  377. return v === 123
  378. })
  379. expect(getWarnCount()).toBe(0)
  380. makeInstance(123, [Number, String, Boolean], function (v) {
  381. return v === 234
  382. })
  383. expect('custom validator check failed').toHaveBeenWarned()
  384. makeInstance(123, [String, Boolean], function (v) {
  385. return v === 123
  386. })
  387. expect('Expected String, Boolean').toHaveBeenWarned()
  388. })
  389. it('type check + coerce', function () {
  390. makeInstance(123, String, null, String)
  391. expect(getWarnCount()).toBe(0)
  392. makeInstance('123', Number, null, Number)
  393. expect(getWarnCount()).toBe(0)
  394. makeInstance('123', Boolean, null, function (val) {
  395. return val === '123'
  396. })
  397. expect(getWarnCount()).toBe(0)
  398. })
  399. it('warn if coerce is not a function', function () {
  400. var coerce = 1
  401. makeInstance('123', String, null, coerce)
  402. expect(getWarnCount()).toBe(1)
  403. })
  404. it('multiple types + custom validator', function () {
  405. makeInstance(123, [String, Boolean, Number], null, String)
  406. expect(getWarnCount()).toBe(0)
  407. makeInstance('123', [String, Boolean, Number], null, Number)
  408. expect(getWarnCount()).toBe(0)
  409. makeInstance('123', [String, Boolean, Function], null, function (val) {
  410. return val === '123'
  411. })
  412. expect(getWarnCount()).toBe(0)
  413. })
  414. it('required', function () {
  415. new Vue({
  416. el: document.createElement('div'),
  417. template: '<test></test>',
  418. components: {
  419. test: {
  420. props: {
  421. prop: { required: true }
  422. }
  423. }
  424. }
  425. })
  426. expect('Missing required prop').toHaveBeenWarned()
  427. })
  428. it('optional with type + null/undefined', function () {
  429. makeInstance(undefined, String)
  430. expect(getWarnCount()).toBe(0)
  431. makeInstance(null, String)
  432. expect(getWarnCount()).toBe(0)
  433. })
  434. it('required with type + null/undefined', function () {
  435. makeInstance(undefined, String, null, null, true)
  436. expect(getWarnCount()).toBe(1)
  437. expect('Expected String').toHaveBeenWarned()
  438. makeInstance(null, Boolean, null, null, true)
  439. expect(getWarnCount()).toBe(2)
  440. expect('Expected Boolean').toHaveBeenWarned()
  441. })
  442. })
  443. it('alternative syntax', function () {
  444. new Vue({
  445. el: el,
  446. template: '<test :b="a" :c="d"></test>',
  447. data: {
  448. a: 'foo',
  449. d: 'bar'
  450. },
  451. components: {
  452. test: {
  453. props: {
  454. b: String,
  455. c: {
  456. type: Number
  457. },
  458. d: {
  459. required: true
  460. }
  461. },
  462. template: '<p>{{b}}</p><p>{{c}}</p>'
  463. }
  464. }
  465. })
  466. expect('Missing required prop').toHaveBeenWarned()
  467. expect('Expected Number').toHaveBeenWarned()
  468. expect(el.textContent).toBe('foo')
  469. })
  470. it('mixed syntax', function () {
  471. new Vue({
  472. el: el,
  473. template: '<test :b="a" :c="d"></test>',
  474. data: {
  475. a: 'foo',
  476. d: 'bar'
  477. },
  478. components: {
  479. test: {
  480. props: [
  481. 'b',
  482. {
  483. name: 'c',
  484. type: Number
  485. },
  486. {
  487. name: 'd',
  488. required: true
  489. }
  490. ],
  491. template: '<p>{{b}}</p><p>{{c}}</p>'
  492. }
  493. }
  494. })
  495. expect('Missing required prop').toHaveBeenWarned()
  496. expect('Expected Number').toHaveBeenWarned()
  497. expect(el.textContent).toBe('foo')
  498. })
  499. it('should respect default value of a Boolean prop', function () {
  500. var vm = new Vue({
  501. el: el,
  502. template: '<test></test>',
  503. components: {
  504. test: {
  505. props: {
  506. prop: {
  507. type: Boolean,
  508. default: true
  509. }
  510. },
  511. template: '{{prop}}'
  512. }
  513. }
  514. })
  515. expect(vm.$el.textContent).toBe('true')
  516. })
  517. it('should initialize with default value when not provided & has default data', function (done) {
  518. var vm = new Vue({
  519. el: el,
  520. template: '<test></test>',
  521. components: {
  522. test: {
  523. props: {
  524. prop: {
  525. type: String,
  526. default: 'hello'
  527. },
  528. prop2: {
  529. type: Object,
  530. default: function () {
  531. return { vm: this }
  532. }
  533. }
  534. },
  535. data: function () {
  536. return {
  537. other: 'world'
  538. }
  539. },
  540. template: '{{prop}} {{other}}'
  541. }
  542. }
  543. })
  544. expect(vm.$el.textContent).toBe('hello world')
  545. // object/array default value initializers should be
  546. // called with the correct `this` context
  547. var child = vm.$children[0]
  548. expect(child.prop2.vm).toBe(child)
  549. vm.$children[0].prop = 'bye'
  550. Vue.nextTick(function () {
  551. expect(vm.$el.textContent).toBe('bye world')
  552. done()
  553. })
  554. })
  555. it('should warn data fields already defined as a prop', function () {
  556. var Comp = Vue.extend({
  557. data: function () {
  558. return { a: 123 }
  559. },
  560. props: {
  561. a: null
  562. }
  563. })
  564. new Vue({
  565. el: el,
  566. template: '<comp a="1"></comp>',
  567. components: {
  568. comp: Comp
  569. }
  570. })
  571. expect('already defined as a prop').toHaveBeenWarned()
  572. })
  573. it('propsData options', function () {
  574. var vm = new Vue({
  575. el: el,
  576. props: {
  577. a: null
  578. },
  579. propsData: {
  580. a: 123
  581. }
  582. })
  583. expect(getWarnCount()).toBe(0)
  584. expect(vm.a).toBe(123)
  585. })
  586. // # GitHub issues #3183
  587. it('pass propsData to create component that props is defined', function () {
  588. var Comp = Vue.extend({
  589. template: '<div>{{propA.a}}:{{propB.b}}</div>',
  590. props: {
  591. propA: {
  592. type: Object,
  593. required: true
  594. },
  595. 'prop-b': {
  596. type: Object,
  597. required: true
  598. }
  599. }
  600. })
  601. var vm = new Comp({
  602. el: el,
  603. propsData: {
  604. propA: { a: 123 },
  605. propB: { b: '456' }
  606. }
  607. })
  608. expect(vm.propA.a).toBe(123)
  609. expect(vm.propB.b).toBe('456')
  610. expect('Missing required prop: propA').not.toHaveBeenWarned()
  611. expect('Invalid prop: type check failed for prop "propA". Expected Object, got Undefined').not.toHaveBeenWarned()
  612. expect('Missing required prop: prop-b').not.toHaveBeenWarned()
  613. expect('Invalid prop: type check failed for prop "prop-b". Expected Object, got Undefined').not.toHaveBeenWarned()
  614. })
  615. it('should warn using propsData during extension', function () {
  616. Vue.extend({
  617. propsData: {
  618. a: 123
  619. }
  620. })
  621. expect('propsData can only be used as an instantiation option').toHaveBeenWarned()
  622. })
  623. it('should not warn for non-required, absent prop', function () {
  624. new Vue({
  625. el: el,
  626. template: '<test></test>',
  627. components: {
  628. test: {
  629. props: {
  630. prop: {
  631. type: String
  632. }
  633. }
  634. }
  635. }
  636. })
  637. expect(getWarnCount()).toBe(0)
  638. })
  639. // #1683
  640. it('should properly sync back up when mutating then replace', function (done) {
  641. var vm = new Vue({
  642. el: el,
  643. data: {
  644. items: [1, 2]
  645. },
  646. template: '<comp :items.sync="items"></comp>',
  647. components: {
  648. comp: {
  649. props: ['items']
  650. }
  651. }
  652. })
  653. var child = vm.$children[0]
  654. child.items.push(3)
  655. var newArray = child.items = [4]
  656. Vue.nextTick(function () {
  657. expect(child.items).toBe(newArray)
  658. expect(vm.items).toBe(newArray)
  659. done()
  660. })
  661. })
  662. it('treat boolean props properly', function () {
  663. var vm = new Vue({
  664. el: el,
  665. template: '<comp v-ref:child prop-a prop-b="prop-b"></comp>',
  666. components: {
  667. comp: {
  668. props: {
  669. propA: Boolean,
  670. propB: Boolean,
  671. propC: Boolean
  672. }
  673. }
  674. }
  675. })
  676. expect(vm.$refs.child.propA).toBe(true)
  677. expect(vm.$refs.child.propB).toBe(true)
  678. expect(vm.$refs.child.propC).toBe(false)
  679. })
  680. it('detect possible camelCase prop usage', function () {
  681. new Vue({
  682. el: el,
  683. template: '<comp propA="true" :propB="true" v-bind:propC.sync="true"></comp>',
  684. components: {
  685. comp: {
  686. props: ['propA', 'propB', 'prop-c']
  687. }
  688. }
  689. })
  690. expect(getWarnCount()).toBe(3)
  691. expect('did you mean `prop-a`').toHaveBeenWarned()
  692. expect('did you mean `prop-b`').toHaveBeenWarned()
  693. expect('did you mean `prop-c`').toHaveBeenWarned()
  694. })
  695. it('should use default for undefined values', function (done) {
  696. var vm = new Vue({
  697. el: el,
  698. template: '<comp :a="a"></comp>',
  699. data: {
  700. a: undefined
  701. },
  702. components: {
  703. comp: {
  704. template: '{{a}}',
  705. props: {
  706. a: {
  707. default: 1
  708. }
  709. }
  710. }
  711. }
  712. })
  713. expect(vm.$el.textContent).toBe('1')
  714. vm.a = 2
  715. Vue.nextTick(function () {
  716. expect(vm.$el.textContent).toBe('2')
  717. vm.a = undefined
  718. Vue.nextTick(function () {
  719. expect(vm.$el.textContent).toBe('1')
  720. done()
  721. })
  722. })
  723. })
  724. it('non reactive values passed down as prop should not be converted', function (done) {
  725. var a = Object.freeze({
  726. nested: {
  727. msg: 'hello'
  728. }
  729. })
  730. var parent = new Vue({
  731. el: el,
  732. template: '<comp :a="a.nested"></comp>',
  733. data: {
  734. a: a
  735. },
  736. components: {
  737. comp: {
  738. props: ['a']
  739. }
  740. }
  741. })
  742. var child = parent.$children[0]
  743. expect(child.a.msg).toBe('hello')
  744. expect(child.a.__ob__).toBeUndefined() // should not be converted
  745. parent.a = Object.freeze({
  746. nested: {
  747. msg: 'yo'
  748. }
  749. })
  750. Vue.nextTick(function () {
  751. expect(child.a.msg).toBe('yo')
  752. expect(child.a.__ob__).toBeUndefined()
  753. done()
  754. })
  755. })
  756. it('inline prop values should be converted', function (done) {
  757. var vm = new Vue({
  758. el: el,
  759. template: '<comp :a="[1, 2, 3]"></comp>',
  760. components: {
  761. comp: {
  762. props: ['a'],
  763. template: '<div v-for="i in a">{{ i }}</div>'
  764. }
  765. }
  766. })
  767. expect(vm.$el.textContent).toBe('123')
  768. vm.$children[0].a.pop()
  769. Vue.nextTick(function () {
  770. expect(vm.$el.textContent).toBe('12')
  771. done()
  772. })
  773. })
  774. // #2549
  775. it('mutating child prop binding should be reactive', function (done) {
  776. var vm = new Vue({
  777. el: el,
  778. template: '<comp :list="list"></comp>',
  779. data: {
  780. list: [1, 2, 3]
  781. },
  782. components: {
  783. comp: {
  784. props: ['list'],
  785. template: '<div v-for="i in list">{{ i }}</div>',
  786. created: function () {
  787. this.list = [2, 3, 4]
  788. }
  789. }
  790. }
  791. })
  792. expect(vm.$el.textContent).toBe('234')
  793. vm.$children[0].list.push(5)
  794. Vue.nextTick(function () {
  795. expect(vm.$el.textContent).toBe('2345')
  796. done()
  797. })
  798. })
  799. it('prop default value should be reactive', function (done) {
  800. var vm = new Vue({
  801. el: el,
  802. template: '<comp :list="list"></comp>',
  803. data: {
  804. list: undefined
  805. },
  806. components: {
  807. comp: {
  808. props: {
  809. list: {
  810. default: function () {
  811. return [2, 3, 4]
  812. }
  813. }
  814. },
  815. template: '<div v-for="i in list">{{ i }}</div>'
  816. }
  817. }
  818. })
  819. expect(vm.$el.textContent).toBe('234')
  820. vm.$children[0].list.push(5)
  821. Vue.nextTick(function () {
  822. expect(vm.$el.textContent).toBe('2345')
  823. done()
  824. })
  825. })
  826. it('prop coerced value should be reactive', function (done) {
  827. var vm = new Vue({
  828. el: el,
  829. template: '<comp :obj="obj"></comp>',
  830. data: {
  831. obj: { ok: true }
  832. },
  833. components: {
  834. comp: {
  835. props: {
  836. obj: {
  837. coerce: function () {
  838. return { ok: false }
  839. }
  840. }
  841. },
  842. template: '<div>{{ obj.ok }}</div>'
  843. }
  844. }
  845. })
  846. expect(vm.$el.textContent).toBe('false')
  847. vm.$children[0].obj.ok = true
  848. Vue.nextTick(function () {
  849. expect(vm.$el.textContent).toBe('true')
  850. done()
  851. })
  852. })
  853. it('prop coercion should be applied after defaulting', function () {
  854. var vm = new Vue({
  855. el: el,
  856. template: '<comp></comp>',
  857. components: {
  858. comp: {
  859. props: {
  860. color: {
  861. type: String,
  862. default: 'blue',
  863. coerce: function (color) {
  864. return 'color-' + color
  865. }
  866. }
  867. },
  868. template: '<div>{{ color }}</div>'
  869. }
  870. }
  871. })
  872. expect(vm.$el.textContent).toBe('color-blue')
  873. })
  874. })