packages/server-renderer/test/ssr-string.spec.ts TYPESCRIPT 2,167 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,167.
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 &lt;span&gt;rendering&lt;/span&gt;</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">&lt;span&gt;foo&lt;/span&gt;</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>&lt;span&gt;foo&lt;/span&gt;</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>&lt;foo&gt;</div>`1529      },1530      res => {1531        expect(res).toBe(`<div data-server-rendered="true">&lt;foo&gt;</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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.