1// @vitest-environment node23import Vue from 'vue'4import VM from 'vm'5import { createRenderer } from 'server/index'6import { _it } from './utils'78const { renderToString } = createRenderer()910describe('SSR: renderToString', () => {11 _it('static attributes', done => {12 renderVmWithOptions(13 {14 template: '<div id="foo" bar="123"></div>'15 },16 result => {17 expect(result).toContain(18 '<div id="foo" bar="123" data-server-rendered="true"></div>'19 )20 done()21 }22 )23 })2425 _it('unary tags', done => {26 renderVmWithOptions(27 {28 template: '<input value="123">'29 },30 result => {31 expect(result).toContain(32 '<input value="123" data-server-rendered="true">'33 )34 done()35 }36 )37 })3839 _it('dynamic attributes', done => {40 renderVmWithOptions(41 {42 template: '<div qux="quux" :id="foo" :bar="baz"></div>',43 data: {44 foo: 'hi',45 baz: 12346 }47 },48 result => {49 expect(result).toContain(50 '<div qux="quux" id="hi" bar="123" data-server-rendered="true"></div>'51 )52 done()53 }54 )55 })5657 _it('static class', done => {58 renderVmWithOptions(59 {60 template: '<div class="foo bar"></div>'61 },62 result => {63 expect(result).toContain(64 '<div data-server-rendered="true" class="foo bar"></div>'65 )66 done()67 }68 )69 })7071 _it('dynamic class', done => {72 renderVmWithOptions(73 {74 template:75 '<div class="foo bar" :class="[a, { qux: hasQux, quux: hasQuux }]"></div>',76 data: {77 a: 'baz',78 hasQux: true,79 hasQuux: false80 }81 },82 result => {83 expect(result).toContain(84 '<div data-server-rendered="true" class="foo bar baz qux"></div>'85 )86 done()87 }88 )89 })9091 _it('custom component class', done => {92 renderVmWithOptions(93 {94 template: '<div><cmp class="cmp"></cmp></div>',95 components: {96 cmp: {97 render: h => h('div', 'test')98 }99 }100 },101 result => {102 expect(result).toContain(103 '<div data-server-rendered="true"><div class="cmp">test</div></div>'104 )105 done()106 }107 )108 })109110 _it('nested component class', done => {111 renderVmWithOptions(112 {113 template: '<cmp class="outer" :class="cls"></cmp>',114 data: { cls: { success: 1 } },115 components: {116 cmp: {117 render: h =>118 h('div', [119 h('nested', { staticClass: 'nested', class: { error: 1 } })120 ]),121 components: {122 nested: {123 render: h => h('div', { staticClass: 'inner' }, 'test')124 }125 }126 }127 }128 },129 result => {130 expect(result).toContain(131 '<div data-server-rendered="true" class="outer success">' +132 '<div class="inner nested error">test</div>' +133 '</div>'134 )135 done()136 }137 )138 })139140 _it('dynamic style', done => {141 renderVmWithOptions(142 {143 template:144 '<div style="background-color:black" :style="{ fontSize: fontSize + \'px\', color: color }"></div>',145 data: {146 fontSize: 14,147 color: 'red'148 }149 },150 result => {151 expect(result).toContain(152 '<div data-server-rendered="true" style="background-color:black;font-size:14px;color:red;"></div>'153 )154 done()155 }156 )157 })158159 _it('dynamic string style', done => {160 renderVmWithOptions(161 {162 template: '<div :style="style"></div>',163 data: {164 style: 'color:red'165 }166 },167 result => {168 expect(result).toContain(169 '<div data-server-rendered="true" style="color:red;"></div>'170 )171 done()172 }173 )174 })175176 _it('auto-prefixed style value as array', done => {177 renderVmWithOptions(178 {179 template: '<div :style="style"></div>',180 data: {181 style: {182 display: ['-webkit-box', '-ms-flexbox', 'flex']183 }184 }185 },186 result => {187 expect(result).toContain(188 '<div data-server-rendered="true" style="display:-webkit-box;display:-ms-flexbox;display:flex;"></div>'189 )190 done()191 }192 )193 })194195 _it('custom component style', done => {196 renderVmWithOptions(197 {198 template: '<section><comp :style="style"></comp></section>',199 data: {200 style: 'color:red'201 },202 components: {203 comp: {204 template: '<div></div>'205 }206 }207 },208 result => {209 expect(result).toContain(210 '<section data-server-rendered="true"><div style="color:red;"></div></section>'211 )212 done()213 }214 )215 })216217 _it('nested custom component style', done => {218 renderVmWithOptions(219 {220 template: '<comp style="color: blue" :style="style"></comp>',221 data: {222 style: 'color:red'223 },224 components: {225 comp: {226 template:227 '<nested style="text-align: left;" :style="{fontSize:\'520rem\'}"></nested>',228 components: {229 nested: {230 template: '<div></div>'231 }232 }233 }234 }235 },236 result => {237 expect(result).toContain(238 '<div data-server-rendered="true" style="text-align:left;font-size:520rem;color:red;"></div>'239 )240 done()241 }242 )243 })244245 _it('component style not passed to child', done => {246 renderVmWithOptions(247 {248 template: '<comp :style="style"></comp>',249 data: {250 style: 'color:red'251 },252 components: {253 comp: {254 template: '<div><div></div></div>'255 }256 }257 },258 result => {259 expect(result).toContain(260 '<div data-server-rendered="true" style="color:red;"><div></div></div>'261 )262 done()263 }264 )265 })266267 _it('component style not passed to slot', done => {268 renderVmWithOptions(269 {270 template:271 '<comp :style="style"><span style="color:black"></span></comp>',272 data: {273 style: 'color:red'274 },275 components: {276 comp: {277 template: '<div><slot></slot></div>'278 }279 }280 },281 result => {282 expect(result).toContain(283 '<div data-server-rendered="true" style="color:red;"><span style="color:black;"></span></div>'284 )285 done()286 }287 )288 })289290 _it('attrs merging on components', done => {291 const Test = {292 render: h =>293 h('div', {294 attrs: { id: 'a' }295 })296 }297 renderVmWithOptions(298 {299 render: h =>300 h(Test, {301 attrs: { id: 'b', name: 'c' }302 })303 },304 res => {305 expect(res).toContain(306 '<div id="b" data-server-rendered="true" name="c"></div>'307 )308 done()309 }310 )311 })312313 _it('domProps merging on components', done => {314 const Test = {315 render: h =>316 h('div', {317 domProps: { innerHTML: 'a' }318 })319 }320 renderVmWithOptions(321 {322 render: h =>323 h(Test, {324 domProps: { innerHTML: 'b', value: 'c' }325 })326 },327 res => {328 expect(res).toContain(329 '<div data-server-rendered="true" value="c">b</div>'330 )331 done()332 }333 )334 })335336 _it('v-show directive render', done => {337 renderVmWithOptions(338 {339 template: '<div v-show="false"><span>inner</span></div>'340 },341 res => {342 expect(res).toContain(343 '<div data-server-rendered="true" style="display:none;"><span>inner</span></div>'344 )345 done()346 }347 )348 })349350 _it('v-show directive merge with style', done => {351 renderVmWithOptions(352 {353 template:354 '<div :style="[{lineHeight: 1}]" v-show="false"><span>inner</span></div>'355 },356 res => {357 expect(res).toContain(358 '<div data-server-rendered="true" style="line-height:1;display:none;"><span>inner</span></div>'359 )360 done()361 }362 )363 })364365 _it('v-show directive not passed to child', done => {366 renderVmWithOptions(367 {368 template: '<foo v-show="false"></foo>',369 components: {370 foo: {371 template: '<div><span>inner</span></div>'372 }373 }374 },375 res => {376 expect(res).toContain(377 '<div data-server-rendered="true" style="display:none;"><span>inner</span></div>'378 )379 done()380 }381 )382 })383384 _it('v-show directive not passed to slot', done => {385 renderVmWithOptions(386 {387 template: '<foo v-show="false"><span>inner</span></foo>',388 components: {389 foo: {390 template: '<div><slot></slot></div>'391 }392 }393 },394 res => {395 expect(res).toContain(396 '<div data-server-rendered="true" style="display:none;"><span>inner</span></div>'397 )398 done()399 }400 )401 })402403 _it('v-show directive merging on components', done => {404 renderVmWithOptions(405 {406 template: '<foo v-show="false"></foo>',407 components: {408 foo: {409 render: h =>410 h('bar', {411 directives: [412 {413 name: 'show',414 value: true415 }416 ]417 }),418 components: {419 bar: {420 render: h => h('div', 'inner')421 }422 }423 }424 }425 },426 res => {427 expect(res).toContain(428 '<div data-server-rendered="true" style="display:none;">inner</div>'429 )430 done()431 }432 )433 })434435 _it('text interpolation', done => {436 renderVmWithOptions(437 {438 template: '<div>{{ foo }} side {{ bar }}</div>',439 data: {440 foo: 'server',441 bar: '<span>rendering</span>'442 }443 },444 result => {445 expect(result).toContain(446 '<div data-server-rendered="true">server side <span>rendering</span></div>'447 )448 done()449 }450 )451 })452453 _it('v-html on root', done => {454 renderVmWithOptions(455 {456 template: '<div v-html="text"></div>',457 data: {458 text: '<span>foo</span>'459 }460 },461 result => {462 expect(result).toContain(463 '<div data-server-rendered="true"><span>foo</span></div>'464 )465 done()466 }467 )468 })469470 _it('v-text on root', done => {471 renderVmWithOptions(472 {473 template: '<div v-text="text"></div>',474 data: {475 text: '<span>foo</span>'476 }477 },478 result => {479 expect(result).toContain(480 '<div data-server-rendered="true"><span>foo</span></div>'481 )482 done()483 }484 )485 })486487 _it('v-html', done => {488 renderVmWithOptions(489 {490 template: '<div><div v-html="text"></div></div>',491 data: {492 text: '<span>foo</span>'493 }494 },495 result => {496 expect(result).toContain(497 '<div data-server-rendered="true"><div><span>foo</span></div></div>'498 )499 done()500 }501 )502 })503504 _it('v-html with null value', done => {505 renderVmWithOptions(506 {507 template: '<div><div v-html="text"></div></div>',508 data: {509 text: null510 }511 },512 result => {513 expect(result).toContain(514 '<div data-server-rendered="true"><div></div></div>'515 )516 done()517 }518 )519 })520521 _it('v-text', done => {522 renderVmWithOptions(523 {524 template: '<div><div v-text="text"></div></div>',525 data: {526 text: '<span>foo</span>'527 }528 },529 result => {530 expect(result).toContain(531 '<div data-server-rendered="true"><div><span>foo</span></div></div>'532 )533 done()534 }535 )536 })537538 _it('v-text with null value', done => {539 renderVmWithOptions(540 {541 template: '<div><div v-text="text"></div></div>',542 data: {543 text: null544 }545 },546 result => {547 expect(result).toContain(548 '<div data-server-rendered="true"><div></div></div>'549 )550 done()551 }552 )553 })554555 _it('child component (hoc)', done => {556 renderVmWithOptions(557 {558 template: '<child class="foo" :msg="msg"></child>',559 data: {560 msg: 'hello'561 },562 components: {563 child: {564 props: ['msg'],565 data() {566 return { name: 'bar' }567 },568 render() {569 const h = this.$createElement570 return h('div', { class: ['bar'] }, [`${this.msg} ${this.name}`])571 }572 }573 }574 },575 result => {576 expect(result).toContain(577 '<div data-server-rendered="true" class="foo bar">hello bar</div>'578 )579 done()580 }581 )582 })583584 _it('has correct lifecycle during render', done => {585 let lifecycleCount = 1586 renderVmWithOptions(587 {588 template: '<div><span>{{ val }}</span><test></test></div>',589 data: {590 val: 'hi'591 },592 beforeCreate() {593 expect(lifecycleCount++).toBe(1)594 },595 created() {596 this.val = 'hello'597 expect(this.val).toBe('hello')598 expect(lifecycleCount++).toBe(2)599 },600 components: {601 test: {602 beforeCreate() {603 expect(lifecycleCount++).toBe(3)604 },605 created() {606 expect(lifecycleCount++).toBe(4)607 },608 render() {609 expect(lifecycleCount++).toBeGreaterThan(4)610 return this.$createElement('span', { class: ['b'] }, 'testAsync')611 }612 }613 }614 },615 result => {616 expect(result).toContain(617 '<div data-server-rendered="true">' +618 '<span>hello</span>' +619 '<span class="b">testAsync</span>' +620 '</div>'621 )622 done()623 }624 )625 })626627 _it('computed properties', done => {628 renderVmWithOptions(629 {630 template: '<div>{{ b }}</div>',631 data: {632 a: {633 b: 1634 }635 },636 computed: {637 b() {638 return this.a.b + 1639 }640 },641 created() {642 this.a.b = 2643 expect(this.b).toBe(3)644 }645 },646 result => {647 expect(result).toContain('<div data-server-rendered="true">3</div>')648 done()649 }650 )651 })652653 _it('renders async component', done => {654 renderVmWithOptions(655 {656 template: `657 <div>658 <test-async></test-async>659 </div>660 `,661 components: {662 testAsync(resolve) {663 setTimeout(664 () =>665 resolve({666 render() {667 return this.$createElement(668 'span',669 { class: ['b'] },670 'testAsync'671 )672 }673 }),674 1675 )676 }677 }678 },679 result => {680 expect(result).toContain(681 '<div data-server-rendered="true"><span class="b">testAsync</span></div>'682 )683 done()684 }685 )686 })687688 _it('renders async component (Promise, nested)', done => {689 const Foo = () =>690 Promise.resolve({691 render: h => h('div', [h('span', 'foo'), h(Bar)])692 })693 const Bar = () => ({694 component: Promise.resolve({695 render: h => h('span', 'bar')696 })697 })698 renderVmWithOptions(699 {700 render: h => h(Foo)701 },702 res => {703 expect(res).toContain(704 `<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`705 )706 done()707 }708 )709 })710711 _it('renders async component (ES module)', done => {712 const Foo = () =>713 Promise.resolve({714 __esModule: true,715 default: {716 render: h => h('div', [h('span', 'foo'), h(Bar)])717 }718 })719 const Bar = () => ({720 component: Promise.resolve({721 __esModule: true,722 default: {723 render: h => h('span', 'bar')724 }725 })726 })727 renderVmWithOptions(728 {729 render: h => h(Foo)730 },731 res => {732 expect(res).toContain(733 `<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`734 )735 done()736 }737 )738 })739740 _it('renders async component (hoc)', done => {741 renderVmWithOptions(742 {743 template: '<test-async></test-async>',744 components: {745 testAsync: () =>746 Promise.resolve({747 render() {748 return this.$createElement(749 'span',750 { class: ['b'] },751 'testAsync'752 )753 }754 })755 }756 },757 result => {758 expect(result).toContain(759 '<span data-server-rendered="true" class="b">testAsync</span>'760 )761 done()762 }763 )764 })765766 _it('renders async component (functional, single node)', done => {767 renderVmWithOptions(768 {769 template: `770 <div>771 <test-async></test-async>772 </div>773 `,774 components: {775 testAsync(resolve) {776 setTimeout(777 () =>778 resolve({779 functional: true,780 render(h) {781 return h('span', { class: ['b'] }, 'testAsync')782 }783 }),784 1785 )786 }787 }788 },789 result => {790 expect(result).toContain(791 '<div data-server-rendered="true"><span class="b">testAsync</span></div>'792 )793 done()794 }795 )796 })797798 _it('renders async component (functional, multiple nodes)', done => {799 renderVmWithOptions(800 {801 template: `802 <div>803 <test-async></test-async>804 </div>805 `,806 components: {807 testAsync(resolve) {808 setTimeout(809 () =>810 resolve({811 functional: true,812 render(h) {813 return [814 h('span', { class: ['a'] }, 'foo'),815 h('span', { class: ['b'] }, 'bar')816 ]817 }818 }),819 1820 )821 }822 }823 },824 result => {825 expect(result).toContain(826 '<div data-server-rendered="true">' +827 '<span class="a">foo</span>' +828 '<span class="b">bar</span>' +829 '</div>'830 )831 done()832 }833 )834 })835836 _it('renders nested async functional component', done => {837 renderVmWithOptions(838 {839 template: `840 <div>841 <outer-async></outer-async>842 </div>843 `,844 components: {845 outerAsync(resolve) {846 setTimeout(847 () =>848 resolve({849 functional: true,850 render(h) {851 return h('innerAsync')852 }853 }),854 1855 )856 },857 innerAsync(resolve) {858 setTimeout(859 () =>860 resolve({861 functional: true,862 render(h) {863 return h('span', { class: ['a'] }, 'inner')864 }865 }),866 1867 )868 }869 }870 },871 result => {872 expect(result).toContain(873 '<div data-server-rendered="true">' +874 '<span class="a">inner</span>' +875 '</div>'876 )877 done()878 }879 )880 })881882 _it('should catch async component error', done => {883 renderToString(884 new Vue({885 template: '<test-async></test-async>',886 components: {887 testAsync: () =>888 Promise.resolve({889 render() {890 throw new Error('foo')891 }892 })893 }894 }),895 (err, result) => {896 expect(err).toBeTruthy()897 expect(result).toBeUndefined()898 expect('foo').toHaveBeenWarned()899 done()900 }901 )902 })903904 // #11963, #10391905 _it('renders async children passed in slots', done => {906 const Parent = {907 template: `<div><slot name="child"/></div>`908 }909 const Child = {910 template: `<p>child</p>`911 }912 renderVmWithOptions(913 {914 template: `915 <Parent>916 <template #child>917 <Child/>918 </template>919 </Parent>920 `,921 components: {922 Parent,923 Child: () => Promise.resolve(Child)924 }925 },926 result => {927 expect(result).toContain(928 `<div data-server-rendered="true"><p>child</p></div>`929 )930 done()931 }932 )933 })934935 _it('everything together', done => {936 renderVmWithOptions(937 {938 template: `939 <div>940 <p class="hi">yoyo</p>941 <div id="ho" :class="{ red: isRed }"></div>942 <span>{{ test }}</span>943 <input :value="test">944 <img :src="imageUrl">945 <test></test>946 <test-async></test-async>947 </div>948 `,949 data: {950 test: 'hi',951 isRed: true,952 imageUrl: 'https://vuejs.org/images/logo.png'953 },954 components: {955 test: {956 render() {957 return this.$createElement('div', { class: ['a'] }, 'test')958 }959 },960 testAsync(resolve) {961 resolve({962 render() {963 return this.$createElement(964 'span',965 { class: ['b'] },966 'testAsync'967 )968 }969 })970 }971 }972 },973 result => {974 expect(result).toContain(975 '<div data-server-rendered="true">' +976 '<p class="hi">yoyo</p> ' +977 '<div id="ho" class="red"></div> ' +978 '<span>hi</span> ' +979 '<input value="hi"> ' +980 '<img src="https://vuejs.org/images/logo.png"> ' +981 '<div class="a">test</div> ' +982 '<span class="b">testAsync</span>' +983 '</div>'984 )985 done()986 }987 )988 })989990 _it('normal attr', done => {991 renderVmWithOptions(992 {993 template: `994 <div>995 <span :test="'ok'">hello</span>996 <span :test="null">hello</span>997 <span :test="false">hello</span>998 <span :test="true">hello</span>999 <span :test="0">hello</span>1000 </div>1001 `1002 },1003 result => {1004 expect(result).toContain(1005 '<div data-server-rendered="true">' +1006 '<span test="ok">hello</span> ' +1007 '<span>hello</span> ' +1008 '<span>hello</span> ' +1009 '<span test="true">hello</span> ' +1010 '<span test="0">hello</span>' +1011 '</div>'1012 )1013 done()1014 }1015 )1016 })10171018 _it('enumerated attr', done => {1019 renderVmWithOptions(1020 {1021 template: `1022 <div>1023 <span :draggable="true">hello</span>1024 <span :draggable="'ok'">hello</span>1025 <span :draggable="null">hello</span>1026 <span :draggable="false">hello</span>1027 <span :draggable="''">hello</span>1028 <span :draggable="'false'">hello</span>1029 </div>1030 `1031 },1032 result => {1033 expect(result).toContain(1034 '<div data-server-rendered="true">' +1035 '<span draggable="true">hello</span> ' +1036 '<span draggable="true">hello</span> ' +1037 '<span draggable="false">hello</span> ' +1038 '<span draggable="false">hello</span> ' +1039 '<span draggable="true">hello</span> ' +1040 '<span draggable="false">hello</span>' +1041 '</div>'1042 )1043 done()1044 }1045 )1046 })10471048 _it('boolean attr', done => {1049 renderVmWithOptions(1050 {1051 template: `1052 <div>1053 <span :disabled="true">hello</span>1054 <span :disabled="'ok'">hello</span>1055 <span :disabled="null">hello</span>1056 <span :disabled="''">hello</span>1057 </div>1058 `1059 },1060 result => {1061 expect(result).toContain(1062 '<div data-server-rendered="true">' +1063 '<span disabled="disabled">hello</span> ' +1064 '<span disabled="disabled">hello</span> ' +1065 '<span>hello</span> ' +1066 '<span disabled="disabled">hello</span>' +1067 '</div>'1068 )1069 done()1070 }1071 )1072 })10731074 _it('v-bind object', done => {1075 renderVmWithOptions(1076 {1077 data: {1078 test: { id: 'a', class: ['a', 'b'], value: 'c' }1079 },1080 template: '<input v-bind="test">'1081 },1082 result => {1083 expect(result).toContain(1084 '<input id="a" data-server-rendered="true" value="c" class="a b">'1085 )1086 done()1087 }1088 )1089 })10901091 _it('custom directives on raw element', done => {1092 const renderer = createRenderer({1093 directives: {1094 'class-prefixer': (node, dir) => {1095 if (node.data.class) {1096 node.data.class = `${dir.value}-${node.data.class}`1097 }1098 if (node.data.staticClass) {1099 node.data.staticClass = `${dir.value}-${node.data.staticClass}`1100 }1101 }1102 }1103 })1104 renderer.renderToString(1105 new Vue({1106 render() {1107 const h = this.$createElement1108 return h(1109 'p',1110 {1111 class: 'class1',1112 staticClass: 'class2',1113 directives: [1114 {1115 name: 'class-prefixer',1116 value: 'my'1117 }1118 ]1119 },1120 ['hello world']1121 )1122 }1123 }),1124 (err, result) => {1125 expect(err).toBeNull()1126 expect(result).toContain(1127 '<p data-server-rendered="true" class="my-class2 my-class1">hello world</p>'1128 )1129 done()1130 }1131 )1132 })11331134 _it('custom directives on component', done => {1135 const Test = {1136 template: '<span>hello world</span>'1137 }1138 const renderer = createRenderer({1139 directives: {1140 'class-prefixer': (node, dir) => {1141 if (node.data.class) {1142 node.data.class = `${dir.value}-${node.data.class}`1143 }1144 if (node.data.staticClass) {1145 node.data.staticClass = `${dir.value}-${node.data.staticClass}`1146 }1147 }1148 }1149 })1150 renderer.renderToString(1151 new Vue({1152 template:1153 '<p><Test v-class-prefixer="\'my\'" class="class1" :class="\'class2\'" /></p>',1154 components: { Test }1155 }),1156 (err, result) => {1157 expect(err).toBeNull()1158 expect(result).toContain(1159 '<p data-server-rendered="true"><span class="my-class1 my-class2">hello world</span></p>'1160 )1161 done()1162 }1163 )1164 })11651166 _it('custom directives on element root of a component', done => {1167 const Test = {1168 template:1169 '<span v-class-prefixer="\'my\'" class="class1" :class="\'class2\'">hello world</span>'1170 }1171 const renderer = createRenderer({1172 directives: {1173 'class-prefixer': (node, dir) => {1174 if (node.data.class) {1175 node.data.class = `${dir.value}-${node.data.class}`1176 }1177 if (node.data.staticClass) {1178 node.data.staticClass = `${dir.value}-${node.data.staticClass}`1179 }1180 }1181 }1182 })1183 renderer.renderToString(1184 new Vue({1185 template: '<p><Test /></p>',1186 components: { Test }1187 }),1188 (err, result) => {1189 expect(err).toBeNull()1190 expect(result).toContain(1191 '<p data-server-rendered="true"><span class="my-class1 my-class2">hello world</span></p>'1192 )1193 done()1194 }1195 )1196 })11971198 _it('custom directives on element with parent element', done => {1199 const renderer = createRenderer({1200 directives: {1201 'class-prefixer': (node, dir) => {1202 if (node.data.class) {1203 node.data.class = `${dir.value}-${node.data.class}`1204 }1205 if (node.data.staticClass) {1206 node.data.staticClass = `${dir.value}-${node.data.staticClass}`1207 }1208 }1209 }1210 })1211 renderer.renderToString(1212 new Vue({1213 template:1214 '<p><span v-class-prefixer="\'my\'" class="class1" :class="\'class2\'">hello world</span></p>'1215 }),1216 (err, result) => {1217 expect(err).toBeNull()1218 expect(result).toContain(1219 '<p data-server-rendered="true"><span class="my-class1 my-class2">hello world</span></p>'1220 )1221 done()1222 }1223 )1224 })12251226 _it(1227 'should not warn for custom directives that do not have server-side implementation',1228 done => {1229 renderToString(1230 new Vue({1231 directives: {1232 test: {1233 bind() {1234 // noop1235 }1236 }1237 },1238 template: '<div v-test></div>'1239 }),1240 () => {1241 expect('Failed to resolve directive: test').not.toHaveBeenWarned()1242 done()1243 }1244 )1245 }1246 )12471248 _it('_scopeId', done => {1249 renderVmWithOptions(1250 {1251 _scopeId: '_v-parent',1252 template: '<div id="foo"><p><child></child></p></div>',1253 components: {1254 child: {1255 _scopeId: '_v-child',1256 render() {1257 const h = this.$createElement1258 return h('div', null, [h('span', null, ['foo'])])1259 }1260 }1261 }1262 },1263 result => {1264 expect(result).toContain(1265 '<div id="foo" data-server-rendered="true" _v-parent>' +1266 '<p _v-parent>' +1267 '<div _v-child _v-parent><span _v-child>foo</span></div>' +1268 '</p>' +1269 '</div>'1270 )1271 done()1272 }1273 )1274 })12751276 _it('_scopeId on slot content', done => {1277 renderVmWithOptions(1278 {1279 _scopeId: '_v-parent',1280 template: '<div><child><p>foo</p></child></div>',1281 components: {1282 child: {1283 _scopeId: '_v-child',1284 render() {1285 const h = this.$createElement1286 return h('div', null, this.$slots.default)1287 }1288 }1289 }1290 },1291 result => {1292 expect(result).toContain(1293 '<div data-server-rendered="true" _v-parent>' +1294 '<div _v-child _v-parent><p _v-child _v-parent>foo</p></div>' +1295 '</div>'1296 )1297 done()1298 }1299 )1300 })13011302 _it('comment nodes', done => {1303 renderVmWithOptions(1304 {1305 template: '<div><transition><div v-if="false"></div></transition></div>'1306 },1307 result => {1308 expect(result).toContain(1309 `<div data-server-rendered="true"><!----></div>`1310 )1311 done()1312 }1313 )1314 })13151316 _it('should catch error', done => {1317 renderToString(1318 new Vue({1319 render() {1320 throw new Error('oops')1321 }1322 }),1323 err => {1324 expect(err instanceof Error).toBe(true)1325 expect(`oops`).toHaveBeenWarned()1326 done()1327 }1328 )1329 })13301331 _it('default value Foreign Function', () => {1332 const FunctionConstructor = VM.runInNewContext('Function')1333 const func = () => 1231334 const vm = new Vue({1335 props: {1336 a: {1337 type: FunctionConstructor,1338 default: func1339 }1340 },1341 propsData: {1342 a: undefined1343 }1344 })1345 expect(vm.a).toBe(func)1346 })13471348 _it('should prevent xss in attributes', done => {1349 renderVmWithOptions(1350 {1351 data: {1352 xss: '"><script>alert(1)</script>'1353 },1354 template: `1355 <div>1356 <a :title="xss" :style="{ color: xss }" :class="[xss]">foo</a>1357 </div>1358 `1359 },1360 res => {1361 expect(res).not.toContain(`<script>alert(1)</script>`)1362 done()1363 }1364 )1365 })13661367 _it('should prevent xss in attribute names', done => {1368 renderVmWithOptions(1369 {1370 data: {1371 xss: {1372 'foo="bar"></div><script>alert(1)</script>': ''1373 }1374 },1375 template: `1376 <div v-bind="xss"></div>1377 `1378 },1379 res => {1380 expect(res).not.toContain(`<script>alert(1)</script>`)1381 done()1382 }1383 )1384 })13851386 _it('should prevent xss in attribute names (optimized)', done => {1387 renderVmWithOptions(1388 {1389 data: {1390 xss: {1391 'foo="bar"></div><script>alert(1)</script>': ''1392 }1393 },1394 template: `1395 <div>1396 <a v-bind="xss">foo</a>1397 </div>1398 `1399 },1400 res => {1401 expect(res).not.toContain(`<script>alert(1)</script>`)1402 done()1403 }1404 )1405 })14061407 _it(1408 'should prevent script xss with v-bind object syntax + array value',1409 done => {1410 renderVmWithOptions(1411 {1412 data: {1413 test: ['"><script>alert(1)</script><!--"']1414 },1415 template: `<div v-bind="{ test }"></div>`1416 },1417 res => {1418 expect(res).not.toContain(`<script>alert(1)</script>`)1419 done()1420 }1421 )1422 }1423 )14241425 _it('v-if', done => {1426 renderVmWithOptions(1427 {1428 template: `1429 <div>1430 <span v-if="true">foo</span>1431 <span v-if="false">bar</span>1432 </div>1433 `1434 },1435 res => {1436 expect(res).toContain(1437 `<div data-server-rendered="true"><span>foo</span> <!----></div>`1438 )1439 done()1440 }1441 )1442 })14431444 _it('v-for', done => {1445 renderVmWithOptions(1446 {1447 template: `1448 <div>1449 <span>foo</span>1450 <span v-for="i in 2">{{ i }}</span>1451 </div>1452 `1453 },1454 res => {1455 expect(res).toContain(1456 `<div data-server-rendered="true"><span>foo</span> <span>1</span><span>2</span></div>`1457 )1458 done()1459 }1460 )1461 })14621463 _it('template v-if', done => {1464 renderVmWithOptions(1465 {1466 template: `1467 <div>1468 <span>foo</span>1469 <template v-if="true">1470 <span>foo</span> bar <span>baz</span>1471 </template>1472 </div>1473 `1474 },1475 res => {1476 expect(res).toContain(1477 `<div data-server-rendered="true"><span>foo</span> <span>foo</span> bar <span>baz</span></div>`1478 )1479 done()1480 }1481 )1482 })14831484 _it('template v-for', done => {1485 renderVmWithOptions(1486 {1487 template: `1488 <div>1489 <span>foo</span>1490 <template v-for="i in 2">1491 <span>{{ i }}</span><span>bar</span>1492 </template>1493 </div>1494 `1495 },1496 res => {1497 expect(res).toContain(1498 `<div data-server-rendered="true"><span>foo</span> <span>1</span><span>bar</span><span>2</span><span>bar</span></div>`1499 )1500 done()1501 }1502 )1503 })15041505 _it('with inheritAttrs: false + $attrs', done => {1506 renderVmWithOptions(1507 {1508 template: `<foo id="a"/>`,1509 components: {1510 foo: {1511 inheritAttrs: false,1512 template: `<div><div v-bind="$attrs"></div></div>`1513 }1514 }1515 },1516 res => {1517 expect(res).toBe(1518 `<div data-server-rendered="true"><div id="a"></div></div>`1519 )1520 done()1521 }1522 )1523 })15241525 _it('should escape static strings', done => {1526 renderVmWithOptions(1527 {1528 template: `<div><foo></div>`1529 },1530 res => {1531 expect(res).toBe(`<div data-server-rendered="true"><foo></div>`)1532 done()1533 }1534 )1535 })15361537 _it('should not cache computed properties', done => {1538 renderVmWithOptions(1539 {1540 template: `<div>{{ foo }}</div>`,1541 data: () => ({ bar: 1 }),1542 computed: {1543 foo() {1544 return this.bar + 11545 }1546 },1547 created() {1548 this.foo // access1549 this.bar++ // trigger change1550 }1551 },1552 res => {1553 expect(res).toBe(`<div data-server-rendered="true">3</div>`)1554 done()1555 }1556 )1557 })15581559 // #89771560 _it('should call computed properties with vm as first argument', done => {1561 renderToString(1562 new Vue({1563 data: {1564 firstName: 'Evan',1565 lastName: 'You'1566 },1567 computed: {1568 fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`1569 },1570 template: '<div>{{ fullName }}</div>'1571 }),1572 (err, result) => {1573 expect(err).toBeNull()1574 expect(result).toContain(1575 '<div data-server-rendered="true">Evan You</div>'1576 )1577 done()1578 }1579 )1580 })15811582 _it('return Promise', async () => {1583 await renderToString(1584 new Vue({1585 template: `<div>{{ foo }}</div>`,1586 data: { foo: 'bar' }1587 })1588 )!.then(res => {1589 expect(res).toBe(`<div data-server-rendered="true">bar</div>`)1590 })1591 })15921593 _it('return Promise (error)', async () => {1594 await renderToString(1595 new Vue({1596 render() {1597 throw new Error('foobar')1598 }1599 })1600 )!.catch(err => {1601 expect('foobar').toHaveBeenWarned()1602 expect(err.toString()).toContain(`foobar`)1603 })1604 })16051606 _it('should catch template compilation error', done => {1607 renderToString(1608 new Vue({1609 template: `<div></div><div></div>`1610 }),1611 err => {1612 expect(err.toString()).toContain(1613 'Component template should contain exactly one root element'1614 )1615 done()1616 }1617 )1618 })16191620 // #69071621 _it('should not optimize root if conditions', done => {1622 renderVmWithOptions(1623 {1624 data: { foo: 123 },1625 template: `<input :type="'text'" v-model="foo">`1626 },1627 res => {1628 expect(res).toBe(1629 `<input type="text" data-server-rendered="true" value="123">`1630 )1631 done()1632 }1633 )1634 })16351636 _it('render muted properly', done => {1637 renderVmWithOptions(1638 {1639 template: '<video muted></video>'1640 },1641 result => {1642 expect(result).toContain(1643 '<video muted="muted" data-server-rendered="true"></video>'1644 )1645 done()1646 }1647 )1648 })16491650 _it('render v-model with textarea', done => {1651 renderVmWithOptions(1652 {1653 data: { foo: 'bar' },1654 template: '<div><textarea v-model="foo"></textarea></div>'1655 },1656 result => {1657 expect(result).toContain('<textarea>bar</textarea>')1658 done()1659 }1660 )1661 })16621663 _it('render v-model with textarea (non-optimized)', done => {1664 renderVmWithOptions(1665 {1666 render(h) {1667 return h('textarea', {1668 domProps: {1669 value: 'foo'1670 }1671 })1672 }1673 },1674 result => {1675 expect(result).toContain(1676 '<textarea data-server-rendered="true">foo</textarea>'1677 )1678 done()1679 }1680 )1681 })16821683 _it('render v-model with <select> (value binding)', done => {1684 renderVmWithOptions(1685 {1686 data: {1687 selected: 2,1688 options: [1689 { id: 1, label: 'one' },1690 { id: 2, label: 'two' }1691 ]1692 },1693 template: `1694 <div>1695 <select v-model="selected">1696 <option v-for="o in options" :value="o.id">{{ o.label }}</option>1697 </select>1698 </div>1699 `1700 },1701 result => {1702 expect(result).toContain(1703 '<select>' +1704 '<option value="1">one</option>' +1705 '<option selected="selected" value="2">two</option>' +1706 '</select>'1707 )1708 done()1709 }1710 )1711 })17121713 _it('render v-model with <select> (static value)', done => {1714 renderVmWithOptions(1715 {1716 data: {1717 selected: 21718 },1719 template: `1720 <div>1721 <select v-model="selected">1722 <option value="1">one</option>1723 <option value="2">two</option>1724 </select>1725 </div>1726 `1727 },1728 result => {1729 expect(result).toContain(1730 '<select>' +1731 '<option value="1">one</option> ' +1732 '<option value="2" selected="selected">two</option>' +1733 '</select>'1734 )1735 done()1736 }1737 )1738 })17391740 _it('render v-model with <select> (text as value)', done => {1741 renderVmWithOptions(1742 {1743 data: {1744 selected: 2,1745 options: [1746 { id: 1, label: 'one' },1747 { id: 2, label: 'two' }1748 ]1749 },1750 template: `1751 <div>1752 <select v-model="selected">1753 <option v-for="o in options">{{ o.id }}</option>1754 </select>1755 </div>1756 `1757 },1758 result => {1759 expect(result).toContain(1760 '<select>' +1761 '<option>1</option>' +1762 '<option selected="selected">2</option>' +1763 '</select>'1764 )1765 done()1766 }1767 )1768 })17691770 // #72231771 _it('should not double escape attribute values', done => {1772 renderVmWithOptions(1773 {1774 template: `1775 <div>1776 <div id="a\nb"></div>1777 </div>1778 `1779 },1780 result => {1781 expect(result).toContain(`<div id="a\nb"></div>`)1782 done()1783 }1784 )1785 })17861787 // #78591788 _it('should not double escape class values', done => {1789 renderVmWithOptions(1790 {1791 template: `1792 <div>1793 <div class="a\nb"></div>1794 </div>1795 `1796 },1797 result => {1798 expect(result).toContain(`<div class="a b"></div>`)1799 done()1800 }1801 )1802 })18031804 _it('should expose ssr helpers on functional context', done => {1805 let called = false1806 renderVmWithOptions(1807 {1808 template: `<div><foo/></div>`,1809 components: {1810 foo: {1811 functional: true,1812 render(h, ctx) {1813 expect(ctx._ssrNode).toBeTruthy()1814 called = true1815 }1816 }1817 }1818 },1819 () => {1820 expect(called).toBe(true)1821 done()1822 }1823 )1824 })18251826 _it('should support serverPrefetch option', done => {1827 renderVmWithOptions(1828 {1829 template: `1830 <div>{{ count }}</div>1831 `,1832 data: {1833 count: 01834 },1835 serverPrefetch() {1836 return new Promise<void>(resolve => {1837 setTimeout(() => {1838 this.count = 421839 resolve()1840 }, 1)1841 })1842 }1843 },1844 result => {1845 expect(result).toContain('<div data-server-rendered="true">42</div>')1846 done()1847 }1848 )1849 })18501851 _it('should support serverPrefetch option (nested)', done => {1852 renderVmWithOptions(1853 {1854 template: `1855 <div>1856 <span>{{ count }}</span>1857 <nested-prefetch></nested-prefetch>1858 </div>1859 `,1860 data: {1861 count: 01862 },1863 serverPrefetch() {1864 return new Promise<void>(resolve => {1865 setTimeout(() => {1866 this.count = 421867 resolve()1868 }, 1)1869 })1870 },1871 components: {1872 nestedPrefetch: {1873 template: `1874 <div>{{ message }}</div>1875 `,1876 data() {1877 return {1878 message: ''1879 }1880 },1881 serverPrefetch() {1882 return new Promise<void>(resolve => {1883 setTimeout(() => {1884 this.message = 'vue.js'1885 resolve()1886 }, 1)1887 })1888 }1889 }1890 }1891 },1892 result => {1893 expect(result).toContain(1894 '<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>'1895 )1896 done()1897 }1898 )1899 })19001901 _it('should support serverPrefetch option (nested async)', done => {1902 renderVmWithOptions(1903 {1904 template: `1905 <div>1906 <span>{{ count }}</span>1907 <nested-prefetch></nested-prefetch>1908 </div>1909 `,1910 data: {1911 count: 01912 },1913 serverPrefetch() {1914 return new Promise<void>(resolve => {1915 setTimeout(() => {1916 this.count = 421917 resolve()1918 }, 1)1919 })1920 },1921 components: {1922 nestedPrefetch(resolve) {1923 resolve({1924 template: `1925 <div>{{ message }}</div>1926 `,1927 data() {1928 return {1929 message: ''1930 }1931 },1932 serverPrefetch() {1933 return new Promise<void>(resolve => {1934 setTimeout(() => {1935 this.message = 'vue.js'1936 resolve()1937 }, 1)1938 })1939 }1940 })1941 }1942 }1943 },1944 result => {1945 expect(result).toContain(1946 '<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>'1947 )1948 done()1949 }1950 )1951 })19521953 _it('should merge serverPrefetch option', done => {1954 const mixin = {1955 data: {1956 message: ''1957 },1958 serverPrefetch() {1959 return new Promise<void>(resolve => {1960 setTimeout(() => {1961 this.message = 'vue.js'1962 resolve()1963 }, 1)1964 })1965 }1966 }1967 renderVmWithOptions(1968 {1969 mixins: [mixin],1970 template: `1971 <div>1972 <span>{{ count }}</span>1973 <div>{{ message }}</div>1974 </div>1975 `,1976 data: {1977 count: 01978 },1979 serverPrefetch() {1980 return new Promise<void>(resolve => {1981 setTimeout(() => {1982 this.count = 421983 resolve()1984 }, 1)1985 })1986 }1987 },1988 result => {1989 expect(result).toContain(1990 '<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>'1991 )1992 done()1993 }1994 )1995 })19961997 _it(1998 `should skip serverPrefetch option that doesn't return a promise`,1999 done => {2000 renderVmWithOptions(
Findings
✓ No findings reported for this file.