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